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 @@...@@ -1,4 +1,3 @@
1import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
2import * as NodeServices from "@effect/platform-node/NodeServices";1import * as NodeServices from "@effect/platform-node/NodeServices";
3import { assert, it } from "@effect/vitest";2import { assert, it } from "@effect/vitest";
4import * as Deferred from "effect/Deferred";3import * as Deferred from "effect/Deferred";
...@@ -95,19 +94,6 @@ it.layer(NodeServices.layer)("ensurePinnedRuntimeInstalled", (it) => {...@@ -95,19 +94,6 @@ it.layer(NodeServices.layer)("ensurePinnedRuntimeInstalled", (it) => {
95 assert.deepEqual(commands, ["tar"]);94 assert.deepEqual(commands, ["tar"]);
96 assert.equal(yield* fs.readFileString(paths.sentinelPath), `${version}\n`);95 assert.equal(yield* fs.readFileString(paths.sentinelPath), `${version}\n`);
97 assert.isFalse(yield* fs.exists(path.join(paths.versionDir, "t3-runtime-archive")));96 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 }
111 }),97 }),
112 );98 );
11399
...@@ -223,40 +209,6 @@ it.layer(NodeServices.layer)("ensurePinnedRuntimeInstalled", (it) => {...@@ -223,40 +209,6 @@ it.layer(NodeServices.layer)("ensurePinnedRuntimeInstalled", (it) => {
223 }),209 }),
224 );210 );
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
260 it.effect("preserves a completed runtime when validation fails", () =>212 it.effect("preserves a completed runtime when validation fails", () =>
261 Effect.gen(function* () {213 Effect.gen(function* () {
262 const fs = yield* FileSystem.FileSystem;214 const fs = yield* FileSystem.FileSystem;
apps/server/src/cloud/pinnedRuntime.ts+1-26
...@@ -8,7 +8,6 @@ import * as Option from "effect/Option";...@@ -8,7 +8,6 @@ import * as Option from "effect/Option";
8import * as Semaphore from "effect/Semaphore";8import * as Semaphore from "effect/Semaphore";
9import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";9import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
1010
11import { legacyCliLauncherScript } from "@t3tools/shared/legacyCliLauncher";
12import {11import {
13 CLI_RELEASE_CHECKSUMS_FILE,12 CLI_RELEASE_CHECKSUMS_FILE,
14 cliArchiveFileName,13 cliArchiveFileName,
...@@ -230,24 +229,6 @@ const installPinnedRuntime = Effect.fn("cloud.pinned_runtime.ensure_installed")(...@@ -230,24 +229,6 @@ const installPinnedRuntime = Effect.fn("cloud.pinned_runtime.ensure_installed")(
230 input: PinnedRuntimeInstallInput,229 input: PinnedRuntimeInstallInput,
231) {230) {
232 const { fs } = input;231 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 );
251 const paths = pinnedRuntimePaths(input.path, input.baseDir, input.version, input.platform);232 const paths = pinnedRuntimePaths(input.path, input.baseDir, input.version, input.platform);
252 const [versionDirExists, entryExists, sentinel] = yield* Effect.all([233 const [versionDirExists, entryExists, sentinel] = yield* Effect.all([
253 fs.exists(paths.versionDir),234 fs.exists(paths.versionDir),
...@@ -261,7 +242,6 @@ const installPinnedRuntime = Effect.fn("cloud.pinned_runtime.ensure_installed")(...@@ -261,7 +242,6 @@ const installPinnedRuntime = Effect.fn("cloud.pinned_runtime.ensure_installed")(
261 const alreadyPinned =242 const alreadyPinned =
262 entryExists && Option.isSome(sentinel) && sentinel.value.trim() === input.version;243 entryExists && Option.isSome(sentinel) && sentinel.value.trim() === input.version;
263 if (alreadyPinned) {244 if (alreadyPinned) {
264 yield* ensureLegacyEntry(paths.versionDir);
265 yield* input.validate(paths);245 yield* input.validate(paths);
266 return paths;246 return paths;
267 }247 }
...@@ -310,8 +290,6 @@ const installPinnedRuntime = Effect.fn("cloud.pinned_runtime.ensure_installed")(...@@ -310,8 +290,6 @@ const installPinnedRuntime = Effect.fn("cloud.pinned_runtime.ensure_installed")(
310 return yield* Effect.gen(function* () {290 return yield* Effect.gen(function* () {
311 yield* installFromArchive(input, stagingDir);291 yield* installFromArchive(input, stagingDir);
312292
313 yield* ensureLegacyEntry(stagingDir);
314
315 yield* input.validate(stagingPaths);293 yield* input.validate(stagingPaths);
316 yield* fs294 yield* fs
317 .writeFileString(stagingPaths.sentinelPath, `${input.version}\n`)295 .writeFileString(stagingPaths.sentinelPath, `${input.version}\n`)
...@@ -350,10 +328,7 @@ const installPinnedRuntime = Effect.fn("cloud.pinned_runtime.ensure_installed")(...@@ -350,10 +328,7 @@ const installPinnedRuntime = Effect.fn("cloud.pinned_runtime.ensure_installed")(
350 ),328 ),
351 ),329 ),
352 );330 );
353 if (!published) {331 if (!published) yield* input.validate(paths);
354 yield* ensureLegacyEntry(paths.versionDir);
355 yield* input.validate(paths);
356 }
357 return paths;332 return paths;
358 }).pipe(333 }).pipe(
359 Effect.ensuring(fs.remove(stagingDir, { recursive: true, force: true }).pipe(Effect.ignore)),334 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();...@@ -14,22 +14,22 @@ const hostPlatform = NodeOS.platform();
14const hostArch = NodeOS.arch();14const hostArch = NodeOS.arch();
1515
16// The fixture executable uses a POSIX shebang. The wrapper itself also runs on Windows.16// The fixture executable uses a POSIX shebang. The wrapper itself also runs on Windows.
17it.skipIf(hostPlatform === "win32").each(["npm", "archive"] as const)(17it.skipIf(hostPlatform === "win32")(
18 "keeps %s service IPC, arguments, and termination connected",18 "keeps service IPC, arguments, and termination connected",
19 async (distribution) => {19 async () => {
20 const root = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-legacy-launcher-"));20 const root = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-legacy-launcher-"));
21 const entry = NodePath.join(root, "node_modules/t3/dist/bin.mjs");21 const entry = NodePath.join(root, "node_modules/t3/dist/bin.mjs");
22 const executable =22 const executable = NodePath.join(
23 distribution === "archive"23 root,
24 ? NodePath.join(root, "t3")24 `node_modules/@t3code/t3-${hostPlatform}-${hostArch}/t3`,
25 : NodePath.join(root, `node_modules/@t3code/t3-${hostPlatform}-${hostArch}/t3`);25 );
26 await NodeFSP.mkdir(NodePath.dirname(entry), { recursive: true });26 await NodeFSP.mkdir(NodePath.dirname(entry), { recursive: true });
27 await NodeFSP.mkdir(NodePath.dirname(executable), { recursive: true });27 await NodeFSP.mkdir(NodePath.dirname(executable), { recursive: true });
28 await NodeFSP.writeFile(28 await NodeFSP.writeFile(
29 NodePath.join(NodePath.dirname(executable), "package.json"),29 NodePath.join(NodePath.dirname(executable), "package.json"),
30 '{"type":"commonjs"}',30 '{"type":"commonjs"}',
31 );31 );
32 await NodeFSP.writeFile(entry, legacyCliLauncherScript(distribution));32 await NodeFSP.writeFile(entry, legacyCliLauncherScript());
33 await NodeFSP.writeFile(33 await NodeFSP.writeFile(
34 executable,34 executable,
35 `#!${process.execPath}35 `#!${process.execPath}
packages/shared/src/legacyCliLauncher.ts+15-9
...@@ -1,17 +1,23 @@...@@ -1,17 +1,23 @@
1/** Node entry point for service launchers installed before executable releases. */1/**
2export function legacyCliLauncherScript(distribution: "npm" | "archive"): string {2 * `dist/bin.mjs` of the `t3` npm package: the entry point boot-service
3 const executable =3 * launchers installed before 0.0.41 run with Node to start a new version they
4 distribution === "npm"4 * just npm-installed. It forwards everything (arguments, stdio, the IPC
5 ? 'join(dirname(require.resolve("@t3code/t3-" + process.platform + "-" + process.arch + "/package.json")), executableName)'5 * channel the launcher talks over, signals, exit status) to the platform
6 : 'resolve(dirname(fileURLToPath(import.meta.url)), "../../..", executableName)';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 {
7 return `import { spawn } from "node:child_process";14 return `import { spawn } from "node:child_process";
8import { constants } from "node:os";15import { constants } from "node:os";
9import { dirname, join, resolve } from "node:path";16import { dirname, join } from "node:path";
10import { fileURLToPath } from "node:url";
11import { createRequire } from "node:module";17import { createRequire } from "node:module";
12const require = createRequire(import.meta.url);18const require = createRequire(import.meta.url);
13const executableName = process.platform === "win32" ? "t3.exe" : "t3";19const 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);
15const ipc = process.send !== undefined;21const ipc = process.send !== undefined;
16const child = spawn(executable, process.argv.slice(2), {22const child = spawn(executable, process.argv.slice(2), {
17 stdio: ipc ? ["inherit", "inherit", "inherit", "ipc"] : "inherit",23 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:...@@ -389,7 +389,7 @@ const stageLauncherPackage = Effect.fn("stageLauncherPackage")(function* (input:
389 // Older service updaters and launchers run this exact path with Node.389 // Older service updaters and launchers run this exact path with Node.
390 // Keep it in the package so they can preflight and start the new executable.390 // Keep it in the package so they can preflight and start the new executable.
391 yield* fs.makeDirectory(path.join(stageDir, "dist"));391 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());
393 const readme = yield* path.fromFileUrl(new URL("../apps/server/README.md", import.meta.url));393 const readme = yield* path.fromFileUrl(new URL("../apps/server/README.md", import.meta.url));
394 if (yield* fs.exists(readme)) {394 if (yield* fs.exists(readme)) {
395 yield* fs.copyFile(readme, path.join(stageDir, "README.md"));395 yield* fs.copyFile(readme, path.join(stageDir, "README.md"));