zdashboard 1.0.1 → 1.2.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
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/server/index.ts
|
|
4
4
|
import http from "http";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import fs6 from "fs";
|
|
6
|
+
import path6 from "path";
|
|
7
7
|
import crypto from "crypto";
|
|
8
8
|
import { exec } from "child_process";
|
|
9
9
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -210,6 +210,48 @@ var JustRunner = class {
|
|
|
210
210
|
// src/server/bugs.ts
|
|
211
211
|
import fs2 from "fs";
|
|
212
212
|
import path2 from "path";
|
|
213
|
+
|
|
214
|
+
// src/server/api/fetch.ts
|
|
215
|
+
import ky from "ky";
|
|
216
|
+
|
|
217
|
+
// src/server/errors.ts
|
|
218
|
+
var HttpError = class extends Error {
|
|
219
|
+
constructor(status, message, body) {
|
|
220
|
+
super(message);
|
|
221
|
+
this.status = status;
|
|
222
|
+
this.body = body;
|
|
223
|
+
this.name = "HttpError";
|
|
224
|
+
}
|
|
225
|
+
status;
|
|
226
|
+
body;
|
|
227
|
+
};
|
|
228
|
+
var NetworkError = class extends Error {
|
|
229
|
+
constructor(message, cause) {
|
|
230
|
+
super(message);
|
|
231
|
+
this.cause = cause;
|
|
232
|
+
this.name = "NetworkError";
|
|
233
|
+
}
|
|
234
|
+
cause;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// src/server/api/fetch.ts
|
|
238
|
+
async function fetchJson(url, init) {
|
|
239
|
+
try {
|
|
240
|
+
const res = await ky(url, { ...init, timeout: 8e3, retry: 2 });
|
|
241
|
+
return await res.json();
|
|
242
|
+
} catch (e) {
|
|
243
|
+
if (e instanceof HttpError) throw e;
|
|
244
|
+
if (e instanceof Error && e.name === "TimeoutError") {
|
|
245
|
+
throw new NetworkError(`\u8BF7\u6C42\u8D85\u65F6: ${url}`);
|
|
246
|
+
}
|
|
247
|
+
if (e instanceof Error && e.name === "HTTPError") {
|
|
248
|
+
throw new HttpError(e.status ?? 500, e.message);
|
|
249
|
+
}
|
|
250
|
+
throw new NetworkError(`\u8BF7\u6C42\u5931\u8D25: ${url}`, e);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/server/bugs.ts
|
|
213
255
|
function loadZgoalConfig(root2) {
|
|
214
256
|
const file = path2.join(root2, ".zgoal", "config.yaml");
|
|
215
257
|
if (!fs2.existsSync(file)) return null;
|
|
@@ -228,26 +270,6 @@ function loadZgoalConfig(root2) {
|
|
|
228
270
|
product
|
|
229
271
|
};
|
|
230
272
|
}
|
|
231
|
-
async function fetchJson(url, init) {
|
|
232
|
-
const ctrl = new AbortController();
|
|
233
|
-
const timer = setTimeout(() => ctrl.abort(), 8e3);
|
|
234
|
-
try {
|
|
235
|
-
const res = await fetch(url, { ...init, signal: ctrl.signal });
|
|
236
|
-
const text = await res.text();
|
|
237
|
-
let json = {};
|
|
238
|
-
try {
|
|
239
|
-
json = JSON.parse(text);
|
|
240
|
-
} catch {
|
|
241
|
-
}
|
|
242
|
-
if (!res.ok) {
|
|
243
|
-
const err = json.error;
|
|
244
|
-
throw new Error(`HTTP ${res.status}${typeof err === "string" ? `: ${err}` : ""}`);
|
|
245
|
-
}
|
|
246
|
-
return json;
|
|
247
|
-
} finally {
|
|
248
|
-
clearTimeout(timer);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
273
|
var tokenCache = null;
|
|
252
274
|
async function getToken(cfg) {
|
|
253
275
|
if (cfg.token) return cfg.token;
|
|
@@ -309,15 +331,128 @@ function allBuiltins() {
|
|
|
309
331
|
return Array.from(builtinPlugins.values());
|
|
310
332
|
}
|
|
311
333
|
|
|
334
|
+
// src/server/review-store.ts
|
|
335
|
+
import fs4 from "fs";
|
|
336
|
+
import path4 from "path";
|
|
337
|
+
import YAML from "yaml";
|
|
338
|
+
var REVIEW_FILE = "review.yaml";
|
|
339
|
+
var ReviewStore = class {
|
|
340
|
+
root;
|
|
341
|
+
file;
|
|
342
|
+
onChange;
|
|
343
|
+
constructor(root2, onChange) {
|
|
344
|
+
this.root = root2;
|
|
345
|
+
this.file = path4.join(root2, REVIEW_FILE);
|
|
346
|
+
this.onChange = onChange;
|
|
347
|
+
}
|
|
348
|
+
exists() {
|
|
349
|
+
return fs4.existsSync(this.file);
|
|
350
|
+
}
|
|
351
|
+
read() {
|
|
352
|
+
try {
|
|
353
|
+
const parsed = YAML.parse(fs4.readFileSync(this.file, "utf8"));
|
|
354
|
+
if (!parsed || !Array.isArray(parsed.items)) return { status: "draft", items: [] };
|
|
355
|
+
return parsed;
|
|
356
|
+
} catch {
|
|
357
|
+
return { status: "draft", items: [] };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
write(data) {
|
|
361
|
+
fs4.writeFileSync(this.file, YAML.stringify(data), "utf8");
|
|
362
|
+
this.onChange?.();
|
|
363
|
+
}
|
|
364
|
+
updateItem(id, patch) {
|
|
365
|
+
const data = this.read();
|
|
366
|
+
const item = data.items.find((i) => i.id === id);
|
|
367
|
+
if (!item) throw new Error(`item ${id} not found`);
|
|
368
|
+
if (patch.answer !== void 0) item.answer = patch.answer;
|
|
369
|
+
if (patch.state !== void 0) item.state = patch.state;
|
|
370
|
+
if (patch.state === "answered" && !patch.answer && !item.answer) item.answer = "";
|
|
371
|
+
this.write(data);
|
|
372
|
+
return data;
|
|
373
|
+
}
|
|
374
|
+
setStatus(status) {
|
|
375
|
+
const data = this.read();
|
|
376
|
+
if (status === "passed" && data.items.some((i) => i.state === "open")) {
|
|
377
|
+
throw new Error("\u5B58\u5728\u672A\u5904\u7406\u7684\u8BC4\u5BA1\u9879(open),\u4E0D\u80FD\u901A\u8FC7");
|
|
378
|
+
}
|
|
379
|
+
data.status = status;
|
|
380
|
+
this.write(data);
|
|
381
|
+
return data;
|
|
382
|
+
}
|
|
383
|
+
docs() {
|
|
384
|
+
try {
|
|
385
|
+
return fs4.readdirSync(this.root).filter((f) => /\.(md|markdown)$/i.test(f) && fs4.statSync(path4.join(this.root, f)).isFile()).sort();
|
|
386
|
+
} catch {
|
|
387
|
+
return [];
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// src/server/design-assets.ts
|
|
393
|
+
import fs5 from "fs";
|
|
394
|
+
import path5 from "path";
|
|
395
|
+
var PAGE_EXTS = [".html", ".htm"];
|
|
396
|
+
var ICON_EXTS = [".svg", ".png", ".ico", ".jpg", ".jpeg", ".gif", ".webp"];
|
|
397
|
+
var VIDEO_EXTS = [".mp4", ".webm", ".mov", ".ogg", ".ogv"];
|
|
398
|
+
var AUDIO_EXTS = [".mp3", ".wav", ".flac", ".aac", ".m4a"];
|
|
399
|
+
var CODE_EXTS = [".js", ".mjs", ".ts", ".tsx", ".jsx", ".css", ".json", ".txt", ".xml", ".yml", ".yaml", ".sh", ".md"];
|
|
400
|
+
var FONT_EXTS = [".woff", ".woff2", ".ttf", ".otf"];
|
|
401
|
+
var TOKEN_RE = /token|theme|design|color|palette|typograph/i;
|
|
402
|
+
function categorize(rel, ext) {
|
|
403
|
+
if (rel.indexOf("components/") === 0) return "component";
|
|
404
|
+
if (VIDEO_EXTS.includes(ext)) return "video";
|
|
405
|
+
if (AUDIO_EXTS.includes(ext)) return "audio";
|
|
406
|
+
if (ext === ".pdf") return "pdf";
|
|
407
|
+
if (ext === ".md") return "md";
|
|
408
|
+
if (FONT_EXTS.includes(ext)) return "font";
|
|
409
|
+
if (ICON_EXTS.includes(ext)) return "icon";
|
|
410
|
+
if (PAGE_EXTS.includes(ext)) return "page";
|
|
411
|
+
if (CODE_EXTS.includes(ext)) return TOKEN_RE.test(rel) && (ext === ".css" || ext === ".json") ? "token" : "code";
|
|
412
|
+
return "other";
|
|
413
|
+
}
|
|
414
|
+
function scanAssets(root2) {
|
|
415
|
+
const out = {};
|
|
416
|
+
const keys = ["page", "component", "icon", "token", "md", "video", "audio", "pdf", "code", "font", "other"];
|
|
417
|
+
for (const k of keys) out[k] = [];
|
|
418
|
+
function walk(dir, rel) {
|
|
419
|
+
let ents;
|
|
420
|
+
try {
|
|
421
|
+
ents = fs5.readdirSync(dir, { withFileTypes: true });
|
|
422
|
+
} catch {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
for (const ent of ents) {
|
|
426
|
+
if (ent.name.charAt(0) === ".") continue;
|
|
427
|
+
const r = rel ? `${rel}/${ent.name}` : ent.name;
|
|
428
|
+
if (ent.isDirectory()) {
|
|
429
|
+
walk(path5.join(dir, ent.name), r);
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
const ext = path5.extname(ent.name).toLowerCase();
|
|
433
|
+
const t = categorize(r, ext);
|
|
434
|
+
out[t].push({ path: r, name: ent.name, ext, type: t });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
walk(root2, "");
|
|
438
|
+
return out;
|
|
439
|
+
}
|
|
440
|
+
|
|
312
441
|
// package.json
|
|
313
442
|
var package_default = {
|
|
314
443
|
name: "zdashboard",
|
|
315
|
-
version: "1.0
|
|
444
|
+
version: "1.2.0",
|
|
316
445
|
description: "ZCode skill dashboard platform \u2014 pluggable viewers for zdesign/zview/zreview/zgoal",
|
|
317
446
|
type: "module",
|
|
318
|
-
bin: {
|
|
319
|
-
|
|
320
|
-
|
|
447
|
+
bin: {
|
|
448
|
+
zdashboard: "./dist/cli.js"
|
|
449
|
+
},
|
|
450
|
+
files: [
|
|
451
|
+
"dist"
|
|
452
|
+
],
|
|
453
|
+
publishConfig: {
|
|
454
|
+
access: "public"
|
|
455
|
+
},
|
|
321
456
|
scripts: {
|
|
322
457
|
dev: "vite",
|
|
323
458
|
build: "tsup && vite build",
|
|
@@ -331,47 +466,63 @@ var package_default = {
|
|
|
331
466
|
"@radix-ui/react-separator": "^1.1.0",
|
|
332
467
|
"@radix-ui/react-slot": "^1.1.0",
|
|
333
468
|
"@radix-ui/react-tooltip": "^1.1.2",
|
|
469
|
+
"@uidotdev/usehooks": "^2.4.0",
|
|
334
470
|
"ansi-to-react": "^6.1.6",
|
|
335
471
|
"class-variance-authority": "^0.7.0",
|
|
336
472
|
clsx: "^2.1.1",
|
|
473
|
+
"date-fns": "^3.6.0",
|
|
474
|
+
filesize: "^11.0.0",
|
|
337
475
|
"highlight.js": "^11.11.1",
|
|
338
476
|
katex: "^0.16.11",
|
|
477
|
+
ky: "^1.7.0",
|
|
478
|
+
"lodash-es": "^4.17.21",
|
|
339
479
|
"lucide-react": "^0.460.0",
|
|
340
480
|
react: "^18.3.1",
|
|
341
481
|
"react-dom": "^18.3.1",
|
|
482
|
+
"react-error-boundary": "^5.0.0",
|
|
342
483
|
"react-markdown": "^9.0.1",
|
|
343
|
-
"remark-frontmatter": "^5.0.0",
|
|
344
|
-
"remark-gfm": "^4.0.0",
|
|
345
|
-
"remark-math": "^6.0.0",
|
|
346
484
|
"rehype-autolink-headings": "^7.1.0",
|
|
347
485
|
"rehype-highlight": "^7.0.1",
|
|
348
486
|
"rehype-katex": "^7.0.1",
|
|
349
487
|
"rehype-raw": "^7.0.0",
|
|
350
488
|
"rehype-slug": "^6.0.0",
|
|
351
|
-
"
|
|
489
|
+
"remark-frontmatter": "^5.0.0",
|
|
490
|
+
"remark-gfm": "^4.0.0",
|
|
491
|
+
"remark-math": "^6.0.0",
|
|
492
|
+
sonner: "^1.5.0",
|
|
493
|
+
"tailwind-merge": "^2.5.4",
|
|
494
|
+
"use-debounce": "^10.0.0",
|
|
495
|
+
yaml: "^2.9.0"
|
|
352
496
|
},
|
|
353
497
|
devDependencies: {
|
|
354
498
|
"@tailwindcss/typography": "^0.5.20",
|
|
499
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
500
|
+
"@testing-library/react": "^16.3.2",
|
|
501
|
+
"@types/lodash-es": "^4.17.12",
|
|
355
502
|
"@types/node": "^22.9.0",
|
|
356
503
|
"@types/react": "^18.3.12",
|
|
357
504
|
"@types/react-dom": "^18.3.1",
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
505
|
+
"@vitejs/plugin-react": "^4.7.0",
|
|
506
|
+
autoprefixer: "^10.4.20",
|
|
507
|
+
jsdom: "^30.0.1",
|
|
508
|
+
postcss: "^8.4.49",
|
|
361
509
|
tailwindcss: "^3.4.14",
|
|
362
510
|
"tailwindcss-animate": "^1.0.7",
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
511
|
+
tsup: "^8.3.5",
|
|
512
|
+
typescript: "^5.6.3",
|
|
513
|
+
vite: "^5.4.10",
|
|
514
|
+
vitest: "^1.6.0"
|
|
366
515
|
},
|
|
367
516
|
pnpm: {
|
|
368
|
-
onlyBuiltDependencies: [
|
|
517
|
+
onlyBuiltDependencies: [
|
|
518
|
+
"esbuild"
|
|
519
|
+
]
|
|
369
520
|
}
|
|
370
521
|
};
|
|
371
522
|
|
|
372
523
|
// src/server/index.ts
|
|
373
524
|
var VERSION = package_default.version;
|
|
374
|
-
var __dirname2 =
|
|
525
|
+
var __dirname2 = path6.dirname(fileURLToPath2(import.meta.url));
|
|
375
526
|
var STOP_TOKEN = crypto.randomBytes(12).toString("hex");
|
|
376
527
|
var INJECT = `<script>(function(){try{var es=new EventSource('/__reload');es.addEventListener('reload',function(){location.reload();});es.onerror=function(){es.close();};}catch(e){}document.addEventListener('click',function(e){var t=e.target;if(t&&t.closest){var a=t.closest('a[target]');if(a&&a.target!=='_self'){a.target='_self';}}},true);})();</script>`;
|
|
377
528
|
var MIME = {
|
|
@@ -405,11 +556,11 @@ function readBody(req) {
|
|
|
405
556
|
});
|
|
406
557
|
}
|
|
407
558
|
function createServer(opts) {
|
|
408
|
-
const ROOT =
|
|
559
|
+
const ROOT = path6.resolve(opts.root);
|
|
409
560
|
const PORT0 = opts.port ?? 4190;
|
|
410
561
|
const OPEN = !!opts.open;
|
|
411
|
-
const APP_DIR = opts.dashboardDir ??
|
|
412
|
-
if (!
|
|
562
|
+
const APP_DIR = opts.dashboardDir ?? path6.resolve(__dirname2, "web");
|
|
563
|
+
if (!fs6.existsSync(ROOT)) fs6.mkdirSync(ROOT, { recursive: true });
|
|
413
564
|
const det2 = opts.detect;
|
|
414
565
|
const runner = new JustRunner(ROOT);
|
|
415
566
|
const MODE = opts.mode;
|
|
@@ -423,7 +574,61 @@ function createServer(opts) {
|
|
|
423
574
|
} }
|
|
424
575
|
});
|
|
425
576
|
registerBuiltin({ mode: "view", label: "\u9879\u76EE\u6D4F\u89C8", icon: "\u{1F441}\uFE0F" });
|
|
426
|
-
|
|
577
|
+
const reviewStore = new ReviewStore(ROOT);
|
|
578
|
+
registerBuiltin({
|
|
579
|
+
mode: "review",
|
|
580
|
+
label: "\u6587\u6863\u8BC4\u5BA1",
|
|
581
|
+
icon: "\u2705",
|
|
582
|
+
apiRoutes: {
|
|
583
|
+
"/__review": async (_, res) => {
|
|
584
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-cache" });
|
|
585
|
+
res.end(JSON.stringify(reviewStore.read()));
|
|
586
|
+
},
|
|
587
|
+
"/__review/item": async (req, res) => {
|
|
588
|
+
if (req.headers["x-stop-token"] !== STOP_TOKEN) {
|
|
589
|
+
res.writeHead(403);
|
|
590
|
+
res.end("forbidden");
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
(async () => {
|
|
594
|
+
try {
|
|
595
|
+
const body = JSON.parse(await readBody(req) || "{}");
|
|
596
|
+
const data = reviewStore.updateItem(body.id, { answer: body.answer, state: body.state });
|
|
597
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
598
|
+
res.end(JSON.stringify(data));
|
|
599
|
+
} catch (e) {
|
|
600
|
+
res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
|
|
601
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
602
|
+
}
|
|
603
|
+
})();
|
|
604
|
+
return;
|
|
605
|
+
},
|
|
606
|
+
"/__review/status": async (req, res) => {
|
|
607
|
+
if (req.headers["x-stop-token"] !== STOP_TOKEN) {
|
|
608
|
+
res.writeHead(403);
|
|
609
|
+
res.end("forbidden");
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
(async () => {
|
|
613
|
+
try {
|
|
614
|
+
const body = JSON.parse(await readBody(req) || "{}");
|
|
615
|
+
const data = reviewStore.setStatus(body.status);
|
|
616
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
617
|
+
res.end(JSON.stringify(data));
|
|
618
|
+
} catch (e) {
|
|
619
|
+
res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
|
|
620
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
621
|
+
}
|
|
622
|
+
})();
|
|
623
|
+
return;
|
|
624
|
+
},
|
|
625
|
+
"/__docs": async (_, res) => {
|
|
626
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-cache" });
|
|
627
|
+
res.end(JSON.stringify(reviewStore.docs()));
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
registerBuiltin({ mode: "design", label: "\u8BBE\u8BA1\u8D44\u4EA7", icon: "\u{1F3A8}" });
|
|
427
632
|
const clients = /* @__PURE__ */ new Set();
|
|
428
633
|
const broadcast = (ev, data = "") => {
|
|
429
634
|
const payload = `event: ${ev}
|
|
@@ -433,12 +638,12 @@ data: ${JSON.stringify(data == null ? "" : data)}
|
|
|
433
638
|
for (const c of clients) c.write(payload);
|
|
434
639
|
};
|
|
435
640
|
function serveFile(filePath, res, injectHtml) {
|
|
436
|
-
|
|
641
|
+
fs6.readFile(filePath, (err, data) => {
|
|
437
642
|
if (err) {
|
|
438
643
|
res.writeHead(404);
|
|
439
644
|
return res.end("Not found");
|
|
440
645
|
}
|
|
441
|
-
const ext =
|
|
646
|
+
const ext = path6.extname(filePath).toLowerCase();
|
|
442
647
|
const ct = MIME[ext] ?? "application/octet-stream";
|
|
443
648
|
let body = data;
|
|
444
649
|
if (injectHtml && ext === ".html") {
|
|
@@ -481,6 +686,10 @@ data: ${JSON.stringify(data == null ? "" : data)}
|
|
|
481
686
|
return;
|
|
482
687
|
}
|
|
483
688
|
if (url === "/__files") {
|
|
689
|
+
if (MODE === "design") {
|
|
690
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-cache" });
|
|
691
|
+
return res.end(JSON.stringify(scanAssets(ROOT)));
|
|
692
|
+
}
|
|
484
693
|
const tree = scanTree(ROOT, det2.hasOpenspec, det2.hasDocs);
|
|
485
694
|
const payload = { tree, ...det2 };
|
|
486
695
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-cache" });
|
|
@@ -548,25 +757,25 @@ data: ${JSON.stringify(data == null ? "" : data)}
|
|
|
548
757
|
}
|
|
549
758
|
}
|
|
550
759
|
}
|
|
551
|
-
if (url === "/") return serveFile(
|
|
760
|
+
if (url === "/") return serveFile(path6.join(APP_DIR, "index.html"), res, false);
|
|
552
761
|
if (url.indexOf("/__app/") === 0) {
|
|
553
|
-
const fp2 =
|
|
554
|
-
if (fp2 !== APP_DIR && fp2.indexOf(APP_DIR +
|
|
762
|
+
const fp2 = path6.join(APP_DIR, url.slice(7));
|
|
763
|
+
if (fp2 !== APP_DIR && fp2.indexOf(APP_DIR + path6.sep) !== 0) {
|
|
555
764
|
res.writeHead(403);
|
|
556
765
|
return res.end("Forbidden");
|
|
557
766
|
}
|
|
558
767
|
return serveFile(fp2, res, false);
|
|
559
768
|
}
|
|
560
769
|
if (url.indexOf("/assets/") === 0) {
|
|
561
|
-
const fp2 =
|
|
562
|
-
if (fp2.indexOf(APP_DIR +
|
|
770
|
+
const fp2 = path6.join(APP_DIR, decodeURIComponent(url));
|
|
771
|
+
if (fp2.indexOf(APP_DIR + path6.sep) !== 0) {
|
|
563
772
|
res.writeHead(403);
|
|
564
773
|
return res.end("Forbidden");
|
|
565
774
|
}
|
|
566
775
|
return serveFile(fp2, res, false);
|
|
567
776
|
}
|
|
568
|
-
const fp =
|
|
569
|
-
if (fp !== ROOT && fp.indexOf(ROOT +
|
|
777
|
+
const fp = path6.join(ROOT, decodeURIComponent(url));
|
|
778
|
+
if (fp !== ROOT && fp.indexOf(ROOT + path6.sep) !== 0) {
|
|
570
779
|
res.writeHead(403);
|
|
571
780
|
return res.end("Forbidden");
|
|
572
781
|
}
|
|
@@ -592,7 +801,7 @@ data: ${JSON.stringify(data == null ? "" : data)}
|
|
|
592
801
|
}
|
|
593
802
|
let debounce;
|
|
594
803
|
try {
|
|
595
|
-
|
|
804
|
+
fs6.watch(ROOT, { recursive: true }, () => {
|
|
596
805
|
clearTimeout(debounce);
|
|
597
806
|
debounce = setTimeout(() => {
|
|
598
807
|
broadcast("reload");
|
|
@@ -607,8 +816,8 @@ data: ${JSON.stringify(data == null ? "" : data)}
|
|
|
607
816
|
}
|
|
608
817
|
|
|
609
818
|
// src/server/detect.ts
|
|
610
|
-
import
|
|
611
|
-
import
|
|
819
|
+
import fs7 from "fs";
|
|
820
|
+
import path7 from "path";
|
|
612
821
|
import { execFile as execFile2 } from "child_process";
|
|
613
822
|
function justAvailable(cwd) {
|
|
614
823
|
return new Promise((resolve) => {
|
|
@@ -619,10 +828,10 @@ function justAvailable(cwd) {
|
|
|
619
828
|
});
|
|
620
829
|
}
|
|
621
830
|
async function detect(root2) {
|
|
622
|
-
const hasOpenspec =
|
|
623
|
-
const hasDocs =
|
|
831
|
+
const hasOpenspec = fs7.existsSync(path7.join(root2, "openspec"));
|
|
832
|
+
const hasDocs = fs7.existsSync(path7.join(root2, "docs"));
|
|
624
833
|
const hasJust = await justAvailable(root2);
|
|
625
|
-
const hasBugs =
|
|
834
|
+
const hasBugs = fs7.existsSync(path7.join(root2, ".zgoal", "config.yaml"));
|
|
626
835
|
return { hasOpenspec, hasDocs, hasJust, hasBugs };
|
|
627
836
|
}
|
|
628
837
|
|