zdashboard 2.8.4 → 2.8.5
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 +80 -305
- package/dist/cli.js.map +1 -1
- package/dist/web/assets/CodeViewer-BrquqtDE.js +307 -0
- package/dist/web/assets/{FileIcon-1KBdrfw3.js → FileIcon-FyROY2Qe.js} +1 -1
- package/dist/web/assets/{FilterPills-CCubROoX.js → FilterPills-DwCH0Z96.js} +1 -1
- package/dist/web/assets/{Sidebar-D4sJtPI_.js → Sidebar-BUi2jDuu.js} +1 -1
- package/dist/web/assets/{Sidebar-DE0UBp9b.js → Sidebar-Cz3W5MP6.js} +1 -1
- package/dist/web/assets/Workspace-CafALYiK.js +1 -0
- package/dist/web/assets/Workspace-D-n83gau.js +1 -0
- package/dist/web/assets/{Workspace-CvG5lIPT.js → Workspace-Db4r4oQO.js} +1 -1
- package/dist/web/assets/Workspace-ICT0FTaI.js +1 -0
- package/dist/web/assets/index--t_ijYil.css +1 -0
- package/dist/web/assets/{index-Co7MWSFf.js → index-BAFFLjYP.js} +12 -12
- package/dist/web/assets/{usePluginData-IoRqReIJ.js → usePluginData-DEpAv1Yl.js} +1 -1
- package/dist/web/assets/web-BP-QGcd3.js +2 -0
- package/dist/web/assets/web-CE86ahqk.js +2 -0
- package/dist/web/assets/{web-DOEmlI8d.js → web-D0z_1kb8.js} +2 -2
- package/dist/web/assets/web-S85uEYzf.js +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/BatchView-pClKtVa8.js +0 -2
- package/dist/web/assets/CheckpointViewer-CAbXbMti.js +0 -1
- package/dist/web/assets/CodeViewer-Cs0mG1h6.js +0 -279
- package/dist/web/assets/DependencyGraph-Czdg00AM.js +0 -1
- package/dist/web/assets/ProgressBar-DVfldtzq.js +0 -1
- package/dist/web/assets/SingleChangeView-BYcaGOyR.js +0 -1
- package/dist/web/assets/ViewHeader-DPfbaJmK.js +0 -1
- package/dist/web/assets/Workspace-B7MhdnwY.js +0 -2
- package/dist/web/assets/Workspace-CAcrVL-m.js +0 -1
- package/dist/web/assets/Workspace-DWHHR42N.js +0 -1
- package/dist/web/assets/Workspace-cXLWoMNL.js +0 -1
- package/dist/web/assets/index-CwS-KyOV.css +0 -1
- package/dist/web/assets/index-D6EJRHBV.js +0 -29
- package/dist/web/assets/web-BZSTgldm.js +0 -2
- package/dist/web/assets/web-CX1FEFxC.js +0 -2
- package/dist/web/assets/web-DLwxO4fO.js +0 -2
- package/dist/web/assets/web-DxbVzGf3.js +0 -2
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import path9 from "path";
|
|
5
|
+
import fs9 from "fs";
|
|
6
6
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
7
|
import { access, readdir } from "fs/promises";
|
|
8
8
|
import { parseArgs as utilParseArgs } from "util";
|
|
@@ -43,7 +43,7 @@ import { fileURLToPath } from "url";
|
|
|
43
43
|
// package.json
|
|
44
44
|
var package_default = {
|
|
45
45
|
name: "zdashboard",
|
|
46
|
-
version: "2.8.
|
|
46
|
+
version: "2.8.5",
|
|
47
47
|
description: "ZCode skill dashboard platform \u2014 pluggable viewers for zdesign/zview/zreview/zgoal",
|
|
48
48
|
type: "module",
|
|
49
49
|
bin: {
|
|
@@ -452,13 +452,13 @@ var ServerService = class extends Service {
|
|
|
452
452
|
});
|
|
453
453
|
this.start(config.port);
|
|
454
454
|
}
|
|
455
|
-
route(
|
|
456
|
-
this.routes.set(
|
|
457
|
-
this.ctx.effect(() => () => this.routes.delete(
|
|
455
|
+
route(path10, handler) {
|
|
456
|
+
this.routes.set(path10, handler);
|
|
457
|
+
this.ctx.effect(() => () => this.routes.delete(path10));
|
|
458
458
|
}
|
|
459
|
-
sse(
|
|
460
|
-
this.sses.set(
|
|
461
|
-
this.ctx.effect(() => () => this.sses.delete(
|
|
459
|
+
sse(path10, onConnect) {
|
|
460
|
+
this.sses.set(path10, onConnect);
|
|
461
|
+
this.ctx.effect(() => () => this.sses.delete(path10));
|
|
462
462
|
}
|
|
463
463
|
static(prefix, dir) {
|
|
464
464
|
this.prefixStatic.set(prefix, dir);
|
|
@@ -757,7 +757,7 @@ import path5 from "path";
|
|
|
757
757
|
import fs5 from "fs";
|
|
758
758
|
import path4 from "path";
|
|
759
759
|
function walkDir(root, opts = {}) {
|
|
760
|
-
const { skip = /* @__PURE__ */ new Set(), maxDepth, onFile, onDir } = opts;
|
|
760
|
+
const { skip = /* @__PURE__ */ new Set(), maxDepth, allowDotDirs = false, onFile, onDir } = opts;
|
|
761
761
|
function walk(dir, rel, depth) {
|
|
762
762
|
if (typeof maxDepth === "number" && depth > maxDepth) return;
|
|
763
763
|
let ents;
|
|
@@ -767,7 +767,7 @@ function walkDir(root, opts = {}) {
|
|
|
767
767
|
return;
|
|
768
768
|
}
|
|
769
769
|
for (const ent of ents) {
|
|
770
|
-
if (ent.name.startsWith(".")) continue;
|
|
770
|
+
if (ent.name.startsWith(".") && !(allowDotDirs && ent.isDirectory())) continue;
|
|
771
771
|
if (skip.has(ent.name)) continue;
|
|
772
772
|
const abs = path4.join(dir, ent.name);
|
|
773
773
|
const r = rel ? `${rel}/${ent.name}` : ent.name;
|
|
@@ -835,12 +835,14 @@ function prefixPaths(nodes, prefix) {
|
|
|
835
835
|
function scanTree(root, scanDirs, opts) {
|
|
836
836
|
const tree = [];
|
|
837
837
|
const defaultExpandDepth = typeof opts?.defaultExpandDepth === "number" ? opts.defaultExpandDepth : 2;
|
|
838
|
+
const dotDeclared = new Set(Array.isArray(opts?.dotDirs) ? opts.dotDirs : []);
|
|
838
839
|
const dirs = Array.isArray(scanDirs) ? scanDirs.filter((d) => typeof d === "string" && d) : [];
|
|
839
840
|
for (const dir of dirs) {
|
|
841
|
+
if (dir.startsWith(".") && !dotDeclared.has(dir)) continue;
|
|
840
842
|
const dirPath = path5.join(root, dir);
|
|
841
843
|
if (!fs6.existsSync(dirPath)) continue;
|
|
842
844
|
const paths = [];
|
|
843
|
-
walkDir(dirPath, { maxDepth: 4, onFile: (_, rel) => paths.push(rel) });
|
|
845
|
+
walkDir(dirPath, { maxDepth: 4, allowDotDirs: dotDeclared.has(dir), onFile: (_, rel) => paths.push(rel) });
|
|
844
846
|
const prefix = dir;
|
|
845
847
|
const children = prefixPaths(buildTree(paths, defaultExpandDepth), prefix);
|
|
846
848
|
if (children.length) {
|
|
@@ -851,7 +853,8 @@ function scanTree(root, scanDirs, opts) {
|
|
|
851
853
|
}
|
|
852
854
|
|
|
853
855
|
// src/core/tree.ts
|
|
854
|
-
var CONVENTION_SCAN_DIRS = ["openspec", "docs"];
|
|
856
|
+
var CONVENTION_SCAN_DIRS = ["openspec", "docs", ".zdev/apply"];
|
|
857
|
+
var CONVENTION_DOT_DIRS = CONVENTION_SCAN_DIRS.filter((d) => d.startsWith("."));
|
|
855
858
|
var apply = {
|
|
856
859
|
inject: ["server"],
|
|
857
860
|
apply(ctx, config) {
|
|
@@ -862,7 +865,7 @@ var apply = {
|
|
|
862
865
|
const url = new URL(req.url || "/", "http://localhost");
|
|
863
866
|
const wt = url.searchParams.get("wt");
|
|
864
867
|
const scanRoot = wt ? decodeURIComponent(wt) : config.root;
|
|
865
|
-
const tree = scanTree(scanRoot, [...CONVENTION_SCAN_DIRS]);
|
|
868
|
+
const tree = scanTree(scanRoot, [...CONVENTION_SCAN_DIRS], { dotDirs: [...CONVENTION_DOT_DIRS] });
|
|
866
869
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-cache" });
|
|
867
870
|
res.end(JSON.stringify({ tree }));
|
|
868
871
|
} catch {
|
|
@@ -1028,9 +1031,9 @@ var DashboardService = class extends Service3 {
|
|
|
1028
1031
|
res.end('{"error":"method not allowed"}');
|
|
1029
1032
|
});
|
|
1030
1033
|
}
|
|
1031
|
-
register(
|
|
1032
|
-
this.plugins.set(
|
|
1033
|
-
this.ctx.effect(() => () => this.plugins.delete(
|
|
1034
|
+
register(manifest5) {
|
|
1035
|
+
this.plugins.set(manifest5.mode, manifest5);
|
|
1036
|
+
this.ctx.effect(() => () => this.plugins.delete(manifest5.mode));
|
|
1034
1037
|
}
|
|
1035
1038
|
list() {
|
|
1036
1039
|
return Array.from(this.plugins.values());
|
|
@@ -1193,7 +1196,7 @@ var JustRunner = class {
|
|
|
1193
1196
|
|
|
1194
1197
|
// src/sdk/server.ts
|
|
1195
1198
|
function definePlugin(def) {
|
|
1196
|
-
const { manifest:
|
|
1199
|
+
const { manifest: manifest5, setup } = def;
|
|
1197
1200
|
return {
|
|
1198
1201
|
inject: ["server", "dashboard", "reload"],
|
|
1199
1202
|
apply(ctx) {
|
|
@@ -1203,49 +1206,49 @@ function definePlugin(def) {
|
|
|
1203
1206
|
try {
|
|
1204
1207
|
fn();
|
|
1205
1208
|
} catch (e) {
|
|
1206
|
-
console.error(`[zdashboard] plugin ${
|
|
1209
|
+
console.error(`[zdashboard] plugin ${manifest5.mode} dispose error:`, e);
|
|
1207
1210
|
}
|
|
1208
1211
|
}
|
|
1209
1212
|
});
|
|
1210
|
-
ctx.dashboard.register(
|
|
1213
|
+
ctx.dashboard.register(manifest5);
|
|
1211
1214
|
const respondWith = (handler) => async (req, res) => {
|
|
1212
1215
|
if (res.headersSent) return;
|
|
1213
1216
|
try {
|
|
1214
1217
|
const data = await handler(req, res);
|
|
1215
1218
|
if (data !== void 0 && !res.writableEnded) json(res, data);
|
|
1216
1219
|
} catch (e) {
|
|
1217
|
-
console.error(`[zdashboard] plugin ${
|
|
1220
|
+
console.error(`[zdashboard] plugin ${manifest5.mode} route error:`, e);
|
|
1218
1221
|
if (!res.headersSent) json(res, { error: "internal" }, 500);
|
|
1219
1222
|
}
|
|
1220
1223
|
};
|
|
1221
1224
|
const pctx = {
|
|
1222
|
-
mode:
|
|
1225
|
+
mode: manifest5.mode,
|
|
1223
1226
|
config() {
|
|
1224
|
-
const schema =
|
|
1225
|
-
const stored = ctx.server.getPluginConfig(
|
|
1227
|
+
const schema = manifest5.config ?? {};
|
|
1228
|
+
const stored = ctx.server.getPluginConfig(manifest5.mode) ?? {};
|
|
1226
1229
|
const defaults = Object.fromEntries(
|
|
1227
1230
|
Object.entries(schema).map(([key, field]) => [key, field.default])
|
|
1228
1231
|
);
|
|
1229
1232
|
return { ...defaults, ...stored };
|
|
1230
1233
|
},
|
|
1231
|
-
route(
|
|
1232
|
-
ctx.server.route(
|
|
1234
|
+
route(path10, handler) {
|
|
1235
|
+
ctx.server.route(path10, respondWith(handler));
|
|
1233
1236
|
},
|
|
1234
|
-
guardedRoute(
|
|
1237
|
+
guardedRoute(path10, handler) {
|
|
1235
1238
|
const token = () => ctx.server.stopToken;
|
|
1236
|
-
ctx.server.route(
|
|
1239
|
+
ctx.server.route(path10, (req, res) => {
|
|
1237
1240
|
if (!guarded(req, res, token())) return;
|
|
1238
1241
|
void respondWith(handler)(req, res);
|
|
1239
1242
|
});
|
|
1240
1243
|
},
|
|
1241
|
-
sse(
|
|
1242
|
-
ctx.server.sse(
|
|
1244
|
+
sse(path10, handler) {
|
|
1245
|
+
ctx.server.sse(path10, handler);
|
|
1243
1246
|
},
|
|
1244
1247
|
static(prefix, dir) {
|
|
1245
1248
|
ctx.server.static(prefix, dir);
|
|
1246
1249
|
},
|
|
1247
1250
|
broadcast(event, data) {
|
|
1248
|
-
ctx.reload?.broadcast(`plugin:${
|
|
1251
|
+
ctx.reload?.broadcast(`plugin:${manifest5.mode}:${event}`, data ?? "");
|
|
1249
1252
|
},
|
|
1250
1253
|
onDispose(fn) {
|
|
1251
1254
|
disposers.push(fn);
|
|
@@ -1350,240 +1353,12 @@ var apply3 = defineBuiltin({
|
|
|
1350
1353
|
}
|
|
1351
1354
|
});
|
|
1352
1355
|
|
|
1353
|
-
// src/plugins/apply/scan.ts
|
|
1354
|
-
import fs7 from "fs";
|
|
1355
|
-
import path6 from "path";
|
|
1356
|
-
|
|
1357
|
-
// src/plugins/apply/parse-tasks.ts
|
|
1358
|
-
var TASK_RE = /^\s*-\s*\[([ xX])\]\s*(.*)$/;
|
|
1359
|
-
var MANUAL_PREFIX = "\u{1F527}[\u4EBA\u5DE5]";
|
|
1360
|
-
function parseTasks(md) {
|
|
1361
|
-
return md.split(/\r?\n/).filter((l) => TASK_RE.test(l)).map((l) => {
|
|
1362
|
-
const m = l.match(TASK_RE);
|
|
1363
|
-
const text = (m?.[2] ?? l).trim();
|
|
1364
|
-
return { text, checked: m?.[1] !== " ", manual: text.startsWith(MANUAL_PREFIX) };
|
|
1365
|
-
});
|
|
1366
|
-
}
|
|
1367
|
-
function countTasks(md) {
|
|
1368
|
-
const tasks = parseTasks(md);
|
|
1369
|
-
const normal = tasks.filter((t) => !t.manual);
|
|
1370
|
-
return {
|
|
1371
|
-
total: normal.length,
|
|
1372
|
-
done: normal.filter((t) => t.checked).length,
|
|
1373
|
-
manual: tasks.length - normal.length
|
|
1374
|
-
};
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
|
-
// src/plugins/apply/scan.ts
|
|
1378
|
-
function readText(p) {
|
|
1379
|
-
try {
|
|
1380
|
-
return fs7.readFileSync(p, "utf8");
|
|
1381
|
-
} catch {
|
|
1382
|
-
return "";
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
function worktreeDir(root, name) {
|
|
1386
|
-
return path6.join(root, ".zworktree", name);
|
|
1387
|
-
}
|
|
1388
|
-
function changeDir(root, name) {
|
|
1389
|
-
return path6.join(root, "openspec", "changes", name);
|
|
1390
|
-
}
|
|
1391
|
-
function parseDependsOn(proposal) {
|
|
1392
|
-
const deps = [];
|
|
1393
|
-
const m = proposal.match(/^##\s+依赖\s*\n([\s\S]*?)(?:\n#{1,6}\s|\n---\s*\n|$)/i);
|
|
1394
|
-
if (!m) return deps;
|
|
1395
|
-
for (const line of m[1].split("\n")) {
|
|
1396
|
-
if (!/^-\s+/.test(line)) continue;
|
|
1397
|
-
const name = line.replace(/^-\s+/, "").trim();
|
|
1398
|
-
if (name) deps.push(name);
|
|
1399
|
-
}
|
|
1400
|
-
return deps;
|
|
1401
|
-
}
|
|
1402
|
-
function scanApplyChanges(root) {
|
|
1403
|
-
const changesDir = path6.join(root, "openspec", "changes");
|
|
1404
|
-
if (!fs7.existsSync(changesDir)) return [];
|
|
1405
|
-
const out = [];
|
|
1406
|
-
for (const ent of fs7.readdirSync(changesDir, { withFileTypes: true })) {
|
|
1407
|
-
if (!ent.isDirectory() || ent.name.startsWith(".") || ent.name === "archive") continue;
|
|
1408
|
-
const wt = worktreeDir(root, ent.name);
|
|
1409
|
-
const tasks = readText(path6.join(wt, "openspec", "changes", ent.name, "tasks.md"));
|
|
1410
|
-
if (!tasks) {
|
|
1411
|
-
const fallback = readText(path6.join(changesDir, ent.name, "tasks.md"));
|
|
1412
|
-
if (fallback) {
|
|
1413
|
-
const { total, done } = countTasks(fallback);
|
|
1414
|
-
const wtChange = path6.join(wt, "openspec", "changes", ent.name);
|
|
1415
|
-
const mainChange = path6.join(changesDir, ent.name);
|
|
1416
|
-
out.push({
|
|
1417
|
-
name: ent.name,
|
|
1418
|
-
path: `openspec/changes/${ent.name}`,
|
|
1419
|
-
total,
|
|
1420
|
-
done,
|
|
1421
|
-
hasProposal: fs7.existsSync(path6.join(wtChange, "proposal.md")) || fs7.existsSync(path6.join(mainChange, "proposal.md")),
|
|
1422
|
-
hasDesign: fs7.existsSync(path6.join(wtChange, "design.md")) || fs7.existsSync(path6.join(mainChange, "design.md")),
|
|
1423
|
-
// worktree 目录存在即算执行中(tasks.md 可能尚未落盘)
|
|
1424
|
-
inWorktree: fs7.existsSync(wtChange)
|
|
1425
|
-
});
|
|
1426
|
-
}
|
|
1427
|
-
} else {
|
|
1428
|
-
const { total, done } = countTasks(tasks);
|
|
1429
|
-
const wtChange = path6.join(wt, "openspec", "changes", ent.name);
|
|
1430
|
-
const mainChange = path6.join(changesDir, ent.name);
|
|
1431
|
-
out.push({
|
|
1432
|
-
name: ent.name,
|
|
1433
|
-
path: `openspec/changes/${ent.name}`,
|
|
1434
|
-
total,
|
|
1435
|
-
done,
|
|
1436
|
-
// 任一侧存在即算(详情读取是 worktree 优先、主目录兜底,列表标记保持一致)
|
|
1437
|
-
hasProposal: fs7.existsSync(path6.join(wtChange, "proposal.md")) || fs7.existsSync(path6.join(mainChange, "proposal.md")),
|
|
1438
|
-
hasDesign: fs7.existsSync(path6.join(wtChange, "design.md")) || fs7.existsSync(path6.join(mainChange, "design.md")),
|
|
1439
|
-
inWorktree: true
|
|
1440
|
-
});
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
out.sort((a, b) => a.name.localeCompare(b.name));
|
|
1444
|
-
return out;
|
|
1445
|
-
}
|
|
1446
|
-
function readApplyChange(root, name) {
|
|
1447
|
-
const wt = worktreeDir(root, name);
|
|
1448
|
-
const mainChangeDir = changeDir(root, name);
|
|
1449
|
-
const proposalFrom = fs7.existsSync(path6.join(wt, "openspec", "changes", name, "proposal.md")) ? path6.join(wt, "openspec", "changes", name, "proposal.md") : path6.join(mainChangeDir, "proposal.md");
|
|
1450
|
-
const designFrom = fs7.existsSync(path6.join(wt, "openspec", "changes", name, "design.md")) ? path6.join(wt, "openspec", "changes", name, "design.md") : path6.join(mainChangeDir, "design.md");
|
|
1451
|
-
const tasksFrom = fs7.existsSync(path6.join(wt, "openspec", "changes", name, "tasks.md")) ? path6.join(wt, "openspec", "changes", name, "tasks.md") : path6.join(mainChangeDir, "tasks.md");
|
|
1452
|
-
const proposal = readText(proposalFrom);
|
|
1453
|
-
const design = readText(designFrom);
|
|
1454
|
-
const tasks = readText(tasksFrom);
|
|
1455
|
-
const { total, done } = countTasks(tasks);
|
|
1456
|
-
const inWorktree = fs7.existsSync(path6.join(wt, "openspec", "changes", name));
|
|
1457
|
-
const dependsOn = parseDependsOn(proposal);
|
|
1458
|
-
const hasTestStrategy = /^##\s+测试策略\s*$/m.test(design);
|
|
1459
|
-
return {
|
|
1460
|
-
name,
|
|
1461
|
-
path: `openspec/changes/${name}`,
|
|
1462
|
-
total,
|
|
1463
|
-
done,
|
|
1464
|
-
hasProposal: !!proposal,
|
|
1465
|
-
hasDesign: !!design,
|
|
1466
|
-
inWorktree,
|
|
1467
|
-
proposal,
|
|
1468
|
-
design,
|
|
1469
|
-
tasks,
|
|
1470
|
-
dependsOn,
|
|
1471
|
-
hasTestStrategy
|
|
1472
|
-
};
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
// src/plugins/apply/batch.ts
|
|
1476
|
-
import fs8 from "fs";
|
|
1477
|
-
import path7 from "path";
|
|
1478
|
-
var RUN_ID_PATTERN = /^[A-Za-z0-9-]+$/;
|
|
1479
|
-
function isPlainObject(v) {
|
|
1480
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1481
|
-
}
|
|
1482
|
-
function readText2(p) {
|
|
1483
|
-
try {
|
|
1484
|
-
return fs8.readFileSync(p, "utf8");
|
|
1485
|
-
} catch {
|
|
1486
|
-
return null;
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
function readBatchState(root, explicitRun) {
|
|
1490
|
-
const applyDir = path7.join(root, ".zdev", "apply");
|
|
1491
|
-
const requested = explicitRun?.trim() ?? "";
|
|
1492
|
-
const runId = requested && RUN_ID_PATTERN.test(requested) ? requested : readText2(path7.join(applyDir, "CURRENT"))?.trim() ?? "";
|
|
1493
|
-
if (!runId || !RUN_ID_PATTERN.test(runId)) {
|
|
1494
|
-
return { run: null, state: null };
|
|
1495
|
-
}
|
|
1496
|
-
const parsed = (() => {
|
|
1497
|
-
const text = readText2(path7.join(applyDir, "runs", runId, "state.json"));
|
|
1498
|
-
if (text == null) return null;
|
|
1499
|
-
try {
|
|
1500
|
-
return JSON.parse(text);
|
|
1501
|
-
} catch {
|
|
1502
|
-
return null;
|
|
1503
|
-
}
|
|
1504
|
-
})();
|
|
1505
|
-
if (!isPlainObject(parsed)) {
|
|
1506
|
-
return { run: { id: runId }, state: null };
|
|
1507
|
-
}
|
|
1508
|
-
return { run: { id: runId }, state: parsed };
|
|
1509
|
-
}
|
|
1510
|
-
function projectGraph(state) {
|
|
1511
|
-
if (!state) {
|
|
1512
|
-
return { changes: [], batches: [], conflicts: [] };
|
|
1513
|
-
}
|
|
1514
|
-
const rawChanges = state.changes;
|
|
1515
|
-
const rawBatches = state.batches;
|
|
1516
|
-
const rawConflicts = state.conflicts;
|
|
1517
|
-
return {
|
|
1518
|
-
changes: (Array.isArray(rawChanges) ? rawChanges : []).map((c) => ({
|
|
1519
|
-
name: c.name,
|
|
1520
|
-
status: c.status,
|
|
1521
|
-
dependencies: Array.isArray(c?.dependencies) ? c.dependencies : [],
|
|
1522
|
-
batchIndex: c.batchIndex
|
|
1523
|
-
})),
|
|
1524
|
-
batches: Array.isArray(rawBatches) ? rawBatches : [],
|
|
1525
|
-
conflicts: Array.isArray(rawConflicts) ? rawConflicts : []
|
|
1526
|
-
};
|
|
1527
|
-
}
|
|
1528
|
-
var BATCH_LOGS_TAIL = 100;
|
|
1529
|
-
function tailLogs(state) {
|
|
1530
|
-
const logs = state?.logs;
|
|
1531
|
-
return (Array.isArray(logs) ? logs : []).slice(-BATCH_LOGS_TAIL);
|
|
1532
|
-
}
|
|
1533
|
-
function readBatchPlan(root, runId) {
|
|
1534
|
-
return readText2(path7.join(root, ".zdev", "apply", "runs", runId, "plan.md"));
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
|
-
// src/plugins/apply/manifest.ts
|
|
1538
|
-
var manifest2 = {
|
|
1539
|
-
mode: "apply",
|
|
1540
|
-
label: "\u6267\u884C\u8FDB\u5EA6",
|
|
1541
|
-
icon: "\u2699\uFE0F",
|
|
1542
|
-
description: "OpenSpec \u6267\u884C\u8FDB\u5EA6 \xB7 zapply \u5355 change \u4E0E\u6279\u91CF",
|
|
1543
|
-
order: 40
|
|
1544
|
-
};
|
|
1545
|
-
|
|
1546
|
-
// src/plugins/apply/index.ts
|
|
1547
|
-
function errorResponse(res, status, msg) {
|
|
1548
|
-
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
1549
|
-
res.end(JSON.stringify({ error: msg }));
|
|
1550
|
-
return void 0;
|
|
1551
|
-
}
|
|
1552
|
-
var apply4 = defineBuiltin({
|
|
1553
|
-
manifest: manifest2,
|
|
1554
|
-
setup(ctx, root) {
|
|
1555
|
-
ctx.route("/__apply", async () => scanApplyChanges(root));
|
|
1556
|
-
ctx.route("/__apply/change", async (req, res) => {
|
|
1557
|
-
const url = new URL(req.url || "", "http://x");
|
|
1558
|
-
const name = url.searchParams.get("name");
|
|
1559
|
-
const bad = (msg) => errorResponse(res, 400, msg);
|
|
1560
|
-
if (!name) return bad("missing name");
|
|
1561
|
-
if (name.includes("..") || name.includes("/") || name.includes("\\")) return bad("invalid name");
|
|
1562
|
-
try {
|
|
1563
|
-
return readApplyChange(root, name);
|
|
1564
|
-
} catch (e) {
|
|
1565
|
-
return bad(e instanceof Error ? e.message : "unknown error");
|
|
1566
|
-
}
|
|
1567
|
-
});
|
|
1568
|
-
const queryRun = (req) => new URL(req.url || "", "http://x").searchParams.get("run") ?? void 0;
|
|
1569
|
-
ctx.route("/__apply/batch", async (req) => readBatchState(root, queryRun(req)));
|
|
1570
|
-
ctx.route("/__apply/batch/graph", async (req) => projectGraph(readBatchState(root, queryRun(req)).state));
|
|
1571
|
-
ctx.route("/__apply/batch/logs", async (req) => tailLogs(readBatchState(root, queryRun(req)).state));
|
|
1572
|
-
ctx.route("/__apply/batch/plan", async (req, res) => {
|
|
1573
|
-
const snap = readBatchState(root, queryRun(req));
|
|
1574
|
-
const plan = snap.run ? readBatchPlan(root, snap.run.id) : null;
|
|
1575
|
-
if (plan == null) return errorResponse(res, 404, "plan not found");
|
|
1576
|
-
return { plan };
|
|
1577
|
-
});
|
|
1578
|
-
}
|
|
1579
|
-
});
|
|
1580
|
-
|
|
1581
1356
|
// src/plugins/design/index.ts
|
|
1582
|
-
import
|
|
1583
|
-
import
|
|
1357
|
+
import path7 from "path";
|
|
1358
|
+
import fs7 from "fs";
|
|
1584
1359
|
|
|
1585
1360
|
// src/server/design-assets.ts
|
|
1586
|
-
import
|
|
1361
|
+
import path6 from "path";
|
|
1587
1362
|
var PAGE_EXTS = [".html", ".htm"];
|
|
1588
1363
|
var ICON_EXTS = [".svg", ".png", ".ico", ".jpg", ".jpeg", ".gif", ".webp"];
|
|
1589
1364
|
var VIDEO_EXTS = [".mp4", ".webm", ".mov", ".ogg", ".ogv"];
|
|
@@ -1611,17 +1386,17 @@ function scanAssets(root, subDir = "") {
|
|
|
1611
1386
|
const keys = ["page", "component", "icon", "token", "md", "video", "audio", "pdf", "font"];
|
|
1612
1387
|
for (const k of keys) out[k] = [];
|
|
1613
1388
|
const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".next", ".cache"]);
|
|
1614
|
-
const scanRoot = subDir ?
|
|
1389
|
+
const scanRoot = subDir ? path6.join(root, subDir) : root;
|
|
1615
1390
|
walkDir(scanRoot, { skip: SKIP, onFile: (abs, rel) => {
|
|
1616
|
-
const ext =
|
|
1391
|
+
const ext = path6.extname(abs).toLowerCase();
|
|
1617
1392
|
const t = categorize(rel, ext);
|
|
1618
|
-
if (t) out[t].push({ path: rel, name:
|
|
1393
|
+
if (t) out[t].push({ path: rel, name: path6.basename(abs), ext, type: t });
|
|
1619
1394
|
} });
|
|
1620
1395
|
return out;
|
|
1621
1396
|
}
|
|
1622
1397
|
|
|
1623
1398
|
// src/plugins/design/manifest.ts
|
|
1624
|
-
var
|
|
1399
|
+
var manifest2 = {
|
|
1625
1400
|
mode: "design",
|
|
1626
1401
|
label: "\u8BBE\u8BA1\u8D44\u4EA7",
|
|
1627
1402
|
icon: "\u{1F3A8}",
|
|
@@ -1636,35 +1411,35 @@ function emptyScan() {
|
|
|
1636
1411
|
for (const k of ASSET_KEYS) out[k] = [];
|
|
1637
1412
|
return out;
|
|
1638
1413
|
}
|
|
1639
|
-
var
|
|
1640
|
-
manifest:
|
|
1414
|
+
var apply4 = defineBuiltin({
|
|
1415
|
+
manifest: manifest2,
|
|
1641
1416
|
setup(ctx, root) {
|
|
1642
1417
|
ctx.route("/__design/assets", async () => {
|
|
1643
|
-
const designRoot =
|
|
1644
|
-
return
|
|
1418
|
+
const designRoot = path7.join(root, ".zdev", "design");
|
|
1419
|
+
return fs7.existsSync(designRoot) ? scanAssets(designRoot) : emptyScan();
|
|
1645
1420
|
});
|
|
1646
1421
|
ctx.route("/__design/asset", async (req, res) => {
|
|
1647
1422
|
const rel = new URL(req.url || "/", "http://localhost").searchParams.get("path") ?? "";
|
|
1648
|
-
if (!rel || rel.includes("..") || rel.includes("\\") ||
|
|
1423
|
+
if (!rel || rel.includes("..") || rel.includes("\\") || path7.isAbsolute(rel)) {
|
|
1649
1424
|
res.writeHead(400);
|
|
1650
1425
|
res.end("bad path");
|
|
1651
1426
|
return;
|
|
1652
1427
|
}
|
|
1653
|
-
const designRoot =
|
|
1654
|
-
const filePath =
|
|
1655
|
-
if (!filePath.startsWith(designRoot +
|
|
1428
|
+
const designRoot = path7.join(root, ".zdev", "design");
|
|
1429
|
+
const filePath = path7.join(designRoot, rel);
|
|
1430
|
+
if (!filePath.startsWith(designRoot + path7.sep)) {
|
|
1656
1431
|
res.writeHead(400);
|
|
1657
1432
|
res.end("bad path");
|
|
1658
1433
|
return;
|
|
1659
1434
|
}
|
|
1660
|
-
|
|
1435
|
+
fs7.stat(filePath, (err) => {
|
|
1661
1436
|
if (err) {
|
|
1662
1437
|
res.writeHead(404);
|
|
1663
1438
|
return res.end("Not found");
|
|
1664
1439
|
}
|
|
1665
|
-
const ct = MIME[
|
|
1440
|
+
const ct = MIME[path7.extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
1666
1441
|
res.writeHead(200, { "Content-Type": ct, "Cache-Control": "no-cache" });
|
|
1667
|
-
const stream =
|
|
1442
|
+
const stream = fs7.createReadStream(filePath);
|
|
1668
1443
|
stream.on("error", () => {
|
|
1669
1444
|
try {
|
|
1670
1445
|
res.destroy();
|
|
@@ -1678,7 +1453,7 @@ var apply5 = defineBuiltin({
|
|
|
1678
1453
|
});
|
|
1679
1454
|
|
|
1680
1455
|
// src/plugins/view/manifest.ts
|
|
1681
|
-
var
|
|
1456
|
+
var manifest3 = {
|
|
1682
1457
|
mode: "view",
|
|
1683
1458
|
label: "\u9879\u76EE\u6D4F\u89C8",
|
|
1684
1459
|
icon: "\u{1F441}\uFE0F",
|
|
@@ -1687,19 +1462,19 @@ var manifest4 = {
|
|
|
1687
1462
|
};
|
|
1688
1463
|
|
|
1689
1464
|
// src/plugins/view/index.ts
|
|
1690
|
-
var
|
|
1691
|
-
manifest:
|
|
1465
|
+
var apply5 = defineBuiltin({
|
|
1466
|
+
manifest: manifest3,
|
|
1692
1467
|
setup() {
|
|
1693
1468
|
}
|
|
1694
1469
|
});
|
|
1695
1470
|
|
|
1696
1471
|
// src/plugins/stats/index.ts
|
|
1697
|
-
import
|
|
1698
|
-
import
|
|
1472
|
+
import fs8 from "fs";
|
|
1473
|
+
import path8 from "path";
|
|
1699
1474
|
import { execFile as execFile6 } from "child_process";
|
|
1700
1475
|
|
|
1701
1476
|
// src/plugins/stats/manifest.ts
|
|
1702
|
-
var
|
|
1477
|
+
var manifest4 = {
|
|
1703
1478
|
mode: "stats",
|
|
1704
1479
|
label: "\u9879\u76EE\u7EDF\u8BA1",
|
|
1705
1480
|
icon: "\u{1F4CA}",
|
|
@@ -1716,14 +1491,14 @@ function execFilePromise(file, args, opts) {
|
|
|
1716
1491
|
}
|
|
1717
1492
|
function scan(root) {
|
|
1718
1493
|
const stats = {
|
|
1719
|
-
root:
|
|
1494
|
+
root: path8.basename(root),
|
|
1720
1495
|
files: 0,
|
|
1721
1496
|
dirs: 0,
|
|
1722
1497
|
totalSize: 0,
|
|
1723
1498
|
byExt: [],
|
|
1724
1499
|
markdown: 0,
|
|
1725
1500
|
openspec: { active: 0, archived: 0 },
|
|
1726
|
-
hasJust:
|
|
1501
|
+
hasJust: fs8.existsSync(path8.join(root, "justfile")),
|
|
1727
1502
|
worktrees: 0
|
|
1728
1503
|
};
|
|
1729
1504
|
const extMap = /* @__PURE__ */ new Map();
|
|
@@ -1733,22 +1508,22 @@ function scan(root) {
|
|
|
1733
1508
|
}, onFile: (abs) => {
|
|
1734
1509
|
stats.files++;
|
|
1735
1510
|
try {
|
|
1736
|
-
stats.totalSize +=
|
|
1511
|
+
stats.totalSize += fs8.statSync(abs).size;
|
|
1737
1512
|
} catch {
|
|
1738
1513
|
}
|
|
1739
|
-
const ext =
|
|
1514
|
+
const ext = path8.extname(abs).toLowerCase() || "(\u65E0\u6269\u5C55\u540D)";
|
|
1740
1515
|
extMap.set(ext, (extMap.get(ext) ?? 0) + 1);
|
|
1741
1516
|
if (ext === ".md" || ext === ".markdown") stats.markdown++;
|
|
1742
1517
|
} });
|
|
1743
1518
|
};
|
|
1744
1519
|
walk(root);
|
|
1745
|
-
const changesDir =
|
|
1746
|
-
if (
|
|
1747
|
-
for (const ent of
|
|
1520
|
+
const changesDir = path8.join(root, "openspec", "changes");
|
|
1521
|
+
if (fs8.existsSync(changesDir)) {
|
|
1522
|
+
for (const ent of fs8.readdirSync(changesDir, { withFileTypes: true })) {
|
|
1748
1523
|
if (!ent.isDirectory() || ent.name.startsWith(".")) continue;
|
|
1749
1524
|
if (ent.name === "archive") {
|
|
1750
1525
|
try {
|
|
1751
|
-
stats.openspec.archived =
|
|
1526
|
+
stats.openspec.archived = fs8.readdirSync(path8.join(changesDir, "archive"), { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).length;
|
|
1752
1527
|
} catch {
|
|
1753
1528
|
}
|
|
1754
1529
|
} else stats.openspec.active++;
|
|
@@ -1757,8 +1532,8 @@ function scan(root) {
|
|
|
1757
1532
|
stats.byExt = Array.from(extMap.entries()).map(([ext, count]) => ({ ext, count })).sort((a, b) => b.count - a.count).slice(0, 10);
|
|
1758
1533
|
return stats;
|
|
1759
1534
|
}
|
|
1760
|
-
var
|
|
1761
|
-
manifest:
|
|
1535
|
+
var apply6 = defineBuiltin({
|
|
1536
|
+
manifest: manifest4,
|
|
1762
1537
|
setup(ctx, root) {
|
|
1763
1538
|
ctx.route("/__stats/data", async () => {
|
|
1764
1539
|
const stats = scan(root);
|
|
@@ -1784,7 +1559,7 @@ var apply7 = defineBuiltin({
|
|
|
1784
1559
|
});
|
|
1785
1560
|
|
|
1786
1561
|
// src/cli.ts
|
|
1787
|
-
var __dirname2 =
|
|
1562
|
+
var __dirname2 = path9.dirname(fileURLToPath2(import.meta.url));
|
|
1788
1563
|
var DEFAULT_PORT = 4190;
|
|
1789
1564
|
function parseArgs() {
|
|
1790
1565
|
const args = process.argv.slice(2);
|
|
@@ -1834,7 +1609,7 @@ async function loadExternal(ctx, dir, root) {
|
|
|
1834
1609
|
if (!ent.isDirectory()) continue;
|
|
1835
1610
|
const candidates = tsxLoaded ? ["index.ts", "index.js", "index.mjs"] : ["index.js", "index.mjs"];
|
|
1836
1611
|
for (const name of candidates) {
|
|
1837
|
-
const p =
|
|
1612
|
+
const p = path9.join(dir, ent.name, name);
|
|
1838
1613
|
if (await pathExists(p)) {
|
|
1839
1614
|
try {
|
|
1840
1615
|
const mod = await import(p);
|
|
@@ -1849,8 +1624,8 @@ async function loadExternal(ctx, dir, root) {
|
|
|
1849
1624
|
const m = ctx.dashboard.get(ent.name);
|
|
1850
1625
|
if (m && m.mode === ent.name) {
|
|
1851
1626
|
const patch = { external: true };
|
|
1852
|
-
const webDir =
|
|
1853
|
-
if (await pathExists(
|
|
1627
|
+
const webDir = path9.join(dir, ent.name, "web");
|
|
1628
|
+
if (await pathExists(path9.join(webDir, "index.html"))) {
|
|
1854
1629
|
ctx.server.static(`${PLUGIN_STATIC_PREFIX}${ent.name}/`, webDir);
|
|
1855
1630
|
if (!m.viewerUrl) patch.viewerUrl = `${PLUGIN_STATIC_PREFIX}${ent.name}/`;
|
|
1856
1631
|
}
|
|
@@ -1869,7 +1644,7 @@ async function loadExternal(ctx, dir, root) {
|
|
|
1869
1644
|
}
|
|
1870
1645
|
async function main() {
|
|
1871
1646
|
const args = parseArgs();
|
|
1872
|
-
const root =
|
|
1647
|
+
const root = path9.resolve(args.dir);
|
|
1873
1648
|
const existing = await findReusable(root);
|
|
1874
1649
|
if (existing && !args.restart) {
|
|
1875
1650
|
const u = `http://localhost:${existing.port}` + (args.page ? `#${args.page}` : "");
|
|
@@ -1883,14 +1658,14 @@ async function main() {
|
|
|
1883
1658
|
console.log(`[zdashboard] --restart\uFF1A\u505C\u6B62\u65E7\u5B9E\u4F8B pid=${oldRecord.pid}`);
|
|
1884
1659
|
await stopInstance(oldRecord);
|
|
1885
1660
|
}
|
|
1886
|
-
const appDir =
|
|
1887
|
-
const BUILTIN_PLUGINS = [
|
|
1661
|
+
const appDir = path9.resolve(__dirname2, "web");
|
|
1662
|
+
const BUILTIN_PLUGINS = [apply6, apply3, apply4, apply5];
|
|
1888
1663
|
if (stripDeadPluginConfig(root, BUILTIN_PLUGINS.map((p) => p.manifest))) {
|
|
1889
1664
|
console.log("[zdashboard] \u5DF2\u6E05\u7406 dashboard.json \u4E2D\u5185\u7F6E\u63D2\u4EF6\u7684\u8FC7\u65F6\u914D\u7F6E\u952E");
|
|
1890
1665
|
}
|
|
1891
1666
|
const startPort = oldRecord && !args.portExplicit ? oldRecord.port : args.port;
|
|
1892
|
-
const zdevDir =
|
|
1893
|
-
const dataDir =
|
|
1667
|
+
const zdevDir = path9.join(root, ".zdev");
|
|
1668
|
+
const dataDir = fs9.existsSync(zdevDir) ? ".zdev/" : "";
|
|
1894
1669
|
const det = await detect(root);
|
|
1895
1670
|
const ctx = new Context();
|
|
1896
1671
|
ctx.plugin(ServerService, {
|
|
@@ -1907,15 +1682,15 @@ async function main() {
|
|
|
1907
1682
|
ctx.plugin(apply, { root });
|
|
1908
1683
|
ctx.plugin(apply2, { root });
|
|
1909
1684
|
ctx.plugin(DashboardService);
|
|
1910
|
-
for (const
|
|
1685
|
+
for (const apply7 of BUILTIN_PLUGINS) {
|
|
1911
1686
|
try {
|
|
1912
|
-
ctx.plugin(
|
|
1687
|
+
ctx.plugin(apply7, { root });
|
|
1913
1688
|
} catch (e) {
|
|
1914
1689
|
console.error("[zdashboard] builtin plugin failed:", e);
|
|
1915
1690
|
}
|
|
1916
1691
|
}
|
|
1917
1692
|
if (args.plugins) {
|
|
1918
|
-
await loadExternal(ctx,
|
|
1693
|
+
await loadExternal(ctx, path9.resolve(args.plugins), root);
|
|
1919
1694
|
}
|
|
1920
1695
|
const shutdown = () => {
|
|
1921
1696
|
try {
|