Skip to content

Commit ad376cd

Browse files
fix(release): release the daemon package in step with core
@retrigger/daemon peer-depends on the @retrigger/core line, and nothing published it -- so the registry held daemon 1.0.4, asking for core ^1.0.4, while this tag was about to publish core 2.0.0. `npm install @retrigger/core @retrigger/daemon` would then fail outright with ERESOLVE, which is worse than the daemon merely being stale: it breaks the pair for anyone who has both. Reproduced against the registry with the 1.0.2/1.0.4 combination, which fails the same way today. The daemon is a pure-JavaScript shim with no artifact to build here, so publishing it is one job. It runs after core rather than beside it, so the peer it names is already on the registry when anyone can install it, and a new verify job installs the two together with no --force and no --legacy-peer-deps, because that default install is the thing that was broken. The guard grows to match: the tag is checked against both manifests rather than core's alone, and the daemon's peer range has to admit the core major being published -- permissive about range syntax, strict about the major, which is the only part that has ever been wrong. Also drops CHANGELOG.md from the daemon's files list. There is no such file in that directory; npm was silently skipping it.
1 parent afaf7c7 commit ad376cd

3 files changed

Lines changed: 116 additions & 10 deletions

File tree

.github/workflows/release.yml

Lines changed: 110 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,49 @@ jobs:
3030
# The tag is the only input this workflow takes, but `npm publish` ships
3131
# whatever version package.json declares and never looks at it. When the
3232
# two disagree the tag names one release and the registry gets another.
33-
- name: Tag must match the version that will be published
33+
# Both published packages are checked, because one tag releases both and
34+
# @retrigger/daemon peer-depends on the exact @retrigger/core line this
35+
# workflow is about to publish.
36+
- name: Tag must match the versions that will be published
3437
shell: bash
3538
run: |
3639
if [ "$GITHUB_REF_TYPE" != tag ]; then
3740
echo "Not a tag build, so there is no tag to disagree with."
3841
exit 0
3942
fi
4043
tagged="${GITHUB_REF_NAME#v}"
41-
declared=$(node -p "require('./$NODE_DIR/package.json').version")
42-
echo "tag: $tagged / package.json: $declared"
43-
if [ "$tagged" != "$declared" ]; then
44-
echo "::error::tag $GITHUB_REF_NAME would publish version $declared"
45-
exit 1
46-
fi
44+
status=0
45+
for manifest in "$NODE_DIR/package.json" src/daemon/package.json; do
46+
declared=$(node -p "require('./$manifest').version")
47+
name=$(node -p "require('./$manifest').name")
48+
echo "tag: $tagged / $name: $declared"
49+
if [ "$tagged" != "$declared" ]; then
50+
echo "::error::tag $GITHUB_REF_NAME would publish $name@$declared"
51+
status=1
52+
fi
53+
done
54+
exit $status
55+
56+
# @retrigger/daemon declares the core line it works with. A release that
57+
# moved core's major without moving that range would publish a pair that
58+
# npm refuses to install together -- which is exactly what shipping core
59+
# 2.0.0 against the 1.0.4 daemon would have done.
60+
- name: The daemon must accept the core version being published
61+
shell: bash
62+
run: |
63+
node -e '
64+
const core = require(`./${process.env.NODE_DIR}/package.json`).version;
65+
const range = require("./src/daemon/package.json").peerDependencies["@retrigger/core"];
66+
const wanted = core.split(".")[0];
67+
// Deliberately permissive about range syntax and strict about the
68+
// only thing that has ever been wrong here: the major.
69+
const named = [...range.matchAll(/(\d+)\.\d+\.\d+/g)].map((m) => m[1]);
70+
if (!named.includes(wanted)) {
71+
console.error(`::error::daemon peer range "${range}" does not admit core ${core}`);
72+
process.exit(1);
73+
}
74+
console.log(`daemon accepts core ${core} via "${range}"`);
75+
'
4776
4877
# Releases are cut from tags, and CI runs on branches and pull requests --
4978
# so nothing in this repository previously established that the commit
@@ -293,6 +322,47 @@ jobs:
293322
env:
294323
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
295324

