|
| 1 | +# Releasing First Draft CLI |
| 2 | + |
| 3 | +Publishing is a separate, explicit action after a release-preparation pull request has merged. npm registry bytes |
| 4 | +and package versions cannot be replaced, so do not create or push a release tag as a dry run. |
| 5 | + |
| 6 | +## Repository and registry setup |
| 7 | + |
| 8 | +Before the first release, a repository administrator must: |
| 9 | + |
| 10 | +1. Confirm `firstdraft/cli` is public. The release workflow deliberately removes checkout credentials and re-fetches |
| 11 | + the public release refs anonymously. |
| 12 | +2. Protect `main` with pull-request and CI requirements, and add a `v*` tag ruleset that restricts tag creation, |
| 13 | + update, and deletion. |
| 14 | +3. Create a GitHub environment named `npm`, restrict it to release tags, require a reviewer, prevent self-review, |
| 15 | + and add the environment variable `NPM_RELEASE_ENABLED=true`. The workflow fails before publishing when this |
| 16 | + variable is absent. |
| 17 | +4. Confirm that the bootstrap publisher account has write-protecting 2FA enabled. The first publish creates this |
| 18 | + unscoped package under that account; organization access cannot be granted before the package exists. |
| 19 | +5. Create a one-day granular npm token with read/write access to All Packages, no organization-management access, |
| 20 | + and bypass 2FA enabled. A new unscoped package cannot yet be selected individually. Add it directly as the `npm` |
| 21 | + environment secret `NPM_TOKEN`; never put it in an Issue, chat, workflow file, repository file, or command |
| 22 | + history. |
| 23 | + |
| 24 | +The token is a one-time bootstrap credential. After the package exists, use the repository-pinned Node.js 24.18.0 |
| 25 | +toolchain with npm 11.16.0 to give the npm organization durable read/write access and configure trusted publishing: |
| 26 | + |
| 27 | +```sh |
| 28 | +npm --version |
| 29 | +npm access grant read-write firstdraft.com:developers firstdraft |
| 30 | +``` |
| 31 | + |
| 32 | +```sh |
| 33 | +npm trust github firstdraft \ |
| 34 | + --repository firstdraft/cli \ |
| 35 | + --file publish.yml \ |
| 36 | + --environment npm \ |
| 37 | + --allow-publish |
| 38 | +npm trust list firstdraft |
| 39 | +``` |
| 40 | + |
| 41 | +Confirm the listed relationship identifies `firstdraft/cli`, `publish.yml`, the `npm` environment, and publish |
| 42 | +permission. Before creating another release tag, merge a follow-up pull request that removes the `NODE_AUTH_TOKEN` |
| 43 | +environment from the publish step. Then remove the GitHub secret, revoke the bootstrap token, and configure the |
| 44 | +package to disallow token publication. The workflow continues through GitHub OIDC without a persistent npm |
| 45 | +credential. |
| 46 | + |
| 47 | +## Prepare a release |
| 48 | + |
| 49 | +1. Update `package.json` and `package-lock.json` to the exact release version. |
| 50 | +2. Keep prereleases on the `next` dist-tag. Do not create `latest` until a stable release is intentionally approved. |
| 51 | +3. Update user-facing documentation and release notes for behavior changes. |
| 52 | +4. Run: |
| 53 | + |
| 54 | + ```sh |
| 55 | + npm ci --ignore-scripts |
| 56 | + npm audit |
| 57 | + npm run check |
| 58 | + ``` |
| 59 | + |
| 60 | +5. Merge the reviewed pull request only after local and hosted checks pass. |
| 61 | + |
| 62 | +## Publish |
| 63 | + |
| 64 | +The manual boundary is creation of the version tag. From an up-to-date, clean `main`, verify the intended commit and |
| 65 | +then create and push `v<package-version>`. For version `0.1.0-alpha.1`, the tag is `v0.1.0-alpha.1`. |
| 66 | +Push one release tag at a time; the workflow serializes publication, but GitHub retains at most one pending run in a |
| 67 | +concurrency group. |
| 68 | + |
| 69 | +The workflow rejects accidental or stale inputs unless they use a protected `v*` tag in `firstdraft/cli`, the tag |
| 70 | +equals `v` plus the version in `package.json`, the remote tag still identifies the triggering commit, and that commit |
| 71 | +appears in the first-parent history of `origin/main`. First-parent membership allows an older reviewed `main` state |
| 72 | +after another change lands while rejecting intermediate commits from a merged side branch. The workflow reruns the |
| 73 | +complete check, waits for approval in the `npm` environment, reverifies the remote refs, and publishes to the public |
| 74 | +registry with provenance under `next`. |
| 75 | + |
| 76 | +The tag ruleset and `npm` environment approval are the external trust boundary because a tag-push run loads its |
| 77 | +workflow from the tagged commit. Before approving the `npm` deployment, the reviewer must confirm: |
| 78 | + |
| 79 | +- The tag, package version, and commit SHA are the intended release. |
| 80 | +- The commit is a known reviewed state in protected `main` history and its required checks passed. |
| 81 | +- `.github/workflows/publish.yml` at that commit is the reviewed workflow, still selects the `npm` environment, and |
| 82 | + publishes only under `next` with provenance. |
| 83 | +- The unprivileged verification job passed for that exact commit. |
| 84 | + |
| 85 | +Do not move or reuse a release tag. If the tagged commit is not a first-parent state of `main`, merge the intended |
| 86 | +change and prepare a new version rather than moving an already shared tag. |
| 87 | + |
| 88 | +## Verify and recover |
| 89 | + |
| 90 | +After publication, inspect the registry before retrying any reported failure; the package may already exist. Verify |
| 91 | +the exact version, `next` dist-tag, integrity metadata, and provenance metadata: |
| 92 | + |
| 93 | +```sh |
| 94 | +npm view firstdraft@0.1.0-alpha.1 \ |
| 95 | + version dist.integrity dist.shasum repository.url engines bin --json |
| 96 | +npm dist-tag ls firstdraft |
| 97 | +``` |
| 98 | + |
| 99 | +Install `firstdraft@0.1.0-alpha.1` into a fresh temporary prefix, confirm `firstdraft --version`, compare the packed |
| 100 | +file list with the release workflow, and run `npm audit signatures` after an exact installation. |
| 101 | + |
| 102 | +A published version cannot be overwritten or reused. For a bad release, move `next` to a known-good version, |
| 103 | +deprecate the bad version, and publish a corrected higher version. Treat unpublishing as an exceptional incident |
| 104 | +response, not a routine rollback. |
0 commit comments