Open History to see the last commit
1// @effect-diagnostics nodeBuiltinImport:off - runs before `vp i`, so only Node built-ins exist.
2import * as NodeChildProcess from "node:child_process";
3import * as NodeFS from "node:fs";
4import * as NodePath from "node:path";
5
6const ENV_FILES = [".env", NodePath.join("infra", "relay", ".env")];
7
8const worktree = NodePath.dirname(import.meta.dirname);
9
10// T3 runs setup in the background, so agents can start before it ends.
11// `setup-running` holds this PID until setup ends with `setup-done` or
12// `setup-failed`, so agents wait instead of racing it with their own `vp i`.
13const markerDir = NodePath.join(worktree, ".t3");
14const runningMarker = NodePath.join(markerDir, "setup-running");
15const doneMarker = NodePath.join(markerDir, "setup-done");
16const failedMarker = NodePath.join(markerDir, "setup-failed");
17NodeFS.rmSync(doneMarker, { force: true });
18NodeFS.rmSync(failedMarker, { force: true });
19NodeFS.mkdirSync(markerDir, { recursive: true });
20NodeFS.writeFileSync(runningMarker, `${process.pid}\n`);
21let failureDetail = "";
22process.on("uncaughtExceptionMonitor", (error) => {
23 failureDetail = error.stack ?? String(error);
24});
25process.on("exit", (code) => {
26 if (code === 0) NodeFS.writeFileSync(doneMarker, "");
27 else NodeFS.writeFileSync(failedMarker, `Setup exited with code ${code}.\n${failureDetail}\n`);
28 NodeFS.rmSync(runningMarker, { force: true });
29});
30// A cancelled setup must still leave a marker, or agents would wait forever.
31// During install, the signal goes on to the installer and setup exits when the
32// install output closes. Any child the shell leaves behind holds that output
33// open, so no marker appears while `vp i` can still write node_modules.
34let cancelSignal: NodeJS.Signals | undefined;
35let activeInstall: NodeChildProcess.ChildProcess | undefined;
36for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"] as const) {
37 process.on(signal, () => {
38 cancelSignal = signal;
39 if (activeInstall) activeInstall.kill(signal);
40 else process.exit(1);
41 });
42}
43
44const projectRoot = process.env.T3CODE_PROJECT_ROOT;
45if (!projectRoot) {
46 throw new Error("T3CODE_PROJECT_ROOT is not set. Run this through the t3.json setup action.");
47}
48
49// Use the shell for Windows vp shims. Unattended installs need pnpm's confirmation
50// flag and closed stdin so other prompts fail. A stale lockfile must fail, not be rewritten.
51// Pipe output so the failure marker can keep its tail.
52const install = NodeChildProcess.spawn("vp i --frozen-lockfile -- --yes", {
53 cwd: worktree,
54 shell: true,
55 stdio: ["ignore", "pipe", "pipe"],
56});
57activeInstall = install;
58let installOutput = "";
59for (const [source, target] of [
60 [install.stdout, process.stdout],
61 [install.stderr, process.stderr],
62] as const) {
63 source.setEncoding("utf8");
64 source.on("data", (chunk: string) => {
65 target.write(chunk);
66 installOutput = (installOutput + chunk).slice(-8_000);
67 });
68}
69const installStatus = await new Promise<number | null>((resolve) => install.on("close", resolve));
70activeInstall = undefined;
71if (installStatus !== 0 || cancelSignal) {
72 failureDetail = cancelSignal
73 ? `Cancelled by ${cancelSignal}.`
74 : installOutput.trimEnd().split("\n").slice(-30).join("\n");
75 process.exit(installStatus || 1);
76}
77
78// pnpm cannot clone or hardlink across filesystems, so a store on another
79// device silently turns every worktree install into a full copy.
80const storeDir: unknown = JSON.parse(
81 NodeFS.readFileSync(NodePath.join(worktree, "node_modules", ".modules.yaml"), "utf8"),
82).storeDir;
83if (
84 typeof storeDir === "string" &&
85 NodeFS.statSync(storeDir).dev !== NodeFS.statSync(NodePath.join(worktree, "node_modules")).dev
86) {
87 process.stderr.write(
88 [
89 "",
90 "WARNING: the pnpm store is on a different filesystem than this worktree.",
91 ` store: ${storeDir}`,
92 ` worktree: ${worktree}`,
93 "pnpm cannot hardlink or clone across filesystems, so it copied every package into node_modules.",
94 "Fix: put the store on the worktree's filesystem, for example:",
95 ` pnpm config set store-dir ${JSON.stringify(NodePath.join(NodePath.dirname(worktree), ".pnpm-store"))} --global`,
96 "",
97 "",
98 ].join("\n"),
99 );
100}
101
102// Worktrees link main-checkout env files; replace only symlinks so real files always survive.
103for (const file of ENV_FILES) {
104 const source = NodePath.join(projectRoot, file);
105 const sourceStat = NodeFS.lstatSync(source, { throwIfNoEntry: false });
106 if (!sourceStat) continue;
107 if (!sourceStat.isFile()) {
108 process.stderr.write(`Skipping ${file}: ${source} is not a regular file.\n`);
109 continue;
110 }
111 const target = NodePath.join(worktree, file);
112 const existing = NodeFS.lstatSync(target, { throwIfNoEntry: false });
113 if (existing && !existing.isSymbolicLink()) continue;
114 if (existing) NodeFS.rmSync(target);
115 NodeFS.symlinkSync(source, target);
116}