325+
# @retrigger/daemon is a pure-JavaScript shim -- it has no artifact of its own
326+
# to build here, because the per-platform Rust binaries it looks for are not
327+
# published yet and it is written to degrade politely when they are absent.
328+
# It still has to be released in step with core: it peer-depends on the core
329+
# line, so leaving it a major version behind makes `npm install` of the two
330+
# together fail outright with ERESOLVE.
331+
#
332+
# It goes after core rather than beside it, so the peer it names already
333+
# exists on the registry by the time anyone can install it.
334+
publish-daemon:
335+
name: Publish the daemon package
336+
runs-on: ubuntu-latest
337+
needs: publish
338+
permissions:
339+
contents: read
340+
id-token: write
341+
steps:
342+
- uses: actions/checkout@v7
343+
344+
- uses: actions/setup-node@v7
345+
with:
346+
node-version: 22
347+
registry-url: 'https://registry.npmjs.org'
348+
349+
# The shim, the shipped config, and the documented no-binary degradation.
350+
# Cheap, and it is the whole package.
351+
- name: Smoke test the shim
352+
run: node scripts/test-daemon.js
353+
working-directory: src/daemon
354+
355+
- name: Show what would be published
356+
run: npm pack --dry-run
357+
working-directory: src/daemon
358+
359+
- name: Publish
360+
if: github.event_name == 'push' || inputs.dry_run == false
361+
run: npm publish --access public --provenance
362+
working-directory: src/daemon
363+
env:
364+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
365+
296366
# Installs the just-published package from the real registry on every OS and
297367
# confirms it works there. Until this passes, a release is not proven; it is
298368
# only uploaded.
@@ -372,10 +442,42 @@ jobs:
372442
console.log('ok');
373443
"
374444
445+
# The two packages are released together because npm resolves them together.
446+
# A default `npm install` of both is the exact command that fails with
447+
# ERESOLVE when the daemon's peer range trails core's major, so it is the
448+
# command that proves the pair -- no --force, no --legacy-peer-deps.
449+
verify-published-pair:
450+
name: Verify install / core + daemon
451+
runs-on: ubuntu-latest
452+
needs: publish-daemon
453+
if: github.event_name == 'push'
454+
steps:
455+
- uses: actions/setup-node@v7
456+
with: { node-version: 22 }
457+
458+
- name: Wait for registry propagation
459+
run: sleep 45
460+
461+
- name: Install both from the registry
462+
shell: bash
463+
run: |
464+
mkdir -p "$RUNNER_TEMP/pair" && cd "$RUNNER_TEMP/pair"
465+
npm init -y >/dev/null
466+
version="${GITHUB_REF_NAME#v}"
467+
npm install "@retrigger/core@$version" "@retrigger/daemon@$version"
468+
node -e "
469+
const core = require('@retrigger/core');
470+
require('@retrigger/daemon');
471+
if (core.getEngineInfo().engine !== 'native') {
472+
throw new Error('expected the native engine alongside the daemon');
473+
}
474+
console.log('ok');
475+
"
476+
375477
github-release:
376478
name: GitHub release
377479
runs-on: ubuntu-latest
378-
needs: [build, build-musl, publish]
480+
needs: [build, build-musl, publish, publish-daemon]
379481
if: startsWith(github.ref, 'refs/tags/v')
380482
permissions:
381483
contents: write

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ machine it was built on.
7373
back. The release now publishes the platform packages and then installs the
7474
result from the registry on every supported OS, failing if the native engine
7575
is not the one that loads.
76+
- `@retrigger/core` and `@retrigger/daemon` are released in step. The daemon
77+
peer-depends on the core line, so a release that moved core's major and left
78+
the daemon behind would make `npm install` of the two together fail outright
79+
with `ERESOLVE`. One tag now publishes both, and the release proves a default
80+
install of the pair before it finishes.
7681
- Linux delivered no events at all — the old Zig watcher never armed its inotify
7782
thread — and the paths that did arrive were corrupt, because `FileEvent` used
7883
a fat pointer where Rust read a thin one.

src/daemon/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515
"bin/retrigger.js",
1616
"config/",
1717
"scripts/",
18-
"README.md",
19-
"CHANGELOG.md"
18+
"README.md"
2019
],
2120
"engines": {
2221
"node": ">=18.0.0"

0 commit comments

Comments
 (0)