Skip to content

Commit 63d23d6

Browse files
committed
docs(release): add release flow + provenance + dist-tag guide
Covers the three-step release shape (bump → dispatch → workflow), how the repo delegates to the fleet-shared provenance workflow, what trusted publishing means (no NPM_TOKEN stored), the dist-tag choices and consequences, what lands in the published tarball (and what doesn't), common release scenarios (patch, pre-release, backport, emergency revert), hazards (wrong dist-tag, script-name drift, OIDC window), and a dispatch checklist. Junior-dev level: every command has a when-to-use, the provenance chain is explained in plain English (sigstore / attestation / npm audit signatures), hazards section names what has broken releases before. Notes the release:ci / publish:ci script-name mismatch between .github/workflows/provenance.yml (calls release:ci) and package.json (defines publish:ci) so a reader can cross-check before dispatching.
1 parent 76ec78e commit 63d23d6

2 files changed

Lines changed: 301 additions & 0 deletions

File tree

docs/release.md

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
# Release
2+
3+
How a new version of `@socketregistry/packageurl-js` gets cut and
4+
shipped to npm with provenance. Read this before running a release
5+
or changing anything under `.github/workflows/provenance.yml`,
6+
`scripts/publish.mts`, or `scripts/bump.mts`.
7+
8+
## Who this is for
9+
10+
Maintainers cutting a release and contributors curious about what
11+
happens after a PR lands on main. You do not run `npm publish`
12+
directly in this repo — the workflow does it on your behalf.
13+
14+
## The shape of a release
15+
16+
Three things happen, in order:
17+
18+
```
19+
┌───────────────────────────────────────────────────────────────┐
20+
│ 1. bump version — local or via CI │
21+
│ • pnpm bump <patch|minor|major|prerelease> │
22+
│ • updates package.json + CHANGELOG │
23+
│ • commits the bump │
24+
└───────────────────┬──────────────────────────────────────────┘
25+
26+
27+
┌───────────────────────────────────────────────────────────────┐
28+
│ 2. trigger the Publish workflow — manual dispatch │
29+
│ GitHub → Actions → 📦 Publish → Run workflow │
30+
│ Inputs: │
31+
│ • dist-tag (latest | next | beta | canary | …) │
32+
│ • debug (0 | 1) │
33+
└───────────────────┬──────────────────────────────────────────┘
34+
35+
36+
┌───────────────────────────────────────────────────────────────┐
37+
│ 3. the workflow (calling the fleet's shared provenance.yml): │
38+
│ ci:validate → pnpm check + build + coverage │
39+
│ release:ci → npm publish --provenance │
40+
│ Socket.dev → post-publish malware audit │
41+
│ Sigstore → attestation published │
42+
└───────────────────────────────────────────────────────────────┘
43+
```
44+
45+
Nothing publishes without passing all checks. The `prepublishOnly`
46+
hook in `package.json` throws on local `npm publish` attempts so
47+
you cannot accidentally ship from a laptop.
48+
49+
## The workflow file
50+
51+
`.github/workflows/provenance.yml` is a thin delegation to the
52+
fleet-shared workflow at
53+
`SocketDev/socket-registry/.github/workflows/provenance.yml`. The
54+
shared workflow does the heavy lifting; this file just wires up
55+
repo-specific inputs:
56+
57+
```yaml
58+
with:
59+
debug: ${{ inputs.debug }}
60+
dist-tag: ${{ inputs.dist-tag }}
61+
package-name: '@socketregistry/packageurl-js'
62+
publish-script: 'release:ci'
63+
setup-script: 'ci:validate'
64+
use-trusted-publishing: true
65+
secrets:
66+
SOCKET_API_KEY: ${{ secrets.SOCKET_API_TOKEN }}
67+
```
68+
69+
Key choices:
70+
71+
- **`use-trusted-publishing: true`** — npm recognizes this
72+
repository as an OIDC-trusted publisher. No long-lived `NPM_TOKEN`
73+
secret is stored anywhere; the GitHub Actions runner presents an
74+
OIDC token, npm verifies it, and the publish proceeds. A leaked
75+
token in the repo history cannot be used to publish.
76+
- **`setup-script: 'ci:validate'`** — runs `pnpm check` in a clean
77+
env before the publish starts. Publication aborts if any check
78+
fails.
79+
- **`publish-script: 'release:ci'`** — the npm script the workflow
80+
invokes to actually publish. Wraps `npm publish --provenance`.
81+
82+
### The dist-tag input
83+
84+
| Tag | Use |
85+
|---|---|
86+
| `latest` | Default. Production release for most consumers. |
87+
| `next` | Release candidate before cutting `latest`. |
88+
| `beta` | Pre-release on a feature branch. |
89+
| `canary` | Experimental / daily / short-lived. |
90+
| `backport` | Patches to older major lines. |
91+
92+
npm shows the `latest` tag by default; other tags require
93+
`npm install <pkg>@<tag>`. Picking the wrong tag (e.g. `latest` for
94+
a breaking pre-release) breaks every downstream consumer that did
95+
not pin. Triple-check the dist-tag on the dispatch form.
96+
97+
## Version bump commands
98+
99+
```bash
100+
# Patch release (bug fixes)
101+
pnpm bump patch
102+
103+
# Minor release (backward-compatible features)
104+
pnpm bump minor
105+
106+
# Major release (breaking changes)
107+
pnpm bump major
108+
109+
# Pre-release
110+
pnpm bump prerelease --preid=beta
111+
```
112+
113+
`pnpm bump` runs `scripts/bump.mts`. It:
114+
115+
1. Reads the current version from `package.json`.
116+
2. Computes the next version per semver rules.
117+
3. Updates `package.json`.
118+
4. Updates `CHANGELOG.md` (prepending a section).
119+
5. Commits the change with a message
120+
`chore(release): v<new-version>`.
121+
6. Does **not** tag — tagging happens as part of the workflow.
122+
123+
You review and push the bump commit like any other change. The
124+
Publish workflow is a manual dispatch after the bump is on main.
125+
126+
## Provenance — what it means on the npm side
127+
128+
Every release ships with a **provenance attestation**: an Sigstore-
129+
signed statement that says "this tarball was built from commit
130+
`<sha>` of `<repo>` by `<workflow>` at `<time>`." Consumers can
131+
verify via:
132+
133+
```bash
134+
npm install @socketregistry/packageurl-js
135+
npm audit signatures
136+
```
137+
138+
or
139+
140+
```bash
141+
# Inspect the attestation on npm
142+
npm view @socketregistry/packageurl-js dist.provenance
143+
```
144+
145+
A release without a valid attestation either (a) was cut before the
146+
fleet adopted provenance, or (b) was published out-of-band and
147+
should be treated with suspicion. The `provenance.yml` path is the
148+
**only** supported release path for new versions.
149+
150+
## The Socket.dev post-publish audit
151+
152+
The shared provenance workflow runs a Socket.dev scan on the
153+
published tarball immediately after `npm publish`. If Socket flags
154+
malware or a critical issue in the published artifact, the workflow
155+
fails loudly — you'll get a notification, and the release row in
156+
GitHub Actions goes red. npm keeps the version (publish is
157+
irrevocable) but the audit trail captures the alert.
158+
159+
This is belt-and-suspenders: Socket checks the dependency closure
160+
pre-publish via `ci:validate`, but the post-publish audit catches
161+
anything that slipped in between "CI passed" and "tarball on
162+
registry" (re-bundle side-effects, mis-configured files, etc.).
163+
164+
## What lands in the published tarball
165+
166+
```
167+
@socketregistry/packageurl-js-<version>.tgz
168+
├── package.json
169+
├── CHANGELOG.md
170+
├── dist/
171+
│ ├── index.js + index.d.ts
172+
│ ├── package-url.js + .d.ts
173+
│ ├── … (every compiled module)
174+
│ ├── purl-types/<ecosystem>.js + .d.ts
175+
│ └── (no sourcemaps — we don't ship them)
176+
└── data/npm/
177+
├── builtin-names.json
178+
└── legacy-names.json
179+
```
180+
181+
Controlled by `package.json`'s `files` field:
182+
183+
```json
184+
"files": [
185+
"dist/**/*",
186+
"data/**/*.json",
187+
"CHANGELOG.md"
188+
]
189+
```
190+
191+
Things explicitly NOT shipped:
192+
193+
- `src/` — TypeScript sources.
194+
- `test/` — test files.
195+
- `scripts/`, `.config/`, `.github/`, `.claude/` — tooling.
196+
- `docs/` — rendered as the tour site, not shipped to npm.
197+
- Any sourcemap or tsbuildinfo.
198+
199+
A CI check (`scripts/ci-validate.mts`) asserts the packed tarball
200+
size stays under a sanity threshold so an accidental `dist/`
201+
explosion or a misconfigured `files` field fails loudly.
202+
203+
## Common release scenarios
204+
205+
### Cutting a normal patch release
206+
207+
1. PRs with bug fixes merge to main.
208+
2. `pnpm bump patch` on a branch; commit + PR + merge.
209+
3. In GitHub, dispatch the Publish workflow with `dist-tag: latest`.
210+
4. Wait for green. Verify the new version on npmjs.com.
211+
212+
### Cutting a pre-release for testing
213+
214+
1. `pnpm bump prerelease --preid=beta` on a branch; commit + PR +
215+
merge to main (or to a release branch, if you're working off one).
216+
2. Dispatch Publish with `dist-tag: beta`.
217+
3. Consumers test with `npm install @socketregistry/packageurl-js@beta`.
218+
4. When satisfied, `pnpm bump <appropriate-level>` to drop the
219+
pre-release tag and dispatch again with `dist-tag: latest`.
220+
221+
### Patching an older major
222+
223+
1. Checkout the last-known-good commit on the older major
224+
(`v1.x.x`).
225+
2. `git checkout -b backport-fix-xyz`.
226+
3. Apply the fix, test, PR into a `release/1.x` branch.
227+
4. `pnpm bump patch` on that branch.
228+
5. Dispatch Publish with `dist-tag: backport` (or a specific tag
229+
like `1.x-latest`) to avoid bumping the `latest` tag.
230+
231+
### Emergency revert
232+
233+
npm does not let you delete a published version after 72 hours. For
234+
emergencies:
235+
236+
1. Publish a patch release that reverts the bad change.
237+
2. Use `npm deprecate @socketregistry/packageurl-js@<bad-version>
238+
"<reason>"` to mark the bad version deprecated (visible in
239+
installs, but not removed).
240+
3. Announce in the repo's CHANGELOG and any Socket channels.
241+
242+
Do not use `npm unpublish`; it is a last resort and has messy
243+
consequences (integrity mismatches, dependency confusion risks).
244+
245+
## Hazards
246+
247+
- **Dispatching with the wrong `dist-tag`.** Promoting a
248+
pre-release to `latest` by accident breaks every un-pinned
249+
consumer. Read the tag twice before clicking Run.
250+
- **Publishing from a non-clean tree.** The workflow runs
251+
`ci:validate`, but if a bump commit bundles unrelated changes,
252+
those ship too. Keep bump commits single-purpose.
253+
- **`release:ci` vs `publish:ci` script name drift.** The workflow
254+
calls `release:ci`. Confirm `package.json` has that script name;
255+
otherwise the workflow falls over with "script not found." (If
256+
renamed, update either this file and the workflow, or the
257+
script, in the same PR.)
258+
- **Trusted publisher misconfig.** If the npm-side trusted-publisher
259+
config for this repo is removed or the workflow file is renamed,
260+
publishes will fail with a 403. Check npm's trusted publisher
261+
settings first.
262+
- **OIDC token lifetime.** The workflow's OIDC-to-npm exchange
263+
happens in a narrow window. If a retry lands outside it, re-
264+
dispatch rather than trying to edit tokens.
265+
266+
## Checklist before dispatching a release
267+
268+
- [ ] Main is green on CI.
269+
- [ ] Bump commit is on main and builds + tests pass locally.
270+
- [ ] `CHANGELOG.md` describes every user-visible change in the
271+
bumped version.
272+
- [ ] `dist-tag` chosen is correct for the kind of release
273+
(latest / next / beta / canary / backport).
274+
- [ ] If this is a breaking major, the migration notes are in the
275+
CHANGELOG.
276+
- [ ] No uncommitted changes to `package.json`, `pnpm-lock.yaml`,
277+
or `data/`.
278+
279+
## Further reading
280+
281+
- [`docs/contributing.md`](./contributing.md) — the pre-PR
282+
workflow that must land cleanly before any release is possible.
283+
- [`docs/hardening.md`](./hardening.md) — the hostile-input
284+
posture that makes Socket.dev post-publish scans meaningful.
285+
- [`scripts/publish.mts`](../scripts/publish.mts) — the local
286+
publish script (mainly for CI; `prepublishOnly` blocks local
287+
runs).
288+
- [`scripts/bump.mts`](../scripts/bump.mts) — version-bumping
289+
logic.
290+
- [`.github/workflows/provenance.yml`](../.github/workflows/provenance.yml)
291+
— the wrapper workflow.
292+
- [npm docs: provenance](https://docs.npmjs.com/generating-provenance-statements)
293+
— upstream documentation on the attestation format.
294+
- [sigstore.dev](https://www.sigstore.dev/) — the signing /
295+
transparency service.

tour.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,12 @@
197197
"source": "docs/hardening.md",
198198
"summary": "How the library treats PURL strings as hostile input — injection-character detection, frozen instances, and a strong protective stance."
199199
},
200+
{
201+
"filename": "release",
202+
"title": "Release",
203+
"source": "docs/release.md",
204+
"summary": "Publish flow — npm trusted publishing, provenance attestations, Socket.dev post-publish audit, and common release scenarios."
205+
},
200206
{
201207
"filename": "tour",
202208
"title": "Tour",

0 commit comments

Comments
 (0)