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/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, }; 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..74fe6108929fc 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", () => { @@ -330,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-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-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-node-launch.test.ts b/scripts/lastcode-node-launch.test.ts index 302e54bef3100..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@24.13.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/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/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..270ee8f62f3f9 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]; @@ -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/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"), diff --git a/t3.json b/t3.json index b8afad7f0871a..769953797f71d 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 [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" }, { "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" } ]