leglas 1.0.0 → 1.1.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/README.md +7 -1
- package/dist/bin.js +2384 -1629
- package/dist/index.d.ts +1 -1
- package/dist/index.js +143 -65
- package/dist/restart.d.ts +13 -0
- package/dist/run.d.ts +5 -0
- package/dist/shell/assets/index-BzaBzGEv.css +1 -0
- package/dist/shell/assets/index-CKmxj5gW.js +14 -0
- package/dist/shell/index.html +2 -2
- package/package.json +1 -1
- package/dist/shell/assets/index-BCvek69-.css +0 -1
- package/dist/shell/assets/index-BjY8sKEQ.js +0 -14
package/dist/index.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export { runWatch } from "./run-watch.js";
|
|
|
16
16
|
export type { TemplateResult, WatchTemplate } from "./watch.js";
|
|
17
17
|
export type { WatchDeps } from "./run-watch.js";
|
|
18
18
|
export { CAPTURES_DIR, DEFAULT_PORT, LEGLAS_PREFIX, NO_BROWSER, REFERENCES_DIR, SERVER_INFO_PATH, findBrowser, isOwnCapture, readRequests, readServerInfo, } from "@leglas/server";
|
|
19
|
-
export type { Attachment, AttachmentKind, Failure, FailureCode, PendingRequest, RequestStatus, ServerInfo, } from "@leglas/server";
|
|
19
|
+
export type { Attachment, AttachmentKind, Failure, FailureCode, PendingRequest, RequestStatus, ServerInfo, UpdateService, UpdateStatus, } from "@leglas/server";
|
|
20
20
|
export { runClassify } from "./run-classify.js";
|
|
21
21
|
export { run } from "./run.js";
|
|
22
22
|
export type { ParseResult, RunOptions } from "./args.js";
|
package/dist/index.js
CHANGED
|
@@ -1247,13 +1247,21 @@ var KNOWN_AGENTS = {
|
|
|
1247
1247
|
name: "Cursor",
|
|
1248
1248
|
binary: "cursor-agent",
|
|
1249
1249
|
efforts: [],
|
|
1250
|
+
// `--trust` on every invocation. Without it, print mode stops at a
|
|
1251
|
+
// "Workspace Trust Required" prompt nothing can answer and exits 1 with
|
|
1252
|
+
// no events, in any directory Cursor has not been trusted for by hand:
|
|
1253
|
+
// measured against cursor-agent 2026.09.02, one second, zero output. The
|
|
1254
|
+
// project is the one the user pointed Leglas at, which is the trust the
|
|
1255
|
+
// flag grants. It is the only permission the run needs: with it alone,
|
|
1256
|
+
// the edit and the shell command in the same run both executed.
|
|
1250
1257
|
args: (prompt, _effort = null, _images = []) => [
|
|
1251
1258
|
"-p",
|
|
1252
1259
|
prompt,
|
|
1253
1260
|
"--output-format",
|
|
1254
|
-
"stream-json"
|
|
1261
|
+
"stream-json",
|
|
1262
|
+
"--trust"
|
|
1255
1263
|
],
|
|
1256
|
-
terminalArgs: (prompt, _effort = null, _images = []) => ["-p", prompt],
|
|
1264
|
+
terminalArgs: (prompt, _effort = null, _images = []) => ["-p", prompt, "--trust"],
|
|
1257
1265
|
// `--resume [chatId]` is documented alongside `--continue` in the CLI
|
|
1258
1266
|
// parameter reference, and every stream-json event carries the
|
|
1259
1267
|
// `session_id` to feed it. Cursor exposes no persistent transport the way
|
|
@@ -1264,11 +1272,16 @@ var KNOWN_AGENTS = {
|
|
|
1264
1272
|
// Images are accepted and ignored, like its other argument builders:
|
|
1265
1273
|
// `cursor-agent` documents no flag for them, and the capture paths reach
|
|
1266
1274
|
// it as text in the prompt either way.
|
|
1267
|
-
resumeArgs: (sessionId, prompt, _effort = null, _images = []) => ["-p", "--resume", sessionId, prompt, "--output-format", "stream-json"],
|
|
1275
|
+
resumeArgs: (sessionId, prompt, _effort = null, _images = []) => ["-p", "--resume", sessionId, prompt, "--output-format", "stream-json", "--trust"],
|
|
1268
1276
|
sessionFrom: (event) => typeof event.session_id === "string" && event.session_id !== "" ? event.session_id : null,
|
|
1277
|
+
// Read against cursor-agent 2026.09.02: every event carries the id, a
|
|
1278
|
+
// resume in this argument order answers under the same id and remembers
|
|
1279
|
+
// the earlier turn.
|
|
1280
|
+
activityVerified: true,
|
|
1269
1281
|
authArgs: ["status"],
|
|
1270
|
-
//
|
|
1271
|
-
//
|
|
1282
|
+
// The signed-in form was read from the CLI: "✓ Logged in as <email>",
|
|
1283
|
+
// exit 0. The signed-out form has not been, so that side of the reading
|
|
1284
|
+
// stays loose, and anything ambiguous stays unknown.
|
|
1272
1285
|
authVerdict: (result) => {
|
|
1273
1286
|
if (/logged in|signed in/i.test(result.stdout))
|
|
1274
1287
|
return "ok";
|
|
@@ -1472,15 +1485,15 @@ function cursorActivity(event, cwd) {
|
|
|
1472
1485
|
const wrapper = record(event.tool_call);
|
|
1473
1486
|
if (wrapper === null)
|
|
1474
1487
|
return null;
|
|
1475
|
-
const key = Object.keys(wrapper)
|
|
1488
|
+
const key = Object.keys(wrapper).find((name) => name.endsWith("ToolCall"));
|
|
1476
1489
|
if (key === void 0)
|
|
1477
1490
|
return null;
|
|
1478
1491
|
const call = record(wrapper[key]);
|
|
1479
1492
|
const args = record(call?.args);
|
|
1480
1493
|
const tool = key.replace(/ToolCall$/, "");
|
|
1481
|
-
if (tool === "write") {
|
|
1494
|
+
if (tool === "edit" || tool === "write") {
|
|
1482
1495
|
const path2 = shownPath(args?.path, cwd);
|
|
1483
|
-
return path2 === null ? "
|
|
1496
|
+
return path2 === null ? "editing a file" : `editing ${path2}`;
|
|
1484
1497
|
}
|
|
1485
1498
|
if (tool === "read") {
|
|
1486
1499
|
const path2 = shownPath(args?.path, cwd);
|
|
@@ -7514,6 +7527,39 @@ async function startServer(options) {
|
|
|
7514
7527
|
if (context.remote && path.startsWith(`${LEGLAS_PREFIX}/api/`)) {
|
|
7515
7528
|
return sendJson2(res, 403, { error: "Not available to viewers." });
|
|
7516
7529
|
}
|
|
7530
|
+
if (path === `${LEGLAS_PREFIX}/api/update` && req.method === "GET" || req.method === "POST" && ["check", "skip", "install"].some((action) => path === `${LEGLAS_PREFIX}/api/update/${action}`)) {
|
|
7531
|
+
const updates = options.updates;
|
|
7532
|
+
if (updates === void 0) {
|
|
7533
|
+
return sendJson2(res, 404, { ok: false, error: "Updates are not available here." });
|
|
7534
|
+
}
|
|
7535
|
+
if (req.method === "GET")
|
|
7536
|
+
return sendJson2(res, 200, updates.status());
|
|
7537
|
+
if (path.endsWith("/check")) {
|
|
7538
|
+
return void updates.check({ force: true }).then((status) => sendJson2(res, 200, status));
|
|
7539
|
+
}
|
|
7540
|
+
if (path.endsWith("/install")) {
|
|
7541
|
+
return void updates.update().then((status) => sendJson2(res, 200, status), (error) => sendJson2(res, 409, {
|
|
7542
|
+
ok: false,
|
|
7543
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7544
|
+
}));
|
|
7545
|
+
}
|
|
7546
|
+
if (!hasJsonBody(req))
|
|
7547
|
+
return sendJson2(res, 400, { ok: false, error: "Body must be JSON." });
|
|
7548
|
+
let body = "";
|
|
7549
|
+
req.on("data", (chunk) => body += chunk);
|
|
7550
|
+
return void req.on("end", () => {
|
|
7551
|
+
const parsed = jsonBody(body);
|
|
7552
|
+
if (parsed === null)
|
|
7553
|
+
return sendJson2(res, 400, { ok: false, error: "Body must be JSON." });
|
|
7554
|
+
if (typeof parsed.version !== "string" || parsed.version.trim() === "") {
|
|
7555
|
+
return sendJson2(res, 400, { ok: false, error: "Body needs a version." });
|
|
7556
|
+
}
|
|
7557
|
+
void updates.skip(parsed.version).then((status) => sendJson2(res, 200, status), (error) => sendJson2(res, 400, {
|
|
7558
|
+
ok: false,
|
|
7559
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7560
|
+
}));
|
|
7561
|
+
});
|
|
7562
|
+
}
|
|
7517
7563
|
if (!context.remote && path === `${LEGLAS_PREFIX}/api/share` && req.method === "GET") {
|
|
7518
7564
|
return void shares?.tunnels().then((tunnels) => sendJson2(res, 200, { share: shares?.status() ?? null, tunnels }));
|
|
7519
7565
|
}
|
|
@@ -8360,6 +8406,7 @@ async function startServer(options) {
|
|
|
8360
8406
|
...options.startTunnel === void 0 ? {} : { startTunnel: options.startTunnel }
|
|
8361
8407
|
});
|
|
8362
8408
|
port = await bind2(server, options.port ?? DEFAULT_PORT);
|
|
8409
|
+
options.updates?.setPort(port);
|
|
8363
8410
|
const liveFiles = watchLiveFiles(cwd, bootConfigPath, live);
|
|
8364
8411
|
liveHealth = watchHealth(target, live);
|
|
8365
8412
|
await pruneCaptures(cwd, (await readRequests(cwd).catch(() => [])).map((request) => request.id)).catch(() => {
|
|
@@ -8378,6 +8425,8 @@ async function startServer(options) {
|
|
|
8378
8425
|
...options.codexAppServer === void 0 ? {} : { codexAppServer: options.codexAppServer },
|
|
8379
8426
|
...options.claudeAgentSession === void 0 ? {} : { claudeAgentSession: options.claudeAgentSession }
|
|
8380
8427
|
});
|
|
8428
|
+
options.updates?.onBusy(() => runner?.snapshot().running ?? false);
|
|
8429
|
+
options.updates?.onChange(() => live.nudge("update"));
|
|
8381
8430
|
let closePromise = null;
|
|
8382
8431
|
return {
|
|
8383
8432
|
port,
|
|
@@ -8388,6 +8437,7 @@ async function startServer(options) {
|
|
|
8388
8437
|
closePromise = (async () => {
|
|
8389
8438
|
liveFiles.close();
|
|
8390
8439
|
liveHealth?.close();
|
|
8440
|
+
await options.updates?.close();
|
|
8391
8441
|
await Promise.all([
|
|
8392
8442
|
shares?.close() ?? Promise.resolve(),
|
|
8393
8443
|
branches.stop(),
|
|
@@ -8410,6 +8460,15 @@ async function startServer(options) {
|
|
|
8410
8460
|
};
|
|
8411
8461
|
}
|
|
8412
8462
|
|
|
8463
|
+
// ../server/dist/update.js
|
|
8464
|
+
import { spawn as spawnChild2 } from "child_process";
|
|
8465
|
+
import { existsSync as existsSync4, readFileSync, realpathSync } from "fs";
|
|
8466
|
+
import { lstat as lstat3, mkdir as mkdir9, mkdtemp, rename as rename3, rm as rm5, writeFile as writeFile9 } from "fs/promises";
|
|
8467
|
+
import { homedir as homedir2 } from "os";
|
|
8468
|
+
import { dirname as dirname9, join as join13, posix as posix2, win32 } from "path";
|
|
8469
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
8470
|
+
var INSTALL_DEADLINE_MS = 5 * 60 * 1e3;
|
|
8471
|
+
|
|
8413
8472
|
// src/keep.ts
|
|
8414
8473
|
function surfaceOf(url) {
|
|
8415
8474
|
if (!url.startsWith("/") || !url.includes("?")) return null;
|
|
@@ -8465,8 +8524,8 @@ function planKeep(options) {
|
|
|
8465
8524
|
}
|
|
8466
8525
|
|
|
8467
8526
|
// src/run-init.ts
|
|
8468
|
-
import { readFile as readFile12, writeFile as
|
|
8469
|
-
import { join as
|
|
8527
|
+
import { readFile as readFile12, writeFile as writeFile10 } from "fs/promises";
|
|
8528
|
+
import { join as join14 } from "path";
|
|
8470
8529
|
async function readIfPresent(path) {
|
|
8471
8530
|
try {
|
|
8472
8531
|
return await readFile12(path, "utf8");
|
|
@@ -8477,18 +8536,18 @@ async function readIfPresent(path) {
|
|
|
8477
8536
|
async function runInit(options, deps) {
|
|
8478
8537
|
const existingConfig = findConfigFile(options.cwd);
|
|
8479
8538
|
const plan = planInit({
|
|
8480
|
-
agents: await readIfPresent(
|
|
8539
|
+
agents: await readIfPresent(join14(options.cwd, "AGENTS.md")),
|
|
8481
8540
|
config: existingConfig === null ? null : "present",
|
|
8482
|
-
gitignore: await readIfPresent(
|
|
8541
|
+
gitignore: await readIfPresent(join14(options.cwd, ".gitignore")),
|
|
8483
8542
|
force: options.force
|
|
8484
8543
|
});
|
|
8485
8544
|
const touched = [];
|
|
8486
8545
|
for (const write2 of plan.writes) {
|
|
8487
|
-
await
|
|
8546
|
+
await writeFile10(join14(options.cwd, write2.path), write2.contents, "utf8");
|
|
8488
8547
|
touched.push(write2.path);
|
|
8489
8548
|
}
|
|
8490
8549
|
if (plan.gitignore !== null) {
|
|
8491
|
-
await
|
|
8550
|
+
await writeFile10(join14(options.cwd, ".gitignore"), plan.gitignore, "utf8");
|
|
8492
8551
|
touched.push(".gitignore");
|
|
8493
8552
|
}
|
|
8494
8553
|
if (options.json) {
|
|
@@ -8507,9 +8566,9 @@ async function runInit(options, deps) {
|
|
|
8507
8566
|
}
|
|
8508
8567
|
|
|
8509
8568
|
// src/run-keep.ts
|
|
8510
|
-
import { existsSync as
|
|
8511
|
-
import { copyFile as copyFile2, mkdir as
|
|
8512
|
-
import { dirname as
|
|
8569
|
+
import { existsSync as existsSync5 } from "fs";
|
|
8570
|
+
import { copyFile as copyFile2, mkdir as mkdir10, readFile as readFile13, rm as rm6, writeFile as writeFile11 } from "fs/promises";
|
|
8571
|
+
import { dirname as dirname10, join as join15 } from "path";
|
|
8513
8572
|
|
|
8514
8573
|
// src/resolve-title.ts
|
|
8515
8574
|
function resolveOrExplain(input, titles, renames) {
|
|
@@ -8545,15 +8604,15 @@ async function writeLogEntry(options) {
|
|
|
8545
8604
|
annotations: await readAnnotations(options.cwd),
|
|
8546
8605
|
date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
|
|
8547
8606
|
});
|
|
8548
|
-
const dir =
|
|
8549
|
-
await
|
|
8550
|
-
const file =
|
|
8551
|
-
await
|
|
8607
|
+
const dir = join15(options.cwd, options.logDir);
|
|
8608
|
+
await mkdir10(dir, { recursive: true });
|
|
8609
|
+
const file = join15(dir, `${entry.slug}.md`);
|
|
8610
|
+
await writeFile11(file, entry.markdown, "utf8");
|
|
8552
8611
|
if (entry.pictures.length > 0) {
|
|
8553
|
-
const pictureDir =
|
|
8554
|
-
await
|
|
8612
|
+
const pictureDir = join15(dir, entry.slug);
|
|
8613
|
+
await mkdir10(pictureDir, { recursive: true });
|
|
8555
8614
|
for (const picture of entry.pictures) {
|
|
8556
|
-
await copyFile2(
|
|
8615
|
+
await copyFile2(join15(options.cwd, picture.from), join15(pictureDir, picture.to)).catch(() => {
|
|
8557
8616
|
});
|
|
8558
8617
|
}
|
|
8559
8618
|
}
|
|
@@ -8576,17 +8635,17 @@ async function runKeep(options, deps) {
|
|
|
8576
8635
|
if (!resolved.ok) return fail(resolved.error);
|
|
8577
8636
|
const plan = planKeep({ title: resolved.title, previews, to: options.to });
|
|
8578
8637
|
if (!plan.ok) return fail(plan.error);
|
|
8579
|
-
const from =
|
|
8580
|
-
const to =
|
|
8581
|
-
if (!
|
|
8638
|
+
const from = join15(options.cwd, plan.move.from);
|
|
8639
|
+
const to = join15(options.cwd, plan.move.to);
|
|
8640
|
+
if (!existsSync5(from)) {
|
|
8582
8641
|
return fail(`${plan.move.from} does not exist. Nothing to keep.`);
|
|
8583
8642
|
}
|
|
8584
|
-
if (
|
|
8643
|
+
if (existsSync5(to)) {
|
|
8585
8644
|
return fail(`${plan.move.to} already exists. Choose another destination or move it aside.`);
|
|
8586
8645
|
}
|
|
8587
8646
|
const source = await readFile13(from, "utf8");
|
|
8588
|
-
await
|
|
8589
|
-
await
|
|
8647
|
+
await mkdir10(dirname10(to), { recursive: true });
|
|
8648
|
+
await writeFile11(to, renameExport(source, plan.exportName), "utf8");
|
|
8590
8649
|
const surface = plan.removeDir.slice(plan.removeDir.lastIndexOf("/") + 1);
|
|
8591
8650
|
let logged = null;
|
|
8592
8651
|
let logError = null;
|
|
@@ -8601,7 +8660,7 @@ async function runKeep(options, deps) {
|
|
|
8601
8660
|
} catch (error) {
|
|
8602
8661
|
logError = error instanceof Error ? error.message : String(error);
|
|
8603
8662
|
}
|
|
8604
|
-
await
|
|
8663
|
+
await rm6(join15(options.cwd, plan.removeDir), { recursive: true, force: true });
|
|
8605
8664
|
const dropped = await dropLocalPreviews(options.cwd, plan.dropTitles);
|
|
8606
8665
|
if (options.json) {
|
|
8607
8666
|
deps.log(
|
|
@@ -8639,9 +8698,9 @@ async function runKeep(options, deps) {
|
|
|
8639
8698
|
}
|
|
8640
8699
|
|
|
8641
8700
|
// src/run-new.ts
|
|
8642
|
-
import { existsSync as
|
|
8643
|
-
import { mkdir as
|
|
8644
|
-
import { dirname as
|
|
8701
|
+
import { existsSync as existsSync6 } from "fs";
|
|
8702
|
+
import { mkdir as mkdir11, readFile as readFile14, writeFile as writeFile12 } from "fs/promises";
|
|
8703
|
+
import { dirname as dirname11, join as join16 } from "path";
|
|
8645
8704
|
async function readIfPresent2(path) {
|
|
8646
8705
|
try {
|
|
8647
8706
|
return await readFile14(path, "utf8");
|
|
@@ -8652,7 +8711,7 @@ async function readIfPresent2(path) {
|
|
|
8652
8711
|
async function runNew(options, deps) {
|
|
8653
8712
|
let from;
|
|
8654
8713
|
if (options.from !== void 0) {
|
|
8655
|
-
const contents = await readIfPresent2(
|
|
8714
|
+
const contents = await readIfPresent2(join16(options.cwd, options.from));
|
|
8656
8715
|
if (contents === null) {
|
|
8657
8716
|
const message2 = `${options.from} does not exist, so there is nothing to use as the baseline.`;
|
|
8658
8717
|
if (options.json) deps.log(JSON.stringify({ ok: false, error: message2 }));
|
|
@@ -8663,8 +8722,8 @@ async function runNew(options, deps) {
|
|
|
8663
8722
|
}
|
|
8664
8723
|
const plan = planNew({
|
|
8665
8724
|
surface: options.surface,
|
|
8666
|
-
packageJson: await readIfPresent2(
|
|
8667
|
-
gitignore: await readIfPresent2(
|
|
8725
|
+
packageJson: await readIfPresent2(join16(options.cwd, "package.json")),
|
|
8726
|
+
gitignore: await readIfPresent2(join16(options.cwd, ".gitignore")),
|
|
8668
8727
|
from
|
|
8669
8728
|
});
|
|
8670
8729
|
const fail = (error) => {
|
|
@@ -8687,19 +8746,19 @@ async function runNew(options, deps) {
|
|
|
8687
8746
|
deps.log(plan.instructions);
|
|
8688
8747
|
return { exitCode: 0, written: [] };
|
|
8689
8748
|
}
|
|
8690
|
-
const existing = plan.writes.filter((write2) =>
|
|
8749
|
+
const existing = plan.writes.filter((write2) => existsSync6(join16(options.cwd, write2.path)));
|
|
8691
8750
|
if (existing.length > 0) {
|
|
8692
8751
|
return fail(`${existing[0]?.path} already exists. Delete it first, or pick another surface name.`);
|
|
8693
8752
|
}
|
|
8694
8753
|
const written = [];
|
|
8695
8754
|
for (const write2 of plan.writes) {
|
|
8696
|
-
const target =
|
|
8697
|
-
await
|
|
8698
|
-
await
|
|
8755
|
+
const target = join16(options.cwd, write2.path);
|
|
8756
|
+
await mkdir11(dirname11(target), { recursive: true });
|
|
8757
|
+
await writeFile12(target, write2.contents, "utf8");
|
|
8699
8758
|
written.push(write2.path);
|
|
8700
8759
|
}
|
|
8701
8760
|
if (plan.gitignore !== null) {
|
|
8702
|
-
await
|
|
8761
|
+
await writeFile12(join16(options.cwd, ".gitignore"), plan.gitignore, "utf8");
|
|
8703
8762
|
written.push(".gitignore");
|
|
8704
8763
|
}
|
|
8705
8764
|
if (options.json) {
|
|
@@ -8720,13 +8779,13 @@ async function runNew(options, deps) {
|
|
|
8720
8779
|
}
|
|
8721
8780
|
|
|
8722
8781
|
// src/run-previews.ts
|
|
8723
|
-
import { readFile as readFile15, writeFile as
|
|
8724
|
-
import { join as
|
|
8782
|
+
import { readFile as readFile15, writeFile as writeFile13 } from "fs/promises";
|
|
8783
|
+
import { join as join17 } from "path";
|
|
8725
8784
|
function envelope(deps, ok, body) {
|
|
8726
8785
|
deps.log(JSON.stringify({ ok, ...body }));
|
|
8727
8786
|
}
|
|
8728
8787
|
async function ensureIgnored(cwd) {
|
|
8729
|
-
const path =
|
|
8788
|
+
const path = join17(cwd, ".gitignore");
|
|
8730
8789
|
let current = null;
|
|
8731
8790
|
try {
|
|
8732
8791
|
current = await readFile15(path, "utf8");
|
|
@@ -8734,7 +8793,7 @@ async function ensureIgnored(cwd) {
|
|
|
8734
8793
|
current = null;
|
|
8735
8794
|
}
|
|
8736
8795
|
const next = ignoreEntry(current);
|
|
8737
|
-
if (next !== null) await
|
|
8796
|
+
if (next !== null) await writeFile13(path, next, "utf8");
|
|
8738
8797
|
}
|
|
8739
8798
|
async function runAdd(options, deps) {
|
|
8740
8799
|
const loaded = await loadConfig(options.cwd);
|
|
@@ -9064,12 +9123,12 @@ async function runShow(options, deps) {
|
|
|
9064
9123
|
|
|
9065
9124
|
// src/run-watch.ts
|
|
9066
9125
|
import { spawn as spawn3 } from "child_process";
|
|
9067
|
-
import { mkdir as
|
|
9068
|
-
import { dirname as
|
|
9126
|
+
import { mkdir as mkdir12, readFile as readFile16, writeFile as writeFile14 } from "fs/promises";
|
|
9127
|
+
import { dirname as dirname12, join as join18 } from "path";
|
|
9069
9128
|
var POLL_MS2 = 2e3;
|
|
9070
9129
|
var HEARTBEAT_TIMEOUT_MS = 1e3;
|
|
9071
9130
|
async function saveTemplate(cwd, run4) {
|
|
9072
|
-
const path =
|
|
9131
|
+
const path = join18(cwd, WATCH_PATH);
|
|
9073
9132
|
let config = {};
|
|
9074
9133
|
try {
|
|
9075
9134
|
const parsed = JSON.parse(await readFile16(path, "utf8"));
|
|
@@ -9079,8 +9138,8 @@ async function saveTemplate(cwd, run4) {
|
|
|
9079
9138
|
} catch {
|
|
9080
9139
|
}
|
|
9081
9140
|
config.run = run4;
|
|
9082
|
-
await
|
|
9083
|
-
await
|
|
9141
|
+
await mkdir12(dirname12(path), { recursive: true });
|
|
9142
|
+
await writeFile14(path, `${JSON.stringify(config, null, 2)}
|
|
9084
9143
|
`, "utf8");
|
|
9085
9144
|
}
|
|
9086
9145
|
function spawnAgent(command, args, cwd) {
|
|
@@ -9219,12 +9278,12 @@ async function runWatch(options, deps) {
|
|
|
9219
9278
|
|
|
9220
9279
|
// src/run-classify.ts
|
|
9221
9280
|
import { stat as stat4 } from "fs/promises";
|
|
9222
|
-
import { join as
|
|
9281
|
+
import { join as join19 } from "path";
|
|
9223
9282
|
async function runClassify(options, deps) {
|
|
9224
9283
|
const declared = await Promise.all(
|
|
9225
9284
|
options.changes.map(async (change) => ({
|
|
9226
9285
|
...change,
|
|
9227
|
-
exists: await stat4(
|
|
9286
|
+
exists: await stat4(join19(options.cwd, change.path)).then(
|
|
9228
9287
|
() => true,
|
|
9229
9288
|
() => false
|
|
9230
9289
|
)
|
|
@@ -9250,10 +9309,10 @@ async function runClassify(options, deps) {
|
|
|
9250
9309
|
}
|
|
9251
9310
|
|
|
9252
9311
|
// src/run.ts
|
|
9253
|
-
import { existsSync as
|
|
9312
|
+
import { existsSync as existsSync7 } from "fs";
|
|
9254
9313
|
import { realpath as realpath4 } from "fs/promises";
|
|
9255
9314
|
import { createRequire } from "module";
|
|
9256
|
-
import { basename as basename6, dirname as
|
|
9315
|
+
import { basename as basename6, dirname as dirname13, join as join20, relative as relative5, resolve as resolve4 } from "path";
|
|
9257
9316
|
import { fileURLToPath } from "url";
|
|
9258
9317
|
|
|
9259
9318
|
// src/dev-server-owner.ts
|
|
@@ -9339,11 +9398,11 @@ function devServerOwnerWarning(origin, projectRoot, owners) {
|
|
|
9339
9398
|
|
|
9340
9399
|
// src/run.ts
|
|
9341
9400
|
function findShellDir() {
|
|
9342
|
-
const bundled =
|
|
9343
|
-
if (
|
|
9401
|
+
const bundled = join20(dirname13(fileURLToPath(import.meta.url)), "shell");
|
|
9402
|
+
if (existsSync7(join20(bundled, "index.html"))) return bundled;
|
|
9344
9403
|
try {
|
|
9345
9404
|
const require2 = createRequire(import.meta.url);
|
|
9346
|
-
return
|
|
9405
|
+
return dirname13(require2.resolve("@leglas/shell/dist/index.html"));
|
|
9347
9406
|
} catch {
|
|
9348
9407
|
return null;
|
|
9349
9408
|
}
|
|
@@ -9354,10 +9413,14 @@ function shellWord(value) {
|
|
|
9354
9413
|
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
9355
9414
|
}
|
|
9356
9415
|
function embeddedLeglasCommand() {
|
|
9357
|
-
const entry =
|
|
9358
|
-
if (!
|
|
9416
|
+
const entry = join20(dirname13(fileURLToPath(import.meta.url)), "bin.js");
|
|
9417
|
+
if (!existsSync7(entry)) return "npx -y leglas";
|
|
9359
9418
|
return [process.execPath, entry].map(shellWord).join(" ");
|
|
9360
9419
|
}
|
|
9420
|
+
function skipStartupCheck(env) {
|
|
9421
|
+
const off = (value) => value === void 0 || value === "" || value === "0" || value === "false";
|
|
9422
|
+
return env.CI !== void 0 && env.CI !== "" && env.CI !== "false" || !off(env.LEGLAS_NO_UPDATE_CHECK);
|
|
9423
|
+
}
|
|
9361
9424
|
async function run3(options, deps) {
|
|
9362
9425
|
const loaded = await loadConfig(options.cwd);
|
|
9363
9426
|
const local = await readLocalPreviews(options.cwd);
|
|
@@ -9386,8 +9449,8 @@ async function run3(options, deps) {
|
|
|
9386
9449
|
const fileMounts = /* @__PURE__ */ new Map();
|
|
9387
9450
|
for (const preview of merged?.previews ?? []) {
|
|
9388
9451
|
if (preview.file !== void 0) {
|
|
9389
|
-
const absolute =
|
|
9390
|
-
if (!
|
|
9452
|
+
const absolute = join20(options.cwd, preview.file);
|
|
9453
|
+
if (!existsSync7(absolute)) {
|
|
9391
9454
|
previewErrors.push(
|
|
9392
9455
|
`"${preview.title}" names file ${preview.file}, which does not exist. The preview is skipped.`
|
|
9393
9456
|
);
|
|
@@ -9397,7 +9460,7 @@ async function run3(options, deps) {
|
|
|
9397
9460
|
for (let suffix = 2; fileMounts.has(slug); suffix += 1) {
|
|
9398
9461
|
slug = `${worktreeSlug(preview.title) || "file"}-${suffix}`;
|
|
9399
9462
|
}
|
|
9400
|
-
fileMounts.set(slug,
|
|
9463
|
+
fileMounts.set(slug, dirname13(absolute));
|
|
9401
9464
|
previews.push({
|
|
9402
9465
|
...preview,
|
|
9403
9466
|
url: `${FILES_PREFIX}/${slug}/${encodeURIComponent(basename6(absolute))}`
|
|
@@ -9408,8 +9471,8 @@ async function run3(options, deps) {
|
|
|
9408
9471
|
}
|
|
9409
9472
|
const config = merged === null ? null : { ...merged, previews };
|
|
9410
9473
|
const configWarnings = [];
|
|
9411
|
-
const projectRoot = await realpath4(loaded.path === null ? options.cwd :
|
|
9412
|
-
() => resolve4(loaded.path === null ? options.cwd :
|
|
9474
|
+
const projectRoot = await realpath4(loaded.path === null ? options.cwd : dirname13(loaded.path)).catch(
|
|
9475
|
+
() => resolve4(loaded.path === null ? options.cwd : dirname13(loaded.path))
|
|
9413
9476
|
);
|
|
9414
9477
|
const ownerWarning = needsApp && app === null ? inspectLocalDevServer(devServer).then((owners) => devServerOwnerWarning(devServer, projectRoot, owners)).catch(() => null) : Promise.resolve(null);
|
|
9415
9478
|
const serverPromise = startServer({
|
|
@@ -9423,6 +9486,7 @@ async function run3(options, deps) {
|
|
|
9423
9486
|
project: loaded.path ?? options.cwd,
|
|
9424
9487
|
cwd: options.cwd,
|
|
9425
9488
|
leglasCommand: embeddedLeglasCommand(),
|
|
9489
|
+
...deps.updates === void 0 ? {} : { updates: deps.updates },
|
|
9426
9490
|
...options.port === void 0 ? {} : { port: options.port }
|
|
9427
9491
|
});
|
|
9428
9492
|
const [server, warning] = await Promise.all([serverPromise, ownerWarning]);
|
|
@@ -9472,12 +9536,26 @@ async function run3(options, deps) {
|
|
|
9472
9536
|
}
|
|
9473
9537
|
}
|
|
9474
9538
|
if (options.open) await deps.open(url);
|
|
9539
|
+
let stopped = false;
|
|
9540
|
+
let updateTimer = null;
|
|
9541
|
+
if (!options.json && deps.updates !== void 0 && !skipStartupCheck(process.env)) {
|
|
9542
|
+
void deps.updates.check().then(() => {
|
|
9543
|
+
if (stopped) return;
|
|
9544
|
+
const line = deps.updates?.notice();
|
|
9545
|
+
if (line !== null && line !== void 0) deps.log(line);
|
|
9546
|
+
});
|
|
9547
|
+
const updates = deps.updates;
|
|
9548
|
+
updateTimer = setInterval(() => void updates.check(), 60 * 6e4);
|
|
9549
|
+
updateTimer.unref?.();
|
|
9550
|
+
}
|
|
9475
9551
|
return {
|
|
9476
9552
|
exitCode: 0,
|
|
9477
9553
|
url,
|
|
9478
9554
|
devServer,
|
|
9479
9555
|
previewCount,
|
|
9480
9556
|
stop: async () => {
|
|
9557
|
+
stopped = true;
|
|
9558
|
+
if (updateTimer !== null) clearInterval(updateTimer);
|
|
9481
9559
|
await app?.stop().catch(() => {
|
|
9482
9560
|
});
|
|
9483
9561
|
await server.close();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { spawn as spawnChild } from "node:child_process";
|
|
2
|
+
import type { RestartCommand } from "@leglas/server";
|
|
3
|
+
import { type ShutdownSignal, type SignalTarget } from "./shutdown.js";
|
|
4
|
+
export declare function createHandoff(): {
|
|
5
|
+
handOff: (command: RestartCommand, stop: () => Promise<void>, deps: {
|
|
6
|
+
spawn: typeof spawnChild;
|
|
7
|
+
exit: (code: number) => void;
|
|
8
|
+
target: SignalTarget & {
|
|
9
|
+
off(signal: ShutdownSignal, listener: () => void): unknown;
|
|
10
|
+
};
|
|
11
|
+
}) => Promise<void>;
|
|
12
|
+
handedOff: () => boolean;
|
|
13
|
+
};
|
package/dist/run.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import { type UpdateService } from "@leglas/server";
|
|
1
2
|
import type { RunOptions } from "./args.js";
|
|
2
3
|
export type RunDeps = {
|
|
3
4
|
open(url: string): Promise<void>;
|
|
4
5
|
log(line: string): void;
|
|
6
|
+
/** The CLI supplies updates; programmatic hosts can omit them. */
|
|
7
|
+
updates?: UpdateService;
|
|
5
8
|
};
|
|
9
|
+
/** Automated runs and an explicit opt-out should never ask npm at startup. */
|
|
10
|
+
export declare function skipStartupCheck(env: NodeJS.ProcessEnv): boolean;
|
|
6
11
|
export type RunResult = {
|
|
7
12
|
exitCode: number;
|
|
8
13
|
/** Where the interface lives, which is what gets opened and reported. */
|