# Publishing jwilleke's add-ons — plan for review > __Status: approved by the operator, 2026-10-17__ (recorded on [ngdpbase#1863](https://github.com/jwilleke/ngdpbase/issues/1863)). The workflow ships in ngdpbase v5.16.0 (PR #1864): `create:addon --repo` writes it, `create:addon --update-publish` refreshes it. The types package is `@jwilleke/ngdpbase-types`, with a `/testing` export for add-on tests ([ngdpbase#1865](https://github.com/jwilleke/ngdpbase/issues/1865), not built yet). Nothing is published yet. ## What's decided already - All of jwilleke's add-ons are public: public repos, public npm packages (operator, 2026-10-17). - Add-ons are published to npm only from GitHub Actions, with provenance (2026-10-15). - A site installs an add-on pinned to an exact version and checksum, and ngdpbase v5.14.0's installer ([ngdpbase#1830](https://github.com/jwilleke/ngdpbase/issues/1830)) refuses any package whose npm provenance doesn't come from the repository the site names. ## The plan __One workflow file per add-on repo, generated by ngdpbase.__ `npm run create:addon -- --repo` writes `.github/workflows/publish.yml` into the add-on's repo. The file carries a version marker; `create:addon --update-publish` refreshes it, so a change to how add-ons publish is made once, in ngdpbase, and pulled into each repo. __Why not one shared workflow in ngdpbase?__ Two reasons: - npm's trusted publishing checks the calling repo's own workflow file; - the provenance certificate would name ngdpbase's repo as the builder, and the installer as shipped would then refuse every package. A shared workflow could come back only if the installer switched to a different provenance check. ### What the workflow does 1. __Starts on a tag `-v`__, e.g. `phr-v0.0.1`. A plain `v1.2.3` tag never publishes unless the repo opts in with `--plain-tags`. yourphr doesn't: its `v*` tags are yourPHR's app releases. 2. __Finds the add-on__ in `addons//` and takes the package name from its `package.json`. 3. __Checks the package__ before anything else: - the tag version equals the package version; - it isn't `private`; - `ngdpbase.slug` and `ngdpbase.minVersion` are set; - `engines.node` is `>=24.15.0`; - `repository` names this repo and the folder; - an explicit `files` list and a license are present. 4. __Builds and tests:__ `npm ci`, build, then the add-on's own tests. It builds against ngdpbase only when the add-on imports ngdpbase's files directly; phr doesn't, so it builds on its own. 5. __Packs and checks the pack:__ no TypeScript sources (other than type declarations) and no tests in the tarball. Runtime dependencies are bundled, since sites have no npm at runtime. 6. __Publishes__ with `npm publish --provenance --access public` through trusted publishing: a short-lived token from GitHub, nothing stored. 7. __Makes a GitHub release__ with the `.tgz` and its sha512. ## What only the operator can do (once per package) npm requires a package to exist before trusted publishing can be switched on, and switching it on needs the owner's two-factor sign-in. So, for each new package: 1. Publish an empty placeholder, version `0.0.0`, from the npm account `jswilleke`. 2. Mark it: `npm deprecate @0.0.0 "placeholder, do not install"`. 3. On npmjs.com, add the trusted publisher for the package: this GitHub repo and `publish.yml`. Sites never install the placeholder: it has no provenance, and every real version starts at `0.0.1` or later. | Package | Repo | Scope | Status | |---|---|---|---| | `@yourphr/phr-addon` | jwilleke/yourphr (`addons/phr`) | `@yourphr` (org created 2026-10-15) | not on npm yet | | `@jwilleke/accounting-addon` | jwilleke/ngdp-accounting-addons (`addons/accounting`) | `@jwilleke` (already publishes `agent-kit`, `oidc-auth-server`) | not on npm yet; repo is private, so making it public is the operator's step | ## Dry run before anything real `phr` 0.0.1, which holds no records: 1. tag `phr-v0.0.1`; 2. the workflow publishes; 3. a local ngdpbase ≥5.14 site names it in `ngdpbase.addons.install.phr` (package, version, `repository: github.com/jwilleke/yourphr`); 4. the installer checks provenance and the checksum; 5. phr loads. Anything that breaks is cheap to fix at this stage. ## phr's own changes, when the operator says go - `package.json`: - remove `"private": true`; - add `repository` (`{ url: github.com/jwilleke/yourphr, directory: addons/phr }`); - add a `files` list: compiled `.js`, `config/`, `pages/`, `package.json`. `engines.node` is already `>=24`; it becomes `>=24.15.0`. - Add the generated `publish.yml` to yourphr, once PR #1864 is merged and released. ## Open for the operator 1. ~~__Accept this plan?__~~ Approved 2026-10-17. 2. ~~__Accounting's types__~~ Decided 2026-10-17: ngdpbase publishes a types package, and both phr and accounting use it instead of their own copies (see [`ngdp-move.md`](ngdp-move.md)). 3. __When to do the bootstrap__ for `@yourphr/phr-addon` and run the dry run.