authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-14 11:21:44-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-14 11:21:44-07:00
logf328db30da063a7bce9a9d038fc583d3ff83673a
treea2f1e765088e14daba3919672174b3b67864ab73
parent9130d932f4ddb8abab595b7d12553b46a11c7b22
signature Signed by PGP key B5690EEEBB952194

chore(server): keep the legacy service entry point to the npm package only (#11770)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

5 files changed, 25 insertions(+), 92 deletions(-)

apps/server/src/cloud/pinnedRuntime.test.ts-48
......@@ -1,4 +1,3 @@
1import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
21import * as NodeServices from "@effect/platform-node/NodeServices";
32import { assert, it } from "@effect/vitest";
43import * as Deferred from "effect/Deferred";
......@@ -95,19 +94,6 @@ it.layer(NodeServices.layer)("ensurePinnedRuntimeInstalled", (it) => {
9594 assert.deepEqual(commands, ["tar"]);
9695 assert.equal(yield* fs.readFileString(paths.sentinelPath), `${version}\n`);
9796 assert.isFalse(yield* fs.exists(path.join(paths.versionDir, "t3-runtime-archive")));
98 if ((yield* HostProcessPlatform) !== "win32") {
99 // The old launcher must still be able to start this archive after the
100 // first npm-to-executable update, including from the final directory.
101 yield* fs.writeFileString(paths.entryPath, '#!/bin/sh\nprintf "%s\\n" "$@"\n');
102 yield* fs.chmod(paths.entryPath, 0o755);
103 const runner = yield* ProcessRunner.make();
104 const legacyStart = yield* runner.run({
105 command: process.execPath,
106 args: [path.join(paths.versionDir, "node_modules/t3/dist/bin.mjs"), "serve"],
107 });
108 assert.equal(Number(legacyStart.code), 0, legacyStart.stderr);
109 assert.equal(legacyStart.stdout.trim(), "serve");
110 }
11197 }),
11298 );
11399
......@@ -223,40 +209,6 @@ it.layer(NodeServices.layer)("ensurePinnedRuntimeInstalled", (it) => {
223209 }),
224210 );
225211
226 it.effect("backfills a cached archive without downloading or replacing it", () =>
227 Effect.gen(function* () {
228 const fs = yield* FileSystem.FileSystem;
229 const path = yield* Path.Path;
230 const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-pinned-legacy-cache-" });
231 const cached = pinnedRuntimePaths(path, baseDir, version, "linux");
232 const legacyEntry = path.join(cached.versionDir, "node_modules/t3/dist/bin.mjs");
233 yield* fs.makeDirectory(cached.versionDir, { recursive: true });
234 yield* fs.writeFileString(cached.entryPath, "cached executable\n");
235 yield* fs.writeFileString(cached.sentinelPath, `${version}\n`);
236 const requests: string[] = [];
237 const commands: string[] = [];
238 yield* ensurePinnedRuntimeInstalled({
239 baseDir,
240 version,
241 fs,
242 path,
243 platform: "linux",
244 arch: "x64",
245 httpClient: releaseHttpClient(yield* validChecksums, requests),
246 runner: extractingRunner(fs, path, commands),
247 validate: () =>
248 fs.exists(legacyEntry).pipe(
249 Effect.flatMap((exists) => (exists ? Effect.void : Effect.die("missing legacy entry"))),
250 Effect.orDie,
251 ),
252 });
253 assert.deepEqual(requests, []);
254 assert.deepEqual(commands, []);
255 assert.equal(yield* fs.readFileString(cached.entryPath), "cached executable\n");
256 assert.equal(yield* fs.readFileString(cached.sentinelPath), `${version}\n`);
257 }),
258 );
259
260212 it.effect("preserves a completed runtime when validation fails", () =>
261213 Effect.gen(function* () {
262214 const fs = yield* FileSystem.FileSystem;
apps/server/src/cloud/pinnedRuntime.ts+1-26
......@@ -8,7 +8,6 @@ import * as Option from "effect/Option";
88import * as Semaphore from "effect/Semaphore";
99import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
1010
11import { legacyCliLauncherScript } from "@t3tools/shared/legacyCliLauncher";
1211import {
1312 CLI_RELEASE_CHECKSUMS_FILE,
1413 cliArchiveFileName,
......@@ -230,24 +229,6 @@ const installPinnedRuntime = Effect.fn("cloud.pinned_runtime.ensure_installed")(
230229 input: PinnedRuntimeInstallInput,
231230) {
232231 const { fs } = input;
233 // Old service launchers still use the npm entry point, including when an
234 // archive was cached before this compatibility wrapper existed.
235 const ensureLegacyEntry = Effect.fn("cloud.pinned_runtime.ensure_legacy_entry")(
236 function* (versionDir: string) {
237 const legacyDir = input.path.join(versionDir, "node_modules", "t3", "dist");
238 const entryPath = input.path.join(legacyDir, "bin.mjs");
239 if (yield* fs.exists(entryPath)) return;
240 yield* fs.makeDirectory(legacyDir, { recursive: true });
241 yield* fs.writeFileString(entryPath, legacyCliLauncherScript("archive"));
242 },
243 Effect.mapError(
244 (cause) =>
245 new PinnedRuntimeInstallError({
246 step: "writing the legacy service entry point",
247 cause,
248 }),
249 ),
250 );
251232 const paths = pinnedRuntimePaths(input.path, input.baseDir, input.version, input.platform);
252233 const [versionDirExists, entryExists, sentinel] = yield* Effect.all([
253234 fs.exists(paths.versionDir),
......@@ -261,7 +242,6 @@ const installPinnedRuntime = Effect.fn("cloud.pinned_runtime.ensure_installed")(
261242 const alreadyPinned =
262243 entryExists && Option.isSome(sentinel) && sentinel.value.trim() === input.version;
263244 if (alreadyPinned) {
264 yield* ensureLegacyEntry(paths.versionDir);
265245 yield* input.validate(paths);
266246 return paths;
267247 }
......@@ -310,8 +290,6 @@ const installPinnedRuntime = Effect.fn("cloud.pinned_runtime.ensure_installed")(
310290 return yield* Effect.gen(function* () {
311291 yield* installFromArchive(input, stagingDir);
312292
313 yield* ensureLegacyEntry(stagingDir);
314
315293 yield* input.validate(stagingPaths);
316294 yield* fs
317295 .writeFileString(stagingPaths.sentinelPath, `${input.version}\n`)
......@@ -350,10 +328,7 @@ const installPinnedRuntime = Effect.fn("cloud.pinned_runtime.ensure_installed")(
350328 ),
351329 ),
352330 );
353 if (!published) {
354 yield* ensureLegacyEntry(paths.versionDir);
355 yield* input.validate(paths);
356 }
331 if (!published) yield* input.validate(paths);
357332 return paths;
358333 }).pipe(
359334 Effect.ensuring(fs.remove(stagingDir, { recursive: true, force: true }).pipe(Effect.ignore)),
packages/shared/src/legacyCliLauncher.test.ts+8-8
......@@ -14,22 +14,22 @@ const hostPlatform = NodeOS.platform();
1414const hostArch = NodeOS.arch();
1515
1616// The fixture executable uses a POSIX shebang. The wrapper itself also runs on Windows.
17it.skipIf(hostPlatform === "win32").each(["npm", "archive"] as const)(
18 "keeps %s service IPC, arguments, and termination connected",
19 async (distribution) => {
17it.skipIf(hostPlatform === "win32")(
18 "keeps service IPC, arguments, and termination connected",
19 async () => {
2020 const root = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-legacy-launcher-"));
2121 const entry = NodePath.join(root, "node_modules/t3/dist/bin.mjs");
22 const executable =
23 distribution === "archive"
24 ? NodePath.join(root, "t3")
25 : NodePath.join(root, `node_modules/@t3code/t3-${hostPlatform}-${hostArch}/t3`);
22 const executable = NodePath.join(
23 root,
24 `node_modules/@t3code/t3-${hostPlatform}-${hostArch}/t3`,
25 );
2626 await NodeFSP.mkdir(NodePath.dirname(entry), { recursive: true });
2727 await NodeFSP.mkdir(NodePath.dirname(executable), { recursive: true });
2828 await NodeFSP.writeFile(
2929 NodePath.join(NodePath.dirname(executable), "package.json"),
3030 '{"type":"commonjs"}',
3131 );
32 await NodeFSP.writeFile(entry, legacyCliLauncherScript(distribution));
32 await NodeFSP.writeFile(entry, legacyCliLauncherScript());
3333 await NodeFSP.writeFile(
3434 executable,
3535 `#!${process.execPath}
packages/shared/src/legacyCliLauncher.ts+15-9
......@@ -1,17 +1,23 @@
1/** Node entry point for service launchers installed before executable releases. */
2export function legacyCliLauncherScript(distribution: "npm" | "archive"): string {
3 const executable =
4 distribution === "npm"
5 ? 'join(dirname(require.resolve("@t3code/t3-" + process.platform + "-" + process.arch + "/package.json")), executableName)'
6 : 'resolve(dirname(fileURLToPath(import.meta.url)), "../../..", executableName)';
1/**
2 * `dist/bin.mjs` of the `t3` npm package: the entry point boot-service
3 * launchers installed before 0.0.41 run with Node to start a new version they
4 * just npm-installed. It forwards everything (arguments, stdio, the IPC
5 * channel the launcher talks over, signals, exit status) to the platform
6 * executable in the sibling `@t3code/t3-<platform>-<arch>` package.
7 *
8 * The first server started this way rewrites the service unit to run the
9 * executable directly, so nothing depends on this file after one update.
10 * Remove it once no supported release predates the executable (after the
11 * first stable release that ships it).
12 */
13export function legacyCliLauncherScript(): string {
714 return `import { spawn } from "node:child_process";
815import { constants } from "node:os";
9import { dirname, join, resolve } from "node:path";
10import { fileURLToPath } from "node:url";
16import { dirname, join } from "node:path";
1117import { createRequire } from "node:module";
1218const require = createRequire(import.meta.url);
1319const executableName = process.platform === "win32" ? "t3.exe" : "t3";
14const executable = ${executable};
20const executable = join(dirname(require.resolve("@t3code/t3-" + process.platform + "-" + process.arch + "/package.json")), executableName);
1521const ipc = process.send !== undefined;
1622const child = spawn(executable, process.argv.slice(2), {
1723 stdio: ipc ? ["inherit", "inherit", "inherit", "ipc"] : "inherit",
scripts/build-npm-platform-packages.ts+1-1
......@@ -389,7 +389,7 @@ const stageLauncherPackage = Effect.fn("stageLauncherPackage")(function* (input:
389389 // Older service updaters and launchers run this exact path with Node.
390390 // Keep it in the package so they can preflight and start the new executable.
391391 yield* fs.makeDirectory(path.join(stageDir, "dist"));
392 yield* fs.writeFileString(path.join(stageDir, "dist/bin.mjs"), legacyCliLauncherScript("npm"));
392 yield* fs.writeFileString(path.join(stageDir, "dist/bin.mjs"), legacyCliLauncherScript());
393393 const readme = yield* path.fromFileUrl(new URL("../apps/server/README.md", import.meta.url));
394394 if (yield* fs.exists(readme)) {
395395 yield* fs.copyFile(readme, path.join(stageDir, "README.md"));