Skip to content

Commit 83017cb

Browse files
mm-jpooleclaude
andcommitted
Assert tool versions in CI and document unsupported platforms
Addresses code review on #1775. The `node --version` step added with the pnpm conversion only printed the version, so a mise-shadowed Node would have gone undetected unless a human read the log. test.yml now compares the running major against the matrix value and fails on mismatch. The matrix value is passed through env rather than interpolated into the run block, which is the form zizmor's template-injection audit flags. lint.yml has no matrix, so its step is relabelled as the smoke check it actually is. Shadowing is also prevented outright: every mise-action step now sets add_shims_to_path: false, so no mise shims directory reaches PATH. The install step runs `mise install --locked` and fails if `mise which pnpm` returns nothing, rather than appending an empty dirname -- which is `.` -- to $GITHUB_PATH. test.yml and lint.yml also set `cache: false` on mise-action. With install: false, mise-action saves a cache only inside its install branch, so nothing there ever writes one, and the restore it would otherwise still attempt can never hit. release.yml's publish job now asserts npm >= 11.5.1, the invariant that lets it pin Node 24 and skip installing npm globally. A Node downgrade would otherwise only surface during a real release. The step sits immediately before `npm publish`, so nothing can change PATH in between. mise.toml's lockfile_platforms comment claimed more than the setting delivers. `mise lock` always locks the platform it runs on, even one excluded from that list -- verified by dropping the current platform and regenerating, which re-added it. So the list is not a hard guard, and the comment now says so and points at the post-regeneration check. Records that Intel macOS and Windows ARM64 are unsupported, so excluding macos-x64 is not a loss of support. Intel macOS could not work regardless: pnpm 11.0.5 and later ship no darwin-x64 binary, which is why mise falls back to the release's unrelated source-maps.tgz there. README.dev.md gains a supported platforms section. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 467985e commit 83017cb

5 files changed

Lines changed: 106 additions & 58 deletions

File tree

‎.github/workflows/lint.yml‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,25 @@ jobs:
2020
node-version: 22
2121
package-manager-cache: false
2222
# Install only pnpm, not the repo's full toolchain — Node comes from
23-
# actions/setup-node, which pins the version this job needs.
24-
# Appending only pnpm's directory to PATH keeps mise's own Node
25-
# (mise.toml pins "latest") from ever shadowing setup-node's.
23+
# actions/setup-node. See mise.toml [tools] comment for why.
2624
- name: Setup mise
2725
uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4
2826
with:
2927
install: false
28+
add_shims_to_path: false
29+
# mise-action saves a cache only inside its install branch, so with
30+
# install: false nothing here ever writes one. The restore is gated
31+
# separately and would still run, always missing, so turn it off.
32+
cache: false
3033
- name: Install pnpm
3134
env:
3235
MISE_AUTO_INSTALL: 'false'
3336
run: |
34-
mise install github:pnpm/pnpm
35-
dirname "$(mise which pnpm)" >> "$GITHUB_PATH"
36-
# Sanity check: pnpm resolves, and mise has not shadowed setup-node's Node.
37+
mise install --locked github:pnpm/pnpm
38+
pnpm_bin=$(mise which pnpm)
39+
[ -x "$pnpm_bin" ] || { echo "mise which pnpm produced no usable path" >&2; exit 1; }
40+
dirname "$pnpm_bin" >> "$GITHUB_PATH"
41+
# Smoke check only; there is no matrix here to get wrong.
3742
- run: node --version && pnpm --version
3843
- name: Install packages
3944
run: pnpm install --frozen-lockfile

