Skip to content

Commit aef3779

Browse files
Merge remote-tracking branch 'origin/main' into fix/issue-16441
2 parents 1bfc1be + ed9b7ca commit aef3779

5 files changed

Lines changed: 15 additions & 13 deletions

File tree

‎.github/workflows/release-desktop.yml‎

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -253,18 +253,13 @@ jobs:
253253
if [[ "${{ inputs.version }}" =~ -nightly\.[0-9]{8}\.[0-9]+$ ]]; then brand=nightly; fi
254254
node scripts/apply-web-brand-assets.ts "$brand" apps/server/dist/client
255255
256-
# The single-executable is built with a Node that supports --build-sea
257-
# (25.7+); the repo itself stays on the engines.node version. A native
258-
# build injects into the runner's own Node; a Windows target downloads its
259-
# node.exe.
256+
# Setup Vite+ selects the exact engines.node version from package.json.
257+
# Native builds embed that executable; cross-target builds download the
258+
# same version for their target.
260259
- name: Build CLI single-executable
261260
if: inputs.cli_archive
262261
shell: bash
263262
env:
264-
# The exact version, not a major: vp downloads it from nodejs.org/dist on
265-
# the runner, and only exact versions have a dist directory. Keep in
266-
# step with SEA_NODE_VERSION in apps/server/vite.config.ts.
267-
VP_NODE_VERSION: "26.8.2"
268263
# The executable bundles the server again, so it needs the same stamp
269264
# as release.yml's JS bundle.
270265
T3CODE_TELEMETRY_CHANNEL: ${{ github.repository == 'pingdotgg/t3code' && inputs.release_channel || 'dev' }}

‎apps/server/scripts/cli.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/process";
1111

1212
import { DEVELOPMENT_ICON_OVERRIDES } from "../../../scripts/lib/brand-assets.ts";
1313
import { findEsmImportsOfExternalPackages } from "../../../scripts/lib/cli-executable-imports.ts";
14+
import rootPackageJson from "../../../package.json" with { type: "json" };
1415
import { resolveSpawnCommand } from "@t3tools/shared/shell";
1516
import {
1617
ServerCliBuildAssetMissingError,
@@ -149,7 +150,7 @@ const buildExeCmd = Command.make(
149150
}),
150151
).pipe(
151152
Command.withDescription(
152-
"Build the server as a Node single-executable (needs a Node 25.7+ host for --build-sea). The binary still resolves native packages from a node_modules tree beside it.",
153+
`Build the server as a Node single-executable. Native builds require Node ${rootPackageJson.engines.node}; explicit cross-target builds need a Node 25.7+ host for --build-sea. The binary still resolves native packages from a node_modules tree beside it.`,
153154
),
154155
);
155156

‎apps/server/vite.config.ts‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { defineConfig, mergeConfig } from "vite-plus";
33

44
import baseConfig from "../../vite.config.ts";
55
import { loadRepoEnv } from "../../scripts/lib/public-config.ts";
6+
import rootPackageJson from "../../package.json" with { type: "json" };
67
import packageJson from "./package.json" with { type: "json" };
78
import { WeightedShardSequencer } from "./src/testUtils/weightedShardSequencer.ts";
89

