Repository navigation
166 lines (159 loc) · 8.2 KB
/
Copy pathrelease.yml
File metadata and controls
166 lines (159 loc) · 8.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
name: release
on:
push:
tags: ["v*.*.*"]
permissions:
contents: read
# Keyed on the tag, never cancelled. Two runs of the SAME tag race — the second
# would hit a half-published registry — and they are both wanted here: a re-run
# after a transient npm fault is exactly how a release recovers, and the publish
# steps skip versions already on the registry. Cancelling one would leave the
# release in the state where neither run finished, which is the outcome that
# already cost v0.7.0.
concurrency:
group: release-${{ github.ref_name }}
cancel-in-progress: false
jobs:
verify:
name: verify
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# No `version:` input: pnpm/action-setup refuses to run when it is given
# both that and a `packageManager` field, and package.json pins 12.6.0
# exactly. One source of truth, and the action reads it.
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 26
cache: pnpm
- run: pnpm install --frozen-lockfile
# Explicit, not inherited: `lib/` otherwise exists only because the
# `prepare` lifecycle fired during install, so any `--ignore-scripts`
# hardening would break the release with an unrelated error.
- run: pnpm run build
- run: pnpm run check
- run: pnpm run test
- run: node --experimental-strip-types scripts/check.ts
publish:
name: publish
needs: verify
runs-on: ubuntu-latest
# The verification step below polls npm for up to ten minutes by design, so
# this is the floor: a genuine stall should fail rather than hang.
timeout-minutes: 30
permissions:
contents: read
id-token: write
packages: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# No `version:` input: pnpm/action-setup refuses to run when it is given
# both that and a `packageManager` field, and package.json pins 12.6.0
# exactly. One source of truth, and the action reads it.
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 26
cache: pnpm
registry-url: https://registry.npmjs.org
- run: pnpm install --frozen-lockfile
- name: tag matches package.json version
run: |
test "v$(node -p "require('./package.json').version")" = "${GITHUB_REF_NAME}" \
|| { echo "tag ${GITHUB_REF_NAME} != package.json version"; exit 1; }
- run: pnpm run build
# What actually ships, pinned by hash and kept as an artifact. npm's own
# provenance attestation covers WHERE it was built and by whom; it does not
# let a consumer confirm they received those exact bytes. This does, and
# it is the same tarball every registry below receives.
- name: pack the release tarball and record its digest
id: pack
run: |
VER=$(node -p "require('./package.json').version")
mkdir -p dist
# The `files` allowlist, so the tarball cannot silently gain or lose a
# file relative to what `npm publish` would send.
npm pack --ignore-scripts --pack-destination dist >/dev/null
TARBALL="dist/dsh-opencode-patch-${VER}.tgz"
test -f "$TARBALL" || { echo "::error::expected $TARBALL"; exit 1; }
shasum -a 256 "$TARBALL" | tee dist/SHA256SUMS
echo "tarball=$TARBALL" >> "$GITHUB_OUTPUT"
# A release that ships a tarball nobody can unpack is worse than one
# that fails here, and the failure is visible in the logs.
mkdir -p /tmp/verify && tar -tzf "$TARBALL" > /tmp/verify/entries.txt
grep -qx 'package/lib/index.mjs' /tmp/verify/entries.txt \
|| { echo "::error::tarball is missing lib/index.mjs"; exit 1; }
grep -qx 'package/lib/client.js' /tmp/verify/entries.txt \
|| { echo "::error::tarball is missing lib/client.js"; exit 1; }
- name: upload the release tarball
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: release-tarball
path: dist
retention-days: 90
# OIDC trusted publishing: no token needed. Requires the
# viztor/dsh-opencode-patch + release.yml publisher registered on npmjs.com
# with `npm publish` allowed. Provenance is automatic.
#
# Every publish step fails loudly. A swallowed error here once produced a
# green release for a version that never reached the registry, and the
# version was simply lost (v0.7.0). The `npm view` guard is the only place
# idempotency belongs; the final verification step is what proves the
# claim, and it runs even when a publisher is missing.
- name: publish to npmjs (OIDC)
run: |
VER=$(node -p "require('./package.json').version")
if npm view "dsh-opencode-patch@$VER" version 2>/dev/null; then
echo "$VER already on npmjs, skipping"
else
npm publish --provenance --access public --ignore-scripts
fi
# The scoped names are separate packages to the registry, so each needs
# its own Trusted Publisher entry. This step is allowed to fail so the
# GitHub Packages mirror below still runs; the verification step at the
# end turns any gap into a failed release that names the missing target.
- name: publish the scoped aliases (OIDC, provenance)
continue-on-error: true
run: node --experimental-strip-types scripts/publish-scoped.ts
# Mirror the scoped names to GitHub Packages so the repo sidebar populates.
# Unscoped packages are rejected by GHP; scoped packages mirror cleanly.
- name: mirror the scoped aliases to GitHub Packages
run: |
export NODE_AUTH_TOKEN=${{ secrets.GITHUB_TOKEN }}
PUBLISH_REGISTRY=https://npm.pkg.github.com node --experimental-strip-types scripts/publish-scoped.ts
# The release claim, checked instead of assumed. Without this the job
# reported success while `@viztor/dsh-opencode*` sat four versions behind
# the canonical package, because a publisher that was never registered
# fails with the same E404 as a transient fault.
- name: verify every published target
run: |
VER=$(node -p "require('./package.json').version")
pending="dsh-opencode-patch@$VER @viztor/dsh-opencode-patch@$VER @viztor/dsh-opencode@$VER"
# Polls, because a successful `npm publish` does not mean the version
# is readable yet: npm answers "Your package is being processed and
# may take a few minutes to become available", and an immediate
# `npm view` returns nothing. Checking once reported a healthy release
# as failed. Indexing has taken over six minutes for the unscoped
# primary, so the window is ten minutes; the existing-version skips
# in the publish steps keep a re-run cheap when this still runs out.
for attempt in $(seq 1 40); do
still=""
for spec in $pending; do
if npm view "$spec" version --registry=https://registry.npmjs.org >/dev/null 2>&1; then
echo "ok $spec (npmjs.org)"
else
still="$still $spec"
fi
done
pending=$(echo "$still" | xargs)
if [ -z "$pending" ]; then
echo "every published target is live on npmjs.org"
exit 0
fi
echo "attempt $attempt/40: waiting for npm to index:$pending"
sleep 15
done
echo "::error::these targets did not publish:$pending — register a Trusted Publisher for each at https://www.npmjs.com/package/<name>/access (repository viztor/dsh-opencode-patch, workflow release.yml). If the publish steps above passed, npm may still be indexing: a re-run of this job is safe, it skips versions already on the registry."
exit 1