From b3de243d5ed44783c4e7990d8e63cb519d390a03 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Thu, 24 Sep 2026 23:56:35 -0700 Subject: [PATCH] fix(dev): one t3.json setup action that works on every OS (#13589) Co-authored-by: Claude Opus 5.5 (1M context) --- docs/operations/development.md | 3 +-- knip.jsonc | 7 +++--- scripts/setup-worktree.ts | 45 ++++++++++++++++++++++++++++++++++ t3.json | 8 +----- 4 files changed, 51 insertions(+), 12 deletions(-) create mode 100644 scripts/setup-worktree.ts diff --git a/docs/operations/development.md b/docs/operations/development.md index 1cbf09c16008ba517ad0f09253c5d1579eb7e739..b7e1b257d0b2f00959a1ea865e8c6e7a82f33e05 100644 --- a/docs/operations/development.md +++ b/docs/operations/development.md @@ -76,8 +76,7 @@ Put that value in the main checkout's gitignored `.env`: T3CODE_DEV_AUTH_TOKEN= ``` -The `t3.json` Setup Worktree commands on Unix and Windows link that file to each worktree's -`.env`. The dev runner reads repository env files at startup. `.env.local` and inherited process +The `t3.json` Setup Worktree action links that file to each worktree's `.env`. The dev runner reads repository env files at startup. `.env.local` and inherited process environment values override `.env`, so no per-worktree export is needed after setup. For a manual worktree or launcher without that link, export the same fixed value instead: diff --git a/knip.jsonc b/knip.jsonc index d2f1d7f88395f89018c935f97af31092789dc72a..a6e4f94143a011344213fbdee8666a0fb8dadfc8 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -13,8 +13,9 @@ "vitest": { "entry": [".github/**/*.test.cjs"] }, }, "scripts": { - // Knip loads its preprocessor through a CLI option; native verification runs directly. - "entry": ["knip-schemas.ts", "mobile-native-client.ts"], + // Knip loads its preprocessor through a CLI option; native verification and + // worktree setup (from t3.json) run directly. + "entry": ["knip-schemas.ts", "mobile-native-client.ts", "setup-worktree.ts"], }, "apps/server": { // Vite+ pack entries and the launcher used by installed background services. @@ -52,7 +53,7 @@ "ignoreDependencies": ["playwright-core", "electron-builder"], }, "apps/web": { - // Worktree setup invokes this directly from t3.json. + // Worktree setup (scripts/setup-worktree.ts) runs this directly. "entry": ["scripts/warm-dep-cache.ts"], // UI component modules are copied and adapted as cohesive sets. Keep their // named subcomponents even before they have callers; the file audit still diff --git a/scripts/setup-worktree.ts b/scripts/setup-worktree.ts new file mode 100644 index 0000000000000000000000000000000000000000..ea07a8b65ad81aabd8166a5a1094dbf4cf3d7391 --- /dev/null +++ b/scripts/setup-worktree.ts @@ -0,0 +1,45 @@ +// @effect-diagnostics nodeBuiltinImport:off - runs before `vp i`, so only Node built-ins exist. +/** + * Worktree setup, run by the t3.json "Setup Worktree" action as + * `node scripts/setup-worktree.ts`. Plain Node keeps one command working in + * every shell T3 Code spawns (zsh, bash, fish, PowerShell): it installs + * dependencies, links the main checkout's gitignored env files into this + * worktree, then warms the web dependency cache. + */ +import * as NodeChildProcess from "node:child_process"; +import * as NodeFS from "node:fs"; +import * as NodePath from "node:path"; + +const ENV_FILES = [".env", NodePath.join("infra", "relay", ".env")]; + +const projectRoot = process.env.T3CODE_PROJECT_ROOT; +if (!projectRoot) { + throw new Error("T3CODE_PROJECT_ROOT is not set. Run this through the t3.json setup action."); +} +const worktree = NodePath.dirname(import.meta.dirname); + +// `shell` resolves `vp` through PATH, including Windows command shims. +const install = NodeChildProcess.spawnSync("vp i", { + cwd: worktree, + shell: true, + stdio: "inherit", +}); +if (install.status !== 0) process.exit(install.status ?? 1); + +// In the main checkout itself, relinking would replace the real env files. +if (NodeFS.realpathSync(projectRoot) !== NodeFS.realpathSync(worktree)) { + for (const file of ENV_FILES) { + const source = NodePath.join(projectRoot, file); + if (!NodeFS.existsSync(source)) continue; + const target = NodePath.join(worktree, file); + NodeFS.rmSync(target, { force: true }); + NodeFS.symlinkSync(source, target); + } +} + +const warm = NodeChildProcess.spawnSync( + process.execPath, + [NodePath.join(worktree, "apps", "web", "scripts", "warm-dep-cache.ts")], + { cwd: worktree, stdio: "inherit" }, +); +process.exit(warm.status ?? 1); diff --git a/t3.json b/t3.json index 284d416a682074b6b232e888a719b6edfa6881fb..d2c55e050776ecd7bd0b250438660fbdeeef1bd1 100644 --- a/t3.json +++ b/t3.json @@ -4,13 +4,7 @@ "scripts": [ { "name": "Setup Worktree", - "command": "vp i && ln -sf $T3CODE_PROJECT_ROOT/.env .env && ln -sf $T3CODE_PROJECT_ROOT/infra/relay/.env infra/relay/.env && node apps/web/scripts/warm-dep-cache.ts", - "icon": "configure", - "runOnWorktreeCreate": true - }, - { - "name": "Setup Worktree (Windows)", - "command": "vp i && New-Item -ItemType SymbolicLink -Path .env -Target \"$env:T3CODE_PROJECT_ROOT\\.env\" -Force && New-Item -ItemType SymbolicLink -Path \"infra\\relay\\.env\" -Target \"$env:T3CODE_PROJECT_ROOT\\infra\\relay\\.env\" -Force && node apps\\web\\scripts\\warm-dep-cache.ts", + "command": "node scripts/setup-worktree.ts", "icon": "configure", "runOnWorktreeCreate": true } -- 2.54.0