@kurajs/cli 0.0.6 → 0.0.7
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 +95 -0
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// and never reads the filesystem (Workers-safe; mirrors June's app/_content.ts freeze).
|
|
7
7
|
// A content hash short-circuits re-embedding when nothing changed (cheap to run pre-dev).
|
|
8
8
|
import { buildIndex } from "@kurajs/docs/search";
|
|
9
|
+
import { parseMeta, validatePages } from "@kurajs/docs/meta";
|
|
9
10
|
import fs from "node:fs";
|
|
10
11
|
import path from "node:path";
|
|
11
12
|
import crypto from "node:crypto";
|
|
@@ -15,6 +16,79 @@ function arg(name, def) {
|
|
|
15
16
|
const i = process.argv.indexOf(`--${name}`);
|
|
16
17
|
return i >= 0 ? process.argv[i + 1] : def;
|
|
17
18
|
}
|
|
19
|
+
const LOCALE_DIR = /^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/;
|
|
20
|
+
// Walk one meta tree (default OR a single locale's mirror) → a folder-path-keyed MetaMap, STRICTLY
|
|
21
|
+
// validated. `skipTopLocales` excludes top-level locale buckets (true for the default tree, false
|
|
22
|
+
// inside a locale's own mirror). `wherePrefix` prefixes error locations (e.g. "ja-JP/") so a bad
|
|
23
|
+
// locale meta is unambiguous. Folder paths are keyed RELATIVE to the tree root, so a locale's keys
|
|
24
|
+
// line up 1:1 with the default's for per-folder overriding.
|
|
25
|
+
function walkMetaTree(rootDir, skipTopLocales, wherePrefix = "") {
|
|
26
|
+
const meta = {};
|
|
27
|
+
const errors = [];
|
|
28
|
+
if (!fs.existsSync(rootDir))
|
|
29
|
+
return { meta, errors };
|
|
30
|
+
const skipped = (rel, name) => skipTopLocales && rel === "" && LOCALE_DIR.test(name);
|
|
31
|
+
const walk = (dir, rel) => {
|
|
32
|
+
const ents = fs.readdirSync(dir, { withFileTypes: true });
|
|
33
|
+
if (ents.some((e) => !e.isDirectory() && e.name === "meta.json")) {
|
|
34
|
+
const where = `${wherePrefix}${rel || "."}/meta.json`;
|
|
35
|
+
let raw;
|
|
36
|
+
try {
|
|
37
|
+
raw = JSON.parse(fs.readFileSync(path.join(dir, "meta.json"), "utf8"));
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
errors.push(`${where}: invalid JSON — ${e.message}`);
|
|
41
|
+
raw = {};
|
|
42
|
+
}
|
|
43
|
+
const parsed = parseMeta(raw, where);
|
|
44
|
+
errors.push(...parsed.errors);
|
|
45
|
+
const children = new Set();
|
|
46
|
+
for (const c of ents) {
|
|
47
|
+
if (c.isDirectory() && !skipped(rel, c.name))
|
|
48
|
+
children.add(c.name);
|
|
49
|
+
else if (/\.mdx?$/.test(c.name))
|
|
50
|
+
children.add(c.name.replace(/\.mdx?$/, ""));
|
|
51
|
+
}
|
|
52
|
+
errors.push(...validatePages(parsed.meta, children, where));
|
|
53
|
+
// Root-only: every tab's pages must reference a real top-level folder.
|
|
54
|
+
if (rel === "" && parsed.meta.tabs) {
|
|
55
|
+
for (const t of parsed.meta.tabs)
|
|
56
|
+
for (const p of t.pages)
|
|
57
|
+
if (!children.has(p))
|
|
58
|
+
errors.push(`${where}: tab "${t.title}" lists "${p}", which is not a top-level folder (have: ${[...children].sort().join(", ") || "none"})`);
|
|
59
|
+
}
|
|
60
|
+
meta[rel] = parsed.meta;
|
|
61
|
+
}
|
|
62
|
+
for (const e of ents) {
|
|
63
|
+
if (e.isDirectory() && !skipped(rel, e.name))
|
|
64
|
+
walk(path.join(dir, e.name), rel ? `${rel}/${e.name}` : e.name);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
walk(rootDir, "");
|
|
68
|
+
return { meta, errors };
|
|
69
|
+
}
|
|
70
|
+
// Collect the default nav metadata PLUS every locale mirror's overrides. The default tree skips
|
|
71
|
+
// top-level locale buckets; each `content/docs/<locale>/` is then walked as its own tree, so a
|
|
72
|
+
// locale's meta.json keys match the default's by folder path (createDocs merges them per-locale).
|
|
73
|
+
// A locale typically overrides only `title` (folder group label); omitted fields fall back.
|
|
74
|
+
function collectMeta(cwd) {
|
|
75
|
+
const root = path.join(cwd, "content", "docs");
|
|
76
|
+
const errors = [];
|
|
77
|
+
const { meta, errors: e0 } = walkMetaTree(root, true);
|
|
78
|
+
errors.push(...e0);
|
|
79
|
+
const metaLocales = {};
|
|
80
|
+
if (fs.existsSync(root)) {
|
|
81
|
+
for (const ent of fs.readdirSync(root, { withFileTypes: true })) {
|
|
82
|
+
if (!ent.isDirectory() || !LOCALE_DIR.test(ent.name))
|
|
83
|
+
continue;
|
|
84
|
+
const { meta: lm, errors: le } = walkMetaTree(path.join(root, ent.name), false, `${ent.name}/`);
|
|
85
|
+
errors.push(...le);
|
|
86
|
+
if (Object.keys(lm).length)
|
|
87
|
+
metaLocales[ent.name] = lm;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { meta, metaLocales, errors };
|
|
91
|
+
}
|
|
18
92
|
async function cmdIndex() {
|
|
19
93
|
const cwd = process.cwd();
|
|
20
94
|
const contentPath = path.join(cwd, "app", "_content.ts");
|
|
@@ -25,6 +99,27 @@ async function cmdIndex() {
|
|
|
25
99
|
const model = arg("model", "Xenova/bge-m3");
|
|
26
100
|
const indexTs = path.join(cwd, "app", "_index.ts");
|
|
27
101
|
const mdxTs = path.join(cwd, "app", "_mdx.ts");
|
|
102
|
+
const metaTs = path.join(cwd, "app", "_meta.ts");
|
|
103
|
+
// Nav metadata (meta.json) — validated and frozen first, so a bad meta fails fast and cheap.
|
|
104
|
+
// META is the default tree; META_LOCALES holds each locale mirror's per-folder overrides.
|
|
105
|
+
const { meta, metaLocales, errors: metaErrors } = collectMeta(cwd);
|
|
106
|
+
if (metaErrors.length) {
|
|
107
|
+
console.error("kura index: meta.json validation failed —\n " + metaErrors.join("\n "));
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
fs.mkdirSync(path.dirname(metaTs), { recursive: true });
|
|
111
|
+
const metaContents = "// AUTO-GENERATED by `kura index` — do not edit. Frozen folder nav metadata (from meta.json).\n" +
|
|
112
|
+
`export const META = ${JSON.stringify(meta)} as const;\n` +
|
|
113
|
+
"// Per-locale overrides (content/docs/<locale>/**/meta.json), merged over META per folder.\n" +
|
|
114
|
+
`export const META_LOCALES = ${JSON.stringify(metaLocales)} as const;\n`;
|
|
115
|
+
// Write only when changed — `kura dev` re-runs index on restart, and an unconditional write would
|
|
116
|
+
// bump the mtime every time, which the dev watcher sees as an edit → infinite restart loop.
|
|
117
|
+
if (!fs.existsSync(metaTs) || fs.readFileSync(metaTs, "utf8") !== metaContents) {
|
|
118
|
+
fs.writeFileSync(metaTs, metaContents);
|
|
119
|
+
}
|
|
120
|
+
const metaCount = Object.keys(meta).length + Object.values(metaLocales).reduce((n, m) => n + Object.keys(m).length, 0);
|
|
121
|
+
if (metaCount)
|
|
122
|
+
console.log(`kura index: validated ${metaCount} meta.json file(s)`);
|
|
28
123
|
const mod = (await import(pathToFileURL(contentPath).href));
|
|
29
124
|
const DOCS = mod.DOCS ?? [];
|
|
30
125
|
if (!DOCS.length) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kurajs/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Kura CLI — dev, build, deploy, and index a Kura docs app (one surface over June).",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@kurajs/core": "^0.0.1",
|
|
18
|
-
"@kurajs/docs": "^0.0.
|
|
18
|
+
"@kurajs/docs": "^0.0.7"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"@junejs/cli": ">=0.0.25 <0.1.0",
|