docs/planning/addon-publish-plan.md
Rama main · réplica de lectura del repositorio original.
# 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 `<slug>-v<semver>`__, 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/<slug>/` 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 <package>@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.