zdashboard 2.10.0 → 2.11.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.
Files changed (38) hide show
  1. package/dist/cli.js +153 -22
  2. package/dist/cli.js.map +1 -1
  3. package/dist/web/assets/{CodeViewer-yUtAi1hT.js → CodeViewer-B7R0ZVR6.js} +51 -51
  4. package/dist/web/assets/FileIcon-gTgtllQ_.js +1 -0
  5. package/dist/web/assets/FilterPills-BG7PY8Ts.js +1 -0
  6. package/dist/web/assets/Sidebar-BFf7prry.js +1 -0
  7. package/dist/web/assets/Sidebar-Bv_jdwYG.js +1 -0
  8. package/dist/web/assets/Workspace-B7_x75Fc.js +1 -0
  9. package/dist/web/assets/Workspace-BimPBKII.js +1 -0
  10. package/dist/web/assets/Workspace-D42j59Cc.js +5 -0
  11. package/dist/web/assets/Workspace-DXhpVGbA.js +1 -0
  12. package/dist/web/assets/fetchJson-D-dWeu1j.js +1 -0
  13. package/dist/web/assets/index-B4hHecQs.js +316 -0
  14. package/dist/web/assets/index-Bfe1AHR3.css +1 -0
  15. package/dist/web/assets/usePluginData-Dhhi3YQs.js +1 -0
  16. package/dist/web/assets/vendor-react-CDaMBvJU.js +40 -0
  17. package/dist/web/assets/vendor-ui-dpI0b3Ao.js +42 -0
  18. package/dist/web/assets/web-B3RuriWk.js +2 -0
  19. package/dist/web/assets/web-BJPSRqva.js +2 -0
  20. package/dist/web/assets/web-BugMZG5x.js +2 -0
  21. package/dist/web/assets/web-D-lPF0OX.js +2 -0
  22. package/dist/web/index.html +25 -2
  23. package/package.json +1 -1
  24. package/dist/web/assets/FileIcon-DCL9OD6I.js +0 -1
  25. package/dist/web/assets/FilterPills-BD2vsIx2.js +0 -1
  26. package/dist/web/assets/Sidebar-CsmNxVjL.js +0 -1
  27. package/dist/web/assets/Sidebar-DxeR1Shk.js +0 -1
  28. package/dist/web/assets/Workspace-BGj3n613.js +0 -1
  29. package/dist/web/assets/Workspace-CUepnOlM.js +0 -1
  30. package/dist/web/assets/Workspace-D0rSzljF.js +0 -1
  31. package/dist/web/assets/Workspace-DUK4Lm-v.js +0 -5
  32. package/dist/web/assets/index--t_ijYil.css +0 -1
  33. package/dist/web/assets/index-veTBv7Qv.js +0 -7816
  34. package/dist/web/assets/usePluginData-mjHueOjf.js +0 -1
  35. package/dist/web/assets/web-BODcUPmi.js +0 -2
  36. package/dist/web/assets/web-CsCyTuDw.js +0 -2
  37. package/dist/web/assets/web-D6VGQFbH.js +0 -2
  38. package/dist/web/assets/web-DIZjnUb4.js +0 -2
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import { access, readdir } from "fs/promises";
9
9
  // package.json
