| author | |
| committer | |
| log | 15380f2b5f99065efb0100cc47ad96255594ae4e |
| tree | 8da3f6754a591ee9ab60d7640ad475dce75548cb |
| parent | 48b1f7c38862e3672cf5b8ec9ca6edbc6f48e5ef |
- Route `dev*` scripts through `scripts/dev-runner.mjs` to compute shared server/web port offsets
- Support `T3CODE_DEV_INSTANCE` (hashed or numeric) and explicit `T3CODE_PORT_OFFSET`
- Document multi-instance usage and allow server `VITE_DEV_SERVER_URL` override5 files changed, 143 insertions(+), 5 deletions(-)
README.md+13| ... | @@ -49,6 +49,9 @@ bun run dev | ... | @@ -49,6 +49,9 @@ bun run dev |
| 49 | # Desktop development | 49 | # Desktop development |
| 50 | bun run dev:desktop | 50 | bun run dev:desktop |
| 51 | 51 | ||
| 52 | # Desktop development on an isolated port set | ||
| 53 | T3CODE_DEV_INSTANCE=feature-xyz bun run dev:desktop | ||
| 54 | |||
| 52 | # Production | 55 | # Production |
| 53 | bun run build | 56 | bun run build |
| 54 | bun run start | 57 | bun run start |
| ... | @@ -67,6 +70,16 @@ npx t3 | ... | @@ -67,6 +70,16 @@ npx t3 |
| 67 | - `bun run typecheck` — Strict TypeScript checks for all packages. | 70 | - `bun run typecheck` — Strict TypeScript checks for all packages. |
| 68 | - `bun run test` — Runs workspace tests. | 71 | - `bun run test` — Runs workspace tests. |
| 69 | 72 | ||
| 73 | ### Running multiple dev instances | ||
| 74 | |||
| 75 | Set `T3CODE_DEV_INSTANCE` to any value to deterministically shift all dev ports together. | ||
| 76 | |||
| 77 | - Default ports: server `3773`, web `5173` | ||
| 78 | - Shifted ports: `base + offset` (offset is hashed from `T3CODE_DEV_INSTANCE`) | ||
| 79 | - Example: `T3CODE_DEV_INSTANCE=branch-a bun run dev:desktop` | ||
| 80 | |||
| 81 | If you want full control instead of hashing, set `T3CODE_PORT_OFFSET` to a numeric offset. | ||
| 82 | |||
| 70 | ## Runtime modes | 83 | ## Runtime modes |
| 71 | 84 | ||
| 72 | T3 Code has a global runtime mode switch in the chat toolbar: | 85 | T3 Code has a global runtime mode switch in the chat toolbar: |
apps/server/package.json+1-1| ... | @@ -10,7 +10,7 @@ | ... | @@ -10,7 +10,7 @@ |
| 10 | "type": "module", | 10 | "type": "module", |
| 11 | "main": "./dist/index.mjs", | 11 | "main": "./dist/index.mjs", |
| 12 | "scripts": { | 12 | "scripts": { |
| 13 | "dev": "VITE_DEV_SERVER_URL=http://localhost:5173 bun run src/index.ts", | 13 | "dev": "VITE_DEV_SERVER_URL=${VITE_DEV_SERVER_URL:-http://localhost:5173} bun run src/index.ts", |
| 14 | "build": "tsdown && node scripts/bundle-client.mjs", | 14 | "build": "tsdown && node scripts/bundle-client.mjs", |
| 15 | "start": "node dist/index.mjs", | 15 | "start": "node dist/index.mjs", |
| 16 | "typecheck": "tsc --noEmit", | 16 | "typecheck": "tsc --noEmit", |
package.json+4-4| ... | @@ -6,10 +6,10 @@ | ... | @@ -6,10 +6,10 @@ |
| 6 | "packages/*" | 6 | "packages/*" |
| 7 | ], | 7 | ], |
| 8 | "scripts": { | 8 | "scripts": { |
| 9 | "dev": "T3CODE_LOG_WS_EVENTS=1 VITE_WS_URL=ws://localhost:3773 turbo run dev --ui=tui --filter=@t3tools/contracts --filter=@t3tools/web --filter=t3 --parallel", | 9 | "dev": "bun run scripts/dev-runner.mjs dev", |
| 10 | "dev:server": "turbo run dev --filter=t3", | 10 | "dev:server": "bun run scripts/dev-runner.mjs dev:server", |
| 11 | "dev:web": "VITE_WS_URL=ws://localhost:3773 turbo run dev --filter=@t3tools/web", | 11 | "dev:web": "bun run scripts/dev-runner.mjs dev:web", |
| 12 | "dev:desktop": "turbo run dev --filter=@t3tools/desktop --filter=@t3tools/web --parallel", | 12 | "dev:desktop": "bun run scripts/dev-runner.mjs dev:desktop", |
| 13 | "start": "turbo run start --filter=t3", | 13 | "start": "turbo run start --filter=t3", |
| 14 | "start:desktop": "turbo run start --filter=@t3tools/desktop", | 14 | "start:desktop": "turbo run start --filter=@t3tools/desktop", |
| 15 | "build": "turbo run build", | 15 | "build": "turbo run build", |
scripts/dev-runner.mjs created+124| ... | @@ -0,0 +1,124 @@ | ||
| 1 | import { spawn } from "node:child_process"; | ||
| 2 | |||
| 3 | const BASE_SERVER_PORT = 3773; | ||
| 4 | const BASE_WEB_PORT = 5173; | ||
| 5 | const MAX_HASH_OFFSET = 3000; | ||
| 6 | const MODE_ARGS = { | ||
| 7 | dev: [ | ||
| 8 | "run", | ||
| 9 | "dev", | ||
| 10 | "--ui=tui", | ||
| 11 | "--filter=@t3tools/contracts", | ||
| 12 | "--filter=@t3tools/web", | ||
| 13 | "--filter=t3", | ||
| 14 | "--parallel", | ||
| 15 | ], | ||
| 16 | "dev:server": ["run", "dev", "--filter=t3"], | ||
| 17 | "dev:web": ["run", "dev", "--filter=@t3tools/web"], | ||
| 18 | "dev:desktop": ["run", "dev", "--filter=@t3tools/desktop", "--filter=@t3tools/web", "--parallel"], | ||
| 19 | }; | ||
| 20 | |||
| 21 | function parseInteger(value, envName) { | ||
| 22 | const parsed = Number(value); | ||
| 23 | if (!Number.isInteger(parsed)) { | ||
| 24 | throw new Error(`Invalid ${envName}: ${value}`); | ||
| 25 | } | ||
| 26 | return parsed; | ||
| 27 | } | ||
| 28 | |||
| 29 | function hashSeed(seed) { | ||
| 30 | let hash = 0x811c9dc5; | ||
| 31 | for (let i = 0; i < seed.length; i += 1) { | ||
| 32 | hash ^= seed.charCodeAt(i); | ||
| 33 | hash = Math.imul(hash, 0x01000193); | ||
| 34 | } | ||
| 35 | return hash >>> 0; | ||
| 36 | } | ||
| 37 | |||
| 38 | function resolveOffset() { | ||
| 39 | const explicitOffset = process.env.T3CODE_PORT_OFFSET?.trim(); | ||
| 40 | if (explicitOffset) { | ||
| 41 | const parsed = parseInteger(explicitOffset, "T3CODE_PORT_OFFSET"); | ||
| 42 | if (parsed < 0) { | ||
| 43 | throw new Error(`Invalid T3CODE_PORT_OFFSET: ${explicitOffset}`); | ||
| 44 | } | ||
| 45 | return { offset: parsed, source: `T3CODE_PORT_OFFSET=${explicitOffset}` }; | ||
| 46 | } | ||
| 47 | |||
| 48 | const seed = process.env.T3CODE_DEV_INSTANCE?.trim(); | ||
| 49 | if (!seed) { | ||
| 50 | return { offset: 0, source: "default ports" }; | ||
| 51 | } | ||
| 52 | |||
| 53 | if (/^\d+$/.test(seed)) { | ||
| 54 | return { offset: Number(seed), source: `numeric T3CODE_DEV_INSTANCE=${seed}` }; | ||
| 55 | } | ||
| 56 | |||
| 57 | const offset = (hashSeed(seed) % MAX_HASH_OFFSET) + 1; | ||
| 58 | return { offset, source: `hashed T3CODE_DEV_INSTANCE=${seed}` }; | ||
| 59 | } | ||
| 60 | |||
| 61 | function main() { | ||
| 62 | const mode = process.argv[2]; | ||
| 63 | const isDryRun = process.argv.includes("--dry-run"); | ||
| 64 | if (!mode || !(mode in MODE_ARGS)) { | ||
| 65 | const supportedModes = Object.keys(MODE_ARGS).join(", "); | ||
| 66 | throw new Error(`Usage: bun scripts/dev-runner.mjs <mode>. Supported modes: ${supportedModes}`); | ||
| 67 | } | ||
| 68 | |||
| 69 | const { offset, source } = resolveOffset(); | ||
| 70 | const serverPort = BASE_SERVER_PORT + offset; | ||
| 71 | const webPort = BASE_WEB_PORT + offset; | ||
| 72 | |||
| 73 | if (serverPort > 65535 || webPort > 65535) { | ||
| 74 | throw new Error( | ||
| 75 | `Port offset too large (${offset}). Computed ports: server=${serverPort}, web=${webPort}`, | ||
| 76 | ); | ||
| 77 | } | ||
| 78 | |||
| 79 | const env = { | ||
| 80 | ...process.env, | ||
| 81 | T3CODE_PORT: String(serverPort), | ||
| 82 | PORT: String(webPort), | ||
| 83 | ELECTRON_RENDERER_PORT: String(webPort), | ||
| 84 | VITE_WS_URL: `ws://localhost:${serverPort}`, | ||
| 85 | VITE_DEV_SERVER_URL: `http://localhost:${webPort}`, | ||
| 86 | }; | ||
| 87 | if (mode === "dev" && !env.T3CODE_LOG_WS_EVENTS) { | ||
| 88 | env.T3CODE_LOG_WS_EVENTS = "1"; | ||
| 89 | } | ||
| 90 | |||
| 91 | console.info( | ||
| 92 | `[dev-runner] mode=${mode} source=${source} serverPort=${serverPort} webPort=${webPort}`, | ||
| 93 | ); | ||
| 94 | |||
| 95 | if (isDryRun) { | ||
| 96 | return; | ||
| 97 | } | ||
| 98 | |||
| 99 | const command = process.platform === "win32" ? "turbo.cmd" : "turbo"; | ||
| 100 | const child = spawn(command, MODE_ARGS[mode], { | ||
| 101 | stdio: "inherit", | ||
| 102 | env, | ||
| 103 | }); | ||
| 104 | |||
| 105 | child.on("exit", (code, signal) => { | ||
| 106 | if (signal) { | ||
| 107 | process.kill(process.pid, signal); | ||
| 108 | return; | ||
| 109 | } | ||
| 110 | process.exit(code ?? 0); | ||
| 111 | }); | ||
| 112 | |||
| 113 | child.on("error", (error) => { | ||
| 114 | console.error("[dev-runner] failed to start turbo", error); | ||
| 115 | process.exit(1); | ||
| 116 | }); | ||
| 117 | } | ||
| 118 | |||
| 119 | try { | ||
| 120 | main(); | ||
| 121 | } catch (error) { | ||
| 122 | console.error("[dev-runner]", error); | ||
| 123 | process.exit(1); | ||
| 124 | } | ||
turbo.json+1| ... | @@ -4,6 +4,7 @@ | ... | @@ -4,6 +4,7 @@ |
| 4 | "PORT", | 4 | "PORT", |
| 5 | "VITE_WS_URL", | 5 | "VITE_WS_URL", |
| 6 | "VITE_DEV_SERVER_URL", | 6 | "VITE_DEV_SERVER_URL", |
| 7 | "ELECTRON_RENDERER_PORT", | ||
| 7 | "T3CODE_LOG_WS_EVENTS", | 8 | "T3CODE_LOG_WS_EVENTS", |
| 8 | "T3CODE_MODE", | 9 | "T3CODE_MODE", |
| 9 | "T3CODE_PORT", | 10 | "T3CODE_PORT", |