Files
TREK/wiki/Plugin-Publishing.md
T
Maurice 648b4aab96 chore(plugin-sdk): make it npm-publishable so npx resolves for authors
The docs told authors to run `npx create-trek-plugin` / `npx trek-plugin`,
but nothing published under those names, so npx couldn't resolve them.

- Ship one package, `trek-plugin-sdk`, with a bin that matches the package
  name (`trek-plugin-sdk`) so `npx trek-plugin-sdk <command>` resolves with
  zero install. The dispatcher gained a `create` subcommand, so every step
  (create/validate/pack/entry/release) runs through that one entry point.
  The short `trek-plugin` / `create-trek-plugin` bins still work once
  installed.
- Package hardening for publish: repository+directory (monorepo subdir),
  homepage/bugs/author/engines, publishConfig public, a prepublishOnly that
  builds + tests, and a LICENSE file.
- A publish workflow: pushing a `plugin-sdk-v*` tag builds and publishes with
  the NPM_TOKEN repo secret.
- Docs (SDK README + the four wiki pages) now use `npx trek-plugin-sdk <cmd>`,
  the invocation that actually resolves.
2026-07-04 14:44:24 +02:00

9.4 KiB

Publishing a Plugin

Plugins are distributed from a static registry — the TREK-Plugins GitHub repo. There is no upload server and no account: you host the code in your own public GitHub repo, attach a built plugin.zip to a release, and list it with a pull request.

The trek-plugin CLI (shipped in the trek-plugin-sdk package) does almost all the mechanical work — you rarely hand-type a hash, size, commit, or JSON field.

The short version

npx trek-plugin-sdk validate .                       # manifest + layout sanity
npx trek-plugin-sdk pack .                            # build plugin.zip, print sha256 + size
gh release create v1.0.0 plugin.zip --repo you/trek-plugin-flight-tracker
npx trek-plugin-sdk entry --repo you/trek-plugin-flight-tracker --tag v1.0.0 \
  --out registry/plugins/flight-tracker.json     # the ready-to-PR entry

…then open a PR to TREK-Plugins adding that one registry/plugins/<id>.json file. trek-plugin release collapses the middle three steps into one command (see below).

1. Host and build your plugin

Put your plugin in a public GitHub repo (convention: trek-plugin-<id>). create-trek-plugin scaffolds the layout:

npx trek-plugin-sdk create flight-tracker --type widget   # integration | page | widget

A publishable plugin has, at the repo root:

  • trek-plugin.json — the manifest (see Plugin Development)
  • server/index.js — the built server entry (required)
  • client/ — the built frontend (only for page/widget plugins)
  • README.md — filled in, with a real screenshot (the quality gate is strict — see below)
  • docs/screenshot.png — the store card image, committed to the repo

2. Validate

npx trek-plugin-sdk validate .

validate runs the manifest checks plus a light layout/README sanity pass: it fails if trek-plugin.json is invalid or server/index.js is missing, and warns if the directory name doesn't match the plugin id, the README has no screenshot, or the README still contains scaffold placeholders. This is a subset of what CI runs — CI additionally verifies the release, the artifact's SHA-256, and the README over the network — so a clean local run predicts a clean CI run but doesn't replace it.

3. Pack

npx trek-plugin-sdk pack .                 # writes ./plugin.zip
npx trek-plugin-sdk pack . --out dist.zip  # custom output path
npx trek-plugin-sdk pack . --json          # machine-readable result

pack validates first, then builds plugin.zip in the installer's exact layout and prints the sha256 and size you'd otherwise compute by hand. It ships only what the runtime needs — trek-plugin.json, README.md, LICENSE, package.json, and the server/ and client/ trees — and drops node_modules, .git, source maps and .ts sources. It refuses native binaries (.node, binding.gyp, prebuilds/) and enforces the same size limits as the installer (25 MB per file, 50 MB total, 4000 entries).

docs/ is intentionally not shipped. The store fetches your docs/screenshot.png straight from the repo at the pinned commit, so keep it committed to GitHub but out of the zip — pack handles this for you.

4. Create the GitHub release

Tag vX.Y.Z where X.Y.Z equals version in your manifest, and attach the packed plugin.zip as a release asset:

gh release create v1.0.0 plugin.zip --repo you/trek-plugin-flight-tracker

Prefer the uploaded plugin.zip asset — it's the exact bytes you packed and the registry pins their hash. Don't rely on GitHub's auto-generated source archives; they aren't the installer layout and their bytes aren't stable.

5. Generate the registry entry

npx trek-plugin-sdk entry \
  --repo you/trek-plugin-flight-tracker \
  --tag v1.0.0 \
  --out registry/plugins/flight-tracker.json

