zdashboard 2.2.0 → 2.3.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 +309 -211
- package/dist/cli.js.map +1 -1
- package/dist/web/assets/{web-BnZ_68eg.js → CodeViewer-D8YqzmwB.js} +8 -23
- package/dist/web/assets/FilterPills-A_fz1nby.js +1 -0
- package/dist/web/assets/{katex.min-D_D1u30F.js → MdViewer-P4wmRzy_.js} +24 -24
- package/dist/web/assets/ProgressBar-B4U41etC.js +1 -0
- package/dist/web/assets/Workspace-nm0YyxF7.js +1 -0
- package/dist/web/assets/badge-C92Y2FRG.js +1 -0
- package/dist/web/assets/file-text-BHzQwEJK.js +6 -0
- package/dist/web/assets/index-ByI0Pghw.css +1 -0
- package/dist/web/assets/index-CNPKNdvp.js +111 -0
- package/dist/web/assets/{index-6FGn10zY.js → index-C_wTjWH0.js} +1 -1
- package/dist/web/assets/web-5tq6UJuk.js +2 -0
- package/dist/web/assets/web-DgYHvMu9.js +16 -0
- package/dist/web/assets/web-DhyfyLB-.js +11 -0
- package/dist/web/assets/web-JxMZZReP.js +2 -0
- package/dist/web/assets/web-ZQ0eDR1y.js +31 -0
- package/dist/web/assets/web-vTYFh6Bo.js +25 -0
- package/dist/web/assets/web-vbs6AnjC.js +26 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/MdViewer-qvXbO8IA.js +0 -1
- package/dist/web/assets/Workspace-D96dCoOA.js +0 -1
- package/dist/web/assets/git-branch-DJLd5kDE.js +0 -11
- package/dist/web/assets/index-15cLfCw4.js +0 -90
- package/dist/web/assets/index-CYqm3unP.css +0 -1
- package/dist/web/assets/web-BUNysAkV.js +0 -7
- package/dist/web/assets/web-BeuJXCg2.js +0 -26
- package/dist/web/assets/web-C-O8qmKu.js +0 -31
- package/dist/web/assets/web-CJq1r_vk.js +0 -11
- package/dist/web/assets/web-DP_CBpB2.js +0 -2
- package/dist/web/assets/web-Xpqif6fn.js +0 -32
- /package/dist/web/assets/{katex-BTcE7LSd.css → MdViewer-BTcE7LSd.css} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import
|
|
4
|
+
import path11 from "path";
|
|
5
5
|
import fs11 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";
|
|
8
9
|
|
|
9
10
|
// src/server/detect.ts
|
|
10
11
|
import fs2 from "fs";
|
|
@@ -14,6 +15,7 @@ import { execFile } from "child_process";
|
|
|
14
15
|
// src/server/bugs.ts
|
|
15
16
|
import fs from "fs";
|
|
16
17
|
import path from "path";
|
|
18
|
+
import YAML from "yaml";
|
|
17
19
|
|
|
18
20
|
// src/server/api/fetch.ts
|
|
19
21
|
import ky from "ky";
|
|
@@ -61,20 +63,22 @@ function loadConfig(root) {
|
|
|
61
63
|
for (const rel of BUGS_CONFIG_CANDIDATES) {
|
|
62
64
|
const file = path.join(root, rel);
|
|
63
65
|
if (!fs.existsSync(file)) continue;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
try {
|
|
67
|
+
const kv = YAML.parse(fs.readFileSync(file, "utf8"));
|
|
68
|
+
if (!kv || typeof kv !== "object") return null;
|
|
69
|
+
const record = kv;
|
|
70
|
+
const product = Number(record.product);
|
|
71
|
+
if (!record.url || !product) return null;
|
|
72
|
+
return {
|
|
73
|
+
url: String(record.url).replace(/\/+$/, ""),
|
|
74
|
+
account: record.account ?? "",
|
|
75
|
+
password: record.password,
|
|
76
|
+
token: record.token,
|
|
77
|
+
product
|
|
78
|
+
};
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
68
81
|
}
|
|
69
|
-
const product = Number(kv.product);
|
|
70
|
-
if (!kv.url || !product) return null;
|
|
71
|
-
return {
|
|
72
|
-
url: kv.url.replace(/\/+$/, ""),
|
|
73
|
-
account: kv.account ?? "",
|
|
74
|
-
password: kv.password,
|
|
75
|
-
token: kv.token,
|
|
76
|
-
product
|
|
77
|
-
};
|
|
78
82
|
}
|
|
79
83
|
return null;
|
|
80
84
|
}
|
|
@@ -162,7 +166,7 @@ import { fileURLToPath } from "url";
|
|
|
162
166
|
// package.json
|
|
163
167
|
var package_default = {
|
|
164
168
|
name: "zdashboard",
|
|
165
|
-
version: "2.
|
|
169
|
+
version: "2.3.0",
|
|
166
170
|
description: "ZCode skill dashboard platform \u2014 pluggable viewers for zdesign/zview/zreview/zgoal",
|
|
167
171
|
type: "module",
|
|
168
172
|
bin: {
|
|
@@ -344,13 +348,18 @@ async function stopInstance(record) {
|
|
|
344
348
|
}
|
|
345
349
|
|
|
346
350
|
// src/core/open-url.ts
|
|
347
|
-
import { exec } from "child_process";
|
|
351
|
+
import { exec, execFile as execFile2 } from "child_process";
|
|
348
352
|
function openUrl(url) {
|
|
349
353
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
350
|
-
|
|
354
|
+
if (process.platform === "win32") {
|
|
355
|
+
exec(`start "" "${url}"`);
|
|
356
|
+
} else {
|
|
357
|
+
execFile2(cmd, [url]);
|
|
358
|
+
}
|
|
351
359
|
}
|
|
352
360
|
|
|
353
361
|
// src/core/server.ts
|
|
362
|
+
import { execFile as execFile3 } from "child_process";
|
|
354
363
|
var VERSION = package_default.version;
|
|
355
364
|
var __dirname = path4.dirname(fileURLToPath(import.meta.url));
|
|
356
365
|
var MIME = {
|
|
@@ -367,6 +376,7 @@ var MIME = {
|
|
|
367
376
|
".jpeg": "image/jpeg",
|
|
368
377
|
".gif": "image/gif",
|
|
369
378
|
".webp": "image/webp",
|
|
379
|
+
".avif": "image/avif",
|
|
370
380
|
".md": "text/markdown; charset=utf-8",
|
|
371
381
|
".txt": "text/plain; charset=utf-8",
|
|
372
382
|
".sql": "text/plain; charset=utf-8",
|
|
@@ -396,6 +406,11 @@ var MIME = {
|
|
|
396
406
|
".env": "text/plain; charset=utf-8",
|
|
397
407
|
".gitignore": "text/plain; charset=utf-8",
|
|
398
408
|
".dockerfile": "text/plain; charset=utf-8",
|
|
409
|
+
".mp4": "video/mp4",
|
|
410
|
+
".webm": "video/webm",
|
|
411
|
+
".mov": "video/quicktime",
|
|
412
|
+
".m4a": "audio/mp4",
|
|
413
|
+
".aac": "audio/aac",
|
|
399
414
|
".woff": "font/woff",
|
|
400
415
|
".woff2": "font/woff2",
|
|
401
416
|
".ttf": "font/woff",
|
|
@@ -427,9 +442,10 @@ var ServerService = class extends Service {
|
|
|
427
442
|
this.dataDir = config.dataDir;
|
|
428
443
|
this.onListen = config.onListen;
|
|
429
444
|
ctx.effect(() => () => this.dispose());
|
|
445
|
+
this.refreshGitInfo();
|
|
430
446
|
this.route("/__config", (_req, res) => {
|
|
431
447
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-cache" });
|
|
432
|
-
res.end(JSON.stringify({ stopToken: this.stopToken, version: VERSION, root: this.root }));
|
|
448
|
+
res.end(JSON.stringify({ stopToken: this.stopToken, version: VERSION, root: this.root, ...this.gitInfo }));
|
|
433
449
|
});
|
|
434
450
|
this.route("/__stop", async (req, res) => {
|
|
435
451
|
if (req.headers["x-stop-token"] !== this.stopToken) {
|
|
@@ -443,36 +459,74 @@ var ServerService = class extends Service {
|
|
|
443
459
|
});
|
|
444
460
|
this.start(config.port);
|
|
445
461
|
}
|
|
446
|
-
route(
|
|
447
|
-
this.routes.set(
|
|
448
|
-
this.ctx.effect(() => () => this.routes.delete(
|
|
462
|
+
route(path12, handler) {
|
|
463
|
+
this.routes.set(path12, handler);
|
|
464
|
+
this.ctx.effect(() => () => this.routes.delete(path12));
|
|
449
465
|
}
|
|
450
|
-
sse(
|
|
451
|
-
this.sses.set(
|
|
452
|
-
this.ctx.effect(() => () => this.sses.delete(
|
|
466
|
+
sse(path12, onConnect) {
|
|
467
|
+
this.sses.set(path12, onConnect);
|
|
468
|
+
this.ctx.effect(() => () => this.sses.delete(path12));
|
|
453
469
|
}
|
|
454
470
|
static(prefix, dir) {
|
|
455
471
|
this.prefixStatic.set(prefix, dir);
|
|
456
472
|
this.ctx.effect(() => () => this.prefixStatic.delete(prefix));
|
|
457
473
|
}
|
|
458
|
-
serveFile(filePath, res, injectHtml) {
|
|
459
|
-
fs4.
|
|
474
|
+
serveFile(filePath, req, res, injectHtml) {
|
|
475
|
+
fs4.stat(filePath, (err, stat) => {
|
|
460
476
|
if (err) {
|
|
461
477
|
res.writeHead(404);
|
|
462
478
|
return res.end("Not found");
|
|
463
479
|
}
|
|
464
480
|
const ext = path4.extname(filePath).toLowerCase();
|
|
465
481
|
const ct = MIME[ext] ?? "application/octet-stream";
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
482
|
+
const rangeHeader = req.headers["range"];
|
|
483
|
+
let range = null;
|
|
484
|
+
if (rangeHeader) {
|
|
485
|
+
const m = rangeHeader.match(/^bytes=(\d+)-(\d*)$/);
|
|
486
|
+
if (m) {
|
|
487
|
+
const start = Number(m[1]);
|
|
488
|
+
const end = m[2] !== "" ? Number(m[2]) : stat.size - 1;
|
|
489
|
+
if (!Number.isNaN(start) && !Number.isNaN(end) && start <= end && start < stat.size) {
|
|
490
|
+
range = { start, end: Math.min(end, stat.size - 1) };
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (range) {
|
|
495
|
+
const { start, end } = range;
|
|
496
|
+
const chunkSize = end - start + 1;
|
|
497
|
+
res.writeHead(206, {
|
|
498
|
+
"Content-Type": ct,
|
|
499
|
+
"Content-Length": String(chunkSize),
|
|
500
|
+
"Content-Range": `bytes ${start}-${end}/${stat.size}`,
|
|
501
|
+
"Accept-Ranges": "bytes",
|
|
502
|
+
"Cache-Control": "no-cache"
|
|
503
|
+
});
|
|
504
|
+
const stream = fs4.createReadStream(filePath, { start, end });
|
|
505
|
+
stream.on("error", () => {
|
|
506
|
+
try {
|
|
507
|
+
res.destroy();
|
|
508
|
+
} catch {
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
stream.pipe(res);
|
|
512
|
+
return;
|
|
470
513
|
}
|
|
471
|
-
|
|
472
|
-
|
|
514
|
+
fs4.readFile(filePath, (err2, data) => {
|
|
515
|
+
if (err2) {
|
|
516
|
+
res.writeHead(404);
|
|
517
|
+
return res.end("Not found");
|
|
518
|
+
}
|
|
519
|
+
let body = data;
|
|
520
|
+
if (injectHtml && ext === ".html") {
|
|
521
|
+
const s = data.toString("utf8");
|
|
522
|
+
body = Buffer.from(s.indexOf("</body>") >= 0 ? s.replace("</body>", INJECT + "</body>") : s + INJECT);
|
|
523
|
+
}
|
|
524
|
+
res.writeHead(200, { "Content-Type": ct, "Accept-Ranges": "bytes", "Cache-Control": "no-cache" });
|
|
525
|
+
res.end(body);
|
|
526
|
+
});
|
|
473
527
|
});
|
|
474
528
|
}
|
|
475
|
-
servePrefix(url, res) {
|
|
529
|
+
servePrefix(url, req, res) {
|
|
476
530
|
for (const [prefix, dir] of this.prefixStatic) {
|
|
477
531
|
if (url.indexOf(prefix) === 0) {
|
|
478
532
|
let rel = this.safeDecode(url.slice(prefix.length));
|
|
@@ -482,7 +536,7 @@ var ServerService = class extends Service {
|
|
|
482
536
|
res.writeHead(403);
|
|
483
537
|
return res.end("Forbidden");
|
|
484
538
|
}
|
|
485
|
-
this.serveFile(fp, res, true);
|
|
539
|
+
this.serveFile(fp, req, res, true);
|
|
486
540
|
return true;
|
|
487
541
|
}
|
|
488
542
|
}
|
|
@@ -514,7 +568,7 @@ var ServerService = class extends Service {
|
|
|
514
568
|
});
|
|
515
569
|
return;
|
|
516
570
|
}
|
|
517
|
-
if (this.servePrefix(url, res)) return;
|
|
571
|
+
if (this.servePrefix(url, req, res)) return;
|
|
518
572
|
if (url === "/" || url.indexOf("/__app/") === 0 || url.indexOf("/assets/") === 0) {
|
|
519
573
|
let fp2 = this.appDir;
|
|
520
574
|
if (url !== "/") fp2 = path4.join(this.appDir, this.safeDecode(url));
|
|
@@ -524,14 +578,14 @@ var ServerService = class extends Service {
|
|
|
524
578
|
return res.end("Forbidden");
|
|
525
579
|
}
|
|
526
580
|
if (url === "/") fp2 = path4.join(this.appDir, "index.html");
|
|
527
|
-
return this.serveFile(fp2, res, false);
|
|
581
|
+
return this.serveFile(fp2, req, res, false);
|
|
528
582
|
}
|
|
529
583
|
const fp = path4.join(this.root, this.safeDecode(url));
|
|
530
584
|
if (fp !== this.root && fp.indexOf(this.root + path4.sep) !== 0) {
|
|
531
585
|
res.writeHead(403);
|
|
532
586
|
return res.end("Forbidden");
|
|
533
587
|
}
|
|
534
|
-
return this.serveFile(fp, res, true);
|
|
588
|
+
return this.serveFile(fp, req, res, true);
|
|
535
589
|
} catch (e) {
|
|
536
590
|
try {
|
|
537
591
|
res.writeHead(400);
|
|
@@ -562,6 +616,21 @@ var ServerService = class extends Service {
|
|
|
562
616
|
this.onListen?.(port);
|
|
563
617
|
});
|
|
564
618
|
}
|
|
619
|
+
/** 项目 git 信息(分支/脏文件数),启动探测一次,文件变更时刷新 */
|
|
620
|
+
gitInfo = {};
|
|
621
|
+
refreshGitInfo() {
|
|
622
|
+
const run = (args) => new Promise((resolve) => {
|
|
623
|
+
execFile3("git", args, { cwd: this.root, timeout: 3e3 }, (err, stdout) => resolve(err ? "" : stdout));
|
|
624
|
+
});
|
|
625
|
+
void (async () => {
|
|
626
|
+
const [branch, status] = await Promise.all([
|
|
627
|
+
run(["rev-parse", "--abbrev-ref", "HEAD"]),
|
|
628
|
+
run(["status", "--porcelain"])
|
|
629
|
+
]);
|
|
630
|
+
const b = branch.trim();
|
|
631
|
+
this.gitInfo = b ? { branch: b, dirty: status ? status.split("\n").filter(Boolean).length : 0 } : {};
|
|
632
|
+
})();
|
|
633
|
+
}
|
|
565
634
|
stop() {
|
|
566
635
|
if (this.server) {
|
|
567
636
|
try {
|
|
@@ -615,6 +684,7 @@ var ReloadService = class extends Service2 {
|
|
|
615
684
|
this.watcher = fs5.watch(config.root, { recursive: true }, () => {
|
|
616
685
|
if (this.timer) clearTimeout(this.timer);
|
|
617
686
|
this.timer = setTimeout(() => {
|
|
687
|
+
this.ctx.server.refreshGitInfo();
|
|
618
688
|
this.broadcast("reload");
|
|
619
689
|
this.broadcast("files");
|
|
620
690
|
}, WATCH_DEBOUNCE_MS);
|
|
@@ -655,73 +725,128 @@ data: ${JSON.stringify(data == null ? "" : data)}
|
|
|
655
725
|
};
|
|
656
726
|
|
|
657
727
|
// src/server/spec-scan.ts
|
|
728
|
+
import fs7 from "fs";
|
|
729
|
+
import path6 from "path";
|
|
730
|
+
|
|
731
|
+
// src/server/walk.ts
|
|
658
732
|
import fs6 from "fs";
|
|
659
733
|
import path5 from "path";
|
|
660
|
-
function
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
ents
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
734
|
+
function walkDir(root, opts = {}) {
|
|
735
|
+
const { skip = /* @__PURE__ */ new Set(), maxDepth, onFile, onDir } = opts;
|
|
736
|
+
function walk(dir, rel, depth) {
|
|
737
|
+
if (typeof maxDepth === "number" && depth > maxDepth) return;
|
|
738
|
+
let ents;
|
|
739
|
+
try {
|
|
740
|
+
ents = fs6.readdirSync(dir, { withFileTypes: true });
|
|
741
|
+
} catch {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
for (const ent of ents) {
|
|
745
|
+
if (ent.name.startsWith(".") || skip.has(ent.name)) continue;
|
|
746
|
+
const abs = path5.join(dir, ent.name);
|
|
747
|
+
const r = rel ? `${rel}/${ent.name}` : ent.name;
|
|
748
|
+
if (ent.isDirectory()) {
|
|
749
|
+
onDir?.(abs, r);
|
|
750
|
+
walk(abs, r, depth + 1);
|
|
751
|
+
} else if (onFile) {
|
|
752
|
+
onFile(abs, r);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
walk(root, "", 0);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// src/server/spec-scan.ts
|
|
760
|
+
function buildTree(paths) {
|
|
761
|
+
const root = [];
|
|
762
|
+
const map = /* @__PURE__ */ new Map();
|
|
763
|
+
for (const p of paths) {
|
|
764
|
+
const parts = p.split("/");
|
|
765
|
+
let current = "";
|
|
766
|
+
let parent = root;
|
|
767
|
+
for (let i = 0; i < parts.length; i++) {
|
|
768
|
+
const part = parts[i];
|
|
769
|
+
current = current ? `${current}/${part}` : part;
|
|
770
|
+
const isLast = i === parts.length - 1;
|
|
771
|
+
if (!map.has(current)) {
|
|
772
|
+
const node = {
|
|
773
|
+
name: part,
|
|
774
|
+
kind: isLast ? "file" : "dir",
|
|
775
|
+
...isLast ? { path: p } : { children: [] }
|
|
776
|
+
};
|
|
777
|
+
map.set(current, node);
|
|
778
|
+
parent.push(node);
|
|
779
|
+
}
|
|
780
|
+
if (!isLast) {
|
|
781
|
+
parent = map.get(current).children;
|
|
782
|
+
}
|
|
676
783
|
}
|
|
677
784
|
}
|
|
678
|
-
|
|
679
|
-
|
|
785
|
+
const sortNodes = (nodes) => {
|
|
786
|
+
nodes.sort((a, b) => a.kind === b.kind ? a.name.localeCompare(b.name) : a.kind === "dir" ? -1 : 1);
|
|
787
|
+
for (const n of nodes) {
|
|
788
|
+
if (n.children) sortNodes(n.children);
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
sortNodes(root);
|
|
792
|
+
return root;
|
|
680
793
|
}
|
|
681
794
|
function scanTree(root, hasOpenspec, hasDocs) {
|
|
682
795
|
const tree = [];
|
|
683
|
-
if (hasOpenspec &&
|
|
684
|
-
const changesDir =
|
|
796
|
+
if (hasOpenspec && fs7.existsSync(path6.join(root, "openspec", "changes"))) {
|
|
797
|
+
const changesDir = path6.join(root, "openspec", "changes");
|
|
685
798
|
const active = [];
|
|
686
799
|
const archived = [];
|
|
687
|
-
for (const ent of
|
|
800
|
+
for (const ent of fs7.readdirSync(changesDir, { withFileTypes: true })) {
|
|
688
801
|
if (!ent.isDirectory() || ent.name.startsWith(".") || ent.name === "archive") continue;
|
|
689
|
-
|
|
802
|
+
const relBase = `openspec/changes/${ent.name}`;
|
|
803
|
+
const paths = [];
|
|
804
|
+
walkDir(path6.join(changesDir, ent.name), { maxDepth: 4, onFile: (_, rel) => paths.push(rel ? `${relBase}/${rel}` : relBase) });
|
|
805
|
+
const children = buildTree(paths).map((n) => ({ ...n, path: n.path ? `${relBase}/${n.path}` : void 0 }));
|
|
806
|
+
active.push({ name: ent.name, kind: "dir", children });
|
|
690
807
|
}
|
|
691
808
|
active.sort((a, b) => a.name.localeCompare(b.name));
|
|
692
|
-
const archiveDir =
|
|
693
|
-
if (
|
|
694
|
-
for (const ent of
|
|
809
|
+
const archiveDir = path6.join(changesDir, "archive");
|
|
810
|
+
if (fs7.existsSync(archiveDir)) {
|
|
811
|
+
for (const ent of fs7.readdirSync(archiveDir, { withFileTypes: true })) {
|
|
695
812
|
if (!ent.isDirectory() || ent.name.startsWith(".")) continue;
|
|
696
|
-
|
|
813
|
+
const relBase = `openspec/changes/archive/${ent.name}`;
|
|
814
|
+
const paths = [];
|
|
815
|
+
walkDir(path6.join(archiveDir, ent.name), { maxDepth: 4, onFile: (_, rel) => paths.push(rel ? `${relBase}/${rel}` : relBase) });
|
|
816
|
+
const children = buildTree(paths).map((n) => ({ ...n, path: n.path ? `${relBase}/${n.path}` : void 0 }));
|
|
817
|
+
archived.push({ name: ent.name, kind: "dir", children });
|
|
697
818
|
}
|
|
698
819
|
archived.sort((a, b) => b.name.localeCompare(a.name));
|
|
699
820
|
}
|
|
700
|
-
if (active.length) tree.push({ name:
|
|
701
|
-
if (archived.length) tree.push({ name:
|
|
702
|
-
const specsDir =
|
|
703
|
-
if (
|
|
704
|
-
const
|
|
821
|
+
if (active.length) tree.push({ name: `changes (${active.length})`, kind: "dir", children: active });
|
|
822
|
+
if (archived.length) tree.push({ name: `archive (${archived.length})`, kind: "dir", defaultCollapsed: true, children: archived });
|
|
823
|
+
const specsDir = path6.join(root, "openspec", "specs");
|
|
824
|
+
if (fs7.existsSync(specsDir)) {
|
|
825
|
+
const paths = [];
|
|
826
|
+
walkDir(specsDir, { maxDepth: 4, onFile: (_, rel) => paths.push(`openspec/specs/${rel}`) });
|
|
827
|
+
const specs = buildTree(paths).map((n) => ({ ...n, path: n.path ? `openspec/specs/${n.path}` : void 0 }));
|
|
705
828
|
if (specs.length) tree.push({ name: "specs", kind: "dir", children: specs });
|
|
706
829
|
}
|
|
707
830
|
}
|
|
708
|
-
if (hasDocs &&
|
|
709
|
-
const
|
|
831
|
+
if (hasDocs && fs7.existsSync(path6.join(root, "docs"))) {
|
|
832
|
+
const paths = [];
|
|
833
|
+
walkDir(path6.join(root, "docs"), { maxDepth: 4, onFile: (_, rel) => paths.push(`docs/${rel}`) });
|
|
834
|
+
const docs = buildTree(paths).map((n) => ({ ...n, path: n.path ? `docs/${n.path}` : void 0 }));
|
|
710
835
|
if (docs.length) tree.push({ name: "docs", kind: "dir", children: docs });
|
|
711
836
|
}
|
|
712
837
|
const skip = /* @__PURE__ */ new Set(["openspec", "docs", "node_modules", ".git", "dist", "test-server"]);
|
|
713
838
|
const etc = [];
|
|
714
839
|
try {
|
|
715
|
-
for (const ent of
|
|
840
|
+
for (const ent of fs7.readdirSync(root, { withFileTypes: true })) {
|
|
716
841
|
if (ent.name.startsWith(".") || skip.has(ent.name)) continue;
|
|
717
|
-
const ext =
|
|
842
|
+
const ext = path6.extname(ent.name).toLowerCase();
|
|
718
843
|
if (ent.isFile() && (ext === ".md" || ext === ".markdown")) etc.push({ name: ent.name, kind: "file", path: ent.name });
|
|
719
844
|
}
|
|
720
845
|
} catch (e) {
|
|
721
846
|
console.error("[zdashboard] scan root etc failed:", e);
|
|
722
847
|
}
|
|
723
848
|
etc.sort((a, b) => a.name.localeCompare(b.name));
|
|
724
|
-
if (etc.length) tree.push({ name:
|
|
849
|
+
if (etc.length) tree.push({ name: `other (${etc.length})`, kind: "dir", children: etc });
|
|
725
850
|
return tree;
|
|
726
851
|
}
|
|
727
852
|
|
|
@@ -755,12 +880,12 @@ var apply = {
|
|
|
755
880
|
};
|
|
756
881
|
|
|
757
882
|
// src/core/worktrees.ts
|
|
758
|
-
import { execFile as
|
|
883
|
+
import { execFile as execFile4 } from "child_process";
|
|
759
884
|
var GIT_TIMEOUT_MS = 5e3;
|
|
760
885
|
var ZWORKTREE_SEGMENT = ".zworktree/";
|
|
761
886
|
function runGit(args, cwd) {
|
|
762
887
|
return new Promise((resolve) => {
|
|
763
|
-
|
|
888
|
+
execFile4("git", args, { cwd, timeout: GIT_TIMEOUT_MS }, (err, stdout) => {
|
|
764
889
|
if (err) return resolve("");
|
|
765
890
|
resolve(stdout);
|
|
766
891
|
});
|
|
@@ -858,7 +983,7 @@ var DashboardService = class extends Service3 {
|
|
|
858
983
|
};
|
|
859
984
|
|
|
860
985
|
// src/server/just-runner.ts
|
|
861
|
-
import { spawn, execFile as
|
|
986
|
+
import { spawn, execFile as execFile5 } from "child_process";
|
|
862
987
|
var MAX_BUFFER = 1e3;
|
|
863
988
|
var JustRunner = class {
|
|
864
989
|
cwd;
|
|
@@ -871,7 +996,7 @@ var JustRunner = class {
|
|
|
871
996
|
recipes() {
|
|
872
997
|
if (this.recipesCache) return Promise.resolve(this.recipesCache);
|
|
873
998
|
return new Promise((resolve) => {
|
|
874
|
-
|
|
999
|
+
execFile5("just", ["--list", "--unsorted"], { cwd: this.cwd, maxBuffer: 1 << 20, timeout: 8e3 }, (err, stdout) => {
|
|
875
1000
|
if (err) {
|
|
876
1001
|
resolve([]);
|
|
877
1002
|
return;
|
|
@@ -996,6 +1121,18 @@ var JustRunner = class {
|
|
|
996
1121
|
}
|
|
997
1122
|
};
|
|
998
1123
|
|
|
1124
|
+
// src/core/read-body.ts
|
|
1125
|
+
async function readBody(req) {
|
|
1126
|
+
return new Promise((resolve) => {
|
|
1127
|
+
let data = "";
|
|
1128
|
+
req.on("data", (chunk) => {
|
|
1129
|
+
data += chunk;
|
|
1130
|
+
});
|
|
1131
|
+
req.on("end", () => resolve(data));
|
|
1132
|
+
req.on("error", () => resolve(""));
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
|
|
999
1136
|
// src/plugins/just/index.ts
|
|
1000
1137
|
var apply3 = {
|
|
1001
1138
|
inject: ["server", "dashboard"],
|
|
@@ -1077,16 +1214,6 @@ var apply3 = {
|
|
|
1077
1214
|
});
|
|
1078
1215
|
}
|
|
1079
1216
|
};
|
|
1080
|
-
async function readBody(req) {
|
|
1081
|
-
return new Promise((resolve) => {
|
|
1082
|
-
let data = "";
|
|
1083
|
-
req.on("data", (c) => {
|
|
1084
|
-
data += c;
|
|
1085
|
-
});
|
|
1086
|
-
req.on("end", () => resolve(data));
|
|
1087
|
-
req.on("error", () => resolve(""));
|
|
1088
|
-
});
|
|
1089
|
-
}
|
|
1090
1217
|
|
|
1091
1218
|
// src/plugins/bugs/index.ts
|
|
1092
1219
|
var apply4 = {
|
|
@@ -1110,9 +1237,9 @@ var apply4 = {
|
|
|
1110
1237
|
};
|
|
1111
1238
|
|
|
1112
1239
|
// src/server/review-store.ts
|
|
1113
|
-
import
|
|
1114
|
-
import
|
|
1115
|
-
import
|
|
1240
|
+
import fs8 from "fs";
|
|
1241
|
+
import path7 from "path";
|
|
1242
|
+
import YAML2 from "yaml";
|
|
1116
1243
|
var REVIEW_CANDIDATES = [".zdev/review.yaml", "review.yaml"];
|
|
1117
1244
|
var ReviewStore = class {
|
|
1118
1245
|
root;
|
|
@@ -1121,15 +1248,15 @@ var ReviewStore = class {
|
|
|
1121
1248
|
constructor(root, onChange) {
|
|
1122
1249
|
this.root = root;
|
|
1123
1250
|
this.onChange = onChange;
|
|
1124
|
-
const existing = REVIEW_CANDIDATES.find((rel) =>
|
|
1125
|
-
this.file =
|
|
1251
|
+
const existing = REVIEW_CANDIDATES.find((rel) => fs8.existsSync(path7.join(root, rel)));
|
|
1252
|
+
this.file = path7.join(root, existing ?? REVIEW_CANDIDATES[0]);
|
|
1126
1253
|
}
|
|
1127
1254
|
exists() {
|
|
1128
|
-
return
|
|
1255
|
+
return fs8.existsSync(this.file);
|
|
1129
1256
|
}
|
|
1130
1257
|
read() {
|
|
1131
1258
|
try {
|
|
1132
|
-
const parsed =
|
|
1259
|
+
const parsed = YAML2.parse(fs8.readFileSync(this.file, "utf8"));
|
|
1133
1260
|
if (!parsed || !Array.isArray(parsed.items)) return { status: "draft", items: [] };
|
|
1134
1261
|
return parsed;
|
|
1135
1262
|
} catch {
|
|
@@ -1137,8 +1264,8 @@ var ReviewStore = class {
|
|
|
1137
1264
|
}
|
|
1138
1265
|
}
|
|
1139
1266
|
write(data) {
|
|
1140
|
-
|
|
1141
|
-
|
|
1267
|
+
fs8.mkdirSync(path7.dirname(this.file), { recursive: true });
|
|
1268
|
+
fs8.writeFileSync(this.file, YAML2.stringify(data), "utf8");
|
|
1142
1269
|
this.onChange?.();
|
|
1143
1270
|
}
|
|
1144
1271
|
updateItem(id, patch) {
|
|
@@ -1162,9 +1289,9 @@ var ReviewStore = class {
|
|
|
1162
1289
|
}
|
|
1163
1290
|
docs() {
|
|
1164
1291
|
try {
|
|
1165
|
-
const zdevDir =
|
|
1166
|
-
if (!
|
|
1167
|
-
return
|
|
1292
|
+
const zdevDir = path7.join(this.root, ".zdev");
|
|
1293
|
+
if (!fs8.existsSync(zdevDir) || !fs8.statSync(zdevDir).isDirectory()) return [];
|
|
1294
|
+
return fs8.readdirSync(zdevDir).filter((f) => /\.(md|markdown)$/i.test(f) && fs8.statSync(path7.join(zdevDir, f)).isFile()).sort();
|
|
1168
1295
|
} catch {
|
|
1169
1296
|
return [];
|
|
1170
1297
|
}
|
|
@@ -1193,7 +1320,7 @@ var apply5 = {
|
|
|
1193
1320
|
return;
|
|
1194
1321
|
}
|
|
1195
1322
|
try {
|
|
1196
|
-
const body = JSON.parse(await
|
|
1323
|
+
const body = JSON.parse(await readBody(req) || "{}");
|
|
1197
1324
|
const data = reviewStore.updateItem(body.id, { answer: body.answer, state: body.state });
|
|
1198
1325
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-cache" });
|
|
1199
1326
|
res.end(JSON.stringify(data));
|
|
@@ -1209,7 +1336,7 @@ var apply5 = {
|
|
|
1209
1336
|
return;
|
|
1210
1337
|
}
|
|
1211
1338
|
try {
|
|
1212
|
-
const body = JSON.parse(await
|
|
1339
|
+
const body = JSON.parse(await readBody(req) || "{}");
|
|
1213
1340
|
const data = reviewStore.setStatus(body.status);
|
|
1214
1341
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-cache" });
|
|
1215
1342
|
res.end(JSON.stringify(data));
|
|
@@ -1225,36 +1352,37 @@ var apply5 = {
|
|
|
1225
1352
|
});
|
|
1226
1353
|
}
|
|
1227
1354
|
};
|
|
1228
|
-
async function readBody2(req) {
|
|
1229
|
-
return new Promise((resolve) => {
|
|
1230
|
-
let data = "";
|
|
1231
|
-
req.on("data", (c) => {
|
|
1232
|
-
data += c;
|
|
1233
|
-
});
|
|
1234
|
-
req.on("end", () => resolve(data));
|
|
1235
|
-
});
|
|
1236
|
-
}
|
|
1237
1355
|
|
|
1238
1356
|
// src/plugins/apply/scan.ts
|
|
1239
|
-
import
|
|
1240
|
-
import
|
|
1357
|
+
import fs9 from "fs";
|
|
1358
|
+
import path8 from "path";
|
|
1359
|
+
|
|
1360
|
+
// src/plugins/apply/parse-tasks.ts
|
|
1361
|
+
var TASK_RE = /^\s*-\s*\[([ xX])\]\s*(.*)$/;
|
|
1362
|
+
function parseTasks(md) {
|
|
1363
|
+
return md.split("\n").filter((l) => TASK_RE.test(l)).map((l) => {
|
|
1364
|
+
const m = l.match(TASK_RE);
|
|
1365
|
+
return { text: (m?.[2] ?? l).trim(), checked: m?.[1] !== " " };
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1241
1368
|
function countTasks(md) {
|
|
1242
|
-
const
|
|
1243
|
-
|
|
1244
|
-
return { total: all, done };
|
|
1369
|
+
const tasks = parseTasks(md);
|
|
1370
|
+
return { total: tasks.length, done: tasks.filter((t) => t.checked).length };
|
|
1245
1371
|
}
|
|
1372
|
+
|
|
1373
|
+
// src/plugins/apply/scan.ts
|
|
1246
1374
|
function readText(p) {
|
|
1247
1375
|
try {
|
|
1248
|
-
return
|
|
1376
|
+
return fs9.readFileSync(p, "utf8");
|
|
1249
1377
|
} catch {
|
|
1250
1378
|
return "";
|
|
1251
1379
|
}
|
|
1252
1380
|
}
|
|
1253
1381
|
function worktreeDir(root, name) {
|
|
1254
|
-
return
|
|
1382
|
+
return path8.join(root, ".zworktree", name);
|
|
1255
1383
|
}
|
|
1256
1384
|
function changeDir(root, name) {
|
|
1257
|
-
return
|
|
1385
|
+
return path8.join(root, "openspec", "changes", name);
|
|
1258
1386
|
}
|
|
1259
1387
|
function parseDependsOn(proposal) {
|
|
1260
1388
|
const deps = [];
|
|
@@ -1268,42 +1396,42 @@ function parseDependsOn(proposal) {
|
|
|
1268
1396
|
return deps;
|
|
1269
1397
|
}
|
|
1270
1398
|
function scanApplyChanges(root) {
|
|
1271
|
-
const changesDir =
|
|
1272
|
-
if (!
|
|
1399
|
+
const changesDir = path8.join(root, "openspec", "changes");
|
|
1400
|
+
if (!fs9.existsSync(changesDir)) return [];
|
|
1273
1401
|
const out = [];
|
|
1274
|
-
for (const ent of
|
|
1402
|
+
for (const ent of fs9.readdirSync(changesDir, { withFileTypes: true })) {
|
|
1275
1403
|
if (!ent.isDirectory() || ent.name.startsWith(".") || ent.name === "archive") continue;
|
|
1276
1404
|
const wt = worktreeDir(root, ent.name);
|
|
1277
|
-
const tasks = readText(
|
|
1405
|
+
const tasks = readText(path8.join(wt, "openspec", "changes", ent.name, "tasks.md"));
|
|
1278
1406
|
if (!tasks) {
|
|
1279
|
-
const fallback = readText(
|
|
1407
|
+
const fallback = readText(path8.join(changesDir, ent.name, "tasks.md"));
|
|
1280
1408
|
if (fallback) {
|
|
1281
1409
|
const { total, done } = countTasks(fallback);
|
|
1282
|
-
const wtChange =
|
|
1283
|
-
const mainChange =
|
|
1410
|
+
const wtChange = path8.join(wt, "openspec", "changes", ent.name);
|
|
1411
|
+
const mainChange = path8.join(changesDir, ent.name);
|
|
1284
1412
|
out.push({
|
|
1285
1413
|
name: ent.name,
|
|
1286
1414
|
path: `openspec/changes/${ent.name}`,
|
|
1287
1415
|
total,
|
|
1288
1416
|
done,
|
|
1289
|
-
hasProposal:
|
|
1290
|
-
hasDesign:
|
|
1417
|
+
hasProposal: fs9.existsSync(path8.join(wtChange, "proposal.md")) || fs9.existsSync(path8.join(mainChange, "proposal.md")),
|
|
1418
|
+
hasDesign: fs9.existsSync(path8.join(wtChange, "design.md")) || fs9.existsSync(path8.join(mainChange, "design.md")),
|
|
1291
1419
|
// worktree 目录存在即算执行中(tasks.md 可能尚未落盘)
|
|
1292
|
-
inWorktree:
|
|
1420
|
+
inWorktree: fs9.existsSync(wtChange)
|
|
1293
1421
|
});
|
|
1294
1422
|
}
|
|
1295
1423
|
} else {
|
|
1296
1424
|
const { total, done } = countTasks(tasks);
|
|
1297
|
-
const wtChange =
|
|
1298
|
-
const mainChange =
|
|
1425
|
+
const wtChange = path8.join(wt, "openspec", "changes", ent.name);
|
|
1426
|
+
const mainChange = path8.join(changesDir, ent.name);
|
|
1299
1427
|
out.push({
|
|
1300
1428
|
name: ent.name,
|
|
1301
1429
|
path: `openspec/changes/${ent.name}`,
|
|
1302
1430
|
total,
|
|
1303
1431
|
done,
|
|
1304
1432
|
// 任一侧存在即算(详情读取是 worktree 优先、主目录兜底,列表标记保持一致)
|
|
1305
|
-
hasProposal:
|
|
1306
|
-
hasDesign:
|
|
1433
|
+
hasProposal: fs9.existsSync(path8.join(wtChange, "proposal.md")) || fs9.existsSync(path8.join(mainChange, "proposal.md")),
|
|
1434
|
+
hasDesign: fs9.existsSync(path8.join(wtChange, "design.md")) || fs9.existsSync(path8.join(mainChange, "design.md")),
|
|
1307
1435
|
inWorktree: true
|
|
1308
1436
|
});
|
|
1309
1437
|
}
|
|
@@ -1314,14 +1442,14 @@ function scanApplyChanges(root) {
|
|
|
1314
1442
|
function readApplyChange(root, name) {
|
|
1315
1443
|
const wt = worktreeDir(root, name);
|
|
1316
1444
|
const mainChangeDir = changeDir(root, name);
|
|
1317
|
-
const proposalFrom =
|
|
1318
|
-
const designFrom =
|
|
1319
|
-
const tasksFrom =
|
|
1445
|
+
const proposalFrom = fs9.existsSync(path8.join(wt, "openspec", "changes", name, "proposal.md")) ? path8.join(wt, "openspec", "changes", name, "proposal.md") : path8.join(mainChangeDir, "proposal.md");
|
|
1446
|
+
const designFrom = fs9.existsSync(path8.join(wt, "openspec", "changes", name, "design.md")) ? path8.join(wt, "openspec", "changes", name, "design.md") : path8.join(mainChangeDir, "design.md");
|
|
1447
|
+
const tasksFrom = fs9.existsSync(path8.join(wt, "openspec", "changes", name, "tasks.md")) ? path8.join(wt, "openspec", "changes", name, "tasks.md") : path8.join(mainChangeDir, "tasks.md");
|
|
1320
1448
|
const proposal = readText(proposalFrom);
|
|
1321
1449
|
const design = readText(designFrom);
|
|
1322
1450
|
const tasks = readText(tasksFrom);
|
|
1323
1451
|
const { total, done } = countTasks(tasks);
|
|
1324
|
-
const inWorktree =
|
|
1452
|
+
const inWorktree = fs9.existsSync(path8.join(wt, "openspec", "changes", name));
|
|
1325
1453
|
const dependsOn = parseDependsOn(proposal);
|
|
1326
1454
|
const hasTestStrategy = /^##\s+测试策略\s*$/m.test(design);
|
|
1327
1455
|
return {
|
|
@@ -1379,13 +1507,12 @@ var apply6 = {
|
|
|
1379
1507
|
};
|
|
1380
1508
|
|
|
1381
1509
|
// src/server/design-assets.ts
|
|
1382
|
-
import
|
|
1383
|
-
import path8 from "path";
|
|
1510
|
+
import path9 from "path";
|
|
1384
1511
|
var PAGE_EXTS = [".html", ".htm"];
|
|
1385
1512
|
var ICON_EXTS = [".svg", ".png", ".ico", ".jpg", ".jpeg", ".gif", ".webp"];
|
|
1386
1513
|
var VIDEO_EXTS = [".mp4", ".webm", ".mov", ".ogg", ".ogv"];
|
|
1387
1514
|
var AUDIO_EXTS = [".mp3", ".wav", ".flac", ".aac", ".m4a"];
|
|
1388
|
-
var CODE_EXTS = [".js", ".mjs", ".ts", ".tsx", ".jsx", ".css", ".json", ".txt", ".xml", ".yml", ".yaml", ".sh", ".md"];
|
|
1515
|
+
var CODE_EXTS = [".js", ".mjs", ".cjs", ".mts", ".cts", ".ts", ".tsx", ".jsx", ".css", ".json", ".txt", ".xml", ".yml", ".yaml", ".sh", ".md"];
|
|
1389
1516
|
var FONT_EXTS = [".woff", ".woff2", ".ttf", ".otf"];
|
|
1390
1517
|
var TOKEN_RE = /token|theme|design|color|palette|typograph/i;
|
|
1391
1518
|
function categorize(rel, ext) {
|
|
@@ -1408,26 +1535,11 @@ function scanAssets(root) {
|
|
|
1408
1535
|
const keys = ["page", "component", "icon", "token", "md", "video", "audio", "pdf", "font", "other"];
|
|
1409
1536
|
for (const k of keys) out[k] = [];
|
|
1410
1537
|
const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".next", ".cache"]);
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
return;
|
|
1417
|
-
}
|
|
1418
|
-
for (const ent of ents) {
|
|
1419
|
-
if (ent.name.startsWith(".") || SKIP.has(ent.name)) continue;
|
|
1420
|
-
const r = rel ? `${rel}/${ent.name}` : ent.name;
|
|
1421
|
-
if (ent.isDirectory()) {
|
|
1422
|
-
walk(path8.join(dir, ent.name), r);
|
|
1423
|
-
continue;
|
|
1424
|
-
}
|
|
1425
|
-
const ext = path8.extname(ent.name).toLowerCase();
|
|
1426
|
-
const t = categorize(r, ext);
|
|
1427
|
-
if (t) out[t].push({ path: r, name: ent.name, ext, type: t });
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
walk(root, "");
|
|
1538
|
+
walkDir(root, { skip: SKIP, onFile: (abs, rel) => {
|
|
1539
|
+
const ext = path9.extname(abs).toLowerCase();
|
|
1540
|
+
const t = categorize(rel, ext);
|
|
1541
|
+
if (t) out[t].push({ path: rel, name: path9.basename(abs), ext, type: t });
|
|
1542
|
+
} });
|
|
1431
1543
|
return out;
|
|
1432
1544
|
}
|
|
1433
1545
|
|
|
@@ -1457,53 +1569,42 @@ var apply8 = {
|
|
|
1457
1569
|
|
|
1458
1570
|
// src/plugins/stats/index.ts
|
|
1459
1571
|
import fs10 from "fs";
|
|
1460
|
-
import
|
|
1572
|
+
import path10 from "path";
|
|
1461
1573
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", ".cache"]);
|
|
1462
1574
|
function scan(root) {
|
|
1463
1575
|
const stats = {
|
|
1464
|
-
root:
|
|
1576
|
+
root: path10.basename(root),
|
|
1465
1577
|
files: 0,
|
|
1466
1578
|
dirs: 0,
|
|
1467
1579
|
totalSize: 0,
|
|
1468
1580
|
byExt: [],
|
|
1469
1581
|
markdown: 0,
|
|
1470
1582
|
openspec: { active: 0, archived: 0 },
|
|
1471
|
-
hasJust: fs10.existsSync(
|
|
1583
|
+
hasJust: fs10.existsSync(path10.join(root, "justfile"))
|
|
1472
1584
|
};
|
|
1473
1585
|
const extMap = /* @__PURE__ */ new Map();
|
|
1474
1586
|
const walk = (dir) => {
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
} catch {
|
|
1479
|
-
return;
|
|
1480
|
-
}
|
|
1481
|
-
for (const ent of ents) {
|
|
1482
|
-
if (ent.name.startsWith(".") || SKIP_DIRS.has(ent.name)) continue;
|
|
1483
|
-
const abs = path9.join(dir, ent.name);
|
|
1484
|
-
if (ent.isDirectory()) {
|
|
1485
|
-
stats.dirs++;
|
|
1486
|
-
walk(abs);
|
|
1487
|
-
continue;
|
|
1488
|
-
}
|
|
1587
|
+
walkDir(dir, { skip: SKIP_DIRS, onDir: () => {
|
|
1588
|
+
stats.dirs++;
|
|
1589
|
+
}, onFile: (abs) => {
|
|
1489
1590
|
stats.files++;
|
|
1490
1591
|
try {
|
|
1491
1592
|
stats.totalSize += fs10.statSync(abs).size;
|
|
1492
1593
|
} catch {
|
|
1493
1594
|
}
|
|
1494
|
-
const ext =
|
|
1595
|
+
const ext = path10.extname(abs).toLowerCase() || "(\u65E0\u6269\u5C55\u540D)";
|
|
1495
1596
|
extMap.set(ext, (extMap.get(ext) ?? 0) + 1);
|
|
1496
1597
|
if (ext === ".md" || ext === ".markdown") stats.markdown++;
|
|
1497
|
-
}
|
|
1598
|
+
} });
|
|
1498
1599
|
};
|
|
1499
1600
|
walk(root);
|
|
1500
|
-
const changesDir =
|
|
1601
|
+
const changesDir = path10.join(root, "openspec", "changes");
|
|
1501
1602
|
if (fs10.existsSync(changesDir)) {
|
|
1502
1603
|
for (const ent of fs10.readdirSync(changesDir, { withFileTypes: true })) {
|
|
1503
1604
|
if (!ent.isDirectory() || ent.name.startsWith(".")) continue;
|
|
1504
1605
|
if (ent.name === "archive") {
|
|
1505
1606
|
try {
|
|
1506
|
-
stats.openspec.archived = fs10.readdirSync(
|
|
1607
|
+
stats.openspec.archived = fs10.readdirSync(path10.join(changesDir, "archive"), { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).length;
|
|
1507
1608
|
} catch {
|
|
1508
1609
|
}
|
|
1509
1610
|
} else stats.openspec.active++;
|
|
@@ -1534,34 +1635,31 @@ var apply9 = {
|
|
|
1534
1635
|
};
|
|
1535
1636
|
|
|
1536
1637
|
// src/cli.ts
|
|
1537
|
-
var __dirname2 =
|
|
1638
|
+
var __dirname2 = path11.dirname(fileURLToPath2(import.meta.url));
|
|
1538
1639
|
var DEFAULT_PORT = 4190;
|
|
1539
1640
|
function parseArgs() {
|
|
1540
1641
|
const args = process.argv.slice(2);
|
|
1541
|
-
const
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
if (key === "port") portExplicit = true;
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1642
|
+
const { values } = utilParseArgs({
|
|
1643
|
+
options: {
|
|
1644
|
+
dir: { type: "string" },
|
|
1645
|
+
port: { type: "string" },
|
|
1646
|
+
open: { type: "boolean", default: false },
|
|
1647
|
+
page: { type: "string" },
|
|
1648
|
+
restart: { type: "boolean", default: false },
|
|
1649
|
+
plugins: { type: "string" }
|
|
1650
|
+
},
|
|
1651
|
+
args,
|
|
1652
|
+
strict: false,
|
|
1653
|
+
allowPositionals: false
|
|
1654
|
+
});
|
|
1557
1655
|
return {
|
|
1558
|
-
dir: typeof
|
|
1559
|
-
port: typeof
|
|
1560
|
-
portExplicit,
|
|
1561
|
-
open: !!
|
|
1562
|
-
page: typeof
|
|
1563
|
-
restart: !!
|
|
1564
|
-
plugins: typeof
|
|
1656
|
+
dir: typeof values.dir === "string" ? values.dir : process.cwd(),
|
|
1657
|
+
port: typeof values.port === "string" ? Number(values.port) : DEFAULT_PORT,
|
|
1658
|
+
portExplicit: args.some((a) => a === "--port" || a.startsWith("--port=")),
|
|
1659
|
+
open: !!values.open,
|
|
1660
|
+
page: typeof values.page === "string" ? values.page : null,
|
|
1661
|
+
restart: !!values.restart,
|
|
1662
|
+
plugins: typeof values.plugins === "string" ? values.plugins : null
|
|
1565
1663
|
};
|
|
1566
1664
|
}
|
|
1567
1665
|
async function pathExists(p) {
|
|
@@ -1587,7 +1685,7 @@ async function loadExternal(ctx, dir, root) {
|
|
|
1587
1685
|
if (!ent.isDirectory()) continue;
|
|
1588
1686
|
const candidates = tsxLoaded ? ["index.ts", "index.js", "index.mjs"] : ["index.js", "index.mjs"];
|
|
1589
1687
|
for (const name of candidates) {
|
|
1590
|
-
const p =
|
|
1688
|
+
const p = path11.join(dir, ent.name, name);
|
|
1591
1689
|
if (await pathExists(p)) {
|
|
1592
1690
|
try {
|
|
1593
1691
|
const mod = await import(p);
|
|
@@ -1602,8 +1700,8 @@ async function loadExternal(ctx, dir, root) {
|
|
|
1602
1700
|
const m = ctx.dashboard.get(ent.name);
|
|
1603
1701
|
if (m && m.mode === ent.name) {
|
|
1604
1702
|
const patch = { external: true };
|
|
1605
|
-
const webDir =
|
|
1606
|
-
if (await pathExists(
|
|
1703
|
+
const webDir = path11.join(dir, ent.name, "web");
|
|
1704
|
+
if (await pathExists(path11.join(webDir, "index.html"))) {
|
|
1607
1705
|
ctx.server.static(`${PLUGIN_STATIC_PREFIX}${ent.name}/`, webDir);
|
|
1608
1706
|
if (!m.viewerUrl) patch.viewerUrl = `${PLUGIN_STATIC_PREFIX}${ent.name}/`;
|
|
1609
1707
|
}
|
|
@@ -1622,7 +1720,7 @@ async function loadExternal(ctx, dir, root) {
|
|
|
1622
1720
|
}
|
|
1623
1721
|
async function main() {
|
|
1624
1722
|
const args = parseArgs();
|
|
1625
|
-
const root =
|
|
1723
|
+
const root = path11.resolve(args.dir);
|
|
1626
1724
|
const existing = await findReusable(root);
|
|
1627
1725
|
if (existing && !args.restart) {
|
|
1628
1726
|
const u = `http://localhost:${existing.port}` + (args.page ? `#${args.page}` : "");
|
|
@@ -1636,9 +1734,9 @@ async function main() {
|
|
|
1636
1734
|
console.log(`[zdashboard] --restart\uFF1A\u505C\u6B62\u65E7\u5B9E\u4F8B pid=${oldRecord.pid}`);
|
|
1637
1735
|
await stopInstance(oldRecord);
|
|
1638
1736
|
}
|
|
1639
|
-
const appDir =
|
|
1737
|
+
const appDir = path11.resolve(__dirname2, "web");
|
|
1640
1738
|
const startPort = oldRecord && !args.portExplicit ? oldRecord.port : args.port;
|
|
1641
|
-
const zdevDir =
|
|
1739
|
+
const zdevDir = path11.join(root, ".zdev");
|
|
1642
1740
|
const dataDir = fs11.existsSync(zdevDir) ? ".zdev/" : "";
|
|
1643
1741
|
const det = await detect(root);
|
|
1644
1742
|
const ctx = new Context();
|
|
@@ -1673,7 +1771,7 @@ async function main() {
|
|
|
1673
1771
|
}
|
|
1674
1772
|
}
|
|
1675
1773
|
if (args.plugins) {
|
|
1676
|
-
await loadExternal(ctx,
|
|
1774
|
+
await loadExternal(ctx, path11.resolve(args.plugins), root);
|
|
1677
1775
|
}
|
|
1678
1776
|
const shutdown = () => {
|
|
1679
1777
|
try {
|