10
10
  var package_default = {
11
11
  name: "zdashboard",
12
- version: "2.10.0",
12
+ version: "2.11.0",
13
13
  description: "ZCode skill dashboard platform \u2014 pluggable viewers for zdesign/zview/zreview/zgoal",
14
14
  type: "module",
15
15
  bin: {
@@ -386,6 +386,25 @@ function json(res, data, status = 200) {
386
386
  res.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-cache" });
387
387
  res.end(JSON.stringify(data));
388
388
  }
389
+ var ERROR_PAGE_TEXT = {
390
+ 400: "\u8BF7\u6C42\u65E0\u6548",
391
+ 403: "\u6CA1\u6709\u8BBF\u95EE\u6743\u9650",
392
+ 404: "\u9875\u9762\u4E0D\u5B58\u5728"
393
+ };
394
+ function sendErrorPage(res, code) {
395
+ if (res.headersSent) {
396
+ try {
397
+ res.destroy();
398
+ } catch {
399
+ }
400
+ return;
401
+ }
402
+ const text = ERROR_PAGE_TEXT[code] ?? "\u51FA\u9519\u4E86";
403
+ res.writeHead(code, { "Content-Type": "text/html; charset=utf-8" });
404
+ res.end(
405
+ `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><title>${code} ${text} \xB7 zdashboard</title></head><body><h1>${code} \xB7 ${text}</h1><p>\u8BF7\u6C42\u7684\u8D44\u6E90\u4E0D\u53EF\u7528\u6216\u5DF2\u88AB\u79FB\u52A8\u3002</p><p><a href="/">\u8FD4\u56DE\u9996\u9875</a></p></body></html>`
406
+ );
407
+ }
389
408
  function guarded(req, res, token) {
390
409
  if (req.headers["x-stop-token"] !== token) {
391
410
  res.writeHead(403);
@@ -485,10 +504,14 @@ var ServerService = class extends Service {
485
504
  writePluginsConfig(this.root, merged);
486
505
  }
487
506
  serveFile(filePath, req, res, injectHtml) {
507
+ const apiPath = (req.url || "").startsWith("/__");
488
508
  fs3.stat(filePath, (err, stat) => {
489
509
  if (err) {
490
- res.writeHead(404);
491
- return res.end("Not found");
510
+ if (apiPath) {
511
+ res.writeHead(404);
512
+ return res.end("Not found");
513
+ }
514
+ return sendErrorPage(res, 404);
492
515
  }
493
516
  const ext = path3.extname(filePath).toLowerCase();
494
517
  const ct = MIME[ext] ?? "application/octet-stream";
@@ -526,8 +549,11 @@ var ServerService = class extends Service {
526
549
  }
527
550
  fs3.readFile(filePath, (err2, data) => {
528
551
  if (err2) {
529
- res.writeHead(404);
530
- return res.end("Not found");
552
+ if (apiPath) {
553
+ res.writeHead(404);
554
+ return res.end("Not found");
555
+ }
556
+ return sendErrorPage(res, 404);
531
557
  }
532
558
  let body = data;
533
559
  if (injectHtml && ext === ".html") {
@@ -594,22 +620,26 @@ var ServerService = class extends Service {
594
620
  if (url !== "/") fp2 = path3.join(this.appDir, this.safeDecode(url));
595
621
  if (url.indexOf("/__app/") === 0) fp2 = path3.join(this.appDir, url.slice(7));
596
622
  if (fp2 !== this.appDir && fp2.indexOf(this.appDir + path3.sep) !== 0) {
597
- res.writeHead(403);
598
- return res.end("Forbidden");
623
+ return sendErrorPage(res, 403);
599
624
  }
600
625
  if (url === "/") fp2 = path3.join(this.appDir, "index.html");
601
626
  return this.serveFile(fp2, req, res, false);
602
627
  }
603
628
  const fp = path3.join(this.root, this.safeDecode(url));
604
629
  if (fp !== this.root && fp.indexOf(this.root + path3.sep) !== 0) {
605
- res.writeHead(403);
606
- return res.end("Forbidden");
630
+ if ((req.url || "").startsWith("/__")) {
631
+ res.writeHead(403);
632
+ return res.end("Forbidden");
633
+ }
634
+ return sendErrorPage(res, 403);
607
635
  }
608
636
  return this.serveFile(fp, req, res, true);
609
637
  } catch (e) {
610
638
  try {
611
- res.writeHead(400);
612
- res.end("bad request");
639
+ if ((req.url || "").startsWith("/__")) {
640
+ res.writeHead(400);
641
+ res.end("bad request");
642
+ } else sendErrorPage(res, 400);
613
643
  } catch {
614
644
  }
615
645
  console.error("[zdashboard] handler error", e);
@@ -1055,9 +1085,66 @@ var DashboardService = class extends Service3 {
1055
1085
  }
1056
1086
  };
1057
1087
 
1088
+ // src/plugins/just/index.ts
1089
+ import { execFile as execFile6 } from "child_process";
1090
+
1058
1091
  // src/server/just-runner.ts
1059
1092
  import { spawn, execFile as execFile5 } from "child_process";
1060
1093
  import iconv from "iconv-lite";
1094
+
1095
+ // src/server/just-recipe-params.ts
1096
+ var NAME_RE = /^[A-Za-z_][A-Za-z0-9_-]*/;
1097
+ function parseRecipeSignature(line) {
1098
+ let s = line.trim();
1099
+ if (s.startsWith("@")) s = s.slice(1).trimStart();
1100
+ const nameMatch = NAME_RE.exec(s);
1101
+ if (!nameMatch) return null;
1102
+ const name = nameMatch[0];
1103
+ const params = [];
1104
+ let i = name.length;
1105
+ while (true) {
1106
+ while (i < s.length && /\s/.test(s[i])) i++;
1107
+ if (i >= s.length) return null;
1108
+ if (s[i] === ":") {
1109
+ const rest = s.slice(i + 1).trim();
1110
+ if (rest === "" || rest.startsWith("#")) return { name, params };
1111
+ return null;
1112
+ }
1113
+ let j = i;
1114
+ if (s[j] === "+" || s[j] === "$") j++;
1115
+ const pm = NAME_RE.exec(s.slice(j));
1116
+ if (!pm) return null;
1117
+ j += pm[0].length;
1118
+ if (s[j] === "=") {
1119
+ j++;
1120
+ if (s[j] === '"' || s[j] === "'") {
1121
+ const quote = s[j];
1122
+ j++;
1123
+ while (j < s.length && s[j] !== quote) j++;
1124
+ if (j >= s.length) return null;
1125
+ j++;
1126
+ } else {
1127
+ while (j < s.length && !/\s/.test(s[j]) && s[j] !== ":") j++;
1128
+ }
1129
+ }
1130
+ params.push(pm[0]);
1131
+ i = j;
1132
+ }
1133
+ }
1134
+ function buildJustArgv(recipe, args) {
1135
+ if (!args) return [recipe];
1136
+ return [recipe, ...Object.entries(args).map(([k, v]) => `${k}=${v}`)];
1137
+ }
1138
+ function pickStringArgs(args) {
1139
+ if (typeof args !== "object" || args === null) return void 0;
1140
+ const out = {};
1141
+ for (const [k, v] of Object.entries(args)) {
1142
+ if (typeof v === "string") out[k] = v;
1143
+ }
1144
+ return Object.keys(out).length > 0 ? out : void 0;
1145
+ }
1146
+
1147
+ // src/server/just-runner.ts
1061
1148
  var MAX_BUFFER = 1e3;
1062
1149
  var utf8Strict = new TextDecoder("utf-8", { fatal: true });
1063
1150
  function decodeLine(buf) {
@@ -1072,6 +1159,8 @@ var JustRunner = class {
1072
1159
  tasks = /* @__PURE__ */ new Map();
1073
1160
  clients = /* @__PURE__ */ new Set();
1074
1161
  recipesCache = null;
1162
+ /** recipe 参数清单进程内缓存(含失败空数组),避免反复 just --show 探测 */
1163
+ paramsCache = /* @__PURE__ */ new Map();
1075
1164
  constructor(cwd) {
1076
1165
  this.cwd = cwd;
1077
1166
  }
@@ -1101,6 +1190,24 @@ var JustRunner = class {
1101
1190
  });
1102
1191
  });
1103
1192
  }
1193
+ /** 懒解析单 recipe 参数:just --show 输出中首个可解析签名行(跳过 recipe 上方注释/空行)→ 参数名;失败记空数组并缓存 */
1194
+ recipeParams(name) {
1195
+ const hit = this.paramsCache.get(name);
1196
+ if (hit) return Promise.resolve(hit);
1197
+ return new Promise((resolve) => {
1198
+ execFile5("just", ["--show", name], { cwd: this.cwd, maxBuffer: 1 << 20, timeout: 8e3 }, (err, stdout) => {
1199
+ const sig = err ? null : stdout.split(/\r?\n/).map((l) => parseRecipeSignature(l)).find(Boolean) ?? null;
1200
+ const params = sig?.params ?? [];
1201
+ this.paramsCache.set(name, params);
1202
+ resolve(params);
1203
+ });
1204
+ });
1205
+ }
1206
+ /** recipes 列表附参数清单(参数探测逐 recipe 并发,进程内缓存后零开销) */
1207
+ async recipesWithParams() {
1208
+ const list = await this.recipes();
1209
+ return Promise.all(list.map(async (r) => ({ ...r, params: await this.recipeParams(r.name) })));
1210
+ }
1104
1211
  subscribe(fn) {
1105
1212
  this.clients.add(fn);
1106
1213
  for (const t of this.tasks.values()) {
@@ -1115,16 +1222,16 @@ var JustRunner = class {
1115
1222
  list() {
1116
1223
  return Array.from(this.tasks.values(), (t) => ({ recipe: t.recipe, state: t.state, code: t.code, startedAt: t.startedAt }));
1117
1224
  }
1118
- /** 启动 recipe:同名先停旧进程(重启语义),不影响其他任务;调用方须先用 recipes() 校验名字 */
1119
- start(recipe) {
1225
+ /** 启动 recipe:同名先停旧进程(重启语义),不影响其他任务;调用方须先用 recipes() 校验名字;
1226
+ * args 为可选参数表,spawn 数组 argv 追加 k=v(不经 shell,特殊字符安全) */
1227
+ start(recipe, args) {
1120
1228
  this.killOne(recipe);
1121
1229
  const task = { recipe, child: null, state: "running", code: null, startedAt: Date.now(), buffer: [], pending: Buffer.alloc(0) };
1122
1230
  this.tasks.set(recipe, task);
1123
1231
  this.emit({ type: "clear", recipe });
1124
1232
  this.emit({ type: "state", recipe, state: "running", code: null, startedAt: task.startedAt });
1125
- const child = spawn("just", [recipe], {
1233
+ const child = spawn("just", buildJustArgv(recipe, args), {
1126
1234
  cwd: this.cwd,
1127
- shell: true,
1128
1235
  env: {
1129
1236
  ...process.env,
1130
1237
  FORCE_COLOR: "1",
@@ -1165,10 +1272,22 @@ var JustRunner = class {
1165
1272
  this.emit({ type: "state", recipe, state: "exited", code: task.code, startedAt: task.startedAt, signal: task.signal });
1166
1273
  });
1167
1274
  }
1275
+ /** 行推送:行内 \r 再切分为多段独立行(进度条输出降级为分段渲染);
1276
+ * \r 空段丢弃,但整行全空仍推送一条空行(空行是日志内容,不静默丢弃) */
1168
1277
  pushLine(task, line) {
1169
- task.buffer.push(line);
1278
+ const body = line.endsWith("\n") ? line.slice(0, -1) : line;
1279
+ const segs = body.split("\r").filter(Boolean);
1280
+ if (segs.length === 0) {
1281
+ this.pushSegment(task, "");
1282
+ return;
1283
+ }
1284
+ for (const seg of segs) this.pushSegment(task, seg);
1285
+ }
1286
+ pushSegment(task, seg) {
1287
+ const text = seg + "\n";
1288
+ task.buffer.push(text);
1170
1289
  if (task.buffer.length > MAX_BUFFER) task.buffer.shift();
1171
- this.emit({ type: "log", recipe: task.recipe, text: line });
1290
+ this.emit({ type: "log", recipe: task.recipe, text });
1172
1291
  }
1173
1292
  /** 停单个任务;不传 recipe 停全部 */
1174
1293
  stop(recipe) {
@@ -1305,12 +1424,21 @@ var apply3 = defineBuiltin({
1305
1424
  ctx.onDispose(() => runner.stop());
1306
1425
  ctx.route("/__just/recipes", async () => {
1307
1426
  try {
1308
- return await runner.recipes();
1427
+ return await runner.recipesWithParams();
1309
1428
  } catch {
1310
1429
  return [];
1311
1430
  }
1312
1431
  });
1313
1432
  ctx.route("/__just/tasks", async () => runner.list());
1433
+ ctx.route("/__just/status", async () => {
1434
+ return await new Promise((resolve) => {
1435
+ execFile6("just", ["--list", "--unsorted"], { cwd: root, maxBuffer: 1 << 20, timeout: 8e3 }, (err) => {
1436
+ if (!err) return resolve({ installed: true, hasJustfile: true });
1437
+ if (err.code === "ENOENT") return resolve({ installed: false, hasJustfile: false });
1438
+ resolve({ installed: true, hasJustfile: false });
1439
+ });
1440
+ });
1441
+ });
1314
1442
  ctx.sse("/__just/logs", (res) => {
1315
1443
  const unsub = runner.subscribe((ev) => {
1316
1444
  try {
@@ -1326,8 +1454,11 @@ var apply3 = defineBuiltin({
1326
1454
  const handleAction = async (req, res, act) => {
1327
1455
  const body = await readBody(req);
1328
1456
  let recipe;
1457
+ let rawArgs;
1329
1458
  try {
1330
- recipe = JSON.parse(body || "{}").recipe;
1459
+ const parsed = JSON.parse(body || "{}");
1460
+ recipe = parsed.recipe;
1461
+ rawArgs = parsed.args;
1331
1462
  } catch {
1332
1463
  }
1333
1464
  if (act === "clear") {
@@ -1346,7 +1477,7 @@ var apply3 = defineBuiltin({
1346
1477
  res.end('{"error":"unknown recipe"}');
1347
1478
  return void 0;
1348
1479
  }
1349
- runner.start(recipe);
1480
+ runner.start(recipe, pickStringArgs(rawArgs));
1350
1481
  } else {
1351
1482
  runner.stop(recipe || void 0);
1352
1483
  }
@@ -1480,7 +1611,7 @@ var apply5 = defineBuiltin({
1480
1611
  // src/plugins/stats/index.ts
1481
1612
  import fs8 from "fs";
1482
1613
  import path8 from "path";
1483
- import { execFile as execFile6 } from "child_process";
1614
+ import { execFile as execFile7 } from "child_process";
1484
1615
 
1485
1616
  // src/plugins/stats/manifest.ts
1486
1617
  var manifest4 = {
@@ -1495,7 +1626,7 @@ var manifest4 = {
1495
1626
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", ".cache"]);
1496
1627
  function execFilePromise(file, args, opts) {
1497
1628
  return new Promise((resolve) => {
1498
- execFile6(file, args, opts, (err, stdout) => resolve(err ? "" : stdout));
1629
+ execFile7(file, args, opts, (err, stdout) => resolve(err ? "" : stdout));
1499
1630
  });
1500
1631
  }
1501
1632
  function scan(root) {