From e345e18613bee6e697e14dac57c691502a026be2 Mon Sep 17 00:00:00 2001 From: frostime Date: Fri, 6 Sep 2024 19:50:50 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A8=20refactor:=20=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=20dir=20=E7=AC=A6=E5=8F=B7=E9=93=BE=E6=8E=A5=EF=BC=9B=E5=B9=B6?= =?UTF-8?q?=E7=BC=96=E5=86=99=20ps=20=E8=84=9A=E6=9C=AC=E7=94=A8=E4=BA=8E?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=20windows=20=E7=AE=A1=E7=90=86=E5=91=98?= =?UTF-8?q?=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/siyuan-note/siyuan/issues/12399 --- README.md | 54 ++++++----- README_zh_CN.md | 39 +++++--- package.json | 3 +- scripts/elevate.ps1 | 24 +++++ scripts/make_dev_link.js | 188 +++++++------------------------------ scripts/make_install.js | 196 +++++++-------------------------------- scripts/utils.js | 182 ++++++++++++++++++++++++++++++++++++ 7 files changed, 329 insertions(+), 357 deletions(-) create mode 100644 scripts/elevate.ps1 create mode 100644 scripts/utils.js diff --git a/README.md b/README.md index a675463..f0b2a1c 100644 --- a/README.md +++ b/README.md @@ -20,15 +20,21 @@ ## Get started -1. Make a copy of this repo as a template with the `Use this template` button, please note that the repo name must be the same as the plugin name, the default branch must be `main` +1. Use the Use this template button to make a copy of this repo as a template. Note that the repository name should match the plugin name, and the default branch must be `main`. +2. Clone your repository to the local development folder. + * Note: Unlike `plugin-sample`, this example does not recommend directly downloading the code to `{workspace}/data/plugins/`. +3. Install [NodeJS](https://nodejs.org/en/download) and [pnpm](https://pnpm.io/installation), then run `pnpm i` in the development folder to install the required dependencies. +4. Run the `pnpm run make-link` command to create a symbolic link (Windows developers, please refer to the "make-link on Windows" section below). +5. Execute `pnpm run dev` for real-time compilation. +6. Open the marketplace in SiYuan and enable the plugin in the download tab. -2. Clone your repo to a local development folder at any place - - Notice: we **don't recommand** you to place the folder under your `{workspace}/data/plugins/` folder. +### Setting the Target Directory for the make-link Command -3. Install NodeJS and pnpm, then run pnpm i in the command line under your repo folder -4. **Auto create development symbolic links** - - Make sure that SiYuan is running - - Run `pnpm run make-link`, the script will detect all the siyuan workspace, please select the targe workspace and the script will automatically create the symbolic link under the `{workspace}/data/plugins/` folder +The `make-link` command creates a symbolic link that binds your `dev` directory to the SiYuan plugin directory. You can configure the target SiYuan workspace and create the symbolic link in three ways: + +1. **Select Workspace** + - Open SiYuan, ensure the SiYuan kernel is running. + - Run `pnpm run make-link`, the script will automatically detect all SiYuan workspaces, please manually enter the number to select the workspace. ```bash >>> pnpm run make-link > plugin-sample-vite-svelte@0.0.3 make-link H:\SrcCode\开源项目\plugin-sample-vite-svelte @@ -42,22 +48,24 @@ Got target directory: H:\Media\SiYuan/data/plugins Done! Created symlink H:\Media\SiYuan/data/plugins/plugin-sample-vite-svelte ``` -4. **Manually create development symbolic links** - - Open `./scripts/make_dev_link.js` file, set `targetDir` to your SiYuan plugin directory `/data/plugins` - - Run `pnpm run make-link`, succeed if following message is shown: - ```bash - >>> pnpm run make-link - > plugin-sample-vite-svelte@0.0.1 make-link H:\SrcCode\plugin-sample-vite-svelte - > node ./scripts/make_dev_link.js - - Done! Created symlink H:/SiYuanDevSpace/data/plugins/plugin-sample-vite-svelte - ``` -5. **Create development symbolic links by using environment variable** - - You can set environment variable `SIYUAN_PLUGIN_DIR` as `/data/plugins` -6. Execute pnpm run dev for real-time compilation -7. Open SiYuan marketplace and enable plugin in downloaded tab - -> Notice: as the `make-link` script rely on the `fetch` function, please **ensure that at least version v18 of nodejs is installed** if you want to use make-link script. +2. **Manually Configure Target Directory** + - Open the `./scripts/make_dev_link.js` file, change `targetDir` to the SiYuan plugin directory `/data/plugins`. + - Run the `pnpm run make-link` command. If you see a message similar to the one below, it indicates successful creation: + +3. **Set Environment Variable to Create Symbolic Link** + - Set the system environment variable `SIYUAN_PLUGIN_DIR` to the path `workspace/data/plugins`. + +### make-link on Windows + +Due to SiYuan upgrading to Go 1.23, the old version of junction links cannot be recognized normally on Windows, so it has been changed to create `dir` symbolic links. + +> https://github.com/siyuan-note/siyuan/issues/12399 + +However, creating directory symbolic links on Windows using NodeJs may require administrator privileges. You have the following options: + +1. Run `pnpm run make-link` in a command line with administrator privileges. +2. Configure Windows settings, enable developer mode in [System Settings - Update & Security - Developer Mode] then run `pnpm run make-link`. +3. Run `pnpm run make-link-win`, this command will use a PowerShell script to request administrator privileges, requiring the system to enable PowerShell script execution permissions. ## I18n diff --git a/README_zh_CN.md b/README_zh_CN.md index d0f153c..e60f454 100644 --- a/README_zh_CN.md +++ b/README_zh_CN.md @@ -18,11 +18,19 @@ ## 开始 -1. 通过 Use this template 按钮将该库文件复制到你自己的库中,请注意库名必须和插件名称一致,默认分支必须为 `main` +1. 通过 Use this template 按钮将该库文件复制到你自己的库中,请注意库名和插件名称一致,默认分支必须为 `main` 2. 将你的库克隆到本地开发文件夹中 * 注意: 同 `plugin-sample` 不同, 本样例并不推荐直接把代码下载到 `{workspace}/data/plugins/` 3. 安装 [NodeJS](https://nodejs.org/en/download) 和 [pnpm](https://pnpm.io/installation),然后在开发文件夹下执行 `pnpm i` 安装所需要的依赖 -3. **自动创建符号链接** +4. 运行 `pnpm run make-link` 命令创建符号链接 (Windows 下的开发者请参阅下方「Windows 下的 make-link」小节) +5. 执行 `pnpm run dev` 进行实时编译 +6. 在思源中打开集市并在下载选项卡中启用插件 + +### 设置 make-link 命令的目标目录 + +make-link 命令会创建符号链接将你的 `dev` 目录绑定到思源的插件目录下。你可以有三种方式来配置目标的思源工作空间并创建符号链接: + +1. **选择工作空间** - 打开思源笔记, 确保思源内核正在运行 - 运行 `pnpm run make-link`, 脚本会自动检测所有思源的工作空间, 请在命令行中手动输入序号以选择工作空间 ```bash @@ -38,23 +46,26 @@ Got target directory: H:\Media\SiYuan/data/plugins Done! Created symlink H:\Media\SiYuan/data/plugins/plugin-sample-vite-svelte ``` -4. **手动创建符号链接** +2. **手动配置目标目录** - 打开 `./scripts/make_dev_link.js` 文件,更改 `targetDir` 为思源的插件目录 `/data/plugins` - 运行 `pnpm run make-link` 命令, 如果看到类似以下的消息,说明创建成功: - ```bash - ❯❯❯ pnpm run make-link - > plugin-sample-vite-svelte@0.0.1 make-link H:\SrcCode\plugin-sample-vite-svelte - > node ./scripts/make_dev_link.js - Done! Created symlink H:/SiYuanDevSpace/data/plugins/plugin-sample-vite-svelte - ``` -5. **设置环境变量创建符号链接** - - 你也可以设置系统的环境变量 `SIYUAN_PLUGIN_DIR` 为 `/data/plugins` 的路径 -6. 执行 `pnpm run dev` 进行实时编译 -7. 在思源中打开集市并在下载选项卡中启用插件 +3. **设置环境变量创建符号链接** + - 设置系统的环境变量 `SIYUAN_PLUGIN_DIR` 为 `工作空间/data/plugins` 的路径 + + +### Windows 下的 make-link + +由于思源升级了 Go 1.23,旧版创建的 junction link 在 windows 下无法被正常识别,故而改为创建 `dir` 符号链接。 + +> https://github.com/siyuan-note/siyuan/issues/12399 + +不过 Windows 下使用 NodeJs 创建目录符号链接可能需要管理员权限,你可以有如下几种选择: -> 注意由于使用的 make-link 脚本依赖于 `fetch`,所以如果想要使用 make-link **请保证至少安装 v18 版本的 nodejs** +1. 在具有管理员权限的命令行中运行 `pnpm run make-link` +2. 配置 Windows 设置,在 [系统设置-更新与安全-开发者模式] 中启用开发者模式,然后再运行 `pnpm run make-link` +3. 运行 `pnpm run make-link-win`,该命令会使用一个 powershell 脚本来寻求管理员权限,需要在系统中开启 PowerShell 脚本执行权限 ## 国际化 diff --git a/package.json b/package.json index fd49b9c..971fd74 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "author": "frostime", "license": "MIT", "scripts": { - "make-link": "node --no-warnings ./scripts/make_dev_link.js", + "make-link": "node --no-warnings ./scripts/make_dev_link.js", + "make-link-win": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File ./scripts/elevate.ps1 -scriptPath ./scripts/make_dev_link.js", "dev": "vite build --watch", "build": "vite build", "make-install": "vite build && node --no-warnings ./scripts/make_install.js" diff --git a/scripts/elevate.ps1 b/scripts/elevate.ps1 new file mode 100644 index 0000000..151b8ba --- /dev/null +++ b/scripts/elevate.ps1 @@ -0,0 +1,24 @@ +# Copyright (c) 2024 by frostime. All Rights Reserved. +# @Author : frostime +# @Date : 2024-09-06 19:15:53 +# @FilePath : /scripts/elevate.ps1 +# @LastEditTime : 2024-09-06 19:39:13 +# @Description : Force to elevate the script to admin privilege. + +param ( + [string]$scriptPath +) + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition +$projectDir = Split-Path -Parent $scriptDir + +if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { + $args = "-NoProfile -ExecutionPolicy Bypass -File `"" + $MyInvocation.MyCommand.Path + "`" -scriptPath `"" + $scriptPath + "`"" + Start-Process powershell.exe -Verb RunAs -ArgumentList $args -WorkingDirectory $projectDir + exit +} + +Set-Location -Path $projectDir +& node $scriptPath + +pause diff --git a/scripts/make_dev_link.js b/scripts/make_dev_link.js index 845d298..2be3d2b 100644 --- a/scripts/make_dev_link.js +++ b/scripts/make_dev_link.js @@ -1,106 +1,29 @@ +/* + * Copyright (c) 2024 by frostime. All Rights Reserved. + * @Author : frostime + * @Date : 2023-07-15 15:31:31 + * @FilePath : /scripts/make_dev_link.js + * @LastEditTime : 2024-09-06 18:13:53 + * @Description : + */ +// make_dev_link.js import fs from 'fs'; -import http from 'node:http'; -import readline from 'node:readline'; +import { log, error, getSiYuanDir, chooseTarget, getThisPluginName, makeSymbolicLink } from './utils.js'; - -//************************************ Write you dir here ************************************ - -//Please write the "workspace/data/plugins" directory here -//请在这里填写你的 "workspace/data/plugins" 目录 let targetDir = ''; -//Like this -// let targetDir = `H:\\SiYuanDevSpace\\data\\plugins`; -//******************************************************************************************** - -const log = (info) => console.log(`\x1B[36m%s\x1B[0m`, info); -const error = (info) => console.log(`\x1B[31m%s\x1B[0m`, info); - -let POST_HEADER = { - // "Authorization": `Token ${token}`, - "Content-Type": "application/json", -} - -async function myfetch(url, options) { - //使用 http 模块,从而兼容那些不支持 fetch 的 nodejs 版本 - return new Promise((resolve, reject) => { - let req = http.request(url, options, (res) => { - let data = ''; - res.on('data', (chunk) => { - data += chunk; - }); - res.on('end', () => { - resolve({ - ok: true, - status: res.statusCode, - json: () => JSON.parse(data) - }); - }); - }); - req.on('error', (e) => { - reject(e); - }); - req.end(); - }); -} - -async function getSiYuanDir() { - let url = 'http://127.0.0.1:6806/api/system/getWorkspaces'; - let conf = {}; - try { - let response = await myfetch(url, { - method: 'POST', - headers: POST_HEADER - }); - if (response.ok) { - conf = await response.json(); - } else { - error(`\tHTTP-Error: ${response.status}`); - return null; - } - } catch (e) { - error(`\tError: ${e}`); - error("\tPlease make sure SiYuan is running!!!"); - return null; - } - return conf.data; -} - -async function chooseTarget(workspaces) { - let count = workspaces.length; - log(`>>> Got ${count} SiYuan ${count > 1 ? 'workspaces' : 'workspace'}`) - for (let i = 0; i < workspaces.length; i++) { - log(`\t[${i}] ${workspaces[i].path}`); - } - - if (count == 1) { - return `${workspaces[0].path}/data/plugins`; - } else { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout - }); - let index = await new Promise((resolve, reject) => { - rl.question(`\tPlease select a workspace[0-${count-1}]: `, (answer) => { - resolve(answer); - }); - }); - rl.close(); - return `${workspaces[index].path}/data/plugins`; - } -} - -log('>>> Try to visit constant "targetDir" in make_dev_link.js...') +/** + * 1. Get the parent directory to install the plugin + */ +log('>>> Try to visit constant "targetDir" in make_dev_link.js...'); if (targetDir === '') { - log('>>> Constant "targetDir" is empty, try to get SiYuan directory automatically....') + log('>>> Constant "targetDir" is empty, try to get SiYuan directory automatically....'); let res = await getSiYuanDir(); - - if (res === null || res === undefined || res.length === 0) { - log('>>> Can not get SiYuan directory automatically, try to visit environment variable "SIYUAN_PLUGIN_DIR"....'); - // console.log(process.env) + if (!res || res.length === 0) { + log('>>> Can not get SiYuan directory automatically, try to visit environment variable "SIYUAN_PLUGIN_DIR"....'); let env = process.env?.SIYUAN_PLUGIN_DIR; - if (env !== undefined && env !== null && env !== '') { + if (env) { targetDir = env; log(`\tGot target directory from environment variable "SIYUAN_PLUGIN_DIR": ${targetDir}`); } else { @@ -111,76 +34,33 @@ if (targetDir === '') { targetDir = await chooseTarget(res); } - log(`>>> Successfully got target directory: ${targetDir}`); } - -//Check if (!fs.existsSync(targetDir)) { - error(`Failed! plugin directory not exists: "${targetDir}"`); - error(`Please set the plugin directory in scripts/make_dev_link.js`); + error(`Failed! Plugin directory not exists: "${targetDir}"`); + error('Please set the plugin directory in scripts/make_dev_link.js'); process.exit(1); } - -//check if plugin.json exists -if (!fs.existsSync('./plugin.json')) { - //change dir to parent - process.chdir('../'); - if (!fs.existsSync('./plugin.json')) { - error('Failed! plugin.json not found'); - process.exit(1); - } -} - -//load plugin.json -const plugin = JSON.parse(fs.readFileSync('./plugin.json', 'utf8')); -const name = plugin?.name; -if (!name || name === '') { - error('Failed! Please set plugin name in plugin.json'); - process.exit(1); -} - -//dev directory +/** + * 2. The dev directory, which contains the compiled plugin code + */ const devDir = `${process.cwd()}/dev`; -//mkdir if not exists if (!fs.existsSync(devDir)) { fs.mkdirSync(devDir); } -function cmpPath(path1, path2) { - path1 = path1.replace(/\\/g, '/'); - path2 = path2.replace(/\\/g, '/'); - // sepertor at tail - if (path1[path1.length - 1] !== '/') { - path1 += '/'; - } - if (path2[path2.length - 1] !== '/') { - path2 += '/'; - } - return path1 === path2; -} - -const targetPath = `${targetDir}/${name}`; -//如果已经存在,就退出 -if (fs.existsSync(targetPath)) { - let isSymbol = fs.lstatSync(targetPath).isSymbolicLink(); - - if (isSymbol) { - let srcPath = fs.readlinkSync(targetPath); - - if (cmpPath(srcPath, devDir)) { - log(`Good! ${targetPath} is already linked to ${devDir}`); - } else { - error(`Error! Already exists symbolic link ${targetPath}\nBut it links to ${srcPath}`); - } - } else { - error(`Failed! ${targetPath} already exists and is not a symbolic link`); - } -} else { - //创建软链接 - fs.symlinkSync(devDir, targetPath, 'junction'); - log(`Done! Created symlink ${targetPath}`); +/** + * 3. The target directory to make symbolic link to dev directory + */ +const name = getThisPluginName(); +if (name === null) { + process.exit(1); } +const targetPath = `${targetDir}/${name}`; +/** + * 4. Make symbolic link + */ +makeSymbolicLink(devDir, targetPath); diff --git a/scripts/make_install.js b/scripts/make_install.js index 9c2cd9b..cb2a4ac 100644 --- a/scripts/make_install.js +++ b/scripts/make_install.js @@ -1,191 +1,57 @@ +/* + * Copyright (c) 2024 by frostime. All Rights Reserved. + * @Author : frostime + * @Date : 2024-03-28 20:03:59 + * @FilePath : /scripts/make_install.js + * @LastEditTime : 2024-09-06 18:08:19 + * @Description : + */ +// make_install.js import fs from 'fs'; -import path from 'path'; -import http from 'node:http'; -import readline from 'node:readline'; +import { log, error, getSiYuanDir, chooseTarget, copyDirectory, getThisPluginName } from './utils.js'; +let targetDir = ''; -//************************************ Write you dir here ************************************ - -let targetDir = ''; // the target directory of the plugin, '*/data/plugin' -//******************************************************************************************** - -const log = (info) => console.log(`\x1B[36m%s\x1B[0m`, info); -const error = (info) => console.log(`\x1B[31m%s\x1B[0m`, info); - -let POST_HEADER = { - // "Authorization": `Token ${token}`, - "Content-Type": "application/json", -} - -async function myfetch(url, options) { - //使用 http 模块,从而兼容那些不支持 fetch 的 nodejs 版本 - return new Promise((resolve, reject) => { - let req = http.request(url, options, (res) => { - let data = ''; - res.on('data', (chunk) => { - data += chunk; - }); - res.on('end', () => { - resolve({ - ok: true, - status: res.statusCode, - json: () => JSON.parse(data) - }); - }); - }); - req.on('error', (e) => { - reject(e); - }); - req.end(); - }); -} - -async function getSiYuanDir() { - let url = 'http://127.0.0.1:6806/api/system/getWorkspaces'; - let conf = {}; - try { - let response = await myfetch(url, { - method: 'POST', - headers: POST_HEADER - }); - if (response.ok) { - conf = await response.json(); - } else { - error(`\tHTTP-Error: ${response.status}`); - return null; - } - } catch (e) { - error(`\tError: ${e}`); - error("\tPlease make sure SiYuan is running!!!"); - return null; - } - return conf.data; -} - -async function chooseTarget(workspaces) { - let count = workspaces.length; - log(`>>> Got ${count} SiYuan ${count > 1 ? 'workspaces' : 'workspace'}`) - for (let i = 0; i < workspaces.length; i++) { - log(`\t[${i}] ${workspaces[i].path}`); - } - - if (count == 1) { - return `${workspaces[0].path}/data/plugins`; - } else { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout - }); - let index = await new Promise((resolve, reject) => { - rl.question(`\tPlease select a workspace[0-${count-1}]: `, (answer) => { - resolve(answer); - }); - }); - rl.close(); - return `${workspaces[index].path}/data/plugins`; - } -} - -log('>>> Try to visit constant "targetDir" in make_install.js...') - +/** + * 1. Get the parent directory to install the plugin + */ +log('>>> Try to visit constant "targetDir" in make_install.js...'); if (targetDir === '') { - log('>>> Constant "targetDir" is empty, try to get SiYuan directory automatically....') + log('>>> Constant "targetDir" is empty, try to get SiYuan directory automatically....'); let res = await getSiYuanDir(); - + if (res === null || res === undefined || res.length === 0) { error('>>> Can not get SiYuan directory automatically'); - process.exit(1); } else { targetDir = await chooseTarget(res); } - log(`>>> Successfully got target directory: ${targetDir}`); } - -//Check if (!fs.existsSync(targetDir)) { - error(`Failed! plugin directory not exists: "${targetDir}"`); - error(`Please set the plugin directory in scripts/make_install.js`); - process.exit(1); -} - - -//check if plugin.json exists -if (!fs.existsSync('./plugin.json')) { - //change dir to parent - process.chdir('../'); - if (!fs.existsSync('./plugin.json')) { - error('Failed! plugin.json not found'); - process.exit(1); - } -} - -//load plugin.json -const plugin = JSON.parse(fs.readFileSync('./plugin.json', 'utf8')); -const name = plugin?.name; -if (!name || name === '') { - error('Failed! Please set plugin name in plugin.json'); + error(`Failed! Plugin directory not exists: "${targetDir}"`); + error('Please set the plugin directory in scripts/make_install.js'); process.exit(1); } +/** + * 2. The dist directory, which contains the compiled plugin code + */ const distDir = `${process.cwd()}/dist`; -//mkdir if not exists if (!fs.existsSync(distDir)) { fs.mkdirSync(distDir); } -function cmpPath(path1, path2) { - path1 = path1.replace(/\\/g, '/'); - path2 = path2.replace(/\\/g, '/'); - // sepertor at tail - if (path1[path1.length - 1] !== '/') { - path1 += '/'; - } - if (path2[path2.length - 1] !== '/') { - path2 += '/'; - } - return path1 === path2; +/** + * 3. The target directory to install the plugin + */ +const name = getThisPluginName(); +if (name === null) { + process.exit(1); } - const targetPath = `${targetDir}/${name}`; - -function copyDirectory(srcDir, dstDir) { - if (!fs.existsSync(dstDir)) { - fs.mkdirSync(dstDir); - log(`Created directory ${dstDir}`); - } - //将 distDir 下的所有文件复制到 targetPath - fs.readdir(srcDir, { withFileTypes: true }, (err, files) => { - if (err) { - error('Error reading source directory:', err); - return; - } - - // 遍历源目录中的所有文件和子目录 - files.forEach((file) => { - const src = path.join(srcDir, file.name); - const dst = path.join(dstDir, file.name); - - // 判断当前项是文件还是目录 - if (file.isDirectory()) { - // 如果是目录,则递归调用复制函数复制子目录 - copyDirectory(src, dst); - } else { - // 如果是文件,则复制文件到目标目录 - fs.copyFile(src, dst, (err) => { - if (err) { - error('Error copying file:' + err); - } else { - log(`Copied file: ${src} --> ${dst}`); - } - }); - } - }); - log(`Copied ${distDir} to ${targetPath}`); - }); -} +/** + * 4. Copy the compiled plugin code to the target directory + */ copyDirectory(distDir, targetPath); - - diff --git a/scripts/utils.js b/scripts/utils.js new file mode 100644 index 0000000..210b6b1 --- /dev/null +++ b/scripts/utils.js @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2024 by frostime. All Rights Reserved. + * @Author : frostime + * @Date : 2024-09-06 17:42:57 + * @FilePath : /scripts/utils.js + * @LastEditTime : 2024-09-06 19:23:12 + * @Description : + */ +// common.js +import fs from 'fs'; +import path from 'node:path'; +import http from 'node:http'; +import readline from 'node:readline'; + +// Logging functions +export const log = (info) => console.log(`\x1B[36m%s\x1B[0m`, info); +export const error = (info) => console.log(`\x1B[31m%s\x1B[0m`, info); + +// HTTP POST headers +export const POST_HEADER = { + "Content-Type": "application/json", +}; + +// Fetch function compatible with older Node.js versions +export async function myfetch(url, options) { + return new Promise((resolve, reject) => { + let req = http.request(url, options, (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => { + resolve({ + ok: true, + status: res.statusCode, + json: () => JSON.parse(data) + }); + }); + }); + req.on('error', (e) => { + reject(e); + }); + req.end(); + }); +} + +/** + * Fetch SiYuan workspaces from port 6806 + * @returns {Promise} + */ +export async function getSiYuanDir() { + let url = 'http://127.0.0.1:6806/api/system/getWorkspaces'; + let conf = {}; + try { + let response = await myfetch(url, { + method: 'POST', + headers: POST_HEADER + }); + if (response.ok) { + conf = await response.json(); + } else { + error(`\tHTTP-Error: ${response.status}`); + return null; + } + } catch (e) { + error(`\tError: ${e}`); + error("\tPlease make sure SiYuan is running!!!"); + return null; + } + return conf?.data; // 保持原始返回值 +} + +/** + * Choose target workspace + * @param {{path: string}[]} workspaces + * @returns {string} The path of the selected workspace + */ +export async function chooseTarget(workspaces) { + let count = workspaces.length; + log(`>>> Got ${count} SiYuan ${count > 1 ? 'workspaces' : 'workspace'}`); + workspaces.forEach((workspace, i) => { + log(`\t[${i}] ${workspace.path}`); + }); + + if (count === 1) { + return `${workspaces[0].path}/data/plugins`; + } else { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + let index = await new Promise((resolve) => { + rl.question(`\tPlease select a workspace[0-${count - 1}]: `, (answer) => { + resolve(answer); + }); + }); + rl.close(); + return `${workspaces[index].path}/data/plugins`; + } +} + +/** + * Check if two paths are the same + * @param {string} path1 + * @param {string} path2 + * @returns {boolean} + */ +export function cmpPath(path1, path2) { + path1 = path1.replace(/\\/g, '/'); + path2 = path2.replace(/\\/g, '/'); + if (path1[path1.length - 1] !== '/') { + path1 += '/'; + } + if (path2[path2.length - 1] !== '/') { + path2 += '/'; + } + return path1 === path2; +} + +export function getThisPluginName() { + if (!fs.existsSync('./plugin.json')) { + process.chdir('../'); + if (!fs.existsSync('./plugin.json')) { + error('Failed! plugin.json not found'); + return null; + } + } + + const plugin = JSON.parse(fs.readFileSync('./plugin.json', 'utf8')); + const name = plugin?.name; + if (!name) { + error('Failed! Please set plugin name in plugin.json'); + return null; + } + + return name; +} + +export function copyDirectory(srcDir, dstDir) { + if (!fs.existsSync(dstDir)) { + fs.mkdirSync(dstDir); + log(`Created directory ${dstDir}`); + } + + fs.readdirSync(srcDir, { withFileTypes: true }).forEach((file) => { + const src = path.join(srcDir, file.name); + const dst = path.join(dstDir, file.name); + + if (file.isDirectory()) { + copyDirectory(src, dst); + } else { + fs.copyFileSync(src, dst); + log(`Copied file: ${src} --> ${dst}`); + } + }); + log(`All files copied!`); +} + + +export function makeSymbolicLink(srcPath, targetPath) { + if (!fs.existsSync(targetPath)) { + // fs.symlinkSync(srcPath, targetPath, 'junction'); + //Go 1.23 no longer supports junctions as symlinks + //Please refer to https://github.com/siyuan-note/siyuan/issues/12399 + fs.symlinkSync(srcPath, targetPath, 'dir'); + log(`Done! Created symlink ${targetPath}`); + return; + } + + //Check the existed target path + let isSymbol = fs.lstatSync(targetPath).isSymbolicLink(); + if (!isSymbol) { + error(`Failed! ${targetPath} already exists and is not a symbolic link`); + return; + } + let existedPath = fs.readlinkSync(targetPath); + if (cmpPath(existedPath, srcPath)) { + log(`Good! ${targetPath} is already linked to ${srcPath}`); + } else { + error(`Error! Already exists symbolic link ${targetPath}\nBut it links to ${existedPath}`); + } +}