entry reads your manifest and plugin.zip and emits the complete entry — deriving commitSha (from git rev-parse <tag>^{commit}), downloadUrl, sha256, size, apiVersion, and minTrekVersion (the lower bound of the manifest's trek range, e.g. ">=3.2.0 <4.0.0" → 3.2.0). Flags: --zip (default plugin.zip), --commit <sha> to override commit resolution, --asset to name a differently-named release asset, --merge for updates (below), and --out to write a file.

One-shot: release

npx trek-plugin-sdk release . --repo you/trek-plugin-flight-tracker --tag v1.0.0

release does pack → gh release create → entry in one go and prints the entry to stdout. It accepts --out, --notes, --commit, and --merge. (It requires the gh CLI, authenticated.)

6. Open a registry PR

You don't have write access, so fork TREK-Plugins, add your generated file as registry/plugins/<id>.json, and open a PR back to main. Add only that file — dist/ is generated on merge, and CI rejects manual edits to it.

The entry follows schema/plugin-entry.schema.json; schema/example-entry.json is the canonical shape. size is required (a common omission), as are commitSha, downloadUrl, sha256, minTrekVersion, apiVersion, and nativeModules: false on every version — all of which trek-plugin entry fills in for you.

What CI enforces

CI runs scripts/validate-entry.mjs and scripts/check-readme.mjs on every changed registry/plugins/*.json.

Entry (validate-entry.mjs): valid schema · id matches the filename and the slug pattern ^[a-z][a-z0-9-]{2,39}$ · your id is bound to your GitHub owner on first registration, so nobody can repoint it later (owner changes need a maintainer override) · homoglyph/mixed-script name check · the release tag exists and resolves to commitSha · manifest parity at that commit (id, version, type, apiVersion, and nativeModules must not be true) · the downloaded artifact's SHA-256 matches the pin and its size is within bounds · no native binaries in the archive · egress[] present (and no bare *) when http:outbound is declared. Any unique slug is fine — there are no reserved namespaces.

README (check-readme.mjs, fetched at the pinned commit): must exist at the repo root, contain the sections What it does / Screenshots / Permissions / Setup, carry at least one screenshot that resolves to a real image (a relative docs/screenshot.png is resolved against the commit), have real prose (≥ 400 characters after stripping headings/code/images/tables), contain no leftover scaffold placeholders, and explain every permission your manifest declares (each permission string must appear in the README).

Provenance & integrity

  • commitSha pins the exact source the maintainer reviewed (git tags are movable).
  • sha256 pins the exact artifact bytes TREK will run (release assets are mutable).

TREK verifies the downloaded bytes against sha256 and refuses to install on a mismatch. A reviewedAt date on your entry means a maintainer looked at that exact commit — it is not an ongoing guarantee. reviewedAt and boundOwner are maintained by CI on merge; don't set them yourself.

sha256 proves the bytes are the ones the registry vouches for. An author signature additionally proves the bytes were signed by you, so a compromised registry can't ship attacker code under your name. The entry schema allows two optional fields — authorPublicKey on the entry and signature on each version. TREK verifies the signature offline (minisign / Ed25519, no external service) and pins your key on first install (trust-on-first-use): a later release signed with a different key is refused until an admin re-trusts it.

One-time — create a key:

minisign -G            # writes minisign.key (keep secret) + minisign.pub

Put the base64 payload line from minisign.pub in your entry as authorPublicKey (stable across versions).

Each release — sign the artifact:

minisign -Sm plugin.zip   # writes plugin.zip.minisig

Add the base64 line from plugin.zip.minisig to that version as signature, next to its sha256:

{
  "id": "flight-tracker",
  "authorPublicKey": "RWQ…base64 minisign public key…",
  "versions": [{
    "version": "1.2.0",
    "sha256": "3b2a…",
    "signature": "RUR…base64 .minisig payload…"
  }]
}

Signing is opt-in: an entry without authorPublicKey/signature installs on sha256 alone. But once a plugin has shipped signed, an unsigned update is refused — don't drop the signature between versions.

Updating

Bump version in the manifest, re-pack, cut a new vX.Y.Z release, then fold the new version onto your existing entry with --merge:

npx trek-plugin-sdk entry --repo you/trek-plugin-flight-tracker --tag v1.1.0 \
  --merge registry/plugins/flight-tracker.json \
  --out registry/plugins/flight-tracker.json

--merge prepends the new version (keeping the array newest-first) and preserves the rest of the entry. PR the updated file. Instances see the update on their next registry poll; applying it is always an explicit admin action, and if a new version requests more permissions the admin must re-approve — see Plugin Permissions.