zdashboard 2.9.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 +216 -68
  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
@@ -5,45 +5,11 @@ import path9 from "path";
5
5
  import fs9 from "fs";
6
6
  import { fileURLToPath as fileURLToPath2 } from "url";
7
7
  import { access, readdir } from "fs/promises";
8
- import { parseArgs as utilParseArgs } from "util";
9
-
10
- // src/server/detect.ts
11
- import fs from "fs";
12
- import path from "path";
13
- import { execFile } from "child_process";
14
- var JUST_PROBE_TIMEOUT_MS = 5e3;
15
- var JUST_FALLBACK_TIMEOUT_MS = 3e3;
16
- function justAvailable(cwd) {
17
- const probe = (args, timeout) => new Promise((resolve) => {
18
- const child = execFile("just", args, { cwd, timeout }, (err) => resolve(!err));
19
- if (child.killed) resolve(false);
20
- });
21
- return probe(["--list", "--unsupported"], JUST_PROBE_TIMEOUT_MS).then((ok) => ok ? true : probe(["--version"], JUST_FALLBACK_TIMEOUT_MS));
22
- }
23
- async function detect(root) {
24
- const hasOpenspec = fs.existsSync(path.join(root, "openspec"));
25
- const hasDocs = fs.existsSync(path.join(root, "docs"));
26
- const hasJust = await justAvailable(root);
27
- return { hasOpenspec, hasDocs, hasJust };
28
- }
29
- async function detectLiveShape(root) {
30
- return detect(root);
31
- }
32
-
33
- // src/cli.ts
34
- import { Context } from "cordis";
35
-
36
- // src/core/server.ts
37
- import http from "http";
38
- import fs3 from "fs";
39
- import path3 from "path";
40
- import crypto from "crypto";
41
- import { fileURLToPath } from "url";
42
8
 
43
9
  // package.json
