From 0d76086064f8ac2a9a715a3457bc986616cb7b7a Mon Sep 17 00:00:00 2001 From: Northword <44738481+northword@users.noreply.github.com> Date: Wed, 15 May 2024 15:33:42 +0800 Subject: [PATCH] feat: publish development scripts as independent npm packages (#109) * feat!: use zotero-plugin-scaffold * fix: tsc error * docs: update readme and comment * fix: clean code, remove useless file * fix: update zotero-plugin-scaffold * fix: set esbuild target to ff115 * chore: clean and update deps * fix: Simplify config files and update dep * fix: update deps * chore: update renovate config * fix: default use proxy mode to install plugin * chore: bump scaffold to 0.0.19 resolve: #113 * fix: bump scaffold to 0.0.20 * fix: use new devtools hack --- .env.example | 29 ++++ .github/renovate.json | 11 +- .github/workflows/release.yml | 12 +- .gitignore | 3 +- README.md | 36 ++--- doc/README-zhCN.md | 81 +++++------ package.json | 58 ++------ scripts/build.mjs | 243 ------------------------------- scripts/scripts.mjs | 75 ---------- scripts/server.mjs | 87 ----------- scripts/start.mjs | 119 --------------- scripts/stop.mjs | 26 ---- scripts/update-template.json | 17 --- scripts/utils.mjs | 129 ---------------- scripts/zotero-cmd-template.json | 20 --- src/hooks.ts | 7 - update.json | 17 --- zotero-plugin.config.ts | 61 ++++++++ 18 files changed, 179 insertions(+), 852 deletions(-) create mode 100644 .env.example delete mode 100644 scripts/build.mjs delete mode 100644 scripts/scripts.mjs delete mode 100644 scripts/server.mjs delete mode 100644 scripts/start.mjs delete mode 100644 scripts/stop.mjs delete mode 100644 scripts/update-template.json delete mode 100644 scripts/utils.mjs delete mode 100644 scripts/zotero-cmd-template.json delete mode 100644 update.json create mode 100644 zotero-plugin.config.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9ea84ee --- /dev/null +++ b/.env.example @@ -0,0 +1,29 @@ +# Usage: +# Copy this file as `.env` and fill in the variables below as instructed. + +# If you are developing more than one plugin, you can store the bin path and +# profile path in the system environment variables, which can be omitted here. + +# The path of the Zotero binary file. +# The path delimiter should be escaped as `\\` for win32. +# The path is `*/Zotero.app/Contents/MacOS/zotero` for MacOS. +ZOTERO_PLUGIN_ZOTERO_BIN_PATH = /path/to/zotero.exe + +# The path of the profile used for development. +# Start the profile manager by `/path/to/zotero.exe -p` to create a profile for development. +# @see https://www.zotero.org/support/kb/profile_directory +ZOTERO_PLUGIN_PROFILE_PATH = /path/to/profile + +# The directory where the database is located. +# If this field is kept empty, Zotero will start with the default data. +# @see https://www.zotero.org/support/zotero_data +ZOTERO_PLUGIN_DATA_DIR = + +# Custom commands to kill Zotero processes. +# Commands for different platforms are already built into zotero-plugin, +# if the built-in commands are not suitable for your needs, please modify this variable. +# ZOTERO_PLUGIN_KILL_COMMAND = + +# GitHub Token +# For release-it auto create release and upload assets +# GITHUB_TOKEN = \ No newline at end of file diff --git a/.github/renovate.json b/.github/renovate.json index 1289413..db0b4f0 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -7,11 +7,18 @@ ":prConcurrentLimitNone", ":enableVulnerabilityAlerts", ":dependencyDashboard", - "schedule:weekends" + "group:allNonMajor", + "schedule:weekly" ], + "labels": ["dependencies"], "packageRules": [ { - "matchPackageNames": ["zotero-plugin-toolkit", "zotero-types"], + "matchPackageNames": [ + "zotero-plugin-toolkit", + "zotero-types", + "zotero-plugin-scaffold" + ], + "schedule": ["at any time"], "automerge": true } ], diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a57bde9..53b073f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,9 +29,19 @@ jobs: - name: Install deps run: npm install + - name: Build + run: | + npm run build + - name: Release to GitHub run: | - npm run release -- --no-increment --no-git --github.release --ci --VV + npm run release + # cp build/update.json update.json + # cp build/update-beta.json update-beta.json + # git add update.json + # git add update-beta.json + # git commit -m 'chore(publish): synchronizing `update.json`' + # git push sleep 1s - name: Notify release diff --git a/.gitignore b/.gitignore index 4c3136c..3168d8e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ package-lock.json pnpm-lock.yaml yarn.lock zotero-cmd.json -.DS_Store \ No newline at end of file +.DS_Store +.env \ No newline at end of file diff --git a/README.md b/README.md index 33367b2..b75c417 100644 --- a/README.md +++ b/README.md @@ -65,12 +65,9 @@ If you are using this repo, I recommended that you put the following badge on yo - Plugin develop/build/release workflow: - Automatically generate/update plugin id/version, update configrations, and set environment variables (`development` / `production`); - Automatically build and reload code in Zotero; - - Automatically release to GitHub (using [release-it](https://github.com/release-it/release-it)); + - Automatically release to GitHub; - Prettier and ES Lint integration. -> [!warning] -> The localization system is upgraded (`dtd` is deprecated and we do not use `.properties` anymore). Only supports Zotero 7.0.0-beta.12 or higher now. If you want to support Zotero 6, you may need to use `dtd`, `properties`, and `ftl` at the same time. See the staled branch `zotero6-bootstrap`. - ## Examples This repo provides examples for [zotero-plugin-toolkit](https://github.com/windingwind/zotero-plugin-toolkit) APIs. @@ -178,8 +175,6 @@ Activate with `Shift+P`. addonRef: "", // e.g. Element ID prefix addonInstance: "", // the plugin's root instance: Zotero.${addonInstance} prefsPrefix: "extensions.zotero.${addonRef}", // the prefix of prefs - releasePage: "", // URL to releases - updateJSON: "", // URL to update.json }, } ``` @@ -187,18 +182,20 @@ Activate with `Shift+P`. > [!warning] > Be careful to set the addonID and addonRef to avoid conflict. - If you need to host your XPI packages outside of GitHub, remove `releasePage` and add `updateLink` with the value set to your XPI download URL. + If you need to host your XPI packages outside of GitHub, moidify `updateURL` and add `xpiDownloadLink` in `zotero-plugin.config.ts`. -2. Copy zotero command line config file. Modify the commands that starts your installation of the beta Zotero. +2. Copy the environment variable file. Modify the commands that starts your installation of the beta Zotero. - > (Optional) Do this only once: Start the beta Zotero with `/path/to/zotero -p`. Create a new profile and use it as your development profile. - > Put the path of the profile into the `profilePath` in `zotero-cmd.json` to specify which profile to use. + > Create a development profile (Optional) + > Start the beta Zotero with `/path/to/zotero -p`. Create a new profile and use it as your development profile. Do this only once ```sh - cp ./scripts/zotero-cmd-template.json ./scripts/zotero-cmd.json - vim ./scripts/zotero-cmd.json + cp .env.example .env + vim .env ``` + If you are developing more than one plugin, you can store the bin path and profile path in the system environment variables, which can be omitted here. + 3. Install dependencies with `npm install` > If you are using `pnpm` as the package manager for your project, you need to add `public-hoist-pattern[]=*@types/bluebird*` to `.npmrc`, see . @@ -209,7 +206,6 @@ Start development server with `npm start`, it will: - Prebuild the plugin in development mode - Start Zotero with plugin loaded from `build/` -- Open devtool - Watch `src/**` and `addon/**`. - If `src/**` changed, run esbuild and reload - If `addon/**` has changed, rebuild the plugin (in development mode) and reload @@ -226,10 +222,7 @@ When file changes are detected in `src` or `addon`, the plugin will be automatic
💡 Steps to add this feature to an existing plugin -1. Copy `scripts/**.mjs` -2. Copy `server`, `build`, and `stop` commands in `package.json` -3. Run `npm install --save-dev chokidar` -4. Done. +Please see [zotero-plugin-scaffold](https://github.com/northword/zotero-plugin-scaffold).
@@ -246,7 +239,7 @@ You can also: Run `npm run build` to build the plugin in production mode, and the xpi for installation and the built code is under `build` folder. -Steps in `scripts/build.mjs`: +Steps of build: - Create/empty `build/`. - Copy `addon/**` to `build/addon/**` @@ -254,7 +247,7 @@ Steps in `scripts/build.mjs`: - Prepare locale files to [avoid conflict](https://www.zotero.org/support/dev/zotero_7_for_developers#avoiding_localization_conflicts) - Rename `**/*.flt` to `**/${addonRef}-*.flt` - Prefix each fluent message with `addonRef-` -- Use Esbuild to build `.ts` source code to `.js`, build `src/index.ts` to `./build/addon/chrome/content/scripts`. +- Use ESBuild to build `.ts` source code to `.js`, build `src/index.ts` to `./build/addon/chrome/content/scripts`. - (Production mode only) Zip the `./build/addon` to `./build/*.xpi` - (Production mode only) Prepare `update.json` or `update-beta.json` @@ -271,15 +264,14 @@ Steps in `scripts/build.mjs`: To build and release, use ```shell -# A release-it command: version increase, npm run build, git push, and GitHub release -# release-it: https://github.com/release-it/release-it +# version increase, git add, commit and push +# then on ci, npm run build, and release to GitHub npm run release ``` > [!note] > In this template, release-it is configured to locally bump the version, build, and push commits and git.tags, subsequently GitHub Action will rebuild the plugin and publish the XPI to GitHub Release. > -> If you need to release a locally built XPI, set `release-it.github.release` to `true` in `package.json` and remove `.github/workflows/release.yml`. Besides that, you need to set the environment variable `GITHUB_TOKEN`, get it in . #### About Prerelease diff --git a/doc/README-zhCN.md b/doc/README-zhCN.md index 606de38..1a8c7c4 100644 --- a/doc/README-zhCN.md +++ b/doc/README-zhCN.md @@ -57,18 +57,15 @@ - 事件驱动、函数式编程的可扩展框架; - 简单易用,开箱即用; - ⭐[新特性!]自动热重载!每当修改源码时,都会自动编译并重新加载插件;[详情请跳转→](#自动热重载) -- `src/modules/examples.ts` 中有丰富的示例,涵盖了插件中常用的大部分API (使用的插件工具包 zotero-plugin-toolkit,仓库地址 https://github.com/windingwind/zotero-plugin-toolkit); +- `src/modules/examples.ts` 中有丰富的示例,涵盖了插件中常用的大部分API (使用 [zotero-plugin-toolkit](https://github.com/windingwind/zotero-plugin-toolkit); - TypeScript 支持: - - 为使用 JavaScript 编写的Zotero源码提供全面的类型定义支持 (使用类型定义包 zotero-types,仓库地址 https://github.com/windingwind/zotero-types); + - 为使用 JavaScript 编写的 Zotero 源码提供全面的类型定义支持 (使用 [zotero-types](https://github.com/windingwind/zotero-types)); - 全局变量和环境设置; - 插件开发/构建/发布工作流: - - 自动生成/更新插件id和版本、更新配置和设置环境变量 (`development`/`production`); + - 自动生成/更新插件版本、更新配置和设置环境变量 (`development`/`production`); - 自动在 Zotero 中构建和重新加载代码; - - 自动发布到GitHub (使用[release-it](https://github.com/release-it/release-it)); -- 集成Prettier和ES Lint; - -> [!warning] -> Zotero本地化已升级(`dtd` 已弃用,我们将不再使用 `.properties`). 主分支将只支持 Zotero 7.0.0-beta.12 或更高版本. 如果需要支持 Zotero 6,你可能需要同时使用`dtd`、`properties` 和`ftl`. 请参考此库的 `zotero6-bootstrap` 分支. + - 自动发布到 GitHub ; +- 集成 Prettier 和 ES Lint; ## Examples 示例 @@ -133,15 +130,15 @@ Obsidian风格的指令输入模块,它通过接受文本来运行插件,并 - registerAlertPromptExample -## Quick Start Guide 快速入门指南 +## 快速上手 -### 0 前置要求(Requirement) +### 0 环境要求 -1. 安装测试版 Zotero:https://www.zotero.org/support/beta_builds -2. 安装 Node.js(https://nodejs.org/en/)和 Git(https://git-scm.com/) +1. 安装 [beta 版 Zotero](https://www.zotero.org/support/beta_builds) +2. 安装 [Node.js](https://nodejs.org/en/) 和 [Git](https://git-scm.com/) > [!note] -> 本指南假定你已经对 Zotero 插件的基本结构和工作原理有初步的了解. 如果你还不了解,请先参考官方文档(https://www.zotero.org/support/dev/zotero_7_for_developers)和官方插件样例 Make It Red(仓库地址 https://github.com/zotero/make-it-red). +> 本指南假定你已经对 Zotero 插件的基本结构和工作原理有初步的了解. 如果你还不了解,请先参考[官方文档](https://www.zotero.org/support/dev/zotero_7_for_developers) 和[官方插件样例 Make It Red](https://github.com/zotero/make-it-red)。 ### 1 创建你的仓库(Create Your Repo) @@ -167,18 +164,16 @@ Obsidian风格的指令输入模块,它通过接受文本来运行插件,并 ```json5 { - version: "", // to 0.0.0 + version: "", // 修改为 0.0.0 author: "", description: "", homepage: "", config: { - addonName: "", // name to be displayed in the plugin manager - addonID: "", // ID to avoid conflict. IMPORTANT! - addonRef: "", // e.g. Element ID prefix - addonInstance: "", // the plugin's root instance: Zotero.${addonInstance} - prefsPrefix: "extensions.zotero.${addonRef}", // the prefix of prefs - releasePage: "", // URL to releases - updateJSON: "", // URL to update.json + addonName: "", // 插件名称 + addonID: "", // 插件 ID 【重要:防止冲突】 + addonRef: "", // 插件命名空间:元素前缀等 + addonInstance: "", // 注册在 Zotero 根下的实例名 + prefsPrefix: "extensions.zotero.${addonRef}", // 首选项的前缀 }, } ``` @@ -186,23 +181,26 @@ Obsidian风格的指令输入模块,它通过接受文本来运行插件,并 > [!warning] > 注意设置 addonID 和 addonRef 以避免冲突. -如果你需要在GitHub以外的地方托管你的 XPI 包,请删除 `releasePage` 并添加 `updateLink`,并将值设置为你的 XPI 下载地址. + 如果你需要在 GitHub 以外的地方托管你的 XPI 包,请修改 `zotero-plugin.config.ts` 中的 `updateURL` 和 `xpiDownloadLink`。 2. 复制 Zotero 启动配置,填入 Zotero 可执行文件路径和 profile 路径. - > (可选项) 此操作仅需执行一次: 使用 `/path/to/zotero -p` 启动 Zotero,创建一个新的配置文件并用作开发配置文件. - > 将配置文件的路径 `profilePath` 放入 `zotero-cmd.json` 中,以指定要使用的配置文件. + > (可选项) 创建开发用 profile 目录: + > + > 此操作仅需执行一次: 使用 `/path/to/zotero -p` 启动 Zotero,创建一个新的配置文件并用作开发配置文件。 ```sh - cp ./scripts/zotero-cmd-template.json ./scripts/zotero-cmd.json - vim ./scripts/zotero-cmd.json + cp .env.example .env + vim .env ``` + 如果你维护了多个插件,可以将这些内容存入系统环境变量,以避免在每个插件中都需要重复设置。 + 3. 运行 `npm install` 以安装相关依赖 - > 如果你使用 `pnpm` 作为包管理器,你需要添加 `public-hoist-pattern[]=*@types/bluebird*` 到`.npmrc`, 详情请查看 zotero-types(https://github.com/windingwind/zotero-types?tab=readme-ov-file#usage)的文档. + > 如果你使用 `pnpm` 作为包管理器,你需要添加 `public-hoist-pattern[]=*@types/bluebird*` 到`.npmrc`, 详情请查看 zotero-types(. -### 3 开始开发(Coding) +### 3 开发插件 使用 `npm start` 启动开发服务器,它将: @@ -225,14 +223,11 @@ Obsidian风格的指令输入模块,它通过接受文本来运行插件,并
💡 将此功能添加到现有插件的步骤 -1. 复制 `scripts/**.mjs` -2. 复制 `server` 、`build` 和 `stop` 命令到 `package.json` -3. 运行 `npm install --save-dev chokidar` -4. 结束. +请参阅:[zotero-plugin-scaffold](https://github.com/northword/zotero-plugin-scaffold)。
-#### 在 Zotero 中 Debug +#### 调试代码 你还可以: @@ -244,16 +239,16 @@ Obsidian风格的指令输入模块,它通过接受文本来运行插件,并 > XUL 文档: -### 4 构建(Build) +### 4 构建插件 运行 `npm run build` 在生产模式下构建插件,构建的结果位于 `build/` 目录中. -`scripts/build.mjs` 的运行步骤: +构建步骤: - 创建/清空 `build/` - 复制 `addon/**` 到 `build/addon/**` - 替换占位符:使用 `replace-in-file` 去替换在 `package.json` 中定义的关键字和配置 (`xhtml`、`.flt` 等) -- 准备本地化文件以避免冲突,查看官方文档了解更多(https://www.zotero.org/support/dev/zotero_7_for_developers#avoiding_localization_conflicts) +- 准备本地化文件以避免冲突,查看官方文档了解更多( - 重命名`**/*.flt` 为 `**/${addonRef}-*.flt` - 在每个消息前加上 `addonRef-` - 使用 Esbuild 来将 `.ts` 源码构建为 `.js`,从 `src/index.ts` 构建到`./build/addon/chrome/content/scripts` @@ -268,27 +263,25 @@ Obsidian风格的指令输入模块,它通过接受文本来运行插件,并 > - 你可以根据此变量决定用户无法查看/使用的内容. > - 在生产模式下,构建脚本将自动打包插件并更新 `update.json`. -### 5 发布(Release) +### 5 发布 如果要构建和发布插件,运行如下指令: ```shell -# A release-it command: version increase, npm run build, git push, and GitHub release -# release-it: https://github.com/release-it/release-it +# version increase, git add, commit and push +# then on ci, npm run build, and release to GitHub npm run release ``` > [!note] -> 在此模板中,release-it 被配置为在本地升级版本、构建、推送提交和 git 标签,随后GitHub Action 将重新构建插件并将 XPI 发布到 GitHub Release. -> -> 如果你需要发布一个本地构建的 XPI,将 `package.json` 中的 `release-it.github.release` 设置为 `true`,然后移除 `.github/workflows/release.yml`. 此外,你还需要设置环境变量 `GITHUB_TOKEN`,获取 GitHub Token(https://github.com/settings/tokens). +> 在此模板中,release-it 被配置为在本地更新版本号、提交并推送标签,随后 GitHub Action 将重新构建插件并将 XPI 发布到 GitHub Release. #### 关于预发布 该模板将 `prerelease` 定义为插件的测试版,当你在 release-it 中选择 `prerelease` 版本 (版本号中带有 `-` ),构建脚本将创建一个 `update-beta.json` 给预发布版本使用,这将确保常规版本的用户不会自动更新到测试版,只有手动下载并安装了测试版的用户才能自动更新到下一个测试版. 当下一个正式版本更新时,脚本将同步更新 `update.json` 和 `update-beta.json`,这将使正式版和测试版用户都可以更新到最新的正式版. > [!warning] -> 严格来说,区分 Zotero 6 和 Zotero 7 兼容的插件版本应该通过 `update.json` 的 `addons.__addonID__.updates[]` 中分别配置 `applications.zotero.strict_min_version`,这样 Zotero 才能正确识别,详情在 Zotero 7 开发文档(https://www.zotero.org/support/dev/zotero_7_for_developers#updaterdf_updatesjson)获取. +> 严格来说,区分 Zotero 6 和 Zotero 7 兼容的插件版本应该通过 `update.json` 的 `addons.__addonID__.updates[]` 中分别配置 `applications.zotero.strict_min_version`,这样 Zotero 才能正确识别,详情在 Zotero 7 开发文档(. ## Details 更多细节 @@ -333,7 +326,7 @@ createElement(document, "button", { namespace: "xul" }); // manually set namespa ### 关于 Zotero API(About Zotero API) -Zotero 文档已过时且不完整,克隆 https://github.com/zotero/zotero 并全局搜索关键字. +Zotero 文档已过时且不完整,克隆 并全局搜索关键字. > ⭐[zotero-types](https://github.com/windingwind/zotero-types) 提供了最常用的 Zotero API,在默认情况下它被包含在此模板中. 你的 IDE 将为大多数的 API 提供提醒. diff --git a/package.json b/package.json index 5b16fb7..ce103cd 100644 --- a/package.json +++ b/package.json @@ -7,47 +7,39 @@ "addonID": "addontemplate@euclpts.com", "addonRef": "addontemplate", "addonInstance": "AddonTemplate", - "prefsPrefix": "extensions.zotero.addontemplate", - "releasePage": "https://github.com/windingwind/zotero-addon-template/releases", - "updateJSON": "https://raw.githubusercontent.com/windingwind/zotero-addon-template/main/update.json" - }, - "main": "src/index.ts", - "scripts": { - "start": "node scripts/server.mjs", - "build": "tsc --noEmit && node scripts/build.mjs production", - "stop": "node scripts/stop.mjs", - "lint": "prettier --write . && eslint . --ext .ts --fix", - "test": "echo \"Error: no test specified\" && exit 1", - "release": "release-it --only-version --preReleaseId=beta", - "update-deps": "npm update --save" + "prefsPrefix": "extensions.zotero.addontemplate" }, "repository": { "type": "git", "url": "git+https://github.com/windingwind/zotero-addon-template.git" }, "author": "windingwind", - "license": "AGPL-3.0-or-later", "bugs": { "url": "https://github.com/windingwind/zotero-addon-template/issues" }, "homepage": "https://github.com/windingwind/zotero-addon-template#readme", + "license": "AGPL-3.0-or-later", + "scripts": { + "start": "zotero-plugin serve", + "build": "tsc --noEmit && zotero-plugin build", + "lint": "prettier --write . && eslint . --ext .ts --fix", + "release": "zotero-plugin release", + "test": "echo \"Error: no test specified\" && exit 1", + "update-deps": "npm update --save" + }, "dependencies": { - "zotero-plugin-toolkit": "^2.3.30" + "zotero-plugin-toolkit": "^2.3.31" }, "devDependencies": { - "@types/node": "^20.12.8", - "@typescript-eslint/eslint-plugin": "^7.8.0", - "@typescript-eslint/parser": "^7.8.0", - "chokidar": "^3.6.0", - "compressing": "^1.10.0", - "esbuild": "^0.20.2", + "@types/node": "^20.12.12", + "@typescript-eslint/eslint-plugin": "^7.9.0", + "@typescript-eslint/parser": "^7.9.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "prettier": "^3.2.5", - "release-it": "^17.2.1", - "replace-in-file": "^7.1.0", "typescript": "^5.4.5", - "zotero-types": "^2.0.0" + "zotero-plugin-scaffold": "^0.0.21", + "zotero-types": "^1.3.24" }, "eslintConfig": { "env": { @@ -113,23 +105,5 @@ } } ] - }, - "release-it": { - "git": { - "tagName": "v${version}" - }, - "npm": { - "publish": false - }, - "github": { - "release": false, - "assets": [ - "build/*.xpi" - ] - }, - "hooks": { - "before:init": "npm run lint", - "after:bump": "npm run build" - } } } diff --git a/scripts/build.mjs b/scripts/build.mjs deleted file mode 100644 index c12682f..0000000 --- a/scripts/build.mjs +++ /dev/null @@ -1,243 +0,0 @@ -import details from "../package.json" with { type: "json" }; -import { - Logger, - clearFolder, - copyFileSync, - copyFolderRecursiveSync, - dateFormat, -} from "./utils.mjs"; -import { zip } from "compressing"; -import { build } from "esbuild"; -import { existsSync, readdirSync, renameSync } from "fs"; -import path from "path"; -import { env, exit } from "process"; -import replaceInFile from "replace-in-file"; - -const { replaceInFileSync } = replaceInFile; - -process.env.NODE_ENV = - process.argv[2] === "production" ? "production" : "development"; - -const buildDir = "build"; - -const { name, author, description, homepage, version, config } = details; -const isPreRelease = version.includes("-"); - -function replaceString(buildTime) { - const replaceFrom = [ - /__author__/g, - /__description__/g, - /__homepage__/g, - /__buildVersion__/g, - /__buildTime__/g, - ]; - const replaceTo = [author, description, homepage, version, buildTime]; - - config.updateURL = isPreRelease - ? config.updateJSON.replace("update.json", "update-beta.json") - : config.updateJSON; - - replaceFrom.push( - ...Object.keys(config).map((k) => new RegExp(`__${k}__`, "g")), - ); - replaceTo.push(...Object.values(config)); - - const replaceResult = replaceInFileSync({ - files: [ - `${buildDir}/addon/**/*.xhtml`, - `${buildDir}/addon/**/*.html`, - `${buildDir}/addon/**/*.css`, - `${buildDir}/addon/**/*.json`, - `${buildDir}/addon/prefs.js`, - `${buildDir}/addon/manifest.json`, - `${buildDir}/addon/bootstrap.js`, - ], - from: replaceFrom, - to: replaceTo, - countMatches: true, - }); - - // Logger.debug( - // "[Build] Run replace in ", - // replaceResult.filter((f) => f.hasChanged).map((f) => `${f.file} : ${f.numReplacements} / ${f.numMatches}`), - // ); -} - -function prepareLocaleFiles() { - // Prefix Fluent messages in xhtml - const MessagesInHTML = new Set(); - replaceInFileSync({ - files: [`${buildDir}/addon/**/*.xhtml`, `${buildDir}/addon/**/*.html`], - processor: (input) => { - const matchs = [...input.matchAll(/(data-l10n-id)="(\S*)"/g)]; - matchs.map((match) => { - input = input.replace( - match[0], - `${match[1]}="${config.addonRef}-${match[2]}"`, - ); - MessagesInHTML.add(match[2]); - }); - return input; - }, - }); - - // Walk the sub folders of `build/addon/locale` - const localesPath = path.join(buildDir, "addon/locale"), - localeNames = readdirSync(localesPath, { withFileTypes: true }) - .filter((dirent) => dirent.isDirectory()) - .map((dirent) => dirent.name); - - for (const localeName of localeNames) { - const localePath = path.join(localesPath, localeName); - const ftlFiles = readdirSync(localePath, { - withFileTypes: true, - }) - .filter((dirent) => dirent.isFile()) - .map((dirent) => dirent.name); - - // rename *.ftl to addonRef-*.ftl - for (const ftlFile of ftlFiles) { - if (ftlFile.endsWith(".ftl")) { - renameSync( - path.join(localePath, ftlFile), - path.join(localePath, `${config.addonRef}-${ftlFile}`), - ); - } - } - - // Prefix Fluent messages in each ftl - const MessageInThisLang = new Set(); - replaceInFileSync({ - files: [`${buildDir}/addon/locale/${localeName}/*.ftl`], - processor: (fltContent) => { - const lines = fltContent.split("\n"); - const prefixedLines = lines.map((line) => { - // https://regex101.com/r/lQ9x5p/1 - const match = line.match( - /^(?[a-zA-Z]\S*)([ ]*=[ ]*)(?.*)$/m, - ); - if (match) { - MessageInThisLang.add(match.groups.message); - return `${config.addonRef}-${line}`; - } else { - return line; - } - }); - return prefixedLines.join("\n"); - }, - }); - - // If a message in xhtml but not in ftl of current language, log it - MessagesInHTML.forEach((message) => { - if (!MessageInThisLang.has(message)) { - Logger.error(`[Build] ${message} don't exist in ${localeName}`); - } - }); - } -} - -function prepareUpdateJson() { - // If it is a pre-release, use update-beta.json - if (!isPreRelease) { - copyFileSync("scripts/update-template.json", "update.json"); - } - if (existsSync("update-beta.json") || isPreRelease) { - copyFileSync("scripts/update-template.json", "update-beta.json"); - } - - const updateLink = - config.updateLink ?? isPreRelease - ? `${config.releasePage}/download/v${version}/${name}.xpi` - : `${config.releasePage}/latest/download/${name}.xpi`; - - const replaceResult = replaceInFileSync({ - files: [ - "update-beta.json", - isPreRelease ? "pass" : "update.json", - `${buildDir}/addon/manifest.json`, - ], - from: [ - /__addonID__/g, - /__buildVersion__/g, - /__updateLink__/g, - /__updateURL__/g, - ], - to: [config.addonID, version, updateLink, config.updateURL], - countMatches: true, - }); - - Logger.debug( - `[Build] Prepare Update.json for ${ - isPreRelease - ? "\u001b[31m Prerelease \u001b[0m" - : "\u001b[32m Release \u001b[0m" - }`, - replaceResult - .filter((f) => f.hasChanged) - .map((f) => `${f.file} : ${f.numReplacements} / ${f.numMatches}`), - ); -} - -export const esbuildOptions = { - entryPoints: ["src/index.ts"], - define: { - __env__: `"${env.NODE_ENV}"`, - }, - bundle: true, - target: "firefox102", - outfile: path.join( - buildDir, - `addon/chrome/content/scripts/${config.addonRef}.js`, - ), - // Don't turn minify on - minify: env.NODE_ENV === "production", -}; - -export async function main() { - const t = new Date(); - const buildTime = dateFormat("YYYY-mm-dd HH:MM:SS", new Date()); - - Logger.info( - `[Build] BUILD_DIR=${buildDir}, VERSION=${version}, BUILD_TIME=${buildTime}, ENV=${[ - env.NODE_ENV, - ]}`, - ); - - clearFolder(buildDir); - copyFolderRecursiveSync("addon", buildDir); - - Logger.debug("[Build] Replacing"); - replaceString(buildTime); - - Logger.debug("[Build] Preparing locale files"); - prepareLocaleFiles(); - - Logger.debug("[Build] Running esbuild"); - await build(esbuildOptions); - - Logger.debug("[Build] Addon prepare OK"); - - if (process.env.NODE_ENV === "production") { - Logger.debug("[Build] Packing Addon"); - await zip.compressDir( - path.join(buildDir, "addon"), - path.join(buildDir, `${name}.xpi`), - { - ignoreBase: true, - }, - ); - - prepareUpdateJson(); - - Logger.debug( - `[Build] Finished in ${(new Date().getTime() - t.getTime()) / 1000} s.`, - ); - } -} - -if (process.env.NODE_ENV === "production") { - main().catch((err) => { - Logger.error(err); - exit(1); - }); -} diff --git a/scripts/scripts.mjs b/scripts/scripts.mjs deleted file mode 100644 index 126d8e6..0000000 --- a/scripts/scripts.mjs +++ /dev/null @@ -1,75 +0,0 @@ -import details from "../package.json" assert { type: "json" }; - -const { addonID, addonName } = details.config; -const { version } = details; - -export const reloadScript = ` -(async () => { -Services.obs.notifyObservers(null, "startupcache-invalidate", null); -const { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm"); -const addon = await AddonManager.getAddonByID("${addonID}"); -await addon.reload(); -const progressWindow = new Zotero.ProgressWindow({ closeOnClick: true }); -progressWindow.changeHeadline("${addonName} Hot Reload"); -progressWindow.progress = new progressWindow.ItemProgress( - "chrome://zotero/skin/tick.png", - "VERSION=${version}, BUILD=${new Date().toLocaleString()}. By zotero-plugin-toolkit" -); -progressWindow.progress.setProgress(100); -progressWindow.show(); -progressWindow.startCloseTimer(5000); -})()`; - -export const openDevToolScript = ` -(async () => { - -// const { BrowserToolboxLauncher } = ChromeUtils.import( -// "resource://devtools/client/framework/browser-toolbox/Launcher.jsm", -// ); -// BrowserToolboxLauncher.init(); -// TODO: Use the above code to open the devtool after https://github.com/zotero/zotero/pull/3387 - -Zotero.Prefs.set("devtools.debugger.remote-enabled", true, true); -Zotero.Prefs.set("devtools.debugger.remote-port", 6100, true); -Zotero.Prefs.set("devtools.debugger.prompt-connection", false, true); -Zotero.Prefs.set("devtools.debugger.chrome-debugging-websocket", false, true); - -env = - Services.env || - Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment); - -env.set("MOZ_BROWSER_TOOLBOX_PORT", 6100); -Zotero.openInViewer( - "chrome://devtools/content/framework/browser-toolbox/window.html", - { - onLoad: (doc) => { - doc.querySelector("#status-message-container").style.visibility = - "collapse"; - let toolboxBody; - waitUntil( - () => { - toolboxBody = doc - .querySelector(".devtools-toolbox-browsertoolbox-iframe") - ?.contentDocument?.querySelector(".theme-body"); - return toolboxBody; - }, - () => { - toolboxBody.style = "pointer-events: all !important"; - } - ); - }, - } -); - -function waitUntil(condition, callback, interval = 100, timeout = 10000) { - const start = Date.now(); - const intervalId = setInterval(() => { - if (condition()) { - clearInterval(intervalId); - callback(); - } else if (Date.now() - start > timeout) { - clearInterval(intervalId); - } - }, interval); -} -})()`; diff --git a/scripts/server.mjs b/scripts/server.mjs deleted file mode 100644 index 3f35430..0000000 --- a/scripts/server.mjs +++ /dev/null @@ -1,87 +0,0 @@ -import { main as build, esbuildOptions } from "./build.mjs"; -import { openDevToolScript, reloadScript } from "./scripts.mjs"; -import { main as startZotero } from "./start.mjs"; -import { Logger } from "./utils.mjs"; -import cmd from "./zotero-cmd.json" assert { type: "json" }; -import { execSync } from "child_process"; -import chokidar from "chokidar"; -import { context } from "esbuild"; -import { exit } from "process"; - -process.env.NODE_ENV = "development"; - -const { zoteroBinPath, profilePath } = cmd.exec; - -const startZoteroCmd = `"${zoteroBinPath}" --debugger --purgecaches -profile "${profilePath}"`; - -async function watch() { - const watcher = chokidar.watch(["src/**", "addon/**"], { - ignored: /(^|[\/\\])\../, // ignore dotfiles - persistent: true, - }); - - let esbuildCTX = await context(esbuildOptions); - - watcher - .on("ready", () => { - Logger.info("Server Ready! \n"); - }) - .on("change", async (path) => { - Logger.info(`${path} changed.`); - if (path.startsWith("src")) { - await esbuildCTX.rebuild(); - } else if (path.startsWith("addon")) { - await build() - // Do not abort the watcher when errors occur in builds triggered by the watcher. - .catch((err) => { - Logger.error(err); - }); - } - // reload - reload(); - }) - .on("error", (err) => { - Logger.error("Server start failed!", err); - }); -} - -function reload() { - Logger.debug("Reloading..."); - const url = `zotero://ztoolkit-debug/?run=${encodeURIComponent( - reloadScript, - )}`; - const command = `${startZoteroCmd} -url "${url}"`; - execSync(command); -} - -function openDevTool() { - Logger.debug("Open dev tools..."); - const url = `zotero://ztoolkit-debug/?run=${encodeURIComponent( - openDevToolScript, - )}`; - const command = `${startZoteroCmd} -url "${url}"`; - execSync(command); -} - -async function main() { - // build - await build(); - - // start Zotero - startZotero(openDevTool); - - // watch - await watch(); -} - -main().catch((err) => { - Logger.error(err); - // execSync("node scripts/stop.mjs"); - exit(1); -}); - -process.on("SIGINT", (code) => { - execSync("node scripts/stop.mjs"); - Logger.info(`Server terminated with signal ${code}.`); - exit(0); -}); diff --git a/scripts/start.mjs b/scripts/start.mjs deleted file mode 100644 index 0229d2b..0000000 --- a/scripts/start.mjs +++ /dev/null @@ -1,119 +0,0 @@ -import details from "../package.json" assert { type: "json" }; -import { Logger } from "./utils.mjs"; -import cmd from "./zotero-cmd.json" assert { type: "json" }; -import { spawn } from "child_process"; -import { existsSync, readFileSync, writeFileSync, rmSync } from "fs"; -import { clearFolder } from "./utils.mjs"; -import path from "path"; -import { exit } from "process"; - -const { addonID } = details.config; -const { zoteroBinPath, profilePath, dataDir } = cmd.exec; - -// Keep in sync with the addon's onStartup -const loadDevToolWhen = `Plugin ${addonID} startup`; - -const logPath = "logs"; -const logFilePath = path.join(logPath, "zotero.log"); - -if (!existsSync(zoteroBinPath)) { - throw new Error("Zotero binary does not exist."); -} - -if (!existsSync(profilePath)) { - throw new Error("The given Zotero profile does not exist."); -} - -function prepareDevEnv() { - const addonProxyFilePath = path.join(profilePath, `extensions/${addonID}`); - const buildPath = path.resolve("build/addon"); - - function writeAddonProxyFile() { - writeFileSync(addonProxyFilePath, buildPath); - Logger.debug( - `Addon proxy file has been updated. - File path: ${addonProxyFilePath} - Addon path: ${buildPath} `, - ); - } - - if (existsSync(addonProxyFilePath)) { - if (readFileSync(addonProxyFilePath, "utf-8") !== buildPath) { - writeAddonProxyFile(); - } - } else { - writeAddonProxyFile(); - } - - const addonXpiFilePath = path.join(profilePath, `extensions/${addonID}.xpi`); - if (existsSync(addonXpiFilePath)) { - rmSync(addonXpiFilePath); - } - - const prefsPath = path.join(profilePath, "prefs.js"); - if (existsSync(prefsPath)) { - const PrefsLines = readFileSync(prefsPath, "utf-8").split("\n"); - const filteredLines = PrefsLines.map((line) => { - if ( - line.includes("extensions.lastAppBuildId") || - line.includes("extensions.lastAppVersion") - ) { - return; - } - if (line.includes("extensions.zotero.dataDir") && dataDir !== "") { - return `user_pref("extensions.zotero.dataDir", "${dataDir.replace(/\\\\?/g, "\\\\")}");`; - } - return line; - }); - const updatedPrefs = filteredLines.join("\n"); - writeFileSync(prefsPath, updatedPrefs, "utf-8"); - Logger.debug("The /prefs.js has been modified."); - } -} - -function prepareLog() { - clearFolder(logPath); - writeFileSync(logFilePath, ""); -} - -export function main(callback) { - let isZoteroReady = false; - - prepareDevEnv(); - - prepareLog(); - - const zoteroProcess = spawn(zoteroBinPath, [ - "--debugger", - "--purgecaches", - "-profile", - profilePath, - ]); - - zoteroProcess.stdout.on("data", (data) => { - if (!isZoteroReady && data.toString().includes(loadDevToolWhen)) { - isZoteroReady = true; - callback(); - } - writeFileSync(logFilePath, data, { - flag: "a", - }); - }); - - zoteroProcess.stderr.on("data", (data) => { - writeFileSync(logFilePath, data, { - flag: "a", - }); - }); - - zoteroProcess.on("close", (code) => { - Logger.info(`Zotero terminated with code ${code}.`); - exit(0); - }); - - process.on("SIGINT", () => { - // Handle interrupt signal (Ctrl+C) to gracefully terminate Zotero process - zoteroProcess.kill(); - exit(); - }); -} diff --git a/scripts/stop.mjs b/scripts/stop.mjs deleted file mode 100644 index 3b44ef2..0000000 --- a/scripts/stop.mjs +++ /dev/null @@ -1,26 +0,0 @@ -import { Logger, isRunning } from "./utils.mjs"; -import cmd from "./zotero-cmd.json" assert { type: "json" }; -import { execSync } from "child_process"; -import process from "process"; - -const { killZoteroWindows, killZoteroUnix } = cmd; - -isRunning("zotero", (status) => { - if (status) { - killZotero(); - } else { - Logger.warn("No Zotero running."); - } -}); - -function killZotero() { - try { - if (process.platform === "win32") { - execSync(killZoteroWindows); - } else { - execSync(killZoteroUnix); - } - } catch (e) { - Logger.error(e); - } -} diff --git a/scripts/update-template.json b/scripts/update-template.json deleted file mode 100644 index 8f65a4c..0000000 --- a/scripts/update-template.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "addons": { - "__addonID__": { - "updates": [ - { - "version": "__buildVersion__", - "update_link": "__updateLink__", - "applications": { - "zotero": { - "strict_min_version": "6.999" - } - } - } - ] - } - } -} diff --git a/scripts/utils.mjs b/scripts/utils.mjs deleted file mode 100644 index d17288a..0000000 --- a/scripts/utils.mjs +++ /dev/null @@ -1,129 +0,0 @@ -import { exec } from "child_process"; -import { - existsSync, - lstatSync, - mkdirSync, - readFileSync, - readdirSync, - rmSync, - writeFileSync, -} from "fs"; -import path from "path"; - -export function copyFileSync(source, target) { - var targetFile = target; - - // If target is a directory, a new file with the same name will be created - if (existsSync(target)) { - if (lstatSync(target).isDirectory()) { - targetFile = path.join(target, path.basename(source)); - } - } - - writeFileSync(targetFile, readFileSync(source)); -} - -export function copyFolderRecursiveSync(source, target) { - var files = []; - - // Check if folder needs to be created or integrated - var targetFolder = path.join(target, path.basename(source)); - if (!existsSync(targetFolder)) { - mkdirSync(targetFolder); - } - - // Copy - if (lstatSync(source).isDirectory()) { - files = readdirSync(source); - files.forEach(function (file) { - var curSource = path.join(source, file); - if (lstatSync(curSource).isDirectory()) { - copyFolderRecursiveSync(curSource, targetFolder); - } else { - copyFileSync(curSource, targetFolder); - } - }); - } -} - -export function clearFolder(target) { - if (existsSync(target)) { - rmSync(target, { recursive: true, force: true }); - } - - mkdirSync(target, { recursive: true }); -} - -export function dateFormat(fmt, date) { - let ret; - const opt = { - "Y+": date.getFullYear().toString(), - "m+": (date.getMonth() + 1).toString(), - "d+": date.getDate().toString(), - "H+": date.getHours().toString(), - "M+": date.getMinutes().toString(), - "S+": date.getSeconds().toString(), - }; - for (let k in opt) { - ret = new RegExp("(" + k + ")").exec(fmt); - if (ret) { - fmt = fmt.replace( - ret[1], - ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, "0"), - ); - } - } - return fmt; -} - -export class Logger { - static log(...args) { - console.log(...args); - } - - // red - static error(...args) { - console.error("\u001b[31m [ERROR]", ...args, "\u001b[0m"); - } - - // yellow - static warn(...args) { - console.warn("\u001b[33m [WARN]", ...args, "\u001b[0m"); - } - - // blue - static debug(...args) { - console.log("\u001b[34m [DEBUG]\u001b[0m", ...args); - } - - // green - static info(...args) { - console.log("\u001b[32m [INFO]", ...args, "\u001b[0m"); - } - - // cyan - static trace(...args) { - console.log("\u001b[36m [TRACE]\u001b[0m", ...args); - } -} - -export function isRunning(query, cb) { - let platform = process.platform; - let cmd = ""; - switch (platform) { - case "win32": - cmd = `tasklist`; - break; - case "darwin": - cmd = `ps -ax | grep ${query}`; - break; - case "linux": - cmd = `ps -A`; - break; - default: - break; - } - exec(cmd, (err, stdout, stderr) => { - cb(stdout.toLowerCase().indexOf(query.toLowerCase()) > -1); - }); -} diff --git a/scripts/zotero-cmd-template.json b/scripts/zotero-cmd-template.json deleted file mode 100644 index 27143f6..0000000 --- a/scripts/zotero-cmd-template.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "usage": "Copy and rename this file to zotero-cmd.json. Edit the cmd.", - "killZoteroWindows": "taskkill /f /im zotero.exe", - "killZoteroUnix": "kill -9 $(ps -x | grep '[z]otero' | awk '{print $1}')", - "exec": { - "@comment-zoteroBinPath": "Please input the path of the Zotero binary file in `zoteroBinPath`.", - "@comment-zoteroBinPath-tip": "The path delimiter should be escaped as `\\` for win32. The path is `*/Zotero.app/Contents/MacOS/zotero` for MacOS.", - "zoteroBinPath": "/path/to/zotero.exe", - - "@comment-profilePath": "Please input the path of the profile used for development in `profilePath`.", - "@comment-profilePath-tip": "Start the profile manager by `/path/to/zotero.exe -p` to create a profile for development", - "@comment-profilePath-see": "https://www.zotero.org/support/kb/profile_directory", - "profilePath": "/path/to/profile", - - "@comment-dataDir": "Please input the directory where the database is located in dataDir", - "@comment-dataDir-tip": "If this field is kept empty, Zotero will start with the default data.", - "@comment-dataDir-see": "https://www.zotero.org/support/zotero_data", - "dataDir": "" - } -} diff --git a/src/hooks.ts b/src/hooks.ts index 3d7fdc8..5fe3cf0 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -17,13 +17,6 @@ async function onStartup() { Zotero.uiReadyPromise, ]); - // TODO: Remove this after zotero#3387 is merged - if (__env__ === "development") { - // Keep in sync with the scripts/startup.mjs - const loadDevToolWhen = `Plugin ${config.addonID} startup`; - ztoolkit.log(loadDevToolWhen); - } - initLocale(); BasicExampleFactory.registerPrefs(); diff --git a/update.json b/update.json deleted file mode 100644 index 418efe6..0000000 --- a/update.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "addons": { - "addontemplate@euclpts.com": { - "updates": [ - { - "version": "1.1.2", - "update_link": "https://github.com/windingwind/zotero-addon-template/releases/latest/download/zotero-addon-template.xpi", - "applications": { - "zotero": { - "strict_min_version": "6.999" - } - } - } - ] - } - } -} diff --git a/zotero-plugin.config.ts b/zotero-plugin.config.ts new file mode 100644 index 0000000..d1659e0 --- /dev/null +++ b/zotero-plugin.config.ts @@ -0,0 +1,61 @@ +import { defineConfig } from "zotero-plugin-scaffold"; +import pkg from "./package.json"; +import { copyFileSync } from "fs"; + +export default defineConfig({ + source: ["src", "addon"], + dist: "build", + name: pkg.config.addonName, + id: pkg.config.addonID, + namespace: pkg.config.addonRef, + updateURL: `https://github.com/{{owner}}/{{repo}}/releases/download/release/${ + pkg.version.includes("-") ? "update-beta.json" : "update.json" + }`, + xpiDownloadLink: + "https://github.com/{{owner}}/{{repo}}/releases/download/v{{version}}/{{xpiName}}.xpi", + + server: { + asProxy: true, + }, + + build: { + assets: ["addon/**/*.*"], + define: { + ...pkg.config, + author: pkg.author, + description: pkg.description, + homepage: pkg.homepage, + buildVersion: pkg.version, + buildTime: "{{buildTime}}", + }, + esbuildOptions: [ + { + entryPoints: ["src/index.ts"], + define: { + __env__: `"${process.env.NODE_ENV}"`, + }, + bundle: true, + target: "firefox115", + outfile: `build/addon/chrome/content/scripts/${pkg.config.addonRef}.js`, + }, + ], + // If you want to checkout update.json into the repository, uncomment the following lines: + // makeUpdateJson: { + // hash: false, + // }, + // hooks: { + // "build:makeUpdateJSON": (ctx) => { + // copyFileSync("build/update.json", "update.json"); + // copyFileSync("build/update-beta.json", "update-beta.json"); + // }, + // }, + }, + // release: { + // bumpp: { + // execute: "npm run build", + // }, + // }, + + // If you need to see a more detailed build log, uncomment the following line: + // logLevel: "trace", +});