‎.github/workflows/release.yml‎

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,26 +24,26 @@ jobs:
2424
node-version: 22
2525
package-manager-cache: false
2626
# Install only pnpm, not the repo's full toolchain — Node comes from
27-
# actions/setup-node, which pins the version this job needs.
28-
# Appending only pnpm's directory to PATH keeps mise's own Node
29-
# (mise.toml pins "latest") from ever shadowing setup-node's.
27+
# actions/setup-node. See mise.toml [tools] comment for why.
3028
- name: Setup mise
3129
uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4
3230
with:
3331
install: false
34-
# Caching is disabled in this workflow only. zizmor's cache-poisoning
35-
# audit flags a restorable cache in a job that publishes artifacts
36-
# built at runtime, which is also why the setup-node steps here set
37-
# package-manager-cache: false. test.yml and lint.yml leave the
38-
# default, which zizmor does not flag -- though with install: false
39-
# mise-action never writes a cache there either.
32+
add_shims_to_path: false
33+
# zizmor's cache-poisoning audit flags a restorable cache in a job
34+
# that publishes artifacts built at runtime, which is also why the
35+
# setup-node steps here set package-manager-cache: false. test.yml
36+
# and lint.yml set it too, there just to skip a restore that can
37+
# never hit: with install: false mise-action never saves one.
4038
cache: false
4139
- name: Install pnpm
4240
env:
4341
MISE_AUTO_INSTALL: 'false'
4442
run: |
45-
mise install github:pnpm/pnpm
46-
dirname "$(mise which pnpm)" >> "$GITHUB_PATH"
43+
mise install --locked github:pnpm/pnpm
44+
pnpm_bin=$(mise which pnpm)
45+
[ -x "$pnpm_bin" ] || { echo "mise which pnpm produced no usable path" >&2; exit 1; }
46+
dirname "$pnpm_bin" >> "$GITHUB_PATH"
4747
- run: pnpm install --frozen-lockfile
4848
- run: pnpm test
4949
- run: pnpm run lint
@@ -65,42 +65,44 @@ jobs:
6565
persist-credentials: false
6666
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
6767
with:
68-
# Node 24 bundles npm 11.17.0, above the 11.5.1 minimum for npm
69-
# Trusted Publishing. On Node 22 (npm 10.9.8) this job had to run
70-
# `npm install -g npm@latest`, which tripped zizmor's adhoc-packages
71-
# rule for installing outside a lockfile.
68+
# npm Trusted Publishing needs npm >= 11.5.1 and Node >= 22.14.0.
69+
# Node 24 bundles a new enough npm; Node 22 does not, which is why
70+
# `npm install -g npm@latest` is gone (zizmor adhoc-packages). The
71+
# bundled version moves each 24.x, so it is asserted before publish.
7272
node-version: 24
7373
package-manager-cache: false
7474
# Writes the .npmrc that OIDC publishing needs.
7575
registry-url: 'https://registry.npmjs.org'
7676
# Install only pnpm, not the repo's full toolchain — Node comes from
77-
# actions/setup-node, which pins the version this job needs.
78-
# Appending only pnpm's directory to PATH keeps mise's own Node
79-
# (mise.toml pins "latest") from ever shadowing setup-node's.
77+
# actions/setup-node. See mise.toml [tools] comment for why.
8078
- name: Setup mise
8179
uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4
8280
with:
8381
install: false
84-
# Caching is disabled in this workflow only. zizmor's cache-poisoning
85-
# audit flags a restorable cache in a job that publishes artifacts
86-
# built at runtime, which is also why the setup-node steps here set
87-
# package-manager-cache: false. test.yml and lint.yml leave the
88-
# default, which zizmor does not flag -- though with install: false
89-
# mise-action never writes a cache there either.
82+
add_shims_to_path: false
83+
# Same reason as the build job above.
9084
cache: false
9185
- name: Install pnpm
9286
env:
9387
MISE_AUTO_INSTALL: 'false'
9488
run: |
95-
mise install github:pnpm/pnpm
96-
dirname "$(mise which pnpm)" >> "$GITHUB_PATH"
89+
mise install --locked github:pnpm/pnpm
90+
pnpm_bin=$(mise which pnpm)
91+
[ -x "$pnpm_bin" ] || { echo "mise which pnpm produced no usable path" >&2; exit 1; }
92+
dirname "$pnpm_bin" >> "$GITHUB_PATH"
9793
- run: pnpm install --frozen-lockfile
9894
- run: pnpm run build
99-
# Publishing stays on the npm CLI. pnpm has no native OIDC support — it
100-
# shells out to npm for trusted publishing and provenance, and that path
101-
# has known failures (pnpm/pnpm#9812). npm publish is safe in a
102-
# pnpm-installed tree: it packs only `files: ["dist"]` and reads no
103-
# lockfile.
95+
# Assert rather than trust the npm that setup-node's `node-version` pin
96+
# bundled: a Node downgrade would otherwise only surface during a real
97+
# release. Sits immediately before the publish so nothing can change PATH.
98+
- name: Verify npm supports trusted publishing
99+
run: |
100+
npm_version=$(npm --version)
101+
echo "npm $npm_version (need >= 11.5.1 for OIDC trusted publishing)"
102+
printf '11.5.1\n%s\n' "$npm_version" | sort -V -C
103+
# Publishing deliberately stays on the npm CLI; adopting pnpm's own OIDC
104+
# support is a separate follow-up issue. npm publish is safe in a
105+
# pnpm-installed tree: it packs only `files: ["dist"]`, reads no lockfile.
104106
- run: npm publish --provenance
105107
- run: pnpm run build:docs
106108
- name: Deploy docs to gh-pages