44
10
  var package_default = {
45
11
  name: "zdashboard",
46
- version: "2.9.0",
12
+ version: "2.11.0",
47
13
  description: "ZCode skill dashboard platform \u2014 pluggable viewers for zdesign/zview/zreview/zgoal",
48
14
  type: "module",
49
15
  bin: {
@@ -134,12 +100,47 @@ var package_default = {
134
100
  }
135
101
  };
136
102
 
103
+ // src/cli.ts
104
+ import { parseArgs as utilParseArgs } from "util";
105
+
106
+ // src/server/detect.ts
107
+ import fs from "fs";
108
+ import path from "path";
109
+ import { execFile } from "child_process";
110
+ var JUST_PROBE_TIMEOUT_MS = 5e3;
111
+ var JUST_FALLBACK_TIMEOUT_MS = 3e3;
112
+ function justAvailable(cwd) {
113
+ const probe = (args, timeout) => new Promise((resolve) => {
114
+ const child = execFile("just", args, { cwd, timeout }, (err) => resolve(!err));
115
+ if (child.killed) resolve(false);
116
+ });
117
+ return probe(["--list", "--unsupported"], JUST_PROBE_TIMEOUT_MS).then((ok) => ok ? true : probe(["--version"], JUST_FALLBACK_TIMEOUT_MS));
118
+ }
119
+ async function detect(root) {
120
+ const hasOpenspec = fs.existsSync(path.join(root, "openspec"));
121
+ const hasDocs = fs.existsSync(path.join(root, "docs"));
122
+ const hasJust = await justAvailable(root);
123
+ return { hasOpenspec, hasDocs, hasJust };
124
+ }
125
+ async function detectLiveShape(root) {
126
+ return detect(root);
127
+ }
128
+
129
+ // src/cli.ts
130
+ import { Context } from "cordis";
131
+
137
132
  // src/core/server.ts
133
+ import http from "http";
134
+ import fs3 from "fs";
135
+ import path3 from "path";
136
+ import crypto from "crypto";
137
+ import { fileURLToPath } from "url";
138
138
  import { Service } from "cordis";
139
139
 
140
140
  // src/core/instance.ts
141
141
  import fs2 from "fs";
142
142
  import path2 from "path";
143
+ var CURRENT_VERSION = package_default.version;
143
144
  var RECORD_FILE = ".zdev/dashboard.json";
144
145
  var VERIFY_TIMEOUT_MS = 1500;
145
146
  var STOP_POLL_MS = 2e3;
@@ -169,7 +170,8 @@ function writeRecord(root, port) {
169
170
  pid: process.pid,
170
171
  port,
171
172
  root,
172
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
173
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
174
+ version: CURRENT_VERSION
173
175
  };
174
176
  if (old?.plugins && typeof old.plugins === "object") {
175
177
  rec.plugins = old.plugins;
@@ -269,20 +271,27 @@ async function fetchWithTimeout(url) {
269
271
  async function verifyRoot(port, root) {
270
272
  try {
271
273
  const res = await fetchWithTimeout(`http://127.0.0.1:${port}/__config`);
272
- if (res.status !== 200) return false;
274
+ if (res.status !== 200) return { reusable: false };
273
275
  const body = await res.json();
274
- if (typeof body.root !== "string") return false;
275
- return body.root === root;
276
+ if (typeof body.root !== "string") return { reusable: false };
277
+ return {
278
+ reusable: body.root === root,
279
+ version: typeof body.version === "string" ? body.version : void 0
280
+ };
276
281
  } catch {
277
- return false;
282
+ return { reusable: false };
278
283
  }
279
284
  }
280
- async function findReusable(root) {
285
+ async function findReusable(root, currentVersion) {
281
286
  const rec = readRecord(root);
282
287
  if (!rec) return null;
283
288
  if (!isAlive(rec.pid)) return null;
284
- if (!await verifyRoot(rec.port, root)) return null;
285
- return rec;
289
+ const v = await verifyRoot(rec.port, root);
290
+ if (!v.reusable) return null;
291
+ if (v.version && v.version !== currentVersion) {
292
+ return { status: "stale-version", record: rec, liveVersion: v.version };
293
+ }
294
+ return { status: "reuse", record: rec };
286
295
  }
287
296
  async function stopInstance(record) {
288
297
  try {
@@ -377,6 +386,25 @@ function json(res, data, status = 200) {
377
386
  res.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-cache" });
378
387
  res.end(JSON.stringify(data));
379
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
+ }
380
408
  function guarded(req, res, token) {
381
409
  if (req.headers["x-stop-token"] !== token) {
382
410
  res.writeHead(403);
@@ -476,10 +504,14 @@ var ServerService = class extends Service {
476
504
  writePluginsConfig(this.root, merged);
477
505
  }
478
506
  serveFile(filePath, req, res, injectHtml) {
507
+ const apiPath = (req.url || "").startsWith("/__");
479
508
  fs3.stat(filePath, (err, stat) => {
480
509
  if (err) {
481
- res.writeHead(404);
482
- return res.end("Not found");
510
+ if (apiPath) {
511
+ res.writeHead(404);
512
+ return res.end("Not found");
513
+ }
514
+ return sendErrorPage(res, 404);
483
515
  }
484
516
  const ext = path3.extname(filePath).toLowerCase();
485
517
  const ct = MIME[ext] ?? "application/octet-stream";
@@ -517,8 +549,11 @@ var ServerService = class extends Service {
517
549
  }
518
550
  fs3.readFile(filePath, (err2, data) => {
519
551
  if (err2) {
520
- res.writeHead(404);
521
- return res.end("Not found");
552
+ if (apiPath) {
553
+ res.writeHead(404);
554
+ return res.end("Not found");
555
+ }
556
+ return sendErrorPage(res, 404);
522
557
  }
523
558
  let body = data;
524
559
  if (injectHtml && ext === ".html") {
@@ -585,22 +620,26 @@ var ServerService = class extends Service {
585
620
  if (url !== "/") fp2 = path3.join(this.appDir, this.safeDecode(url));
586
621
  if (url.indexOf("/__app/") === 0) fp2 = path3.join(this.appDir, url.slice(7));
587
622
  if (fp2 !== this.appDir && fp2.indexOf(this.appDir + path3.sep) !== 0) {
588
- res.writeHead(403);
589
- return res.end("Forbidden");
623
+ return sendErrorPage(res, 403);
590
624
  }
591
625
  if (url === "/") fp2 = path3.join(this.appDir, "index.html");
592
626
  return this.serveFile(fp2, req, res, false);
593
627
  }
594
628
  const fp = path3.join(this.root, this.safeDecode(url));
595
629
  if (fp !== this.root && fp.indexOf(this.root + path3.sep) !== 0) {
596
- res.writeHead(403);
597
- return res.end("Forbidden");
630
+ if ((req.url || "").startsWith("/__")) {
631
+ res.writeHead(403);
632
+ return res.end("Forbidden");
633
+ }
634
+ return sendErrorPage(res, 403);
598
635
  }
599
636
  return this.serveFile(fp, req, res, true);
600
637
  } catch (e) {
601
638
  try {
602
- res.writeHead(400);
603
- res.end("bad request");
639
+ if ((req.url || "").startsWith("/__")) {
640
+ res.writeHead(400);
641
+ res.end("bad request");
642
+ } else sendErrorPage(res, 400);
604
643
  } catch {
605
644
  }
606
645
  console.error("[zdashboard] handler error", e);
@@ -1046,9 +1085,66 @@ var DashboardService = class extends Service3 {
1046
1085
  }
1047
1086
  };
1048
1087
 
1088
+ // src/plugins/just/index.ts
1089
+ import { execFile as execFile6 } from "child_process";
1090
+
1049
1091
  // src/server/just-runner.ts
1050
1092
  import { spawn, execFile as execFile5 } from "child_process";
1051
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
1052
1148
  var MAX_BUFFER = 1e3;
1053
1149
  var utf8Strict = new TextDecoder("utf-8", { fatal: true });
1054
1150
  function decodeLine(buf) {
@@ -1063,6 +1159,8 @@ var JustRunner = class {
1063
1159
  tasks = /* @__PURE__ */ new Map();
1064
1160
  clients = /* @__PURE__ */ new Set();
1065
1161
  recipesCache = null;
1162
+ /** recipe 参数清单进程内缓存(含失败空数组),避免反复 just --show 探测 */
1163
+ paramsCache = /* @__PURE__ */ new Map();
1066
1164
  constructor(cwd) {
1067
1165
  this.cwd = cwd;
1068
1166
  }
@@ -1092,6 +1190,24 @@ var JustRunner = class {
1092
1190
  });
1093
1191
  });
1094
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
+ }
1095
1211
  subscribe(fn) {
1096
1212
  this.clients.add(fn);
1097
1213
  for (const t of this.tasks.values()) {
@@ -1106,16 +1222,16 @@ var JustRunner = class {
1106
1222
  list() {
1107
1223
  return Array.from(this.tasks.values(), (t) => ({ recipe: t.recipe, state: t.state, code: t.code, startedAt: t.startedAt }));
1108
1224
  }
1109
- /** 启动 recipe:同名先停旧进程(重启语义),不影响其他任务;调用方须先用 recipes() 校验名字 */
1110
- start(recipe) {
1225
+ /** 启动 recipe:同名先停旧进程(重启语义),不影响其他任务;调用方须先用 recipes() 校验名字;
1226
+ * args 为可选参数表,spawn 数组 argv 追加 k=v(不经 shell,特殊字符安全) */
1227
+ start(recipe, args) {
1111
1228
  this.killOne(recipe);
1112
1229
  const task = { recipe, child: null, state: "running", code: null, startedAt: Date.now(), buffer: [], pending: Buffer.alloc(0) };
1113
1230
  this.tasks.set(recipe, task);
1114
1231
  this.emit({ type: "clear", recipe });
1115
1232
  this.emit({ type: "state", recipe, state: "running", code: null, startedAt: task.startedAt });
1116
- const child = spawn("just", [recipe], {
1233
+ const child = spawn("just", buildJustArgv(recipe, args), {
1117
1234
  cwd: this.cwd,
1118
- shell: true,
1119
1235
  env: {
1120
1236
  ...process.env,
1121
1237
  FORCE_COLOR: "1",
@@ -1156,10 +1272,22 @@ var JustRunner = class {
1156
1272
  this.emit({ type: "state", recipe, state: "exited", code: task.code, startedAt: task.startedAt, signal: task.signal });
1157
1273
  });
1158
1274
  }
1275
+ /** 行推送:行内 \r 再切分为多段独立行(进度条输出降级为分段渲染);
1276
+ * \r 空段丢弃,但整行全空仍推送一条空行(空行是日志内容,不静默丢弃) */
1159
1277
  pushLine(task, line) {
1160
- 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);
1161
1289
  if (task.buffer.length > MAX_BUFFER) task.buffer.shift();
1162
- this.emit({ type: "log", recipe: task.recipe, text: line });
1290
+ this.emit({ type: "log", recipe: task.recipe, text });
1163
1291
  }
1164
1292
  /** 停单个任务;不传 recipe 停全部 */
1165
1293
  stop(recipe) {
@@ -1296,12 +1424,21 @@ var apply3 = defineBuiltin({
1296
1424
  ctx.onDispose(() => runner.stop());
1297
1425
  ctx.route("/__just/recipes", async () => {
1298
1426
  try {
1299
- return await runner.recipes();
1427
+ return await runner.recipesWithParams();
1300
1428
  } catch {
1301
1429
  return [];
1302
1430
  }
1303
1431
  });
1304
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
+ });
1305
1442
  ctx.sse("/__just/logs", (res) => {
1306
1443
  const unsub = runner.subscribe((ev) => {
1307
1444
  try {
@@ -1317,8 +1454,11 @@ var apply3 = defineBuiltin({
1317
1454
  const handleAction = async (req, res, act) => {
1318
1455
  const body = await readBody(req);
1319
1456
  let recipe;
1457
+ let rawArgs;
1320
1458
  try {
1321
- recipe = JSON.parse(body || "{}").recipe;
1459
+ const parsed = JSON.parse(body || "{}");
1460
+ recipe = parsed.recipe;
1461
+ rawArgs = parsed.args;
1322
1462
  } catch {
1323
1463
  }
1324
1464
  if (act === "clear") {
@@ -1337,7 +1477,7 @@ var apply3 = defineBuiltin({
1337
1477
  res.end('{"error":"unknown recipe"}');
1338
1478
  return void 0;
1339
1479
  }
1340
- runner.start(recipe);
1480
+ runner.start(recipe, pickStringArgs(rawArgs));
1341
1481
  } else {
1342
1482
  runner.stop(recipe || void 0);
1343
1483
  }
@@ -1471,7 +1611,7 @@ var apply5 = defineBuiltin({
1471
1611
  // src/plugins/stats/index.ts
1472
1612
  import fs8 from "fs";
1473
1613
  import path8 from "path";
1474
- import { execFile as execFile6 } from "child_process";
1614
+ import { execFile as execFile7 } from "child_process";
1475
1615
 
1476
1616
  // src/plugins/stats/manifest.ts
1477
1617
  var manifest4 = {
@@ -1486,7 +1626,7 @@ var manifest4 = {
1486
1626
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", ".cache"]);
1487
1627
  function execFilePromise(file, args, opts) {
1488
1628
  return new Promise((resolve) => {
1489
- execFile6(file, args, opts, (err, stdout) => resolve(err ? "" : stdout));
1629
+ execFile7(file, args, opts, (err, stdout) => resolve(err ? "" : stdout));
1490
1630
  });
1491
1631
  }
1492
1632
  function scan(root) {
@@ -1645,17 +1785,25 @@ async function loadExternal(ctx, dir, root) {
1645
1785
  async function main() {
1646
1786
  const args = parseArgs();
1647
1787
  const root = path9.resolve(args.dir);
1648
- const existing = await findReusable(root);
1649
- if (existing && !args.restart) {
1788
+ const found = await findReusable(root, package_default.version);
1789
+ const existing = found?.record ?? null;
1790
+ const autoUpgrade = found?.status === "stale-version";
1791
+ if (existing && !args.restart && !autoUpgrade) {
1650
1792
  const u = `http://localhost:${existing.port}` + (args.page ? `#${args.page}` : "");
1651
1793
  if (args.open) openUrl(u);
1652
1794
  console.log(`[zdashboard] \u5DF2\u590D\u7528\u5B9E\u4F8B ${u}\uFF08--restart \u53EF\u5F3A\u5236\u91CD\u5F00\uFF09`);
1653
1795
  await new Promise((resolve) => process.stdout.write("", () => resolve()));
1654
1796
  process.exit(0);
1655
1797
  }
1656
- const oldRecord = existing && args.restart ? existing : null;
1798
+ const oldRecord = existing && (args.restart || autoUpgrade) ? existing : null;
1657
1799
  if (oldRecord) {
1658
- console.log(`[zdashboard] --restart\uFF1A\u505C\u6B62\u65E7\u5B9E\u4F8B pid=${oldRecord.pid}`);
1800
+ if (autoUpgrade) {
1801
+ console.log(
1802
+ `[zdashboard] \u68C0\u6D4B\u5230\u65E7\u7248\u672C\u5B9E\u4F8B v${found.liveVersion ?? "unknown"}\uFF0C\u81EA\u52A8\u5347\u7EA7\u5230 v${package_default.version}\uFF08\u505C\u6B62\u65E7\u5B9E\u4F8B pid=${oldRecord.pid}\uFF09`
1803
+ );
1804
+ } else {
1805
+ console.log(`[zdashboard] --restart\uFF1A\u505C\u6B62\u65E7\u5B9E\u4F8B pid=${oldRecord.pid}`);
1806
+ }
1659
1807
  await stopInstance(oldRecord);
1660
1808
  }
1661
1809
  const appDir = path9.resolve(__dirname2, "web");