@adieyal/catalogue-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 (50) hide show
  1. package/dist/bin.d.ts +5 -0
  2. package/dist/bin.d.ts.map +1 -0
  3. package/dist/bin.js +53 -0
  4. package/dist/bin.js.map +1 -0
  5. package/dist/commands/build.d.ts +9 -0
  6. package/dist/commands/build.d.ts.map +1 -0
  7. package/dist/commands/dev.d.ts +9 -0
  8. package/dist/commands/dev.d.ts.map +1 -0
  9. package/dist/commands/index.d.ts +8 -0
  10. package/dist/commands/index.d.ts.map +1 -0
  11. package/dist/commands/init.d.ts +7 -0
  12. package/dist/commands/init.d.ts.map +1 -0
  13. package/dist/commands/new.d.ts +11 -0
  14. package/dist/commands/new.d.ts.map +1 -0
  15. package/dist/commands/preview.d.ts +9 -0
  16. package/dist/commands/preview.d.ts.map +1 -0
  17. package/dist/commands/test.d.ts +10 -0
  18. package/dist/commands/test.d.ts.map +1 -0
  19. package/dist/commands/validate.d.ts +8 -0
  20. package/dist/commands/validate.d.ts.map +1 -0
  21. package/dist/config/index.d.ts +3 -0
  22. package/dist/config/index.d.ts.map +1 -0
  23. package/dist/config/loader.d.ts +17 -0
  24. package/dist/config/loader.d.ts.map +1 -0
  25. package/dist/config/schema.d.ts +229 -0
  26. package/dist/config/schema.d.ts.map +1 -0
  27. package/dist/index.d.ts +11 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +23 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/init-CI0WzrG1.js +2702 -0
  32. package/dist/init-CI0WzrG1.js.map +1 -0
  33. package/dist/registry/file-loader.d.ts +21 -0
  34. package/dist/registry/file-loader.d.ts.map +1 -0
  35. package/dist/registry/index.d.ts +2 -0
  36. package/dist/registry/index.d.ts.map +1 -0
  37. package/dist/vite/entry.d.ts +6 -0
  38. package/dist/vite/entry.d.ts.map +1 -0
  39. package/dist/vite/index.d.ts +4 -0
  40. package/dist/vite/index.d.ts.map +1 -0
  41. package/dist/vite/plugin.d.ts +15 -0
  42. package/dist/vite/plugin.d.ts.map +1 -0
  43. package/dist/vite/server.d.ts +15 -0
  44. package/dist/vite/server.d.ts.map +1 -0
  45. package/package.json +59 -0
  46. package/skills/document-component.md +83 -0
  47. package/skills/migrate-library.md +193 -0
  48. package/skills/new-component.md +98 -0
  49. package/skills/new-scenario.md +56 -0
  50. package/skills/setup-tokens.md +148 -0
