authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-24 23:56:35-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-24 23:56:35-07:00
logb3de243d5ed44783c4e7990d8e63cb519d390a03
tree13557e4ad2a5227c94a6cf3ab4059ff042011707
parentd10bd1360ace3ab1dbccc1ee706c9169982d98ae
signature Signed by PGP key B5690EEEBB952194

fix(dev): one t3.json setup action that works on every OS (#13589)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

4 files changed, 51 insertions(+), 12 deletions(-)

docs/operations/development.md+1-2
......@@ -76,8 +76,7 @@ Put that value in the main checkout's gitignored `.env`:
7676T3CODE_DEV_AUTH_TOKEN=<the value generated above>
7777```
7878
79The `t3.json` Setup Worktree commands on Unix and Windows link that file to each worktree's
80`.env`. The dev runner reads repository env files at startup. `.env.local` and inherited process
79The `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
8180environment values override `.env`, so no per-worktree export is needed after setup.
8281
8382For a manual worktree or launcher without that link, export the same fixed value instead:
knip.jsonc+4-3
......@@ -13,8 +13,9 @@
1313 "vitest": { "entry": [".github/**/*.test.cjs"] },
1414 },
1515 "scripts": {
16 // Knip loads its preprocessor through a CLI option; native verification runs directly.
17 "entry": ["knip-schemas.ts", "mobile-native-client.ts"],
16 // Knip loads its preprocessor through a CLI option; native verification and
17 // worktree setup (from t3.json) run directly.
18 "entry": ["knip-schemas.ts", "mobile-native-client.ts", "setup-worktree.ts"],
1819 },
1920 "apps/server": {
2021 // Vite+ pack entries and the launcher used by installed background services.
......@@ -52,7 +53,7 @@
5253 "ignoreDependencies": ["playwright-core", "electron-builder"],
5354 },
5455 "apps/web": {
55 // Worktree setup invokes this directly from t3.json.
56 // Worktree setup (scripts/setup-worktree.ts) runs this directly.
5657 "entry": ["scripts/warm-dep-cache.ts"],
5758 // UI component modules are copied and adapted as cohesive sets. Keep their
5859 // named subcomponents even before they have callers; the file audit still
scripts/setup-worktree.ts created+45
......@@ -0,0 +1,45 @@
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 */
9import * as NodeChildProcess from "node:child_process";
10import * as NodeFS from "node:fs";
11import * as NodePath from "node:path";
12
13const ENV_FILES = [".env", NodePath.join("infra", "relay", ".env")];
14
15const projectRoot = process.env.T3CODE_PROJECT_ROOT;
16if (!projectRoot) {
17 throw new Error("T3CODE_PROJECT_ROOT is not set. Run this through the t3.json setup action.");
18}
19const worktree = NodePath.dirname(import.meta.dirname);
20
21// `shell` resolves `vp` through PATH, including Windows command shims.
22const install = NodeChildProcess.spawnSync("vp i", {
23 cwd: worktree,
24 shell: true,
25 stdio: "inherit",
26});
27if (install.status !== 0) process.exit(install.status ?? 1);
28
29// In the main checkout itself, relinking would replace the real env files.
30if (NodeFS.realpathSync(projectRoot) !== NodeFS.realpathSync(worktree)) {
31 for (const file of ENV_FILES) {
32 const source = NodePath.join(projectRoot, file);
33 if (!NodeFS.existsSync(source)) continue;
34 const target = NodePath.join(worktree, file);
35 NodeFS.rmSync(target, { force: true });
36 NodeFS.symlinkSync(source, target);
37 }
38}
39
40const warm = NodeChildProcess.spawnSync(
41 process.execPath,
42 [NodePath.join(worktree, "apps", "web", "scripts", "warm-dep-cache.ts")],
43 { cwd: worktree, stdio: "inherit" },
44);
45process.exit(warm.status ?? 1);
t3.json+1-7
......@@ -4,13 +4,7 @@
44 "scripts": [
55 {
66 "name": "Setup Worktree",
7 "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",
8 "icon": "configure",
9 "runOnWorktreeCreate": true
10 },
11 {
12 "name": "Setup Worktree (Windows)",
13 "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",
7 "command": "node scripts/setup-worktree.ts",
148 "icon": "configure",
159 "runOnWorktreeCreate": true
1610 }