@sourcescape/ds-cli 0.1.0 → 0.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/chunk-U44UTENA.js +492 -0
- package/dist/cli.js +494 -712
- package/dist/index.d.ts +127 -0
- package/dist/index.js +47 -0
- package/package.json +11 -2
package/dist/cli.js
CHANGED
|
@@ -1,176 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildPreviewHtml,
|
|
4
|
+
buildRegistry,
|
|
5
|
+
bundleJs,
|
|
6
|
+
descriptionMtime,
|
|
7
|
+
findComponent,
|
|
8
|
+
findComponentSources,
|
|
9
|
+
getSystemInfo,
|
|
10
|
+
listComponents,
|
|
11
|
+
listSystems,
|
|
12
|
+
readComponentJson,
|
|
13
|
+
readComponentMeta,
|
|
14
|
+
readDescription,
|
|
15
|
+
readFile,
|
|
16
|
+
readFullDescription,
|
|
17
|
+
resolveMarkup,
|
|
18
|
+
startPreviewServer,
|
|
19
|
+
suggestComponent,
|
|
20
|
+
systemDir,
|
|
21
|
+
systemExists
|
|
22
|
+
} from "./chunk-U44UTENA.js";
|
|
2
23
|
|
|
3
24
|
// src/cli.ts
|
|
4
25
|
import { parseArgs } from "util";
|
|
5
26
|
|
|
6
|
-
// src/systems.ts
|
|
7
|
-
import { readFileSync, readdirSync, existsSync, statSync } from "fs";
|
|
8
|
-
import { join, resolve, dirname } from "path";
|
|
9
|
-
var REPO_ROOT = resolve(dirname(new URL(import.meta.url).pathname), "..", "..");
|
|
10
|
-
var SYSTEMS_ROOT = join(REPO_ROOT, "systems");
|
|
11
|
-
function setSystemsRoot(dir) {
|
|
12
|
-
SYSTEMS_ROOT = resolve(dir);
|
|
13
|
-
}
|
|
14
|
-
function listSystems() {
|
|
15
|
-
return readdirSync(SYSTEMS_ROOT, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
16
|
-
}
|
|
17
|
-
function systemDir(system) {
|
|
18
|
-
return join(SYSTEMS_ROOT, system);
|
|
19
|
-
}
|
|
20
|
-
function systemExists(system) {
|
|
21
|
-
return existsSync(systemDir(system));
|
|
22
|
-
}
|
|
23
|
-
function readFirstHeading(filePath) {
|
|
24
|
-
if (!existsSync(filePath)) return "";
|
|
25
|
-
const content = readFileSync(filePath, "utf-8");
|
|
26
|
-
const match = content.match(/^#\s+(.+)$/m);
|
|
27
|
-
return match ? match[1].trim() : "";
|
|
28
|
-
}
|
|
29
|
-
function readFirstParagraph(filePath) {
|
|
30
|
-
if (!existsSync(filePath)) return "";
|
|
31
|
-
const lines = readFileSync(filePath, "utf-8").split("\n");
|
|
32
|
-
let pastHeading = false;
|
|
33
|
-
for (const line of lines) {
|
|
34
|
-
if (line.startsWith("# ")) {
|
|
35
|
-
pastHeading = true;
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
if (pastHeading && line.trim().length > 0 && !line.startsWith("#")) {
|
|
39
|
-
return line.trim();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return "";
|
|
43
|
-
}
|
|
44
|
-
function readFile(filePath) {
|
|
45
|
-
return readFileSync(filePath, "utf-8");
|
|
46
|
-
}
|
|
47
|
-
function getSystemInfo(system) {
|
|
48
|
-
const descPath = join(systemDir(system), "DESCRIPTION.md");
|
|
49
|
-
return {
|
|
50
|
-
name: system,
|
|
51
|
-
description: readFirstHeading(descPath)
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
function readDescription(system) {
|
|
55
|
-
const descPath = join(systemDir(system), "DESCRIPTION.md");
|
|
56
|
-
if (!existsSync(descPath)) return "";
|
|
57
|
-
return readFile(descPath);
|
|
58
|
-
}
|
|
59
|
-
function listComponents(system) {
|
|
60
|
-
const compsDir = join(systemDir(system), "components");
|
|
61
|
-
const results = [];
|
|
62
|
-
for (const kind of ["molecules", "cells"]) {
|
|
63
|
-
const kindDir = join(compsDir, kind);
|
|
64
|
-
if (!existsSync(kindDir)) continue;
|
|
65
|
-
const dirs = readdirSync(kindDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
66
|
-
for (const name of dirs) {
|
|
67
|
-
const descPath = join(kindDir, name, "description.md");
|
|
68
|
-
results.push({
|
|
69
|
-
name,
|
|
70
|
-
kind,
|
|
71
|
-
description: readFirstParagraph(descPath)
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return results;
|
|
76
|
-
}
|
|
77
|
-
function findComponent(system, name) {
|
|
78
|
-
const compsDir = join(systemDir(system), "components");
|
|
79
|
-
for (const kind of ["molecules", "cells"]) {
|
|
80
|
-
const dir = join(compsDir, kind, name);
|
|
81
|
-
if (existsSync(dir) && statSync(dir).isDirectory()) {
|
|
82
|
-
return { kind, dir };
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
function allComponentNames(system) {
|
|
88
|
-
const compsDir = join(systemDir(system), "components");
|
|
89
|
-
const results = [];
|
|
90
|
-
for (const kind of ["molecules", "cells"]) {
|
|
91
|
-
const kindDir = join(compsDir, kind);
|
|
92
|
-
if (!existsSync(kindDir)) continue;
|
|
93
|
-
const dirs = readdirSync(kindDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => ({ name: d.name, kind }));
|
|
94
|
-
results.push(...dirs);
|
|
95
|
-
}
|
|
96
|
-
return results;
|
|
97
|
-
}
|
|
98
|
-
function listTokenFiles(system) {
|
|
99
|
-
const tokensDir = join(systemDir(system), "tokens");
|
|
100
|
-
if (!existsSync(tokensDir)) return [];
|
|
101
|
-
return readdirSync(tokensDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", "")).sort();
|
|
102
|
-
}
|
|
103
|
-
function readTokenFile(system, name) {
|
|
104
|
-
const filePath = join(systemDir(system), "tokens", `${name}.json`);
|
|
105
|
-
if (!existsSync(filePath)) return null;
|
|
106
|
-
return JSON.parse(readFile(filePath));
|
|
107
|
-
}
|
|
108
|
-
function listPrinciples(system) {
|
|
109
|
-
const dir = join(systemDir(system), "principles");
|
|
110
|
-
if (!existsSync(dir)) return [];
|
|
111
|
-
return readdirSync(dir).filter((f) => f.endsWith(".md") && f !== "index.md").map((f) => {
|
|
112
|
-
const name = f.replace(".md", "");
|
|
113
|
-
const title = readFirstHeading(join(dir, f));
|
|
114
|
-
return { name, title: title || name };
|
|
115
|
-
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
116
|
-
}
|
|
117
|
-
function readPrinciple(system, name) {
|
|
118
|
-
const filePath = join(systemDir(system), "principles", `${name}.md`);
|
|
119
|
-
if (!existsSync(filePath)) return null;
|
|
120
|
-
return readFile(filePath);
|
|
121
|
-
}
|
|
122
|
-
function hasArchitecture(system) {
|
|
123
|
-
return existsSync(join(systemDir(system), "architecture"));
|
|
124
|
-
}
|
|
125
|
-
function listArchitecture(system, subpath) {
|
|
126
|
-
const baseDir = join(systemDir(system), "architecture");
|
|
127
|
-
const dir = subpath ? join(baseDir, subpath) : baseDir;
|
|
128
|
-
if (!existsSync(dir)) return { entries: [], indexContent: null };
|
|
129
|
-
const indexPath = join(dir, "index.md");
|
|
130
|
-
const indexContent = existsSync(indexPath) ? readFile(indexPath) : null;
|
|
131
|
-
const entries = readdirSync(dir, { withFileTypes: true }).filter((d) => d.name !== "index.md").map((d) => ({ name: d.name, isDir: d.isDirectory() })).sort((a, b) => {
|
|
132
|
-
if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
|
|
133
|
-
return a.name.localeCompare(b.name);
|
|
134
|
-
});
|
|
135
|
-
return { entries, indexContent };
|
|
136
|
-
}
|
|
137
|
-
function readArchitectureFile(system, subpath) {
|
|
138
|
-
const filePath = join(systemDir(system), "architecture", subpath);
|
|
139
|
-
if (!existsSync(filePath)) return null;
|
|
140
|
-
if (statSync(filePath).isDirectory()) return null;
|
|
141
|
-
return readFile(filePath);
|
|
142
|
-
}
|
|
143
|
-
function isArchitectureDir(system, subpath) {
|
|
144
|
-
const filePath = join(systemDir(system), "architecture", subpath);
|
|
145
|
-
return existsSync(filePath) && statSync(filePath).isDirectory();
|
|
146
|
-
}
|
|
147
|
-
function listExamples(system) {
|
|
148
|
-
const dir = join(systemDir(system), "examples");
|
|
149
|
-
if (!existsSync(dir)) return [];
|
|
150
|
-
return readdirSync(dir).filter((f) => f.endsWith(".html")).sort();
|
|
151
|
-
}
|
|
152
|
-
function listReferences(system) {
|
|
153
|
-
const dir = join(systemDir(system), "references");
|
|
154
|
-
if (!existsSync(dir)) return [];
|
|
155
|
-
return readdirSync(dir, { withFileTypes: true }).filter((d) => !d.isDirectory()).map((d) => d.name).sort();
|
|
156
|
-
}
|
|
157
|
-
function suggestComponent(system, query) {
|
|
158
|
-
const all = allComponentNames(system);
|
|
159
|
-
const q = query.toLowerCase();
|
|
160
|
-
return all.filter((c) => c.name.includes(q) || q.includes(c.name)).map((c) => `${c.name} (${c.kind})`);
|
|
161
|
-
}
|
|
162
|
-
function findComponentSources(system, name) {
|
|
163
|
-
const found = findComponent(system, name);
|
|
164
|
-
if (!found) return { css: [], js: [] };
|
|
165
|
-
const css = [];
|
|
166
|
-
const js = [];
|
|
167
|
-
const stylePath = join(found.dir, "style.css");
|
|
168
|
-
const scriptPath = join(found.dir, "script.js");
|
|
169
|
-
if (existsSync(stylePath)) css.push(stylePath);
|
|
170
|
-
if (existsSync(scriptPath)) js.push(scriptPath);
|
|
171
|
-
return { css, js };
|
|
172
|
-
}
|
|
173
|
-
|
|
174
27
|
// src/format.ts
|
|
175
28
|
function heading(text) {
|
|
176
29
|
console.log(`
|
|
@@ -181,12 +34,6 @@ function subheading(text) {
|
|
|
181
34
|
console.log(`
|
|
182
35
|
${text}`);
|
|
183
36
|
}
|
|
184
|
-
function list(items, indent = 2) {
|
|
185
|
-
const pad = " ".repeat(indent);
|
|
186
|
-
for (const item of items) {
|
|
187
|
-
console.log(`${pad}${item}`);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
37
|
function descriptionList(items, indent = 2) {
|
|
191
38
|
if (items.length === 0) return;
|
|
192
39
|
const maxName = Math.max(...items.map((i) => i.name.length));
|
|
@@ -196,9 +43,6 @@ function descriptionList(items, indent = 2) {
|
|
|
196
43
|
console.log(`${pad}${item.name.padEnd(maxName)}${desc}`);
|
|
197
44
|
}
|
|
198
45
|
}
|
|
199
|
-
function countLine(label, count) {
|
|
200
|
-
console.log(` ${label}: ${count}`);
|
|
201
|
-
}
|
|
202
46
|
function error(message, suggestions) {
|
|
203
47
|
console.error(`Error: ${message}`);
|
|
204
48
|
if (suggestions && suggestions.length > 0) {
|
|
@@ -229,82 +73,15 @@ function commandList() {
|
|
|
229
73
|
return 0;
|
|
230
74
|
}
|
|
231
75
|
|
|
232
|
-
// src/tokens.ts
|
|
233
|
-
function isToken(obj) {
|
|
234
|
-
return typeof obj === "object" && obj !== null && "$value" in obj;
|
|
235
|
-
}
|
|
236
|
-
function flattenTokens(obj, prefix = "") {
|
|
237
|
-
const tokens = [];
|
|
238
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
239
|
-
if (key.startsWith("$")) continue;
|
|
240
|
-
const path = prefix ? `${prefix}.${key}` : key;
|
|
241
|
-
if (isToken(value)) {
|
|
242
|
-
tokens.push({
|
|
243
|
-
path,
|
|
244
|
-
$value: value.$value,
|
|
245
|
-
$type: value.$type ?? "unknown"
|
|
246
|
-
});
|
|
247
|
-
} else if (typeof value === "object" && value !== null) {
|
|
248
|
-
tokens.push(...flattenTokens(value, path));
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
return tokens;
|
|
252
|
-
}
|
|
253
|
-
function groupTokens(tokens) {
|
|
254
|
-
const groups = /* @__PURE__ */ new Map();
|
|
255
|
-
for (const token of tokens) {
|
|
256
|
-
const category = token.path.split(".")[0];
|
|
257
|
-
if (!groups.has(category)) {
|
|
258
|
-
groups.set(category, []);
|
|
259
|
-
}
|
|
260
|
-
groups.get(category).push(token);
|
|
261
|
-
}
|
|
262
|
-
return Array.from(groups.entries()).map(([name, tokens2]) => ({
|
|
263
|
-
name,
|
|
264
|
-
tokens: tokens2
|
|
265
|
-
}));
|
|
266
|
-
}
|
|
267
|
-
function formatValue(value) {
|
|
268
|
-
if (typeof value === "string") return value;
|
|
269
|
-
if (typeof value === "number") return String(value);
|
|
270
|
-
if (typeof value === "object" && value !== null) {
|
|
271
|
-
return JSON.stringify(value);
|
|
272
|
-
}
|
|
273
|
-
return String(value);
|
|
274
|
-
}
|
|
275
|
-
function renderTokens(data) {
|
|
276
|
-
const tokens = flattenTokens(data);
|
|
277
|
-
const groups = groupTokens(tokens);
|
|
278
|
-
for (const group of groups) {
|
|
279
|
-
console.log(`
|
|
280
|
-
${group.name}`);
|
|
281
|
-
console.log("\u2500".repeat(Math.min(group.name.length, 40)));
|
|
282
|
-
const maxPath = Math.max(...group.tokens.map((t) => t.path.length));
|
|
283
|
-
for (const token of group.tokens) {
|
|
284
|
-
const typeTag = token.$type !== "unknown" ? ` [${token.$type}]` : "";
|
|
285
|
-
console.log(` ${token.path.padEnd(maxPath)} ${formatValue(token.$value)}${typeTag}`);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
console.log();
|
|
289
|
-
}
|
|
290
|
-
function renderFlat(data) {
|
|
291
|
-
const tokens = flattenTokens(data);
|
|
292
|
-
for (const token of tokens) {
|
|
293
|
-
console.log(`${token.path} = ${formatValue(token.$value)}`);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
function renderJson(data) {
|
|
297
|
-
console.log(JSON.stringify(data, null, 2));
|
|
298
|
-
}
|
|
299
|
-
function tokenSummary(data) {
|
|
300
|
-
const tokens = flattenTokens(data);
|
|
301
|
-
const types = new Set(tokens.map((t) => t.$type));
|
|
302
|
-
const typeList = Array.from(types).filter((t) => t !== "unknown").sort();
|
|
303
|
-
const typeSuffix = typeList.length > 0 ? ` (${typeList.join(", ")})` : "";
|
|
304
|
-
return `${tokens.length} tokens${typeSuffix}`;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
76
|
// src/commands/show.ts
|
|
77
|
+
import { existsSync, readFileSync } from "fs";
|
|
78
|
+
import { join } from "path";
|
|
79
|
+
function loadManifest(system) {
|
|
80
|
+
const path = join(systemDir(system), "references", "manifest.json");
|
|
81
|
+
if (!existsSync(path)) return [];
|
|
82
|
+
const data = JSON.parse(readFileSync(path, "utf-8"));
|
|
83
|
+
return data.references || [];
|
|
84
|
+
}
|
|
308
85
|
function commandShow(system) {
|
|
309
86
|
if (!systemExists(system)) {
|
|
310
87
|
const available = listSystems();
|
|
@@ -321,48 +98,312 @@ function commandShow(system) {
|
|
|
321
98
|
if (desc) {
|
|
322
99
|
markdown(desc);
|
|
323
100
|
}
|
|
324
|
-
heading("Sections");
|
|
325
101
|
const components = listComponents(system);
|
|
326
102
|
const molecules = components.filter((c) => c.kind === "molecules");
|
|
327
103
|
const cells = components.filter((c) => c.kind === "cells");
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
console.log(
|
|
104
|
+
console.log("\n## Component Inventory");
|
|
105
|
+
function printTable(label, items) {
|
|
106
|
+
const metas = items.map((c) => readComponentMeta(system, c.name));
|
|
107
|
+
console.log(`
|
|
108
|
+
### ${label} (${items.length} active)
|
|
109
|
+
`);
|
|
110
|
+
console.log("| Component | Tag | Description |");
|
|
111
|
+
console.log("|---|---|---|");
|
|
112
|
+
for (const m of metas) {
|
|
113
|
+
console.log(`| ${m.name} | ${m.tag} | ${m.description} |`);
|
|
338
114
|
}
|
|
339
115
|
}
|
|
340
|
-
|
|
341
|
-
if (
|
|
342
|
-
|
|
343
|
-
|
|
116
|
+
if (molecules.length > 0) printTable("Molecules", molecules);
|
|
117
|
+
if (cells.length > 0) printTable("Cells", cells);
|
|
118
|
+
const refs = loadManifest(system);
|
|
119
|
+
if (refs.length > 0) {
|
|
120
|
+
console.log("\n## References\n");
|
|
121
|
+
for (const ref of refs) {
|
|
122
|
+
console.log(`- \`${ref.file}\` \u2014 ${ref.description}`);
|
|
123
|
+
}
|
|
344
124
|
}
|
|
345
|
-
const examples = listExamples(system);
|
|
346
|
-
if (examples.length > 0) countLine("Examples", examples.length);
|
|
347
|
-
const references = listReferences(system);
|
|
348
|
-
if (references.length > 0) countLine("References", references.length);
|
|
349
125
|
blank();
|
|
350
126
|
return 0;
|
|
351
127
|
}
|
|
352
128
|
|
|
353
129
|
// src/commands/browse.ts
|
|
354
|
-
import { join as join5 } from "path";
|
|
355
|
-
import { existsSync as existsSync6 } from "fs";
|
|
356
130
|
import { exec } from "child_process";
|
|
357
131
|
|
|
358
132
|
// src/commands/components.ts
|
|
359
|
-
import { join as
|
|
360
|
-
import { existsSync as
|
|
133
|
+
import { join as join3, basename, relative } from "path";
|
|
134
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync } from "fs";
|
|
135
|
+
|
|
136
|
+
// src/search/tfidf.ts
|
|
137
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
138
|
+
"a",
|
|
139
|
+
"an",
|
|
140
|
+
"the",
|
|
141
|
+
"and",
|
|
142
|
+
"or",
|
|
143
|
+
"but",
|
|
144
|
+
"in",
|
|
145
|
+
"on",
|
|
146
|
+
"at",
|
|
147
|
+
"to",
|
|
148
|
+
"for",
|
|
149
|
+
"of",
|
|
150
|
+
"with",
|
|
151
|
+
"by",
|
|
152
|
+
"from",
|
|
153
|
+
"is",
|
|
154
|
+
"are",
|
|
155
|
+
"was",
|
|
156
|
+
"were",
|
|
157
|
+
"be",
|
|
158
|
+
"been",
|
|
159
|
+
"has",
|
|
160
|
+
"have",
|
|
161
|
+
"had",
|
|
162
|
+
"do",
|
|
163
|
+
"does",
|
|
164
|
+
"did",
|
|
165
|
+
"will",
|
|
166
|
+
"would",
|
|
167
|
+
"could",
|
|
168
|
+
"should",
|
|
169
|
+
"may",
|
|
170
|
+
"might",
|
|
171
|
+
"can",
|
|
172
|
+
"this",
|
|
173
|
+
"that",
|
|
174
|
+
"these",
|
|
175
|
+
"those",
|
|
176
|
+
"it",
|
|
177
|
+
"its",
|
|
178
|
+
"not",
|
|
179
|
+
"no",
|
|
180
|
+
"as",
|
|
181
|
+
"if",
|
|
182
|
+
"so",
|
|
183
|
+
"than",
|
|
184
|
+
"up",
|
|
185
|
+
"out",
|
|
186
|
+
"about",
|
|
187
|
+
"into",
|
|
188
|
+
"over",
|
|
189
|
+
"after",
|
|
190
|
+
"before",
|
|
191
|
+
"between",
|
|
192
|
+
"under",
|
|
193
|
+
"such",
|
|
194
|
+
"each",
|
|
195
|
+
"all",
|
|
196
|
+
"any",
|
|
197
|
+
"both",
|
|
198
|
+
"more",
|
|
199
|
+
"other",
|
|
200
|
+
"some"
|
|
201
|
+
]);
|
|
202
|
+
function stripMarkdown(text) {
|
|
203
|
+
return text.replace(/^#{1,6}\s+/gm, "").replace(/```[\s\S]*?```/g, "").replace(/`[^`]+`/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/[|─┌┐└┘├┤┬┴┼]/g, " ").replace(/<[^>]+>/g, "").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/!\[[^\]]*\]\([^)]+\)/g, "").replace(/^[-*]\s+/gm, "").replace(/^\d+\.\s+/gm, "").replace(/^>\s+/gm, "");
|
|
204
|
+
}
|
|
205
|
+
function tokenize(text) {
|
|
206
|
+
return text.toLowerCase().replace(/[^a-z0-9-]/g, " ").split(/\s+/).filter((t) => t.length > 1 && !STOP_WORDS.has(t));
|
|
207
|
+
}
|
|
208
|
+
function computeTF(tokens) {
|
|
209
|
+
const counts = {};
|
|
210
|
+
for (const t of tokens) {
|
|
211
|
+
counts[t] = (counts[t] || 0) + 1;
|
|
212
|
+
}
|
|
213
|
+
const len = tokens.length || 1;
|
|
214
|
+
const tf = {};
|
|
215
|
+
for (const [term, count] of Object.entries(counts)) {
|
|
216
|
+
tf[term] = count / len;
|
|
217
|
+
}
|
|
218
|
+
return tf;
|
|
219
|
+
}
|
|
220
|
+
function computeIDF(documents) {
|
|
221
|
+
const N = documents.length || 1;
|
|
222
|
+
const docFreq = {};
|
|
223
|
+
for (const tokens of documents) {
|
|
224
|
+
const seen = new Set(tokens);
|
|
225
|
+
for (const t of seen) {
|
|
226
|
+
docFreq[t] = (docFreq[t] || 0) + 1;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const idf = {};
|
|
230
|
+
for (const [term, df] of Object.entries(docFreq)) {
|
|
231
|
+
idf[term] = Math.log(1 + N / df);
|
|
232
|
+
}
|
|
233
|
+
return idf;
|
|
234
|
+
}
|
|
235
|
+
function computeTFIDF(tf, idf) {
|
|
236
|
+
const tfidf = {};
|
|
237
|
+
for (const [term, tfVal] of Object.entries(tf)) {
|
|
238
|
+
const idfVal = idf[term] ?? Math.log(1 + 1);
|
|
239
|
+
tfidf[term] = tfVal * idfVal;
|
|
240
|
+
}
|
|
241
|
+
return tfidf;
|
|
242
|
+
}
|
|
243
|
+
function cosineSimilarity(a, b) {
|
|
244
|
+
let dot = 0;
|
|
245
|
+
let magA = 0;
|
|
246
|
+
let magB = 0;
|
|
247
|
+
for (const [term, val] of Object.entries(a)) {
|
|
248
|
+
magA += val * val;
|
|
249
|
+
if (term in b) {
|
|
250
|
+
dot += val * b[term];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
for (const val of Object.values(b)) {
|
|
254
|
+
magB += val * val;
|
|
255
|
+
}
|
|
256
|
+
const denom = Math.sqrt(magA) * Math.sqrt(magB);
|
|
257
|
+
return denom === 0 ? 0 : dot / denom;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/search/indexer.ts
|
|
261
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2 } from "fs";
|
|
262
|
+
import { join as join2 } from "path";
|
|
263
|
+
var INDEX_FILE = ".search-index.json";
|
|
264
|
+
function indexPath(system) {
|
|
265
|
+
return join2(systemDir(system), INDEX_FILE);
|
|
266
|
+
}
|
|
267
|
+
function buildSearchIndex(system) {
|
|
268
|
+
const components = listComponents(system);
|
|
269
|
+
const docTokens = [];
|
|
270
|
+
const rawEntries = [];
|
|
271
|
+
for (const comp of components) {
|
|
272
|
+
const fullDesc = readFullDescription(system, comp.name, comp.kind);
|
|
273
|
+
const plain = stripMarkdown(fullDesc || comp.description);
|
|
274
|
+
const tokens = tokenize(`${comp.name} ${plain}`);
|
|
275
|
+
const unique = [...new Set(tokens)];
|
|
276
|
+
docTokens.push(unique);
|
|
277
|
+
rawEntries.push({
|
|
278
|
+
name: comp.name,
|
|
279
|
+
kind: comp.kind,
|
|
280
|
+
summary: comp.description.slice(0, 200),
|
|
281
|
+
tokens: unique,
|
|
282
|
+
tf: computeTF(tokens),
|
|
283
|
+
mtime: descriptionMtime(system, comp.name, comp.kind)
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
const idf = computeIDF(docTokens);
|
|
287
|
+
const entries = rawEntries.map((raw) => ({
|
|
288
|
+
name: raw.name,
|
|
289
|
+
kind: raw.kind,
|
|
290
|
+
summary: raw.summary,
|
|
291
|
+
tfidf: computeTFIDF(raw.tf, idf),
|
|
292
|
+
tokens: raw.tokens,
|
|
293
|
+
descriptionMtime: raw.mtime
|
|
294
|
+
}));
|
|
295
|
+
return {
|
|
296
|
+
version: 1,
|
|
297
|
+
buildTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
298
|
+
systemName: system,
|
|
299
|
+
documentCount: entries.length,
|
|
300
|
+
idf,
|
|
301
|
+
entries
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function writeSearchIndex(system, index) {
|
|
305
|
+
try {
|
|
306
|
+
writeFileSync(indexPath(system), JSON.stringify(index, null, 2), "utf-8");
|
|
307
|
+
} catch {
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function loadSearchIndex(system) {
|
|
311
|
+
const path = indexPath(system);
|
|
312
|
+
if (!existsSync2(path)) return null;
|
|
313
|
+
try {
|
|
314
|
+
const data = JSON.parse(readFileSync2(path, "utf-8"));
|
|
315
|
+
if (data.version !== 1) return null;
|
|
316
|
+
return data;
|
|
317
|
+
} catch {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function isStale(system, index) {
|
|
322
|
+
for (const entry of index.entries) {
|
|
323
|
+
const currentMtime = descriptionMtime(system, entry.name, entry.kind);
|
|
324
|
+
if (currentMtime !== entry.descriptionMtime) return true;
|
|
325
|
+
}
|
|
326
|
+
const currentCount = listComponents(system).length;
|
|
327
|
+
if (currentCount !== index.documentCount) return true;
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
function ensureSearchIndex(system) {
|
|
331
|
+
const existing = loadSearchIndex(system);
|
|
332
|
+
if (existing && !isStale(system, existing)) {
|
|
333
|
+
return existing;
|
|
334
|
+
}
|
|
335
|
+
const index = buildSearchIndex(system);
|
|
336
|
+
writeSearchIndex(system, index);
|
|
337
|
+
return index;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/search/search.ts
|
|
341
|
+
var NAME_WEIGHT = 0.4;
|
|
342
|
+
var KEYWORD_WEIGHT = 0.3;
|
|
343
|
+
var TFIDF_WEIGHT = 0.3;
|
|
344
|
+
function nameScore(queryTokens, componentName) {
|
|
345
|
+
const name = componentName.toLowerCase();
|
|
346
|
+
const parts = name.split("-");
|
|
347
|
+
let matched = 0;
|
|
348
|
+
for (const qt of queryTokens) {
|
|
349
|
+
if (name.includes(qt) || parts.some((p) => p.includes(qt) || qt.includes(p))) {
|
|
350
|
+
matched++;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return queryTokens.length === 0 ? 0 : matched / queryTokens.length;
|
|
354
|
+
}
|
|
355
|
+
function keywordScore(queryTokens, docTokens) {
|
|
356
|
+
if (queryTokens.length === 0) return 0;
|
|
357
|
+
const docSet = new Set(docTokens);
|
|
358
|
+
let matched = 0;
|
|
359
|
+
for (const qt of queryTokens) {
|
|
360
|
+
if (docSet.has(qt)) {
|
|
361
|
+
matched++;
|
|
362
|
+
} else {
|
|
363
|
+
for (const dt of docTokens) {
|
|
364
|
+
if (dt.includes(qt) || qt.includes(dt)) {
|
|
365
|
+
matched += 0.5;
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return matched / queryTokens.length;
|
|
372
|
+
}
|
|
373
|
+
function searchComponents(system, query) {
|
|
374
|
+
const index = ensureSearchIndex(system);
|
|
375
|
+
const queryTokens = tokenize(query);
|
|
376
|
+
if (queryTokens.length === 0) return [];
|
|
377
|
+
const queryTF = computeTF(queryTokens);
|
|
378
|
+
const queryVec = computeTFIDF(queryTF, index.idf);
|
|
379
|
+
const results = [];
|
|
380
|
+
for (const entry of index.entries) {
|
|
381
|
+
const ns = nameScore(queryTokens, entry.name);
|
|
382
|
+
const ks = keywordScore(queryTokens, entry.tokens);
|
|
383
|
+
const ts = cosineSimilarity(queryVec, entry.tfidf);
|
|
384
|
+
const score = NAME_WEIGHT * ns + KEYWORD_WEIGHT * ks + TFIDF_WEIGHT * ts;
|
|
385
|
+
if (score > 0.01) {
|
|
386
|
+
results.push({
|
|
387
|
+
name: entry.name,
|
|
388
|
+
kind: entry.kind,
|
|
389
|
+
summary: entry.summary,
|
|
390
|
+
score: Math.round(score * 100) / 100
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
results.sort((a, b) => b.score - a.score);
|
|
395
|
+
return results;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// src/commands/components.ts
|
|
361
399
|
async function commandComponents(system, args) {
|
|
362
400
|
const [subcommand, ...rest] = args;
|
|
363
401
|
if (!subcommand || subcommand === "list") {
|
|
364
402
|
return listAllComponents(system);
|
|
365
403
|
}
|
|
404
|
+
if (subcommand === "inventory") {
|
|
405
|
+
return showInventory(system);
|
|
406
|
+
}
|
|
366
407
|
if (subcommand === "details") {
|
|
367
408
|
const name = rest[0];
|
|
368
409
|
if (!name) {
|
|
@@ -371,6 +412,17 @@ async function commandComponents(system, args) {
|
|
|
371
412
|
}
|
|
372
413
|
return showDetails(system, name);
|
|
373
414
|
}
|
|
415
|
+
if (subcommand === "search") {
|
|
416
|
+
const query = rest.join(" ");
|
|
417
|
+
if (!query) {
|
|
418
|
+
error("Usage: ds <system> components search <query>");
|
|
419
|
+
return 1;
|
|
420
|
+
}
|
|
421
|
+
return showSearch(system, query);
|
|
422
|
+
}
|
|
423
|
+
if (subcommand === "index") {
|
|
424
|
+
return rebuildIndex(system);
|
|
425
|
+
}
|
|
374
426
|
return showDetails(system, subcommand);
|
|
375
427
|
}
|
|
376
428
|
function listAllComponents(system) {
|
|
@@ -385,14 +437,20 @@ function listAllComponents(system) {
|
|
|
385
437
|
if (molecules.length > 0) {
|
|
386
438
|
subheading(`Molecules (${molecules.length})`);
|
|
387
439
|
descriptionList(
|
|
388
|
-
molecules.map((c) => ({
|
|
440
|
+
molecules.map((c) => ({
|
|
441
|
+
name: c.tag ? `${c.name} ${c.tag}` : c.name,
|
|
442
|
+
description: c.description
|
|
443
|
+
})),
|
|
389
444
|
4
|
|
390
445
|
);
|
|
391
446
|
}
|
|
392
447
|
if (cells.length > 0) {
|
|
393
448
|
subheading(`Cells (${cells.length})`);
|
|
394
449
|
descriptionList(
|
|
395
|
-
cells.map((c) => ({
|
|
450
|
+
cells.map((c) => ({
|
|
451
|
+
name: c.tag ? `${c.name} ${c.tag}` : c.name,
|
|
452
|
+
description: c.description
|
|
453
|
+
})),
|
|
396
454
|
4
|
|
397
455
|
);
|
|
398
456
|
}
|
|
@@ -408,244 +466,188 @@ function showDetails(system, name) {
|
|
|
408
466
|
}
|
|
409
467
|
const sysDir = systemDir(system);
|
|
410
468
|
const relPath = relative(sysDir, found.dir);
|
|
469
|
+
const json = readComponentJson(found.dir);
|
|
411
470
|
heading(`${name}`);
|
|
412
471
|
console.log(` Kind: ${found.kind}`);
|
|
472
|
+
if (json?.tag) console.log(` Tag: ${json.tag}`);
|
|
413
473
|
console.log(` Path: ${relPath}`);
|
|
414
|
-
const files =
|
|
474
|
+
const files = readdirSync(found.dir);
|
|
415
475
|
console.log(` Files: ${files.join(", ")}`);
|
|
416
476
|
blank();
|
|
417
|
-
|
|
418
|
-
if (existsSync2(descPath)) {
|
|
477
|
+
if (json?.description) {
|
|
419
478
|
subheading("Description");
|
|
420
479
|
blank();
|
|
480
|
+
console.log(` ${json.description}`);
|
|
481
|
+
blank();
|
|
482
|
+
}
|
|
483
|
+
if (json?.example) {
|
|
484
|
+
subheading("Example");
|
|
485
|
+
blank();
|
|
486
|
+
console.log("```xml");
|
|
487
|
+
console.log(json.example);
|
|
488
|
+
console.log("```");
|
|
489
|
+
blank();
|
|
490
|
+
}
|
|
491
|
+
const descPath = join3(found.dir, "description.md");
|
|
492
|
+
if (existsSync3(descPath)) {
|
|
493
|
+
subheading("Description (full)");
|
|
494
|
+
blank();
|
|
421
495
|
console.log(readFile(descPath));
|
|
422
496
|
}
|
|
423
497
|
const sources = findComponentSources(system, name);
|
|
424
498
|
if (sources.css.length > 0) {
|
|
425
499
|
for (const cssPath of sources.css) {
|
|
426
|
-
subheading(`Source CSS: ${
|
|
500
|
+
subheading(`Source CSS: ${basename(cssPath)}`);
|
|
427
501
|
blank();
|
|
428
502
|
console.log("```css");
|
|
429
|
-
console.log(
|
|
503
|
+
console.log(readFileSync3(cssPath, "utf-8").trimEnd());
|
|
430
504
|
console.log("```");
|
|
431
505
|
blank();
|
|
432
506
|
}
|
|
433
507
|
}
|
|
434
508
|
if (sources.js.length > 0) {
|
|
435
509
|
for (const jsPath of sources.js) {
|
|
436
|
-
subheading(`Source JS: ${
|
|
510
|
+
subheading(`Source JS: ${basename(jsPath)}`);
|
|
437
511
|
blank();
|
|
438
512
|
console.log("```js");
|
|
439
|
-
console.log(
|
|
513
|
+
console.log(readFileSync3(jsPath, "utf-8").trimEnd());
|
|
440
514
|
console.log("```");
|
|
441
515
|
blank();
|
|
442
516
|
}
|
|
443
517
|
}
|
|
444
518
|
return 0;
|
|
445
519
|
}
|
|
446
|
-
|
|
447
|
-
// src/commands/render.ts
|
|
448
|
-
import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
|
|
449
|
-
import { resolve as resolve2 } from "path";
|
|
450
|
-
|
|
451
|
-
// src/resolve-deps.ts
|
|
452
|
-
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
453
|
-
import { join as join3, relative as relative2 } from "path";
|
|
454
|
-
function buildRegistry(system) {
|
|
520
|
+
function showInventory(system) {
|
|
455
521
|
const components = listComponents(system);
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const found = findComponent(system, comp.name);
|
|
460
|
-
if (!found) continue;
|
|
461
|
-
const scriptPath = join3(found.dir, "script.js");
|
|
462
|
-
const stylePath = join3(found.dir, "style.css");
|
|
463
|
-
const hasJs = existsSync3(scriptPath);
|
|
464
|
-
const hasCss = existsSync3(stylePath);
|
|
465
|
-
const tags = [];
|
|
466
|
-
const dynamicTags = [];
|
|
467
|
-
if (hasJs) {
|
|
468
|
-
const js = readFileSync3(scriptPath, "utf-8");
|
|
469
|
-
for (const m of js.matchAll(/customElements\.define\(\s*['"]([^'"]+)['"]/g)) {
|
|
470
|
-
tags.push(m[1]);
|
|
471
|
-
}
|
|
472
|
-
for (const m of js.matchAll(/document\.createElement\(\s*['"]([a-z][a-z0-9]*-[a-z0-9-]*)['"]\s*\)/g)) {
|
|
473
|
-
if (!tags.includes(m[1])) dynamicTags.push(m[1]);
|
|
474
|
-
}
|
|
475
|
-
for (const m of js.matchAll(/<([a-z][a-z0-9]*-[a-z0-9-]*)[>\s/'"]/g)) {
|
|
476
|
-
const tag = m[1];
|
|
477
|
-
if (!tags.includes(tag) && !dynamicTags.includes(tag)) {
|
|
478
|
-
dynamicTags.push(tag);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
if (hasCss) {
|
|
483
|
-
const css = readFileSync3(stylePath, "utf-8");
|
|
484
|
-
for (const m of css.matchAll(/(?:^|[\s,}>+~])([a-z][a-z0-9]*-[a-z0-9-]*)(?=[\s,{[:.>+~]|$)/gm)) {
|
|
485
|
-
const tag = m[1];
|
|
486
|
-
if (!tags.includes(tag)) tags.push(tag);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
const dirName = comp.name;
|
|
490
|
-
if (dirName.includes("-") && !tags.includes(dirName)) {
|
|
491
|
-
tags.push(dirName);
|
|
492
|
-
}
|
|
493
|
-
const prdName = `prd-${dirName}`;
|
|
494
|
-
if (!tags.includes(prdName)) {
|
|
495
|
-
tags.push(prdName);
|
|
496
|
-
}
|
|
497
|
-
const entry = {
|
|
498
|
-
name: comp.name,
|
|
499
|
-
kind: comp.kind,
|
|
500
|
-
dir: found.dir,
|
|
501
|
-
tags,
|
|
502
|
-
dynamicTags,
|
|
503
|
-
hasCss,
|
|
504
|
-
hasJs
|
|
505
|
-
};
|
|
506
|
-
entries.push(entry);
|
|
507
|
-
for (const tag of tags) {
|
|
508
|
-
if (!tagMap.has(tag)) tagMap.set(tag, entry);
|
|
509
|
-
}
|
|
522
|
+
if (components.length === 0) {
|
|
523
|
+
console.log(`No components found in ${system}.`);
|
|
524
|
+
return 0;
|
|
510
525
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
const unmatchedTags = [];
|
|
520
|
-
const resolved = /* @__PURE__ */ new Set();
|
|
521
|
-
const queue = [...markupTags];
|
|
522
|
-
while (queue.length > 0) {
|
|
523
|
-
const tag = queue.pop();
|
|
524
|
-
if (resolved.has(tag)) continue;
|
|
525
|
-
resolved.add(tag);
|
|
526
|
-
const entry = registry.tagMap.get(tag);
|
|
527
|
-
if (entry) {
|
|
528
|
-
if (!matched.has(entry)) {
|
|
529
|
-
matched.add(entry);
|
|
530
|
-
for (const dt of entry.dynamicTags) {
|
|
531
|
-
if (!resolved.has(dt)) queue.push(dt);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
} else {
|
|
535
|
-
unmatchedTags.push(tag);
|
|
526
|
+
const molecules = components.filter((c) => c.kind === "molecules");
|
|
527
|
+
const cells = components.filter((c) => c.kind === "cells");
|
|
528
|
+
const printTable = (items) => {
|
|
529
|
+
console.log("| Name | Tag | Description |");
|
|
530
|
+
console.log("|------|-----|-------------|");
|
|
531
|
+
for (const c of items) {
|
|
532
|
+
const tag = c.tag ? `\`${c.tag}\`` : "";
|
|
533
|
+
console.log(`| ${c.name} | ${tag} | ${c.description} |`);
|
|
536
534
|
}
|
|
535
|
+
};
|
|
536
|
+
console.log(`# ${system} \u2014 Component Inventory
|
|
537
|
+
`);
|
|
538
|
+
if (molecules.length > 0) {
|
|
539
|
+
console.log(`## Molecules (${molecules.length})
|
|
540
|
+
`);
|
|
541
|
+
printTable(molecules);
|
|
542
|
+
console.log();
|
|
537
543
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
544
|
+
if (cells.length > 0) {
|
|
545
|
+
console.log(`## Cells (${cells.length})
|
|
546
|
+
`);
|
|
547
|
+
printTable(cells);
|
|
548
|
+
console.log();
|
|
543
549
|
}
|
|
544
|
-
return
|
|
550
|
+
return 0;
|
|
545
551
|
}
|
|
546
|
-
function
|
|
547
|
-
const
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
552
|
+
function showSearch(system, query) {
|
|
553
|
+
const results = searchComponents(system, query);
|
|
554
|
+
const total = listComponents(system).length;
|
|
555
|
+
console.log(`
|
|
556
|
+
Search: "${query}" (${total} components indexed)`);
|
|
557
|
+
console.log("\u2500".repeat(50));
|
|
558
|
+
if (results.length === 0) {
|
|
559
|
+
console.log("\n No matches found.\n");
|
|
560
|
+
return 0;
|
|
561
|
+
}
|
|
562
|
+
console.log(`
|
|
563
|
+
Results (${results.length} match${results.length === 1 ? "" : "es"})
|
|
564
|
+
`);
|
|
565
|
+
const maxName = Math.max(...results.map((r) => r.name.length));
|
|
566
|
+
const maxKind = Math.max(...results.map((r) => r.kind.length + 2));
|
|
567
|
+
for (const r of results) {
|
|
568
|
+
const name = r.name.padEnd(maxName);
|
|
569
|
+
const kind = `(${r.kind})`.padEnd(maxKind);
|
|
570
|
+
const score = r.score.toFixed(2);
|
|
571
|
+
console.log(` ${name} ${kind} ${score} ${r.summary}`);
|
|
559
572
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
return
|
|
571
|
-
<html lang="en">
|
|
572
|
-
<head>
|
|
573
|
-
<meta charset="UTF-8">
|
|
574
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
575
|
-
<style>
|
|
576
|
-
${inlinedCss}
|
|
577
|
-
</style>
|
|
578
|
-
</head>
|
|
579
|
-
<body>
|
|
580
|
-
${markup}
|
|
581
|
-
${scriptTags}
|
|
582
|
-
</body>
|
|
583
|
-
</html>`;
|
|
573
|
+
blank();
|
|
574
|
+
return 0;
|
|
575
|
+
}
|
|
576
|
+
function rebuildIndex(system) {
|
|
577
|
+
console.log(`Building search index for ${system}...`);
|
|
578
|
+
const index = buildSearchIndex(system);
|
|
579
|
+
writeSearchIndex(system, index);
|
|
580
|
+
console.log(` Documents: ${index.documentCount}`);
|
|
581
|
+
console.log(` Terms: ${Object.keys(index.idf).length}`);
|
|
582
|
+
console.log(` Written: ${index.buildTime}`);
|
|
583
|
+
return 0;
|
|
584
584
|
}
|
|
585
585
|
|
|
586
|
-
// src/
|
|
587
|
-
import {
|
|
586
|
+
// src/commands/references.ts
|
|
587
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
|
|
588
588
|
import { join as join4, extname } from "path";
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
".
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
res.end("Not found");
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
622
|
-
try {
|
|
623
|
-
const data = await readFile2(filePath);
|
|
624
|
-
const ext = extname(filePath).toLowerCase();
|
|
625
|
-
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
626
|
-
res.writeHead(200, { "Content-Type": contentType });
|
|
627
|
-
res.end(data);
|
|
628
|
-
} catch {
|
|
629
|
-
res.writeHead(500);
|
|
630
|
-
res.end("Internal server error");
|
|
631
|
-
}
|
|
632
|
-
});
|
|
633
|
-
server.listen(0, () => {
|
|
634
|
-
const addr = server.address();
|
|
635
|
-
if (!addr || typeof addr === "string") {
|
|
636
|
-
reject(new Error("Failed to get server address"));
|
|
637
|
-
return;
|
|
589
|
+
function referencesDir(system) {
|
|
590
|
+
return join4(systemDir(system), "references");
|
|
591
|
+
}
|
|
592
|
+
function loadManifest2(system) {
|
|
593
|
+
const path = join4(referencesDir(system), "manifest.json");
|
|
594
|
+
if (!existsSync4(path)) return null;
|
|
595
|
+
return JSON.parse(readFileSync4(path, "utf-8"));
|
|
596
|
+
}
|
|
597
|
+
function listReferenceFiles(system) {
|
|
598
|
+
const dir = referencesDir(system);
|
|
599
|
+
if (!existsSync4(dir)) return [];
|
|
600
|
+
return readdirSync2(dir).filter((f) => f !== "manifest.json").sort();
|
|
601
|
+
}
|
|
602
|
+
function commandReferences(system, segments) {
|
|
603
|
+
const dir = referencesDir(system);
|
|
604
|
+
if (!existsSync4(dir)) {
|
|
605
|
+
error(`No references directory for "${system}"`);
|
|
606
|
+
return 1;
|
|
607
|
+
}
|
|
608
|
+
if (segments.length === 0) {
|
|
609
|
+
const files = listReferenceFiles(system);
|
|
610
|
+
if (files.length === 0) {
|
|
611
|
+
console.log("No reference files.");
|
|
612
|
+
return 0;
|
|
613
|
+
}
|
|
614
|
+
const manifest = loadManifest2(system);
|
|
615
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
616
|
+
if (manifest) {
|
|
617
|
+
for (const entry of manifest.references) {
|
|
618
|
+
lookup.set(entry.file, entry);
|
|
638
619
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
620
|
+
}
|
|
621
|
+
heading(`References (${files.length})`);
|
|
622
|
+
descriptionList(
|
|
623
|
+
files.map((f) => {
|
|
624
|
+
const entry = lookup.get(f);
|
|
625
|
+
return {
|
|
626
|
+
name: f,
|
|
627
|
+
description: entry ? entry.description : ""
|
|
628
|
+
};
|
|
629
|
+
})
|
|
630
|
+
);
|
|
631
|
+
return 0;
|
|
632
|
+
}
|
|
633
|
+
const fileName = segments.join("/");
|
|
634
|
+
const filePath = join4(dir, fileName);
|
|
635
|
+
if (!existsSync4(filePath)) {
|
|
636
|
+
const files = listReferenceFiles(system);
|
|
637
|
+
const suggestions = files.filter(
|
|
638
|
+
(f) => f.includes(fileName) || fileName.includes(f.replace(extname(f), ""))
|
|
639
|
+
);
|
|
640
|
+
error(`Reference "${fileName}" not found`, suggestions);
|
|
641
|
+
return 1;
|
|
642
|
+
}
|
|
643
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
644
|
+
process.stdout.write(content);
|
|
645
|
+
return 0;
|
|
646
646
|
}
|
|
647
647
|
|
|
648
648
|
// src/commands/render.ts
|
|
649
|
+
import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
|
|
650
|
+
import { resolve } from "path";
|
|
649
651
|
async function readStdin() {
|
|
650
652
|
const chunks = [];
|
|
651
653
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
@@ -671,19 +673,20 @@ async function commandRender(system, fileArg, flags) {
|
|
|
671
673
|
if (fileArg === "-") {
|
|
672
674
|
markup = await readStdin();
|
|
673
675
|
} else {
|
|
674
|
-
const filePath =
|
|
676
|
+
const filePath = resolve(fileArg);
|
|
675
677
|
if (!existsSync5(filePath)) {
|
|
676
678
|
error(`File not found: ${filePath}`);
|
|
677
679
|
return 1;
|
|
678
680
|
}
|
|
679
|
-
markup =
|
|
681
|
+
markup = readFileSync5(filePath, "utf-8");
|
|
680
682
|
}
|
|
681
683
|
const registry = buildRegistry(system);
|
|
682
684
|
const deps = resolveMarkup(registry, markup);
|
|
683
685
|
for (const tag of deps.unmatchedTags) {
|
|
684
686
|
console.warn(` warn: unmatched custom element <${tag}>`);
|
|
685
687
|
}
|
|
686
|
-
const
|
|
688
|
+
const js = await bundleJs(system);
|
|
689
|
+
const html = buildPreviewHtml(system, markup, deps, js);
|
|
687
690
|
if (flags.html) {
|
|
688
691
|
process.stdout.write(html);
|
|
689
692
|
return 0;
|
|
@@ -693,14 +696,14 @@ async function commandRender(system, fileArg, flags) {
|
|
|
693
696
|
console.log(`Preview server running at ${server.url}`);
|
|
694
697
|
console.log(`Opening in browser... (Ctrl+C to stop)`);
|
|
695
698
|
openInBrowser(server.url);
|
|
696
|
-
await new Promise((
|
|
699
|
+
await new Promise((resolve2) => {
|
|
697
700
|
process.on("SIGINT", () => {
|
|
698
701
|
server.stop();
|
|
699
|
-
|
|
702
|
+
resolve2();
|
|
700
703
|
});
|
|
701
704
|
process.on("SIGTERM", () => {
|
|
702
705
|
server.stop();
|
|
703
|
-
|
|
706
|
+
resolve2();
|
|
704
707
|
});
|
|
705
708
|
});
|
|
706
709
|
return 0;
|
|
@@ -720,27 +723,12 @@ async function commandBrowse(system, path, flags) {
|
|
|
720
723
|
switch (section) {
|
|
721
724
|
case "components":
|
|
722
725
|
return commandComponents(system, segments.slice(1));
|
|
726
|
+
case "references":
|
|
727
|
+
return commandReferences(system, segments.slice(1));
|
|
723
728
|
case "render":
|
|
724
729
|
return commandRender(system, segments.slice(1).join("/"), flags);
|
|
725
|
-
case "tokens":
|
|
726
|
-
return handleTokens(system, segments.slice(1), flags);
|
|
727
|
-
case "principles":
|
|
728
|
-
return handlePrinciples(system, segments.slice(1));
|
|
729
|
-
case "architecture":
|
|
730
|
-
return handleArchitecture(system, segments.slice(1));
|
|
731
|
-
case "examples":
|
|
732
|
-
return handleExamples(system, segments.slice(1), flags);
|
|
733
|
-
case "references":
|
|
734
|
-
return handleReferences(system, segments.slice(1));
|
|
735
730
|
default:
|
|
736
|
-
error(`Unknown section "${section}"`, [
|
|
737
|
-
"components",
|
|
738
|
-
"tokens",
|
|
739
|
-
"principles",
|
|
740
|
-
"architecture",
|
|
741
|
-
"examples",
|
|
742
|
-
"references"
|
|
743
|
-
]);
|
|
731
|
+
error(`Unknown section "${section}"`, ["components", "references"]);
|
|
744
732
|
return 1;
|
|
745
733
|
}
|
|
746
734
|
}
|
|
@@ -748,198 +736,6 @@ function openInBrowser(target) {
|
|
|
748
736
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
749
737
|
exec(`${cmd} "${target}"`);
|
|
750
738
|
}
|
|
751
|
-
function handleTokens(system, segments, flags) {
|
|
752
|
-
if (segments.length === 0) {
|
|
753
|
-
return listAllTokens(system);
|
|
754
|
-
}
|
|
755
|
-
return showTokenFile(system, segments[0], flags);
|
|
756
|
-
}
|
|
757
|
-
function listAllTokens(system) {
|
|
758
|
-
const files = listTokenFiles(system);
|
|
759
|
-
if (files.length === 0) {
|
|
760
|
-
console.log(`No token files found in ${system}.`);
|
|
761
|
-
return 0;
|
|
762
|
-
}
|
|
763
|
-
heading(`${system} \u2014 Tokens`);
|
|
764
|
-
for (const name of files) {
|
|
765
|
-
const data = readTokenFile(system, name);
|
|
766
|
-
if (!data) continue;
|
|
767
|
-
console.log(` ${name}: ${tokenSummary(data)}`);
|
|
768
|
-
}
|
|
769
|
-
blank();
|
|
770
|
-
return 0;
|
|
771
|
-
}
|
|
772
|
-
function showTokenFile(system, name, flags) {
|
|
773
|
-
const data = readTokenFile(system, name);
|
|
774
|
-
if (!data) {
|
|
775
|
-
const available = listTokenFiles(system);
|
|
776
|
-
const suggestions = available.filter(
|
|
777
|
-
(f) => f.includes(name) || name.includes(f)
|
|
778
|
-
);
|
|
779
|
-
error(
|
|
780
|
-
`Token file "${name}" not found in ${system}`,
|
|
781
|
-
suggestions.length > 0 ? suggestions : available
|
|
782
|
-
);
|
|
783
|
-
return 1;
|
|
784
|
-
}
|
|
785
|
-
if (flags.json) {
|
|
786
|
-
renderJson(data);
|
|
787
|
-
return 0;
|
|
788
|
-
}
|
|
789
|
-
if (flags.flat) {
|
|
790
|
-
renderFlat(data);
|
|
791
|
-
return 0;
|
|
792
|
-
}
|
|
793
|
-
renderTokens(data);
|
|
794
|
-
return 0;
|
|
795
|
-
}
|
|
796
|
-
function handlePrinciples(system, segments) {
|
|
797
|
-
if (segments.length === 0) {
|
|
798
|
-
return listAllPrinciples(system);
|
|
799
|
-
}
|
|
800
|
-
return showPrinciple(system, segments[0]);
|
|
801
|
-
}
|
|
802
|
-
function listAllPrinciples(system) {
|
|
803
|
-
const principles = listPrinciples(system);
|
|
804
|
-
if (principles.length === 0) {
|
|
805
|
-
console.log(`No principles found in ${system}.`);
|
|
806
|
-
return 0;
|
|
807
|
-
}
|
|
808
|
-
heading(`${system} \u2014 Principles`);
|
|
809
|
-
descriptionList(
|
|
810
|
-
principles.map((p) => ({ name: p.name, description: p.title }))
|
|
811
|
-
);
|
|
812
|
-
blank();
|
|
813
|
-
return 0;
|
|
814
|
-
}
|
|
815
|
-
function showPrinciple(system, name) {
|
|
816
|
-
const content = readPrinciple(system, name);
|
|
817
|
-
if (!content) {
|
|
818
|
-
const available = listPrinciples(system);
|
|
819
|
-
const suggestions = available.filter((p) => p.name.includes(name) || name.includes(p.name)).map((p) => p.name);
|
|
820
|
-
error(
|
|
821
|
-
`Principle "${name}" not found in ${system}`,
|
|
822
|
-
suggestions.length > 0 ? suggestions : available.map((p) => p.name)
|
|
823
|
-
);
|
|
824
|
-
return 1;
|
|
825
|
-
}
|
|
826
|
-
markdown(content);
|
|
827
|
-
return 0;
|
|
828
|
-
}
|
|
829
|
-
function handleArchitecture(system, segments) {
|
|
830
|
-
if (!hasArchitecture(system)) {
|
|
831
|
-
console.log(`No architecture section in ${system}.`);
|
|
832
|
-
return 0;
|
|
833
|
-
}
|
|
834
|
-
const subpath = segments.length > 0 ? segments.join("/") : void 0;
|
|
835
|
-
if (subpath) {
|
|
836
|
-
const content = readArchitectureFile(system, subpath);
|
|
837
|
-
if (content !== null) {
|
|
838
|
-
markdown(content);
|
|
839
|
-
return 0;
|
|
840
|
-
}
|
|
841
|
-
const contentMd = readArchitectureFile(system, subpath + ".md");
|
|
842
|
-
if (contentMd !== null) {
|
|
843
|
-
markdown(contentMd);
|
|
844
|
-
return 0;
|
|
845
|
-
}
|
|
846
|
-
if (isArchitectureDir(system, subpath)) {
|
|
847
|
-
return listArchitectureDir(system, subpath);
|
|
848
|
-
}
|
|
849
|
-
error(`Architecture path "${subpath}" not found in ${system}`);
|
|
850
|
-
return 1;
|
|
851
|
-
}
|
|
852
|
-
return listArchitectureDir(system, void 0);
|
|
853
|
-
}
|
|
854
|
-
function listArchitectureDir(system, subpath) {
|
|
855
|
-
const { entries, indexContent } = listArchitecture(system, subpath);
|
|
856
|
-
const label = subpath ? `${system} \u2014 Architecture / ${subpath}` : `${system} \u2014 Architecture`;
|
|
857
|
-
heading(label);
|
|
858
|
-
if (indexContent) {
|
|
859
|
-
markdown(indexContent);
|
|
860
|
-
blank();
|
|
861
|
-
}
|
|
862
|
-
if (entries.length > 0) {
|
|
863
|
-
for (const entry of entries) {
|
|
864
|
-
const suffix = entry.isDir ? "/" : "";
|
|
865
|
-
console.log(` ${entry.name}${suffix}`);
|
|
866
|
-
}
|
|
867
|
-
blank();
|
|
868
|
-
}
|
|
869
|
-
return 0;
|
|
870
|
-
}
|
|
871
|
-
function handleExamples(system, segments, flags) {
|
|
872
|
-
if (segments.length === 0) {
|
|
873
|
-
return listAllExamples(system);
|
|
874
|
-
}
|
|
875
|
-
return showExample(system, segments[0], flags);
|
|
876
|
-
}
|
|
877
|
-
function listAllExamples(system) {
|
|
878
|
-
const examples = listExamples(system);
|
|
879
|
-
if (examples.length === 0) {
|
|
880
|
-
console.log(`No examples found in ${system}.`);
|
|
881
|
-
return 0;
|
|
882
|
-
}
|
|
883
|
-
heading(`${system} \u2014 Examples`);
|
|
884
|
-
list(examples);
|
|
885
|
-
blank();
|
|
886
|
-
return 0;
|
|
887
|
-
}
|
|
888
|
-
function showExample(system, name, flags) {
|
|
889
|
-
let filename = name;
|
|
890
|
-
if (!filename.endsWith(".html")) filename += ".html";
|
|
891
|
-
const filePath = join5(systemDir(system), "examples", filename);
|
|
892
|
-
if (!existsSync6(filePath)) {
|
|
893
|
-
const available = listExamples(system);
|
|
894
|
-
const suggestions = available.filter(
|
|
895
|
-
(e) => e.includes(name) || name.includes(e.replace(".html", ""))
|
|
896
|
-
);
|
|
897
|
-
error(
|
|
898
|
-
`Example "${name}" not found in ${system}`,
|
|
899
|
-
suggestions.length > 0 ? suggestions.map((s) => s.replace(".html", "")) : available.map((s) => s.replace(".html", ""))
|
|
900
|
-
);
|
|
901
|
-
return 1;
|
|
902
|
-
}
|
|
903
|
-
if (flags.open) {
|
|
904
|
-
openInBrowser(filePath);
|
|
905
|
-
console.log(`Opening ${filename} in browser...`);
|
|
906
|
-
return 0;
|
|
907
|
-
}
|
|
908
|
-
console.log(`Example: ${filename}`);
|
|
909
|
-
console.log(`Path: ${filePath}`);
|
|
910
|
-
console.log(`Use --open to view in browser.`);
|
|
911
|
-
return 0;
|
|
912
|
-
}
|
|
913
|
-
function handleReferences(system, segments) {
|
|
914
|
-
if (segments.length === 0) {
|
|
915
|
-
return listAllReferences(system);
|
|
916
|
-
}
|
|
917
|
-
const name = segments.join("/");
|
|
918
|
-
const filePath = join5(systemDir(system), "references", name);
|
|
919
|
-
if (!existsSync6(filePath)) {
|
|
920
|
-
const available = listReferences(system);
|
|
921
|
-
error(`Reference "${name}" not found in ${system}`, available);
|
|
922
|
-
return 1;
|
|
923
|
-
}
|
|
924
|
-
if (name.endsWith(".md")) {
|
|
925
|
-
markdown(readFile(filePath));
|
|
926
|
-
return 0;
|
|
927
|
-
}
|
|
928
|
-
console.log(`Reference: ${name}`);
|
|
929
|
-
console.log(`Path: ${filePath}`);
|
|
930
|
-
return 0;
|
|
931
|
-
}
|
|
932
|
-
function listAllReferences(system) {
|
|
933
|
-
const refs = listReferences(system);
|
|
934
|
-
if (refs.length === 0) {
|
|
935
|
-
console.log(`No references found in ${system}.`);
|
|
936
|
-
return 0;
|
|
937
|
-
}
|
|
938
|
-
heading(`${system} \u2014 References`);
|
|
939
|
-
list(refs);
|
|
940
|
-
blank();
|
|
941
|
-
return 0;
|
|
942
|
-
}
|
|
943
739
|
|
|
944
740
|
// src/cli.ts
|
|
945
741
|
function showHelp() {
|
|
@@ -950,25 +746,21 @@ Usage:
|
|
|
950
746
|
design-system list List all design systems
|
|
951
747
|
design-system <system> Show system overview
|
|
952
748
|
design-system <system> components list List all components
|
|
749
|
+
design-system <system> components inventory Generate markdown inventory table
|
|
953
750
|
design-system <system> components details <n> Component description + source CSS/JS
|
|
954
|
-
design-system <system>
|
|
955
|
-
design-system <system>
|
|
956
|
-
design-system <system>
|
|
957
|
-
design-system <system>
|
|
958
|
-
design-system <system> architecture List architecture areas
|
|
959
|
-
design-system <system> architecture/<path> Navigate architecture tree
|
|
751
|
+
design-system <system> components search <q> Search components by query
|
|
752
|
+
design-system <system> components index Rebuild search index
|
|
753
|
+
design-system <system> references List reference files
|
|
754
|
+
design-system <system> references <file> Display reference file
|
|
960
755
|
design-system <system> render <file> Render markup with resolved deps
|
|
961
756
|
design-system <system> render - Render from stdin
|
|
962
|
-
design-system <system> examples List example files
|
|
963
|
-
design-system <system> references List reference files
|
|
964
757
|
|
|
965
758
|
Flags:
|
|
966
|
-
--open Open HTML file in browser (examples)
|
|
967
|
-
--json Output raw JSON (tokens)
|
|
968
|
-
--flat Output flat path = value lines (tokens)
|
|
969
759
|
--html Dump raw HTML to stdout
|
|
970
|
-
--systems-dir <path> Override default systems directory
|
|
971
760
|
--help, -h Show this help message
|
|
761
|
+
|
|
762
|
+
Environment:
|
|
763
|
+
DESIGN_SYSTEMS_DIR Path to directory containing design system definitions (required)
|
|
972
764
|
`);
|
|
973
765
|
}
|
|
974
766
|
async function main() {
|
|
@@ -976,18 +768,11 @@ async function main() {
|
|
|
976
768
|
args: process.argv.slice(2),
|
|
977
769
|
options: {
|
|
978
770
|
help: { type: "boolean", short: "h" },
|
|
979
|
-
|
|
980
|
-
json: { type: "boolean" },
|
|
981
|
-
flat: { type: "boolean" },
|
|
982
|
-
html: { type: "boolean" },
|
|
983
|
-
"systems-dir": { type: "string" }
|
|
771
|
+
html: { type: "boolean" }
|
|
984
772
|
},
|
|
985
773
|
allowPositionals: true,
|
|
986
774
|
strict: true
|
|
987
775
|
});
|
|
988
|
-
if (values["systems-dir"]) {
|
|
989
|
-
setSystemsRoot(values["systems-dir"]);
|
|
990
|
-
}
|
|
991
776
|
if (values.help && positionals.length === 0) {
|
|
992
777
|
showHelp();
|
|
993
778
|
process.exit(0);
|
|
@@ -1012,9 +797,6 @@ async function main() {
|
|
|
1012
797
|
process.exit(await commandRender(first, rest.slice(1).join(" "), { html: values.html }));
|
|
1013
798
|
}
|
|
1014
799
|
const flags = {
|
|
1015
|
-
open: values.open,
|
|
1016
|
-
json: values.json,
|
|
1017
|
-
flat: values.flat,
|
|
1018
800
|
html: values.html
|
|
1019
801
|
};
|
|
1020
802
|
process.exit(await commandBrowse(first, second, flags));
|