@fragments-sdk/cli 0.7.4 → 0.7.6

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 (58) hide show
  1. package/LICENSE +1 -4
  2. package/dist/bin.js +33 -14
  3. package/dist/bin.js.map +1 -1
  4. package/dist/{chunk-NEJ2FBTN.js → chunk-CR3XHBGM.js} +2 -2
  5. package/dist/{chunk-S56I5FST.js → chunk-EFQ7SIBX.js} +582 -107
  6. package/dist/chunk-EFQ7SIBX.js.map +1 -0
  7. package/dist/{chunk-UXLGIGSX.js → chunk-GIC3I2KZ.js} +2 -2
  8. package/dist/{chunk-R6IZZSE7.js → chunk-JZNATKQA.js} +9 -3
  9. package/dist/chunk-JZNATKQA.js.map +1 -0
  10. package/dist/{chunk-P33AKQJW.js → chunk-SFWZ4K7C.js} +8 -2
  11. package/dist/{chunk-P33AKQJW.js.map → chunk-SFWZ4K7C.js.map} +1 -1
  12. package/dist/{core-3NMNCLFW.js → core-T7BDYEGO.js} +3 -3
  13. package/dist/{generate-23VLX7QN.js → generate-C2DKFCFJ.js} +4 -4
  14. package/dist/index.d.ts +28 -2
  15. package/dist/index.js +8 -6
  16. package/dist/index.js.map +1 -1
  17. package/dist/{init-VYVYMVHH.js → init-O3FCHEPN.js} +22 -6
  18. package/dist/init-O3FCHEPN.js.map +1 -0
  19. package/dist/mcp-bin.js +3 -3
  20. package/dist/{scan-FZR6YVI5.js → scan-IYTZDUKG.js} +5 -5
  21. package/dist/{service-CFFBHW4X.js → service-VA6XKADO.js} +3 -3
  22. package/dist/{static-viewer-VA2JXSCX.js → static-viewer-5N42MBDR.js} +3 -3
  23. package/dist/{test-VTD7R6G2.js → test-OMMDWL2W.js} +3 -3
  24. package/dist/{tokens-7JA5CPDL.js → tokens-6VJAHFIG.js} +4 -4
  25. package/dist/{viewer-WXTDDQGK.js → viewer-IVP5XC7U.js} +22 -14
  26. package/dist/viewer-IVP5XC7U.js.map +1 -0
  27. package/package.json +4 -2
  28. package/src/bin.ts +4 -0
  29. package/src/commands/add.ts +6 -0
  30. package/src/commands/init.ts +18 -2
  31. package/src/commands/validate.ts +24 -2
  32. package/src/core/config.ts +6 -0
  33. package/src/core/index.ts +1 -0
  34. package/src/core/schema.ts +6 -0
  35. package/src/core/types.ts +21 -0
  36. package/src/index.ts +2 -1
  37. package/src/service/snippet-validation.test.ts +209 -0
  38. package/src/service/snippet-validation.ts +635 -0
  39. package/src/validators.ts +53 -5
  40. package/src/viewer/__tests__/viewer-integration.test.ts +8 -0
  41. package/src/viewer/components/CodePanel.naming.test.tsx +60 -0
  42. package/src/viewer/components/CodePanel.tsx +76 -468
  43. package/src/viewer/components/Layout.tsx +1 -1
  44. package/src/viewer/utils/a11y-fixes.ts +24 -9
  45. package/src/viewer/vite-plugin.ts +9 -1
  46. package/dist/chunk-R6IZZSE7.js.map +0 -1
  47. package/dist/chunk-S56I5FST.js.map +0 -1
  48. package/dist/init-VYVYMVHH.js.map +0 -1
  49. package/dist/viewer-WXTDDQGK.js.map +0 -1
  50. /package/dist/{chunk-NEJ2FBTN.js.map → chunk-CR3XHBGM.js.map} +0 -0
  51. /package/dist/{chunk-UXLGIGSX.js.map → chunk-GIC3I2KZ.js.map} +0 -0
  52. /package/dist/{core-3NMNCLFW.js.map → core-T7BDYEGO.js.map} +0 -0
  53. /package/dist/{generate-23VLX7QN.js.map → generate-C2DKFCFJ.js.map} +0 -0
  54. /package/dist/{scan-FZR6YVI5.js.map → scan-IYTZDUKG.js.map} +0 -0
  55. /package/dist/{service-CFFBHW4X.js.map → service-VA6XKADO.js.map} +0 -0
  56. /package/dist/{static-viewer-VA2JXSCX.js.map → static-viewer-5N42MBDR.js.map} +0 -0
  57. /package/dist/{test-VTD7R6G2.js.map → test-OMMDWL2W.js.map} +0 -0
  58. /package/dist/{tokens-7JA5CPDL.js.map → tokens-6VJAHFIG.js.map} +0 -0
@@ -4,7 +4,7 @@ import {
4
4
  generateRegistry,
5
5
  loadFragmentFile,
6
6
  parseFragmentFile
7
- } from "./chunk-R6IZZSE7.js";
7
+ } from "./chunk-JZNATKQA.js";
8
8
  import {
9
9
  discoverBlockFiles,
10
10
  discoverComponentFiles,
@@ -25,15 +25,464 @@ import {
25
25
  import {
26
26
  compileBlock,
27
27
  parseTokenFile
28
- } from "./chunk-UXLGIGSX.js";
28
+ } from "./chunk-GIC3I2KZ.js";
29
29
  import {
30
30
  fragmentDefinitionSchema
31
- } from "./chunk-P33AKQJW.js";
31
+ } from "./chunk-SFWZ4K7C.js";
32
32
  import {
33
33
  BRAND,
34
34
  DEFAULTS
35
35
  } from "./chunk-EKLMXTWU.js";
36
36
 
