| 1 | // @effect-diagnostics nodeBuiltinImport:off - runs before `vp i`, so only Node built-ins exist. |
| 2 | /** |
| 3 | * Worktree setup, run by the t3.json "Setup Worktree" action as |
| 4 | * `node scripts/setup-worktree.ts`. Plain Node keeps one command working in |
| 5 | * every shell T3 Code spawns (zsh, bash, fish, PowerShell): it installs |
| 6 | * dependencies, links the main checkout's gitignored env files into this |
| 7 | * worktree, then warms the web dependency cache. |
| 8 | */ |
| 9 | import * as NodeChildProcess from "node:child_process"; |
| 10 | import * as NodeFS from "node:fs"; |
| 11 | import * as NodePath from "node:path"; |
| 12 | |
| 13 | const ENV_FILES = [".env", NodePath.join("infra", "relay", ".env")]; |
| 14 | |
| 15 | const projectRoot = process.env.T3CODE_PROJECT_ROOT; |
| 16 | if (!projectRoot) { |
| 17 | throw new Error("T3CODE_PROJECT_ROOT is not set. Run this through the t3.json setup action."); |
| 18 | } |
| 19 | const worktree = NodePath.dirname(import.meta.dirname); |
| 20 | |
| 21 | // `shell` resolves `vp` through PATH, including Windows command shims. |
| 22 | // Setup has no one answering install prompts. Pass pnpm's confirmation flag |
| 23 | // through vp and leave stdin closed so other prompts fail instead of hanging. |
| 24 | const install = NodeChildProcess.spawnSync("vp i -- --yes", { |
| 25 | cwd: worktree, |
| 26 | shell: true, |
| 27 | stdio: ["ignore", "inherit", "inherit"], |
| 28 | }); |
| 29 | if (install.status !== 0) process.exit(install.status ?? 1); |
| 30 | |
| 31 | // Env files live as real files in the main checkout; worktrees only get |
| 32 | // symlinks to them. Only a symlink is ever replaced, so a real env file is |
| 33 | // never deleted, including when this runs in the main checkout itself. |
| 34 | for (const file of ENV_FILES) { |
| 35 | const source = NodePath.join(projectRoot, file); |
| 36 | const sourceStat = NodeFS.lstatSync(source, { throwIfNoEntry: false }); |
| 37 | if (!sourceStat) continue; |
| 38 | if (!sourceStat.isFile()) { |
| 39 | process.stderr.write(`Skipping ${file}: ${source} is not a regular file.\n`); |
| 40 | continue; |
| 41 | } |
| 42 | const target = NodePath.join(worktree, file); |
| 43 | const existing = NodeFS.lstatSync(target, { throwIfNoEntry: false }); |
| 44 | if (existing && !existing.isSymbolicLink()) continue; |
| 45 | if (existing) NodeFS.rmSync(target); |
| 46 | NodeFS.symlinkSync(source, target); |
| 47 | } |
| 48 | |
| 49 | const warm = NodeChildProcess.spawnSync( |
| 50 | process.execPath, |
| 51 | [NodePath.join(worktree, "apps", "web", "scripts", "warm-dep-cache.ts")], |
| 52 | { cwd: worktree, stdio: "inherit" }, |
| 53 | ); |
| 54 | process.exit(warm.status ?? 1); |