Skip to content

Commit 0400870

Browse files
docs: expand coverage and add optional GH_TOKEN fallback
- Document optional GH_TOKEN secret for auto-release chain (PAT fallback) - Add JSR_TOKEN setup and first-publish sections to README - Add npm-publish comparison section - Add PR process to CONTRIBUTING - Add issue and PR templates - Refresh llms.txt/llms-full.txt and package.json keywords
1 parent 93676d7 commit 0400870

9 files changed

Lines changed: 677 additions & 13 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
name: Feature request
3+
about: Suggest a new gate, input, or capability
4+
labels: enhancement
5+
---
6+
7+
**What problem does this solve?**
8+
9+
Describe the scenario where anvil falls short today. Keep it concrete --
10+
"a library author who X ends up with Y" beats an abstract wish list.
11+
12+
**Proposed shape**
13+
14+
What input, gate, or workflow change would address it? If it's a new
15+
gate, what does it check and what does it fail on?
16+
17+
**Threat-model fit**
18+
19+
Does this fit within the boundaries in [THREAT-MODEL.md](../../THREAT-MODEL.md)?
20+
If it expands the trust surface, call that out -- threat-model-expanding
21+
changes need explicit justification.
22+
23+
**Audit budget impact**
24+
25+
Rough line-count estimate for the addition. The total bash surface area
26+
is a hard ~1600-line budget (thirty-minute audit target).
27+
28+
**Alternatives considered**
29+
30+
Other tools, workflow patterns, or existing gates that partially cover
31+
the problem today.

.github/pull_request_template.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!--
2+
Thanks for contributing. Keep the description short; link to relevant
3+
THREAT-MODEL entries if this changes a gate.
4+
-->
5+
6+
## What
7+
8+
<!-- One or two sentences on what this PR does. -->
9+
10+
## Why
11+
12+
<!-- The motivation. Link to an issue if one exists. -->
13+
14+
## Notes
15+
16+
<!-- Anything a reviewer should know. Delete the checklist lines that
17+
don't apply. -->
18+
19+
- [ ] Tests added / updated (`test/*.bats`)
20+
- [ ] `bats test/*.bats` passes locally
21+
- [ ] `shellcheck -x steps/*.sh` clean
22+
- [ ] No new runtime dependencies outside the GitHub Actions runner image
23+
- [ ] Audit-budget impact acceptable (line-count delta noted if > ~50 lines)
24+
- [ ] THREAT-MODEL.md updated if this changes a gate boundary

.github/workflows/auto-release.yml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,15 @@ on:
4545
description: Determine version and changelog but do not commit, tag, or create a release
4646
type: string
4747
default: 'false'
48+
secrets:
49+
GH_TOKEN:
50+
description: |
51+
Optional GitHub App token or PAT with contents: write scope.
52+
If provided, the push and GitHub Release creation will use this
53+
token instead of the default GITHUB_TOKEN. Required if you want
54+
the created Release to trigger release.yml automatically --
55+
the default GITHUB_TOKEN cannot trigger further workflow runs.
56+
required: false
4857

4958
jobs:
5059
determine:
@@ -106,9 +115,9 @@ jobs:
106115
fetch-depth: 0
107116
# Default GITHUB_TOKEN can push but cannot trigger further
108117
# workflow runs. If the consumer wants the push to trigger
109-
# release.yml, they must pass a PAT or GitHub App token via
110-
# secrets. See README "Auto mode" section.
111-
token: ${{ secrets.GITHUB_TOKEN }}
118+
# release.yml, they pass a PAT or GitHub App token via the
119+
# GH_TOKEN secret. See README "Auto" version strategy section.
120+
token: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }}
112121

113122
- name: Configure git
114123
run: |
@@ -202,7 +211,7 @@ jobs:
202211
env:
203212
NEXT_VERSION: ${{ needs.determine.outputs.next_version }}
204213
CHANGELOG: ${{ needs.determine.outputs.changelog }}
205-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
214+
GH_TOKEN: ${{ secrets.GH_TOKEN || secrets.GITHUB_TOKEN }}
206215
run: |
207216
gh release create "v${NEXT_VERSION}" \
208217
--title "v${NEXT_VERSION}" \

CONTRIBUTING.md

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,48 @@ holds after the addition.
1111
Prerequisites: `bash`, `jq`, `npm`, `gh`, `shellcheck`, `bats`.
1212

1313
```sh
14-
# Run the full test suite (71 tests)
14+
# Run the full test suite
1515
bats test/*.bats
1616

1717
# Lint all step scripts
1818
shellcheck -x steps/*.sh
1919
```
2020

