Skip to content

npm scripts ​

Every script defined in package.json, and what it actually runs.

Build and run ​

ScriptRunsNotes
startzotero-plugin serveLaunches Zotero against the .env dev profile and reloads on change.
start:headlessnode scripts/headless.mjs npm startstart on a virtual display. See below.
prestartnode scripts/clean-dev-profile.mjsRuns automatically before start. See below.
buildzotero-plugin build && tsc --noEmitProduces the .xpi, update.json and update-beta.json in .scaffold/build.
releasezotero-plugin releaseBumps the version and builds through release.bumpp.execute.
clean:profilenode scripts/clean-dev-profile.mjsThe same cleanup, on demand.

build type-checks src/ but not test/, because tsc --noEmit here uses the root tsconfig.json. A changed export signature that breaks a test file is caught by typecheck or by the test stage, not the build.

ScriptRuns
typechecktsc --noEmit && tsc --noEmit -p test

Lint ​

ScriptRuns
lint:checkprettier --check . && eslint .
lint:fixprettier --write . && eslint . --fix

Test ​

ScriptRunsNotes
testnode scripts/headless.mjs npx zotero-plugin testWhat CI runs.
test:fastnode scripts/headless.mjs node scripts/run-tests.mjsThe same suite, wrapped. Use this locally.

scripts/headless.mjs puts the run on a virtual display when the session would otherwise open a Zotero window on the real screen. Competing with the desktop was the suite's largest source of intermittent failures, so every entry point that launches Zotero goes through it.

test:fast exists because zotero-plugin test starts a real Zotero GUI that does not reliably exit once the suite has finished. The wrapper watches stdout for the Test run completed line and kills the run at that point rather than waiting.

It spawns the run detached, in its own process group, so it can kill that group by id. Killing by pattern instead matches every Zotero on the machine: a dev instance from npm start, another worktree's test run, and your own library along with them.

Its exit code comes from the parsed summary line, not from the killed process. A run that prints no summary within 15 minutes is treated as a hang and exits non-zero.

Docs ​

ScriptRuns
docs:devvitepress dev docs
docs:buildvitepress build docs
docs:previewvitepress preview docs

docs:build validates internal links and fails on a dead one. Prose references to a renamed page break silently in the source and loudly here, which is the point of running it.

Housekeeping ​

ScriptRunsNotes
preparehuskyInstalls the Git hooks. Runs on npm install.
update-depsnpm update --saveWill move the pinned versions; see below.

What clean-dev-profile.mjs does ​

Two things that otherwise cause silent breakage between npm start runs.

It kills the Zotero still holding this checkout's dev profile after a crash or a manifest error. Without that, zotero-plugin serve reuses the running instance and your fix appears not to have worked.

It identifies that instance by two conditions together: argv[0] ends in /zotero or /zotero-bin, and the remaining arguments contain the literal -profile <resolved path>. That pair is what keeps it from touching another worktree's dev Zotero or your own library.

The path is resolved the way the scaffold resolves it: an exported ZOTERO_PLUGIN_PROFILE_PATH wins, and .env fills it in otherwise. Reading .env alone made ZOTERO_PLUGIN_PROFILE_PATH=… npm start kill the Zotero named in the file rather than its own.

It also strips leftover custom-tab entries for this plugin from the profile's session.json. Zotero restores open tabs before plugins register their tab types, so a stale entry crashes startup.

With no ZOTERO_PLUGIN_PROFILE_PATH set anywhere it does nothing and says so. There is nothing to identify the right process by, and guessing means killing someone else's Zotero.

Pinned versions ​

zotero-plugin-toolkit, zotero-types, vis-timeline and edtf are pinned exactly, with no caret. The toolkit and types ranges in the upstream template resolve to newer betas with renamed exports, and tsc --noEmit then fails on code that did not change. update-deps will move them; check the build after running it.

Released under the AGPL-3.0-or-later license.