@texturehq/edges 1.7.1 → 1.7.3

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.
@@ -16,6 +16,24 @@ const setupCursorRules = () => {
16
16
  // ignore
17
17
  }
18
18
 
19
+ // Clean up legacy generated edges context files
20
+ const legacyFiles = [
21
+ path.join(cwd, ".cursor/rules/edges-components-old.mdc"),
22
+ path.join(cwd, ".claude/edges-components.md"),
23
+ // Add any other legacy file names here as needed
24
+ ];
25
+
26
+ legacyFiles.forEach((file) => {
27
+ if (fs.existsSync(file)) {
28
+ try {
29
+ fs.unlinkSync(file);
30
+ console.log(`🧹 Cleaned up legacy file: ${path.relative(cwd, file)}`);
31
+ } catch (err) {
32
+ // Ignore cleanup errors
33
+ }
34
+ }
35
+ });
36
+
19
37
  const cursorTemplatesDir = path.join(
20
38
  path.dirname(new URL(import.meta.url).pathname),
21
39
  "../templates/cursor-rules"
@@ -1,188 +0,0 @@
1
- // generate-components-manifest.js
2
- // Build-time script that scans exported components and creates dist/components.manifest.json
3
-
4
- import fs from "fs";
5
- import path from "path";
6
-
7
- const ROOT = process.cwd();
8
- const SRC_DIR = path.join(ROOT, "src");
9
- const DIST_DIR = path.join(ROOT, "dist");
10
-
11
- function read(file) {
12
- return fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
13
- }
14
-
15
- function ensureDir(dir) {
16
- if (!fs.existsSync(dir)) fs.mkdirSync(dir);
17
- }
18
-
19
- function extractExportsFromIndex(indexContent) {
20
- // Matches: export { Button } from "./components/Button";
21
- const re = /export\s+\{\s*([A-Za-z0-9_,\s]+)\s*\}\s+from\s+"\.\/components\/([A-Za-z0-9_/.-]+)"/g;
22
- const results = [];
23
- let m;
24
- while ((m = re.exec(indexContent))) {
25
- const exported = m[1]
26
- .split(",")
27
- .map((s) => s.trim())
28
- .filter(Boolean);
29
- const rel = m[2];
30
- exported.forEach((name) => {
31
- // Only keep PascalCase (components); ignore sub-exports like Tab, TabList if not directory-named
32
- results.push({ name, relPath: rel });
33
- });
34
- }
35
- return results;
36
- }
37
-
38
- function findComponentFile(relPath) {
39
- // Components can be in directories like Button/Button.tsx or charts/ChartAxis/ChartAxis.tsx
40
- const base = path.join(SRC_DIR, "components", relPath);
41
- // Get the component name (last part of the path)
42
- const componentName = relPath.split("/").pop();
43
- const candidates = [
44
- path.join(base, `${componentName}.tsx`),
45
- path.join(base, `${componentName}.ts`),
46
- path.join(base, `index.tsx`),
47
- path.join(base, `index.ts`),
48
- ];
49
- for (const f of candidates) {
50
- if (fs.existsSync(f)) return f;
51
- }
52
- return null;
53
- }
54
-
55
- function getJsdocAbove(content, idx) {
56
- // Find the last JSDoc block before idx
57
- const upTo = content.slice(0, idx);
58
- const start = upTo.lastIndexOf("/**");
59
- const end = upTo.lastIndexOf("*/");
60
- if (start !== -1 && end !== -1 && end > start) {
61
- const block = upTo.slice(start + 3, end).trim();
62
- return block
63
- .split("\n")
64
- .map((l) => l.replace(/^\s*\*\s?/, "").trim())
65
- .join(" ")
66
- .trim();
67
- }
68
- return "";
69
- }
70
-
71
- function extractProps(content, componentName) {
72
- // Try `export interface <Name>Props` first
73
- const ifaceRe = new RegExp(
74
- `export\\s+interface\\s+${componentName}Props\\s*(?:extends\\s+[^{]+)?\\{([\\s\\S]*?)\\}`,
75
- "m"
76
- );
77
- let m = ifaceRe.exec(content);
78
- if (!m) {
79
- // Try generic name: any exported *Props
80
- const anyIfaceRe =
81
- /export\s+interface\s+([A-Za-z0-9_]+Props)\s*(?:extends\s+[^{]+)?\{([\s\S]*?)\}/m;
82
- m = anyIfaceRe.exec(content);
83
- }
84
- if (!m) {
85
- // Try type alias
86
- const typeRe = new RegExp(
87
- `export\\s+type\\s+${componentName}Props\\s*=\\s*(?:[^{&]+&\s*)?([\\s\\S]*?)\\}`,
88
- "m"
89
- );
90
- m = typeRe.exec(content);
91
- }
92
- const props = [];
93
- if (m) {
94
- const body = m[2] || m[1] || "";
95
- const lines = body
96
- .split("\n")
97
- .map((l) => l.trim())
98
- .filter((l) => !!l && !l.startsWith("//"));
99
- for (const line of lines) {
100
- // Match: name?: Type; or name: Type;
101
- const pm = /^(readonly\s+)?([A-Za-z0-9_]+)\??:\s*([^;]+);?/.exec(line);
102
- if (pm) {
103
- const name = pm[2];
104
- const type = pm[3].trim();
105
- props.push({ name, type });
106
- }
107
- }
108
- }
109
- return props;
110
- }
111
-
112
- function run() {
113
- try {
114
- const indexPath = path.join(SRC_DIR, "index.ts");
115
- const indexContent = read(indexPath);
116
- const exports = extractExportsFromIndex(indexContent);
117
-
118
- const components = [];
119
- for (const { name, relPath } of exports) {
120
- // Only treat PascalCase names as components
121
- if (!/^[A-Z]/.test(name)) continue;
122
-
123
- const componentFile = findComponentFile(relPath);
124
- if (!componentFile) continue;
125
- const content = read(componentFile);
126
-
127
- // Description: JSDoc above various export patterns
128
- let idx = -1;
129
- let description = "";
130
-
131
- // Try multiple patterns to find the component export
132
- const patterns = [
133
- `export function ${name}`,
134
- `export const ${name}:`,
135
- `export const ${name} =`,
136
- `export class ${name}`,
137
- new RegExp(`export\\s+const\\s+${name}\\s*[:=]`),
138
- new RegExp(`export\\s+function\\s+${name}\\s*[(<]`),
139
- new RegExp(`export\\s+class\\s+${name}\\s*[{<]`),
140
- new RegExp(`export\\s+(interface|type)\\s+${name}Props\\b`),
141
- ];
142
-
143
- for (const pattern of patterns) {
144
- if (typeof pattern === "string") {
145
- idx = content.indexOf(pattern);
146
- } else {
147
- const match = content.search(pattern);
148
- if (match !== -1) idx = match;
149
- }
150
- if (idx !== -1) break;
151
- }
152
-
153
- // If we found an export, get the JSDoc above it
154
- if (idx !== -1) {
155
- description = getJsdocAbove(content, idx);
156
- }
157
-
158
- const props = extractProps(content, name);
159
-
160
- components.push({
161
- name,
162
- importRoot: "@texturehq/edges",
163
- importPath: `@texturehq/edges/components/${name}`,
164
- description,
165
- props,
166
- });
167
- }
168
-
169
- ensureDir(DIST_DIR);
170
- const manifest = {
171
- version: process.env.npm_package_version || "unknown",
172
- generatedAt: new Date().toISOString(),
173
- components: components.sort((a, b) => a.name.localeCompare(b.name)),
174
- };
175
- fs.writeFileSync(
176
- path.join(DIST_DIR, "components.manifest.json"),
177
- JSON.stringify(manifest, null, 2)
178
- );
179
- console.log(
180
- `Generated ${path.join("dist", "components.manifest.json")} with ${components.length} components.`
181
- );
182
- } catch (err) {
183
- console.error("Failed to generate components manifest:", err);
184
- process.exitCode = 1;
185
- }
186
- }
187
-
188
- run();
@@ -1,392 +0,0 @@
1
- // generate-utilities-manifest.js
2
- // Build-time script that scans exported utilities and creates dist/utilities.manifest.json
3
-
4
- import fs from "fs";
5
- import path from "path";
6
-
7
- const ROOT = process.cwd();
8
- const SRC_DIR = path.join(ROOT, "src");
9
- const DIST_DIR = path.join(ROOT, "dist");
10
-
11
- function read(file) {
12
- return fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
13
- }
14
-
15
- function ensureDir(dir) {
16
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
17
- }
18
-
19
- /**
20
- * Extract JSDoc comment from text above a function/const
21
- */
22
- function extractJSDoc(content, functionName) {
23
- // Look for JSDoc block immediately before the function/const declaration
24
- const patterns = [
25
- // Function declaration: /** ... */ export function name OR export async function name
26
- new RegExp(
27
- `\\/\\*\\*([^*]|\\*(?!\\/))*\\*\\/\\s*export\\s+(?:async\\s+)?function\\s+${functionName}\\b`,
28
- "s"
29
- ),
30
- // Arrow function: /** ... */ export const name =
31
- new RegExp(`\\/\\*\\*([^*]|\\*(?!\\/))*\\*\\/\\s*export\\s+const\\s+${functionName}\\s*=`, "s"),
32
- // Type export: /** ... */ export type name
33
- new RegExp(`\\/\\*\\*([^*]|\\*(?!\\/))*\\*\\/\\s*export\\s+type\\s+${functionName}\\b`, "s"),
34
- // Interface export: /** ... */ export interface name
35
- new RegExp(
36
- `\\/\\*\\*([^*]|\\*(?!\\/))*\\*\\/\\s*export\\s+interface\\s+${functionName}\\b`,
37
- "s"
38
- ),
39
- ];
40
-
41
- for (const pattern of patterns) {
42
- const match = content.match(pattern);
43
- if (match) {
44
- const jsdocMatch = match[0].match(/\/\*\*([\s\S]*?)\*\//);
45
- if (jsdocMatch) {
46
- // Clean up the JSDoc comment
47
- const cleaned = jsdocMatch[1]
48
- .split("\n")
49
- .map((line) => line.replace(/^\s*\*\s?/, "").trim())
50
- .filter((line) => line && !line.startsWith("@"))
51
- .join(" ")
52
- .trim();
53
- return cleaned;
54
- }
55
- }
56
- }
57
- return "";
58
- }
59
-
60
- /**
61
- * Extract @param tags from JSDoc
62
- */
63
- function extractParams(content, functionName) {
64
- const patterns = [
65
- new RegExp(
66
- `\\/\\*\\*([^*]|\\*(?!\\/))*\\*\\/\\s*export\\s+(?:async\\s+)?function\\s+${functionName}\\b`,
67
- "s"
68
- ),
69
- new RegExp(`\\/\\*\\*([^*]|\\*(?!\\/))*\\*\\/\\s*export\\s+const\\s+${functionName}\\s*=`, "s"),
70
- ];
71
-
72
- for (const pattern of patterns) {
73
- const match = content.match(pattern);
74
- if (match) {
75
- const jsdocMatch = match[0].match(/\/\*\*([\s\S]*?)\*\//);
76
- if (jsdocMatch) {
77
- const params = [];
78
- const lines = jsdocMatch[1].split("\n");
79
- for (const line of lines) {
80
- const paramMatch = line.match(/@param\s+(?:\{([^}]+)\})?\s*(\S+)\s*-?\s*(.*)/);
81
- if (paramMatch) {
82
- params.push({
83
- name: paramMatch[2],
84
- type: paramMatch[1] || "unknown",
85
- description: paramMatch[3].trim(),
86
- });
87
- }
88
- }
89
- return params;
90
- }
91
- }
92
- }
93
- return [];
94
- }
95
-
96
- /**
97
- * Extract @returns tag from JSDoc
98
- */
99
- function extractReturns(content, functionName) {
100
- const patterns = [
101
- new RegExp(
102
- `\\/\\*\\*([^*]|\\*(?!\\/))*\\*\\/\\s*export\\s+(?:async\\s+)?function\\s+${functionName}\\b`,
103
- "s"
104
- ),
105
- new RegExp(`\\/\\*\\*([^*]|\\*(?!\\/))*\\*\\/\\s*export\\s+const\\s+${functionName}\\s*=`, "s"),
106
- ];
107
-
108
- for (const pattern of patterns) {
109
- const match = content.match(pattern);
110
- if (match) {
111
- const jsdocMatch = match[0].match(/\/\*\*([\s\S]*?)\*\//);
112
- if (jsdocMatch) {
113
- const lines = jsdocMatch[1].split("\n");
114
- for (const line of lines) {
115
- const returnMatch = line.match(/@returns?\s+(?:\{([^}]+)\})?\s*(.*)/);
116
- if (returnMatch) {
117
- return {
118
- type: returnMatch[1] || "unknown",
119
- description: returnMatch[2].trim(),
120
- };
121
- }
122
- }
123
- }
124
- }
125
- }
126
- return null;
127
- }
128
-
129
- /**
130
- * Extract @example from JSDoc
131
- */
132
- function extractExample(content, functionName) {
133
- const patterns = [
134
- new RegExp(
135
- `\\/\\*\\*([^*]|\\*(?!\\/))*\\*\\/\\s*export\\s+(?:async\\s+)?function\\s+${functionName}\\b`,
136
- "s"
137
- ),
138
- new RegExp(`\\/\\*\\*([^*]|\\*(?!\\/))*\\*\\/\\s*export\\s+const\\s+${functionName}\\s*=`, "s"),
139
- ];
140
-
141
- for (const pattern of patterns) {
142
- const match = content.match(pattern);
143
- if (match) {
144
- const jsdocMatch = match[0].match(/\/\*\*([\s\S]*?)\*\//);
145
- if (jsdocMatch) {
146
- const exampleMatch = jsdocMatch[1].match(/@example\s*([\s\S]*?)(?=@\w+|$)/);
147
- if (exampleMatch) {
148
- // Clean up the example
149
- const example = exampleMatch[1]
150
- .split("\n")
151
- .map((line) => line.replace(/^\s*\*\s?/, ""))
152
- .join("\n")
153
- .trim();
154
- // Remove ```typescript and ``` markers if present
155
- return example
156
- .replace(/^```\w*\n?/, "")
157
- .replace(/\n?```$/, "")
158
- .trim();
159
- }
160
- }
161
- }
162
- }
163
- return null;
164
- }
165
-
166
- /**
167
- * Extract exported functions from a file
168
- */
169
- function extractExports(filePath) {
170
- const content = read(filePath);
171
- if (!content) return [];
172
-
173
- const utilities = [];
174
-
175
- // Match export function declarations
176
- const functionPattern = /export\s+(?:async\s+)?function\s+(\w+)/g;
177
- let match;
178
- while ((match = functionPattern.exec(content))) {
179
- const name = match[1];
180
- utilities.push({
181
- name,
182
- type: "function",
183
- description: extractJSDoc(content, name),
184
- params: extractParams(content, name),
185
- returns: extractReturns(content, name),
186
- example: extractExample(content, name),
187
- });
188
- }
189
-
190
- // Match export const arrow functions and constants
191
- const constPattern = /export\s+const\s+(\w+)\s*(?::\s*[^=]+)?\s*=/g;
192
- while ((match = constPattern.exec(content))) {
193
- const name = match[1];
194
- // Check if it's likely a function (has => or function keyword after =)
195
- const afterMatch = content.slice(
196
- match.index + match[0].length,
197
- match.index + match[0].length + 50
198
- );
199
- const isFunction = afterMatch.includes("=>") || afterMatch.includes("function");
200
-
201
- utilities.push({
202
- name,
203
- type: isFunction ? "function" : "constant",
204
- description: extractJSDoc(content, name),
205
- params: isFunction ? extractParams(content, name) : [],
206
- returns: isFunction ? extractReturns(content, name) : null,
207
- example: extractExample(content, name),
208
- });
209
- }
210
-
211
- // Match export type/interface (for documentation purposes)
212
- const typePattern = /export\s+(?:type|interface)\s+(\w+)/g;
213
- while ((match = typePattern.exec(content))) {
214
- const name = match[1];
215
- utilities.push({
216
- name,
217
- type: "type",
218
- description: extractJSDoc(content, name),
219
- example: extractExample(content, name),
220
- });
221
- }
222
-
223
- return utilities;
224
- }
225
-
226
- /**
227
- * Scan a directory for utility files
228
- */
229
- function scanDirectory(dir, category) {
230
- const utilities = [];
231
-
232
- if (!fs.existsSync(dir)) return utilities;
233
-
234
- const files = fs.readdirSync(dir);
235
-
236
- for (const file of files) {
237
- const filePath = path.join(dir, file);
238
- const stat = fs.statSync(filePath);
239
-
240
- if (stat.isDirectory() && file !== "__tests__" && file !== "tests") {
241
- // Recursively scan subdirectories
242
- utilities.push(...scanDirectory(filePath, `${category}/${file}`));
243
- } else if ((file.endsWith(".ts") || file.endsWith(".tsx")) && !file.endsWith(".d.ts")) {
244
- // Skip test files, stories, and type definition files
245
- if (file.includes(".test.") || file.includes(".spec.") || file.includes(".stories.")) {
246
- continue;
247
- }
248
-
249
- const funcs = extractExports(filePath);
250
- for (const func of funcs) {
251
- utilities.push({
252
- ...func,
253
- category,
254
- file: path.relative(SRC_DIR, filePath),
255
- });
256
- }
257
- }
258
- }
259
-
260
- return utilities;
261
- }
262
-
263
- function run() {
264
- try {
265
- console.log("Generating utilities manifest...");
266
-
267
- const manifest = {
268
- version: process.env.npm_package_version || "unknown",
269
- generatedAt: new Date().toISOString(),
270
- categories: {},
271
- };
272
-
273
- // Scan hooks directory
274
- const hooksDir = path.join(SRC_DIR, "hooks");
275
- const hooks = scanDirectory(hooksDir, "hooks");
276
- if (hooks.length > 0) {
277
- manifest.categories.hooks = {
278
- description:
279
- "React hooks for common functionality like debouncing, local storage, and time controls",
280
- utilities: hooks.sort((a, b) => a.name.localeCompare(b.name)),
281
- };
282
- }
283
-
284
- // Scan utils directory
285
- const utilsDir = path.join(SRC_DIR, "utils");
286
-
287
- // Formatting utilities
288
- const formattingDir = path.join(utilsDir, "formatting");
289
- const formatting = scanDirectory(formattingDir, "formatting");
290
- if (formatting.length > 0) {
291
- manifest.categories.formatting = {
292
- description:
293
- "Comprehensive formatting utilities for text, numbers, dates, currency, energy, temperature, distance, and more",
294
- utilities: formatting.sort((a, b) => a.name.localeCompare(b.name)),
295
- };
296
- }
297
-
298
- // Chart utilities
299
- const chartsDir = path.join(utilsDir, "charts");
300
- const charts = scanDirectory(chartsDir, "charts");
301
- const chartExportUtils = extractExports(path.join(utilsDir, "chartExport.ts"));
302
- const chartUtils = extractExports(path.join(utilsDir, "charts.ts"));
303
- const allChartUtils = [
304
- ...charts,
305
- ...chartExportUtils.map((f) => ({ ...f, category: "charts", file: "utils/chartExport.ts" })),
306
- ...chartUtils.map((f) => ({ ...f, category: "charts", file: "utils/charts.ts" })),
307
- ];
308
-
309
- if (allChartUtils.length > 0) {
310
- manifest.categories.charts = {
311
- description: "Chart utilities for data visualization, scaling, and export functionality",
312
- utilities: allChartUtils.sort((a, b) => a.name.localeCompare(b.name)),
313
- };
314
- }
315
-
316
- // Color utilities
317
- const colorUtils = extractExports(path.join(utilsDir, "colors.ts"));
318
- if (colorUtils.length > 0) {
319
- manifest.categories.colors = {
320
- description:
321
- "Color management utilities for theme-aware color resolution, contrast calculation, and palette generation",
322
- utilities: colorUtils
323
- .map((f) => ({ ...f, category: "colors", file: "utils/colors.ts" }))
324
- .sort((a, b) => a.name.localeCompare(b.name)),
325
- };
326
- }
327
-
328
- // General utilities from utils/index.ts
329
- const generalUtils = extractExports(path.join(utilsDir, "index.ts"));
330
- if (generalUtils.length > 0) {
331
- manifest.categories.general = {
332
- description:
333
- "General utility functions for focus management, Tailwind class composition, and theme utilities",
334
- utilities: generalUtils
335
- .map((f) => ({ ...f, category: "general", file: "utils/index.ts" }))
336
- .sort((a, b) => a.name.localeCompare(b.name)),
337
- };
338
- }
339
-
340
- // Add summary
341
- const totalUtilities = Object.values(manifest.categories).reduce(
342
- (sum, cat) => sum + cat.utilities.length,
343
- 0
344
- );
345
-
346
- manifest.summary = {
347
- totalUtilities,
348
- totalCategories: Object.keys(manifest.categories).length,
349
- categories: Object.keys(manifest.categories).map((cat) => ({
350
- name: cat,
351
- count: manifest.categories[cat].utilities.length,
352
- })),
353
- };
354
-
355
- // Add import information
356
- manifest.importInfo = {
357
- package: "@texturehq/edges",
358
- examples: [
359
- {
360
- description: "Import specific utilities",
361
- code: 'import { formatNumber, formatCurrency, useDebounce } from "@texturehq/edges"',
362
- },
363
- {
364
- description: "Import all formatting utilities",
365
- code: 'import * as formatting from "@texturehq/edges"',
366
- },
367
- {
368
- description: "Import hooks",
369
- code: 'import { useDebounce, useLocalStorage } from "@texturehq/edges"',
370
- },
371
- {
372
- description: "Import color utilities",
373
- code: 'import { getResolvedColor, isLightColor } from "@texturehq/edges"',
374
- },
375
- ],
376
- };
377
-
378
- // Write the manifest
379
- ensureDir(DIST_DIR);
380
- const outputPath = path.join(DIST_DIR, "utilities.manifest.json");
381
- fs.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
382
-
383
- console.log(
384
- `Generated ${path.join("dist", "utilities.manifest.json")} with ${totalUtilities} utilities in ${Object.keys(manifest.categories).length} categories.`
385
- );
386
- } catch (err) {
387
- console.error("Failed to generate utilities manifest:", err);
388
- process.exitCode = 1;
389
- }
390
- }
391
-
392
- run();