update: use plugin-scaffold
This commit is contained in:
parent
5fbdd96785
commit
6f758ee59b
|
|
@ -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 =
|
||||||
|
|
@ -7,27 +7,47 @@ on:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-it:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GitHub_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GitHub_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
# cache: npm
|
|
||||||
|
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
run: npm install
|
run: npm install -f
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
npm run build
|
||||||
|
|
||||||
- name: Release to GitHub
|
- name: Release to GitHub
|
||||||
# if: github.event_name == 'push' && github.ref_type == 'tag' && startsWith(github.ref, 'refs/tags/v')
|
|
||||||
run: |
|
run: |
|
||||||
npm run release -- --no-increment --no-git --github.release --ci --verbose
|
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
|
||||||
|
uses: apexskier/github-release-commenter@v1
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
comment-template: |
|
||||||
|
:rocket: _This ticket has been resolved in {release_tag}. See {release_link} for release notes._
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
build
|
build
|
||||||
logs
|
logs
|
||||||
node_modules
|
node_modules
|
||||||
package-lock.json
|
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
yarn.lock
|
yarn.lock
|
||||||
zotero-cmd.json
|
zotero-cmd.json
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.env
|
||||||
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
|
|
@ -8,18 +8,15 @@
|
||||||
"addonRef": "BetterNotes",
|
"addonRef": "BetterNotes",
|
||||||
"prefsPrefix": "extensions.zotero.Knowledge4Zotero",
|
"prefsPrefix": "extensions.zotero.Knowledge4Zotero",
|
||||||
"addonInstance": "BetterNotes",
|
"addonInstance": "BetterNotes",
|
||||||
"dataSchemaVersion": "8",
|
"dataSchemaVersion": "8"
|
||||||
"releasePage": "https://github.com/windingwind/zotero-better-notes/releases/",
|
|
||||||
"updateJSON": "https://raw.githubusercontent.com/windingwind/zotero-better-notes/master/update.json"
|
|
||||||
},
|
},
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node scripts/server.mjs",
|
"start": "zotero-plugin serve",
|
||||||
"build": "tsc --noEmit && node scripts/build.mjs production",
|
"build": "tsc --noEmit && zotero-plugin build",
|
||||||
"stop": "node scripts/stop.mjs",
|
"release": "zotero-plugin release",
|
||||||
"lint": "prettier --write . && eslint . --ext .ts --fix",
|
"lint": "prettier --write . && eslint . --ext .ts --fix",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"release": "release-it --only-version --preReleaseId=beta",
|
|
||||||
"update-deps": "npm update --save"
|
"update-deps": "npm update --save"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
@ -59,7 +56,7 @@
|
||||||
"unist-util-visit": "^5.0.0",
|
"unist-util-visit": "^5.0.0",
|
||||||
"unist-util-visit-parents": "^6.0.1",
|
"unist-util-visit-parents": "^6.0.1",
|
||||||
"yamljs": "^0.3.0",
|
"yamljs": "^0.3.0",
|
||||||
"zotero-plugin-toolkit": "^2.3.29"
|
"zotero-plugin-toolkit": "^2.3.31"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
||||||
|
|
@ -75,10 +72,8 @@
|
||||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||||
"@typescript-eslint/parser": "^6.14.0",
|
"@typescript-eslint/parser": "^6.14.0",
|
||||||
"chokidar-cli": "^3.0.0",
|
"chokidar-cli": "^3.0.0",
|
||||||
"compressing": "^1.10.0",
|
|
||||||
"concurrently": "^8.2.2",
|
"concurrently": "^8.2.2",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"esbuild": "^0.19.9",
|
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"prettier": "^3.1.1",
|
"prettier": "^3.1.1",
|
||||||
|
|
@ -86,11 +81,10 @@
|
||||||
"prosemirror-state": "^1.4.3",
|
"prosemirror-state": "^1.4.3",
|
||||||
"prosemirror-transform": "^1.8.0",
|
"prosemirror-transform": "^1.8.0",
|
||||||
"prosemirror-view": "^1.32.6",
|
"prosemirror-view": "^1.32.6",
|
||||||
"release-it": "^16.3.0",
|
|
||||||
"replace-in-file": "^7.0.2",
|
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"xslt3": "^2.6.0",
|
"xslt3": "^2.6.0",
|
||||||
"zotero-types": "^2.0.0"
|
"zotero-plugin-scaffold": "^0.0.26",
|
||||||
|
"zotero-types": "^2.0.1"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"env": {
|
"env": {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
import { build } from "esbuild";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
const buildDir = "build";
|
|
||||||
|
|
||||||
export async function main() {
|
|
||||||
await build({
|
|
||||||
entryPoints: ["./src/extras/*.*"],
|
|
||||||
outdir: path.join(buildDir, "addon/chrome/content/scripts"),
|
|
||||||
bundle: true,
|
|
||||||
target: ["firefox115"],
|
|
||||||
}).catch(() => exit(1));
|
|
||||||
}
|
|
||||||
|
|
@ -1,256 +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";
|
|
||||||
|
|
||||||
import { main as buildExtras } from "./build-extras.mjs";
|
|
||||||
|
|
||||||
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/**/*.json`,
|
|
||||||
`${buildDir}/addon/**/*.css`,
|
|
||||||
`${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() {
|
|
||||||
// Walk the builds/addon/locale folder's sub folders and rename *.ftl to addonRef-*.ftl
|
|
||||||
const localeDir = path.join(buildDir, "addon/locale");
|
|
||||||
const localeFolders = readdirSync(localeDir, { withFileTypes: true })
|
|
||||||
.filter((dirent) => dirent.isDirectory())
|
|
||||||
.map((dirent) => dirent.name);
|
|
||||||
|
|
||||||
for (const localeSubFolder of localeFolders) {
|
|
||||||
const localeSubDir = path.join(localeDir, localeSubFolder);
|
|
||||||
const localeSubFiles = readdirSync(localeSubDir, {
|
|
||||||
withFileTypes: true,
|
|
||||||
})
|
|
||||||
.filter((dirent) => dirent.isFile())
|
|
||||||
.map((dirent) => dirent.name);
|
|
||||||
|
|
||||||
for (const localeSubFile of localeSubFiles) {
|
|
||||||
if (localeSubFile.endsWith(".ftl")) {
|
|
||||||
renameSync(
|
|
||||||
path.join(localeSubDir, localeSubFile),
|
|
||||||
path.join(localeSubDir, `${config.addonRef}-${localeSubFile}`),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const localeMessage = new Set();
|
|
||||||
const localeMessageMiss = new Set();
|
|
||||||
|
|
||||||
const replaceResultFlt = replaceInFileSync({
|
|
||||||
files: [`${buildDir}/addon/locale/**/*.ftl`],
|
|
||||||
processor: (fltContent) => {
|
|
||||||
const lines = fltContent.split("\n");
|
|
||||||
const prefixedLines = lines.map((line) => {
|
|
||||||
// https://regex101.com/r/lQ9x5p/1
|
|
||||||
const match = line.match(
|
|
||||||
/^(?<message>[a-zA-Z]\S*)([ ]*=[ ]*)(?<pattern>.*)$/m,
|
|
||||||
);
|
|
||||||
if (match) {
|
|
||||||
localeMessage.add(match.groups.message);
|
|
||||||
return `${config.addonRef}-${line}`;
|
|
||||||
} else {
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return prefixedLines.join("\n");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const replaceResultXhtml = replaceInFileSync({
|
|
||||||
files: [`${buildDir}/addon/**/*.xhtml`],
|
|
||||||
processor: (input) => {
|
|
||||||
const matchs = [...input.matchAll(/(data-l10n-id)="(\S*)"/g)];
|
|
||||||
matchs.map((match) => {
|
|
||||||
if (localeMessage.has(match[2])) {
|
|
||||||
input = input.replace(
|
|
||||||
match[0],
|
|
||||||
`${match[1]}="${config.addonRef}-${match[2]}"`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
localeMessageMiss.add(match[2]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return input;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Logger.debug(
|
|
||||||
"[Build] Prepare locale files OK",
|
|
||||||
// replaceResultFlt.filter((f) => f.hasChanged).map((f) => `${f.file} : OK`),
|
|
||||||
// replaceResultXhtml.filter((f) => f.hasChanged).map((f) => `${f.file} : OK`),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (localeMessageMiss.size !== 0) {
|
|
||||||
Logger.warn(
|
|
||||||
`[Build] Fluent message [${new Array(
|
|
||||||
...localeMessageMiss,
|
|
||||||
)}] do not exsit in addon's locale files.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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: "firefox115",
|
|
||||||
outfile: path.join(
|
|
||||||
buildDir,
|
|
||||||
`addon/chrome/content/scripts/${config.addonRef}.js`,
|
|
||||||
),
|
|
||||||
// Don't turn minify on
|
|
||||||
minify: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
replaceString(buildTime);
|
|
||||||
Logger.debug("[Build] Replace OK");
|
|
||||||
|
|
||||||
prepareLocaleFiles();
|
|
||||||
|
|
||||||
await build(esbuildOptions);
|
|
||||||
Logger.debug("[Build] Run esbuild OK");
|
|
||||||
|
|
||||||
Logger.debug("[Build] Addon prepare OK");
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "production") {
|
|
||||||
await zip.compressDir(
|
|
||||||
path.join(buildDir, "addon"),
|
|
||||||
path.join(buildDir, `${name}.xpi`),
|
|
||||||
{
|
|
||||||
ignoreBase: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Logger.debug("[Build] Addon pack OK");
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
buildExtras().catch((err) => {
|
|
||||||
Logger.error(err);
|
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
})()`;
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
import { main as build, esbuildOptions } from "./build.mjs";
|
|
||||||
import { main as buildExtras } from "./build-extras.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();
|
|
||||||
await buildExtras();
|
|
||||||
} 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();
|
|
||||||
await buildExtras();
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
});
|
|
||||||
|
|
@ -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}");`;
|
|
||||||
}
|
|
||||||
return line;
|
|
||||||
});
|
|
||||||
const updatedPrefs = filteredLines.join("\n");
|
|
||||||
writeFileSync(prefsPath, updatedPrefs, "utf-8");
|
|
||||||
Logger.debug("The <profile>/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();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"addons": {
|
|
||||||
"__addonID__": {
|
|
||||||
"updates": [
|
|
||||||
{
|
|
||||||
"version": "__buildVersion__",
|
|
||||||
"update_link": "__updateLink__",
|
|
||||||
"applications": {
|
|
||||||
"zotero": {
|
|
||||||
"strict_min_version": "7.0.0-beta.70"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -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 zotero)",
|
|
||||||
"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": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
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`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entryPoints: ["src/extras/*.*"],
|
||||||
|
outdir: "build/addon/chrome/content/scripts",
|
||||||
|
bundle: true,
|
||||||
|
target: ["firefox115"],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// 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",
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue