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.
- package/dist/cli.js +216 -68
- package/dist/cli.js.map +1 -1
- package/dist/web/assets/{CodeViewer-yUtAi1hT.js → CodeViewer-B7R0ZVR6.js} +51 -51
- package/dist/web/assets/FileIcon-gTgtllQ_.js +1 -0
- package/dist/web/assets/FilterPills-BG7PY8Ts.js +1 -0
- package/dist/web/assets/Sidebar-BFf7prry.js +1 -0
- package/dist/web/assets/Sidebar-Bv_jdwYG.js +1 -0
- package/dist/web/assets/Workspace-B7_x75Fc.js +1 -0
- package/dist/web/assets/Workspace-BimPBKII.js +1 -0
- package/dist/web/assets/Workspace-D42j59Cc.js +5 -0
- package/dist/web/assets/Workspace-DXhpVGbA.js +1 -0
- package/dist/web/assets/fetchJson-D-dWeu1j.js +1 -0
- package/dist/web/assets/index-B4hHecQs.js +316 -0
- package/dist/web/assets/index-Bfe1AHR3.css +1 -0
- package/dist/web/assets/usePluginData-Dhhi3YQs.js +1 -0
- package/dist/web/assets/vendor-react-CDaMBvJU.js +40 -0
- package/dist/web/assets/vendor-ui-dpI0b3Ao.js +42 -0
- package/dist/web/assets/web-B3RuriWk.js +2 -0
- package/dist/web/assets/web-BJPSRqva.js +2 -0
- package/dist/web/assets/web-BugMZG5x.js +2 -0
- package/dist/web/assets/web-D-lPF0OX.js +2 -0
- package/dist/web/index.html +25 -2
- package/package.json +1 -1
- package/dist/web/assets/FileIcon-DCL9OD6I.js +0 -1
- package/dist/web/assets/FilterPills-BD2vsIx2.js +0 -1
- package/dist/web/assets/Sidebar-CsmNxVjL.js +0 -1
- package/dist/web/assets/Sidebar-DxeR1Shk.js +0 -1
- package/dist/web/assets/Workspace-BGj3n613.js +0 -1
- package/dist/web/assets/Workspace-CUepnOlM.js +0 -1
- package/dist/web/assets/Workspace-D0rSzljF.js +0 -1
- package/dist/web/assets/Workspace-DUK4Lm-v.js +0 -5
- package/dist/web/assets/index--t_ijYil.css +0 -1
- package/dist/web/assets/index-veTBv7Qv.js +0 -7816
- package/dist/web/assets/usePluginData-mjHueOjf.js +0 -1
- package/dist/web/assets/web-BODcUPmi.js +0 -2
- package/dist/web/assets/web-CsCyTuDw.js +0 -2
- package/dist/web/assets/web-D6VGQFbH.js +0 -2
- 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.
|
|
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
|
|
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
|
-
|
|
285
|
-
return
|
|
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
|
-
|
|
482
|
-
|
|
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
|
-
|
|
521
|
-
|
|
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
|
|
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
|
-
|
|
597
|
-
|
|
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
|
-
|
|
603
|
-
|
|
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
|
-
|
|
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",
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
1649
|
-
|
|
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
|
-
|
|
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");
|