forked from Spectoda/t3code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstart.ts
More file actions
169 lines (152 loc) · 4.97 KB
/
start.ts
File metadata and controls
169 lines (152 loc) · 4.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/**
* Content Studio launcher — starts the server + web app in Content Studio mode.
* Designed to be invoked from the content app's "Start Studio" button.
*
* Spawns two processes in parallel:
* 1. Content Studio server (port 3774) — orchestration, provider sessions, WebSocket RPC
* 2. Content Studio web/Vite (port 5290) — Content Studio UI
*
* This file lives inside the content-studio submodule.
*/
import { join, dirname, resolve } from "path";
import { existsSync } from "fs";
import { execSync } from "child_process";
// content/studio/ is the submodule root, which IS the t3code root
const studioRoot = import.meta.dir;
const contentRoot = dirname(studioRoot);
const modulesRoot = dirname(contentRoot);
const workspaceRoot = dirname(modulesRoot);
const serverDir = join(studioRoot, "apps", "server");
const webDir = join(studioRoot, "apps", "web");
const bunPath = process.execPath;
const SERVER_PORT = 3774;
const WEB_PORT = 5290;
// Ensure provider CLIs (codex, claude) are on PATH even when launched from Astro integration
const homedir = process.env.HOME ?? "/Users/" + process.env.USER;
const nvmDir = join(homedir, ".nvm", "versions", "node");
let nvmBin = "";
if (existsSync(nvmDir)) {
try {
const versions = execSync(`ls -1 "${nvmDir}"`, { encoding: "utf8" }).trim().split("\n");
const latest = versions
.filter((v) => v.startsWith("v"))
.toSorted()
.pop();
if (latest) nvmBin = join(nvmDir, latest, "bin");
} catch {}
}
const extraPaths = [
join(homedir, ".superset", "bin"),
nvmBin,
join(homedir, ".bun", "bin"),
join(homedir, ".local", "bin"),
"/usr/local/bin",
"/opt/homebrew/bin",
].filter((p) => p && existsSync(p));
const enrichedPath = [...extraPaths, process.env.PATH ?? ""].join(":");
console.log(`[Content Studio] Studio root: ${studioRoot}`);
console.log(`[Content Studio] Workspace root: ${workspaceRoot}`);
if (!existsSync(serverDir)) {
console.error(`[Content Studio] ERROR: server not found at ${serverDir}`);
process.exit(1);
}
if (!existsSync(webDir)) {
console.error(`[Content Studio] ERROR: web not found at ${webDir}`);
process.exit(1);
}
// --- Reclaim ports from stale processes ---
function reclaimPort(port: number) {
try {
const output = execSync(`lsof -ti:${port}`, { encoding: "utf8" }).trim();
if (output) {
const pids = output
.split("\n")
.map((p) => p.trim())
.filter(Boolean);
console.log(`[Content Studio] Reclaiming port ${port} from PIDs: ${pids.join(", ")}`);
for (const pid of pids) {
try {
execSync(`kill -9 ${pid}`, { stdio: "ignore" });
} catch {}
}
execSync("sleep 1", { stdio: "ignore" });
}
} catch {}
}
reclaimPort(SERVER_PORT);
reclaimPort(WEB_PORT);
// --- Start BOTH processes in parallel ---
console.log(
`[Content Studio] Starting server (port ${SERVER_PORT}) and web (port ${WEB_PORT}) in parallel...`,
);
const serverProc = Bun.spawn(
[
bunPath,
"run",
"src/bin.ts",
workspaceRoot,
"--mode",
"web",
"--port",
String(SERVER_PORT),
"--no-browser",
"--auto-bootstrap-project-from-cwd",
],
{
cwd: serverDir,
stdout: "inherit",
stderr: "inherit",
env: {
...process.env,
PATH: enrichedPath,
T3CODE_MODE: "web",
T3CODE_PORT: String(SERVER_PORT),
VITE_CONTENT_STUDIO: "true",
CONTENT_EDITOR_URL: `http://localhost:55279`,
CONTENT_MANIFEST_PATH: resolve(contentRoot, "app/v1/generated/content-manifest.json"),
},
},
);
const webProc = Bun.spawn([bunPath, "run", "vite", "--port", String(WEB_PORT), "--strictPort"], {
cwd: webDir,
stdout: "inherit",
stderr: "inherit",
env: {
...process.env,
VITE_CONTENT_STUDIO: "true",
VITE_WS_URL: `ws://localhost:${SERVER_PORT}/ws`,
// Route HTTP requests through Vite's own origin so `/api/*` hits the dev
// proxy (configured in vite.config.ts) instead of going cross-origin to the
// server on SERVER_PORT — the server uses `Access-Control-Allow-Origin: *`
// which browsers reject on credentialed fetches. See resolveHttpRequestBaseUrl
// in apps/web/src/environments/primary/target.ts.
VITE_DEV_SERVER_URL: `http://localhost:${WEB_PORT}`,
VITE_LAUNCHPAD_URL: `http://localhost:${process.env.LAUNCHPAD_PORT ?? "8888"}`,
VITE_WORKSPACE_ROOT: workspaceRoot,
PORT: String(WEB_PORT),
},
});
// Cleanup: kill both process trees
function killTree(pid: number) {
try {
execSync(`pkill -9 -P ${pid}`, { stdio: "ignore" });
} catch {}
try {
process.kill(pid, "SIGKILL");
} catch {}
}
const cleanup = () => {
killTree(webProc.pid);
killTree(serverProc.pid);
};
process.on("SIGINT", cleanup);
process.on("SIGTERM", cleanup);
serverProc.exited.then((code) => {
console.log(`[Content Studio] Server process exited (code ${code})`);
cleanup();
});
webProc.exited.then((code) => {
console.log(`[Content Studio] Web process exited (code ${code})`);
cleanup();
});
await Promise.race([serverProc.exited, webProc.exited]);