21+
## Pull requests
22+
23+
1. Branch from `main`. Name the branch `type/short-description`
24+
(e.g. `fix/verify-exports-windows`, `feat/gate-for-banned-licences`).
25+
2. Write a bats test first when adding or changing gate behaviour.
26+
Every step script has a matching `test/<name>.bats` fixture.
27+
3. Commit style: `type: description` (lowercase, imperative). No
28+
`Co-Authored-By`. Examples: `fix: handle empty exports map`,
29+
`docs: clarify JSR_TOKEN scope`.
30+
4. Run `bats test/*.bats` and `shellcheck -x steps/*.sh` locally
31+
before pushing. CI runs the same checks.
32+
5. Open the PR against `main`. Keep the description short -- link to
33+
the relevant THREAT-MODEL entry if the change affects a gate, and
34+
mention the line-count delta if the change is non-trivial (the
35+
thirty-minute audit budget is a hard constraint).
36+
6. A solo maintainer reviews and merges. Expect feedback within a day
37+
or two; longer for design-impacting changes.
38+
39+
## Proposing larger changes
40+
41+
For anything that adds a new gate, changes a trust boundary, or grows
42+
the line count by more than ~50 lines, open an issue first with the
43+
proposed design. The audit budget means every addition has to earn
44+
its place; discussing the shape before writing the code saves everyone
45+
time.
46+
2147
## Non-goals
2248

2349
- Automated commit analysis or semver determination from commit messages
50+
as a release-blocking step (the `auto-release.yml` companion workflow
51+
handles this outside the gate pipeline)
2452
- Changelog generation as a release-blocking step
2553
- Node-based tooling inside the action itself
2654
- Dependencies that are not already on the default GitHub Actions runner image
2755

28-
## Commit style
29-
30-
`type: description` (lowercase, imperative). No `Co-Authored-By`.
31-
3256
## Reporting vulnerabilities
3357

3458
Security issues should be reported via GitHub Security Advisories at

README.md

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,20 @@ when warranted. Zero dependencies, zero config files.
167167

168168
**Note:** The default `GITHUB_TOKEN` can create releases but cannot
169169
trigger further workflow runs. If you need the auto-created release to
170-
trigger `release.yml` automatically, use a GitHub App token or PAT
171-
with `contents: write` scope.
170+
trigger `release.yml` automatically, pass a GitHub App token or PAT
171+
with `contents: write` scope via the `GH_TOKEN` secret:
172+
173+
```yaml
174+
jobs:
175+
auto-release:
176+
uses: forgesworn/anvil/.github/workflows/auto-release.yml@v0
177+
secrets:
178+
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
179+
```
180+
181+
Store the PAT as a repo secret (e.g. `RELEASE_PAT`). Without this,
182+
the created Release still appears in GitHub but won't trigger
183+
`release.yml`.
172184

173185
## What the action does
174186

@@ -268,7 +280,7 @@ no reproducibility check). Use the reusable workflow as the default.
268280
| `audit-level` | `low` | `npm audit` severity floor |
269281
| `version-strategy` | `manual` | One of `manual`, `verify`. `manual` is the default: you bump, you tag, the action publishes. `verify` parses conventional commits and fails if your bump is smaller than what the commits imply. For fully automatic versioning, use the companion `auto-release.yml` workflow instead. |
270282
| `strict-action-pins` | `true` | If `true` (the default), **verify-action-pins** fails the release on any unpinned `uses:` reference in `.github/workflows`. Set to `false` for warn-only mode. `forgesworn/anvil` is exempt by name. |
271-
| `reproducibility-mode` | `strict` | One of `strict`, `warn`, `off`. `strict` blocks the release if the two parallel builds produce different sha256s. `warn` logs the mismatch but publishes. `off` skips the second build entirely (v0.3 single-runner behaviour). |
283+
| `reproducibility-mode` | `strict` | Reusable workflow only. One of `strict`, `warn`, `off`. `strict` blocks the release if the two parallel builds produce different sha256s. `warn` logs the mismatch but publishes. `off` skips the second build entirely (v0.3 single-runner behaviour). The composite action silently ignores this input (it cannot run the two-build DAG; see "Advanced: composite action directly"). |
272284
| `dry-run` | `false` | Skip real publish (for smoke-testing) |
273285
| `debug` | `false` | If `true`, run a diagnostic step before publish that dumps npm version, redacted `.npmrc`, OIDC env vars, and `npm config list`. Flip this on when debugging trusted-publisher errors -- see "Trusted publisher caveat". Does not print token values. |
274286