‎.github/workflows/test.yml‎

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,36 @@ jobs:
2323
node-version: ${{ matrix.version }}
2424
package-manager-cache: false
2525
# Install only pnpm, not the repo's full toolchain — Node comes from
26-
# actions/setup-node so the version matrix stays in control.
27-
# Appending only pnpm's directory to PATH keeps mise's own Node
28-
# (mise.toml pins "latest") from ever shadowing setup-node's.
26+
# actions/setup-node. See mise.toml [tools] comment for why.
2927
- name: Setup mise
3028
uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4
3129
with:
3230
install: false
31+
add_shims_to_path: false
32+
# mise-action saves a cache only inside its install branch, so with
33+
# install: false nothing here ever writes one. The restore is gated
34+
# separately and would still run, always missing, so turn it off.
35+
cache: false
3336
- name: Install pnpm
3437
env:
3538
MISE_AUTO_INSTALL: 'false'
3639
run: |
37-
mise install github:pnpm/pnpm
38-
dirname "$(mise which pnpm)" >> "$GITHUB_PATH"
39-
# Guard: mise must not shadow the Node that setup-node selected, or the
40-
# matrix would silently test one version twice and still pass.
41-
- run: node --version && pnpm --version
42-
# One root install links the workspace, so e2e/js and e2e/ts resolve
43-
# @maxmind/geoip2-node through a symlink to the repo root. `build` must
44-
# run before them so dist/ exists.
40+
mise install --locked github:pnpm/pnpm
41+
pnpm_bin=$(mise which pnpm)
42+
[ -x "$pnpm_bin" ] || { echo "mise which pnpm produced no usable path" >&2; exit 1; }
43+
dirname "$pnpm_bin" >> "$GITHUB_PATH"
44+
# Assert, don't just print: if mise shadowed the Node that setup-node
45+
# selected, every matrix leg would test the same version and still pass.
46+
- name: Verify the matrix Node is active
47+
env:
48+
EXPECTED_MAJOR: ${{ matrix.version }}
49+
run: |
50+
actual=$(node -p 'process.versions.node.split(".")[0]')
51+
echo "node major: $actual (expected $EXPECTED_MAJOR), pnpm $(pnpm --version)"
52+
test "$actual" = "$EXPECTED_MAJOR"
53+
# One root install links the workspace, so e2e/js and e2e/ts resolve the
54+
# library through a symlink to the repo root. `build` must run before
55+
# them so dist/ exists.
4556
- run: pnpm install --frozen-lockfile
4657
- run: pnpm run test:coverage
4758
- run: pnpm run build
@@ -50,12 +61,10 @@ jobs:
5061
working-directory: e2e/js
5162
- run: pnpm exec vitest run
5263
working-directory: e2e/ts
53-
# Vitest transpiles without checking types, and neither root tsconfig
54-
# covers e2e -- both use include: ["src"] -- so nothing else type-checks
55-
# the TypeScript consumer. That matters here because the failure mode a
56-
# non-hoisting package manager introduces is exactly a type-resolution
57-
# break in the published surface. Must run after `build`: the import
58-
# resolves through the workspace symlink to dist/src/index.d.ts, so with
59-
# no dist/ this reports TS2307 instead of checking anything.
64+
# Nothing else type-checks the TypeScript consumer: vitest transpiles
65+
# without checking, and both root tsconfigs use include: ["src"]. That
66+
# matters because a non-hoisting package manager breaks exactly this --
67+
# type resolution in the published surface. Must run after `build`, or
68+
# the workspace symlink to dist/src/index.d.ts gives TS2307.
6069
- run: pnpm exec tsc --noEmit
6170
working-directory: e2e/ts

