@sourcescape/ds-cli 0.1.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.
Files changed (3) hide show
  1. package/dist/cli.d.ts +1 -0
  2. package/dist/cli.js +1022 -0
  3. package/package.json +22 -0
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,1022 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { parseArgs } from "util";
5
+
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
+ // src/format.ts
175
+ function heading(text) {
176
+ console.log(`
177
+ ${text}`);
178
+ console.log("\u2500".repeat(Math.min(text.length, 60)));
179
+ }
180
+ function subheading(text) {
181
+ console.log(`
182
+ ${text}`);
183
+ }
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
+ function descriptionList(items, indent = 2) {
191
+ if (items.length === 0) return;
192
+ const maxName = Math.max(...items.map((i) => i.name.length));
193
+ const pad = " ".repeat(indent);
194
+ for (const item of items) {
195
+ const desc = item.description ? ` ${item.description}` : "";
196
+ console.log(`${pad}${item.name.padEnd(maxName)}${desc}`);
197
+ }
198
+ }
199
+ function countLine(label, count) {
200
+ console.log(` ${label}: ${count}`);
201
+ }
202
+ function error(message, suggestions) {
203
+ console.error(`Error: ${message}`);
204
+ if (suggestions && suggestions.length > 0) {
205
+ console.error(`Did you mean: ${suggestions.join(", ")}`);
206
+ }
207
+ }
208
+ function markdown(content) {
209
+ console.log(content);
210
+ }
211
+ function blank() {
212
+ console.log();
213
+ }
214
+
215
+ // src/commands/list.ts
216
+ function commandList() {
217
+ const systems = listSystems();
218
+ if (systems.length === 0) {
219
+ console.log("No design systems found.");
220
+ return 0;
221
+ }
222
+ heading("Design Systems");
223
+ const items = systems.map((name) => {
224
+ const info = getSystemInfo(name);
225
+ return { name: info.name, description: info.description };
226
+ });
227
+ descriptionList(items);
228
+ console.log();
229
+ return 0;
230
+ }
231
+
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
+ // src/commands/show.ts
308
+ function commandShow(system) {
309
+ if (!systemExists(system)) {
310
+ const available = listSystems();
311
+ const suggestions = available.filter(
312
+ (s) => s.includes(system) || system.includes(s)
313
+ );
314
+ error(
315
+ `Design system "${system}" not found`,
316
+ suggestions.length > 0 ? suggestions : available
317
+ );
318
+ return 1;
319
+ }
320
+ const desc = readDescription(system);
321
+ if (desc) {
322
+ markdown(desc);
323
+ }
324
+ heading("Sections");
325
+ const components = listComponents(system);
326
+ const molecules = components.filter((c) => c.kind === "molecules");
327
+ const cells = components.filter((c) => c.kind === "cells");
328
+ countLine("Components", components.length);
329
+ if (molecules.length > 0) countLine(" Molecules", molecules.length);
330
+ if (cells.length > 0) countLine(" Cells", cells.length);
331
+ const tokenFiles = listTokenFiles(system);
332
+ if (tokenFiles.length > 0) {
333
+ countLine("Token files", tokenFiles.length);
334
+ for (const name of tokenFiles) {
335
+ const data = readTokenFile(system, name);
336
+ const summary = data ? tokenSummary(data) : "";
337
+ console.log(` ${name}: ${summary}`);
338
+ }
339
+ }
340
+ const principles = listPrinciples(system);
341
+ if (principles.length > 0) countLine("Principles", principles.length);
342
+ if (hasArchitecture(system)) {
343
+ console.log(" Architecture: yes");
344
+ }
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
+ blank();
350
+ return 0;
351
+ }
352
+
353
+ // src/commands/browse.ts
354
+ import { join as join5 } from "path";
355
+ import { existsSync as existsSync6 } from "fs";
356
+ import { exec } from "child_process";
357
+
358
+ // src/commands/components.ts
359
+ import { join as join2, basename as basename2, relative } from "path";
360
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
361
+ async function commandComponents(system, args) {
362
+ const [subcommand, ...rest] = args;
363
+ if (!subcommand || subcommand === "list") {
364
+ return listAllComponents(system);
365
+ }
366
+ if (subcommand === "details") {
367
+ const name = rest[0];
368
+ if (!name) {
369
+ error("Usage: ds <system> components details <name>");
370
+ return 1;
371
+ }
372
+ return showDetails(system, name);
373
+ }
374
+ return showDetails(system, subcommand);
375
+ }
376
+ function listAllComponents(system) {
377
+ const components = listComponents(system);
378
+ if (components.length === 0) {
379
+ console.log(`No components found in ${system}.`);
380
+ return 0;
381
+ }
382
+ const molecules = components.filter((c) => c.kind === "molecules");
383
+ const cells = components.filter((c) => c.kind === "cells");
384
+ heading(`${system} \u2014 Components`);
385
+ if (molecules.length > 0) {
386
+ subheading(`Molecules (${molecules.length})`);
387
+ descriptionList(
388
+ molecules.map((c) => ({ name: c.name, description: c.description })),
389
+ 4
390
+ );
391
+ }
392
+ if (cells.length > 0) {
393
+ subheading(`Cells (${cells.length})`);
394
+ descriptionList(
395
+ cells.map((c) => ({ name: c.name, description: c.description })),
396
+ 4
397
+ );
398
+ }
399
+ blank();
400
+ return 0;
401
+ }
402
+ function showDetails(system, name) {
403
+ const found = findComponent(system, name);
404
+ if (!found) {
405
+ const suggestions = suggestComponent(system, name);
406
+ error(`Component "${name}" not found in ${system}`, suggestions);
407
+ return 1;
408
+ }
409
+ const sysDir = systemDir(system);
410
+ const relPath = relative(sysDir, found.dir);
411
+ heading(`${name}`);
412
+ console.log(` Kind: ${found.kind}`);
413
+ console.log(` Path: ${relPath}`);
414
+ const files = readdirSync2(found.dir);
415
+ console.log(` Files: ${files.join(", ")}`);
416
+ blank();
417
+ const descPath = join2(found.dir, "description.md");
418
+ if (existsSync2(descPath)) {
419
+ subheading("Description");
420
+ blank();
421
+ console.log(readFile(descPath));
422
+ }
423
+ const sources = findComponentSources(system, name);
424
+ if (sources.css.length > 0) {
425
+ for (const cssPath of sources.css) {
426
+ subheading(`Source CSS: ${basename2(cssPath)}`);
427
+ blank();
428
+ console.log("```css");
429
+ console.log(readFileSync2(cssPath, "utf-8").trimEnd());
430
+ console.log("```");
431
+ blank();
432
+ }
433
+ }
434
+ if (sources.js.length > 0) {
435
+ for (const jsPath of sources.js) {
436
+ subheading(`Source JS: ${basename2(jsPath)}`);
437
+ blank();
438
+ console.log("```js");
439
+ console.log(readFileSync2(jsPath, "utf-8").trimEnd());
440
+ console.log("```");
441
+ blank();
442
+ }
443
+ }
444
+ return 0;
445
+ }
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) {
455
+ const components = listComponents(system);
456
+ const entries = [];
457
+ const tagMap = /* @__PURE__ */ new Map();
458
+ for (const comp of components) {
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
+ }
510
+ }
511
+ return { tagMap, entries };
512
+ }
513
+ function resolveMarkup(registry, markup) {
514
+ const markupTags = /* @__PURE__ */ new Set();
515
+ for (const m of markup.matchAll(/<([a-z][a-z0-9]*-[a-z0-9-]*)/g)) {
516
+ markupTags.add(m[1]);
517
+ }
518
+ const matched = /* @__PURE__ */ new Set();
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);
536
+ }
537
+ }
538
+ const cssFiles = [];
539
+ const jsFiles = [];
540
+ for (const entry of matched) {
541
+ if (entry.hasCss) cssFiles.push(join3(entry.dir, "style.css"));
542
+ if (entry.hasJs) jsFiles.push(join3(entry.dir, "script.js"));
543
+ }
544
+ return { cssFiles, jsFiles, unmatchedTags };
545
+ }
546
+ function buildPreviewHtml(system, markup, deps) {
547
+ const sysDir = systemDir(system);
548
+ const sharedCss = [
549
+ join3(sysDir, "src", "_shared", "_tokens.css"),
550
+ join3(sysDir, "src", "_shared", "_base.css")
551
+ ];
552
+ const utilitiesCss = join3(sysDir, "src", "_shared", "_utilities.css");
553
+ const blocks = ["@layer tokens, base, components, utilities;"];
554
+ for (const p of sharedCss) {
555
+ if (existsSync3(p)) {
556
+ blocks.push(`/* ${relative2(sysDir, p)} */`);
557
+ blocks.push(readFileSync3(p, "utf-8"));
558
+ }
559
+ }
560
+ for (const p of deps.cssFiles) {
561
+ blocks.push(`/* ${relative2(sysDir, p)} */`);
562
+ blocks.push(readFileSync3(p, "utf-8"));
563
+ }
564
+ if (existsSync3(utilitiesCss)) {
565
+ blocks.push(`/* ${relative2(sysDir, utilitiesCss)} */`);
566
+ blocks.push(readFileSync3(utilitiesCss, "utf-8"));
567
+ }
568
+ const inlinedCss = blocks.join("\n");
569
+ const scriptTags = deps.jsFiles.map((p) => ` <script type="module" src="/${relative2(sysDir, p)}"></script>`).join("\n");
570
+ return `<!DOCTYPE html>
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>`;
584
+ }
585
+
586
+ // src/preview-server.ts
587
+ import { createServer } from "http";
588
+ import { join as join4, extname } from "path";
589
+ import { readFile as readFile2 } from "fs/promises";
590
+ import { existsSync as existsSync4 } from "fs";
591
+ var MIME_TYPES = {
592
+ ".html": "text/html; charset=utf-8",
593
+ ".css": "text/css; charset=utf-8",
594
+ ".js": "application/javascript; charset=utf-8",
595
+ ".json": "application/json; charset=utf-8",
596
+ ".svg": "image/svg+xml",
597
+ ".png": "image/png",
598
+ ".jpg": "image/jpeg",
599
+ ".jpeg": "image/jpeg",
600
+ ".gif": "image/gif",
601
+ ".woff": "font/woff",
602
+ ".woff2": "font/woff2",
603
+ ".ttf": "font/ttf",
604
+ ".otf": "font/otf",
605
+ ".eot": "application/vnd.ms-fontobject"
606
+ };
607
+ function startPreviewServer(systemDir2, opts) {
608
+ return new Promise((resolve3, reject) => {
609
+ const server = createServer(async (req, res) => {
610
+ const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
611
+ if (url.pathname === "/" && opts.html) {
612
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
613
+ res.end(opts.html);
614
+ return;
615
+ }
616
+ const filePath = join4(systemDir2, decodeURIComponent(url.pathname));
617
+ if (!existsSync4(filePath)) {
618
+ res.writeHead(404);
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;
638
+ }
639
+ resolve3({
640
+ url: `http://localhost:${addr.port}`,
641
+ stop: () => server.close()
642
+ });
643
+ });
644
+ server.on("error", reject);
645
+ });
646
+ }
647
+
648
+ // src/commands/render.ts
649
+ async function readStdin() {
650
+ const chunks = [];
651
+ for await (const chunk of process.stdin) chunks.push(chunk);
652
+ return Buffer.concat(chunks).toString("utf-8");
653
+ }
654
+ async function commandRender(system, fileArg, flags) {
655
+ if (!systemExists(system)) {
656
+ const suggestions = listSystems().filter(
657
+ (s) => s.includes(system) || system.includes(s)
658
+ );
659
+ error(`Design system "${system}" not found`, suggestions);
660
+ return 1;
661
+ }
662
+ if (!fileArg) {
663
+ error("Usage: ds <system> render <file|->", [
664
+ "ds fw-prd render page.xml",
665
+ "cat page.xml | ds fw-prd render -",
666
+ "ds fw-prd render page.xml --html"
667
+ ]);
668
+ return 1;
669
+ }
670
+ let markup;
671
+ if (fileArg === "-") {
672
+ markup = await readStdin();
673
+ } else {
674
+ const filePath = resolve2(fileArg);
675
+ if (!existsSync5(filePath)) {
676
+ error(`File not found: ${filePath}`);
677
+ return 1;
678
+ }
679
+ markup = readFileSync4(filePath, "utf-8");
680
+ }
681
+ const registry = buildRegistry(system);
682
+ const deps = resolveMarkup(registry, markup);
683
+ for (const tag of deps.unmatchedTags) {
684
+ console.warn(` warn: unmatched custom element <${tag}>`);
685
+ }
686
+ const html = buildPreviewHtml(system, markup, deps);
687
+ if (flags.html) {
688
+ process.stdout.write(html);
689
+ return 0;
690
+ }
691
+ const sysDir = systemDir(system);
692
+ const server = await startPreviewServer(sysDir, { html });
693
+ console.log(`Preview server running at ${server.url}`);
694
+ console.log(`Opening in browser... (Ctrl+C to stop)`);
695
+ openInBrowser(server.url);
696
+ await new Promise((resolve3) => {
697
+ process.on("SIGINT", () => {
698
+ server.stop();
699
+ resolve3();
700
+ });
701
+ process.on("SIGTERM", () => {
702
+ server.stop();
703
+ resolve3();
704
+ });
705
+ });
706
+ return 0;
707
+ }
708
+
709
+ // src/commands/browse.ts
710
+ async function commandBrowse(system, path, flags) {
711
+ if (!systemExists(system)) {
712
+ const suggestions = listSystems().filter(
713
+ (s) => s.includes(system) || system.includes(s)
714
+ );
715
+ error(`Design system "${system}" not found`, suggestions);
716
+ return 1;
717
+ }
718
+ const segments = path.split("/").filter(Boolean);
719
+ const section = segments[0];
720
+ switch (section) {
721
+ case "components":
722
+ return commandComponents(system, segments.slice(1));
723
+ case "render":
724
+ 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
+ default:
736
+ error(`Unknown section "${section}"`, [
737
+ "components",
738
+ "tokens",
739
+ "principles",
740
+ "architecture",
741
+ "examples",
742
+ "references"
743
+ ]);
744
+ return 1;
745
+ }
746
+ }
747
+ function openInBrowser(target) {
748
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
749
+ exec(`${cmd} "${target}"`);
750
+ }
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
+
944
+ // src/cli.ts
945
+ function showHelp() {
946
+ console.log(`
947
+ Design Systems CLI
948
+
949
+ Usage:
950
+ design-system list List all design systems
951
+ design-system <system> Show system overview
952
+ design-system <system> components list List all components
953
+ design-system <system> components details <n> Component description + source CSS/JS
954
+ design-system <system> tokens List token files
955
+ design-system <system> tokens/<name> Pretty-print token values
956
+ design-system <system> principles List principles
957
+ design-system <system> principles/<name> Show principle content
958
+ design-system <system> architecture List architecture areas
959
+ design-system <system> architecture/<path> Navigate architecture tree
960
+ design-system <system> render <file> Render markup with resolved deps
961
+ design-system <system> render - Render from stdin
962
+ design-system <system> examples List example files
963
+ design-system <system> references List reference files
964
+
965
+ Flags:
966
+ --open Open HTML file in browser (examples)
967
+ --json Output raw JSON (tokens)
968
+ --flat Output flat path = value lines (tokens)
969
+ --html Dump raw HTML to stdout
970
+ --systems-dir <path> Override default systems directory
971
+ --help, -h Show this help message
972
+ `);
973
+ }
974
+ async function main() {
975
+ const { values, positionals } = parseArgs({
976
+ args: process.argv.slice(2),
977
+ options: {
978
+ help: { type: "boolean", short: "h" },
979
+ open: { type: "boolean" },
980
+ json: { type: "boolean" },
981
+ flat: { type: "boolean" },
982
+ html: { type: "boolean" },
983
+ "systems-dir": { type: "string" }
984
+ },
985
+ allowPositionals: true,
986
+ strict: true
987
+ });
988
+ if (values["systems-dir"]) {
989
+ setSystemsRoot(values["systems-dir"]);
990
+ }
991
+ if (values.help && positionals.length === 0) {
992
+ showHelp();
993
+ process.exit(0);
994
+ }
995
+ const [first, ...rest] = positionals;
996
+ const second = rest.join("/");
997
+ if (!first) {
998
+ showHelp();
999
+ process.exit(0);
1000
+ }
1001
+ if (first === "list") {
1002
+ process.exit(commandList());
1003
+ }
1004
+ if (!second) {
1005
+ if (values.help) {
1006
+ showHelp();
1007
+ process.exit(0);
1008
+ }
1009
+ process.exit(commandShow(first));
1010
+ }
1011
+ if (rest[0] === "render") {
1012
+ process.exit(await commandRender(first, rest.slice(1).join(" "), { html: values.html }));
1013
+ }
1014
+ const flags = {
1015
+ open: values.open,
1016
+ json: values.json,
1017
+ flat: values.flat,
1018
+ html: values.html
1019
+ };
1020
+ process.exit(await commandBrowse(first, second, flags));
1021
+ }
1022
+ main();
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@sourcescape/ds-cli",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "CLI for browsing design system definitions",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "design-system": "./dist/cli.js"
9
+ },
10
+ "files": ["dist"],
11
+ "scripts": {
12
+ "build": "tsup src/cli.ts --format esm --dts --clean",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "devDependencies": {
19
+ "tsup": "^8.0.0",
20
+ "typescript": "^5.0.0"
21
+ }
22
+ }