From 368a2c1bff701540123e67f52f1c472962582beb Mon Sep 17 00:00:00 2001 From: Michael Johnston Date: Sat, 14 Nov 2026 01:01:02 -0700 Subject: [PATCH 1/5] fix(tooling): align downstream launchers and CI with Node 26 Move package commands, installed launchers, CI, and setup examples to Node 26.12.1. Package builds resolve the selected tag's own CI runtime so existing installables remain buildable. Keep the pilot rejection assertion and isolate the merge-lock hook fixture from global Git settings. Carry-Group: build-ci Carry-Fix: https://github.com/lastobelus/lastCode/issues/665 Carry-Upstream: https://redirect.github.com/pingdotgg/t3code/pull/23165 Carry-Observation: Runtime setup follows upstream Node alignment commit ed9b7ca9c53843ea1e6b466bf7d52928d10e7db0; historical package tags retain their own CI runtime. --- .github/workflows/ci.yml | 2 - .../workflows/server-test-pilot-worker.yml | 6 +-- README.md | 2 +- docs/lastcode/nightly-workflow.md | 2 +- docs/lastcode/release.md | 2 +- docs/lastcode/setup.md | 2 +- package.json | 38 +++++++++---------- scripts/lastcode-build.mjs | 4 +- scripts/lastcode-build.test.mjs | 2 +- scripts/lastcode-checkpoints.mjs | 4 +- scripts/lastcode-checkpoints.test.mjs | 2 +- scripts/lastcode-ci-workflow.test.ts | 3 +- scripts/lastcode-install.mjs | 4 +- scripts/lastcode-install.test.mjs | 2 +- scripts/lastcode-intel-release.test.mjs | 2 +- scripts/lastcode-local-ci.test.ts | 11 ++++-- scripts/lastcode-local-update.mjs | 8 +++- scripts/lastcode-local-update.test.ts | 29 +++++++++----- scripts/lastcode-main-write-lock.test.ts | 1 + scripts/lastcode-node-launch.test.ts | 2 +- scripts/lib/server-test-pilot.mjs | 2 +- scripts/lib/server-test-pilot.test.mjs | 6 +-- scripts/prepare-dependencies.test.mjs | 2 +- scripts/server-test-pilot.ts | 4 +- 24 files changed, 80 insertions(+), 62 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44f11bf580d21..25324e1cdd8e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -375,8 +375,6 @@ jobs: TEST_PACKAGES: ${{ needs.changes.outputs.test_packages }} run: | set -o pipefail - # LastCode scripts invoke CLIs that require Node ^24.13.1. - vp env use 24.13.1 filters=(--filter '!t3' --filter '!@t3tools/monorepo' --filter '!@t3tools/web') if [[ -n "$TEST_PACKAGES" ]]; then filters=() diff --git a/.github/workflows/server-test-pilot-worker.yml b/.github/workflows/server-test-pilot-worker.yml index ae7b51e4300a6..b559d4d17c755 100644 --- a/.github/workflows/server-test-pilot-worker.yml +++ b/.github/workflows/server-test-pilot-worker.yml @@ -31,9 +31,9 @@ jobs: install-args: --frozen-lockfile - name: Select checkpoint pilot Node runtime run: | - vp env use 24.13.1 - echo 'VP_NODE_VERSION=24.13.1' >> "$GITHUB_ENV" - vp env exec --node 24.13.1 node -p 'require("node:path").dirname(process.execPath)' >> "$GITHUB_PATH" + vp env use 26.12.1 + echo 'VP_NODE_VERSION=26.12.1' >> "$GITHUB_ENV" + vp env exec --node 26.12.1 node -p 'require("node:path").dirname(process.execPath)' >> "$GITHUB_PATH" - name: Install Chromium and system dependencies run: node apps/server/node_modules/playwright-core/cli.js install --with-deps --only-shell chromium - name: Run uncached cohort diff --git a/README.md b/README.md index b55a27143bf20..118246303b1b7 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ git clone git@github.com:YOUR_GITHUB_USER/LastCode.git ~/projects/lastCode cd ~/projects/lastCode git switch lastcode/main git remote add upstream https://github.com/pingdotgg/t3code.git -mise exec node@24.13.1 -- node scripts/lastcode-setup.mjs \ +mise exec node@26.12.1 -- node scripts/lastcode-setup.mjs \ --enable-nightly-writes \ --checkpoint-interval-seconds "$LASTCODE_CHECKPOINT_INTERVAL_SECONDS" ``` diff --git a/docs/lastcode/nightly-workflow.md b/docs/lastcode/nightly-workflow.md index 28ce48eb5c136..2e4ea7a7c6292 100644 --- a/docs/lastcode/nightly-workflow.md +++ b/docs/lastcode/nightly-workflow.md @@ -480,7 +480,7 @@ the dedicated automation worktree in `~/.lastcode/dashboard.json`, so the command works from any directory without depending on a human development worktree. Installation fails instead of recording the current checkout when the automation worktree is missing. The installed launcher uses the repository's -pinned Node 24 runtime through `mise`, independent of the shell's default Node. +pinned Node runtime through `mise`, independent of the shell's default Node. Show the latest eight checkpoint activities, or choose another count: diff --git a/docs/lastcode/release.md b/docs/lastcode/release.md index f44872a117bdc..43d3d40411463 100644 --- a/docs/lastcode/release.md +++ b/docs/lastcode/release.md @@ -475,7 +475,7 @@ For an authorized local Apple Silicon build, first select one exact installable checkpoint or revision in the requesting thread's worktree: ```bash -MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node "$T3CODE_PROJECT_ROOT/scripts/lastcode-build-local-package.ts" select \ +MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node "$T3CODE_PROJECT_ROOT/scripts/lastcode-build-local-package.ts" select \ --tag lastcode/revision/v0.0.34-nightly.20260825.1185.3 ``` diff --git a/docs/lastcode/setup.md b/docs/lastcode/setup.md index 93d6282009de3..bb4439e03d296 100644 --- a/docs/lastcode/setup.md +++ b/docs/lastcode/setup.md @@ -53,7 +53,7 @@ git remote add upstream https://github.com/pingdotgg/t3code.git Run the guarded bootstrap from the checkout: ```bash -MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-setup.mjs \ +MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-setup.mjs \ --enable-nightly-writes \ --checkpoint-daily-at "$LASTCODE_CHECKPOINT_DAILY_AT" \ --checkpoint-time-zone "$LASTCODE_CHECKPOINT_TIME_ZONE" diff --git a/package.json b/package.json index fcb4de21add29..77bd697c69483 100644 --- a/package.json +++ b/package.json @@ -58,29 +58,29 @@ "dist:desktop:win:arm64": "node scripts/build-desktop-artifact.ts --platform win --target nsis --arch arm64", "dist:desktop:win:x64": "node scripts/build-desktop-artifact.ts --platform win --target nsis --arch x64", "release:smoke": "node scripts/release-smoke.ts", - "lastcode:checkpoint": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-checkpoint.ts", - "lastcode:carry-set": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-carry-set.ts", - "lastcode:checkpoints": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-checkpoints.mjs", - "lastcode:build": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-build.mjs", - "lastcode:install": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-install.mjs", - "lastcode:intel-build": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-build-intel-package.ts", - "lastcode:intel-stage": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-intel-stage.mjs", - "lastcode:managed-checkout": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-managed-checkout.mjs", - "lastcode:project-actions": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-project-actions.mjs", - "lastcode:headless-service": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-headless-service.mjs", - "lastcode:setup": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-setup.mjs", - "lastcode:checkpoint:service": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-nightly-service.ts", - "lastcode:build:mac:arm64": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-build-mac.ts --arch arm64", - "lastcode:build:mac:x64": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-build-mac.ts --arch x64", - "lastcode:ci": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-local-ci.ts --full", - "lastcode:ci:quick": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-local-ci.ts --quick", - "lastcode:merge": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-merge.ts", + "lastcode:checkpoint": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-checkpoint.ts", + "lastcode:carry-set": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-carry-set.ts", + "lastcode:checkpoints": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-checkpoints.mjs", + "lastcode:build": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-build.mjs", + "lastcode:install": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-install.mjs", + "lastcode:intel-build": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-build-intel-package.ts", + "lastcode:intel-stage": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-intel-stage.mjs", + "lastcode:managed-checkout": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-managed-checkout.mjs", + "lastcode:project-actions": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-project-actions.mjs", + "lastcode:headless-service": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-headless-service.mjs", + "lastcode:setup": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-setup.mjs", + "lastcode:checkpoint:service": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-nightly-service.ts", + "lastcode:build:mac:arm64": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-build-mac.ts --arch arm64", + "lastcode:build:mac:x64": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-build-mac.ts --arch x64", + "lastcode:ci": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-local-ci.ts --full", + "lastcode:ci:quick": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-local-ci.ts --quick", + "lastcode:merge": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-merge.ts", "lastcode:docs:check": "node scripts/lastcode-docs-features.ts --check", "lastcode:docs:readme": "node scripts/lastcode-docs-features.ts --write-readme", - "lastcode:docs:fixtures": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-public-doc-fixtures.ts", + "lastcode:docs:fixtures": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-public-doc-fixtures.ts", "clean": "rm -rf node_modules apps/*/node_modules packages/*/node_modules apps/*/dist apps/*/dist-electron packages/*/dist .vite-plus apps/*/.vite-plus packages/*/.vite-plus", "sync:repos": "node scripts/sync-reference-repos.ts", - "lastcode:pr-gates": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-pr-gates.ts" + "lastcode:pr-gates": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-pr-gates.ts" }, "devDependencies": { "@coderabbitai/config": "1.2.0", diff --git a/scripts/lastcode-build.mjs b/scripts/lastcode-build.mjs index 63114c87f4848..afdfcef4e7c81 100644 --- a/scripts/lastcode-build.mjs +++ b/scripts/lastcode-build.mjs @@ -144,7 +144,7 @@ function shellQuote(value) { } export function renderLauncher(modulePath) { - return `#!/bin/sh\n# ${BUILD_MANAGED_MARKER}\nexec env MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node ${shellQuote(modulePath)} "$@"\n`; + return `#!/bin/sh\n# ${BUILD_MANAGED_MARKER}\nexec env MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node ${shellQuote(modulePath)} "$@"\n`; } function runGit(repoRoot, args) { @@ -392,7 +392,7 @@ export function installCommandAssets(automationWorktree, home) { ); replaceManagedSymlink(exposed, target); - console.log(`Installed ${target} with the pinned Node 24 runtime`); + console.log(`Installed ${target} with the pinned Node runtime`); console.log(`Exposed on PATH as ${exposed}`); } diff --git a/scripts/lastcode-build.test.mjs b/scripts/lastcode-build.test.mjs index 8da2ebd417d57..8a5da27bc4833 100644 --- a/scripts/lastcode-build.test.mjs +++ b/scripts/lastcode-build.test.mjs @@ -314,7 +314,7 @@ describe("LastCode userland build command", () => { it("launches with the repository's pinned Node runtime", () => { expect(renderLauncher("/tmp/Last Code/lastcode-build.mjs")).toContain( - "env MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node '/tmp/Last Code/lastcode-build.mjs' \"$@\"", + "env MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node '/tmp/Last Code/lastcode-build.mjs' \"$@\"", ); }); }); diff --git a/scripts/lastcode-checkpoints.mjs b/scripts/lastcode-checkpoints.mjs index 15633ed786b25..a73b7c15170a3 100644 --- a/scripts/lastcode-checkpoints.mjs +++ b/scripts/lastcode-checkpoints.mjs @@ -756,7 +756,7 @@ function readCarryReplayPlan(worktree) { } export function renderLauncher(modulePath) { - return `#!/bin/sh\nexec env MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node ${shellQuote(modulePath)} "$@"\n`; + return `#!/bin/sh\nexec env MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node ${shellQuote(modulePath)} "$@"\n`; } function installCommand(repoRoot, home) { @@ -790,7 +790,7 @@ function installCommand(repoRoot, home) { NodeFS.unlinkSync(exposed); } NodeFS.symlinkSync(target, exposed); - console.log(`Installed ${target} with the pinned Node 24 runtime`); + console.log(`Installed ${target} with the pinned Node runtime`); console.log(`Exposed on PATH as ${exposed}`); } diff --git a/scripts/lastcode-checkpoints.test.mjs b/scripts/lastcode-checkpoints.test.mjs index 41774e0174fde..11d4a3f2dad62 100644 --- a/scripts/lastcode-checkpoints.test.mjs +++ b/scripts/lastcode-checkpoints.test.mjs @@ -185,7 +185,7 @@ describe("LastCode checkpoint dashboard", () => { NodeFS.rmSync(directory, { recursive: true, force: true }); } expect(renderLauncher("/tmp/Last Code/checkpoints.mjs")).toContain( - "env MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node '/tmp/Last Code/checkpoints.mjs' \"$@\"", + "env MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node '/tmp/Last Code/checkpoints.mjs' \"$@\"", ); }); diff --git a/scripts/lastcode-ci-workflow.test.ts b/scripts/lastcode-ci-workflow.test.ts index 850ee648aeb9e..acdaec68f401d 100644 --- a/scripts/lastcode-ci-workflow.test.ts +++ b/scripts/lastcode-ci-workflow.test.ts @@ -252,7 +252,7 @@ describe("LastCode GitHub CI workflow", () => { it.each([ { packages: "", filters: ["!t3", "!@t3tools/monorepo", "!@t3tools/web"] }, { packages: "@t3tools/scripts", filters: ["@t3tools/scripts"] }, - ])("selects the supported Node before testing packages '$packages'", ({ packages, filters }) => { + ])("tests packages '$packages' on the project Node", ({ packages, filters }) => { const steps = asRecord(asRecord(asRecord(parse(workflow))?.jobs)?.test)?.steps; if (!Array.isArray(steps)) throw new Error("CI test job is missing its steps."); const script = steps.map(asRecord).find((step) => step?.name === "Test")?.run; @@ -265,7 +265,6 @@ describe("LastCode GitHub CI workflow", () => { ); expect(result.status, result.stderr).toBe(0); expect(result.stdout.trim().split("\n")).toEqual([ - "env use 24.13.1", `run --parallel --concurrency-limit 4 ${filters.map((filter) => `--filter ${filter}`).join(" ")} test`, ]); }); diff --git a/scripts/lastcode-install.mjs b/scripts/lastcode-install.mjs index 0eca24ff579a2..6b24247455781 100644 --- a/scripts/lastcode-install.mjs +++ b/scripts/lastcode-install.mjs @@ -34,7 +34,7 @@ function shellQuote(value) { } export function renderLauncher(modulePath) { - return `#!/bin/sh\n# ${INSTALL_MANAGED_MARKER}\nexec env MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node ${shellQuote(modulePath)} "$@"\n`; + return `#!/bin/sh\n# ${INSTALL_MANAGED_MARKER}\nexec env MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node ${shellQuote(modulePath)} "$@"\n`; } export function parseOptions(argv) { @@ -959,7 +959,7 @@ export function installCommand(home) { NodeFS.writeFileSync(target, renderLauncher(moduleTarget), { encoding: "utf8", mode: 0o755 }); NodeFS.chmodSync(target, 0o755); replaceManagedSymlink(exposed, target); - console.log(`Installed ${target} with the pinned Node 24 runtime`); + console.log(`Installed ${target} with the pinned Node runtime`); console.log(`Exposed on PATH as ${exposed}`); } diff --git a/scripts/lastcode-install.test.mjs b/scripts/lastcode-install.test.mjs index f0bf41bd778d0..87346b46ebf52 100644 --- a/scripts/lastcode-install.test.mjs +++ b/scripts/lastcode-install.test.mjs @@ -985,7 +985,7 @@ describe("LastCode userland install command", () => { it("launches with the repository's pinned Node runtime", () => { expect(renderLauncher("/tmp/Last Code/lastcode-install.mjs")).toContain( - "env MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node '/tmp/Last Code/lastcode-install.mjs' \"$@\"", + "env MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node '/tmp/Last Code/lastcode-install.mjs' \"$@\"", ); }); diff --git a/scripts/lastcode-intel-release.test.mjs b/scripts/lastcode-intel-release.test.mjs index 0d28931defcea..589c718a1934d 100644 --- a/scripts/lastcode-intel-release.test.mjs +++ b/scripts/lastcode-intel-release.test.mjs @@ -180,7 +180,7 @@ describe("immutable Intel release validation", () => { id: "lc-build-intel-package", name: "Build Intel package (macOS)", command: - "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-build-intel-package.ts run", + "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-build-intel-package.ts run", icon: "build", }); const automationCheckout = workflow.slice( diff --git a/scripts/lastcode-local-ci.test.ts b/scripts/lastcode-local-ci.test.ts index 2b586ac1a4bd5..d303501fda055 100644 --- a/scripts/lastcode-local-ci.test.ts +++ b/scripts/lastcode-local-ci.test.ts @@ -305,11 +305,16 @@ describe("lastcode-local-ci", () => { expect(hook).toContain("lastcode:ci:quick -- --pre-push"); }); - it("requires the repository's supported Node release line", () => { + it("requires engines.node, or ^24.13.1 during the transition", () => { + const enginesNode: string = JSON.parse( + NodeFS.readFileSync(NodePath.join(import.meta.dirname, "..", "package.json"), "utf8"), + ).engines.node; + expect(() => assertSupportedNodeVersion(enginesNode)).not.toThrow(); expect(() => assertSupportedNodeVersion("24.13.1")).not.toThrow(); expect(() => assertSupportedNodeVersion("24.99.0")).not.toThrow(); - expect(() => assertSupportedNodeVersion("24.13.0")).toThrow("Node ^24.13.1"); - expect(() => assertSupportedNodeVersion("26.7.0")).toThrow("Node ^24.13.1"); + expect(() => assertSupportedNodeVersion("24.13.0")).toThrow(`Node ${enginesNode}`); + expect(() => assertSupportedNodeVersion("26.7.0")).toThrow(`Node ${enginesNode}`); + expect(() => assertSupportedNodeVersion("25.0.0")).toThrow(`Node ${enginesNode}`); }); it("defaults to the full gate and supports the quick pre-push gate", () => { diff --git a/scripts/lastcode-local-update.mjs b/scripts/lastcode-local-update.mjs index 58a15277a4464..741c6d7efe7b7 100644 --- a/scripts/lastcode-local-update.mjs +++ b/scripts/lastcode-local-update.mjs @@ -771,10 +771,14 @@ function resolveMise(home) { } export function resolveMiseNodeExecutable(cwd, mise, runCommand = run) { - const installRoot = runCommand(cwd, mise, ["where", "node@24.13.1"]); + // Older installable tags can require a different tooling runtime than engines.node. + const manifest = JSON.parse(NodeFS.readFileSync(NodePath.join(cwd, "package.json"), "utf8")); + const nodeTool = manifest.scripts["lastcode:ci"].match(/\bnode@\d+\.\d+\.\d+\b/u)?.[0]; + if (!nodeTool) throw new Error("The target tag's lastcode:ci command must pin a Node runtime."); + const installRoot = runCommand(cwd, mise, ["where", nodeTool]); const executable = NodePath.join(installRoot, "bin", "node"); if (!NodeFS.existsSync(executable)) { - throw new Error(`mise did not provide the expected Node 24 executable at ${executable}.`); + throw new Error(`mise did not provide the expected ${nodeTool} executable at ${executable}.`); } return executable; } diff --git a/scripts/lastcode-local-update.test.ts b/scripts/lastcode-local-update.test.ts index 6153081e3e094..0398e63e5a62e 100644 --- a/scripts/lastcode-local-update.test.ts +++ b/scripts/lastcode-local-update.test.ts @@ -175,15 +175,26 @@ describe("lastcode-local-update", () => { try { NodeFS.mkdirSync(NodePath.dirname(executable), { recursive: true }); NodeFS.writeFileSync(executable, ""); - assert.equal( - resolveMiseNodeExecutable("/build", "/mise", (cwd, command, args) => { - assert.equal(cwd, "/build"); - assert.equal(command, "/mise"); - assert.deepEqual(args, ["where", "node@24.13.1"]); - return root; - }), - executable, - ); + for (const version of ["26.12.1", "24.13.1"]) { + NodeFS.writeFileSync( + NodePath.join(root, "package.json"), + JSON.stringify({ + engines: { node: "26.12.1" }, + scripts: { + "lastcode:ci": `MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@${version} -- node scripts/lastcode-local-ci.ts --full`, + }, + }), + ); + assert.equal( + resolveMiseNodeExecutable(root, "/mise", (cwd, command, args) => { + assert.equal(cwd, root); + assert.equal(command, "/mise"); + assert.deepEqual(args, ["where", `node@${version}`]); + return root; + }), + executable, + ); + } } finally { NodeFS.rmSync(root, { recursive: true, force: true }); } diff --git a/scripts/lastcode-main-write-lock.test.ts b/scripts/lastcode-main-write-lock.test.ts index a942d71a373ac..4bc9e1a5c6ec8 100644 --- a/scripts/lastcode-main-write-lock.test.ts +++ b/scripts/lastcode-main-write-lock.test.ts @@ -363,6 +363,7 @@ JS`, vi.stubEnv("FIXTURE_REAL_GIT", realGit); vi.stubEnv("PATH", `${bin}${NodePath.delimiter}${process.env.PATH ?? ""}`); const hooks = NodePath.join(f.first, ".git", "hooks"); + f.git(f.first, ["config", "core.hooksPath", hooks]); NodeFS.copyFileSync( NodePath.join(import.meta.dirname, "..", ".vite-hooks", "pre-push"), NodePath.join(hooks, "lastcode-pre-push"), diff --git a/scripts/lastcode-node-launch.test.ts b/scripts/lastcode-node-launch.test.ts index 302e54bef3100..9ceac79eeed98 100644 --- a/scripts/lastcode-node-launch.test.ts +++ b/scripts/lastcode-node-launch.test.ts @@ -50,7 +50,7 @@ function createLaunchFixture(root: string) { writeExecutable( NodePath.join(toolBin, "mise"), `#!/bin/sh -[ "$1" = exec ] && [ "$2" = node@24.13.1 ] && [ "$3" = -- ] || exit 90 +[ "$1" = exec ] && [ "$2" = node@26.12.1 ] && [ "$3" = -- ] || exit 90 shift 3 # Model mise restoring pristine PATH entries unless aggressive activation is explicit. if [ "$MISE_ACTIVATE_AGGRESSIVE" = 1 ]; then diff --git a/scripts/lib/server-test-pilot.mjs b/scripts/lib/server-test-pilot.mjs index 2c8b46575c0d7..d254d6c41b460 100644 --- a/scripts/lib/server-test-pilot.mjs +++ b/scripts/lib/server-test-pilot.mjs @@ -3,7 +3,7 @@ import * as NodeAssert from "node:assert/strict"; const transferTest = "integration/transferBudgetV2.integration.test.ts"; export function assertPilotRuntime(version) { - NodeAssert.equal(version, "v24.13.1", "Pilot requires checkpoint Node v24.13.1"); + NodeAssert.equal(version, "v26.12.1", "Pilot requires checkpoint Node v26.12.1"); } function strictFingerprint({ cpu: _cpu, ...identity }) { diff --git a/scripts/lib/server-test-pilot.test.mjs b/scripts/lib/server-test-pilot.test.mjs index 443471a67bdf9..d41ee643c980b 100644 --- a/scripts/lib/server-test-pilot.test.mjs +++ b/scripts/lib/server-test-pilot.test.mjs @@ -11,8 +11,8 @@ function fixture() { cohort, fingerprint: { candidate: "candidate", - node: "v24.13.1", - testNode: "v24.13.1", + node: "v26.12.1", + testNode: "v26.12.1", cpu: "CPU family A", cpus: 4, memoryGiB: 16, @@ -79,7 +79,7 @@ it("accepts complete disjoint cohorts and rejects unusable benchmark evidence", r[1].fingerprint[field] = "other"; }), (r) => { - for (const receipt of r) receipt.fingerprint.node = receipt.fingerprint.testNode = "v26.12.1"; + for (const receipt of r) receipt.fingerprint.node = receipt.fingerprint.testNode = "v24.13.1"; }, (r) => { r[1].discovery = ["other"]; diff --git a/scripts/prepare-dependencies.test.mjs b/scripts/prepare-dependencies.test.mjs index f5a52892a94db..577a894f4cfa5 100644 --- a/scripts/prepare-dependencies.test.mjs +++ b/scripts/prepare-dependencies.test.mjs @@ -13,7 +13,7 @@ const declarations = JSON.parse( function declaredLauncher(name) { const command = declarations.find((declaration) => declaration.name === name)?.command; const match = command?.match( - /^MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24\.13\.1 -- node -e "([^"]+)"$/, + /^MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26\.12\.1 -- node -e "([^"]+)"$/, ); NodeAssert.ok(match, `${name} must use the pinned Node inline launcher`); return match[1]; diff --git a/scripts/server-test-pilot.ts b/scripts/server-test-pilot.ts index 1bca38551e5c5..79c177bab85cf 100644 --- a/scripts/server-test-pilot.ts +++ b/scripts/server-test-pilot.ts @@ -8,7 +8,7 @@ import * as NodePath from "node:path"; import * as NodeURL from "node:url"; import { shardedTestFiles } from "./lib/sharded-tests.ts"; -NodeAssert.equal(process.version, "v24.13.1", "Pilot driver requires checkpoint Node v24.13.1"); +NodeAssert.equal(process.version, "v26.12.1", "Pilot driver requires checkpoint Node v26.12.1"); const root = NodePath.resolve(import.meta.dirname, ".."); const server = NodePath.join(root, "apps/server"); @@ -75,7 +75,7 @@ const result = NodeChildProcess.spawnSync( ...process.env, VP_REMOTE_CACHE: "off", VP_REMOTE_CACHE_URL: "", - VP_NODE_VERSION: "24.13.1", + VP_NODE_VERSION: "26.12.1", T3CODE_SERVER_PILOT_RUNTIME_PATH: runtimePath, T3CODE_TRANSFER_BUDGET_REPORT_PATH: NodePath.join(output, "transfer.md"), T3CODE_TRANSFER_BUDGET_RESULT_PATH: NodePath.join(output, "transfer.json"), From 65db45e1664dc4d55852752cbac15e44dc622e61 Mon Sep 17 00:00:00 2001 From: Michael Johnston Date: Sat, 14 Nov 2026 01:01:02 -0700 Subject: [PATCH 2/5] test(actions): align saved command fixtures with Node 26 Keep Action reconciliation fixtures aligned with the downstream runtime declarations. Carry-Group: resumable-actions Carry-Fix: https://github.com/lastobelus/lastCode/issues/665 --- apps/server/src/cli/projectActionReconciliation.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/server/src/cli/projectActionReconciliation.test.ts b/apps/server/src/cli/projectActionReconciliation.test.ts index ced809b9c1a45..1e15d97c2bc71 100644 --- a/apps/server/src/cli/projectActionReconciliation.test.ts +++ b/apps/server/src/cli/projectActionReconciliation.test.ts @@ -11,14 +11,14 @@ import { const waitDeclaration = { id: "lc-wait-for-checkpoint", name: "Wait for Checkpoint", - command: "mise exec node@24.13.1 -- node scripts/lastcode-wait-for-checkpoint.ts", + command: "mise exec node@26.12.1 -- node scripts/lastcode-wait-for-checkpoint.ts", icon: "test" as const, }; const quickDeclaration = { id: "lc-local-ci", name: "Run Quick CI", - command: "mise exec node@24.13.1 -- node scripts/lastcode-local-ci.ts --quick", + command: "mise exec node@26.12.1 -- node scripts/lastcode-local-ci.ts --quick", icon: "test" as const, }; From da221160f5306160152c919a8e622a7e39933758 Mon Sep 17 00:00:00 2001 From: Michael Johnston Date: Sat, 14 Nov 2026 01:01:02 -0700 Subject: [PATCH 3/5] fix(tooling): accept engines.node while saved Actions transition Accept root engines.node exactly alongside temporary Node ^24.13.1 support, and align setup and Action declarations with Node 26.12.1. Carry-Group: incubator Carry-Fix: https://github.com/lastobelus/lastCode/issues/665 Carry-Observation: These runtime and setup edits depend on definitions currently introduced or reconciled by the Incubator group, so they must replay after it. Independent packaging and CI edits remain in Build & CI, and Action reconciliation fixtures remain in Resumable Actions. --- scripts/lastcode-nightly-service.test.ts | 4 ++-- scripts/lastcode-setup.mjs | 2 +- scripts/lib/lastcode-git.ts | 21 +++++++++++++++------ t3.json | 12 ++++++------ 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/scripts/lastcode-nightly-service.test.ts b/scripts/lastcode-nightly-service.test.ts index cec1551603800..34eb86de3ccf6 100644 --- a/scripts/lastcode-nightly-service.test.ts +++ b/scripts/lastcode-nightly-service.test.ts @@ -49,14 +49,14 @@ it("renders the deployment-defined interval in a source-only launch agent", () = const plist = renderLaunchAgentPlist({ repoRoot: "/Users/example/LastCode & experiments", logDirectory: "/Users/example/.lastcode/automation", - nodePath: "/Users/example/.local/share/mise/installs/node/24.13.1/bin/node", + nodePath: "/Users/example/.local/share/mise/installs/node/26.12.1/bin/node", schedule: { kind: "interval", intervalSeconds: 1234 }, scheduleHelperPath: "/Users/example/.lastcode/automation/bin/schedule helper.mjs", supervisorPath: "/Users/example/.lastcode/automation/bin/checkpoint supervisor.mjs", }); expect(plist).toContain("1234"); - expect(plist).toContain("node/24.13.1/bin/node"); + expect(plist).toContain("node/26.12.1/bin/node"); expect(plist).toContain("checkpoint supervisor.mjs"); expect(plist).not.toContain("/bin/zsh"); expect(plist).not.toContain("/usr/bin/env"); diff --git a/scripts/lastcode-setup.mjs b/scripts/lastcode-setup.mjs index 42278eebdf9a2..13e10f0ee5d14 100644 --- a/scripts/lastcode-setup.mjs +++ b/scripts/lastcode-setup.mjs @@ -225,7 +225,7 @@ function main(argv) { const options = parseOptions(argv); if (options.help) { console.log( - "Usage: MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-setup.mjs --enable-nightly-writes (--checkpoint-interval-seconds | --checkpoint-daily-at --checkpoint-time-zone ) [--trusted-project-action ]... [--dry-run]", + "Usage: MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-setup.mjs --enable-nightly-writes (--checkpoint-interval-seconds | --checkpoint-daily-at --checkpoint-time-zone ) [--trusted-project-action ]... [--dry-run]", ); console.log(); console.log( diff --git a/scripts/lib/lastcode-git.ts b/scripts/lib/lastcode-git.ts index f6669e3571154..15eec379cfaf5 100644 --- a/scripts/lib/lastcode-git.ts +++ b/scripts/lib/lastcode-git.ts @@ -1,6 +1,7 @@ // @effect-diagnostics nodeBuiltinImport:off -- Host-side Git helpers shared by local CI and the guarded merge. // No installed dependencies: lastcode:merge runs from checkouts without node_modules. import * as NodeChildProcess from "node:child_process"; +import * as NodeFS from "node:fs"; import * as NodePath from "node:path"; import { CiCommandFailure } from "./lastcode-ci-process.ts"; @@ -37,14 +38,22 @@ export function cleanGitEnvironment( ); } +const PROJECT_NODE_VERSION: string = JSON.parse( + NodeFS.readFileSync(NodePath.join(import.meta.dirname, "..", "..", "package.json"), "utf8"), +).engines.node; + +// Transition: saved Project Actions in Settings still launch through `mise exec node@24.13.1`. +// Remove the ^24.13.1 branch once they launch with the `engines.node` version. +function isTransitionalNode24(major: number, minor: number, patch: number): boolean { + return major === 24 && (minor > 13 || (minor === 13 && patch >= 1)); +} + export function assertSupportedNodeVersion(version = process.versions.node): void { const [major = 0, minor = 0, patch = 0] = version.split(".").map(Number); - const supported = major === 24 && (minor > 13 || (minor === 13 && patch >= 1)); - if (!supported) { - throw new Error( - `LastCode local CI requires Node ^24.13.1, received ${version}. Run it through the package script so mise selects the project runtime.`, - ); - } + if (version === PROJECT_NODE_VERSION || isTransitionalNode24(major, minor, patch)) return; + throw new Error( + `LastCode local CI requires Node ${PROJECT_NODE_VERSION} (root package.json engines.node) or ^24.13.1, received ${version}. Run it through the package script so mise selects the project runtime.`, + ); } export function runProcess( diff --git a/t3.json b/t3.json index b8afad7f0871a..f2f3b048a000c 100644 --- a/t3.json +++ b/t3.json @@ -10,38 +10,38 @@ }, { "name": "Setup Worktree", - "command": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node -e \"const r=require('node:child_process').spawnSync(process.execPath,[require('node:path').join(process.env.T3CODE_PROJECT_ROOT,'scripts','setup-worktree.ts')],{stdio:'inherit'});if(r.error)console.error(r.error.message);process.exit(r.status??1);\"", + "command": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node -e \"const r=require('node:child_process').spawnSync(process.execPath,[require('node:path').join(process.env.T3CODE_PROJECT_ROOT,'scripts','setup-worktree.ts')],{stdio:'inherit'});if(r.error)console.error(r.error.message);process.exit(r.status??1);\"", "icon": "configure", "runOnWorktreeCreate": true }, { "id": "lc-prepare-dependencies", "name": "Prepare Dependencies", - "command": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node -e \"const r=require('node:child_process').spawnSync(process.execPath,[require('node:path').join(process.env.T3CODE_PROJECT_ROOT,'scripts','prepare-dependencies.ts')],{stdio:'inherit'});if(r.error)console.error(r.error.message);process.exit(r.status??1);\"", + "command": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node -e \"const r=require('node:child_process').spawnSync(process.execPath,[require('node:path').join(process.env.T3CODE_PROJECT_ROOT,'scripts','prepare-dependencies.ts')],{stdio:'inherit'});if(r.error)console.error(r.error.message);process.exit(r.status??1);\"", "icon": "configure" }, { "id": "lc-wait-for-checkpoint", "name": "Wait for Checkpoint", - "command": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node \"$T3CODE_PROJECT_ROOT/scripts/lastcode-wait-for-checkpoint.ts\"", + "command": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node \"$T3CODE_PROJECT_ROOT/scripts/lastcode-wait-for-checkpoint.ts\"", "icon": "test" }, { "id": "lc-local-ci", "name": "Run Quick CI", - "command": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node -e \"for(const args of [[require('node:path').join(process.env.T3CODE_PROJECT_ROOT,'scripts','prepare-dependencies.ts'),'--for-ci'],['scripts/lastcode-local-ci.ts','--quick']]){const r=require('node:child_process').spawnSync(process.execPath,args,{stdio:'inherit'});if(r.error)console.error(r.error.message);if(r.status===0)continue;process.exit(r.status??1);}\"", + "command": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node -e \"for(const args of [[require('node:path').join(process.env.T3CODE_PROJECT_ROOT,'scripts','prepare-dependencies.ts'),'--for-ci'],['scripts/lastcode-local-ci.ts','--quick']]){const r=require('node:child_process').spawnSync(process.execPath,args,{stdio:'inherit'});if(r.error)console.error(r.error.message);if(r.status===0)continue;process.exit(r.status??1);}\"", "icon": "test" }, { "id": "lc-build-local-package", "name": "Build Local Package", - "command": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node \"$T3CODE_PROJECT_ROOT/scripts/lastcode-build-local-package.ts\" run", + "command": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node \"$T3CODE_PROJECT_ROOT/scripts/lastcode-build-local-package.ts\" run", "icon": "build" }, { "id": "lc-build-intel-package", "name": "Build Intel package (macOS)", - "command": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@24.13.1 -- node scripts/lastcode-build-intel-package.ts run", + "command": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node scripts/lastcode-build-intel-package.ts run", "icon": "build" } ] From be506d96f6f2c0b308fb84984dd85b6c1a745c13 Mon Sep 17 00:00:00 2001 From: Michael Johnston Date: Sat, 14 Nov 2026 01:24:21 -0700 Subject: [PATCH 4/5] fix(actions): use each checkout's CI runtime After dependency preparation, execute the target checkout's own lastcode:ci command with the requested mode. This preserves its Node pin when a shared Action targets an older checkout whose runtime guard still requires Node 24. Cover older target runtime selection for Quick and Full CI, nested Node processes, preparation failure and CI exit propagation. Carry-Group: incubator Carry-Fix: https://github.com/lastobelus/lastCode/pull/708#discussion_r4572478539 Carry-Observation: The shared Action declaration is reconciled by Incubator; its runtime-selection repair and focused fixtures must replay together after that declaration. --- scripts/lastcode-local-ci.test.ts | 1 + scripts/lastcode-node-launch.test.ts | 92 +++++++++++++++++++++------ scripts/prepare-dependencies.test.mjs | 13 +++- t3.json | 2 +- 4 files changed, 87 insertions(+), 21 deletions(-) diff --git a/scripts/lastcode-local-ci.test.ts b/scripts/lastcode-local-ci.test.ts index d303501fda055..74fe6108929fc 100644 --- a/scripts/lastcode-local-ci.test.ts +++ b/scripts/lastcode-local-ci.test.ts @@ -335,6 +335,7 @@ describe("lastcode-local-ci", () => { prePush: false, requireLocal: true, }); + expect(parseLocalCiOptions(["--full", "--quick"]).mode).toBe("quick"); expect(() => parseLocalCiOptions(["--require-local"])).toThrow("only supported with --quick"); expect(() => parseLocalCiOptions(["--full", "--pre-push"])).toThrow( "only supported with --quick", diff --git a/scripts/lastcode-node-launch.test.ts b/scripts/lastcode-node-launch.test.ts index 9ceac79eeed98..f47ea511bc49b 100644 --- a/scripts/lastcode-node-launch.test.ts +++ b/scripts/lastcode-node-launch.test.ts @@ -13,31 +13,46 @@ const projectConfig = JSON.parse( NodeFS.readFileSync(new URL("../t3.json", import.meta.url), "utf8"), ) as { scripts: Array<{ name: string; command: string }> }; -function withLaunchFixture(run: (fixture: ReturnType) => void) { +function withLaunchFixture( + run: (fixture: ReturnType) => void, + targetVersion = "26.12.1", +) { const root = NodeFS.realpathSync( NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "lastcode node launch ")), ); try { - run(createLaunchFixture(root)); + run(createLaunchFixture(root, targetVersion)); } finally { NodeFS.rmSync(root, { recursive: true, force: true }); } } -function createLaunchFixture(root: string) { +function createLaunchFixture(root: string, targetVersion: string) { const cwd = NodePath.join(root, "checkout with spaces"); const projectRoot = NodePath.join(root, "primary checkout with spaces"); const pinnedBin = NodePath.join(root, "pinned bin"); + const legacyBin = NodePath.join(root, "legacy pinned bin"); const ambientBin = NodePath.join(root, "ambient bin"); const toolBin = NodePath.join(root, "external tools"); const checkoutBin = NodePath.join(cwd, "node_modules", ".bin"); - for (const directory of [cwd, projectRoot, pinnedBin, ambientBin, toolBin, checkoutBin]) { + for (const directory of [ + cwd, + projectRoot, + pinnedBin, + legacyBin, + ambientBin, + toolBin, + checkoutBin, + ]) { NodeFS.mkdirSync(directory, { recursive: true }); } const pinnedNode = NodePath.join(pinnedBin, "node"); + const legacyNode = NodePath.join(legacyBin, "node"); // A copied executable gives process.execPath an observable identity independent of PATH shims. NodeFS.copyFileSync(process.execPath, pinnedNode); NodeFS.chmodSync(pinnedNode, 0o755); + NodeFS.copyFileSync(process.execPath, legacyNode); + NodeFS.chmodSync(legacyNode, 0o755); const writeExecutable = (path: string, source: string) => { NodeFS.writeFileSync(path, source, { mode: 0o755 }); }; @@ -50,18 +65,31 @@ function createLaunchFixture(root: string) { writeExecutable( NodePath.join(toolBin, "mise"), `#!/bin/sh -[ "$1" = exec ] && [ "$2" = node@26.12.1 ] && [ "$3" = -- ] || exit 90 +[ "$1" = exec ] && [ "$3" = -- ] || exit 90 +case "$2" in + node@26.12.1) selected="$FIXTURE_PINNED_BIN" ;; + node@24.13.1) selected="$FIXTURE_LEGACY_BIN" ;; + *) exit 90 ;; +esac shift 3 # Model mise restoring pristine PATH entries unless aggressive activation is explicit. if [ "$MISE_ACTIVATE_AGGRESSIVE" = 1 ]; then - PATH="$FIXTURE_PINNED_BIN:$FIXTURE_CHECKOUT_BIN:$FIXTURE_AMBIENT_BIN:/usr/bin:/bin" + PATH="$selected:$FIXTURE_CHECKOUT_BIN:$FIXTURE_TOOL_BIN:$FIXTURE_AMBIENT_BIN:/usr/bin:/bin" else - PATH="$FIXTURE_AMBIENT_BIN:$FIXTURE_CHECKOUT_BIN:$FIXTURE_PINNED_BIN:/usr/bin:/bin" + PATH="$FIXTURE_AMBIENT_BIN:$FIXTURE_CHECKOUT_BIN:$FIXTURE_TOOL_BIN:$selected:/usr/bin:/bin" fi export PATH exec "$@" `, ); + NodeFS.writeFileSync( + NodePath.join(cwd, "package.json"), + JSON.stringify({ + scripts: { + "lastcode:ci": `MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@${targetVersion} -- node scripts/lastcode-local-ci.ts --full`, + }, + }), + ); const leaf = `const { spawnSync } = require('node:child_process'); const path = require('node:path'); const grandchild = spawnSync('node', ['-e', 'console.log(JSON.stringify({ executable: process.execPath, version: process.version }))'], { encoding: 'utf8' }); @@ -82,6 +110,7 @@ process.exit(Number(name === process.env.FIXTURE_FAIL_LEAF ? process.env.FIXTURE return { cwd, pinnedNode, + ciNode: targetVersion === "24.13.1" ? legacyNode : pinnedNode, run(command: string, args: string[], failLeaf: string, exitCode: number) { const env: NodeJS.ProcessEnv = { ...process.env, @@ -91,6 +120,8 @@ process.exit(Number(name === process.env.FIXTURE_FAIL_LEAF ? process.env.FIXTURE MISE_ACTIVATE_AGGRESSIVE: "0", T3CODE_PROJECT_ROOT: projectRoot, FIXTURE_PINNED_BIN: pinnedBin, + FIXTURE_LEGACY_BIN: legacyBin, + FIXTURE_TOOL_BIN: toolBin, FIXTURE_AMBIENT_BIN: ambientBin, FIXTURE_CHECKOUT_BIN: checkoutBin, FIXTURE_AMBIENT_NODE: process.execPath, @@ -133,10 +164,12 @@ function expectPinned( records: ReturnType["run"]>, ) { for (const record of records) { - expect(record.executable).toBe(fixture.pinnedNode); + const expectedNode = + record.name === "lastcode-local-ci.ts" ? fixture.ciNode : fixture.pinnedNode; + expect(record.executable).toBe(expectedNode); expect(record.version).toBe(process.version); expect(record.cwd).toBe(NodeFS.realpathSync(fixture.cwd)); - expect(record.grandchild).toEqual({ executable: fixture.pinnedNode, version: process.version }); + expect(record.grandchild).toEqual({ executable: expectedNode, version: process.version }); } } @@ -158,21 +191,44 @@ describe("saved LastCode Node launchers", () => { }); it.each([ - { failLeaf: "", exitCode: 0, runsCi: true }, - { failLeaf: "prepare-dependencies.ts", exitCode: 29, runsCi: false }, - { failLeaf: "lastcode-local-ci.ts", exitCode: 31, runsCi: true }, + { targetVersion: "26.12.1", mode: "--quick", failLeaf: "", exitCode: 0, runsCi: true }, + { + targetVersion: "26.12.1", + mode: "--quick", + failLeaf: "prepare-dependencies.ts", + exitCode: 29, + runsCi: false, + }, + { + targetVersion: "26.12.1", + mode: "--quick", + failLeaf: "lastcode-local-ci.ts", + exitCode: 31, + runsCi: true, + }, + { targetVersion: "24.13.1", mode: "--quick", failLeaf: "", exitCode: 0, runsCi: true }, + { targetVersion: "24.13.1", mode: "--full", failLeaf: "", exitCode: 0, runsCi: true }, + { + targetVersion: "24.13.1", + mode: "--full", + failLeaf: "lastcode-local-ci.ts", + exitCode: 31, + runsCi: true, + }, ])( - "pins Run Quick CI preparation and execution ($failLeaf, exit $exitCode)", - ({ failLeaf, exitCode, runsCi }) => { + "pins preparation and uses target Node $targetVersion for $mode ($failLeaf, exit $exitCode)", + ({ targetVersion, mode, failLeaf, exitCode, runsCi }) => { withLaunchFixture((fixture) => { - const command = projectConfig.scripts.find(({ name }) => name === "Run Quick CI")!.command; + const command = projectConfig.scripts + .find(({ name }) => name === "Run Quick CI")! + .command.replace("--quick", mode); const records = fixture.run(command, [], failLeaf, exitCode); + expectPinned(fixture, records); expect(records.map(({ name, args }) => ({ name, args }))).toEqual([ { name: "prepare-dependencies.ts", args: ["--for-ci"] }, - ...(runsCi ? [{ name: "lastcode-local-ci.ts", args: ["--quick"] }] : []), + ...(runsCi ? [{ name: "lastcode-local-ci.ts", args: ["--full", mode] }] : []), ]); - expectPinned(fixture, records); - }); + }, targetVersion); }, ); }); diff --git a/scripts/prepare-dependencies.test.mjs b/scripts/prepare-dependencies.test.mjs index 577a894f4cfa5..270ee8f62f3f9 100644 --- a/scripts/prepare-dependencies.test.mjs +++ b/scripts/prepare-dependencies.test.mjs @@ -22,7 +22,12 @@ function declaredLauncher(name) { function runDeclaredLauncher(name, target, anchor, env = {}) { return NodeChildProcess.spawnSync(process.execPath, ["-e", declaredLauncher(name)], { cwd: target, - env: { ...process.env, T3CODE_PROJECT_ROOT: anchor, ...env }, + env: { + ...process.env, + PATH: `${NodePath.dirname(process.execPath)}${NodePath.delimiter}${process.env.PATH ?? ""}`, + T3CODE_PROJECT_ROOT: anchor, + ...env, + }, encoding: "utf8", }); } @@ -177,6 +182,10 @@ function launcherFixture() { NodePath.join(target, "scripts", "lastcode-local-ci.ts"), child("ci", "FIXTURE_CI_STATUS"), ); + NodeFS.writeFileSync( + NodePath.join(target, "package.json"), + JSON.stringify({ scripts: { "lastcode:ci": "node scripts/lastcode-local-ci.ts --full" } }), + ); return { target, anchor }; } @@ -211,7 +220,7 @@ it("declared CI launcher stops after preparation failure and propagates CI failu .map((line) => JSON.parse(line)), [ { kind: "prepare", cwd: target, args: ["--for-ci"] }, - { kind: "ci", cwd: target, args: ["--quick"] }, + { kind: "ci", cwd: target, args: ["--full", "--quick"] }, ], ); const success = runDeclaredLauncher("Run Quick CI", target, anchor); diff --git a/t3.json b/t3.json index f2f3b048a000c..769953797f71d 100644 --- a/t3.json +++ b/t3.json @@ -29,7 +29,7 @@ { "id": "lc-local-ci", "name": "Run Quick CI", - "command": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node -e \"for(const args of [[require('node:path').join(process.env.T3CODE_PROJECT_ROOT,'scripts','prepare-dependencies.ts'),'--for-ci'],['scripts/lastcode-local-ci.ts','--quick']]){const r=require('node:child_process').spawnSync(process.execPath,args,{stdio:'inherit'});if(r.error)console.error(r.error.message);if(r.status===0)continue;process.exit(r.status??1);}\"", + "command": "MISE_ACTIVATE_AGGRESSIVE=1 mise exec node@26.12.1 -- node -e \"for(const [command,args] of [[process.execPath,[require('node:path').join(process.env.T3CODE_PROJECT_ROOT,'scripts','prepare-dependencies.ts'),'--for-ci']],['/bin/sh',['-c',JSON.parse(require('node:fs').readFileSync('package.json','utf8')).scripts['lastcode:ci']+' --quick']]]){const r=require('node:child_process').spawnSync(command,args,{stdio:'inherit'});if(r.error)console.error(r.error.message);if(r.status===0)continue;process.exit(r.status??1);}\"", "icon": "test" }, { From 2c2dfb5494c380dfd54441a204155fa9f127a114 Mon Sep 17 00:00:00 2001 From: Michael Johnston Date: Sat, 14 Nov 2026 02:12:02 -0700 Subject: [PATCH 5/5] ci: refresh required checks after a metadata-only run A later metadata-only CI check suite leaves GitHub expecting CI Gate despite successful exact-head CI and a successful gate rerun. Refresh the source check suite with an unchanged tree. Carry-Group: build-ci Carry-Observation: This empty commit refreshes GitHub required checks; it changes no source behavior.