37
+ // src/service/snippet-validation.ts
38
+ import ts from "typescript";
39
+ import { readFile } from "fs/promises";
40
+ import { existsSync } from "fs";
41
+ import { join } from "path";
42
+ var INTRINSIC_TAGS = /* @__PURE__ */ new Set([
43
+ "div",
44
+ "span",
45
+ "p",
46
+ "h1",
47
+ "h2",
48
+ "h3",
49
+ "h4",
50
+ "h5",
51
+ "h6",
52
+ "main",
53
+ "section",
54
+ "article",
55
+ "aside",
56
+ "nav",
57
+ "header",
58
+ "footer",
59
+ "ul",
60
+ "ol",
61
+ "li",
62
+ "button",
63
+ "input",
64
+ "textarea",
65
+ "label",
66
+ "svg",
67
+ "path"
68
+ ]);
69
+ var JSX_TAG_PATTERN = /<\s*([A-Za-z][A-Za-z0-9.]*)\b/g;
70
+ var STYLE_PATTERN = /\bstyle\s*=\s*\{/;
71
+ var TRANSPILED_PATTERN = /jsxDEV|_jsx|@__PURE__|\bfileName\s*:|\blineNumber\s*:|\bcolumnNumber\s*:/;
72
+ var ALIAS_DRIFT_PATTERN = /<\s*[A-Z][A-Za-z0-9]*(?:Root|2)\b/;
73
+ var HAS_IMPORT_PATTERN = /\bimport\s+[^;]+\s+from\s+['"][^'"]+['"]/;
74
+ var HAS_JSX_PATTERN = /<\s*[A-Za-z][A-Za-z0-9.]*\b/;
75
+ var DEFAULT_POLICY = {
76
+ mode: "warn",
77
+ scope: "snippet+render",
78
+ requireFullSnippet: true,
79
+ allowedExternalModules: /* @__PURE__ */ new Set([
80
+ "@phosphor-icons/react",
81
+ "recharts",
82
+ "react-day-picker"
83
+ ])
84
+ };
85
+ function normalizePolicy(configured, overrides) {
86
+ const fromConfig = {
87
+ mode: configured?.mode ?? DEFAULT_POLICY.mode,
88
+ scope: configured?.scope ?? DEFAULT_POLICY.scope,
89
+ requireFullSnippet: configured?.requireFullSnippet ?? DEFAULT_POLICY.requireFullSnippet,
90
+ allowedExternalModules: new Set(configured?.allowedExternalModules ?? [...DEFAULT_POLICY.allowedExternalModules]),
91
+ componentStart: overrides.componentStart,
92
+ componentLimit: overrides.componentLimit
93
+ };
94
+ if (overrides.mode) fromConfig.mode = overrides.mode;
95
+ if (overrides.scope) fromConfig.scope = overrides.scope;
96
+ if (typeof overrides.requireFullSnippet === "boolean") {
97
+ fromConfig.requireFullSnippet = overrides.requireFullSnippet;
98
+ }
99
+ if (overrides.allowedExternalModules && overrides.allowedExternalModules.length > 0) {
100
+ fromConfig.allowedExternalModules = new Set(overrides.allowedExternalModules);
101
+ }
102
+ return fromConfig;
103
+ }
104
+ function isFragmentsModule(modulePath) {
105
+ return modulePath === "@fragments-sdk/ui" || modulePath === "@fragments/ui" || modulePath === "." || modulePath === ".." || modulePath.startsWith("@/components/") || modulePath.startsWith("@components/") || modulePath.startsWith("./") || modulePath.startsWith("../");
106
+ }
107
+ function collectSourceContext(sourceFile) {
108
+ const imports = /* @__PURE__ */ new Map();
109
+ const localComponents = /* @__PURE__ */ new Set();
110
+ function markLocal(name) {
111
+ if (!name) return;
112
+ if (/^[A-Z]/.test(name)) {
113
+ localComponents.add(name);
114
+ }
115
+ }
116
+ function visit(node) {
117
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
118
+ const modulePath = node.moduleSpecifier.text;
119
+ const clause = node.importClause;
120
+ if (clause?.name) {
121
+ imports.set(clause.name.text, modulePath);
122
+ }
123
+ if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings)) {
124
+ for (const item of clause.namedBindings.elements) {
125
+ imports.set(item.name.text, modulePath);
126
+ }
127
+ }
128
+ }
129
+ if (ts.isFunctionDeclaration(node)) {
130
+ markLocal(node.name?.text);
131
+ }
132
+ if (ts.isClassDeclaration(node)) {
133
+ markLocal(node.name?.text);
134
+ }
135
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {
136
+ markLocal(node.name.text);
137
+ }
138
+ ts.forEachChild(node, visit);
139
+ }
140
+ visit(sourceFile);
141
+ return { imports, localComponents };
142
+ }
143
+ function getJsxTags(code) {
144
+ const tags = [];
145
+ JSX_TAG_PATTERN.lastIndex = 0;
146
+ let match;
147
+ while ((match = JSX_TAG_PATTERN.exec(code)) !== null) {
148
+ tags.push(match[1]);
149
+ }
150
+ return tags;
151
+ }
152
+ function rootTagName(tag) {
153
+ return tag.split(".")[0];
154
+ }
155
+ function parseSnippetImports(snippet) {
156
+ const sourceFile = ts.createSourceFile(
157
+ "snippet.tsx",
158
+ snippet,
159
+ ts.ScriptTarget.Latest,
160
+ true,
161
+ ts.ScriptKind.TSX
162
+ );
163
+ return collectSourceContext(sourceFile).imports;
164
+ }
165
+ function findDefineCall(sourceFile, name) {
166
+ let result = null;
167
+ function visit(node) {
168
+ if (result) return;
169
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === name) {
170
+ result = node;
171
+ return;
172
+ }
173
+ ts.forEachChild(node, visit);
174
+ }
175
+ visit(sourceFile);
176
+ return result;
177
+ }
178
+ function findProperty(obj, propertyName) {
179
+ for (const prop of obj.properties) {
180
+ if (!ts.isPropertyAssignment(prop)) continue;
181
+ if (!ts.isIdentifier(prop.name)) continue;
182
+ if (prop.name.text === propertyName) {
183
+ return prop.initializer;
184
+ }
185
+ }
186
+ return null;
187
+ }
188
+ function readStaticString(expr) {
189
+ if (!expr) return null;
190
+ if (ts.isStringLiteral(expr) || ts.isNoSubstitutionTemplateLiteral(expr)) {
191
+ return expr.text;
192
+ }
193
+ return null;
194
+ }
195
+ function readRenderBody(renderExpr, sourceFile) {
196
+ if (!ts.isArrowFunction(renderExpr) && !ts.isFunctionExpression(renderExpr)) {
197
+ return null;
198
+ }
199
+ const body = renderExpr.body;
200
+ const start = body.getStart(sourceFile);
201
+ const end = body.getEnd();
202
+ return sourceFile.text.slice(start, end).trim();
203
+ }
204
+ function report(issues, file, message) {
205
+ issues.push({ file, message });
206
+ }
207
+ function validateRawRules(issues, file, label, code) {
208
+ if (STYLE_PATTERN.test(code)) {
209
+ report(issues, file, `${label}: inline style usage is not allowed; use Box/Stack/Text props.`);
210
+ }
211
+ if (TRANSPILED_PATTERN.test(code)) {
212
+ report(issues, file, `${label}: transpiler output detected (jsxDEV/_jsx/@__PURE__). Use authored snippet source.`);
213
+ }
214
+ if (ALIAS_DRIFT_PATTERN.test(code)) {
215
+ report(issues, file, `${label}: alias drift tag detected (*Root/*2). Use canonical component names.`);
216
+ }
217
+ const tags = getJsxTags(code);
218
+ const intrinsic = tags.map((tag) => rootTagName(tag)).filter((tag) => /^[a-z]/.test(tag)).map((tag) => tag.toLowerCase()).filter((tag) => INTRINSIC_TAGS.has(tag));
219
+ if (intrinsic.length > 0) {
220
+ const names = [...new Set(intrinsic)].sort().join(", ");
221
+ report(issues, file, `${label}: raw HTML tags are not allowed (${names}). Use Fragments primitives.`);
222
+ }
223
+ }
224
+ function validateComponentAllowlist(issues, file, label, code, imports, localComponents, policy) {
225
+ const tags = getJsxTags(code);
226
+ const seen = /* @__PURE__ */ new Set();
227
+ for (const tag of tags) {
228
+ const root = rootTagName(tag);
229
+ if (seen.has(root)) continue;
230
+ seen.add(root);
231
+ if (!/^[A-Z]/.test(root)) {
232
+ continue;
233
+ }
234
+ const modulePath = imports.get(root);
235
+ if (modulePath) {
236
+ if (isFragmentsModule(modulePath)) {
237
+ continue;
238
+ }
239
+ if (policy.allowedExternalModules.has(modulePath)) {
240
+ continue;
241
+ }
242
+ report(
243
+ issues,
244
+ file,
245
+ `${label}: component "${root}" comes from "${modulePath}" and is not in snippets.allowedExternalModules.`
246
+ );
247
+ continue;
248
+ }
249
+ if (localComponents.has(root)) {
250
+ report(
251
+ issues,
252
+ file,
253
+ `${label}: locally defined JSX component "${root}" is not allowed in snippets/renders. Import approved components instead.`
254
+ );
255
+ continue;
256
+ }
257
+ report(
258
+ issues,
259
+ file,
260
+ `${label}: component "${root}" is used without an import and is not allowed.`
261
+ );
262
+ }
263
+ }
264
+ function validateSnippetString(issues, file, label, snippet, policy) {
265
+ validateRawRules(issues, file, label, snippet);
266
+ if (policy.requireFullSnippet) {
267
+ if (!HAS_IMPORT_PATTERN.test(snippet)) {
268
+ report(issues, file, `${label}: full snippet required (missing import statement).`);
269
+ }
270
+ if (!HAS_JSX_PATTERN.test(snippet)) {
271
+ report(issues, file, `${label}: full snippet required (missing JSX usage).`);
272
+ }
273
+ }
274
+ const imports = parseSnippetImports(snippet);
275
+ validateComponentAllowlist(issues, file, label, snippet, imports, /* @__PURE__ */ new Set(), policy);
276
+ }
277
+ function validateFragmentSource(sourceFile, file, policy, issues) {
278
+ const context = collectSourceContext(sourceFile);
279
+ const defineCall = findDefineCall(sourceFile, "defineFragment");
280
+ if (!defineCall) {
281
+ return;
282
+ }
283
+ const arg = defineCall.arguments[0];
284
+ if (!arg || !ts.isObjectLiteralExpression(arg)) {
285
+ return;
286
+ }
287
+ const variantsExpr = findProperty(arg, "variants");
288
+ if (!variantsExpr || !ts.isArrayLiteralExpression(variantsExpr)) {
289
+ return;
290
+ }
291
+ for (const variantExpr of variantsExpr.elements) {
292
+ if (!ts.isObjectLiteralExpression(variantExpr)) continue;
293
+ const name = readStaticString(findProperty(variantExpr, "name")) ?? "Unknown";
294
+ const labelPrefix = `variant "${name}"`;
295
+ const codeExpr = findProperty(variantExpr, "code");
296
+ const snippet = readStaticString(codeExpr);
297
+ if (snippet) {
298
+ validateSnippetString(issues, file, `${labelPrefix} snippet`, snippet, policy);
299
+ } else {
300
+ report(issues, file, `${labelPrefix}: missing explicit code snippet (variant.code).`);
301
+ }
302
+ if (policy.scope === "snippet+render") {
303
+ const renderExpr = findProperty(variantExpr, "render");
304
+ if (renderExpr) {
305
+ const renderBody = readRenderBody(renderExpr, sourceFile);
306
+ if (!renderBody) {
307
+ report(issues, file, `${labelPrefix} render: expected a static render function.`);
308
+ continue;
309
+ }
310
+ validateRawRules(issues, file, `${labelPrefix} render`, renderBody);
311
+ validateComponentAllowlist(
312
+ issues,
313
+ file,
314
+ `${labelPrefix} render`,
315
+ renderBody,
316
+ context.imports,
317
+ context.localComponents,
318
+ policy
319
+ );
320
+ }
321
+ }
322
+ }
323
+ }
324
+ function validateBlockSource(sourceFile, file, policy, issues) {
325
+ const defineCall = findDefineCall(sourceFile, "defineBlock");
326
+ if (!defineCall) {
327
+ return;
328
+ }
329
+ const arg = defineCall.arguments[0];
330
+ if (!arg || !ts.isObjectLiteralExpression(arg)) {
331
+ return;
332
+ }
333
+ const codeExpr = findProperty(arg, "code");
334
+ const snippet = readStaticString(codeExpr);
335
+ if (!snippet) {
336
+ report(issues, file, "block snippet: missing static code string.");
337
+ return;
338
+ }
339
+ validateSnippetString(issues, file, "block snippet", snippet, policy);
340
+ }
341
+ function validateBlockPreviewExamples(sourceFile, file, policy, issues) {
342
+ if (policy.scope !== "snippet+render") {
343
+ return;
344
+ }
345
+ const code = sourceFile.text;
346
+ const context = collectSourceContext(sourceFile);
347
+ validateRawRules(issues, file, "block preview render", code);
348
+ validateComponentAllowlist(
349
+ issues,
350
+ file,
351
+ "block preview render",
352
+ code,
353
+ context.imports,
354
+ context.localComponents,
355
+ policy
356
+ );
357
+ }
358
+ function sourceFileFromText(filePath, content) {
359
+ return ts.createSourceFile(
360
+ filePath,
361
+ content,
362
+ ts.ScriptTarget.Latest,
363
+ true,
364
+ ts.ScriptKind.TSX
365
+ );
366
+ }
367
+ function sortAndFilterBatch(files, componentStart, componentLimit) {
368
+ const getComponentName = (relativePath) => {
369
+ const normalized = relativePath.replace(/\\/g, "/");
370
+ const fileName = normalized.split("/").pop() ?? normalized;
371
+ if (fileName.endsWith(BRAND.fileExtension)) {
372
+ return fileName.slice(0, -BRAND.fileExtension.length);
373
+ }
374
+ return extractComponentName(relativePath);
375
+ };
376
+ const sorted = [...files].sort((a, b) => {
377
+ const nameA = getComponentName(a.relativePath).toLowerCase();
378
+ const nameB = getComponentName(b.relativePath).toLowerCase();
379
+ return nameA.localeCompare(nameB);
380
+ });
381
+ if (!componentStart && !componentLimit) {
382
+ return { selected: sorted };
383
+ }
384
+ const startName = componentStart?.toLowerCase();
385
+ let startIndex = 0;
386
+ if (startName) {
387
+ const foundIndex = sorted.findIndex((file) => getComponentName(file.relativePath).toLowerCase() === startName);
388
+ if (foundIndex === -1) {
389
+ return {
390
+ selected: [],
391
+ warning: `Component start "${componentStart}" not found for snippet validation batch.`
392
+ };
393
+ }
394
+ startIndex = foundIndex;
395
+ }
396
+ const limit = componentLimit && componentLimit > 0 ? componentLimit : sorted.length;
397
+ return {
398
+ selected: sorted.slice(startIndex, startIndex + limit)
399
+ };
400
+ }
401
+ async function findBlockPreviewExamplesFile(configDir) {
402
+ const candidates = [
403
+ join(configDir, "apps/docs/src/app/(docs)/blocks/examples/index.tsx"),
404
+ join(configDir, "../apps/docs/src/app/(docs)/blocks/examples/index.tsx"),
405
+ join(configDir, "../../apps/docs/src/app/(docs)/blocks/examples/index.tsx")
406
+ ];
407
+ for (const candidate of candidates) {
408
+ if (existsSync(candidate)) {
409
+ return candidate;
410
+ }
411
+ }
412
+ return null;
413
+ }
414
+ function toValidationResult(policy, issues) {
415
+ if (policy.mode === "error") {
416
+ return {
417
+ errors: issues,
418
+ warnings: []
419
+ };
420
+ }
421
+ return {
422
+ errors: [],
423
+ warnings: issues
424
+ };
425
+ }
426
+ async function validateSnippetPolicy(config, configDir, options = {}) {
427
+ const policy = normalizePolicy(config.snippets, options);
428
+ const issues = [];
429
+ const discovered = await discoverFragmentFiles(config, configDir);
430
+ const fragmentFiles = discovered.filter((file) => file.relativePath.endsWith(BRAND.fileExtension));
431
+ const batchResult = sortAndFilterBatch(fragmentFiles, policy.componentStart, policy.componentLimit);
432
+ if (batchResult.warning) {
433
+ issues.push({ file: "snippets", message: batchResult.warning });
434
+ }
435
+ for (const file of batchResult.selected) {
436
+ try {
437
+ const content = await readFile(file.absolutePath, "utf-8");
438
+ const sourceFile = sourceFileFromText(file.relativePath, content);
439
+ validateFragmentSource(sourceFile, file.relativePath, policy, issues);
440
+ } catch (error) {
441
+ issues.push({
442
+ file: file.relativePath,
443
+ message: `Failed to validate fragment snippets: ${error instanceof Error ? error.message : String(error)}`
444
+ });
445
+ }
446
+ }
447
+ const isBatchOnly = Boolean(policy.componentStart || policy.componentLimit);
448
+ if (!isBatchOnly) {
449
+ try {
450
+ const blockFiles = await discoverBlockFiles(configDir, config.exclude);
451
+ for (const file of blockFiles) {
452
+ try {
453
+ const content = await readFile(file.absolutePath, "utf-8");
454
+ const sourceFile = sourceFileFromText(file.relativePath, content);
455
+ validateBlockSource(sourceFile, file.relativePath, policy, issues);
456
+ } catch (error) {
457
+ issues.push({
458
+ file: file.relativePath,
459
+ message: `Failed to validate block snippets: ${error instanceof Error ? error.message : String(error)}`
460
+ });
461
+ }
462
+ }
463
+ } catch (error) {
464
+ issues.push({
465
+ file: "blocks",
466
+ message: `Failed to discover block files: ${error instanceof Error ? error.message : String(error)}`
467
+ });
468
+ }
469
+ const blockPreviewFile = await findBlockPreviewExamplesFile(configDir);
470
+ if (blockPreviewFile) {
471
+ try {
472
+ const content = await readFile(blockPreviewFile, "utf-8");
473
+ const sourceFile = sourceFileFromText(blockPreviewFile, content);
474
+ validateBlockPreviewExamples(sourceFile, blockPreviewFile, policy, issues);
475
+ } catch (error) {
476
+ issues.push({
477
+ file: blockPreviewFile,
478
+ message: `Failed to validate block preview examples: ${error instanceof Error ? error.message : String(error)}`
479
+ });
480
+ }
481
+ }
482
+ }
483
+ return toValidationResult(policy, issues);
484
+ }
485
+
37
486
  // src/validators.ts
