@texturehq/edges 1.30.0 → 1.30.2

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.
@@ -1,1103 +0,0 @@
1
- // generate-edges-docs.js
2
- // Unified AI Context Generator for @texturehq/edges
3
- //
4
- // Consolidates and enhances:
5
- // - generate-components-manifest.js
6
- // - generate-utilities-manifest.js
7
- //
8
- // Generates:
9
- // - Component, utility, and hook manifests
10
- // - docs/ai/edges/ markdown documentation
11
- // - .agent-fragments/edges.md (for merging with other fragments)
12
- // - .cursor/rules/edges-components.mdc
13
- // - Distributable context files for consuming apps
14
-
15
- import { execSync } from "child_process";
16
- import fs from "fs";
17
- import path from "path";
18
-
19
- const ROOT = process.cwd();
20
- const SRC_DIR = path.join(ROOT, "src");
21
- const DIST_DIR = path.join(ROOT, "dist");
22
- const MONO_ROOT = path.join(ROOT, "../..");
23
- const DOCS_AI_EDGES = path.join(MONO_ROOT, "docs/ai/edges");
24
-
25
- const PACKAGE_JSON_PATH = path.join(ROOT, "package.json");
26
- const PACKAGE_VERSION = JSON.parse(fs.readFileSync(PACKAGE_JSON_PATH, "utf8")).version;
27
-
28
- // ============================================================================
29
- // UTILITIES
30
- // ============================================================================
31
-
32
- function read(file) {
33
- return fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
34
- }
35
-
36
- function ensureDir(dir) {
37
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
38
- }
39
-
40
- function escapeMarkdownTableCell(text) {
41
- // Only escape backslashes since the text will be wrapped in backticks (inline code).
42
- // Pipes and backticks don't need escaping inside inline code spans.
43
- return text.replace(/\\/g, "\\\\");
44
- }
45
-
46
- function writeFile(filePath, content) {
47
- ensureDir(path.dirname(filePath));
48
- fs.writeFileSync(filePath, content, "utf8");
49
- console.log(`✓ Generated: ${filePath}`);
50
- }
51
-
52
- // ============================================================================
53
- // COMPONENT MANIFEST GENERATION
54
- // ============================================================================
55
-
56
- function extractExportsFromIndex(indexContent) {
57
- const re = /export\s+\{\s*([A-Za-z0-9_,\s]+)\s*\}\s+from\s+"\.\/components\/([A-Za-z0-9_/.-]+)"/g;
58
- const results = [];
59
- let m;
60
- while ((m = re.exec(indexContent))) {
61
- const exported = m[1]
62
- .split(",")
63
- .map((s) => s.trim())
64
- .filter(Boolean);
65
- const rel = m[2];
66
- for (const name of exported) {
67
- results.push({ name, relPath: rel });
68
- }
69
- }
70
- return results;
71
- }
72
-
73
- function findComponentFile(relPath) {
74
- const base = path.join(SRC_DIR, "components", relPath);
75
- const componentName = relPath.split("/").pop();
76
- const candidates = [
77
- path.join(base, `${componentName}.tsx`),
78
- path.join(base, `${componentName}.ts`),
79
- path.join(base, `index.tsx`),
80
- path.join(base, `index.ts`),
81
- ];
82
- for (const f of candidates) {
83
- if (fs.existsSync(f)) return f;
84
- }
85
- return null;
86
- }
87
-
88
- function findStorybookFile(relPath) {
89
- const base = path.join(SRC_DIR, "components", relPath);
90
- const componentName = relPath.split("/").pop();
91
- const candidates = [path.join(base, `${componentName}.stories.tsx`), path.join(base, `${componentName}.stories.ts`)];
92
- for (const f of candidates) {
93
- if (fs.existsSync(f)) return f;
94
- }
95
- return null;
96
- }
97
-
98
- function extractStorybookCategory(storyContent) {
99
- // Extract: title: "Form Controls/Button"
100
- const titleMatch = storyContent.match(/title:\s*["']([^"']+)["']/);
101
- if (titleMatch) {
102
- const fullTitle = titleMatch[1];
103
- const parts = fullTitle.split("/");
104
- if (parts.length > 1) {
105
- return parts[0]; // "Form Controls"
106
- }
107
- }
108
- return null;
109
- }
110
-
111
- function extractRelatedComponents(storyContent) {
112
- // Find other component imports in the story file
113
- const importMatch = storyContent.match(/import\s*\{([^}]+)\}\s*from\s*["']\.\.\/(\w+)["']/g);
114
- const related = new Set();
115
- if (importMatch) {
116
- for (const imp of importMatch) {
117
- const m = imp.match(/import\s*\{([^}]+)\}/);
118
- if (m) {
119
- const imports = m[1].split(",").map((s) => s.trim());
120
- for (const name of imports) {
121
- if (/^[A-Z]/.test(name)) {
122
- related.add(name);
123
- }
124
- }
125
- }
126
- }
127
- }
128
- return Array.from(related);
129
- }
130
-
131
- function getJsdocAbove(content, idx) {
132
- const upTo = content.slice(0, idx);
133
- const start = upTo.lastIndexOf("/**");
134
- const end = upTo.lastIndexOf("*/");
135
- if (start !== -1 && end !== -1 && end > start) {
136
- const block = upTo.slice(start + 3, end).trim();
137
- return block
138
- .split("\n")
139
- .map((l) => l.replace(/^\s*\*\s?/, "").trim())
140
- .join(" ")
141
- .trim();
142
- }
143
- return "";
144
- }
145
-
146
- function extractProps(content, componentName) {
147
- const ifaceRe = new RegExp(
148
- `export\\s+interface\\s+${componentName}Props\\s*(?:extends\\s+[^{]+)?\\{([\\s\\S]*?)\\}`,
149
- "m"
150
- );
151
- let m = ifaceRe.exec(content);
152
- if (!m) {
153
- const anyIfaceRe = /export\s+interface\s+([A-Za-z0-9_]+Props)\s*(?:extends\s+[^{]+)?\{([\s\S]*?)\}/m;
154
- m = anyIfaceRe.exec(content);
155
- }
156
- if (!m) {
157
- const typeRe = new RegExp(`export\\s+type\\s+${componentName}Props\\s*=\\s*(?:[^{&]+&\s*)?([\\s\\S]*?)\\}`, "m");
158
- m = typeRe.exec(content);
159
- }
160
- const props = [];
161
- if (m) {
162
- const body = m[2] || m[1] || "";
163
- // Parse props more carefully to handle nested types
164
- let currentProp = "";
165
- let braceDepth = 0;
166
- const lines = body.split("\n");
167
-
168
- for (const line of lines) {
169
- const trimmed = line.trim();
170
- if (!trimmed || trimmed.startsWith("//")) continue;
171
-
172
- // Count braces to track nested objects
173
- for (const char of trimmed) {
174
- if (char === "{") braceDepth++;
175
- if (char === "}") braceDepth--;
176
- }
177
-
178
- currentProp += (currentProp ? " " : "") + trimmed;
179
-
180
- // If we're at depth 0 and the line ends with semicolon or we're complete, process the prop
181
- if (braceDepth === 0 && (trimmed.endsWith(";") || trimmed.endsWith(","))) {
182
- const pm = /^(readonly\s+)?([A-Za-z0-9_]+)\??:\s*(.+?)[;,]?\s*$/.exec(currentProp.trim());
183
- if (pm) {
184
- const name = pm[2];
185
- let type = pm[3].trim();
186
- // Clean up type - remove trailing semicolon/comma
187
- type = type.replace(/[;,]\s*$/, "");
188
- // Simplify very long nested types for readability
189
- if (type.includes("{") && type.length > 100) {
190
- // For nested object types, show simplified version
191
- type = type.substring(0, 80) + "...}";
192
- }
193
- props.push({ name, type });
194
- }
195
- currentProp = "";
196
- braceDepth = 0;
197
- }
198
- }
199
- }
200
- return props;
201
- }
202
-
203
- function generateComponentsManifest() {
204
- console.log("\n📦 Generating components manifest...");
205
-
206
- const indexPath = path.join(SRC_DIR, "index.ts");
207
- const indexContent = read(indexPath);
208
- const exports = extractExportsFromIndex(indexContent);
209
-
210
- const components = [];
211
- const categories = {};
212
-
213
- for (const { name, relPath } of exports) {
214
- if (!/^[A-Z]/.test(name)) continue;
215
-
216
- const componentFile = findComponentFile(relPath);
217
- if (!componentFile) continue;
218
- const content = read(componentFile);
219
-
220
- // Extract description from JSDoc
221
- let idx = -1;
222
- let description = "";
223
- // Use regex patterns with word boundaries to avoid substring matches
224
- const patterns = [
225
- new RegExp(`export\\s+function\\s+${name}\\s*[({<]`),
226
- new RegExp(`export\\s+const\\s+${name}\\s*[:=]`),
227
- new RegExp(`export\\s+class\\s+${name}\\s+`),
228
- ];
229
-
230
- for (const pattern of patterns) {
231
- const match = content.search(pattern);
232
- if (match !== -1) {
233
- idx = match;
234
- break;
235
- }
236
- }
237
-
238
- if (idx !== -1) {
239
- description = getJsdocAbove(content, idx);
240
- }
241
-
242
- // Extract props
243
- const props = extractProps(content, name);
244
- // Sort props alphabetically for deterministic output
245
- props.sort((a, b) => a.name.localeCompare(b.name));
246
-
247
- // Extract Storybook metadata
248
- const storyFile = findStorybookFile(relPath);
249
- let category = "Uncategorized";
250
- let relatedComponents = [];
251
-
252
- if (storyFile) {
253
- const storyContent = read(storyFile);
254
- const storyCategory = extractStorybookCategory(storyContent);
255
- if (storyCategory) category = storyCategory;
256
- relatedComponents = extractRelatedComponents(storyContent);
257
- }
258
-
259
- const component = {
260
- name,
261
- category,
262
- description,
263
- importRoot: "@texturehq/edges",
264
- importPath: `@texturehq/edges/components/${name}`,
265
- props,
266
- relatedComponents,
267
- storybookPath: category + "/" + name,
268
- };
269
-
270
- components.push(component);
271
-
272
- // Organize by category
273
- if (!categories[category]) {
274
- categories[category] = [];
275
- }
276
- categories[category].push(name);
277
- }
278
-
279
- // Sort components by name
280
- components.sort((a, b) => a.name.localeCompare(b.name));
281
-
282
- // Sort category arrays
283
- for (const cat in categories) {
284
- categories[cat].sort();
285
- }
286
-
287
- const manifest = {
288
- version: PACKAGE_VERSION,
289
- // generatedAt removed to prevent unnecessary git churn
290
- // Use version field to track which package version generated this
291
- components: components,
292
- categories: categories,
293
- };
294
-
295
- ensureDir(DIST_DIR);
296
- writeFile(path.join(DIST_DIR, "components.manifest.json"), JSON.stringify(manifest, null, 2));
297
-
298
- console.log(` Found ${components.length} components in ${Object.keys(categories).length} categories`);
299
- return manifest;
300
- }
301
-
302
- // ============================================================================
303
- // UTILITIES & HOOKS MANIFEST GENERATION
304
- // ============================================================================
305
-
306
- function extractJSDoc(content, functionName) {
307
- const patterns = [
308
- new RegExp(`\\/\\*\\*([^*]|\\*(?!\\/))*\\*\\/\\s*export\\s+(?:async\\s+)?function\\s+${functionName}\\b`, "s"),
309
- new RegExp(`\\/\\*\\*([^*]|\\*(?!\\/))*\\*\\/\\s*export\\s+const\\s+${functionName}\\s*=`, "s"),
310
- ];
311
-
312
- for (const pattern of patterns) {
313
- const match = content.match(pattern);
314
- if (match) {
315
- const jsdocMatch = match[0].match(/\/\*\*([\s\S]*?)\*\//);
316
- if (jsdocMatch) {
317
- const lines = jsdocMatch[1].split("\n").map((line) => line.replace(/^\s*\*\s?/, "").trim());
318
-
319
- // Extract only the main description (before any @tags)
320
- const descriptionLines = [];
321
- for (const line of lines) {
322
- if (line.startsWith("@")) {
323
- break; // Stop at first @tag
324
- }
325
- if (line) {
326
- descriptionLines.push(line);
327
- }
328
- }
329
-
330
- return descriptionLines.join(" ").trim();
331
- }
332
- }
333
- }
334
- return "";
335
- }
336
-
337
- function extractExportsFromFile(filePath) {
338
- const content = read(filePath);
339
- if (!content) return [];
340
-
341
- const utilities = [];
342
-
343
- // Helper to check if a match is inside a comment
344
- function isInComment(matchIndex) {
345
- const beforeMatch = content.slice(0, matchIndex);
346
-
347
- // Check if we're inside a JSDoc comment (/** ... */)
348
- const lastJSDocStart = beforeMatch.lastIndexOf("/**");
349
- const lastJSDocEnd = beforeMatch.lastIndexOf("*/");
350
- if (lastJSDocStart > lastJSDocEnd) {
351
- return true; // Inside JSDoc comment
352
- }
353
-
354
- // Check if we're inside a regular block comment (/* ... */)
355
- const lastBlockStart = beforeMatch.lastIndexOf("/*");
356
- const lastBlockEnd = beforeMatch.lastIndexOf("*/");
357
- if (lastBlockStart > lastBlockEnd) {
358
- // Make sure it's not a JSDoc opener (/**)
359
- const blockOpener = beforeMatch.slice(lastBlockStart, lastBlockStart + 3);
360
- if (blockOpener !== "/**") {
361
- return true; // Inside regular block comment
362
- }
363
- }
364
-
365
- // Check if we're on a line comment (//)
366
- const lastNewline = beforeMatch.lastIndexOf("\n");
367
- const lineStart = lastNewline === -1 ? 0 : lastNewline + 1;
368
- const line = beforeMatch.slice(lineStart);
369
- if (line.includes("//")) {
370
- return true; // Inside line comment
371
- }
372
-
373
- return false;
374
- }
375
-
376
- // Match export function declarations
377
- const functionPattern = /export\s+(?:async\s+)?function\s+(\w+)/g;
378
- let match;
379
- while ((match = functionPattern.exec(content))) {
380
- if (!isInComment(match.index)) {
381
- const name = match[1];
382
- utilities.push({
383
- name,
384
- type: "function",
385
- description: extractJSDoc(content, name),
386
- });
387
- }
388
- }
389
-
390
- // Match export const arrow functions and constants
391
- const constPattern = /export\s+const\s+(\w+)\s*(?::\s*[^=]+)?\s*=/g;
392
- while ((match = constPattern.exec(content))) {
393
- if (!isInComment(match.index)) {
394
- const name = match[1];
395
- const afterMatch = content.slice(match.index + match[0].length, match.index + match[0].length + 50);
396
- const isFunction = afterMatch.includes("=>") || afterMatch.includes("function");
397
-
398
- utilities.push({
399
- name,
400
- type: isFunction ? "function" : "constant",
401
- description: extractJSDoc(content, name),
402
- });
403
- }
404
- }
405
-
406
- return utilities;
407
- }
408
-
409
- function scanDirectory(dir, category) {
410
- const utilities = [];
411
-
412
- if (!fs.existsSync(dir)) return utilities;
413
-
414
- const files = fs.readdirSync(dir);
415
-
416
- for (const file of files) {
417
- const filePath = path.join(dir, file);
418
- const stat = fs.statSync(filePath);
419
-
420
- if (stat.isDirectory() && file !== "__tests__" && file !== "tests") {
421
- utilities.push(...scanDirectory(filePath, `${category}/${file}`));
422
- } else if ((file.endsWith(".ts") || file.endsWith(".tsx")) && !file.endsWith(".d.ts")) {
423
- if (file.includes(".test.") || file.includes(".spec.") || file.includes(".stories.")) {
424
- continue;
425
- }
426
-
427
- const funcs = extractExportsFromFile(filePath);
428
- for (const func of funcs) {
429
- utilities.push({
430
- ...func,
431
- category,
432
- file: path.relative(SRC_DIR, filePath),
433
- });
434
- }
435
- }
436
- }
437
-
438
- return utilities;
439
- }
440
-
441
- function generateUtilitiesManifest() {
442
- console.log("\n🔧 Generating utilities manifest...");
443
-
444
- const manifest = {
445
- version: PACKAGE_VERSION,
446
- // generatedAt removed to prevent unnecessary git churn
447
- // Use version field to track which package version generated this
448
- categories: {},
449
- };
450
-
451
- // Scan hooks directory
452
- const hooksDir = path.join(SRC_DIR, "hooks");
453
- const hooks = scanDirectory(hooksDir, "hooks");
454
- if (hooks.length > 0) {
455
- manifest.categories.hooks = {
456
- description:
457
- "React hooks for common functionality like breakpoints, debouncing, local storage, and media queries",
458
- utilities: hooks.sort((a, b) => a.name.localeCompare(b.name)),
459
- };
460
- }
461
-
462
- // Scan formatting utilities
463
- const formattingDir = path.join(SRC_DIR, "utils/formatting");
464
- const formatting = scanDirectory(formattingDir, "formatting");
465
- if (formatting.length > 0) {
466
- manifest.categories.formatting = {
467
- description:
468
- "Comprehensive formatting utilities for text, numbers, dates, currency, energy, temperature, distance, and more",
469
- utilities: formatting.sort((a, b) => a.name.localeCompare(b.name)),
470
- };
471
- }
472
-
473
- // Chart utilities
474
- const chartsDir = path.join(SRC_DIR, "utils/charts");
475
- const charts = scanDirectory(chartsDir, "charts");
476
- const chartExportUtils = extractExportsFromFile(path.join(SRC_DIR, "utils/chartExport.ts"));
477
- const chartUtils = extractExportsFromFile(path.join(SRC_DIR, "utils/charts.ts"));
478
- const allChartUtils = [
479
- ...charts,
480
- ...chartExportUtils.map((f) => ({ ...f, category: "charts", file: "utils/chartExport.ts" })),
481
- ...chartUtils.map((f) => ({ ...f, category: "charts", file: "utils/charts.ts" })),
482
- ];
483
-
484
- if (allChartUtils.length > 0) {
485
- manifest.categories.charts = {
486
- description: "Chart utilities for data visualization, scaling, and export functionality",
487
- utilities: allChartUtils.sort((a, b) => a.name.localeCompare(b.name)),
488
- };
489
- }
490
-
491
- // Color utilities
492
- const colorUtils = extractExportsFromFile(path.join(SRC_DIR, "utils/colors.ts"));
493
- if (colorUtils.length > 0) {
494
- manifest.categories.colors = {
495
- description:
496
- "Color management utilities for theme-aware color resolution, contrast calculation, and palette generation",
497
- utilities: colorUtils
498
- .map((f) => ({ ...f, category: "colors", file: "utils/colors.ts" }))
499
- .sort((a, b) => a.name.localeCompare(b.name)),
500
- };
501
- }
502
-
503
- const totalUtilities = Object.values(manifest.categories).reduce((sum, cat) => sum + cat.utilities.length, 0);
504
-
505
- ensureDir(DIST_DIR);
506
- writeFile(path.join(DIST_DIR, "utilities.manifest.json"), JSON.stringify(manifest, null, 2));
507
-
508
- console.log(` Found ${totalUtilities} utilities in ${Object.keys(manifest.categories).length} categories`);
509
- return manifest;
510
- }
511
-
512
- // ============================================================================
513
- // THEME PARSING
514
- // ============================================================================
515
-
516
- function parseThemeColors() {
517
- console.log("\n🎨 Parsing theme colors...");
518
-
519
- const distThemePath = path.join(DIST_DIR, "theme.css");
520
- const sources = [];
521
-
522
- if (fs.existsSync(distThemePath)) {
523
- sources.push(distThemePath);
524
- } else {
525
- const generatedDir = path.join(SRC_DIR, "generated");
526
- const fallbackFiles = [
527
- path.join(generatedDir, "tailwind-tokens-light.css"),
528
- path.join(generatedDir, "tailwind-tokens-dark.css"),
529
- path.join(generatedDir, "viz-runtime.css"),
530
- ];
531
-
532
- for (const file of fallbackFiles) {
533
- if (fs.existsSync(file)) {
534
- sources.push(file);
535
- }
536
- }
537
-
538
- if (sources.length > 0) {
539
- console.log(" ℹ️ Using generated token CSS as fallback (dist/theme.css missing)");
540
- }
541
- }
542
-
543
- if (sources.length === 0) {
544
- console.log(" ⚠️ No theme CSS sources found, skipping color extraction");
545
- return {};
546
- }
547
-
548
- const colorVars = {};
549
- const seen = new Set();
550
- const colorRegex = /--color-([a-zA-Z0-9-]+):\s*([^;]+);/g;
551
-
552
- for (const source of sources) {
553
- const themeContent = read(source);
554
- let match;
555
-
556
- while ((match = colorRegex.exec(themeContent))) {
557
- const varName = match[1];
558
- const value = match[2].trim();
559
-
560
- if (!seen.has(varName)) {
561
- seen.add(varName);
562
- colorVars[varName] = value;
563
- }
564
- }
565
- }
566
-
567
- console.log(` Found ${Object.keys(colorVars).length} color variables`);
568
- return colorVars;
569
- }
570
-
571
- // ============================================================================
572
- // MARKDOWN DOCUMENTATION GENERATION
573
- // ============================================================================
574
-
575
- function generateComponentsReference(componentsManifest) {
576
- console.log("\n📝 Generating components reference...");
577
-
578
- const { components, categories } = componentsManifest;
579
-
580
- let content = `# Edges Components Reference
581
-
582
- > **Auto-generated** - Do not edit manually. Run \`node scripts/generate-edges-docs.js\` to regenerate.
583
-
584
- This document provides a complete reference of all ${components.length} components in the @texturehq/edges design system, organized by functional category.
585
-
586
- ## Quick Navigation
587
-
588
- `;
589
-
590
- // Table of contents by category
591
- const sortedCategories = Object.keys(categories).sort();
592
- for (const category of sortedCategories) {
593
- const count = categories[category].length;
594
- content += `- [${category}](#${category.toLowerCase().replace(/\s+/g, "-")}) (${count} component${count > 1 ? "s" : ""})\n`;
595
- }
596
-
597
- content += `\n---\n\n`;
598
-
599
- // Generate content by category
600
- for (const category of sortedCategories) {
601
- content += `## ${category}\n\n`;
602
-
603
- const categoryComponents = components.filter((c) => c.category === category);
604
- categoryComponents.sort((a, b) => a.name.localeCompare(b.name));
605
-
606
- for (const component of categoryComponents) {
607
- content += `### ${component.name}\n\n`;
608
-
609
- if (component.description) {
610
- content += `${component.description}\n\n`;
611
- }
612
-
613
- content += `**Import:**\n\n`;
614
- content += `\`\`\`typescript\n`;
615
- content += `import { ${component.name} } from "${component.importRoot}";\n`;
616
- content += `// or\n`;
617
- content += `import { ${component.name} } from "${component.importPath}";\n`;
618
- content += `\`\`\`\n\n`;
619
-
620
- if (component.props && component.props.length > 0) {
621
- content += `**Props:**\n\n`;
622
- content += `| Prop | Type |\n`;
623
- content += `|------|------|\n`;
624
- for (const prop of component.props) {
625
- const safeType = escapeMarkdownTableCell(prop.type);
626
- content += `| \`${prop.name}\` | \`${safeType}\` |\n`;
627
- }
628
- content += `\n`;
629
- }
630
-
631
- if (component.relatedComponents && component.relatedComponents.length > 0) {
632
- content += `**Related:** ${component.relatedComponents.join(", ")}\n\n`;
633
- }
634
-
635
- if (component.storybookPath) {
636
- content += `**Storybook:** \`${component.storybookPath}\`\n\n`;
637
- }
638
-
639
- content += `---\n\n`;
640
- }
641
- }
642
-
643
- writeFile(path.join(DOCS_AI_EDGES, "components-reference.md"), content);
644
- }
645
-
646
- function generateHooksReference(utilitiesManifest) {
647
- console.log("\n🪝 Generating hooks reference...");
648
-
649
- const hooksCategory = utilitiesManifest.categories.hooks;
650
- if (!hooksCategory) {
651
- console.log(" No hooks found");
652
- return;
653
- }
654
-
655
- const hooks = hooksCategory.utilities;
656
-
657
- let content = `# Edges Hooks Reference
658
-
659
- > **Auto-generated** - Do not edit manually. Run \`node scripts/generate-edges-docs.js\` to regenerate.
660
-
661
- This document provides a complete reference of all ${hooks.length} React hooks in the @texturehq/edges design system.
662
-
663
- ${hooksCategory.description}
664
-
665
- ## Available Hooks
666
-
667
- `;
668
-
669
- for (const hook of hooks) {
670
- content += `### ${hook.name}\n\n`;
671
-
672
- if (hook.description) {
673
- content += `${hook.description}\n\n`;
674
- }
675
-
676
- content += `**Import:**\n\n`;
677
- content += `\`\`\`typescript\n`;
678
- content += `import { ${hook.name} } from "@texturehq/edges";\n`;
679
- content += `\`\`\`\n\n`;
680
-
681
- content += `**Source:** \`src/${hook.file}\`\n\n`;
682
- content += `---\n\n`;
683
- }
684
-
685
- writeFile(path.join(DOCS_AI_EDGES, "hooks-reference.md"), content);
686
- }
687
-
688
- function generateUtilitiesReference(utilitiesManifest) {
689
- console.log("\n🛠️ Generating utilities reference...");
690
-
691
- const categories = Object.keys(utilitiesManifest.categories).filter((c) => c !== "hooks");
692
- if (categories.length === 0) {
693
- console.log(" No utilities found");
694
- return;
695
- }
696
-
697
- let content = `# Edges Utilities Reference
698
-
699
- > **Auto-generated** - Do not edit manually. Run \`node scripts/generate-edges-docs.js\` to regenerate.
700
-
701
- This document provides a complete reference of all utility functions in the @texturehq/edges design system.
702
-
703
- ## Categories
704
-
705
- `;
706
-
707
- for (const category of categories) {
708
- const cat = utilitiesManifest.categories[category];
709
- content += `- [${category}](#${category}) (${cat.utilities.length} utilities)\n`;
710
- }
711
-
712
- content += `\n---\n\n`;
713
-
714
- for (const category of categories) {
715
- const cat = utilitiesManifest.categories[category];
716
- content += `## ${category}\n\n`;
717
- content += `${cat.description}\n\n`;
718
-
719
- for (const util of cat.utilities) {
720
- content += `### ${util.name}\n\n`;
721
-
722
- if (util.description) {
723
- content += `${util.description}\n\n`;
724
- }
725
-
726
- content += `**Type:** ${util.type}\n\n`;
727
- content += `**Import:**\n\n`;
728
- content += `\`\`\`typescript\n`;
729
- content += `import { ${util.name} } from "@texturehq/edges";\n`;
730
- content += `\`\`\`\n\n`;
731
-
732
- content += `**Source:** \`src/${util.file}\`\n\n`;
733
- content += `---\n\n`;
734
- }
735
- }
736
-
737
- writeFile(path.join(DOCS_AI_EDGES, "utilities-reference.md"), content);
738
- }
739
-
740
- function generateThemeSystemDoc(colorVars) {
741
- console.log("\n🎨 Generating theme system documentation...");
742
-
743
- let content = `# Edges Theme System
744
-
745
- > **Auto-generated** - Do not edit manually. Run \`node scripts/generate-edges-docs.js\` to regenerate.
746
-
747
- ## Overview
748
-
749
- The @texturehq/edges design system uses Tailwind 4 with CSS variables. All design tokens are defined as CSS variables and automatically become available as Tailwind utility classes.
750
-
751
- ## Setup
752
-
753
- \`\`\`css
754
- @import "@texturehq/edges/theme.css";
755
- \`\`\`
756
-
757
- ## How It Works
758
-
759
- CSS variables automatically become Tailwind classes:
760
- - \`--color-brand-primary\` → \`bg-brand-primary\`, \`text-brand-primary\`, \`border-brand-primary\`
761
- - \`--spacing-md\` → \`p-md\`, \`m-md\`, \`gap-md\`
762
- - \`--text-lg\` → \`text-lg\`
763
- - \`--radius-lg\` → \`rounded-lg\`
764
-
765
- ## Color System
766
-
767
- The theme includes ${Object.keys(colorVars).length} color variables organized by purpose:
768
-
769
- ### Brand Colors
770
- - \`brand-primary\` - Primary brand color
771
- - \`brand-light\` - Light brand variant
772
- - \`brand-dark\` - Dark brand variant
773
-
774
- ### Text Colors
775
- - \`text-body\` - Body text
776
- - \`text-heading\` - Heading text
777
- - \`text-muted\` - Muted/secondary text
778
- - \`text-caption\` - Caption/helper text
779
- - \`text-on-primary\` - Text on primary backgrounds
780
-
781
- ### Background Colors
782
- - \`background-body\` - Main page background
783
- - \`background-surface\` - Card/surface background
784
- - \`background-muted\` - Muted background
785
- - \`background-subtle\` - Subtle background
786
-
787
- ### Border Colors
788
- - \`border-default\` - Default border
789
- - \`border-focus\` - Focused border
790
- - \`border-muted\` - Muted border
791
- - \`border-subtle\` - Subtle border
792
-
793
- ### Action Colors
794
- - \`action-primary\` - Primary actions
795
- - \`action-brand\` - Brand-focused actions
796
- - \`action-secondary\` - Secondary actions
797
- - \`action-default\` - Default gray actions
798
- - \`action-destructive\` - Destructive actions
799
-
800
- ### Feedback Colors
801
- - \`feedback-success\` - Success state
802
- - \`feedback-error\` - Error state
803
- - \`feedback-warning\` - Warning state
804
- - \`feedback-info\` - Info state
805
-
806
- ## Usage Guidelines
807
-
808
- **✅ Good - Use semantic classes:**
809
- \`\`\`tsx
810
- <div className="bg-brand-primary text-text-on-primary p-md rounded-lg shadow-md">
811
- <h2 className="text-text-heading text-lg font-medium">Title</h2>
812
- <p className="text-text-body text-base">Content</p>
813
- </div>
814
- \`\`\`
815
-
816
- **❌ Avoid - Arbitrary values:**
817
- \`\`\`tsx
818
- <div className="bg-[#444ae1] text-[#ffffff] p-[1rem] rounded-[0.5rem]">
819
- <h2 className="text-[#111827] text-[1.125rem] font-[500]">Title</h2>
820
- <p className="text-[#333333] text-[1rem]">Content</p>
821
- </div>
822
- \`\`\`
823
-
824
- ## Spacing Scale
825
-
826
- - \`xs\`, \`sm\`, \`md\`, \`lg\`, \`xl\`, \`2xl\`, \`3xl\`, \`4xl\`
827
-
828
- ## Typography Scale
829
-
830
- - \`xs\`, \`sm\`, \`base\`, \`lg\`, \`xl\`, \`2xl\`, \`3xl\`, \`4xl\`
831
-
832
- ## Border Radius Scale
833
-
834
- - \`xs\`, \`sm\`, \`md\`, \`lg\`, \`xl\`, \`2xl\`, \`3xl\`, \`4xl\`
835
-
836
- ## Dark Mode
837
-
838
- All colors automatically adapt to dark mode when the \`.theme-dark\` class is present on a parent element.
839
-
840
- ## Available Variables
841
-
842
- The theme includes:
843
- - Complete color system (${Object.keys(colorVars).length} color variables)
844
- - Spacing scale
845
- - Typography scale
846
- - Border radius scale
847
- - Shadow system
848
- - Animation definitions
849
- - Form control specifications
850
- `;
851
-
852
- writeFile(path.join(DOCS_AI_EDGES, "theme-system.md"), content);
853
- }
854
-
855
- function generateEdgesReadme(componentsManifest, utilitiesManifest) {
856
- console.log("\n📄 Generating edges README...");
857
-
858
- const componentCount = componentsManifest.components.length;
859
- const categoryCount = Object.keys(componentsManifest.categories).length;
860
- const hookCount = utilitiesManifest.categories.hooks?.utilities.length || 0;
861
- // Use version instead of timestamp to avoid git churn
862
- const version = componentsManifest.version || PACKAGE_VERSION;
863
-
864
- let utilityCount = 0;
865
- for (const cat in utilitiesManifest.categories) {
866
- if (cat !== "hooks") {
867
- utilityCount += utilitiesManifest.categories[cat].utilities.length;
868
- }
869
- }
870
-
871
- let content = `# Edges Design System - AI Context
872
-
873
- > **Auto-generated** - Do not edit manually. Run \`node scripts/generate-edges-docs.js\` to regenerate.
874
-
875
- 👋 **AI Agents: Start here!**
876
-
877
- Complete documentation for the @texturehq/edges design system.
878
-
879
- ## What's Inside
880
-
881
- - **${componentCount} Components** across ${categoryCount} categories
882
- - **${hookCount} React Hooks** for common functionality
883
- - **${utilityCount} Utility Functions** for formatting, charts, colors, and more
884
- - **Complete Theme System** with Tailwind 4 + CSS variables
885
- - **Integrations** (Optional) like React Hook Form support
886
-
887
- ## Quick Links
888
-
889
- ### Component Library
890
- - [Components Reference](components-reference.md) - All ${componentCount} components organized by category
891
- - [Component Categories](#component-categories) - Quick navigation by function
892
-
893
- ### Utilities & Hooks
894
- - [Hooks Reference](hooks-reference.md) - All ${hookCount} React hooks
895
- - [Utilities Reference](utilities-reference.md) - All ${utilityCount} utility functions
896
-
897
- ### Design System
898
- - [Theme System](theme-system.md) - Colors, spacing, typography with Tailwind 4
899
- - [Patterns](patterns.md) - Common UI patterns and best practices
900
-
901
- ### Integrations (Optional)
902
- - [React Hook Form (RHF)](integrations-rhf.md) - Opt-in adapters and utilities for fast form building
903
-
904
- ## Component Categories
905
-
906
- `;
907
-
908
- const sortedCategories = Object.keys(componentsManifest.categories).sort();
909
- for (const category of sortedCategories) {
910
- const components = componentsManifest.categories[category];
911
- content += `### ${category}\n\n`;
912
- content += `${components.join(", ")}\n\n`;
913
- }
914
-
915
- content += `## Installation
916
-
917
- \`\`\`bash
918
- yarn add @texturehq/edges
919
- \`\`\`
920
-
921
- ## Setup
922
-
923
- \`\`\`css
924
- @import "@texturehq/edges/theme.css";
925
- \`\`\`
926
-
927
- \`\`\`typescript
928
- import { Button, TextField, DataTable } from "@texturehq/edges";
929
- \`\`\`
930
-
931
- ## Integrations
932
-
933
- React Hook Form integration is available as an optional subpath and tree-shakes when unused.
934
-
935
- \`\`\`bash
936
- yarn add react-hook-form @hookform/resolvers zod
937
- \`\`\`
938
-
939
- \`\`\`typescript
940
- import { RHFForm, RHFTextField } from "@texturehq/edges/rhf";
941
- \`\`\`
942
-
943
- ## For Dashboard Development
944
-
945
- When building UI in \`apps/dashboard\`, reference:
946
- - **Components**: \`docs/ai/edges/components-reference.md\`
947
- - **Hooks**: \`docs/ai/edges/hooks-reference.md\`
948
- - **Utilities**: \`docs/ai/edges/utilities-reference.md\`
949
- - **Theme**: \`docs/ai/edges/theme-system.md\`
950
- - **Patterns**: \`docs/ai/edges/patterns.md\`
951
- - **Integrations (Optional)**: \`docs/ai/edges/integrations-rhf.md\`
952
-
953
- ---
954
-
955
- **Package Version:** ${version}
956
- `;
957
-
958
- writeFile(path.join(DOCS_AI_EDGES, "README.md"), content);
959
- }
960
-
961
- // ============================================================================
962
- // AGENT INSTRUCTION FRAGMENT
963
- // ============================================================================
964
-
965
- function generateEdgesFragment(componentsManifest, utilitiesManifest) {
966
- console.log("\n📄 Generating edges fragment...");
967
-
968
- const componentCount = componentsManifest.components.length;
969
- const hookCount = utilitiesManifest.categories.hooks?.utilities.length || 0;
970
-
971
- let utilityCount = 0;
972
- for (const cat in utilitiesManifest.categories) {
973
- if (cat !== "hooks") {
974
- utilityCount += utilitiesManifest.categories[cat].utilities.length;
975
- }
976
- }
977
-
978
- const content = `<!-- priority: 20 -->
979
- ## Edges Design System Context
980
-
981
- The @texturehq/edges design system provides ${componentCount} React components, ${hookCount} hooks, and ${utilityCount} utility functions.
982
-
983
- **Complete component documentation is in**: \`docs/ai/edges/\`
984
-
985
- ### Quick Start
986
-
987
- 1. Read \`docs/ai/edges/README.md\` for an overview and component categories
988
- 2. Reference \`docs/ai/edges/components-reference.md\` for detailed component API
989
- 3. Review \`docs/ai/edges/patterns.md\` for common UI patterns and best practices
990
- 4. Check \`docs/ai/edges/theme-system.md\` for Tailwind 4 color tokens and styling
991
-
992
- ### Forms + RHF (Optional)
993
-
994
- - Import adapters from \`@texturehq/edges/rhf\`; install optional peers: \`react-hook-form @hookform/resolvers zod\`
995
- - Quickstart:
996
- \`\`\`tsx
997
- import { RHFForm, RHFTextField, SubmitButton, ErrorSummary } from "@texturehq/edges/rhf";
998
- \`\`\`
999
- - Full guide: \`docs/ai/edges/integrations-rhf.md\`
1000
-
1001
- ### Additional Resources
1002
-
1003
- - Hooks: \`docs/ai/edges/hooks-reference.md\`
1004
- - Utilities: \`docs/ai/edges/utilities-reference.md\`
1005
-
1006
- ### For Dashboard Development
1007
-
1008
- **UI (Edges)**:
1009
- - All components, hooks, and utilities from \`@texturehq/edges\`
1010
- - Use semantic Tailwind classes from theme (see theme-system.md)
1011
- - Follow patterns from \`docs/ai/edges/patterns.md\`
1012
-
1013
- ### Common Patterns
1014
-
1015
- **Import**: \`import { Button, TextField } from "@texturehq/edges"\`
1016
- **Styling**: Use semantic classes like \`bg-brand-primary\`, \`text-text-body\`
1017
- **Forms**: Wrap in \`<Form>\`, use validation with \`errorMessage\`
1018
- **Loading**: Show \`<Skeleton>\` during data fetching
1019
- **Responsive**: Use \`useBreakpoint()\` hook or responsive grid cols
1020
- `;
1021
-
1022
- const fragmentsDir = path.join(MONO_ROOT, ".agent-fragments");
1023
- ensureDir(fragmentsDir);
1024
- writeFile(path.join(fragmentsDir, "edges.md"), content);
1025
- }
1026
-
1027
- // ============================================================================
1028
- // INTEGRATIONS DOCS
1029
- // ============================================================================
1030
-
1031
- function generateIntegrationsDocs() {
1032
- console.log("\n🔗 Generating integrations docs...");
1033
- const rhfReadmeSrc = path.join(SRC_DIR, "integrations", "rhf", "README.md");
1034
- const rhfDocDest = path.join(DOCS_AI_EDGES, "integrations-rhf.md");
1035
- const content = read(rhfReadmeSrc);
1036
- if (content) {
1037
- writeFile(rhfDocDest, content);
1038
- } else {
1039
- console.log(" ℹ️ RHF README not found, skipping integrations-rhf.md");
1040
- }
1041
- }
1042
-
1043
- // ============================================================================
1044
- // MAIN
1045
- // ============================================================================
1046
-
1047
- async function main() {
1048
- console.log("🚀 Generating Edges AI Context...\n");
1049
-
1050
- // Ensure output directories exist
1051
- ensureDir(DOCS_AI_EDGES);
1052
- ensureDir(DIST_DIR);
1053
-
1054
- // Generate manifests
1055
- const componentsManifest = generateComponentsManifest();
1056
- const utilitiesManifest = generateUtilitiesManifest();
1057
-
1058
- // Parse theme
1059
- const colorVars = parseThemeColors();
1060
-
1061
- // Generate markdown documentation
1062
- generateComponentsReference(componentsManifest);
1063
- generateHooksReference(utilitiesManifest);
1064
- generateUtilitiesReference(utilitiesManifest);
1065
- generateThemeSystemDoc(colorVars);
1066
- generateEdgesReadme(componentsManifest, utilitiesManifest);
1067
- generateIntegrationsDocs();
1068
-
1069
- // Generate agent instruction fragment
1070
- generateEdgesFragment(componentsManifest, utilitiesManifest);
1071
-
1072
- // Merge all agent instruction fragments
1073
- console.log("\n🔀 Merging agent instruction fragments...");
1074
- try {
1075
- const output = execSync("ts-node scripts/merge-agent-instructions.ts", {
1076
- cwd: MONO_ROOT,
1077
- encoding: "utf8",
1078
- stdio: "pipe",
1079
- });
1080
- console.log(output);
1081
- } catch (_err) {
1082
- console.log("⚠️ Note: Could not merge agent instructions (merge script may not be available yet)");
1083
- }
1084
-
1085
- console.log("\n✅ Edges AI context generation complete!");
1086
- console.log("\nGenerated files:");
1087
- console.log(" - dist/components.manifest.json");
1088
- console.log(" - dist/utilities.manifest.json");
1089
- console.log(" - docs/ai/edges/README.md");
1090
- console.log(" - docs/ai/edges/components-reference.md");
1091
- console.log(" - docs/ai/edges/hooks-reference.md");
1092
- console.log(" - docs/ai/edges/utilities-reference.md");
1093
- console.log(" - docs/ai/edges/theme-system.md");
1094
- console.log(" - .agent-fragments/edges.md");
1095
- console.log(" - .claude/instructions.md (merged)");
1096
- console.log(" - .codex/instructions.md (merged)");
1097
- console.log(" - .github/copilot-instructions.md (merged)");
1098
- }
1099
-
1100
- main().catch((err) => {
1101
- console.error("❌ Failed to generate AI context:", err);
1102
- process.exit(1);
1103
- });