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