@@ -34,7 +35,6 @@ if (!TELEMETRY_CHANNELS.includes(telemetryChannel)) {
3435
// executable needs a host Node that supports `--build-sea` (25.7+), so this is
3536
// a separate mode rather than a second entry in the default build.
3637
const packExecutable = process.env.T3CODE_PACK_EXE === "1";
37-
const SEA_NODE_VERSION = "26.8.2";
3838
const SEA_TARGETS = {
3939
"darwin-arm64": { platform: "darwin", arch: "arm64" },
4040
"darwin-x64": { platform: "darwin", arch: "x64" },
@@ -44,6 +44,12 @@ const SEA_TARGETS = {
4444
"win-x64": { platform: "win", arch: "x64" },
4545
} as const;
4646
const packExecutableTarget = process.env.T3CODE_PACK_EXE_TARGET?.trim();
47+
const cliNodeVersion = rootPackageJson.engines.node;
48+
if (packExecutable && !packExecutableTarget && process.versions.node !== cliNodeVersion) {
49+
throw new Error(
50+
`Native CLI executables must be built with Node ${cliNodeVersion}; running ${process.versions.node}.`,
51+
);
52+
}
4753
if (packExecutableTarget && !Object.hasOwn(SEA_TARGETS, packExecutableTarget)) {
4854
throw new Error(
4955
`T3CODE_PACK_EXE_TARGET must be one of ${Object.keys(SEA_TARGETS).join(", ")}, got "${packExecutableTarget}".`,
@@ -53,7 +59,7 @@ const packExecutableTargets = packExecutableTarget
5359
? [
5460
{
5561
...SEA_TARGETS[packExecutableTarget as keyof typeof SEA_TARGETS],
56-
nodeVersion: SEA_NODE_VERSION,
62+
nodeVersion: cliNodeVersion,
5763
},
5864
]
5965
: undefined;

‎docs/operations/release.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ the version.
4444
- Includes Electron auto-update metadata (for example `latest*.yml`, `nightly*.yml`, and `*.blockmap`) in release assets.
4545
- Builds a self-contained CLI archive per platform (`t3-<version>-<platform>-<arch>.tar.gz`, `.zip` on Windows) in the same job as that target's desktop artifact and attaches them to the GitHub Release with a `SHA256SUMS` file, on every channel, for five targets: macOS arm64, Linux x64 and arm64, Windows x64 and arm64. Every archive is smoke-tested on hardware of its own architecture, and every one but the Windows archives is also built and signed there. There is no macOS x64 archive: Node single-executables are unsupported on x64 macOS (the SEA docs list macOS as arm64 only) and the binary segfaults on start; the x64 desktop app is Electron and unaffected.
4646
- The archive holds the server as a Node single-executable (`scripts/build-cli-archive.ts`), so unpacking it needs neither Node, npm, nor a compiler. It is the only form in which T3 Code manages a runtime: the desktop's SSH environments, the boot service, `t3 update`, and the install scripts all download and verify this archive against `SHA256SUMS`. The `t3` npm package exists for people who run `npx t3` or `npm install -g t3` themselves, and it downloads this same archive too; nothing in the product installs from npm. The `curl | sh` installers are `scripts/install.sh` and `scripts/install.ps1`; the marketing site copies them into its `public/` at build time (`apps/marketing/scripts/stage-install-scripts.mjs`) and serves them at `t3.codes/install.sh` and `/install.ps1`.
47-
- The executable is built with a Node that supports `--build-sea` (`VP_NODE_VERSION=26.8.2`, kept in step with `SEA_NODE_VERSION` in `apps/server/vite.config.ts`), while the repo stays on `engines.node`.
47+
- The root `package.json` pins `engines.node` to the exact Node version used by development, CI and standalone executables. Keep it exact: cross-target builds download that version, and native executable builds reject a different host Node version. Runtime changes need a new T3 release version because installed runtimes are cached by that version.
4848
- macOS archives are signed with the Developer ID certificate and notarized when the Apple secrets are present (ad hoc otherwise, which still runs from `curl`/`tar` installs). Windows executables use the same Azure Trusted Signing setup as the installer. Every native addon in the macOS archive is signed too, since the hardened runtime refuses unsigned libraries.
4949
- Each archive is extracted and executed (`scripts/smoke-cli-archive.ts`) on its build runner, or on the `verify_runner` for Windows.
5050
- After the GitHub Release is published, publishes the `t3` npm package with OIDC trusted publishing from the same workflow file. It is only a launcher (`scripts/npm-launcher/t3.mjs`): on first run it downloads its exact version's CLI archive from the GitHub Release, verifies it against `SHA256SUMS`, and unpacks it into `<T3 home>/runtime/versions/<version>`, the layout the install scripts and `t3 update` share. The executables are not on npm. They used to be, as `@t3code/t3-<platform>-<arch>` optional dependencies, but npm's publish-time scanning held those 80-250 MB packages back for up to an hour after the launcher was live, and every install in that window got a launcher with no executable. Those packages stay on npm at their old versions and get no new ones. Because the launcher reads from the release, `publish_cli` runs after `release`, never before.

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"vite-plus": "catalog:"
6363
},
6464
"engines": {
65-
"node": "^24.13.1"
65+
"node": "26.12.1"
6666
},
6767
"packageManager": "[email protected]"
6868
}

0 commit comments

Comments
 (0)