38
487
  async function validateSchema(config, configDir) {
39
488
  const files = await discoverFragmentFiles(config, configDir);
@@ -110,26 +559,51 @@ async function validateCoverage(config, configDir) {
110
559
  warnings
111
560
  };
112
561
  }
113
- async function validateAll(config, configDir) {
562
+ async function validateAll(config, configDir, options = {}) {
114
563
  const [schemaResult, coverageResult] = await Promise.all([
115
564
  validateSchema(config, configDir),
116
565
  validateCoverage(config, configDir)
117
566
  ]);
567
+ if (options.snippets === false) {
568
+ return {
569
+ valid: schemaResult.valid && coverageResult.valid,
570
+ errors: [...schemaResult.errors, ...coverageResult.errors],
571
+ warnings: [...schemaResult.warnings, ...coverageResult.warnings]
572
+ };
573
+ }
574
+ const snippetOptions = {
575
+ ...options.snippetMode && { mode: options.snippetMode },
576
+ ...options.componentStart && { componentStart: options.componentStart },
577
+ ...typeof options.componentLimit === "number" ? { componentLimit: options.componentLimit } : {}
578
+ };
579
+ const snippetResult = await validateSnippetPolicy(config, configDir, snippetOptions);
580
+ return {
581
+ valid: schemaResult.valid && coverageResult.valid && snippetResult.errors.length === 0,
582
+ errors: [...schemaResult.errors, ...coverageResult.errors, ...snippetResult.errors],
583
+ warnings: [...schemaResult.warnings, ...coverageResult.warnings, ...snippetResult.warnings]
584
+ };
585
+ }
586
+ async function validateSnippets(config, configDir, options = {}) {
587
+ const snippetResult = await validateSnippetPolicy(config, configDir, {
588
+ ...options.snippetMode && { mode: options.snippetMode },
589
+ ...options.componentStart && { componentStart: options.componentStart },
590
+ ...typeof options.componentLimit === "number" ? { componentLimit: options.componentLimit } : {}
591
+ });
118
592
  return {
119
- valid: schemaResult.valid && coverageResult.valid,
120
- errors: [...schemaResult.errors, ...coverageResult.errors],
121
- warnings: [...schemaResult.warnings, ...coverageResult.warnings]
593
+ valid: snippetResult.errors.length === 0,
594
+ errors: snippetResult.errors,
595
+ warnings: snippetResult.warnings
122
596
  };
123
597
  }
124
598
 
125
599
  // src/build.ts
126
- import { readFile, writeFile, mkdir } from "fs/promises";
127
- import { resolve as resolve4, join as join3 } from "path";
128
- import { existsSync as existsSync4 } from "fs";
600
+ import { readFile as readFile2, writeFile, mkdir } from "fs/promises";
601
+ import { resolve as resolve4, join as join4 } from "path";
602
+ import { existsSync as existsSync5 } from "fs";
129
603
 
130
604
  // src/core/token-resolver.ts
131
605
  import { resolve, dirname, basename } from "path";
132
- import { existsSync, readdirSync } from "fs";
606
+ import { existsSync as existsSync2, readdirSync } from "fs";
133
607
  function roundRgbValues(value) {
134
608
  return value.replace(
135
609
  /rgb\(([^)]+)\)/g,
@@ -202,7 +676,7 @@ function findVariablesFile(tokensDir) {
202
676
  const candidates = ["_variables.scss", "variables.scss"];
203
677
  for (const name of candidates) {
204
678
  const path = resolve(tokensDir, name);
205
- if (existsSync(path)) {
679
+ if (existsSync2(path)) {
206
680
  return path;
207
681
  }
208
682
  }
@@ -220,14 +694,14 @@ function findVariablesFile(tokensDir) {
220
694
  }
221
695
 
222
696
  // src/core/auto-props.ts
223
- import { existsSync as existsSync2, statSync } from "fs";
224
- import { dirname as dirname2, extname, join, resolve as resolve2 } from "path";
225
- import ts from "typescript";
697
+ import { existsSync as existsSync3, statSync } from "fs";
698
+ import { dirname as dirname2, extname, join as join2, resolve as resolve2 } from "path";
699
+ import ts2 from "typescript";
226
700
  function toPosixPath(filePath) {
227
701
  return filePath.replace(/\\/g, "/");
228
702
  }
229
703
  function isFile(filePath) {
230
- if (!existsSync2(filePath)) return false;
704
+ if (!existsSync3(filePath)) return false;
231
705
  try {
232
706
  return statSync(filePath).isFile();
233
707
  } catch {
@@ -245,10 +719,10 @@ function resolveModulePath(basePath) {
245
719
  `${basePath}.ts`,
246
720
  `${basePath}.jsx`,
247
721
  `${basePath}.js`,
248
- join(basePath, "index.tsx"),
249
- join(basePath, "index.ts"),
250
- join(basePath, "index.jsx"),
251
- join(basePath, "index.js")
722
+ join2(basePath, "index.tsx"),
723
+ join2(basePath, "index.ts"),
724
+ join2(basePath, "index.jsx"),
725
+ join2(basePath, "index.js")
252
726
  );
253
727
  }
254
728
  for (const candidate of candidates) {
@@ -270,21 +744,21 @@ function collectTopLevelDeclarations(sourceFile) {
270
744
  const functionDeclarations = /* @__PURE__ */ new Map();
271
745
  const variableDeclarations = /* @__PURE__ */ new Map();
272
746
  for (const node of sourceFile.statements) {
273
- if (ts.isInterfaceDeclaration(node)) {
747
+ if (ts2.isInterfaceDeclaration(node)) {
274
748
  typeDeclarations.set(node.name.text, node);
275
749
  continue;
276
750
  }
277
- if (ts.isTypeAliasDeclaration(node)) {
751
+ if (ts2.isTypeAliasDeclaration(node)) {
278
752
  typeDeclarations.set(node.name.text, node);
279
753
  continue;
280
754
  }
281
- if (ts.isFunctionDeclaration(node) && node.name) {
755
+ if (ts2.isFunctionDeclaration(node) && node.name) {
282
756
  functionDeclarations.set(node.name.text, node);
283
757
  continue;
284
758
  }
285
- if (ts.isVariableStatement(node)) {
759
+ if (ts2.isVariableStatement(node)) {
286
760
  for (const declaration of node.declarationList.declarations) {
287
- if (ts.isIdentifier(declaration.name)) {
761
+ if (ts2.isIdentifier(declaration.name)) {
288
762
  variableDeclarations.set(declaration.name.text, declaration);
289
763
  }
290
764
  }
@@ -293,16 +767,16 @@ function collectTopLevelDeclarations(sourceFile) {
293
767
  return { typeDeclarations, functionDeclarations, variableDeclarations };
294
768
  }
295
769
  function readDefaultValue(expression) {
296
- if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) {
770
+ if (ts2.isStringLiteral(expression) || ts2.isNoSubstitutionTemplateLiteral(expression)) {
297
771
  return expression.text;
298
772
  }
299
- if (ts.isNumericLiteral(expression)) {
773
+ if (ts2.isNumericLiteral(expression)) {
300
774
  return Number(expression.text);
301
775
  }
302
- if (expression.kind === ts.SyntaxKind.TrueKeyword) return true;
303
- if (expression.kind === ts.SyntaxKind.FalseKeyword) return false;
304
- if (expression.kind === ts.SyntaxKind.NullKeyword) return null;
305
- if (ts.isPrefixUnaryExpression(expression) && expression.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(expression.operand)) {
776
+ if (expression.kind === ts2.SyntaxKind.TrueKeyword) return true;
777
+ if (expression.kind === ts2.SyntaxKind.FalseKeyword) return false;
778
+ if (expression.kind === ts2.SyntaxKind.NullKeyword) return null;
779
+ if (ts2.isPrefixUnaryExpression(expression) && expression.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(expression.operand)) {
306
780
  return -Number(expression.operand.text);
307
781
  }
308
782
  return void 0;
@@ -311,14 +785,14 @@ function extractDefaultValues(componentNode) {
311
785
  const defaults = {};
312
786
  if (!componentNode?.parameters?.length) return defaults;
313
787
  const firstParam = componentNode.parameters[0];
314
- if (!ts.isObjectBindingPattern(firstParam.name)) return defaults;
788
+ if (!ts2.isObjectBindingPattern(firstParam.name)) return defaults;
315
789
  for (const element of firstParam.name.elements) {
316
790
  let propName = null;
317
791
  if (element.propertyName) {
318
- if (ts.isIdentifier(element.propertyName) || ts.isStringLiteral(element.propertyName)) {
792
+ if (ts2.isIdentifier(element.propertyName) || ts2.isStringLiteral(element.propertyName)) {
319
793
  propName = element.propertyName.text;
320
794
  }
321
- } else if (ts.isIdentifier(element.name)) {
795
+ } else if (ts2.isIdentifier(element.name)) {
322
796
  propName = element.name.text;
323
797
  }
324
798
  if (!propName || !element.initializer) continue;
@@ -330,13 +804,13 @@ function extractDefaultValues(componentNode) {
330
804
  return defaults;
331
805
  }
332
806
  function isNullishType(type) {
333
- return (type.flags & ts.TypeFlags.Null) !== 0 || (type.flags & ts.TypeFlags.Undefined) !== 0 || (type.flags & ts.TypeFlags.Void) !== 0;
807
+ return (type.flags & ts2.TypeFlags.Null) !== 0 || (type.flags & ts2.TypeFlags.Undefined) !== 0 || (type.flags & ts2.TypeFlags.Void) !== 0;
334
808
  }
335
809
  function isBooleanLikeType(type) {
336
- return (type.flags & ts.TypeFlags.BooleanLike) !== 0 || type.flags === ts.TypeFlags.BooleanLiteral;
810
+ return (type.flags & ts2.TypeFlags.BooleanLike) !== 0 || type.flags === ts2.TypeFlags.BooleanLiteral;
337
811
  }
338
812
  function inferPropType(type, checker) {
339
- const typeText = checker.typeToString(type, void 0, ts.TypeFormatFlags.NoTruncation);
813
+ const typeText = checker.typeToString(type, void 0, ts2.TypeFormatFlags.NoTruncation);
340
814
  if (typeText.includes("ReactNode")) {
341
815
  return { type: "node" };
342
816
  }
@@ -354,7 +828,7 @@ function inferPropType(type, checker) {
354
828
  if (nonNullableTypes.length === 1) {
355
829
  return inferPropType(nonNullableTypes[0], checker);
356
830
  }
357
- const stringLiteralValues = nonNullableTypes.filter((unionType) => (unionType.flags & ts.TypeFlags.StringLiteral) !== 0).map((unionType) => unionType.value);
831
+ const stringLiteralValues = nonNullableTypes.filter((unionType) => (unionType.flags & ts2.TypeFlags.StringLiteral) !== 0).map((unionType) => unionType.value);
358
832
  if (stringLiteralValues.length > 0 && stringLiteralValues.length === nonNullableTypes.length) {
359
833
  return { type: "enum", values: stringLiteralValues };
360
834
  }
@@ -363,16 +837,16 @@ function inferPropType(type, checker) {
363
837
  }
364
838
  return { type: "union" };
365
839
  }
366
- if ((type.flags & ts.TypeFlags.StringLike) !== 0) {
840
+ if ((type.flags & ts2.TypeFlags.StringLike) !== 0) {
367
841
  return { type: "string" };
368
842
  }
369
- if ((type.flags & ts.TypeFlags.NumberLike) !== 0) {
843
+ if ((type.flags & ts2.TypeFlags.NumberLike) !== 0) {
370
844
  return { type: "number" };
371
845
  }
372
- if ((type.flags & ts.TypeFlags.BooleanLike) !== 0) {
846
+ if ((type.flags & ts2.TypeFlags.BooleanLike) !== 0) {
373
847
  return { type: "boolean" };
374
848
  }
375
- if ((type.flags & ts.TypeFlags.Object) !== 0) {
849
+ if ((type.flags & ts2.TypeFlags.Object) !== 0) {
376
850
  return { type: "object" };
377
851
  }
378
852
  return { type: "custom" };
@@ -384,42 +858,42 @@ function resolveComponentSignature(exportName, declarations, sourceFile) {
384
858
  componentNode: node
385
859
  });
386
860
  const resolveFromExpression = (expression) => {
387
- if (ts.isParenthesizedExpression(expression)) {
861
+ if (ts2.isParenthesizedExpression(expression)) {
388
862
  return resolveFromExpression(expression.expression);
389
863
  }
390
- if (ts.isAsExpression(expression) || ts.isTypeAssertionExpression(expression)) {
864
+ if (ts2.isAsExpression(expression) || ts2.isTypeAssertionExpression(expression)) {
391
865
  return resolveFromExpression(expression.expression);
392
866
  }
393
- if (ts.isArrowFunction(expression) || ts.isFunctionExpression(expression)) {
867
+ if (ts2.isArrowFunction(expression) || ts2.isFunctionExpression(expression)) {
394
868
  return typeNodeFromFunction(expression);
395
869
  }
396
- if (ts.isIdentifier(expression)) {
870
+ if (ts2.isIdentifier(expression)) {
397
871
  return resolveFromIdentifier(expression.text);
398
872
  }
399
- if (ts.isCallExpression(expression)) {
400
- if (ts.isPropertyAccessExpression(expression.expression) && expression.expression.name.text === "forwardRef") {
873
+ if (ts2.isCallExpression(expression)) {
874
+ if (ts2.isPropertyAccessExpression(expression.expression) && expression.expression.name.text === "forwardRef") {
401
875
  const forwardRefPropsType = expression.typeArguments?.[1] ?? null;
402
876
  const innerArg = expression.arguments[0];
403
- const inner = innerArg && (ts.isArrowFunction(innerArg) || ts.isFunctionExpression(innerArg)) ? typeNodeFromFunction(innerArg) : innerArg && ts.isIdentifier(innerArg) ? resolveFromIdentifier(innerArg.text) : { propsTypeNode: null, componentNode: null };
877
+ const inner = innerArg && (ts2.isArrowFunction(innerArg) || ts2.isFunctionExpression(innerArg)) ? typeNodeFromFunction(innerArg) : innerArg && ts2.isIdentifier(innerArg) ? resolveFromIdentifier(innerArg.text) : { propsTypeNode: null, componentNode: null };
404
878
  return {
405
879
  propsTypeNode: forwardRefPropsType ?? inner.propsTypeNode,
406
880
  componentNode: inner.componentNode
407
881
  };
408
882
  }
409
- if (ts.isPropertyAccessExpression(expression.expression) && expression.expression.name.text === "memo" && expression.arguments[0]) {
883
+ if (ts2.isPropertyAccessExpression(expression.expression) && expression.expression.name.text === "memo" && expression.arguments[0]) {
410
884
  return resolveFromExpression(expression.arguments[0]);
411
885
  }
412
- if (ts.isPropertyAccessExpression(expression.expression) && expression.expression.expression.getText(sourceFile) === "Object" && expression.expression.name.text === "assign" && expression.arguments[0]) {
886
+ if (ts2.isPropertyAccessExpression(expression.expression) && expression.expression.expression.getText(sourceFile) === "Object" && expression.expression.name.text === "assign" && expression.arguments[0]) {
413
887
  return resolveFromExpression(expression.arguments[0]);
414
888
  }
415
889
  }
416
890
  return { propsTypeNode: null, componentNode: null };
417
891
  };
418
892
  const resolveFromVariable = (declaration) => {
419
- if (declaration.type && ts.isTypeReferenceNode(declaration.type) && declaration.type.typeArguments?.length) {
893
+ if (declaration.type && ts2.isTypeReferenceNode(declaration.type) && declaration.type.typeArguments?.length) {
420
894
  const typeName = declaration.type.typeName.getText(sourceFile);
421
895
  if (typeName.includes("FC") || typeName.includes("FunctionComponent")) {
422
- const componentNode = declaration.initializer && (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer)) ? declaration.initializer : null;
896
+ const componentNode = declaration.initializer && (ts2.isArrowFunction(declaration.initializer) || ts2.isFunctionExpression(declaration.initializer)) ? declaration.initializer : null;
423
897
  return {
424
898
  propsTypeNode: declaration.type.typeArguments[0] ?? null,
425
899
  componentNode
@@ -451,7 +925,7 @@ function resolveComponentSignature(exportName, declarations, sourceFile) {
451
925
  function extractCustomPropsFromComponentFile(componentFilePath, exportName) {
452
926
  const warnings = [];
453
927
  const resolvedPath = resolve2(componentFilePath);
454
- if (!existsSync2(resolvedPath)) {
928
+ if (!existsSync3(resolvedPath)) {
455
929
  return {
456
930
  props: {},
457
931
  warnings: [`Component file not found: ${resolvedPath}`],
@@ -459,17 +933,17 @@ function extractCustomPropsFromComponentFile(componentFilePath, exportName) {
459
933
  };
460
934
  }
461
935
  const compilerOptions = {
462
- target: ts.ScriptTarget.ESNext,
463
- module: ts.ModuleKind.ESNext,
464
- moduleResolution: ts.ModuleResolutionKind.Bundler,
465
- jsx: ts.JsxEmit.ReactJSX,
936
+ target: ts2.ScriptTarget.ESNext,
937
+ module: ts2.ModuleKind.ESNext,
938
+ moduleResolution: ts2.ModuleResolutionKind.Bundler,
939
+ jsx: ts2.JsxEmit.ReactJSX,
466
940
  allowSyntheticDefaultImports: true,
467
941
  esModuleInterop: true,
468
942
  skipLibCheck: true,
469
943
  strict: false,
470
944
  noEmit: true
471
945
  };
472
- const program = ts.createProgram([resolvedPath], compilerOptions);
946
+ const program = ts2.createProgram([resolvedPath], compilerOptions);
473
947
  const sourceFile = program.getSourceFile(resolvedPath);
474
948
  if (!sourceFile) {
475
949
  return {
@@ -506,11 +980,11 @@ function extractCustomPropsFromComponentFile(componentFilePath, exportName) {
506
980
  }
507
981
  const referenceNode = localDeclarations[0];
508
982
  const inferredType = inferPropType(checker.getTypeOfSymbolAtLocation(symbol, referenceNode), checker);
509
- const description = ts.displayPartsToString(symbol.getDocumentationComment(checker)).trim();
983
+ const description = ts2.displayPartsToString(symbol.getDocumentationComment(checker)).trim();
510
984
  extractedProps[propName] = {
511
985
  type: inferredType.type,
512
986
  description,
513
- required: (symbol.getFlags() & ts.SymbolFlags.Optional) === 0,
987
+ required: (symbol.getFlags() & ts2.SymbolFlags.Optional) === 0,
514
988
  ...inferredType.values && { values: inferredType.values },
515
989
  ...defaultValues[propName] !== void 0 && { default: defaultValues[propName] }
516
990
  };
@@ -526,9 +1000,9 @@ function extractCustomPropsFromComponentFile(componentFilePath, exportName) {
526
1000
  }
527
1001
 
528
1002
  // src/core/graph-extractor.ts
529
- import ts2 from "typescript";
530
- import { readFileSync, existsSync as existsSync3 } from "fs";
531
- import { join as join2 } from "path";
1003
+ import ts3 from "typescript";
1004
+ import { readFileSync, existsSync as existsSync4 } from "fs";
1005
+ import { join as join3 } from "path";
532
1006
  import { readdirSync as readdirSync2 } from "fs";
533
1007
  import { EDGE_TYPE_WEIGHTS, computeHealthFromData } from "@fragments-sdk/context/graph";
534
1008
  async function buildComponentGraph(fragments, blocks, componentDir, options) {
@@ -621,17 +1095,17 @@ function extractImportAndHookEdges(componentDir, knownComponents) {
621
1095
  } catch {
622
1096
  continue;
623
1097
  }
624
- const sourceFile = ts2.createSourceFile(
1098
+ const sourceFile = ts3.createSourceFile(
625
1099
  indexPath,
626
1100
  sourceText,
627
- ts2.ScriptTarget.Latest,
1101
+ ts3.ScriptTarget.Latest,
628
1102
  true,
629
- indexPath.endsWith(".tsx") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS
1103
+ indexPath.endsWith(".tsx") ? ts3.ScriptKind.TSX : ts3.ScriptKind.TS
630
1104
  );
631
1105
  const visitNode = (node) => {
632
- if (ts2.isImportDeclaration(node)) {
1106
+ if (ts3.isImportDeclaration(node)) {
633
1107
  const moduleSpecifier = node.moduleSpecifier;
634
- if (ts2.isStringLiteral(moduleSpecifier)) {
1108
+ if (ts3.isStringLiteral(moduleSpecifier)) {
635
1109
  const importPath = moduleSpecifier.text;
636
1110
  if (importPath.startsWith(".") || importPath.startsWith("/")) {
637
1111
  const clause = node.importClause;
@@ -645,7 +1119,7 @@ function extractImportAndHookEdges(componentDir, knownComponents) {
645
1119
  provenance: `source:${componentName}/index.tsx`
646
1120
  });
647
1121
  }
648
- if (clause.namedBindings && ts2.isNamedImports(clause.namedBindings)) {
1122
+ if (clause.namedBindings && ts3.isNamedImports(clause.namedBindings)) {
649
1123
  for (const element of clause.namedBindings.elements) {
650
1124
  const name = element.name.text;
651
1125
  if (isPascalCase(name) && knownComponents.has(name) && name !== componentName) {
@@ -663,7 +1137,7 @@ function extractImportAndHookEdges(componentDir, knownComponents) {
663
1137
  }
664
1138
  }
665
1139
  }
666
- if (ts2.isCallExpression(node) && ts2.isIdentifier(node.expression)) {
1140
+ if (ts3.isCallExpression(node) && ts3.isIdentifier(node.expression)) {
667
1141
  const callName = node.expression.text;
668
1142
  const hookMatch = callName.match(/^use([A-Z][a-zA-Z]*)$/);
669
1143
  if (hookMatch) {
@@ -679,9 +1153,9 @@ function extractImportAndHookEdges(componentDir, knownComponents) {
679
1153
  }
680
1154
  }
681
1155
  }
682
- ts2.forEachChild(node, visitNode);
1156
+ ts3.forEachChild(node, visitNode);
683
1157
  };
684
- ts2.forEachChild(sourceFile, visitNode);
1158
+ ts3.forEachChild(sourceFile, visitNode);
685
1159
  }
686
1160
  return edges;
687
1161
  }
@@ -697,30 +1171,30 @@ function extractSubComponents(componentDir, knownComponents) {
697
1171
  continue;
698
1172
  }
699
1173
  if (!sourceText.includes("Object.assign")) continue;
700
- const sourceFile = ts2.createSourceFile(
1174
+ const sourceFile = ts3.createSourceFile(
701
1175
  indexPath,
702
1176
  sourceText,
703
- ts2.ScriptTarget.Latest,
1177
+ ts3.ScriptTarget.Latest,
704
1178
  true,
705
- indexPath.endsWith(".tsx") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS
1179
+ indexPath.endsWith(".tsx") ? ts3.ScriptKind.TSX : ts3.ScriptKind.TS
706
1180
  );
707
1181
  const subComponents = [];
708
1182
  const visitNode = (node) => {
709
- if (ts2.isCallExpression(node) && ts2.isPropertyAccessExpression(node.expression) && ts2.isIdentifier(node.expression.expression) && node.expression.expression.text === "Object" && node.expression.name.text === "assign" && node.arguments.length >= 2) {
1183
+ if (ts3.isCallExpression(node) && ts3.isPropertyAccessExpression(node.expression) && ts3.isIdentifier(node.expression.expression) && node.expression.expression.text === "Object" && node.expression.name.text === "assign" && node.arguments.length >= 2) {
710
1184
  const propsArg = node.arguments[1];
711
- if (ts2.isObjectLiteralExpression(propsArg)) {
1185
+ if (ts3.isObjectLiteralExpression(propsArg)) {
712
1186
  for (const prop of propsArg.properties) {
713
- if (ts2.isShorthandPropertyAssignment(prop)) {
1187
+ if (ts3.isShorthandPropertyAssignment(prop)) {
714
1188
  subComponents.push(prop.name.text);
715
- } else if (ts2.isPropertyAssignment(prop) && ts2.isIdentifier(prop.name)) {
1189
+ } else if (ts3.isPropertyAssignment(prop) && ts3.isIdentifier(prop.name)) {
716
1190
  subComponents.push(prop.name.text);
717
1191
  }
718
1192
  }
719
1193
  }
720
1194
  }
721
- ts2.forEachChild(node, visitNode);
1195
+ ts3.forEachChild(node, visitNode);
722
1196
  };
723
- ts2.forEachChild(sourceFile, visitNode);
1197
+ ts3.forEachChild(sourceFile, visitNode);
724
1198
  if (subComponents.length > 0) {
725
1199
  result.set(componentName, subComponents);
726
1200
  }
@@ -880,13 +1354,13 @@ function isPascalCase(name) {
880
1354
  }
881
1355
  function findComponentIndex(componentDir, componentName) {
882
1356
  const candidates = [
883
- join2(componentDir, componentName, "index.tsx"),
884
- join2(componentDir, componentName, "index.ts"),
885
- join2(componentDir, componentName, `${componentName}.tsx`),
886
- join2(componentDir, componentName, `${componentName}.ts`)
1357
+ join3(componentDir, componentName, "index.tsx"),
1358
+ join3(componentDir, componentName, "index.ts"),
1359
+ join3(componentDir, componentName, `${componentName}.tsx`),
1360
+ join3(componentDir, componentName, `${componentName}.ts`)
887
1361
  ];
888
1362
  for (const candidate of candidates) {
889
- if (existsSync3(candidate)) {
1363
+ if (existsSync4(candidate)) {
890
1364
  return candidate;
891
1365
  }
892
1366
  }
@@ -895,11 +1369,11 @@ function findComponentIndex(componentDir, componentName) {
895
1369
  for (const entry of entries) {
896
1370
  if (entry.isDirectory() && entry.name === componentName) {
897
1371
  const subCandidates = [
898
- join2(componentDir, entry.name, "index.tsx"),
899
- join2(componentDir, entry.name, "index.ts")
1372
+ join3(componentDir, entry.name, "index.tsx"),
1373
+ join3(componentDir, entry.name, "index.ts")
900
1374
  ];
901
1375
  for (const sc of subCandidates) {
902
- if (existsSync3(sc)) return sc;
1376
+ if (existsSync4(sc)) return sc;
903
1377
  }
904
1378
  }
905
1379
  }
@@ -951,7 +1425,7 @@ async function buildFragments(config, configDir) {
951
1425
  const fragments = {};
952
1426
  for (const file of files) {
953
1427
  try {
954
- const content = await readFile(file.absolutePath, "utf-8");
1428
+ const content = await readFile2(file.absolutePath, "utf-8");
955
1429
  const parsed = parseFragmentFile(content, file.relativePath);
956
1430
  for (const warning of parsed.warnings) {
957
1431
  warnings.push({ file: file.relativePath, warning });
@@ -1086,7 +1560,7 @@ async function buildFragments(config, configDir) {
1086
1560
  let total = 0;
1087
1561
  const fileContents = [];
1088
1562
  for (const file of tokenFiles) {
1089
- const content = await readFile(file.absolutePath, "utf-8");
1563
+ const content = await readFile2(file.absolutePath, "utf-8");
1090
1564
  fileContents.push({ content, path: file.relativePath });
1091
1565
  }
1092
1566
  const allContent = fileContents.map((f) => f.content).join("\n");
@@ -1141,9 +1615,9 @@ async function buildFragments(config, configDir) {
1141
1615
  }
1142
1616
  let packageName;
1143
1617
  const pkgJsonPath = resolve4(configDir, "package.json");
1144
- if (existsSync4(pkgJsonPath)) {
1618
+ if (existsSync5(pkgJsonPath)) {
1145
1619
  try {
1146
- const pkg = JSON.parse(await readFile(pkgJsonPath, "utf-8"));
1620
+ const pkg = JSON.parse(await readFile2(pkgJsonPath, "utf-8"));
1147
1621
  if (pkg.name) packageName = pkg.name;
1148
1622
  } catch {
1149
1623
  }
@@ -1199,8 +1673,8 @@ async function buildFragments(config, configDir) {
1199
1673
  };
1200
1674
  }
1201
1675
  async function buildFragmentsDir(config, configDir) {
1202
- const fragmentsDir = join3(configDir, BRAND.dataDir);
1203
- const componentsDir = join3(fragmentsDir, BRAND.componentsDir);
1676
+ const fragmentsDir = join4(configDir, BRAND.dataDir);
1677
+ const componentsDir = join4(fragmentsDir, BRAND.componentsDir);
1204
1678
  await mkdir(fragmentsDir, { recursive: true });
1205
1679
  await mkdir(componentsDir, { recursive: true });
1206
1680
  const registryResult = await generateRegistry({
@@ -1212,9 +1686,9 @@ async function buildFragmentsDir(config, configDir) {
1212
1686
  });
1213
1687
  const errors = [...registryResult.errors];
1214
1688
  const warnings = [...registryResult.warnings];
1215
- const indexPath = join3(fragmentsDir, "index.json");
1689
+ const indexPath = join4(fragmentsDir, "index.json");
1216
1690
  await writeFile(indexPath, JSON.stringify(registryResult.index, null, 2));
1217
- const registryPath = join3(fragmentsDir, BRAND.registryFile);
1691
+ const registryPath = join4(fragmentsDir, BRAND.registryFile);
1218
1692
  await writeFile(registryPath, JSON.stringify(registryResult.registry, null, 2));
1219
1693
  const contextResult = generateContextMd(registryResult.registry, {
1220
1694
  format: "markdown",
@@ -1226,7 +1700,7 @@ async function buildFragmentsDir(config, configDir) {
1226
1700
  code: false
1227
1701
  }
1228
1702
  });
1229
- const contextPath = join3(fragmentsDir, BRAND.contextFile);
1703
+ const contextPath = join4(fragmentsDir, BRAND.contextFile);
1230
1704
  await writeFile(contextPath, contextResult.content);
1231
1705
  return {
1232
1706
  success: errors.length === 0,
@@ -1579,9 +2053,9 @@ ${BRAND.name} Diff
1579
2053
  }
1580
2054
 
1581
2055
  // src/analyze.ts
1582
- import { existsSync as existsSync5 } from "fs";
1583
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1584
- import { join as join4, dirname as dirname3 } from "path";
2056
+ import { existsSync as existsSync6 } from "fs";
2057
+ import { readFile as readFile3, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
2058
+ import { join as join5, dirname as dirname3 } from "path";
1585
2059
  import pc3 from "picocolors";
1586
2060
  async function runAnalyzeCommand(config, configDir, options = {}) {
1587
2061
  const format = options.format ?? "html";
@@ -1589,8 +2063,8 @@ async function runAnalyzeCommand(config, configDir, options = {}) {
1589
2063
  console.log(pc3.cyan(`
1590
2064
  ${BRAND.name} Analyzer
1591
2065
  `));
1592
- const fragmentsPath = join4(configDir, config.outFile ?? "fragments.json");
1593
- if (!existsSync5(fragmentsPath)) {
2066
+ const fragmentsPath = join5(configDir, config.outFile ?? "fragments.json");
2067
+ if (!existsSync6(fragmentsPath)) {
1594
2068
  console.log(pc3.red(`\u2717 No fragments.json found. Run \`${BRAND.cliCommand} build\` first.
1595
2069
  `));
1596
2070
  return {
@@ -1599,7 +2073,7 @@ ${BRAND.name} Analyzer
1599
2073
  };
1600
2074
  }
1601
2075
  console.log(pc3.dim("Analyzing design system...\n"));
1602
- const content = await readFile2(fragmentsPath, "utf-8");
2076
+ const content = await readFile3(fragmentsPath, "utf-8");
1603
2077
  const data = JSON.parse(content);
1604
2078
  const analytics = analyzeDesignSystem(data);
1605
2079
  printConsoleSummary(analytics);
@@ -1685,7 +2159,7 @@ function colorizeScore(score) {
1685
2159
  }
1686
2160
  function getDefaultOutputPath(format, configDir) {
1687
2161
  const filename = format === "html" ? "fragments-report.html" : "fragments-report.json";
1688
- return join4(configDir, filename);
2162
+ return join5(configDir, filename);
1689
2163
  }
1690
2164
  async function openInBrowser(path) {
1691
2165
  const { platform } = await import("os");
@@ -1747,10 +2221,11 @@ export {
1747
2221
  validateSchema,
1748
2222
  validateCoverage,
1749
2223
  validateAll,
2224
+ validateSnippets,
1750
2225
  buildFragments,
1751
2226
  buildFragmentsDir,
1752
2227
  runScreenshotCommand,
1753
2228
  runDiffCommand,
1754
2229
  runAnalyzeCommand
1755
2230
  };
1756
- //# sourceMappingURL=chunk-S56I5FST.js.map
2231
+ //# sourceMappingURL=chunk-EFQ7SIBX.js.map