@@ -0,0 +1,2702 @@
1
+ import pc from "picocolors";
2
+ import { existsSync, readdirSync, statSync, readFileSync, mkdirSync, writeFileSync } from "node:fs";
3
+ import { resolve, dirname, join, relative } from "node:path";
4
+ import { z } from "zod";
5
+ import "marked";
6
+ import { createServer, build as build$1, preview as preview$1 } from "vite";
7
+ import { spawn } from "node:child_process";
8
+ import { pathToFileURL, fileURLToPath } from "node:url";
9
+ const CategoryDefinitionSchema = z.object({
10
+ /** Unique category identifier (kebab-case) */
11
+ id: z.string().min(1).regex(/^[a-z][a-z0-9-]*$/, {
12
+ message: "Category ID must be kebab-case starting with a letter"
13
+ }),
14
+ /** Human-readable label */
15
+ label: z.string().min(1),
16
+ /** Optional description */
17
+ description: z.string().optional()
18
+ });
19
+ const CategoriesConfigSchema = z.object({
20
+ /** Category definitions */
21
+ items: z.array(CategoryDefinitionSchema).default([]),
22
+ /** Label for uncategorised components */
23
+ uncategorisedLabel: z.string().optional().default("Other")
24
+ });
25
+ const CatalogueConfigSchema = z.object({
26
+ /** Title for the catalogue */
27
+ title: z.string().optional().default("Component Catalogue"),
28
+ /** Category configuration */
29
+ categories: CategoriesConfigSchema.optional().default({ items: [], uncategorisedLabel: "Other" }),
30
+ /** Path to the registry directory (relative to config) */
31
+ registryDir: z.string().optional().default("./registry"),
32
+ /** Output directory for builds (relative to config) */
33
+ outDir: z.string().optional().default("./dist/catalogue"),
34
+ /** Base path for deployment (e.g., '/docs/ui/') */
35
+ basePath: z.string().optional().default("/"),
36
+ /** Port for dev server */
37
+ port: z.number().optional().default(5173),
38
+ /** Component imports configuration */
39
+ components: z.object({
40
+ /** Path to component entry point (relative to config) */
41
+ entry: z.string().optional().default("./src/components/index.ts"),
42
+ /** Package name for imports */
43
+ package: z.string().optional()
44
+ }).optional().default({}),
45
+ /** Playwright test configuration */
46
+ playwright: z.object({
47
+ /** Breakpoints for visual regression tests */
48
+ breakpoints: z.array(z.object({
49
+ name: z.string(),
50
+ width: z.number(),
51
+ height: z.number()
52
+ })).optional(),
53
+ /** Themes to test */
54
+ themes: z.array(z.enum(["light", "dark"])).optional().default(["light", "dark"]),
55
+ /** Screenshot directory */
56
+ screenshotDir: z.string().optional().default("./screenshots")
57
+ }).optional().default({}),
58
+ /** Additional Vite configuration */
59
+ vite: z.record(z.unknown()).optional()
60
+ });
61
+ function validateConfig(data) {
62
+ const result = CatalogueConfigSchema.safeParse(data);
63
+ if (!result.success) {
64
+ return {
65
+ success: false,
66
+ errors: result.error.errors.map((e) => `${e.path.join(".")}: ${e.message}`)
67
+ };
68
+ }
69
+ return { success: true, data: result.data };
70
+ }
71
+ const CONFIG_FILES = [
72
+ "catalogue.config.ts",
73
+ "catalogue.config.js",
74
+ "catalogue.config.mjs"
75
+ ];
76
+ async function loadConfig(options = {}) {
77
+ const cwd = options.cwd || process.cwd();
78
+ let configPath;
79
+ if (options.configFile) {
80
+ configPath = resolve(cwd, options.configFile);
81
+ if (!existsSync(configPath)) {
82
+ throw new Error(`Config file not found: ${configPath}`);
83
+ }
84
+ } else {
85
+ for (const filename of CONFIG_FILES) {
86
+ const candidate = resolve(cwd, filename);
87
+ if (existsSync(candidate)) {
88
+ configPath = candidate;
89
+ break;
90
+ }
91
+ }
92
+ }
93
+ if (!configPath) {
94
+ throw new Error(
95
+ `No catalogue config found in ${cwd}. Create one of: ${CONFIG_FILES.join(", ")}`
96
+ );
97
+ }
98
+ const configUrl = pathToFileURL(configPath).href;
99
+ let rawConfig;
100
+ try {
101
+ const module = await import(configUrl);
102
+ rawConfig = module.default;
103
+ } catch (error) {
104
+ throw new Error(
105
+ `Failed to load config from ${configPath}: ${error instanceof Error ? error.message : "Unknown error"}`
106
+ );
107
+ }
108
+ const result = validateConfig(rawConfig);
109
+ if (!result.success) {
110
+ throw new Error(
111
+ `Invalid config in ${configPath}:
112
+ ${result.errors.map((e) => ` - ${e}`).join("\n")}`
113
+ );
114
+ }
115
+ const config = result.data;
116
+ const configDir = dirname(configPath);
117
+ return {
118
+ ...config,
119
+ configPath,
120
+ configDir,
121
+ registryPath: resolve(configDir, config.registryDir),
122
+ outPath: resolve(configDir, config.outDir)
123
+ };
124
+ }
125
+ function createDefaultConfig() {
126
+ return {
127
+ title: "Component Catalogue",
128
+ categories: {
129
+ items: [],
130
+ uncategorisedLabel: "Other"
131
+ },
132
+ registryDir: "./registry",
133
+ outDir: "./dist/catalogue",
134
+ basePath: "/",
135
+ port: 5173,
136
+ components: {
137
+ entry: "./src/components/index.ts"
138
+ },
139
+ playwright: {
140
+ themes: ["light", "dark"],
141
+ screenshotDir: "./screenshots"
142
+ }
143
+ };
144
+ }
145
+ function loadRegistryFiles(options) {
146
+ const { registryPath, basePath = registryPath } = options;
147
+ const components = [];
148
+ const scenarios = [];
149
+ const examples = [];
150
+ const componentsDir = join(registryPath, "components");
151
+ if (existsSync(componentsDir)) {
152
+ const componentDirs = readdirSync(componentsDir).filter((name) => {
153
+ const path = join(componentsDir, name);
154
+ return statSync(path).isDirectory();
155
+ });
156
+ for (const componentId of componentDirs) {
157
+ const componentDir = join(componentsDir, componentId);
158
+ const componentJsonPath = join(componentDir, "component.json");
159
+ if (existsSync(componentJsonPath)) {
160
+ try {
161
+ const data = JSON.parse(readFileSync(componentJsonPath, "utf-8"));
162
+ const filePath = relative(basePath, componentJsonPath);
163
+ let docs;
164
+ const docsPath = join(componentDir, "docs.md");
165
+ if (existsSync(docsPath)) {
166
+ docs = readFileSync(docsPath, "utf-8");
167
+ }
168
+ components.push({ filePath, data, docs });
169
+ } catch (error) {
170
+ const err = error instanceof Error ? error.message : "Unknown error";
171
+ console.error(`Error loading ${componentJsonPath}: ${err}`);
172
+ }
173
+ }
174
+ const scenariosDir = join(componentDir, "scenarios");
175
+ if (existsSync(scenariosDir)) {
176
+ const scenarioFiles = readdirSync(scenariosDir).filter(
177
+ (name) => name.endsWith(".json")
178
+ );
179
+ for (const scenarioFile of scenarioFiles) {
180
+ const scenarioPath = join(scenariosDir, scenarioFile);
181
+ try {
182
+ const data = JSON.parse(readFileSync(scenarioPath, "utf-8"));
183
+ const filePath = relative(basePath, scenarioPath);
184
+ scenarios.push({ filePath, data });
185
+ } catch (error) {
186
+ const err = error instanceof Error ? error.message : "Unknown error";
187
+ console.error(`Error loading ${scenarioPath}: ${err}`);
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+ const examplesDir = join(registryPath, "examples");
194
+ if (existsSync(examplesDir)) {
195
+ const exampleFiles = readdirSync(examplesDir).filter(
196
+ (name) => name.endsWith(".json")
197
+ );
198
+ for (const exampleFile of exampleFiles) {
199
+ const examplePath = join(examplesDir, exampleFile);
200
+ try {
201
+ const data = JSON.parse(readFileSync(examplePath, "utf-8"));
202
+ const filePath = relative(basePath, examplePath);
203
+ examples.push({ filePath, data });
204
+ } catch (error) {
205
+ const err = error instanceof Error ? error.message : "Unknown error";
206
+ console.error(`Error loading ${examplePath}: ${err}`);
207
+ }
208
+ }
209
+ }
210
+ return { components, scenarios, examples };
211
+ }
212
+ function generateRegistryModule(registryPath) {
213
+ const data = loadRegistryFiles({ registryPath });
214
+ return `
215
+ // Auto-generated registry data
216
+ export const registryData = ${JSON.stringify(data, null, 2)};
217
+ `;
218
+ }
219
+ function getRegistryWatchPaths(registryPath) {
220
+ const paths = [];
221
+ function walkDir(dir) {
222
+ if (!existsSync(dir)) return;
223
+ const entries = readdirSync(dir, { withFileTypes: true });
224
+ for (const entry of entries) {
225
+ const fullPath = join(dir, entry.name);
226
+ if (entry.isDirectory()) {
227
+ walkDir(fullPath);
228
+ } else if (entry.name.endsWith(".json") || entry.name.endsWith(".md")) {
229
+ paths.push(fullPath);
230
+ }
231
+ }
232
+ }
233
+ walkDir(registryPath);
234
+ return paths;
235
+ }
236
+ const VIRTUAL_REGISTRY_ID = "virtual:catalogue-registry";
237
+ const RESOLVED_VIRTUAL_REGISTRY_ID = "\0" + VIRTUAL_REGISTRY_ID;
238
+ function cataloguePlugin(options) {
239
+ const { config } = options;
240
+ let server;
241
+ return {
242
+ name: "catalogue",
243
+ configResolved(viteConfig) {
244
+ },
245
+ configureServer(_server) {
246
+ server = _server;
247
+ const watchPaths = getRegistryWatchPaths(config.registryPath);
248
+ for (const path of watchPaths) {
249
+ server.watcher.add(path);
250
+ }
251
+ server.watcher.on("change", (file) => {
252
+ if (file.startsWith(config.registryPath)) {
253
+ const module = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_REGISTRY_ID);
254
+ if (module) {
255
+ server.moduleGraph.invalidateModule(module);
256
+ server.ws.send({
257
+ type: "full-reload",
258
+ path: "*"
259
+ });
260
+ }
261
+ }
262
+ });
263
+ },
264
+ resolveId(id) {
265
+ if (id === VIRTUAL_REGISTRY_ID) {
266
+ return RESOLVED_VIRTUAL_REGISTRY_ID;
267
+ }
268
+ },
269
+ load(id) {
270
+ if (id === RESOLVED_VIRTUAL_REGISTRY_ID) {
271
+ const data = loadRegistryFiles({ registryPath: config.registryPath });
272
+ const registryData = {
273
+ ...data,
274
+ categories: config.categories
275
+ };
276
+ return `
277
+ export const registryData = ${JSON.stringify(registryData)};
278
+ export const config = ${JSON.stringify({
279
+ title: config.title,
280
+ basePath: config.basePath
281
+ })};
282
+ `;
283
+ }
284
+ }
285
+ };
286
+ }
287
+ function createHtmlTemplate(config) {
288
+ return `<!DOCTYPE html>
289
+ <html lang="en">
290
+ <head>
291
+ <meta charset="UTF-8">
292
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
293
+ <title>${config.title}</title>
294
+ <style>
295
+ * {
296
+ box-sizing: border-box;
297
+ }
298
+
299
+ :root {
300
+ --catalogue-bg: #ffffff;
301
+ --catalogue-text: #1a1a1a;
302
+ --catalogue-border: #e5e5e5;
303
+ --catalogue-primary: #3b82f6;
304
+ --catalogue-code-bg: #f5f5f5;
305
+ }
306
+
307
+ [data-theme="dark"] {
308
+ --catalogue-bg: #1a1a1a;
309
+ --catalogue-text: #f5f5f5;
310
+ --catalogue-border: #333333;
311
+ --catalogue-primary: #60a5fa;
312
+ --catalogue-code-bg: #2a2a2a;
313
+ }
314
+
315
+ body {
316
+ margin: 0;
317
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
318
+ background: var(--catalogue-bg);
319
+ color: var(--catalogue-text);
320
+ line-height: 1.5;
321
+ }
322
+
323
+ .catalogue-harness-body {
324
+ padding: 0;
325
+ margin: 0;
326
+ }
327
+
328
+ #app {
329
+ min-height: 100vh;
330
+ }
331
+
332
+ /* Landing page styles */
333
+ .catalogue-landing {
334
+ max-width: 1200px;
335
+ margin: 0 auto;
336
+ padding: 2rem;
337
+ }
338
+
339
+ .catalogue-landing-header {
340
+ margin-bottom: 2rem;
341
+ }
342
+
343
+ .catalogue-landing-title {
344
+ margin: 0;
345
+ font-size: 2rem;
346
+ }
347
+
348
+ .catalogue-landing-controls {
349
+ display: flex;
350
+ flex-direction: column;
351
+ gap: 1rem;
352
+ margin-bottom: 2rem;
353
+ }
354
+
355
+ .catalogue-search-input {
356
+ width: 100%;
357
+ padding: 0.75rem 1rem;
358
+ font-size: 1rem;
359
+ border: 1px solid var(--catalogue-border);
360
+ border-radius: 0.5rem;
361
+ background: var(--catalogue-bg);
362
+ color: var(--catalogue-text);
363
+ }
364
+
365
+ .catalogue-filters {
366
+ display: flex;
367
+ flex-wrap: wrap;
368
+ gap: 1rem;
369
+ }
370
+
371
+ .catalogue-filter-group {
372
+ display: flex;
373
+ align-items: center;
374
+ gap: 0.5rem;
375
+ }
376
+
377
+ .catalogue-filter-label {
378
+ font-weight: 500;
379
+ font-size: 0.875rem;
380
+ }
381
+
382
+ .catalogue-filter-option {
383
+ display: flex;
384
+ align-items: center;
385
+ gap: 0.25rem;
386
+ cursor: pointer;
387
+ font-size: 0.875rem;
388
+ }
389
+
390
+ .catalogue-component-list {
391
+ display: flex;
392
+ flex-direction: column;
393
+ gap: 1.5rem;
394
+ }
395
+
396
+ .catalogue-component-list--flat {
397
+ display: grid;
398
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
399
+ }
400
+
401
+ .catalogue-expand-toggle {
402
+ padding: 0.5rem 1rem;
403
+ font-size: 0.875rem;
404
+ border: 1px solid var(--catalogue-border);
405
+ border-radius: 0.375rem;
406
+ background: var(--catalogue-bg);
407
+ color: var(--catalogue-text);
408
+ cursor: pointer;
409
+ margin-left: auto;
410
+ }
411
+
412
+ .catalogue-expand-toggle:hover {
413
+ background: var(--catalogue-code-bg);
414
+ }
415
+
416
+ .catalogue-category-section {
417
+ border: 1px solid var(--catalogue-border);
418
+ border-radius: 0.5rem;
419
+ overflow: hidden;
420
+ }
421
+
422
+ .catalogue-category-header {
423
+ display: flex;
424
+ align-items: center;
425
+ gap: 0.5rem;
426
+ padding: 1rem 1.25rem;
427
+ background: var(--catalogue-code-bg);
428
+ cursor: pointer;
429
+ list-style: none;
430
+ }
431
+
432
+ .catalogue-category-header::-webkit-details-marker {
433
+ display: none;
434
+ }
435
+
436
+ .catalogue-category-header::before {
437
+ content: '▶';
438
+ font-size: 0.75rem;
439
+ transition: transform 0.2s;
440
+ }
441
+
442
+ .catalogue-category-section[open] .catalogue-category-header::before {
443
+ transform: rotate(90deg);
444
+ }
445
+
446
+ .catalogue-category-label {
447
+ font-weight: 600;
448
+ font-size: 1rem;
449
+ }
450
+
451
+ .catalogue-category-count {
452
+ font-size: 0.875rem;
453
+ color: var(--catalogue-text);
454
+ opacity: 0.6;
455
+ }
456
+
457
+ .catalogue-category-content {
458
+ display: grid;
459
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
460
+ gap: 1.5rem;
461
+ padding: 1.5rem;
462
+ }
463
+
464
+ .catalogue-component-card {
465
+ border: 1px solid var(--catalogue-border);
466
+ border-radius: 0.5rem;
467
+ padding: 1.25rem;
468
+ background: var(--catalogue-bg);
469
+ }
470
+
471
+ .catalogue-component-card-header {
472
+ display: flex;
473
+ justify-content: space-between;
474
+ align-items: flex-start;
475
+ margin-bottom: 0.5rem;
476
+ }
477
+
478
+ .catalogue-component-card-title {
479
+ margin: 0;
480
+ font-size: 1.125rem;
481
+ }
482
+
483
+ .catalogue-component-card-title a {
484
+ color: var(--catalogue-primary);
485
+ text-decoration: none;
486
+ }
487
+
488
+ .catalogue-component-card-title a:hover {
489
+ text-decoration: underline;
490
+ }
491
+
492
+ .catalogue-status-badge {
493
+ font-size: 0.75rem;
494
+ padding: 0.125rem 0.5rem;
495
+ border-radius: 9999px;
496
+ text-transform: uppercase;
497
+ font-weight: 500;
498
+ }
499
+
500
+ .catalogue-status-badge[data-status="stable"] {
501
+ background: #dcfce7;
502
+ color: #166534;
503
+ }
504
+
505
+ .catalogue-status-badge[data-status="beta"] {
506
+ background: #fef3c7;
507
+ color: #92400e;
508
+ }
509
+
510
+ .catalogue-status-badge[data-status="deprecated"] {
511
+ background: #fee2e2;
512
+ color: #991b1b;
513
+ }
514
+
515
+ [data-theme="dark"] .catalogue-status-badge[data-status="stable"] {
516
+ background: #166534;
517
+ color: #dcfce7;
518
+ }
519
+
520
+ [data-theme="dark"] .catalogue-status-badge[data-status="beta"] {
521
+ background: #92400e;
522
+ color: #fef3c7;
523
+ }
524
+
525
+ [data-theme="dark"] .catalogue-status-badge[data-status="deprecated"] {
526
+ background: #991b1b;
527
+ color: #fee2e2;
528
+ }
529
+
530
+ .catalogue-component-card-description {
531
+ margin: 0.5rem 0;
532
+ color: var(--catalogue-text);
533
+ opacity: 0.8;
534
+ font-size: 0.875rem;
535
+ }
536
+
537
+ .catalogue-component-card-tags {
538
+ display: flex;
539
+ flex-wrap: wrap;
540
+ gap: 0.25rem;
541
+ margin: 0.75rem 0;
542
+ }
543
+
544
+ .catalogue-tag {
545
+ font-size: 0.75rem;
546
+ padding: 0.125rem 0.5rem;
547
+ background: var(--catalogue-code-bg);
548
+ border-radius: 0.25rem;
549
+ }
550
+
551
+ .catalogue-component-card-meta {
552
+ font-size: 0.75rem;
553
+ color: var(--catalogue-text);
554
+ opacity: 0.6;
555
+ }
556
+
557
+ /* Breadcrumb */
558
+ .catalogue-breadcrumb {
559
+ display: flex;
560
+ align-items: center;
561
+ gap: 0.5rem;
562
+ margin-bottom: 1rem;
563
+ font-size: 0.875rem;
564
+ }
565
+
566
+ .catalogue-breadcrumb a {
567
+ color: var(--catalogue-primary);
568
+ text-decoration: none;
569
+ }
570
+
571
+ .catalogue-breadcrumb a:hover {
572
+ text-decoration: underline;
573
+ }
574
+
575
+ .catalogue-breadcrumb-separator {
576
+ color: var(--catalogue-text);
577
+ opacity: 0.5;
578
+ }
579
+
580
+ /* Component page */
581
+ .catalogue-component-page {
582
+ max-width: 1200px;
583
+ margin: 0 auto;
584
+ padding: 2rem;
585
+ }
586
+
587
+ .catalogue-component-header {
588
+ margin-bottom: 2rem;
589
+ }
590
+
591
+ .catalogue-component-title-row {
592
+ display: flex;
593
+ align-items: center;
594
+ gap: 1rem;
595
+ margin-bottom: 0.5rem;
596
+ }
597
+
598
+ .catalogue-component-title {
599
+ margin: 0;
600
+ font-size: 1.75rem;
601
+ }
602
+
603
+ .catalogue-component-description {
604
+ margin: 0.5rem 0;
605
+ font-size: 1rem;
606
+ opacity: 0.8;
607
+ }
608
+
609
+ .catalogue-component-tags {
610
+ display: flex;
611
+ flex-wrap: wrap;
612
+ gap: 0.5rem;
613
+ margin: 1rem 0;
614
+ }
615
+
616
+ .catalogue-component-actions {
617
+ margin-top: 1rem;
618
+ }
619
+
620
+ .catalogue-button {
621
+ display: inline-block;
622
+ padding: 0.5rem 1rem;
623
+ border-radius: 0.375rem;
624
+ text-decoration: none;
625
+ font-size: 0.875rem;
626
+ font-weight: 500;
627
+ cursor: pointer;
628
+ border: none;
629
+ }
630
+
631
+ .catalogue-button-primary {
632
+ background: var(--catalogue-primary);
633
+ color: white;
634
+ }
635
+
636
+ .catalogue-button-primary:hover {
637
+ opacity: 0.9;
638
+ }
639
+
640
+ /* Variants grid */
641
+ .catalogue-variants-section {
642
+ margin-top: 2rem;
643
+ }
644
+
645
+ .catalogue-variants-section h2 {
646
+ font-size: 1.25rem;
647
+ margin-bottom: 1rem;
648
+ }
649
+
650
+ .catalogue-variants-grid {
651
+ display: grid;
652
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
653
+ gap: 1.5rem;
654
+ }
655
+
656
+ .catalogue-scenario-card {
657
+ border: 1px solid var(--catalogue-border);
658
+ border-radius: 0.5rem;
659
+ overflow: hidden;
660
+ }
661
+
662
+ .catalogue-scenario-card-primary {
663
+ border-color: var(--catalogue-primary);
664
+ }
665
+
666
+ .catalogue-scenario-preview {
667
+ padding: 1.5rem;
668
+ background: var(--catalogue-bg);
669
+ min-height: 100px;
670
+ display: flex;
671
+ align-items: center;
672
+ justify-content: center;
673
+ }
674
+
675
+ .catalogue-scenario-info {
676
+ padding: 1rem;
677
+ border-top: 1px solid var(--catalogue-border);
678
+ }
679
+
680
+ .catalogue-scenario-title-row {
681
+ display: flex;
682
+ align-items: center;
683
+ gap: 0.5rem;
684
+ }
685
+
686
+ .catalogue-scenario-title {
687
+ margin: 0;
688
+ font-size: 0.875rem;
689
+ font-weight: 500;
690
+ }
691
+
692
+ .catalogue-primary-badge {
693
+ font-size: 0.625rem;
694
+ padding: 0.125rem 0.375rem;
695
+ background: var(--catalogue-primary);
696
+ color: white;
697
+ border-radius: 0.25rem;
698
+ text-transform: uppercase;
699
+ }
700
+
701
+ .catalogue-scenario-description {
702
+ margin: 0.5rem 0;
703
+ font-size: 0.75rem;
704
+ opacity: 0.7;
705
+ }
706
+
707
+ .catalogue-scenario-links {
708
+ margin-top: 0.5rem;
709
+ }
710
+
711
+ .catalogue-link {
712
+ font-size: 0.75rem;
713
+ color: var(--catalogue-primary);
714
+ text-decoration: none;
715
+ }
716
+
717
+ .catalogue-link:hover {
718
+ text-decoration: underline;
719
+ }
720
+
721
+ /* Docs section */
722
+ .catalogue-docs-section {
723
+ margin-top: 2rem;
724
+ padding-top: 2rem;
725
+ border-top: 1px solid var(--catalogue-border);
726
+ }
727
+
728
+ .catalogue-docs-section h2 {
729
+ font-size: 1.25rem;
730
+ margin-bottom: 1rem;
731
+ }
732
+
733
+ .catalogue-docs-content {
734
+ line-height: 1.7;
735
+ }
736
+
737
+ .catalogue-docs-content h1,
738
+ .catalogue-docs-content h2,
739
+ .catalogue-docs-content h3 {
740
+ margin-top: 1.5rem;
741
+ margin-bottom: 0.5rem;
742
+ }
743
+
744
+ .catalogue-docs-content pre {
745
+ background: var(--catalogue-code-bg);
746
+ padding: 1rem;
747
+ border-radius: 0.375rem;
748
+ overflow-x: auto;
749
+ }
750
+
751
+ .catalogue-docs-content code {
752
+ font-family: ui-monospace, 'SF Mono', Menlo, Monaco, monospace;
753
+ font-size: 0.875em;
754
+ }
755
+
756
+ /* Playground */
757
+ .catalogue-playground {
758
+ min-height: 100vh;
759
+ display: flex;
760
+ flex-direction: column;
761
+ }
762
+
763
+ .catalogue-playground-header {
764
+ padding: 1rem 2rem;
765
+ border-bottom: 1px solid var(--catalogue-border);
766
+ }
767
+
768
+ .catalogue-playground-title-row {
769
+ display: flex;
770
+ justify-content: space-between;
771
+ align-items: center;
772
+ }
773
+
774
+ .catalogue-playground-title-row h1 {
775
+ font-size: 1.25rem;
776
+ margin: 0;
777
+ }
778
+
779
+ .catalogue-playground-main {
780
+ display: flex;
781
+ flex: 1;
782
+ }
783
+
784
+ .catalogue-playground-preview-section {
785
+ flex: 1;
786
+ padding: 1rem;
787
+ display: flex;
788
+ flex-direction: column;
789
+ }
790
+
791
+ .catalogue-scenario-selector {
792
+ margin-bottom: 1rem;
793
+ }
794
+
795
+ .catalogue-scenario-select {
796
+ padding: 0.375rem 0.75rem;
797
+ border: 1px solid var(--catalogue-border);
798
+ border-radius: 0.25rem;
799
+ background: var(--catalogue-bg);
800
+ color: var(--catalogue-text);
801
+ }
802
+
803
+ .catalogue-playground-sidebar {
804
+ width: 350px;
805
+ border-left: 1px solid var(--catalogue-border);
806
+ display: flex;
807
+ flex-direction: column;
808
+ }
809
+
810
+ .catalogue-playground-controls-section {
811
+ padding: 1rem;
812
+ border-bottom: 1px solid var(--catalogue-border);
813
+ }
814
+
815
+ .catalogue-playground-controls-section h2 {
816
+ font-size: 0.875rem;
817
+ margin: 0 0 1rem 0;
818
+ text-transform: uppercase;
819
+ letter-spacing: 0.05em;
820
+ opacity: 0.7;
821
+ }
822
+
823
+ .catalogue-playground-code-section {
824
+ flex: 1;
825
+ overflow: auto;
826
+ }
827
+
828
+ /* Controls */
829
+ .catalogue-controls {
830
+ display: flex;
831
+ flex-direction: column;
832
+ gap: 1rem;
833
+ }
834
+
835
+ .catalogue-control {
836
+ display: flex;
837
+ flex-direction: column;
838
+ gap: 0.25rem;
839
+ }
840
+
841
+ .catalogue-control-label {
842
+ font-size: 0.75rem;
843
+ font-weight: 500;
844
+ }
845
+
846
+ .catalogue-control-input,
847
+ .catalogue-control-select {
848
+ padding: 0.5rem;
849
+ border: 1px solid var(--catalogue-border);
850
+ border-radius: 0.25rem;
851
+ background: var(--catalogue-bg);
852
+ color: var(--catalogue-text);
853
+ font-size: 0.875rem;
854
+ }
855
+
856
+ .catalogue-control-checkbox {
857
+ display: flex;
858
+ align-items: center;
859
+ }
860
+
861
+ .catalogue-control-radio-group {
862
+ display: flex;
863
+ flex-direction: column;
864
+ gap: 0.25rem;
865
+ }
866
+
867
+ .catalogue-control-radio {
868
+ display: flex;
869
+ align-items: center;
870
+ gap: 0.5rem;
871
+ font-size: 0.875rem;
872
+ cursor: pointer;
873
+ }
874
+
875
+ .catalogue-control-range-wrapper {
876
+ display: flex;
877
+ align-items: center;
878
+ gap: 0.5rem;
879
+ }
880
+
881
+ .catalogue-control-range {
882
+ flex: 1;
883
+ }
884
+
885
+ .catalogue-control-range-value {
886
+ font-size: 0.75rem;
887
+ min-width: 3ch;
888
+ text-align: right;
889
+ }
890
+
891
+ .catalogue-control-json {
892
+ width: 100%;
893
+ padding: 0.5rem;
894
+ border: 1px solid var(--catalogue-border);
895
+ border-radius: 0.25rem;
896
+ background: var(--catalogue-bg);
897
+ color: var(--catalogue-text);
898
+ font-family: ui-monospace, monospace;
899
+ font-size: 0.75rem;
900
+ resize: vertical;
901
+ }
902
+
903
+ .catalogue-control-json.invalid {
904
+ border-color: #ef4444;
905
+ }
906
+
907
+ /* Resizer */
908
+ .catalogue-resizer-wrapper {
909
+ flex: 1;
910
+ display: flex;
911
+ justify-content: center;
912
+ padding: 1rem;
913
+ }
914
+
915
+ .catalogue-resizer {
916
+ position: relative;
917
+ border: 1px solid var(--catalogue-border);
918
+ border-radius: 0.25rem;
919
+ background: var(--catalogue-bg);
920
+ overflow: hidden;
921
+ }
922
+
923
+ .catalogue-resizer-content {
924
+ width: 100%;
925
+ height: 100%;
926
+ overflow: auto;
927
+ padding: 1rem;
928
+ }
929
+
930
+ .catalogue-resizer-handle {
931
+ position: absolute;
932
+ background: transparent;
933
+ }
934
+
935
+ .catalogue-resizer-handle-right {
936
+ right: 0;
937
+ top: 0;
938
+ bottom: 0;
939
+ width: 8px;
940
+ cursor: ew-resize;
941
+ }
942
+
943
+ .catalogue-resizer-handle-bottom {
944
+ bottom: 0;
945
+ left: 0;
946
+ right: 0;
947
+ height: 8px;
948
+ cursor: ns-resize;
949
+ }
950
+
951
+ .catalogue-resizer-handle-corner {
952
+ right: 0;
953
+ bottom: 0;
954
+ width: 16px;
955
+ height: 16px;
956
+ cursor: nwse-resize;
957
+ }
958
+
959
+ .catalogue-resizer-dimensions {
960
+ position: absolute;
961
+ bottom: 4px;
962
+ right: 20px;
963
+ font-size: 0.625rem;
964
+ color: var(--catalogue-text);
965
+ opacity: 0.5;
966
+ font-family: ui-monospace, monospace;
967
+ }
968
+
969
+ .catalogue-breakpoint-presets {
970
+ display: flex;
971
+ flex-wrap: wrap;
972
+ gap: 0.5rem;
973
+ margin-bottom: 1rem;
974
+ }
975
+
976
+ .catalogue-breakpoint-button {
977
+ padding: 0.25rem 0.5rem;
978
+ font-size: 0.75rem;
979
+ border: 1px solid var(--catalogue-border);
980
+ border-radius: 0.25rem;
981
+ background: var(--catalogue-bg);
982
+ color: var(--catalogue-text);
983
+ cursor: pointer;
984
+ }
985
+
986
+ .catalogue-breakpoint-button:hover {
987
+ background: var(--catalogue-code-bg);
988
+ }
989
+
990
+ /* Code panel */
991
+ .catalogue-code-panel {
992
+ border-top: 1px solid var(--catalogue-border);
993
+ }
994
+
995
+ .catalogue-code-panel-header {
996
+ display: flex;
997
+ justify-content: space-between;
998
+ align-items: center;
999
+ padding: 0.75rem 1rem;
1000
+ border-bottom: 1px solid var(--catalogue-border);
1001
+ }
1002
+
1003
+ .catalogue-code-panel-title {
1004
+ font-size: 0.75rem;
1005
+ font-weight: 500;
1006
+ text-transform: uppercase;
1007
+ letter-spacing: 0.05em;
1008
+ }
1009
+
1010
+ .catalogue-code-panel-copy {
1011
+ padding: 0.25rem 0.5rem;
1012
+ font-size: 0.75rem;
1013
+ border: 1px solid var(--catalogue-border);
1014
+ border-radius: 0.25rem;
1015
+ background: var(--catalogue-bg);
1016
+ color: var(--catalogue-text);
1017
+ cursor: pointer;
1018
+ }
1019
+
1020
+ .catalogue-code-panel-copy.copied {
1021
+ background: #22c55e;
1022
+ color: white;
1023
+ border-color: #22c55e;
1024
+ }
1025
+
1026
+ .catalogue-code-panel-content {
1027
+ margin: 0;
1028
+ padding: 1rem;
1029
+ background: var(--catalogue-code-bg);
1030
+ overflow-x: auto;
1031
+ font-size: 0.75rem;
1032
+ line-height: 1.5;
1033
+ }
1034
+
1035
+ .catalogue-code-panel-content code {
1036
+ font-family: ui-monospace, 'SF Mono', Menlo, Monaco, monospace;
1037
+ }
1038
+
1039
+ .catalogue-code-panel[data-collapsed] .catalogue-code-panel-content {
1040
+ display: none;
1041
+ }
1042
+
1043
+ /* Theme toggle */
1044
+ .catalogue-theme-toggle {
1045
+ padding: 0.5rem;
1046
+ border: 1px solid var(--catalogue-border);
1047
+ border-radius: 0.375rem;
1048
+ background: var(--catalogue-bg);
1049
+ color: var(--catalogue-text);
1050
+ cursor: pointer;
1051
+ display: flex;
1052
+ align-items: center;
1053
+ justify-content: center;
1054
+ }
1055
+
1056
+ .catalogue-theme-toggle:hover {
1057
+ background: var(--catalogue-code-bg);
1058
+ }
1059
+
1060
+ /* Harness */
1061
+ .catalogue-harness {
1062
+ min-height: 100vh;
1063
+ }
1064
+
1065
+ .catalogue-harness-container {
1066
+ display: inline-block;
1067
+ }
1068
+
1069
+ /* Global header */
1070
+ .catalogue-global-header {
1071
+ display: flex;
1072
+ justify-content: space-between;
1073
+ align-items: center;
1074
+ padding: 0.75rem 2rem;
1075
+ border-bottom: 1px solid var(--catalogue-border);
1076
+ background: var(--catalogue-bg);
1077
+ }
1078
+
1079
+ .catalogue-home-link {
1080
+ font-weight: 600;
1081
+ color: var(--catalogue-text);
1082
+ text-decoration: none;
1083
+ }
1084
+
1085
+ .catalogue-home-link:hover {
1086
+ color: var(--catalogue-primary);
1087
+ }
1088
+
1089
+ /* Not found */
1090
+ .catalogue-not-found {
1091
+ max-width: 600px;
1092
+ margin: 4rem auto;
1093
+ text-align: center;
1094
+ padding: 2rem;
1095
+ }
1096
+
1097
+ .catalogue-not-found h1 {
1098
+ font-size: 1.5rem;
1099
+ margin-bottom: 0.5rem;
1100
+ }
1101
+
1102
+ .catalogue-not-found a {
1103
+ color: var(--catalogue-primary);
1104
+ }
1105
+
1106
+ /* Empty state */
1107
+ .catalogue-empty-state {
1108
+ grid-column: 1 / -1;
1109
+ text-align: center;
1110
+ padding: 3rem;
1111
+ color: var(--catalogue-text);
1112
+ opacity: 0.6;
1113
+ }
1114
+
1115
+ /* Subcomponents */
1116
+ .catalogue-subcomponents-section {
1117
+ margin-top: 2rem;
1118
+ }
1119
+
1120
+ .catalogue-subcomponents-section h2 {
1121
+ font-size: 1.25rem;
1122
+ margin-bottom: 1rem;
1123
+ }
1124
+
1125
+ .catalogue-subcomponents-list {
1126
+ display: flex;
1127
+ flex-wrap: wrap;
1128
+ gap: 1rem;
1129
+ }
1130
+
1131
+ .catalogue-subcomponent-card {
1132
+ padding: 1rem;
1133
+ border: 1px solid var(--catalogue-border);
1134
+ border-radius: 0.375rem;
1135
+ }
1136
+
1137
+ .catalogue-subcomponent-card a {
1138
+ color: var(--catalogue-primary);
1139
+ text-decoration: none;
1140
+ font-weight: 500;
1141
+ }
1142
+
1143
+ .catalogue-subcomponent-card p {
1144
+ margin: 0.5rem 0 0;
1145
+ font-size: 0.875rem;
1146
+ opacity: 0.7;
1147
+ }
1148
+ </style>
1149
+ </head>
1150
+ <body>
1151
+ <div id="app"></div>
1152
+ <script type="module" src="/@catalogue/entry.ts"><\/script>
1153
+ </body>
1154
+ </html>`;
1155
+ }
1156
+ const entryTemplate = `
1157
+ import { loadRegistry, mountCatalogueApp } from '@adieyal/catalogue-core';
1158
+ import { registryData, config } from 'virtual:catalogue-registry';
1159
+
1160
+ // Import consumer's components
1161
+ import '{{COMPONENTS_ENTRY}}';
1162
+
1163
+ // Load and validate the registry
1164
+ const { registry, errors } = loadRegistry(registryData);
1165
+
1166
+ if (errors.length > 0) {
1167
+ console.error('Registry validation errors:', errors);
1168
+ }
1169
+
1170
+ // Mount the app
1171
+ const app = mountCatalogueApp('#app', registry, {
1172
+ basePath: config.basePath,
1173
+ });
1174
+
1175
+ // Hot module replacement
1176
+ if (import.meta.hot) {
1177
+ import.meta.hot.accept('virtual:catalogue-registry', (newModule) => {
1178
+ if (newModule) {
1179
+ window.location.reload();
1180
+ }
1181
+ });
1182
+ }
1183
+ `;
1184
+ const VIRTUAL_ENTRY_ID = "@catalogue/entry.ts";
1185
+ const RESOLVED_VIRTUAL_ENTRY_ID = "\0" + VIRTUAL_ENTRY_ID;
1186
+ const VIRTUAL_ENTRY_PATH = "/@catalogue/entry.ts";
1187
+ function createViteConfig(config, mode) {
1188
+ const componentsEntry = config.components?.entry ? resolve(config.configDir, config.components.entry) : resolve(config.configDir, "./src/components/index.ts");
1189
+ return {
1190
+ root: config.configDir,
1191
+ base: config.basePath,
1192
+ mode,
1193
+ define: {
1194
+ "process.env.NODE_ENV": JSON.stringify(mode)
1195
+ },
1196
+ plugins: [
1197
+ cataloguePlugin({ config }),
1198
+ {
1199
+ name: "catalogue-entry",
1200
+ enforce: "pre",
1201
+ resolveId(id) {
1202
+ if (id === VIRTUAL_ENTRY_ID || id === VIRTUAL_ENTRY_PATH || id.endsWith("@catalogue/entry.ts")) {
1203
+ return RESOLVED_VIRTUAL_ENTRY_ID;
1204
+ }
1205
+ },
1206
+ load(id) {
1207
+ if (id === RESOLVED_VIRTUAL_ENTRY_ID) {
1208
+ return entryTemplate.replace("{{COMPONENTS_ENTRY}}", componentsEntry);
1209
+ }
1210
+ }
1211
+ },
1212
+ {
1213
+ name: "catalogue-html",
1214
+ configureServer(server) {
1215
+ server.middlewares.use((req, res, next) => {
1216
+ if (req.url === "/" || req.url?.startsWith("/?")) {
1217
+ res.setHeader("Content-Type", "text/html");
1218
+ server.transformIndexHtml("/", createHtmlTemplate(config)).then((html) => {
1219
+ res.end(html);
1220
+ });
1221
+ return;
1222
+ }
1223
+ next();
1224
+ });
1225
+ },
1226
+ // Only transform HTML in dev mode - production uses the written index.html
1227
+ transformIndexHtml: {
1228
+ order: "pre",
1229
+ handler(html, ctx) {
1230
+ if (ctx.server) {
1231
+ return createHtmlTemplate(config);
1232
+ }
1233
+ return html;
1234
+ }
1235
+ }
1236
+ }
1237
+ ],
1238
+ server: {
1239
+ port: config.port
1240
+ },
1241
+ build: {
1242
+ outDir: config.outPath,
1243
+ emptyOutDir: true
1244
+ },
1245
+ ...config.vite || {}
1246
+ };
1247
+ }
1248
+ async function startDevServer(config) {
1249
+ const viteConfig = createViteConfig(config, "development");
1250
+ const server = await createServer(viteConfig);
1251
+ await server.listen();
1252
+ server.printUrls();
1253
+ }
1254
+ async function buildStatic(config) {
1255
+ const viteConfig = createViteConfig(config, "production");
1256
+ const { writeFileSync: writeFileSync2, mkdirSync: mkdirSync2, existsSync: existsSync2 } = await import("node:fs");
1257
+ const { join: join2 } = await import("node:path");
1258
+ if (!existsSync2(config.configDir)) {
1259
+ mkdirSync2(config.configDir, { recursive: true });
1260
+ }
1261
+ const componentsEntry = config.components?.entry ? resolve(config.configDir, config.components.entry) : resolve(config.configDir, "./src/components/index.ts");
1262
+ const entryPath = join2(config.configDir, ".catalogue-entry.ts");
1263
+ writeFileSync2(entryPath, entryTemplate.replace("{{COMPONENTS_ENTRY}}", componentsEntry));
1264
+ const indexPath = join2(config.configDir, "index.html");
1265
+ writeFileSync2(indexPath, createHtmlTemplate(config).replace(
1266
+ "/@catalogue/entry.ts",
1267
+ "./.catalogue-entry.ts"
1268
+ ));
1269
+ try {
1270
+ await build$1(viteConfig);
1271
+ } finally {
1272
+ const { unlinkSync } = await import("node:fs");
1273
+ try {
1274
+ unlinkSync(indexPath);
1275
+ unlinkSync(entryPath);
1276
+ } catch {
1277
+ }
1278
+ }
1279
+ }
1280
+ async function startPreviewServer(config) {
1281
+ const viteConfig = createViteConfig(config, "production");
1282
+ const server = await preview$1({
1283
+ ...viteConfig,
1284
+ preview: {
1285
+ port: config.port
1286
+ }
1287
+ });
1288
+ server.printUrls();
1289
+ }
1290
+ async function dev(options = {}) {
1291
+ console.log(pc.cyan("Starting development server..."));
1292
+ try {
1293
+ const config = await loadConfig({
1294
+ configFile: options.config
1295
+ });
1296
+ if (options.port) {
1297
+ config.port = options.port;
1298
+ }
1299
+ await startDevServer(config);
1300
+ } catch (error) {
1301
+ console.error(pc.red("Error starting dev server:"));
1302
+ console.error(error instanceof Error ? error.message : error);
1303
+ process.exit(1);
1304
+ }
1305
+ }
1306
+ async function build(options = {}) {
1307
+ console.log(pc.cyan("Building catalogue..."));
1308
+ try {
1309
+ const config = await loadConfig({
1310
+ configFile: options.config
1311
+ });
1312
+ if (options.outDir) {
1313
+ config.outPath = options.outDir;
1314
+ }
1315
+ await buildStatic(config);
1316
+ console.log(pc.green(`✓ Built to ${config.outPath}`));
1317
+ } catch (error) {
1318
+ console.error(pc.red("Error building catalogue:"));
1319
+ console.error(error instanceof Error ? error.message : error);
1320
+ process.exit(1);
1321
+ }
1322
+ }
1323
+ async function preview(options = {}) {
1324
+ console.log(pc.cyan("Starting preview server..."));
1325
+ try {
1326
+ const config = await loadConfig({
1327
+ configFile: options.config
1328
+ });
1329
+ if (options.port) {
1330
+ config.port = options.port;
1331
+ }
1332
+ await startPreviewServer(config);
1333
+ } catch (error) {
1334
+ console.error(pc.red("Error starting preview server:"));
1335
+ console.error(error instanceof Error ? error.message : error);
1336
+ process.exit(1);
1337
+ }
1338
+ }
1339
+ const ue = z.object({
1340
+ /** Element tag name (e.g., 'div', 'my-component') */
1341
+ element: z.string().min(1),
1342
+ /** DOM attributes (set via setAttribute) */
1343
+ attrs: z.record(z.union([z.string(), z.number(), z.boolean(), z.null()])).optional(),
1344
+ /** DOM properties (set directly on element, for objects/arrays) */
1345
+ props: z.record(z.unknown()).optional(),
1346
+ /** Text content (mutually exclusive with children) */
1347
+ text: z.string().optional()
1348
+ }), R = ue.extend({
1349
+ /** Named slots - content to render with slot attribute */
1350
+ slots: z.record(z.union([
1351
+ z.string(),
1352
+ z.lazy(() => R),
1353
+ z.array(z.lazy(() => R))
1354
+ ])).optional(),
1355
+ /** Child elements */
1356
+ children: z.array(z.lazy(() => R)).optional()
1357
+ });
1358
+ const pe = z.object({
1359
+ type: z.literal("text"),
1360
+ label: z.string().optional(),
1361
+ defaultValue: z.string().optional(),
1362
+ placeholder: z.string().optional()
1363
+ }), me = z.object({
1364
+ type: z.literal("number"),
1365
+ label: z.string().optional(),
1366
+ defaultValue: z.number().optional(),
1367
+ min: z.number().optional(),
1368
+ max: z.number().optional(),
1369
+ step: z.number().optional()
1370
+ }), he = z.object({
1371
+ type: z.literal("boolean"),
1372
+ label: z.string().optional(),
1373
+ defaultValue: z.boolean().optional()
1374
+ }), ge = z.object({
1375
+ type: z.literal("select"),
1376
+ label: z.string().optional(),
1377
+ defaultValue: z.string().optional(),
1378
+ options: z.array(z.union([
1379
+ z.string(),
1380
+ z.object({
1381
+ label: z.string(),
1382
+ value: z.string()
1383
+ })
1384
+ ]))
1385
+ }), fe = z.object({
1386
+ type: z.literal("radio"),
1387
+ label: z.string().optional(),
1388
+ defaultValue: z.string().optional(),
1389
+ options: z.array(z.union([
1390
+ z.string(),
1391
+ z.object({
1392
+ label: z.string(),
1393
+ value: z.string()
1394
+ })
1395
+ ]))
1396
+ }), Ce = z.object({
1397
+ type: z.literal("color"),
1398
+ label: z.string().optional(),
1399
+ defaultValue: z.string().optional()
1400
+ }), be = z.object({
1401
+ type: z.literal("range"),
1402
+ label: z.string().optional(),
1403
+ defaultValue: z.number().optional(),
1404
+ min: z.number(),
1405
+ max: z.number(),
1406
+ step: z.number().optional()
1407
+ }), ve = z.object({
1408
+ type: z.literal("json"),
1409
+ label: z.string().optional(),
1410
+ defaultValue: z.unknown().optional()
1411
+ }), Z = z.discriminatedUnion("type", [
1412
+ pe,
1413
+ me,
1414
+ he,
1415
+ ge,
1416
+ fe,
1417
+ Ce,
1418
+ be,
1419
+ ve
1420
+ ]);
1421
+ const ye = z.enum(["stable", "beta", "deprecated"]), Ee = z.enum(["standalone", "subcomponent", "feature"]), xe = z.object({
1422
+ /** Custom element tag name (must be kebab-case with at least one hyphen) */
1423
+ customElement: z.string().regex(/^[a-z][a-z0-9]*-[a-z0-9-]+$/, {
1424
+ message: 'Custom element name must be kebab-case with at least one hyphen (e.g., "my-button")'
1425
+ }),
1426
+ /** npm package name (optional, defaults to consumer's package) */
1427
+ package: z.string().optional(),
1428
+ /** Entry point path (optional, defaults to main entry) */
1429
+ entry: z.string().optional()
1430
+ }), we = z.object({
1431
+ /** ID of the primary scenario to use as default in playground */
1432
+ primaryScenarioId: z.string().min(1),
1433
+ /** Control definitions keyed by property name */
1434
+ controls: z.record(Z)
1435
+ }), Ne = z.object({
1436
+ /** Unique identifier for the component (kebab-case) */
1437
+ id: z.string().min(1).regex(/^[a-z][a-z0-9-]*$/, {
1438
+ message: "Component ID must be kebab-case starting with a letter"
1439
+ }),
1440
+ /** Human-readable title */
1441
+ title: z.string().min(1),
1442
+ /** Component maturity status */
1443
+ status: ye,
1444
+ /** Component kind */
1445
+ kind: Ee,
1446
+ /** Optional description */
1447
+ description: z.string().optional(),
1448
+ /** Tags for search and filtering */
1449
+ tags: z.array(z.string()).optional(),
1450
+ /** Category ID (must match a category defined in config) */
1451
+ category: z.string().regex(/^[a-z][a-z0-9-]*$/, {
1452
+ message: "Category must be kebab-case starting with a letter"
1453
+ }).optional(),
1454
+ /** Export configuration */
1455
+ exports: xe,
1456
+ /** Playground configuration */
1457
+ playground: we,
1458
+ /** Parent component ID (required when kind is 'subcomponent') */
1459
+ parentId: z.string().optional(),
1460
+ /** List of subcomponent IDs */
1461
+ subcomponents: z.array(z.string()).optional()
1462
+ }).refine(
1463
+ (t) => !(t.kind === "subcomponent" && !t.parentId),
1464
+ {
1465
+ message: "Subcomponents must have a parentId",
1466
+ path: ["parentId"]
1467
+ }
1468
+ );
1469
+ function $e(t) {
1470
+ const e = Ne.safeParse(t);
1471
+ return e.success ? { success: true, data: e.data } : {
1472
+ success: false,
1473
+ errors: e.error.errors.map((a) => ({
1474
+ path: a.path.join(".") || "(root)",
1475
+ message: a.message
1476
+ }))
1477
+ };
1478
+ }
1479
+ const ee = z.object({
1480
+ /** Unique identifier for the scenario (kebab-case) */
1481
+ id: z.string().min(1).regex(/^[a-z][a-z0-9-]*$/, {
1482
+ message: "Scenario ID must be kebab-case starting with a letter"
1483
+ }),
1484
+ /** Human-readable title */
1485
+ title: z.string().min(1),
1486
+ /** Optional description explaining the scenario */
1487
+ description: z.string().optional(),
1488
+ /** Component ID this scenario belongs to */
1489
+ componentId: z.string().min(1),
1490
+ /** Tags for search and filtering */
1491
+ tags: z.array(z.string()).optional(),
1492
+ /** The RenderNode tree that defines the DOM structure */
1493
+ render: R,
1494
+ /** Optional viewport configuration for screenshots */
1495
+ viewport: z.object({
1496
+ width: z.number().positive().optional(),
1497
+ height: z.number().positive().optional()
1498
+ }).optional(),
1499
+ /** Optional background color override */
1500
+ background: z.string().optional(),
1501
+ /** Whether this is the primary scenario (shown first) */
1502
+ primary: z.boolean().optional()
1503
+ });
1504
+ function te(t) {
1505
+ const e = ee.safeParse(t);
1506
+ return e.success ? { success: true, data: e.data } : {
1507
+ success: false,
1508
+ errors: e.error.errors.map((a) => ({
1509
+ path: a.path.join(".") || "(root)",
1510
+ message: a.message
1511
+ }))
1512
+ };
1513
+ }
1514
+ function Se(t) {
1515
+ return te(t);
1516
+ }
1517
+ function ft(t) {
1518
+ const e = [], a = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map();
1519
+ for (const { filePath: s, data: i, docs: c } of t.components) {
1520
+ const d = $e(i);
1521
+ if (d.success) {
1522
+ if (d.data) {
1523
+ const u = d.data;
1524
+ a.set(u.id, {
1525
+ ...u,
1526
+ filePath: s,
1527
+ docs: c,
1528
+ scenarios: []
1529
+ });
1530
+ }
1531
+ } else for (const u of d.errors || [])
1532
+ e.push({
1533
+ file: s,
1534
+ path: u.path,
1535
+ message: u.message
1536
+ });
1537
+ }
1538
+ for (const { filePath: s, data: i } of t.scenarios) {
1539
+ const c = te(i);
1540
+ if (c.success) {
1541
+ if (c.data) {
1542
+ const d = c.data, u = {
1543
+ ...d,
1544
+ filePath: s
1545
+ };
1546
+ o.set(d.id, u);
1547
+ const h = a.get(d.componentId);
1548
+ h && h.scenarios.push(u);
1549
+ }
1550
+ } else for (const d of c.errors || [])
1551
+ e.push({
1552
+ file: s,
1553
+ path: d.path,
1554
+ message: d.message
1555
+ });
1556
+ }
1557
+ for (const { filePath: s, data: i } of t.examples) {
1558
+ const c = Se(i);
1559
+ if (c.success) {
1560
+ if (c.data) {
1561
+ const d = c.data;
1562
+ n.set(d.id, {
1563
+ ...d,
1564
+ filePath: s
1565
+ });
1566
+ }
1567
+ } else for (const d of c.errors || [])
1568
+ e.push({
1569
+ file: s,
1570
+ path: d.path,
1571
+ message: d.message
1572
+ });
1573
+ }
1574
+ const r = t.categories ?? {
1575
+ items: [],
1576
+ uncategorisedLabel: "Other"
1577
+ };
1578
+ return {
1579
+ registry: { components: a, scenarios: o, examples: n, categories: r },
1580
+ errors: e
1581
+ };
1582
+ }
1583
+ function bt(t) {
1584
+ const e = [], a = [], o = /* @__PURE__ */ new Map();
1585
+ for (const [n, r] of t.components)
1586
+ o.has(n) || o.set(n, []), o.get(n).push(r.filePath);
1587
+ for (const [n, r] of t.scenarios)
1588
+ o.has(n) || o.set(n, []), o.get(n).push(r.filePath);
1589
+ for (const [n, r] of t.examples)
1590
+ o.has(n) || o.set(n, []), o.get(n).push(r.filePath);
1591
+ for (const [n, r] of o)
1592
+ r.length > 1 && e.push({
1593
+ file: r.join(", "),
1594
+ path: "id",
1595
+ message: `Duplicate ID "${n}" found in multiple files`
1596
+ });
1597
+ for (const [n, r] of t.components) {
1598
+ const s = r.playground.primaryScenarioId;
1599
+ if (r.scenarios.some((c) => c.id === s) || e.push({
1600
+ file: r.filePath,
1601
+ path: "playground.primaryScenarioId",
1602
+ message: `Primary scenario "${s}" not found for component "${n}"`
1603
+ }), r.parentId && (t.components.has(r.parentId) || e.push({
1604
+ file: r.filePath,
1605
+ path: "parentId",
1606
+ message: `Parent component "${r.parentId}" not found for component "${n}"`
1607
+ })), r.subcomponents)
1608
+ for (const c of r.subcomponents)
1609
+ if (!t.components.has(c))
1610
+ e.push({
1611
+ file: r.filePath,
1612
+ path: "subcomponents",
1613
+ message: `Subcomponent "${c}" not found for component "${n}"`
1614
+ });
1615
+ else {
1616
+ const d = t.components.get(c);
1617
+ d.parentId !== n && a.push({
1618
+ file: r.filePath,
1619
+ path: "subcomponents",
1620
+ message: `Subcomponent "${c}" has different parentId "${d.parentId}" than expected "${n}"`
1621
+ });
1622
+ }
1623
+ r.scenarios.length === 0 && e.push({
1624
+ file: r.filePath,
1625
+ path: "scenarios",
1626
+ message: `Component "${n}" has no scenarios`
1627
+ });
1628
+ }
1629
+ for (const [n, r] of t.scenarios)
1630
+ t.components.has(r.componentId) || e.push({
1631
+ file: r.filePath,
1632
+ path: "componentId",
1633
+ message: `Component "${r.componentId}" not found for scenario "${n}"`
1634
+ });
1635
+ for (const [n, r] of t.examples)
1636
+ t.components.has(r.componentId) || e.push({
1637
+ file: r.filePath,
1638
+ path: "componentId",
1639
+ message: `Component "${r.componentId}" not found for example "${n}"`
1640
+ });
1641
+ return {
1642
+ valid: e.length === 0,
1643
+ errors: e,
1644
+ warnings: a
1645
+ };
1646
+ }
1647
+ function vt(t) {
1648
+ const e = [];
1649
+ if (t.errors.length > 0) {
1650
+ e.push("Errors:");
1651
+ for (const a of t.errors)
1652
+ e.push(` ${a.file}: ${a.path}: ${a.message}`);
1653
+ }
1654
+ if (t.warnings.length > 0) {
1655
+ e.push("Warnings:");
1656
+ for (const a of t.warnings)
1657
+ e.push(` ${a.file}: ${a.path}: ${a.message}`);
1658
+ }
1659
+ return e.join(`
1660
+ `);
1661
+ }
1662
+ function _(t, e) {
1663
+ const a = new URLSearchParams();
1664
+ e != null && e.theme && a.set("theme", e.theme), (e == null ? void 0 : e.width) !== void 0 && a.set("w", String(e.width)), (e == null ? void 0 : e.height) !== void 0 && a.set("h", String(e.height)), e != null && e.background && a.set("bg", e.background);
1665
+ const o = a.toString();
1666
+ return `/harness/${t}${o ? `?${o}` : ""}`;
1667
+ }
1668
+ function St(t, e) {
1669
+ const a = [], o = ["light", "dark"], n = e || [
1670
+ { width: 375, height: 667 },
1671
+ // Mobile
1672
+ { width: 1024, height: 768 }
1673
+ // Desktop
1674
+ ];
1675
+ for (const [r, s] of t.scenarios) {
1676
+ for (const i of o)
1677
+ a.push({
1678
+ scenarioId: r,
1679
+ componentId: s.componentId,
1680
+ url: _(r, { theme: i }),
1681
+ theme: i
1682
+ });
1683
+ for (const i of n)
1684
+ for (const c of o)
1685
+ a.push({
1686
+ scenarioId: r,
1687
+ componentId: s.componentId,
1688
+ url: _(r, {
1689
+ theme: c,
1690
+ width: i.width,
1691
+ height: i.height
1692
+ }),
1693
+ theme: c,
1694
+ width: i.width,
1695
+ height: i.height
1696
+ });
1697
+ }
1698
+ return a;
1699
+ }
1700
+ async function validate(options = {}) {
1701
+ console.log(pc.cyan("Validating registry..."));
1702
+ try {
1703
+ const config = await loadConfig({
1704
+ configFile: options.config
1705
+ });
1706
+ const rawData = loadRegistryFiles({ registryPath: config.registryPath });
1707
+ const { registry, errors: parseErrors } = ft(rawData);
1708
+ if (parseErrors.length > 0) {
1709
+ console.log(pc.red("\nSchema validation errors:"));
1710
+ for (const error of parseErrors) {
1711
+ console.log(pc.red(` ${error.file}: ${error.path}: ${error.message}`));
1712
+ }
1713
+ }
1714
+ const crossRefResult = bt(registry);
1715
+ if (!crossRefResult.valid) {
1716
+ console.log(pc.red("\nCross-reference validation errors:"));
1717
+ console.log(vt(crossRefResult));
1718
+ }
1719
+ if (crossRefResult.warnings.length > 0) {
1720
+ console.log(pc.yellow("\nWarnings:"));
1721
+ for (const warning of crossRefResult.warnings) {
1722
+ console.log(pc.yellow(` ${warning.file}: ${warning.path}: ${warning.message}`));
1723
+ }
1724
+ }
1725
+ const componentCount = registry.components.size;
1726
+ const scenarioCount = registry.scenarios.size;
1727
+ const exampleCount = registry.examples.size;
1728
+ console.log("");
1729
+ console.log(pc.dim(`Found ${componentCount} component(s), ${scenarioCount} scenario(s), ${exampleCount} example(s)`));
1730
+ if (parseErrors.length > 0 || !crossRefResult.valid) {
1731
+ console.log(pc.red("\n✗ Validation failed"));
1732
+ process.exit(1);
1733
+ }
1734
+ console.log(pc.green("\n✓ Registry is valid"));
1735
+ } catch (error) {
1736
+ console.error(pc.red("Error validating registry:"));
1737
+ console.error(error instanceof Error ? error.message : error);
1738
+ process.exit(1);
1739
+ }
1740
+ }
1741
+ async function test(options = {}) {
1742
+ console.log(pc.cyan("Running visual tests..."));
1743
+ try {
1744
+ const config = await loadConfig({
1745
+ configFile: options.config
1746
+ });
1747
+ console.log(pc.dim("Validating registry..."));
1748
+ const rawData = loadRegistryFiles({ registryPath: config.registryPath });
1749
+ const { registry, errors: parseErrors } = ft(rawData);
1750
+ if (parseErrors.length > 0) {
1751
+ console.log(pc.red("Registry validation failed:"));
1752
+ for (const error of parseErrors) {
1753
+ console.log(pc.red(` ${error.file}: ${error.path}: ${error.message}`));
1754
+ }
1755
+ process.exit(1);
1756
+ }
1757
+ const crossRefResult = bt(registry);
1758
+ if (!crossRefResult.valid) {
1759
+ console.log(pc.red("Cross-reference validation failed"));
1760
+ process.exit(1);
1761
+ }
1762
+ console.log(pc.dim("Building catalogue..."));
1763
+ await buildStatic(config);
1764
+ const breakpoints = config.playwright?.breakpoints || [
1765
+ { name: "mobile", width: 375, height: 667 },
1766
+ { name: "desktop", width: 1024, height: 768 }
1767
+ ];
1768
+ const harnessUrls = St(
1769
+ registry,
1770
+ breakpoints.map((bp) => ({ width: bp.width, height: bp.height }))
1771
+ );
1772
+ const testDir = join(config.configDir, ".catalogue-tests");
1773
+ if (!existsSync(testDir)) {
1774
+ mkdirSync(testDir, { recursive: true });
1775
+ }
1776
+ const testFile = generatePlaywrightTest(harnessUrls, config);
1777
+ const testFilePath = join(testDir, "visual.spec.ts");
1778
+ writeFileSync(testFilePath, testFile);
1779
+ const playwrightConfig = generatePlaywrightConfig(config, testDir);
1780
+ const configFilePath = join(testDir, "playwright.config.ts");
1781
+ writeFileSync(configFilePath, playwrightConfig);
1782
+ console.log(pc.dim("Running Playwright tests..."));
1783
+ const args = ["playwright", "test", "-c", configFilePath];
1784
+ if (options.update) {
1785
+ args.push("--update-snapshots");
1786
+ }
1787
+ if (options.headed) {
1788
+ args.push("--headed");
1789
+ }
1790
+ const playwrightProcess = spawn("npx", args, {
1791
+ cwd: config.configDir,
1792
+ stdio: "inherit",
1793
+ shell: true
1794
+ });
1795
+ playwrightProcess.on("close", (code) => {
1796
+ if (code === 0) {
1797
+ console.log(pc.green("\n✓ All visual tests passed"));
1798
+ } else {
1799
+ console.log(pc.red(`
1800
+ ✗ Tests failed with code ${code}`));
1801
+ process.exit(code || 1);
1802
+ }
1803
+ });
1804
+ } catch (error) {
1805
+ console.error(pc.red("Error running tests:"));
1806
+ console.error(error instanceof Error ? error.message : error);
1807
+ process.exit(1);
1808
+ }
1809
+ }
1810
+ function generatePlaywrightTest(harnessUrls, config) {
1811
+ const baseUrl = `http://localhost:${config.port}`;
1812
+ return `
1813
+ import { test, expect } from '@playwright/test';
1814
+
1815
+ const scenarios = ${JSON.stringify(harnessUrls, null, 2)};
1816
+
1817
+ for (const scenario of scenarios) {
1818
+ const testName = [
1819
+ scenario.componentId,
1820
+ scenario.scenarioId,
1821
+ scenario.theme,
1822
+ scenario.width ? \`\${scenario.width}x\${scenario.height}\` : 'default',
1823
+ ].join('-');
1824
+
1825
+ test(testName, async ({ page }) => {
1826
+ const url = '${baseUrl}#' + scenario.url;
1827
+ await page.goto(url);
1828
+
1829
+ // Wait for component to render
1830
+ await page.waitForSelector('[data-catalogue-container]');
1831
+
1832
+ // Take screenshot
1833
+ const container = page.locator('[data-catalogue-container]');
1834
+ await expect(container).toHaveScreenshot(\`\${testName}.png\`);
1835
+ });
1836
+ }
1837
+ `;
1838
+ }
1839
+ function generatePlaywrightConfig(config, testDir) {
1840
+ const screenshotDir = config.playwright?.screenshotDir ? resolve(config.configDir, config.playwright.screenshotDir) : join(config.configDir, "screenshots");
1841
+ return `
1842
+ import { defineConfig, devices } from '@playwright/test';
1843
+
1844
+ export default defineConfig({
1845
+ testDir: '${testDir}',
1846
+ snapshotDir: '${screenshotDir}',
1847
+ snapshotPathTemplate: '{snapshotDir}/{testName}/{arg}{ext}',
1848
+ fullyParallel: true,
1849
+ forbidOnly: !!process.env.CI,
1850
+ retries: process.env.CI ? 2 : 0,
1851
+ workers: process.env.CI ? 1 : undefined,
1852
+ reporter: 'list',
1853
+ use: {
1854
+ baseURL: 'http://localhost:${config.port}',
1855
+ trace: 'on-first-retry',
1856
+ },
1857
+ projects: [
1858
+ {
1859
+ name: 'chromium',
1860
+ use: { ...devices['Desktop Chrome'] },
1861
+ },
1862
+ ],
1863
+ webServer: {
1864
+ command: 'npx vite preview --port ${config.port}',
1865
+ port: ${config.port},
1866
+ reuseExistingServer: !process.env.CI,
1867
+ cwd: '${config.configDir}',
1868
+ },
1869
+ });
1870
+ `;
1871
+ }
1872
+ async function newComponent(componentId, options = {}) {
1873
+ if (!componentId) {
1874
+ console.error(pc.red("Error: Component ID is required"));
1875
+ console.log("Usage: catalogue new <component-id>");
1876
+ process.exit(1);
1877
+ }
1878
+ if (!/^[a-z][a-z0-9-]*$/.test(componentId)) {
1879
+ console.error(pc.red("Error: Component ID must be kebab-case starting with a letter"));
1880
+ console.log("Example: my-button, data-table, user-card");
1881
+ process.exit(1);
1882
+ }
1883
+ try {
1884
+ const config = await loadConfig({
1885
+ configFile: options.config
1886
+ });
1887
+ const componentDir = join(config.registryPath, "components", componentId);
1888
+ const scenariosDir = join(componentDir, "scenarios");
1889
+ if (existsSync(componentDir)) {
1890
+ console.error(pc.red(`Error: Component "${componentId}" already exists at ${componentDir}`));
1891
+ process.exit(1);
1892
+ }
1893
+ mkdirSync(scenariosDir, { recursive: true });
1894
+ const customElement = componentId.includes("-") ? componentId : `x-${componentId}`;
1895
+ const title = options.title || componentId.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1896
+ const status = options.status || "beta";
1897
+ const kind = options.kind || "standalone";
1898
+ const scenarioId = `${componentId}-default`;
1899
+ const componentJson = {
1900
+ id: componentId,
1901
+ title,
1902
+ status,
1903
+ kind,
1904
+ description: `TODO: Add description for ${title}`,
1905
+ tags: [],
1906
+ exports: {
1907
+ customElement
1908
+ },
1909
+ playground: {
1910
+ primaryScenarioId: scenarioId,
1911
+ controls: {
1912
+ // Example controls - customize as needed
1913
+ }
1914
+ }
1915
+ };
1916
+ writeFileSync(
1917
+ join(componentDir, "component.json"),
1918
+ JSON.stringify(componentJson, null, 2) + "\n"
1919
+ );
1920
+ const scenarioJson = {
1921
+ id: scenarioId,
1922
+ title: "Default",
1923
+ description: `Default state of ${title}`,
1924
+ componentId,
1925
+ primary: true,
1926
+ render: {
1927
+ element: customElement,
1928
+ attrs: {},
1929
+ slots: {
1930
+ default: `${title} content`
1931
+ }
1932
+ }
1933
+ };
1934
+ writeFileSync(
1935
+ join(scenariosDir, "default.json"),
1936
+ JSON.stringify(scenarioJson, null, 2) + "\n"
1937
+ );
1938
+ const docsMd = `# ${title}
1939
+
1940
+ TODO: Add description for ${title}.
1941
+
1942
+ ## Usage
1943
+
1944
+ \`\`\`html
1945
+ <${customElement}>Content</${customElement}>
1946
+ \`\`\`
1947
+
1948
+ ## Properties
1949
+
1950
+ | Property | Type | Default | Description |
1951
+ |----------|------|---------|-------------|
1952
+ | | | | |
1953
+
1954
+ ## Slots
1955
+
1956
+ | Slot | Description |
1957
+ |------|-------------|
1958
+ | default | Main content |
1959
+
1960
+ ## Examples
1961
+
1962
+ ### Basic
1963
+
1964
+ \`\`\`html
1965
+ <${customElement}>
1966
+ Hello World
1967
+ </${customElement}>
1968
+ \`\`\`
1969
+ `;
1970
+ writeFileSync(join(componentDir, "docs.md"), docsMd);
1971
+ const componentTs = `/**
1972
+ * ${title} Web Component
1973
+ */
1974
+
1975
+ export class ${toPascalCase(componentId)} extends HTMLElement {
1976
+ static get observedAttributes() {
1977
+ return [];
1978
+ }
1979
+
1980
+ constructor() {
1981
+ super();
1982
+ this.attachShadow({ mode: 'open' });
1983
+ }
1984
+
1985
+ connectedCallback() {
1986
+ this.render();
1987
+ }
1988
+
1989
+ attributeChangedCallback() {
1990
+ this.render();
1991
+ }
1992
+
1993
+ private render() {
1994
+ this.shadowRoot!.innerHTML = \`
1995
+ <style>
1996
+ :host {
1997
+ display: block;
1998
+ }
1999
+ </style>
2000
+ <div class="${componentId}">
2001
+ <slot></slot>
2002
+ </div>
2003
+ \`;
2004
+ }
2005
+ }
2006
+
2007
+ customElements.define('${customElement}', ${toPascalCase(componentId)});
2008
+ `;
2009
+ const srcDir = join(config.configDir, "src", "components");
2010
+ if (existsSync(srcDir)) {
2011
+ const componentSrcPath = join(srcDir, `${componentId}.ts`);
2012
+ if (!existsSync(componentSrcPath)) {
2013
+ writeFileSync(componentSrcPath, componentTs);
2014
+ console.log(pc.green(`✓ Created ${pc.bold(`src/components/${componentId}.ts`)}`));
2015
+ console.log(pc.yellow(` → Add to src/components/index.ts: export * from './${componentId}.js';`));
2016
+ }
2017
+ }
2018
+ console.log(pc.green(`
2019
+ ✓ Created component "${componentId}":`));
2020
+ console.log(pc.dim(` ${componentDir}/`));
2021
+ console.log(` ├── component.json`);
2022
+ console.log(` ├── docs.md`);
2023
+ console.log(` └── scenarios/`);
2024
+ console.log(` └── default.json`);
2025
+ console.log(pc.cyan("\nNext steps:"));
2026
+ console.log(` 1. Edit ${pc.bold("component.json")} to add controls`);
2027
+ console.log(` 2. Edit ${pc.bold("docs.md")} to add documentation`);
2028
+ console.log(` 3. Add more scenarios in ${pc.bold("scenarios/")}`);
2029
+ console.log(` 4. Run ${pc.bold("catalogue validate")} to verify`);
2030
+ console.log(` 5. Run ${pc.bold("catalogue dev")} to preview`);
2031
+ } catch (error) {
2032
+ console.error(pc.red("Error creating component:"));
2033
+ console.error(error instanceof Error ? error.message : error);
2034
+ process.exit(1);
2035
+ }
2036
+ }
2037
+ function toPascalCase(str) {
2038
+ return str.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
2039
+ }
2040
+ async function init(options = {}) {
2041
+ const cwd = process.cwd();
2042
+ const projectName = options.name || "my-component-catalogue";
2043
+ console.log(pc.cyan(`
2044
+ Initializing component catalogue...
2045
+ `));
2046
+ const configFiles = ["catalogue.config.ts", "catalogue.config.js", "catalogue.config.mjs"];
2047
+ const existingConfig = configFiles.find((f) => existsSync(resolve(cwd, f)));
2048
+ if (existingConfig && !options.force) {
2049
+ console.log(pc.yellow(`Found existing ${existingConfig}. Use --force to overwrite.
2050
+ `));
2051
+ return;
2052
+ }
2053
+ const dirs = [
2054
+ "registry/components",
2055
+ "registry/examples",
2056
+ "src/components",
2057
+ "screenshots"
2058
+ ];
2059
+ for (const dir of dirs) {
2060
+ const dirPath = resolve(cwd, dir);
2061
+ if (!existsSync(dirPath)) {
2062
+ mkdirSync(dirPath, { recursive: true });
2063
+ console.log(pc.green(` Created ${dir}/`));
2064
+ }
2065
+ }
2066
+ const configContent = `import type { CatalogueConfig } from '@adieyal/catalogue-cli';
2067
+
2068
+ export default {
2069
+ name: '${projectName}',
2070
+ registryDir: './registry',
2071
+ componentLoader: () => import('./src/components/index.js'),
2072
+ playwright: {
2073
+ breakpoints: [
2074
+ { name: 'mobile', width: 375, height: 667 },
2075
+ { name: 'tablet', width: 768, height: 1024 },
2076
+ { name: 'desktop', width: 1280, height: 800 },
2077
+ ],
2078
+ themes: ['light', 'dark'],
2079
+ screenshotDir: './screenshots',
2080
+ },
2081
+ } satisfies CatalogueConfig;
2082
+ `;
2083
+ writeFileSync(resolve(cwd, "catalogue.config.ts"), configContent);
2084
+ console.log(pc.green(` Created catalogue.config.ts`));
2085
+ const componentsIndexContent = `// Export your web components here
2086
+ // Example:
2087
+ // export * from './button.js';
2088
+ // export * from './card.js';
2089
+ `;
2090
+ writeFileSync(resolve(cwd, "src/components/index.ts"), componentsIndexContent);
2091
+ console.log(pc.green(` Created src/components/index.ts`));
2092
+ const sampleComponentDir = resolve(cwd, "registry/components/sample-button");
2093
+ mkdirSync(sampleComponentDir, { recursive: true });
2094
+ mkdirSync(resolve(sampleComponentDir, "scenarios"), { recursive: true });
2095
+ const componentJson = {
2096
+ id: "sample-button",
2097
+ title: "Sample Button",
2098
+ status: "beta",
2099
+ kind: "standalone",
2100
+ description: "A sample button component to get you started.",
2101
+ tags: ["form", "interactive"],
2102
+ playground: {
2103
+ primaryScenarioId: "sample-button-default"
2104
+ }
2105
+ };
2106
+ writeFileSync(
2107
+ resolve(sampleComponentDir, "component.json"),
2108
+ JSON.stringify(componentJson, null, 2)
2109
+ );
2110
+ const docsContent = `# Sample Button
2111
+
2112
+ This is a sample button component to help you get started.
2113
+
2114
+ ## Usage
2115
+
2116
+ \`\`\`html
2117
+ <sample-button variant="primary">Click me</sample-button>
2118
+ \`\`\`
2119
+
2120
+ ## Features
2121
+
2122
+ - Multiple variants (primary, secondary, outline)
2123
+ - Disabled state
2124
+ - Click handling
2125
+
2126
+ Delete this component and create your own using \`catalogue new <component-id>\`.
2127
+ `;
2128
+ writeFileSync(resolve(sampleComponentDir, "docs.md"), docsContent);
2129
+ const scenarioJson = {
2130
+ id: "sample-button-default",
2131
+ componentId: "sample-button",
2132
+ title: "Default",
2133
+ description: "Default button appearance",
2134
+ render: {
2135
+ element: "sample-button",
2136
+ attributes: {
2137
+ variant: "primary"
2138
+ },
2139
+ slots: {
2140
+ default: "Click me"
2141
+ }
2142
+ }
2143
+ };
2144
+ writeFileSync(
2145
+ resolve(sampleComponentDir, "scenarios/default.json"),
2146
+ JSON.stringify(scenarioJson, null, 2)
2147
+ );
2148
+ console.log(pc.green(` Created sample-button component`));
2149
+ const sampleButtonTs = `export class SampleButton extends HTMLElement {
2150
+ static observedAttributes = ['variant', 'disabled'];
2151
+
2152
+ constructor() {
2153
+ super();
2154
+ this.attachShadow({ mode: 'open' });
2155
+ }
2156
+
2157
+ connectedCallback() {
2158
+ this.render();
2159
+ }
2160
+
2161
+ attributeChangedCallback() {
2162
+ this.render();
2163
+ }
2164
+
2165
+ get variant() {
2166
+ return this.getAttribute('variant') || 'primary';
2167
+ }
2168
+
2169
+ get disabled() {
2170
+ return this.hasAttribute('disabled');
2171
+ }
2172
+
2173
+ render() {
2174
+ const styles = \`
2175
+ :host {
2176
+ display: inline-block;
2177
+ }
2178
+ button {
2179
+ padding: 8px 16px;
2180
+ border-radius: 4px;
2181
+ border: 2px solid transparent;
2182
+ font-size: 14px;
2183
+ font-weight: 500;
2184
+ cursor: pointer;
2185
+ transition: all 0.2s;
2186
+ }
2187
+ button:disabled {
2188
+ opacity: 0.5;
2189
+ cursor: not-allowed;
2190
+ }
2191
+ button.primary {
2192
+ background: #3b82f6;
2193
+ color: white;
2194
+ }
2195
+ button.primary:hover:not(:disabled) {
2196
+ background: #2563eb;
2197
+ }
2198
+ button.secondary {
2199
+ background: #6b7280;
2200
+ color: white;
2201
+ }
2202
+ button.secondary:hover:not(:disabled) {
2203
+ background: #4b5563;
2204
+ }
2205
+ button.outline {
2206
+ background: transparent;
2207
+ border-color: #3b82f6;
2208
+ color: #3b82f6;
2209
+ }
2210
+ button.outline:hover:not(:disabled) {
2211
+ background: #3b82f6;
2212
+ color: white;
2213
+ }
2214
+ \`;
2215
+
2216
+ this.shadowRoot!.innerHTML = \`
2217
+ <style>\${styles}</style>
2218
+ <button class="\${this.variant}" \${this.disabled ? 'disabled' : ''}>
2219
+ <slot></slot>
2220
+ </button>
2221
+ \`;
2222
+ }
2223
+ }
2224
+
2225
+ customElements.define('sample-button', SampleButton);
2226
+ `;
2227
+ writeFileSync(resolve(cwd, "src/components/sample-button.ts"), sampleButtonTs);
2228
+ console.log(pc.green(` Created src/components/sample-button.ts`));
2229
+ writeFileSync(
2230
+ resolve(cwd, "src/components/index.ts"),
2231
+ `export * from './sample-button.js';
2232
+ `
2233
+ );
2234
+ const packageJsonPath = resolve(cwd, "package.json");
2235
+ let packageJson = {};
2236
+ if (existsSync(packageJsonPath)) {
2237
+ packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
2238
+ }
2239
+ packageJson.scripts = {
2240
+ ...packageJson.scripts || {},
2241
+ "catalogue": "catalogue dev",
2242
+ "catalogue:build": "catalogue build",
2243
+ "catalogue:preview": "catalogue preview",
2244
+ "catalogue:validate": "catalogue validate",
2245
+ "catalogue:test": "catalogue test"
2246
+ };
2247
+ writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
2248
+ console.log(pc.green(` Updated package.json scripts`));
2249
+ const tsconfigPath = resolve(cwd, "tsconfig.json");
2250
+ if (!existsSync(tsconfigPath)) {
2251
+ const tsconfig = {
2252
+ compilerOptions: {
2253
+ target: "ES2022",
2254
+ module: "ESNext",
2255
+ moduleResolution: "bundler",
2256
+ strict: true,
2257
+ esModuleInterop: true,
2258
+ skipLibCheck: true,
2259
+ declaration: true,
2260
+ outDir: "./dist",
2261
+ rootDir: "./src"
2262
+ },
2263
+ include: ["src/**/*", "catalogue.config.ts"],
2264
+ exclude: ["node_modules", "dist"]
2265
+ };
2266
+ writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n");
2267
+ console.log(pc.green(` Created tsconfig.json`));
2268
+ }
2269
+ const gitignorePath = resolve(cwd, ".gitignore");
2270
+ const gitignoreAdditions = `
2271
+ # Catalogue
2272
+ .catalogue-tests/
2273
+ playwright-report/
2274
+ test-results/
2275
+ `;
2276
+ if (existsSync(gitignorePath)) {
2277
+ const existing = readFileSync(gitignorePath, "utf-8");
2278
+ if (!existing.includes(".catalogue-tests")) {
2279
+ writeFileSync(gitignorePath, existing + gitignoreAdditions);
2280
+ console.log(pc.green(` Updated .gitignore`));
2281
+ }
2282
+ } else {
2283
+ writeFileSync(gitignorePath, `node_modules/
2284
+ dist/
2285
+ ${gitignoreAdditions}`);
2286
+ console.log(pc.green(` Created .gitignore`));
2287
+ }
2288
+ if (options.withClaude) {
2289
+ const claudeSkillsDir = resolve(cwd, ".claude/skills");
2290
+ mkdirSync(claudeSkillsDir, { recursive: true });
2291
+ const __filename = fileURLToPath(import.meta.url);
2292
+ const __dirname = dirname(__filename);
2293
+ const packageSkillsDir = resolve(__dirname, "../../skills");
2294
+ if (existsSync(packageSkillsDir)) {
2295
+ const skillFiles = readdirSync(packageSkillsDir).filter((f) => f.endsWith(".md"));
2296
+ for (const file of skillFiles) {
2297
+ const content = readFileSync(resolve(packageSkillsDir, file), "utf-8");
2298
+ writeFileSync(resolve(claudeSkillsDir, file), content);
2299
+ }
2300
+ console.log(pc.green(` Created .claude/skills/ (${skillFiles.length} skills)`));
2301
+ } else {
2302
+ installEmbeddedSkills(claudeSkillsDir);
2303
+ console.log(pc.green(` Created .claude/skills/`));
2304
+ }
2305
+ }
2306
+ console.log(pc.cyan(`
2307
+ ✓ Component catalogue initialized!
2308
+ `));
2309
+ console.log(`Next steps:
2310
+ `);
2311
+ console.log(` 1. Install dependencies:`);
2312
+ console.log(pc.gray(` npm install @adieyal/catalogue-cli @adieyal/catalogue-core
2313
+ `));
2314
+ console.log(` 2. Start the dev server:`);
2315
+ console.log(pc.gray(` npm run catalogue
2316
+ `));
2317
+ console.log(` 3. Create your first component:`);
2318
+ console.log(pc.gray(` npx catalogue new my-component
2319
+ `));
2320
+ if (options.withClaude) {
2321
+ console.log(` Claude Code skills installed. Available commands:`);
2322
+ console.log(pc.gray(` /new-component <id> - Create a new component`));
2323
+ console.log(pc.gray(` /new-scenario <id> <name> - Add a scenario`));
2324
+ console.log(pc.gray(` /document-component <id> - Generate docs`));
2325
+ console.log(pc.gray(` /migrate-library [path] - Migrate existing library`));
2326
+ console.log(pc.gray(` /setup-tokens [path] - Configure design tokens
2327
+ `));
2328
+ }
2329
+ }
2330
+ function installEmbeddedSkills(skillsDir) {
2331
+ const skills = {
2332
+ "new-component.md": `# Create New Component
2333
+
2334
+ Create a new component entry in the catalogue registry.
2335
+
2336
+ ## Arguments
2337
+
2338
+ - \`$ARGUMENTS\` - Component ID (kebab-case, e.g., \`user-avatar\`)
2339
+
2340
+ ## Instructions
2341
+
2342
+ 1. Parse the component ID from \`$ARGUMENTS\`. If empty, ask the user for a component ID (must be kebab-case).
2343
+
2344
+ 2. Derive the component title by converting kebab-case to Title Case (e.g., \`user-avatar\` → \`User Avatar\`).
2345
+
2346
+ 3. Ask the user:
2347
+ - What is the component's purpose/description?
2348
+ - What status? (stable, beta, deprecated) - default: beta
2349
+ - What kind? (standalone, subcomponent, feature) - default: standalone
2350
+ - What tags apply? (e.g., form, layout, navigation, feedback)
2351
+
2352
+ 4. Create the directory structure:
2353
+ - \`registry/components/<id>/\`
2354
+ - \`registry/components/<id>/scenarios/\`
2355
+
2356
+ 5. Create \`registry/components/<id>/component.json\`:
2357
+
2358
+ \`\`\`json
2359
+ {
2360
+ "id": "<component-id>",
2361
+ "title": "<Component Title>",
2362
+ "status": "<status>",
2363
+ "kind": "<kind>",
2364
+ "description": "<description>",
2365
+ "tags": [<tags>],
2366
+ "playground": {
2367
+ "primaryScenarioId": "<component-id>-default"
2368
+ }
2369
+ }
2370
+ \`\`\`
2371
+
2372
+ 6. Create \`registry/components/<id>/docs.md\`:
2373
+
2374
+ \`\`\`markdown
2375
+ # <Component Title>
2376
+
2377
+ <description>
2378
+
2379
+ ## Usage
2380
+
2381
+ \\<\\<component-id>>\\</\\<component-id>>
2382
+
2383
+ ## Properties
2384
+
2385
+ | Property | Type | Default | Description |
2386
+ |----------|------|---------|-------------|
2387
+
2388
+ ## Slots
2389
+
2390
+ | Slot | Description |
2391
+ |------|-------------|
2392
+ | default | Main content |
2393
+ \`\`\`
2394
+
2395
+ 7. Create \`registry/components/<id>/scenarios/default.json\`:
2396
+
2397
+ \`\`\`json
2398
+ {
2399
+ "id": "<component-id>-default",
2400
+ "componentId": "<component-id>",
2401
+ "title": "Default",
2402
+ "description": "Default appearance",
2403
+ "render": {
2404
+ "element": "<component-id>",
2405
+ "slots": {
2406
+ "default": "Content"
2407
+ }
2408
+ }
2409
+ }
2410
+ \`\`\`
2411
+
2412
+ 8. Create \`src/components/<id>.ts\` with a Web Component class:
2413
+ - Class name: PascalCase version of the ID (e.g., \`user-avatar\` → \`UserAvatar\`)
2414
+ - Use Shadow DOM with \`mode: 'open'\`
2415
+ - Include basic styles and a slot
2416
+
2417
+ 9. Update \`src/components/index.ts\` to add:
2418
+ \`\`\`typescript
2419
+ export * from './<component-id>.js';
2420
+ \`\`\`
2421
+
2422
+ 10. Run \`npx catalogue validate\` to verify.
2423
+
2424
+ 11. Tell the user what was created and suggest:
2425
+ - Adding properties to the component
2426
+ - Creating more scenarios
2427
+ - Updating the docs
2428
+ `,
2429
+ "new-scenario.md": `# Create New Scenario
2430
+
2431
+ Add a new scenario to an existing component.
2432
+
2433
+ ## Arguments
2434
+
2435
+ - \`$ARGUMENTS\` - Format: \`<component-id> <scenario-name>\` (e.g., \`button disabled\`)
2436
+
2437
+ ## Instructions
2438
+
2439
+ 1. Parse arguments from \`$ARGUMENTS\`:
2440
+ - First word: component ID
2441
+ - Remaining words: scenario name
2442
+ - If missing, ask the user for component ID and scenario name
2443
+
2444
+ 2. Verify the component exists at \`registry/components/<component-id>/component.json\`. If not, list available components and ask user to choose.
2445
+
2446
+ 3. Generate scenario ID: \`<component-id>-<scenario-name-kebab>\` (e.g., \`button-disabled\`)
2447
+
2448
+ 4. Ask the user:
2449
+ - Brief description of this scenario
2450
+ - What attributes/properties should be set?
2451
+ - What slot content should be shown?
2452
+ - Any custom viewport size? (optional)
2453
+ - Any custom background color? (optional)
2454
+
2455
+ 5. Create \`registry/components/<component-id>/scenarios/<scenario-name-kebab>.json\`:
2456
+
2457
+ \`\`\`json
2458
+ {
2459
+ "id": "<scenario-id>",
2460
+ "componentId": "<component-id>",
2461
+ "title": "<Scenario Title>",
2462
+ "description": "<description>",
2463
+ "render": {
2464
+ "element": "<component-id>",
2465
+ "attributes": {
2466
+ // based on user input
2467
+ },
2468
+ "slots": {
2469
+ "default": "<slot content>"
2470
+ }
2471
+ }
2472
+ }
2473
+ \`\`\`
2474
+
2475
+ Include optional fields only if provided:
2476
+ - \`"viewport": { "width": <w>, "height": <h> }\`
2477
+ - \`"background": "<color>"\`
2478
+
2479
+ 6. Run \`npx catalogue validate\` to verify.
2480
+
2481
+ 7. Tell the user:
2482
+ - Scenario created at \`registry/components/<id>/scenarios/<name>.json\`
2483
+ - View it at \`http://localhost:5173/#/harness/<scenario-id>\`
2484
+ - Suggest running \`npm run catalogue\` to see it
2485
+ `,
2486
+ "document-component.md": `# Document Component
2487
+
2488
+ Generate or improve documentation for a component by analyzing its source code.
2489
+
2490
+ ## Arguments
2491
+
2492
+ - \`$ARGUMENTS\` - Component ID (e.g., \`button\`)
2493
+
2494
+ ## Instructions
2495
+
2496
+ 1. Parse component ID from \`$ARGUMENTS\`. If empty, list components in \`registry/components/\` and ask user to choose.
2497
+
2498
+ 2. Read the component source file at \`src/components/<id>.ts\` (or \`.js\`).
2499
+
2500
+ 3. Analyze the source code to extract:
2501
+ - \`static observedAttributes\` → Properties
2502
+ - Getter/setter pairs → Properties with types
2503
+ - \`<slot>\` elements → Available slots
2504
+ - \`this.dispatchEvent()\` calls → Events
2505
+ - Any JSDoc comments
2506
+
2507
+ 4. Read existing docs at \`registry/components/<id>/docs.md\`.
2508
+
2509
+ 5. Read \`registry/components/<id>/component.json\` for title and description.
2510
+
2511
+ 6. Generate improved documentation with sections for Usage, Properties, Slots, Events, Examples, and Accessibility.
2512
+
2513
+ 7. Show the user the generated docs and ask if they want to:
2514
+ - Replace existing docs entirely
2515
+ - Merge with existing docs
2516
+ - Just see the output without saving
2517
+
2518
+ 8. If saving, write to \`registry/components/<id>/docs.md\`.
2519
+
2520
+ 9. Suggest creating scenarios for any documented states/variants not yet covered.
2521
+ `,
2522
+ "migrate-library.md": `# Migrate Existing Component Library
2523
+
2524
+ Guide migration of an existing component library to use the catalogue.
2525
+
2526
+ ## Arguments
2527
+
2528
+ - \`$ARGUMENTS\` - Optional: path to components directory (e.g., \`src/components\`)
2529
+
2530
+ ## Instructions
2531
+
2532
+ Follow this phased approach. Do NOT try to "port everything" at once.
2533
+
2534
+ ### Phase 1: Assess the Library
2535
+
2536
+ 1. If \`$ARGUMENTS\` provided, scan that directory. Otherwise, look for common patterns:
2537
+ - \`src/components/\`
2538
+ - \`lib/components/\`
2539
+ - \`packages/*/src/\`
2540
+
2541
+ 2. List all components found (custom elements, web components, or framework components).
2542
+
2543
+ 3. Check how components are currently registered:
2544
+ - Side-effect registration on import?
2545
+ - Manual \`customElements.define()\` calls?
2546
+
2547
+ 4. Check for existing: design tokens, theme support, documentation, demo pages.
2548
+
2549
+ 5. Report findings to user before proceeding.
2550
+
2551
+ ### Phase 2: Create Registration Entrypoint
2552
+
2553
+ Create \`src/register-all.ts\` that imports all components:
2554
+
2555
+ \`\`\`typescript
2556
+ import './components/button/button';
2557
+ import './components/card/card';
2558
+ \`\`\`
2559
+
2560
+ Update \`catalogue.config.ts\` to point to this entrypoint.
2561
+
2562
+ ### Phase 3: Generate Seed Registry
2563
+
2564
+ For EACH component, create minimal registry files:
2565
+ - \`registry/components/<id>/component.json\`
2566
+ - \`registry/components/<id>/scenarios/default.json\`
2567
+ - \`registry/components/<id>/docs.md\`
2568
+
2569
+ Rules: 1 scenario per component is enough initially. Use default props.
2570
+
2571
+ ### Phase 4: Add Critical Variants (2-5 per component)
2572
+
2573
+ - Default/primary state
2574
+ - Disabled/loading (if applicable)
2575
+ - Dense case (long labels, overflow)
2576
+ - Responsive stress (narrow container)
2577
+
2578
+ Do NOT create scenarios for every prop combination.
2579
+
2580
+ ### Phase 5: Establish Hierarchy
2581
+
2582
+ - \`standalone\` - User-facing components
2583
+ - \`subcomponent\` - Internal pieces (add \`parentId\`)
2584
+ - \`feature\` - Complex composites
2585
+
2586
+ ### Phase 6: Validate
2587
+
2588
+ Run \`npx catalogue validate\` and \`npx catalogue dev\`.
2589
+
2590
+ ### Migration Tips
2591
+
2592
+ **Components requiring app context:** Create wrapper scenarios that provide context.
2593
+
2594
+ **Components that fetch data:** Add prop to accept data directly. Scenarios must NOT make network calls.
2595
+
2596
+ **Container queries:** Recommend \`@container\` for component internals, \`@media\` for page layout. The catalogue resizer tests container width, not viewport.
2597
+
2598
+ ### What "Done" Looks Like
2599
+
2600
+ - Every component has a primary scenario
2601
+ - Key components have 3-5 variants
2602
+ - Playground works for top 20% of components
2603
+ - Old demo pages can be deleted
2604
+ `,
2605
+ "setup-tokens.md": `# Setup Design Tokens
2606
+
2607
+ Configure design tokens for the catalogue so components render with correct styling.
2608
+
2609
+ ## Arguments
2610
+
2611
+ - \`$ARGUMENTS\` - Optional: path to tokens file (e.g., \`src/tokens/tokens.css\`)
2612
+
2613
+ ## Instructions
2614
+
2615
+ ### Step 1: Identify Token Source
2616
+
2617
+ Search for: \`**/tokens.css\`, \`**/variables.css\`, \`**/theme.css\`, \`**/design-tokens.json\`
2618
+
2619
+ Ask user to confirm the correct path.
2620
+
2621
+ ### Step 2: Identify Themes
2622
+
2623
+ Check for:
2624
+ - Separate files: \`tokens-light.css\`, \`tokens-dark.css\`
2625
+ - Selectors: \`[data-theme="dark"]\`, \`.dark-theme\`
2626
+
2627
+ ### Step 3: Update Configuration
2628
+
2629
+ **Single theme:**
2630
+ \`\`\`typescript
2631
+ export default {
2632
+ themes: {
2633
+ tokenCss: './src/tokens/tokens.css',
2634
+ }
2635
+ }
2636
+ \`\`\`
2637
+
2638
+ **Multiple themes:**
2639
+ \`\`\`typescript
2640
+ export default {
2641
+ themes: {
2642
+ default: 'light',
2643
+ available: ['light', 'dark'],
2644
+ tokenCss: {
2645
+ light: './src/tokens/tokens-light.css',
2646
+ dark: './src/tokens/tokens-dark.css',
2647
+ }
2648
+ }
2649
+ }
2650
+ \`\`\`
2651
+
2652
+ ### Step 4: Scope Decision
2653
+
2654
+ **Model A - Global tokens (recommended):** Tokens apply to entire document.
2655
+
2656
+ **Model B - Scoped tokens:** Tokens apply only inside preview frame. Better if tokens use generic names (\`--text\`, \`--bg\`).
2657
+
2658
+ ### Step 5: Container Query Setup
2659
+
2660
+ Ensure preview frame has:
2661
+ \`\`\`css
2662
+ .preview-frame {
2663
+ container-type: inline-size;
2664
+ }
2665
+ \`\`\`
2666
+
2667
+ Tell user: "Components should use \`@container\` queries for internal responsiveness, not \`@media\` queries."
2668
+
2669
+ ### Step 6: Verify
2670
+
2671
+ 1. Run \`npx catalogue dev\`
2672
+ 2. Check components render with correct colors, typography, spacing
2673
+ 3. Toggle themes and verify switching works
2674
+ 4. Resize preview frame and verify container queries respond
2675
+ `
2676
+ };
2677
+ for (const [filename, content] of Object.entries(skills)) {
2678
+ writeFileSync(resolve(skillsDir, filename), content);
2679
+ }
2680
+ }
2681
+ export {
2682
+ CatalogueConfigSchema as C,
2683
+ getRegistryWatchPaths as a,
2684
+ createHtmlTemplate as b,
2685
+ cataloguePlugin as c,
2686
+ buildStatic as d,
2687
+ startPreviewServer as e,
2688
+ dev as f,
2689
+ generateRegistryModule as g,
2690
+ build as h,
2691
+ init as i,
2692
+ validateConfig as j,
2693
+ loadConfig as k,
2694
+ loadRegistryFiles as l,
2695
+ createDefaultConfig as m,
2696
+ newComponent as n,
2697
+ preview as p,
2698
+ startDevServer as s,
2699
+ test as t,
2700
+ validate as v
2701
+ };
2702
+ //# sourceMappingURL=init-CI0WzrG1.js.map