openmagic 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +304 -78
- package/dist/cli.js.map +1 -1
- package/dist/toolbar/index.global.js +1 -1
- package/dist/toolbar/index.global.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ import { createInterface } from "readline";
|
|
|
10
10
|
|
|
11
11
|
// src/proxy.ts
|
|
12
12
|
import http from "http";
|
|
13
|
+
import { createGunzip, createInflate, createBrotliDecompress } from "zlib";
|
|
13
14
|
import httpProxy from "http-proxy";
|
|
14
15
|
|
|
15
16
|
// src/security.ts
|
|
@@ -45,10 +46,7 @@ function createProxyServer(targetHost, targetPort, serverPort) {
|
|
|
45
46
|
proxyRes.pipe(res);
|
|
46
47
|
return;
|
|
47
48
|
}
|
|
48
|
-
|
|
49
|
-
proxyRes.on("data", (chunk) => chunks.push(chunk));
|
|
50
|
-
proxyRes.on("end", () => {
|
|
51
|
-
let body = Buffer.concat(chunks).toString("utf-8");
|
|
49
|
+
collectBody(proxyRes).then((body) => {
|
|
52
50
|
const toolbarScript = buildInjectionScript(serverPort, token);
|
|
53
51
|
if (body.includes("</body>")) {
|
|
54
52
|
body = body.replace("</body>", `${toolbarScript}</body>`);
|
|
@@ -60,18 +58,27 @@ function createProxyServer(targetHost, targetPort, serverPort) {
|
|
|
60
58
|
const headers = { ...proxyRes.headers };
|
|
61
59
|
delete headers["content-length"];
|
|
62
60
|
delete headers["content-encoding"];
|
|
61
|
+
delete headers["transfer-encoding"];
|
|
63
62
|
res.writeHead(proxyRes.statusCode || 200, headers);
|
|
64
63
|
res.end(body);
|
|
64
|
+
}).catch(() => {
|
|
65
|
+
try {
|
|
66
|
+
res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
|
|
67
|
+
res.end();
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
65
70
|
});
|
|
66
71
|
});
|
|
67
72
|
proxy.on("error", (err, _req, res) => {
|
|
68
|
-
console.error("[OpenMagic] Proxy error:", err.message);
|
|
69
73
|
if (res instanceof http.ServerResponse && !res.headersSent) {
|
|
70
|
-
res.writeHead(502, { "Content-Type": "text/
|
|
74
|
+
res.writeHead(502, { "Content-Type": "text/html" });
|
|
71
75
|
res.end(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
`<html><body style="font-family:system-ui;padding:40px;background:#1a1a2e;color:#e0e0e0;">
|
|
77
|
+
<h2 style="color:#e94560;">OpenMagic \u2014 Cannot connect to dev server</h2>
|
|
78
|
+
<p>Could not reach <code>${targetHost}:${targetPort}</code></p>
|
|
79
|
+
<p style="color:#888;">Make sure your dev server is running, then refresh this page.</p>
|
|
80
|
+
<p style="color:#666;font-size:13px;">${err.message}</p>
|
|
81
|
+
</body></html>`
|
|
75
82
|
);
|
|
76
83
|
}
|
|
77
84
|
});
|
|
@@ -90,6 +97,58 @@ Make sure your dev server is running.`
|
|
|
90
97
|
});
|
|
91
98
|
return server;
|
|
92
99
|
}
|
|
100
|
+
function collectBody(stream) {
|
|
101
|
+
return new Promise((resolve3, reject) => {
|
|
102
|
+
const encoding = (stream.headers["content-encoding"] || "").toLowerCase();
|
|
103
|
+
const chunks = [];
|
|
104
|
+
let source = stream;
|
|
105
|
+
if (encoding === "gzip" || encoding === "x-gzip") {
|
|
106
|
+
const gunzip = createGunzip();
|
|
107
|
+
stream.pipe(gunzip);
|
|
108
|
+
source = gunzip;
|
|
109
|
+
gunzip.on("error", () => {
|
|
110
|
+
collectRaw(stream).then(resolve3).catch(reject);
|
|
111
|
+
});
|
|
112
|
+
} else if (encoding === "deflate") {
|
|
113
|
+
const inflate = createInflate();
|
|
114
|
+
stream.pipe(inflate);
|
|
115
|
+
source = inflate;
|
|
116
|
+
inflate.on("error", () => {
|
|
117
|
+
collectRaw(stream).then(resolve3).catch(reject);
|
|
118
|
+
});
|
|
119
|
+
} else if (encoding === "br") {
|
|
120
|
+
const brotli = createBrotliDecompress();
|
|
121
|
+
stream.pipe(brotli);
|
|
122
|
+
source = brotli;
|
|
123
|
+
brotli.on("error", () => {
|
|
124
|
+
collectRaw(stream).then(resolve3).catch(reject);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
source.on("data", (chunk) => chunks.push(chunk));
|
|
128
|
+
source.on("end", () => {
|
|
129
|
+
try {
|
|
130
|
+
resolve3(Buffer.concat(chunks).toString("utf-8"));
|
|
131
|
+
} catch {
|
|
132
|
+
reject(new Error("Failed to decode response body"));
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
source.on("error", (err) => reject(err));
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function collectRaw(stream) {
|
|
139
|
+
return new Promise((resolve3, reject) => {
|
|
140
|
+
const chunks = [];
|
|
141
|
+
stream.on("data", (chunk) => chunks.push(chunk));
|
|
142
|
+
stream.on("end", () => {
|
|
143
|
+
try {
|
|
144
|
+
resolve3(Buffer.concat(chunks).toString("utf-8"));
|
|
145
|
+
} catch {
|
|
146
|
+
reject(new Error("Failed to decode raw body"));
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
stream.on("error", reject);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
93
152
|
function handleToolbarAsset(_req, res, _serverPort) {
|
|
94
153
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
95
154
|
res.end("Not found");
|
|
@@ -143,10 +202,14 @@ function loadConfig() {
|
|
|
143
202
|
}
|
|
144
203
|
}
|
|
145
204
|
function saveConfig(updates) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
205
|
+
try {
|
|
206
|
+
ensureConfigDir();
|
|
207
|
+
const existing = loadConfig();
|
|
208
|
+
const merged = { ...existing, ...updates };
|
|
209
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2), "utf-8");
|
|
210
|
+
} catch (e) {
|
|
211
|
+
console.warn(`[OpenMagic] Warning: Could not save config to ${CONFIG_FILE}: ${e.message}`);
|
|
212
|
+
}
|
|
150
213
|
}
|
|
151
214
|
|
|
152
215
|
// src/filesystem.ts
|
|
@@ -1007,8 +1070,14 @@ async function chatOpenAICompatible(provider, model, apiKey, messages, context,
|
|
|
1007
1070
|
body: JSON.stringify(body)
|
|
1008
1071
|
});
|
|
1009
1072
|
if (!response.ok) {
|
|
1010
|
-
const errorText = await response.text();
|
|
1011
|
-
|
|
1073
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
1074
|
+
if (response.status === 401 || response.status === 403) {
|
|
1075
|
+
onError(`Invalid API key for ${providerConfig.name}. Check your key in Settings.`);
|
|
1076
|
+
} else if (response.status === 429) {
|
|
1077
|
+
onError(`Rate limit exceeded for ${providerConfig.name}. Wait a moment and try again.`);
|
|
1078
|
+
} else {
|
|
1079
|
+
onError(`${providerConfig.name} API error ${response.status}: ${errorText.slice(0, 200)}`);
|
|
1080
|
+
}
|
|
1012
1081
|
return;
|
|
1013
1082
|
}
|
|
1014
1083
|
if (!response.body) {
|
|
@@ -1127,8 +1196,14 @@ async function chatAnthropic(model, apiKey, messages, context, onChunk, onDone,
|
|
|
1127
1196
|
body: JSON.stringify(body)
|
|
1128
1197
|
});
|
|
1129
1198
|
if (!response.ok) {
|
|
1130
|
-
const errorText = await response.text();
|
|
1131
|
-
|
|
1199
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
1200
|
+
if (response.status === 401 || response.status === 403) {
|
|
1201
|
+
onError("Invalid Anthropic API key. Check your key in Settings.");
|
|
1202
|
+
} else if (response.status === 429) {
|
|
1203
|
+
onError("Anthropic rate limit exceeded. Wait a moment and try again.");
|
|
1204
|
+
} else {
|
|
1205
|
+
onError(`Anthropic API error ${response.status}: ${errorText.slice(0, 200)}`);
|
|
1206
|
+
}
|
|
1132
1207
|
return;
|
|
1133
1208
|
}
|
|
1134
1209
|
if (!response.body) {
|
|
@@ -1233,8 +1308,14 @@ async function chatGoogle(model, apiKey, messages, context, onChunk, onDone, onE
|
|
|
1233
1308
|
body: JSON.stringify(body)
|
|
1234
1309
|
});
|
|
1235
1310
|
if (!response.ok) {
|
|
1236
|
-
const errorText = await response.text();
|
|
1237
|
-
|
|
1311
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
1312
|
+
if (response.status === 401 || response.status === 403) {
|
|
1313
|
+
onError("Invalid Google API key. Check your key in Settings.");
|
|
1314
|
+
} else if (response.status === 429) {
|
|
1315
|
+
onError("Google API rate limit exceeded. Wait a moment and try again.");
|
|
1316
|
+
} else {
|
|
1317
|
+
onError(`Google API error ${response.status}: ${errorText.slice(0, 200)}`);
|
|
1318
|
+
}
|
|
1238
1319
|
return;
|
|
1239
1320
|
}
|
|
1240
1321
|
if (!response.body) {
|
|
@@ -1296,23 +1377,32 @@ async function handleLlmChat(params, onChunk, onDone, onError) {
|
|
|
1296
1377
|
}
|
|
1297
1378
|
onDone({ content: result.content, modifications });
|
|
1298
1379
|
};
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1380
|
+
try {
|
|
1381
|
+
if (provider === "anthropic") {
|
|
1382
|
+
await chatAnthropic(model, apiKey, messages, context, onChunk, wrappedOnDone, onError);
|
|
1383
|
+
} else if (provider === "google") {
|
|
1384
|
+
await chatGoogle(model, apiKey, messages, context, onChunk, wrappedOnDone, onError);
|
|
1385
|
+
} else if (OPENAI_COMPATIBLE_PROVIDERS.has(provider)) {
|
|
1386
|
+
await chatOpenAICompatible(
|
|
1387
|
+
provider,
|
|
1388
|
+
model,
|
|
1389
|
+
apiKey,
|
|
1390
|
+
messages,
|
|
1391
|
+
context,
|
|
1392
|
+
onChunk,
|
|
1393
|
+
wrappedOnDone,
|
|
1394
|
+
onError
|
|
1395
|
+
);
|
|
1396
|
+
} else {
|
|
1397
|
+
onError(`Unsupported provider: ${provider}. Check your Settings.`);
|
|
1398
|
+
}
|
|
1399
|
+
} catch (e) {
|
|
1400
|
+
const msg = e.message || "Unknown error";
|
|
1401
|
+
if (msg.includes("fetch") || msg.includes("ECONNREFUSED") || msg.includes("network")) {
|
|
1402
|
+
onError(`Network error: Could not reach the ${provider} API. Check your internet connection.`);
|
|
1403
|
+
} else {
|
|
1404
|
+
onError(`Unexpected error with ${provider}: ${msg}`);
|
|
1405
|
+
}
|
|
1316
1406
|
}
|
|
1317
1407
|
}
|
|
1318
1408
|
|
|
@@ -1329,7 +1419,7 @@ function createOpenMagicServer(proxyPort, roots) {
|
|
|
1329
1419
|
"Content-Type": "application/json",
|
|
1330
1420
|
"Access-Control-Allow-Origin": "*"
|
|
1331
1421
|
});
|
|
1332
|
-
res.end(JSON.stringify({ status: "ok", version: "0.
|
|
1422
|
+
res.end(JSON.stringify({ status: "ok", version: "0.4.0" }));
|
|
1333
1423
|
return;
|
|
1334
1424
|
}
|
|
1335
1425
|
res.writeHead(404);
|
|
@@ -1371,6 +1461,11 @@ async function handleMessage(ws, msg, state, roots, _proxyPort) {
|
|
|
1371
1461
|
switch (msg.type) {
|
|
1372
1462
|
case "handshake": {
|
|
1373
1463
|
const payload = msg.payload;
|
|
1464
|
+
if (!payload?.token) {
|
|
1465
|
+
sendError(ws, "invalid_payload", "Missing token in handshake", msg.id);
|
|
1466
|
+
ws.close();
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1374
1469
|
if (!validateToken(payload.token)) {
|
|
1375
1470
|
sendError(ws, "auth_failed", "Invalid token", msg.id);
|
|
1376
1471
|
ws.close();
|
|
@@ -1382,7 +1477,7 @@ async function handleMessage(ws, msg, state, roots, _proxyPort) {
|
|
|
1382
1477
|
id: msg.id,
|
|
1383
1478
|
type: "handshake.ok",
|
|
1384
1479
|
payload: {
|
|
1385
|
-
version: "0.
|
|
1480
|
+
version: "0.4.0",
|
|
1386
1481
|
roots,
|
|
1387
1482
|
config: {
|
|
1388
1483
|
provider: config.provider,
|
|
@@ -1395,6 +1490,10 @@ async function handleMessage(ws, msg, state, roots, _proxyPort) {
|
|
|
1395
1490
|
}
|
|
1396
1491
|
case "fs.read": {
|
|
1397
1492
|
const payload = msg.payload;
|
|
1493
|
+
if (!payload?.path) {
|
|
1494
|
+
sendError(ws, "invalid_payload", "Missing path", msg.id);
|
|
1495
|
+
break;
|
|
1496
|
+
}
|
|
1398
1497
|
const result = readFileSafe(payload.path, roots);
|
|
1399
1498
|
if ("error" in result) {
|
|
1400
1499
|
sendError(ws, "fs_error", result.error, msg.id);
|
|
@@ -1506,15 +1605,19 @@ function serveToolbarBundle(res) {
|
|
|
1506
1605
|
join3(__dirname, "..", "dist", "toolbar", "index.global.js")
|
|
1507
1606
|
];
|
|
1508
1607
|
for (const bundlePath of bundlePaths) {
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1608
|
+
try {
|
|
1609
|
+
if (existsSync3(bundlePath)) {
|
|
1610
|
+
const content = readFileSync3(bundlePath, "utf-8");
|
|
1611
|
+
res.writeHead(200, {
|
|
1612
|
+
"Content-Type": "application/javascript",
|
|
1613
|
+
"Access-Control-Allow-Origin": "*",
|
|
1614
|
+
"Cache-Control": "no-cache"
|
|
1615
|
+
});
|
|
1616
|
+
res.end(content);
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
} catch {
|
|
1620
|
+
continue;
|
|
1518
1621
|
}
|
|
1519
1622
|
}
|
|
1520
1623
|
res.writeHead(200, {
|
|
@@ -1670,9 +1773,47 @@ function getProjectName(cwd = process.cwd()) {
|
|
|
1670
1773
|
return "this project";
|
|
1671
1774
|
}
|
|
1672
1775
|
}
|
|
1776
|
+
var LOCK_FILES = [
|
|
1777
|
+
{ file: "pnpm-lock.yaml", pm: "pnpm" },
|
|
1778
|
+
{ file: "yarn.lock", pm: "yarn" },
|
|
1779
|
+
{ file: "bun.lockb", pm: "bun" },
|
|
1780
|
+
{ file: "bun.lock", pm: "bun" },
|
|
1781
|
+
{ file: "package-lock.json", pm: "npm" }
|
|
1782
|
+
];
|
|
1783
|
+
var INSTALL_COMMANDS = {
|
|
1784
|
+
npm: "npm install",
|
|
1785
|
+
yarn: "yarn install",
|
|
1786
|
+
pnpm: "pnpm install",
|
|
1787
|
+
bun: "bun install"
|
|
1788
|
+
};
|
|
1789
|
+
function checkDependenciesInstalled(cwd = process.cwd()) {
|
|
1790
|
+
const hasNodeModules = existsSync4(join4(cwd, "node_modules"));
|
|
1791
|
+
let pm = "npm";
|
|
1792
|
+
for (const { file, pm: detectedPm } of LOCK_FILES) {
|
|
1793
|
+
if (existsSync4(join4(cwd, file))) {
|
|
1794
|
+
pm = detectedPm;
|
|
1795
|
+
break;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
return {
|
|
1799
|
+
installed: hasNodeModules,
|
|
1800
|
+
packageManager: pm,
|
|
1801
|
+
installCommand: INSTALL_COMMANDS[pm]
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1673
1804
|
|
|
1674
1805
|
// src/cli.ts
|
|
1675
|
-
|
|
1806
|
+
process.on("unhandledRejection", (err) => {
|
|
1807
|
+
console.error(chalk.red("\n [OpenMagic] Unhandled error:"), err?.message || err);
|
|
1808
|
+
console.error(chalk.dim(" Please report this at https://github.com/Kalmuraee/OpenMagic/issues"));
|
|
1809
|
+
});
|
|
1810
|
+
process.on("uncaughtException", (err) => {
|
|
1811
|
+
console.error(chalk.red("\n [OpenMagic] Fatal error:"), err.message);
|
|
1812
|
+
console.error(chalk.dim(" Please report this at https://github.com/Kalmuraee/OpenMagic/issues"));
|
|
1813
|
+
process.exit(1);
|
|
1814
|
+
});
|
|
1815
|
+
var childProcesses = [];
|
|
1816
|
+
var VERSION = "0.4.0";
|
|
1676
1817
|
function ask(question) {
|
|
1677
1818
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1678
1819
|
return new Promise((resolve3) => {
|
|
@@ -1682,10 +1823,14 @@ function ask(question) {
|
|
|
1682
1823
|
});
|
|
1683
1824
|
});
|
|
1684
1825
|
}
|
|
1685
|
-
function waitForPort(port, timeoutMs = 3e4) {
|
|
1826
|
+
function waitForPort(port, timeoutMs = 3e4, shouldAbort) {
|
|
1686
1827
|
const start = Date.now();
|
|
1687
1828
|
return new Promise((resolve3) => {
|
|
1688
1829
|
const check = async () => {
|
|
1830
|
+
if (shouldAbort?.()) {
|
|
1831
|
+
resolve3(false);
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1689
1834
|
if (await isPortOpen(port)) {
|
|
1690
1835
|
resolve3(true);
|
|
1691
1836
|
return;
|
|
@@ -1699,6 +1844,35 @@ function waitForPort(port, timeoutMs = 3e4) {
|
|
|
1699
1844
|
check();
|
|
1700
1845
|
});
|
|
1701
1846
|
}
|
|
1847
|
+
function runCommand(cmd, args, cwd = process.cwd()) {
|
|
1848
|
+
return new Promise((resolve3) => {
|
|
1849
|
+
try {
|
|
1850
|
+
const child = spawn(cmd, args, {
|
|
1851
|
+
cwd,
|
|
1852
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1853
|
+
shell: true
|
|
1854
|
+
});
|
|
1855
|
+
child.stdout?.on("data", (data) => {
|
|
1856
|
+
const lines = data.toString().trim().split("\n");
|
|
1857
|
+
for (const line of lines) {
|
|
1858
|
+
if (line.trim()) process.stdout.write(chalk.dim(` \u2502 ${line}
|
|
1859
|
+
`));
|
|
1860
|
+
}
|
|
1861
|
+
});
|
|
1862
|
+
child.stderr?.on("data", (data) => {
|
|
1863
|
+
const lines = data.toString().trim().split("\n");
|
|
1864
|
+
for (const line of lines) {
|
|
1865
|
+
if (line.trim()) process.stdout.write(chalk.dim(` \u2502 ${line}
|
|
1866
|
+
`));
|
|
1867
|
+
}
|
|
1868
|
+
});
|
|
1869
|
+
child.on("error", () => resolve3(false));
|
|
1870
|
+
child.on("close", (code) => resolve3(code === 0));
|
|
1871
|
+
} catch {
|
|
1872
|
+
resolve3(false);
|
|
1873
|
+
}
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1702
1876
|
var program = new Command();
|
|
1703
1877
|
program.name("openmagic").description("AI-powered coding toolbar for any web application").version(VERSION).option("-p, --port <port>", "Dev server port to proxy", "").option(
|
|
1704
1878
|
"-l, --listen <port>",
|
|
@@ -1811,6 +1985,34 @@ async function offerToStartDevServer(expectedPort) {
|
|
|
1811
1985
|
console.log("");
|
|
1812
1986
|
return false;
|
|
1813
1987
|
}
|
|
1988
|
+
const deps = checkDependenciesInstalled();
|
|
1989
|
+
if (!deps.installed) {
|
|
1990
|
+
console.log(
|
|
1991
|
+
chalk.yellow(" \u26A0 node_modules/ not found. Dependencies need to be installed.")
|
|
1992
|
+
);
|
|
1993
|
+
console.log("");
|
|
1994
|
+
const answer = await ask(
|
|
1995
|
+
chalk.white(` Run `) + chalk.cyan(deps.installCommand) + chalk.white("? ") + chalk.dim("(Y/n) ")
|
|
1996
|
+
);
|
|
1997
|
+
if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no") {
|
|
1998
|
+
console.log("");
|
|
1999
|
+
console.log(chalk.dim(` Run ${deps.installCommand} manually, then try again.`));
|
|
2000
|
+
console.log("");
|
|
2001
|
+
return false;
|
|
2002
|
+
}
|
|
2003
|
+
console.log("");
|
|
2004
|
+
console.log(chalk.dim(` Installing dependencies with ${deps.packageManager}...`));
|
|
2005
|
+
const [installCmd, ...installArgs] = deps.installCommand.split(" ");
|
|
2006
|
+
const installed = await runCommand(installCmd, installArgs);
|
|
2007
|
+
if (!installed) {
|
|
2008
|
+
console.log(chalk.red(" \u2717 Dependency installation failed."));
|
|
2009
|
+
console.log(chalk.dim(` Try running ${deps.installCommand} manually.`));
|
|
2010
|
+
console.log("");
|
|
2011
|
+
return false;
|
|
2012
|
+
}
|
|
2013
|
+
console.log(chalk.green(" \u2713 Dependencies installed."));
|
|
2014
|
+
console.log("");
|
|
2015
|
+
}
|
|
1814
2016
|
let chosen = scripts[0];
|
|
1815
2017
|
if (scripts.length === 1) {
|
|
1816
2018
|
console.log(
|
|
@@ -1868,52 +2070,66 @@ async function offerToStartDevServer(expectedPort) {
|
|
|
1868
2070
|
console.log(
|
|
1869
2071
|
chalk.dim(` Starting `) + chalk.cyan(`npm run ${chosen.name}`) + chalk.dim("...")
|
|
1870
2072
|
);
|
|
1871
|
-
const
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
2073
|
+
const depsInfo = checkDependenciesInstalled();
|
|
2074
|
+
const runCmd = depsInfo.packageManager === "yarn" ? "yarn" : depsInfo.packageManager === "pnpm" ? "pnpm" : depsInfo.packageManager === "bun" ? "bun" : "npm";
|
|
2075
|
+
const runArgs = runCmd === "npm" ? ["run", chosen.name] : [chosen.name];
|
|
2076
|
+
let child;
|
|
2077
|
+
try {
|
|
2078
|
+
child = spawn(runCmd, runArgs, {
|
|
2079
|
+
cwd: process.cwd(),
|
|
2080
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2081
|
+
detached: false,
|
|
2082
|
+
shell: true,
|
|
2083
|
+
env: {
|
|
2084
|
+
...process.env,
|
|
2085
|
+
PORT: String(port),
|
|
2086
|
+
BROWSER: "none",
|
|
2087
|
+
BROWSER_NONE: "true"
|
|
2088
|
+
}
|
|
2089
|
+
});
|
|
2090
|
+
} catch (e) {
|
|
2091
|
+
console.log(chalk.red(` \u2717 Failed to start: ${e.message}`));
|
|
2092
|
+
return false;
|
|
2093
|
+
}
|
|
2094
|
+
childProcesses.push(child);
|
|
2095
|
+
let childExited = false;
|
|
1886
2096
|
child.stdout?.on("data", (data) => {
|
|
1887
|
-
const
|
|
1888
|
-
|
|
1889
|
-
if (line.trim()) {
|
|
1890
|
-
process.stdout.write(chalk.dim(` \u2502 ${line}
|
|
2097
|
+
for (const line of data.toString().trim().split("\n")) {
|
|
2098
|
+
if (line.trim()) process.stdout.write(chalk.dim(` \u2502 ${line}
|
|
1891
2099
|
`));
|
|
1892
|
-
}
|
|
1893
2100
|
}
|
|
1894
2101
|
});
|
|
1895
2102
|
child.stderr?.on("data", (data) => {
|
|
1896
|
-
const
|
|
1897
|
-
|
|
1898
|
-
if (line.trim()) {
|
|
1899
|
-
process.stdout.write(chalk.dim(` \u2502 ${line}
|
|
2103
|
+
for (const line of data.toString().trim().split("\n")) {
|
|
2104
|
+
if (line.trim()) process.stdout.write(chalk.dim(` \u2502 ${line}
|
|
1900
2105
|
`));
|
|
1901
|
-
}
|
|
1902
2106
|
}
|
|
1903
2107
|
});
|
|
1904
2108
|
child.on("error", (err) => {
|
|
2109
|
+
childExited = true;
|
|
1905
2110
|
console.log(chalk.red(` \u2717 Failed to start: ${err.message}`));
|
|
1906
2111
|
});
|
|
1907
2112
|
child.on("exit", (code) => {
|
|
2113
|
+
childExited = true;
|
|
1908
2114
|
if (code !== null && code !== 0) {
|
|
1909
2115
|
console.log(chalk.red(` \u2717 Dev server exited with code ${code}`));
|
|
1910
2116
|
}
|
|
1911
2117
|
});
|
|
1912
2118
|
const cleanup = () => {
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
2119
|
+
for (const cp of childProcesses) {
|
|
2120
|
+
try {
|
|
2121
|
+
cp.kill("SIGTERM");
|
|
2122
|
+
} catch {
|
|
2123
|
+
}
|
|
1916
2124
|
}
|
|
2125
|
+
setTimeout(() => {
|
|
2126
|
+
for (const cp of childProcesses) {
|
|
2127
|
+
try {
|
|
2128
|
+
cp.kill("SIGKILL");
|
|
2129
|
+
} catch {
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
}, 3e3);
|
|
1917
2133
|
};
|
|
1918
2134
|
process.on("exit", cleanup);
|
|
1919
2135
|
process.on("SIGINT", cleanup);
|
|
@@ -1921,7 +2137,17 @@ async function offerToStartDevServer(expectedPort) {
|
|
|
1921
2137
|
console.log(
|
|
1922
2138
|
chalk.dim(` Waiting for port ${port}...`)
|
|
1923
2139
|
);
|
|
1924
|
-
const isUp = await waitForPort(port, 3e4);
|
|
2140
|
+
const isUp = await waitForPort(port, 3e4, () => childExited);
|
|
2141
|
+
if (childExited && !isUp) {
|
|
2142
|
+
console.log(
|
|
2143
|
+
chalk.red(` \u2717 Dev server exited before it was ready.`)
|
|
2144
|
+
);
|
|
2145
|
+
console.log(
|
|
2146
|
+
chalk.dim(` Check the error output above and fix the issue.`)
|
|
2147
|
+
);
|
|
2148
|
+
console.log("");
|
|
2149
|
+
return false;
|
|
2150
|
+
}
|
|
1925
2151
|
if (!isUp) {
|
|
1926
2152
|
console.log(
|
|
1927
2153
|
chalk.yellow(` \u26A0 Port ${port} didn't open after 30s.`)
|