‎README.dev.md‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,27 @@
2020
Note: Publishing is done via GitHub Actions using npm Trusted Publishing
2121
(OIDC). Manual `npm publish` is not supported.
2222

23+
## Development setup
24+
25+
Dependencies are managed with pnpm, pinned in `mise.toml`. Run `mise install`
26+
to get it, then `pnpm install`. Do not use npm or corepack: the repo is a pnpm
27+
workspace (`e2e/js` and `e2e/ts` are members) with a single root
28+
`pnpm-lock.yaml`, and npm cannot resolve their `workspace:*` dependency.
29+
30+
Publishing is the one exception. `release.yml` calls `npm publish --provenance`.
31+
pnpm 11 supports OIDC trusted publishing, but that change will be made in a
32+
future issue.
33+
34+
## Supported development platforms
35+
36+
Linux on x64 and arm64, Apple Silicon macOS on arm64, and Windows on x64.
37+
**Intel macOS and Windows ARM64 are not supported.** Intel macOS in particular
38+
cannot be: pnpm 11.0.5 and later ship no `darwin-x64` binary, so there is
39+
nothing for mise to install. `mise.toml` restricts `lockfile_platforms`
40+
accordingly, but note that `mise lock` always locks whichever platform it runs
41+
on -- so if you regenerate `mise.lock`, verify `grep -c source-maps mise.lock`
42+
prints 0 before committing.
43+
2344
## Set up Precious to tidy and lint
2445

2546
1. Run `mkdir -p local && ./bin/install-precious local` to set up Precious locally

‎mise.toml‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,16 @@ disable_backends = [
66
"vfox",
77
]
88

9-
# Excludes macos-x64 only. pnpm 11 ships no darwin-x64 binary at all, so mise
10-
# mis-resolves that platform to the release's unrelated source-maps.tgz asset.
11-
# Every other platform the tools actually support is kept.
9+
# The platforms we support. Intel macOS (macos-x64) and Windows ARM64
10+
# (windows-arm64) are absent because we do not support either. Intel macOS also
11+
# cannot work: pnpm 11.0.5 and later ship no darwin-x64 binary, so mise
12+
# mis-resolves that platform to the release's unrelated source-maps.tgz asset,
13+
# producing a lock entry that verifies but contains no pnpm.
14+
#
15+
# This list is not a hard guard: `mise lock` always locks the platform it runs
16+
# on, excluded or not -- verified by dropping the current platform and
17+
# regenerating, which re-added it. After any regeneration, check
18+
# `grep -c source-maps mise.lock` prints 0.
1219
lockfile_platforms = [
1320
"linux-arm64",
1421
"linux-arm64-musl",
@@ -19,6 +26,10 @@ lockfile_platforms = [
1926
]
2027

2128
[tools]
29+
# CI never installs this. Every workflow runs mise-action with install: false
30+
# and add_shims_to_path: false, then puts only pnpm's install directory on
31+
# PATH. So two things independently keep this "latest" pin out of jobs whose
32+
# Node actions/setup-node fixed: no shims directory, and no node in that dir.
2233
node = "latest"
2334
lychee = "latest"
2435
# pnpm is on the github backend because aqua-registry's pnpm config keeps

0 commit comments

Comments
 (0)