@@ -277,6 +289,33 @@ no reproducibility check). Use the reusable workflow as the default.
277289
| Secret | When needed |
278290
|---|---|
279291
| `JSR_TOKEN` | Only if `jsr.json` exists. JSR does not yet support OIDC. |
292+
| `GH_TOKEN` | Only for `auto-release.yml` when you want the auto-created Release to trigger `release.yml`. See "Version strategy -> Auto". |
293+
294+
### JSR_TOKEN setup
295+
296+
If your package publishes to JSR alongside npm, add a `jsr.json` in the
297+
repo root and provide a `JSR_TOKEN` secret.
298+
299+
1. Generate the token at [jsr.io/account/tokens](https://jsr.io/account/tokens).
300+
Choose **Personal access token** with the `publish` scope for the
301+
specific package (or `publish` on the whole org). Short-lived tokens
302+
are preferred -- rotate whenever convenient.
303+
2. Add the token as a repo secret named `JSR_TOKEN` under
304+
Settings -> Secrets and variables -> Actions.
305+
3. Pass it through from your caller workflow:
306+
307+
```yaml
308+
jobs:
309+
release:
310+
uses: forgesworn/anvil/.github/workflows/release.yml@v0
311+
secrets:
312+
JSR_TOKEN: ${{ secrets.JSR_TOKEN }}
313+
```
314+
315+
JSR does not yet support OIDC trusted publishing; the token is the
316+
only authentication path today. The action skips JSR publish entirely
317+
when `jsr.json` is absent, so existing npm-only consumers are
318+
unaffected.
280319

281320
## CHANGELOG format
282321

@@ -420,6 +459,23 @@ Configure on npmjs.com → your package → Settings → Trusted Publisher:
420459
| Workflow filename | **your caller workflow file** (e.g. `release.yml`) |
421460
| Environment | (leave empty) |
422461

462+
### First publish of a new package
463+
464+
npm's trusted publisher flow requires the package to already exist on
465+
the registry. For a brand-new package that has never been published,
466+
do a one-time manual publish first:
467+
468+
```sh
469+
# From your workstation, with a granular access token scoped to publish
470+
npm publish --access public
471+
```
472+
473+
Then configure trusted publishing on npmjs.com for all subsequent
474+
releases. The manual token can be revoked after the first publish --
475+
from that point on, OIDC handles everything.
476+
477+
### Why the caller-workflow trust model
478+
423479
The reusable workflow still gets you centralised gate logic -- one place
424480
to update tag-match, secret scan, exports sanity, frozen-vector check,
425481
runtime audit, etc., across every consumer. That's the real benefit.

docs/comparison.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,67 @@ If you prefer the interactive local workflow, np is the right tool.
206206

207207
---
208208

209+
### vs JS-DevTools/npm-publish (666 stars, GitHub Action)
210+
211+
JS-DevTools/npm-publish is the dominant "just publish" action. It
212+
detects version bumps in package.json, runs npm publish, and exits.
213+
v4 supports OIDC trusted publishing. No gates beyond tag/version
214+
detection. Node-based.
215+
216+
**What JS-DevTools/npm-publish does better:**
217+
- Battle-tested (666 stars, widely used)
218+
- Simpler mental model when all you want is publish
219+
- Handles the version-detection heuristic well
220+
221+
**What anvil does better:**
222+
- Pre-publish gates: secret scan, exports check, frozen vectors,
223+
runtime audit, action-pin audit (JS-DevTools offers none of these)
224+
- Reproducible-build attestation (two-runner)
225+
- SLSA provenance required by default, not opt-in
226+
- Pure bash vs Node runtime (smaller attack surface)
227+
- Tarball integrity stamped into Release body + uploaded as asset
228+
229+
**The philosophy gap:**
230+
JS-DevTools/npm-publish publishes. anvil publishes safely.
231+
If the only bar is "get bytes onto the registry with OIDC",
232+
JS-DevTools/npm-publish is fine. If the bar includes "no accidental
233+
secret leak, no broken exports, no silent non-determinism,
234+
no compromised third-party action in your pipeline", anvil is
235+
the deliberate upgrade.
236+
237+
**Who should switch:**
238+
Authors who adopted JS-DevTools/npm-publish for its simplicity but
239+
now want supply-chain guarantees on top of OIDC.
240+
241+
**Migration effort:** Minimal. Swap the caller workflow;
242+
OIDC trusted publisher config carries over unchanged.
243+
244+
---
245+
246+
### vs pascalgn/npm-publish-action (227 stars, GitHub Action)
247+
248+
pascalgn/npm-publish-action is a simpler "auto-detect version,
249+
publish" action. No OIDC support last time checked; relies on
250+
long-lived NPM_TOKEN. No gates.
251+
252+
**What pascalgn does better:**
253+
- Nothing relevant to the anvil user profile.
254+
255+
**What anvil does better:**
256+
- OIDC vs stored NPM_TOKEN (the attack vector this tool predates)
257+
- All pre-publish gates
258+
- Reproducible builds, provenance, integrity stamping
259+
260+
**Who should switch:**
261+
Anyone using pascalgn/npm-publish-action who hasn't yet migrated
262+
off long-lived NPM_TOKEN. The security gap vs modern tooling is
263+
significant.
264+
265+
**Migration effort:** Minimal on the action side; revoke NPM_TOKEN
266+
and configure trusted publishing on npmjs.com.
267+
268+
---
269+
209270
## Feature matrix: supply-chain hardening
210271

211272
This is where anvil's differentiation is clearest.

0 commit comments

Comments
 (0)