@fragments-sdk/cli 0.15.0 → 0.15.1
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.
- package/dist/{ai-client-I6MDWNYA.js → ai-client-LSLQGOMM.js} +1 -2
- package/dist/bin.js +463 -71
- package/dist/bin.js.map +1 -1
- package/dist/chunk-5JF26E55.js +1255 -0
- package/dist/chunk-5JF26E55.js.map +1 -0
- package/dist/{chunk-XJQ5BIWI.js → chunk-6SQPP47U.js} +30 -314
- package/dist/chunk-6SQPP47U.js.map +1 -0
- package/dist/{chunk-65WSVDV5.js → chunk-HQ6A6DTV.js} +1386 -1097
- package/dist/chunk-HQ6A6DTV.js.map +1 -0
- package/dist/chunk-MHIBEEW4.js +511 -0
- package/dist/chunk-MHIBEEW4.js.map +1 -0
- package/dist/{chunk-CZD3AD4Q.js → chunk-ONUP6Z4W.js} +17 -6
- package/dist/chunk-ONUP6Z4W.js.map +1 -0
- package/dist/{codebase-scanner-VOTPXRYW.js → codebase-scanner-MQHUZC2G.js} +1 -2
- package/dist/{converter-JLINP7CJ.js → converter-7XM3Y6NJ.js} +1 -2
- package/dist/{converter-JLINP7CJ.js.map → converter-7XM3Y6NJ.js.map} +1 -1
- package/dist/core/index.js +0 -1
- package/dist/create-IH4R45GE.js +806 -0
- package/dist/create-IH4R45GE.js.map +1 -0
- package/dist/{generate-A4FP5426.js → generate-PVOLUAAC.js} +3 -4
- package/dist/{generate-A4FP5426.js.map → generate-PVOLUAAC.js.map} +1 -1
- package/dist/{govern-scan-UCBZR6D6.js → govern-scan-OYFZYOQW.js} +142 -9
- package/dist/govern-scan-OYFZYOQW.js.map +1 -0
- package/dist/index.d.ts +2 -22
- package/dist/index.js +8 -7
- package/dist/index.js.map +1 -1
- package/dist/{init-HGSM35XA.js → init-SSGUSP7Z.js} +3 -4
- package/dist/{init-HGSM35XA.js.map → init-SSGUSP7Z.js.map} +1 -1
- package/dist/{init-cloud-MQ6GRJAZ.js → init-cloud-3DNKPWFB.js} +29 -4
- package/dist/{init-cloud-MQ6GRJAZ.js.map → init-cloud-3DNKPWFB.js.map} +1 -1
- package/dist/mcp-bin.js +1 -2
- package/dist/mcp-bin.js.map +1 -1
- package/dist/node-37AUE74M.js +65 -0
- package/dist/push-contracts-WY32TFP6.js +84 -0
- package/dist/push-contracts-WY32TFP6.js.map +1 -0
- package/dist/{scan-VNNKACG2.js → scan-PKSYSTRR.js} +5 -5
- package/dist/{scan-generate-TWRHNU5M.js → scan-generate-VY27PIOX.js} +8 -9
- package/dist/scan-generate-VY27PIOX.js.map +1 -0
- package/dist/{scanner-7LAZYPWZ.js → scanner-4KZNOXAK.js} +1 -2
- package/dist/{service-FHQU7YS7.js → service-QJGWUIVL.js} +16 -9
- package/dist/{snapshot-KQEQ6XHL.js → snapshot-WIJMEIFT.js} +1 -2
- package/dist/{snapshot-KQEQ6XHL.js.map → snapshot-WIJMEIFT.js.map} +1 -1
- package/dist/{static-viewer-63PG6FWY.js → static-viewer-7QIBQZRC.js} +1 -2
- package/dist/{test-UQYUCZIS.js → test-64Z5BKBA.js} +2 -3
- package/dist/{test-UQYUCZIS.js.map → test-64Z5BKBA.js.map} +1 -1
- package/dist/token-normalizer-TEPOVBPV.js +312 -0
- package/dist/token-normalizer-TEPOVBPV.js.map +1 -0
- package/dist/token-parser-32KOIOFN.js +22 -0
- package/dist/token-parser-32KOIOFN.js.map +1 -0
- package/dist/{tokens-6GYKDV6U.js → tokens-NZWFQIAB.js} +7 -7
- package/dist/{tokens-generate-VTZV5EEW.js → tokens-generate-5JQSJ27E.js} +1 -2
- package/dist/{tokens-generate-VTZV5EEW.js.map → tokens-generate-5JQSJ27E.js.map} +1 -1
- package/dist/tokens-push-HY3KO36V.js +148 -0
- package/dist/tokens-push-HY3KO36V.js.map +1 -0
- package/package.json +5 -3
- package/src/bin.ts +90 -0
- package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/label.contract.json +1 -1
- package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/primitive.contract.json +1 -1
- package/src/commands/__tests__/build-freshness.test.ts +231 -0
- package/src/commands/__tests__/create.test.ts +71 -0
- package/src/commands/__tests__/drift-sync.test.ts +1 -1
- package/src/commands/__tests__/govern.test.ts +258 -0
- package/src/commands/__tests__/init.test.ts +1 -1
- package/src/commands/__tests__/scan-generate.test.ts +1 -1
- package/src/commands/build.ts +54 -1
- package/src/commands/context.ts +1 -1
- package/src/commands/create.ts +536 -0
- package/src/commands/doctor.ts +3 -2
- package/src/commands/govern-scan.ts +187 -8
- package/src/commands/govern.ts +65 -2
- package/src/commands/init-cloud.ts +32 -4
- package/src/commands/push-contracts.ts +112 -0
- package/src/commands/scan-generate.ts +1 -1
- package/src/commands/scan.ts +13 -0
- package/src/commands/sync.ts +2 -2
- package/src/commands/tokens-push.ts +199 -0
- package/src/core/__tests__/token-resolver.test.ts +1 -1
- package/src/core/component-extractor.test.ts +1 -1
- package/src/core/drift-verifier.ts +1 -1
- package/src/core/extractor-adapter.ts +1 -1
- package/src/index.ts +3 -3
- package/src/migrate/fragment-to-contract.ts +2 -2
- package/src/service/index.ts +8 -0
- package/src/service/tailwind-v4-parser.ts +314 -0
- package/src/service/token-parser.ts +56 -0
- package/src/setup.ts +10 -39
- package/src/theme/__tests__/component-contrast.test.ts +2 -2
- package/src/theme/__tests__/serializer.test.ts +1 -1
- package/src/theme/generator.ts +16 -1
- package/src/theme/schema.ts +8 -0
- package/src/theme/serializer.ts +13 -9
- package/src/theme/types.ts +8 -0
- package/src/validators.ts +1 -2
- package/dist/chunk-65WSVDV5.js.map +0 -1
- package/dist/chunk-7WHVW72L.js +0 -2664
- package/dist/chunk-7WHVW72L.js.map +0 -1
- package/dist/chunk-CZD3AD4Q.js.map +0 -1
- package/dist/chunk-MN3TJ3D5.js +0 -695
- package/dist/chunk-MN3TJ3D5.js.map +0 -1
- package/dist/chunk-XJQ5BIWI.js.map +0 -1
- package/dist/chunk-Z7EY4VHE.js +0 -50
- package/dist/govern-scan-UCBZR6D6.js.map +0 -1
- package/dist/sass.node-4XJK6YBF.js +0 -130708
- package/dist/sass.node-4XJK6YBF.js.map +0 -1
- package/dist/scan-generate-TWRHNU5M.js.map +0 -1
- package/src/build.ts +0 -736
- package/src/core/auto-props.ts +0 -464
- package/src/core/component-extractor.ts +0 -1121
- package/src/core/token-resolver.ts +0 -155
- package/src/viewer/preview-adapter.ts +0 -116
- /package/dist/{ai-client-I6MDWNYA.js.map → ai-client-LSLQGOMM.js.map} +0 -0
- /package/dist/{chunk-Z7EY4VHE.js.map → codebase-scanner-MQHUZC2G.js.map} +0 -0
- /package/dist/{codebase-scanner-VOTPXRYW.js.map → node-37AUE74M.js.map} +0 -0
- /package/dist/{scan-VNNKACG2.js.map → scan-PKSYSTRR.js.map} +0 -0
- /package/dist/{scanner-7LAZYPWZ.js.map → scanner-4KZNOXAK.js.map} +0 -0
- /package/dist/{service-FHQU7YS7.js.map → service-QJGWUIVL.js.map} +0 -0
- /package/dist/{static-viewer-63PG6FWY.js.map → static-viewer-7QIBQZRC.js.map} +0 -0
- /package/dist/{tokens-6GYKDV6U.js.map → tokens-NZWFQIAB.js.map} +0 -0
|
@@ -132,6 +132,7 @@ async function discoverBlockFiles(configDir, exclude) {
|
|
|
132
132
|
absolutePath: resolve2(configDir, relativePath)
|
|
133
133
|
}));
|
|
134
134
|
}
|
|
135
|
+
var discoverRecipeFiles = discoverBlockFiles;
|
|
135
136
|
async function discoverFragmentFiles(config, configDir) {
|
|
136
137
|
const defaultExcludes = [
|
|
137
138
|
"**/*.test.stories.*",
|
|
@@ -346,6 +347,31 @@ async function discoverTokenFiles(configDir, patterns, exclude) {
|
|
|
346
347
|
absolutePath: resolve2(configDir, relativePath)
|
|
347
348
|
}));
|
|
348
349
|
}
|
|
350
|
+
async function discoverInstalledFragments(projectRoot) {
|
|
351
|
+
const pkgJsonPath = resolve2(projectRoot, "package.json");
|
|
352
|
+
if (!existsSync2(pkgJsonPath)) return [];
|
|
353
|
+
const pkgJson = JSON.parse(await readFile(pkgJsonPath, "utf-8"));
|
|
354
|
+
const allDeps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
|
|
355
|
+
const results = [];
|
|
356
|
+
for (const depName of Object.keys(allDeps)) {
|
|
357
|
+
const depDir = resolve2(projectRoot, "node_modules", depName);
|
|
358
|
+
const depPkgPath = resolve2(depDir, "package.json");
|
|
359
|
+
if (!existsSync2(depPkgPath)) continue;
|
|
360
|
+
const depPkg = JSON.parse(await readFile(depPkgPath, "utf-8"));
|
|
361
|
+
if (!depPkg.fragments) continue;
|
|
362
|
+
const files = await fg(
|
|
363
|
+
[`src/**/*${BRAND.fileExtension}`, "src/**/*.stories.tsx"],
|
|
364
|
+
{ cwd: depDir, ignore: ["**/node_modules/**"], absolute: false }
|
|
365
|
+
);
|
|
366
|
+
for (const rel of files) {
|
|
367
|
+
results.push({
|
|
368
|
+
relativePath: `${depName}/${rel}`,
|
|
369
|
+
absolutePath: resolve2(depDir, rel)
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return results;
|
|
374
|
+
}
|
|
349
375
|
async function discoverAllComponents(configDir, options = {}) {
|
|
350
376
|
const componentsMap = /* @__PURE__ */ new Map();
|
|
351
377
|
const sourceComponents = await discoverComponentsFromSource(
|
|
@@ -369,1269 +395,1532 @@ async function discoverAllComponents(configDir, options = {}) {
|
|
|
369
395
|
return Array.from(componentsMap.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
370
396
|
}
|
|
371
397
|
|
|
372
|
-
// src/core/
|
|
373
|
-
import
|
|
374
|
-
import {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
398
|
+
// src/core/loader.ts
|
|
399
|
+
import { unlink } from "fs/promises";
|
|
400
|
+
import { dirname as dirname3, basename as basename2, join } from "path";
|
|
401
|
+
import { pathToFileURL } from "url";
|
|
402
|
+
import { build } from "esbuild";
|
|
403
|
+
var DEFINE_FRAGMENT_SHIM = `
|
|
404
|
+
export function defineFragment(def) {
|
|
405
|
+
return def;
|
|
378
406
|
}
|
|
379
|
-
function
|
|
407
|
+
export function defineBlock(def) {
|
|
408
|
+
return def;
|
|
409
|
+
}
|
|
410
|
+
export function defineRecipe(def) {
|
|
411
|
+
return def;
|
|
412
|
+
}
|
|
413
|
+
`;
|
|
414
|
+
function createFragmentsCoreShimPlugin() {
|
|
415
|
+
return {
|
|
416
|
+
name: BRAND.vitePluginNamespace,
|
|
417
|
+
setup(build2) {
|
|
418
|
+
build2.onResolve({ filter: /^@fragments-sdk\/cli\/core$/ }, (args) => {
|
|
419
|
+
return {
|
|
420
|
+
path: args.path,
|
|
421
|
+
namespace: BRAND.vitePluginNamespace
|
|
422
|
+
};
|
|
423
|
+
});
|
|
424
|
+
build2.onLoad({ filter: /.*/, namespace: BRAND.vitePluginNamespace }, () => {
|
|
425
|
+
return {
|
|
426
|
+
contents: DEFINE_FRAGMENT_SHIM,
|
|
427
|
+
loader: "js"
|
|
428
|
+
};
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
async function loadFragmentFile(absolutePath) {
|
|
434
|
+
const unwrapFragmentExport = (value) => {
|
|
435
|
+
if (!value) return null;
|
|
436
|
+
const asObject = (v) => v && typeof v === "object" ? v : null;
|
|
437
|
+
const isFragmentLike = (v) => {
|
|
438
|
+
const obj = asObject(v);
|
|
439
|
+
return !!obj && ("component" in obj || "meta" in obj || "variants" in obj);
|
|
440
|
+
};
|
|
441
|
+
if (isFragmentLike(value)) {
|
|
442
|
+
return value;
|
|
443
|
+
}
|
|
444
|
+
const first = asObject(value)?.default;
|
|
445
|
+
if (isFragmentLike(first)) {
|
|
446
|
+
return first;
|
|
447
|
+
}
|
|
448
|
+
const second = asObject(first)?.default;
|
|
449
|
+
if (isFragmentLike(second)) {
|
|
450
|
+
return second;
|
|
451
|
+
}
|
|
452
|
+
return value ?? null;
|
|
453
|
+
};
|
|
454
|
+
const ext = absolutePath.split(".").pop()?.toLowerCase();
|
|
455
|
+
const needsTransform = ext === "tsx" || ext === "ts" || ext === "jsx";
|
|
456
|
+
if (!needsTransform) {
|
|
457
|
+
const fileUrl = pathToFileURL(absolutePath).href;
|
|
458
|
+
const module = await import(fileUrl);
|
|
459
|
+
return unwrapFragmentExport(module.default ?? null);
|
|
460
|
+
}
|
|
461
|
+
const sourceDir = dirname3(absolutePath);
|
|
462
|
+
const baseName = basename2(absolutePath, `.${ext}`);
|
|
463
|
+
const tempFile = join(sourceDir, `.${baseName}.fragments-temp-${Date.now()}.mjs`);
|
|
464
|
+
try {
|
|
465
|
+
await build({
|
|
466
|
+
entryPoints: [absolutePath],
|
|
467
|
+
outfile: tempFile,
|
|
468
|
+
bundle: true,
|
|
469
|
+
format: "esm",
|
|
470
|
+
target: "es2022",
|
|
471
|
+
jsx: "automatic",
|
|
472
|
+
platform: "node",
|
|
473
|
+
plugins: [createFragmentsCoreShimPlugin()],
|
|
474
|
+
// Externalize all node_modules - we only need fragment metadata, not component code
|
|
475
|
+
packages: "external",
|
|
476
|
+
// Also explicitly list patterns for nested imports
|
|
477
|
+
external: [
|
|
478
|
+
// React and its subpaths
|
|
479
|
+
"react",
|
|
480
|
+
"react-dom",
|
|
481
|
+
"react/*",
|
|
482
|
+
"react-dom/*"
|
|
483
|
+
],
|
|
484
|
+
// Don't emit sourcemaps for temp files
|
|
485
|
+
sourcemap: false,
|
|
486
|
+
// Suppress warnings
|
|
487
|
+
logLevel: "silent",
|
|
488
|
+
// Use loader to ignore style imports
|
|
489
|
+
loader: {
|
|
490
|
+
".scss": "empty",
|
|
491
|
+
".css": "empty",
|
|
492
|
+
".svg": "empty",
|
|
493
|
+
".png": "empty",
|
|
494
|
+
".jpg": "empty",
|
|
495
|
+
".jpeg": "empty",
|
|
496
|
+
".gif": "empty",
|
|
497
|
+
".woff": "empty",
|
|
498
|
+
".woff2": "empty",
|
|
499
|
+
".ttf": "empty",
|
|
500
|
+
".eot": "empty"
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
const fileUrl = pathToFileURL(tempFile).href;
|
|
504
|
+
const module = await import(fileUrl);
|
|
505
|
+
return unwrapFragmentExport(module.default ?? null);
|
|
506
|
+
} finally {
|
|
507
|
+
try {
|
|
508
|
+
await unlink(tempFile);
|
|
509
|
+
} catch {
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
async function loadFragmentFiles(absolutePaths) {
|
|
514
|
+
const results = /* @__PURE__ */ new Map();
|
|
515
|
+
await Promise.all(
|
|
516
|
+
absolutePaths.map(async (path) => {
|
|
517
|
+
try {
|
|
518
|
+
const fragment = await loadFragmentFile(path);
|
|
519
|
+
if (fragment) {
|
|
520
|
+
results.set(path, fragment);
|
|
521
|
+
} else {
|
|
522
|
+
results.set(path, new Error("No default export found"));
|
|
523
|
+
}
|
|
524
|
+
} catch (error) {
|
|
525
|
+
results.set(
|
|
526
|
+
path,
|
|
527
|
+
error instanceof Error ? error : new Error(String(error))
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
})
|
|
531
|
+
);
|
|
532
|
+
return results;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// src/core/parser.ts
|
|
536
|
+
import ts from "typescript";
|
|
537
|
+
function parseFragmentFile(fileContent, filePath) {
|
|
538
|
+
const warnings = [];
|
|
380
539
|
const sourceFile = ts.createSourceFile(
|
|
381
|
-
|
|
382
|
-
|
|
540
|
+
filePath ?? "fragment.tsx",
|
|
541
|
+
fileContent,
|
|
383
542
|
ts.ScriptTarget.Latest,
|
|
384
543
|
true,
|
|
385
|
-
|
|
544
|
+
ts.ScriptKind.TSX
|
|
386
545
|
);
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
546
|
+
const imports = extractImports(sourceFile);
|
|
547
|
+
const defineFragmentCall = findDefineFragmentCall(sourceFile);
|
|
548
|
+
if (!defineFragmentCall) {
|
|
549
|
+
warnings.push("No defineFragment() call found");
|
|
550
|
+
return {
|
|
551
|
+
componentImport: null,
|
|
552
|
+
componentName: null,
|
|
553
|
+
meta: {},
|
|
554
|
+
usage: { when: [], whenNot: [] },
|
|
555
|
+
props: {},
|
|
556
|
+
variants: [],
|
|
557
|
+
relations: [],
|
|
558
|
+
warnings
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
const arg = defineFragmentCall.arguments[0];
|
|
562
|
+
if (!arg || !ts.isObjectLiteralExpression(arg)) {
|
|
563
|
+
warnings.push("defineFragment() argument is not an object literal");
|
|
564
|
+
return {
|
|
565
|
+
componentImport: null,
|
|
566
|
+
componentName: null,
|
|
567
|
+
meta: {},
|
|
568
|
+
usage: { when: [], whenNot: [] },
|
|
569
|
+
props: {},
|
|
570
|
+
variants: [],
|
|
571
|
+
relations: [],
|
|
572
|
+
warnings
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
const componentProp = findProperty(arg, "component");
|
|
576
|
+
let componentName = null;
|
|
577
|
+
let componentImport = null;
|
|
578
|
+
if (componentProp && ts.isIdentifier(componentProp)) {
|
|
579
|
+
componentName = componentProp.text;
|
|
580
|
+
componentImport = imports.get(componentName) ?? null;
|
|
581
|
+
}
|
|
582
|
+
const meta = extractMeta(arg, warnings);
|
|
583
|
+
const usage = extractUsage(arg, warnings);
|
|
584
|
+
const props = extractProps(arg, warnings);
|
|
585
|
+
const variants = extractVariants(arg, sourceFile, warnings);
|
|
586
|
+
const relations = extractRelations(arg, warnings);
|
|
587
|
+
const ai = extractAIMetadata(arg, warnings);
|
|
588
|
+
const contract = extractContractMetadata(arg);
|
|
589
|
+
return {
|
|
590
|
+
componentImport,
|
|
591
|
+
componentName,
|
|
592
|
+
meta,
|
|
593
|
+
usage,
|
|
594
|
+
props,
|
|
595
|
+
variants,
|
|
596
|
+
relations,
|
|
597
|
+
ai,
|
|
598
|
+
contract,
|
|
599
|
+
warnings
|
|
392
600
|
};
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const
|
|
396
|
-
const importedModules = [];
|
|
601
|
+
}
|
|
602
|
+
function extractImports(sourceFile) {
|
|
603
|
+
const imports = /* @__PURE__ */ new Map();
|
|
397
604
|
ts.forEachChild(sourceFile, (node) => {
|
|
398
605
|
if (ts.isImportDeclaration(node)) {
|
|
399
606
|
const moduleSpecifier = node.moduleSpecifier;
|
|
400
607
|
if (ts.isStringLiteral(moduleSpecifier)) {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
if (name.endsWith("Props")) {
|
|
413
|
-
propsInterfaces.set(name, node);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
417
|
-
const hasExport = node.modifiers?.some(
|
|
418
|
-
(m) => m.kind === ts.SyntaxKind.ExportKeyword
|
|
419
|
-
);
|
|
420
|
-
const hasDefault = node.modifiers?.some(
|
|
421
|
-
(m) => m.kind === ts.SyntaxKind.DefaultKeyword
|
|
422
|
-
);
|
|
423
|
-
if (hasExport) {
|
|
424
|
-
componentExports.push(node.name.text);
|
|
425
|
-
if (hasDefault) {
|
|
426
|
-
defaultExports.add(node.name.text);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
if (ts.isVariableStatement(node)) {
|
|
431
|
-
const hasExport = node.modifiers?.some(
|
|
432
|
-
(m) => m.kind === ts.SyntaxKind.ExportKeyword
|
|
433
|
-
);
|
|
434
|
-
if (hasExport) {
|
|
435
|
-
for (const decl of node.declarationList.declarations) {
|
|
436
|
-
if (ts.isIdentifier(decl.name)) {
|
|
437
|
-
componentExports.push(decl.name.text);
|
|
608
|
+
const modulePath = moduleSpecifier.text;
|
|
609
|
+
const importClause = node.importClause;
|
|
610
|
+
if (importClause) {
|
|
611
|
+
if (importClause.name) {
|
|
612
|
+
imports.set(importClause.name.text, modulePath);
|
|
613
|
+
}
|
|
614
|
+
const namedBindings = importClause.namedBindings;
|
|
615
|
+
if (namedBindings && ts.isNamedImports(namedBindings)) {
|
|
616
|
+
for (const element of namedBindings.elements) {
|
|
617
|
+
imports.set(element.name.text, modulePath);
|
|
618
|
+
}
|
|
438
619
|
}
|
|
439
620
|
}
|
|
440
621
|
}
|
|
441
622
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
623
|
+
});
|
|
624
|
+
return imports;
|
|
625
|
+
}
|
|
626
|
+
function findDefineFragmentCall(sourceFile) {
|
|
627
|
+
let result = null;
|
|
628
|
+
function visit(node) {
|
|
629
|
+
if (ts.isCallExpression(node)) {
|
|
630
|
+
const expression = node.expression;
|
|
631
|
+
if (ts.isIdentifier(expression) && expression.text === "defineFragment") {
|
|
632
|
+
result = node;
|
|
633
|
+
return;
|
|
447
634
|
}
|
|
448
635
|
}
|
|
449
|
-
|
|
450
|
-
result.exports = componentExports;
|
|
451
|
-
result.imports = importedModules;
|
|
452
|
-
const mainComponent = componentExports.find(
|
|
453
|
-
(name) => /^[A-Z]/.test(name) && !name.endsWith("Props")
|
|
454
|
-
);
|
|
455
|
-
if (!mainComponent) {
|
|
456
|
-
return null;
|
|
457
|
-
}
|
|
458
|
-
result.componentName = mainComponent;
|
|
459
|
-
result.isDefaultExport = defaultExports.has(mainComponent);
|
|
460
|
-
const propsInterfaceName = `${mainComponent}Props`;
|
|
461
|
-
const propsInterface = propsInterfaces.get(propsInterfaceName);
|
|
462
|
-
if (propsInterface) {
|
|
463
|
-
result.propsInterfaceName = propsInterfaceName;
|
|
464
|
-
result.props = extractPropsFromInterface(propsInterface, sourceFile);
|
|
636
|
+
ts.forEachChild(node, visit);
|
|
465
637
|
}
|
|
638
|
+
visit(sourceFile);
|
|
466
639
|
return result;
|
|
467
640
|
}
|
|
468
|
-
function
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
if (ts.
|
|
473
|
-
|
|
474
|
-
const prop = extractPropFromMember(member, sourceFile);
|
|
475
|
-
if (prop) {
|
|
476
|
-
props[propName] = prop;
|
|
477
|
-
}
|
|
641
|
+
function findProperty(obj, name) {
|
|
642
|
+
for (const prop of obj.properties) {
|
|
643
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
644
|
+
const propName = prop.name;
|
|
645
|
+
if (ts.isIdentifier(propName) && propName.text === name) {
|
|
646
|
+
return prop.initializer;
|
|
478
647
|
}
|
|
479
648
|
}
|
|
480
649
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
function extractMeta(arg, warnings) {
|
|
653
|
+
const metaProp = findProperty(arg, "meta");
|
|
654
|
+
if (!metaProp || !ts.isObjectLiteralExpression(metaProp)) {
|
|
655
|
+
warnings.push("No meta object found");
|
|
656
|
+
return {};
|
|
657
|
+
}
|
|
658
|
+
const meta = {};
|
|
659
|
+
const name = extractStringProperty(metaProp, "name");
|
|
660
|
+
if (name) meta.name = name;
|
|
661
|
+
const description = extractStringProperty(metaProp, "description");
|
|
662
|
+
if (description) meta.description = description;
|
|
663
|
+
const category = extractStringProperty(metaProp, "category");
|
|
664
|
+
if (category) meta.category = category;
|
|
665
|
+
const status = extractStringProperty(metaProp, "status");
|
|
666
|
+
if (status) meta.status = status;
|
|
667
|
+
const since = extractStringProperty(metaProp, "since");
|
|
668
|
+
if (since) meta.since = since;
|
|
669
|
+
const figma = extractStringProperty(metaProp, "figma");
|
|
670
|
+
if (figma) meta.figma = figma;
|
|
671
|
+
const tags = extractStringArray(metaProp, "tags");
|
|
672
|
+
if (tags.length > 0) meta.tags = tags;
|
|
673
|
+
const depsProp = findProperty(metaProp, "dependencies");
|
|
674
|
+
if (depsProp && ts.isArrayLiteralExpression(depsProp)) {
|
|
675
|
+
const deps = extractLiteralValue(depsProp);
|
|
676
|
+
if (Array.isArray(deps) && deps.length > 0) {
|
|
677
|
+
meta.dependencies = deps;
|
|
493
678
|
}
|
|
494
679
|
}
|
|
495
|
-
return
|
|
680
|
+
return meta;
|
|
496
681
|
}
|
|
497
|
-
function
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
const typeInfo = parseTypeNode(member.type, sourceFile);
|
|
502
|
-
entry.type = typeInfo.type;
|
|
503
|
-
entry.typeKind = typeInfo.typeKind;
|
|
504
|
-
if (typeInfo.options) {
|
|
505
|
-
entry.options = typeInfo.options;
|
|
506
|
-
}
|
|
682
|
+
function extractUsage(arg, warnings) {
|
|
683
|
+
const usageProp = findProperty(arg, "usage");
|
|
684
|
+
if (!usageProp || !ts.isObjectLiteralExpression(usageProp)) {
|
|
685
|
+
return { when: [], whenNot: [] };
|
|
507
686
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
687
|
+
return {
|
|
688
|
+
when: extractStringArray(usageProp, "when"),
|
|
689
|
+
whenNot: extractStringArray(usageProp, "whenNot"),
|
|
690
|
+
guidelines: extractStringArray(usageProp, "guidelines"),
|
|
691
|
+
accessibility: extractStringArray(usageProp, "accessibility")
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function extractProps(arg, warnings) {
|
|
695
|
+
const propsProp = findProperty(arg, "props");
|
|
696
|
+
if (!propsProp || !ts.isObjectLiteralExpression(propsProp)) {
|
|
697
|
+
return {};
|
|
511
698
|
}
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
699
|
+
const props = {};
|
|
700
|
+
for (const prop of propsProp.properties) {
|
|
701
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
702
|
+
const propName = prop.name.text;
|
|
703
|
+
const propValue = prop.initializer;
|
|
704
|
+
if (ts.isObjectLiteralExpression(propValue)) {
|
|
705
|
+
props[propName] = extractPropDefinition(propValue);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
515
708
|
}
|
|
516
|
-
return
|
|
709
|
+
return props;
|
|
517
710
|
}
|
|
518
|
-
function
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
711
|
+
function extractPropDefinition(obj) {
|
|
712
|
+
const def = {};
|
|
713
|
+
const type = extractStringProperty(obj, "type");
|
|
714
|
+
if (type) def.type = type;
|
|
715
|
+
const description = extractStringProperty(obj, "description");
|
|
716
|
+
if (description) def.description = description;
|
|
717
|
+
const values = extractStringArray(obj, "values");
|
|
718
|
+
if (values.length > 0) def.values = values;
|
|
719
|
+
const required = extractBooleanProperty(obj, "required");
|
|
720
|
+
if (required !== null) def.required = required;
|
|
721
|
+
const defaultProp = findProperty(obj, "default");
|
|
722
|
+
if (defaultProp) {
|
|
723
|
+
def.default = extractLiteralValue(defaultProp);
|
|
524
724
|
}
|
|
525
|
-
|
|
526
|
-
|
|
725
|
+
const constraints = extractStringArray(obj, "constraints");
|
|
726
|
+
if (constraints.length > 0) def.constraints = constraints;
|
|
727
|
+
return def;
|
|
728
|
+
}
|
|
729
|
+
function extractVariants(arg, sourceFile, warnings) {
|
|
730
|
+
const variantsProp = findProperty(arg, "variants");
|
|
731
|
+
if (!variantsProp || !ts.isArrayLiteralExpression(variantsProp)) {
|
|
732
|
+
return [];
|
|
527
733
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
734
|
+
const variants = [];
|
|
735
|
+
for (const element of variantsProp.elements) {
|
|
736
|
+
if (ts.isObjectLiteralExpression(element)) {
|
|
737
|
+
const name = extractStringProperty(element, "name");
|
|
738
|
+
const description = extractStringProperty(element, "description");
|
|
739
|
+
if (name) {
|
|
740
|
+
const variant = {
|
|
741
|
+
name,
|
|
742
|
+
description: description ?? ""
|
|
743
|
+
};
|
|
744
|
+
const codeProp = findProperty(element, "code");
|
|
745
|
+
if (codeProp && (ts.isStringLiteral(codeProp) || ts.isNoSubstitutionTemplateLiteral(codeProp))) {
|
|
746
|
+
variant.code = codeProp.text;
|
|
541
747
|
}
|
|
542
|
-
|
|
543
|
-
|
|
748
|
+
const renderProp = findProperty(element, "render");
|
|
749
|
+
if (renderProp && !variant.code) {
|
|
750
|
+
variant.code = extractRenderCode(renderProp, sourceFile);
|
|
751
|
+
}
|
|
752
|
+
const figma = extractStringProperty(element, "figma");
|
|
753
|
+
if (figma) {
|
|
754
|
+
variant.figma = figma;
|
|
755
|
+
}
|
|
756
|
+
const argsProp = findProperty(element, "args");
|
|
757
|
+
if (argsProp && ts.isObjectLiteralExpression(argsProp)) {
|
|
758
|
+
const argsValue = extractLiteralValue(argsProp);
|
|
759
|
+
if (argsValue && typeof argsValue === "object" && !Array.isArray(argsValue)) {
|
|
760
|
+
variant.args = argsValue;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
variants.push(variant);
|
|
544
764
|
}
|
|
545
765
|
}
|
|
546
|
-
if (allLiterals && options.length > 0) {
|
|
547
|
-
return {
|
|
548
|
-
type: options.map((o) => `"${o}"`).join(" | "),
|
|
549
|
-
typeKind: "enum",
|
|
550
|
-
options
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
return {
|
|
554
|
-
type: typeNode.getText(sourceFile),
|
|
555
|
-
typeKind: "union"
|
|
556
|
-
};
|
|
557
|
-
}
|
|
558
|
-
if (ts.isFunctionTypeNode(typeNode)) {
|
|
559
|
-
return {
|
|
560
|
-
type: typeNode.getText(sourceFile),
|
|
561
|
-
typeKind: "function"
|
|
562
|
-
};
|
|
563
766
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
767
|
+
return variants;
|
|
768
|
+
}
|
|
769
|
+
function dedent(str) {
|
|
770
|
+
const lines = str.split("\n");
|
|
771
|
+
if (lines.length <= 1) {
|
|
772
|
+
return str;
|
|
569
773
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
if (
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
return { type: "JSX.Element", typeKind: "element" };
|
|
774
|
+
const firstLineIndent = lines[0].match(/^(\s*)/)?.[1].length ?? 0;
|
|
775
|
+
const startIndex = firstLineIndent === 0 ? 1 : 0;
|
|
776
|
+
let minIndent = Infinity;
|
|
777
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
778
|
+
const line = lines[i];
|
|
779
|
+
if (line.trim() === "") continue;
|
|
780
|
+
const match = line.match(/^(\s*)/);
|
|
781
|
+
if (match) {
|
|
782
|
+
minIndent = Math.min(minIndent, match[1].length);
|
|
580
783
|
}
|
|
581
|
-
return {
|
|
582
|
-
type: typeNode.getText(sourceFile),
|
|
583
|
-
typeKind: "object"
|
|
584
|
-
};
|
|
585
784
|
}
|
|
586
|
-
if (
|
|
587
|
-
return
|
|
588
|
-
type: typeNode.getText(sourceFile),
|
|
589
|
-
typeKind: "object"
|
|
590
|
-
};
|
|
785
|
+
if (minIndent === Infinity || minIndent === 0) {
|
|
786
|
+
return str;
|
|
591
787
|
}
|
|
592
|
-
return {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
};
|
|
596
|
-
}
|
|
597
|
-
function getJSDocComment(node) {
|
|
598
|
-
const jsDocTags = ts.getJSDocTags(node);
|
|
599
|
-
const jsDoc = node.jsDoc;
|
|
600
|
-
if (jsDoc && jsDoc.length > 0) {
|
|
601
|
-
const comment = jsDoc[0].comment;
|
|
602
|
-
if (typeof comment === "string") {
|
|
603
|
-
return comment;
|
|
604
|
-
}
|
|
605
|
-
if (Array.isArray(comment)) {
|
|
606
|
-
return comment.map((c) => typeof c === "string" ? c : c.text).join("");
|
|
788
|
+
return lines.map((line, index) => {
|
|
789
|
+
if (index === 0 && firstLineIndent === 0) {
|
|
790
|
+
return line;
|
|
607
791
|
}
|
|
608
|
-
|
|
609
|
-
|
|
792
|
+
return line.slice(minIndent);
|
|
793
|
+
}).join("\n");
|
|
610
794
|
}
|
|
611
|
-
function
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
} catch {
|
|
620
|
-
return comment;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
795
|
+
function extractRenderCode(renderProp, sourceFile) {
|
|
796
|
+
if (ts.isArrowFunction(renderProp)) {
|
|
797
|
+
const body = renderProp.body;
|
|
798
|
+
const start = body.getStart(sourceFile);
|
|
799
|
+
const end = body.getEnd();
|
|
800
|
+
let code = sourceFile.text.substring(start, end).trim();
|
|
801
|
+
if (code.startsWith("(") && code.endsWith(")")) {
|
|
802
|
+
code = code.slice(1, -1).trim();
|
|
623
803
|
}
|
|
804
|
+
code = dedent(code);
|
|
805
|
+
return code;
|
|
624
806
|
}
|
|
625
807
|
return void 0;
|
|
626
808
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
import fg2 from "fast-glob";
|
|
632
|
-
async function generateRegistry(options) {
|
|
633
|
-
const {
|
|
634
|
-
projectRoot,
|
|
635
|
-
componentPatterns = ["src/**/*.tsx", "src/**/*.ts"],
|
|
636
|
-
storyPatterns = ["src/**/*.stories.tsx", "src/**/*.stories.ts"],
|
|
637
|
-
fragmentsDir = join(projectRoot, BRAND.dataDir),
|
|
638
|
-
registryOptions = {}
|
|
639
|
-
} = options;
|
|
640
|
-
const {
|
|
641
|
-
requireStory = false,
|
|
642
|
-
publicOnly = false,
|
|
643
|
-
categoryDepth = 1,
|
|
644
|
-
includeProps = false,
|
|
645
|
-
embedFragments = false
|
|
646
|
-
} = registryOptions;
|
|
647
|
-
const errors = [];
|
|
648
|
-
const warnings = [];
|
|
649
|
-
const storyFiles = await fg2(storyPatterns, {
|
|
650
|
-
cwd: projectRoot,
|
|
651
|
-
ignore: ["**/node_modules/**"],
|
|
652
|
-
absolute: true
|
|
653
|
-
});
|
|
654
|
-
const storyMap = /* @__PURE__ */ new Map();
|
|
655
|
-
for (const storyPath of storyFiles) {
|
|
656
|
-
const storyDir = dirname3(storyPath);
|
|
657
|
-
const storyBase = basename2(storyPath).replace(/\.stories\.(tsx?|jsx?)$/, "");
|
|
658
|
-
storyMap.set(`${storyDir}/${storyBase}`, storyPath);
|
|
659
|
-
}
|
|
660
|
-
const componentFiles = await fg2(componentPatterns, {
|
|
661
|
-
cwd: projectRoot,
|
|
662
|
-
ignore: [
|
|
663
|
-
"**/node_modules/**",
|
|
664
|
-
"**/*.stories.*",
|
|
665
|
-
"**/*.test.*",
|
|
666
|
-
"**/*.spec.*",
|
|
667
|
-
"**/*.d.ts"
|
|
668
|
-
],
|
|
669
|
-
absolute: true
|
|
670
|
-
});
|
|
671
|
-
const fragmentPattern = join(
|
|
672
|
-
BRAND.dataDir,
|
|
673
|
-
BRAND.componentsDir,
|
|
674
|
-
`*${BRAND.fileExtension}`
|
|
675
|
-
);
|
|
676
|
-
const fragmentFiles = await fg2(fragmentPattern, {
|
|
677
|
-
cwd: projectRoot,
|
|
678
|
-
absolute: true
|
|
679
|
-
});
|
|
680
|
-
const fragmentMap = /* @__PURE__ */ new Map();
|
|
681
|
-
for (const fragmentPath of fragmentFiles) {
|
|
682
|
-
const fragmentName = basename2(fragmentPath).replace(BRAND.fileExtension, "");
|
|
683
|
-
try {
|
|
684
|
-
const content = readFileSync2(fragmentPath, "utf-8");
|
|
685
|
-
const fragment = JSON.parse(content);
|
|
686
|
-
fragmentMap.set(fragmentName, {
|
|
687
|
-
path: relative(projectRoot, fragmentPath),
|
|
688
|
-
fragment
|
|
689
|
-
});
|
|
690
|
-
} catch (e) {
|
|
691
|
-
errors.push({
|
|
692
|
-
file: fragmentPath,
|
|
693
|
-
error: `Failed to parse fragment: ${e instanceof Error ? e.message : String(e)}`
|
|
694
|
-
});
|
|
695
|
-
}
|
|
809
|
+
function extractRelations(arg, warnings) {
|
|
810
|
+
const relationsProp = findProperty(arg, "relations");
|
|
811
|
+
if (!relationsProp || !ts.isArrayLiteralExpression(relationsProp)) {
|
|
812
|
+
return [];
|
|
696
813
|
}
|
|
697
|
-
const
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
const
|
|
703
|
-
if (
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
continue;
|
|
710
|
-
}
|
|
711
|
-
const componentDir = dirname3(filePath);
|
|
712
|
-
const baseNameWithoutExt = basename2(filePath).replace(/\.(tsx?|jsx?)$/, "");
|
|
713
|
-
const storyPath = storyMap.get(`${componentDir}/${baseNameWithoutExt}`);
|
|
714
|
-
if (requireStory && !storyPath) {
|
|
715
|
-
continue;
|
|
716
|
-
}
|
|
717
|
-
const fragmentData = fragmentMap.get(componentName);
|
|
718
|
-
const category = fragmentData?.fragment?.meta?.status ? void 0 : getCategoryFromPath(relativePath, categoryDepth);
|
|
719
|
-
const hasEnrichment = fragmentData ? hasRealEnrichment(fragmentData.fragment) : false;
|
|
720
|
-
const entry = {
|
|
721
|
-
path: relativePath
|
|
722
|
-
};
|
|
723
|
-
if (storyPath) {
|
|
724
|
-
entry.storyPath = relative(projectRoot, storyPath);
|
|
725
|
-
}
|
|
726
|
-
if (fragmentData) {
|
|
727
|
-
entry.fragmentPath = fragmentData.path;
|
|
728
|
-
if (hasEnrichment) {
|
|
729
|
-
entry.hasEnrichment = true;
|
|
730
|
-
}
|
|
731
|
-
if (fragmentData.fragment.description) {
|
|
732
|
-
entry.description = fragmentData.fragment.description;
|
|
733
|
-
}
|
|
734
|
-
if (fragmentData.fragment.meta?.status) {
|
|
735
|
-
entry.status = fragmentData.fragment.meta.status;
|
|
736
|
-
}
|
|
737
|
-
if (embedFragments) {
|
|
738
|
-
entry.fragment = fragmentData.fragment;
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
if (category) {
|
|
742
|
-
entry.category = category;
|
|
743
|
-
if (!categories[category]) {
|
|
744
|
-
categories[category] = [];
|
|
745
|
-
}
|
|
746
|
-
categories[category].push(componentName);
|
|
747
|
-
}
|
|
748
|
-
if (includeProps && extracted.props) {
|
|
749
|
-
entry.props = extracted.props;
|
|
750
|
-
}
|
|
751
|
-
if (extracted.exports.length > 1) {
|
|
752
|
-
entry.exports = extracted.exports;
|
|
814
|
+
const relations = [];
|
|
815
|
+
for (const element of relationsProp.elements) {
|
|
816
|
+
if (ts.isObjectLiteralExpression(element)) {
|
|
817
|
+
const component = extractStringProperty(element, "component");
|
|
818
|
+
const relationship = extractStringProperty(element, "relationship");
|
|
819
|
+
const note = extractStringProperty(element, "note");
|
|
820
|
+
if (component && relationship) {
|
|
821
|
+
relations.push({
|
|
822
|
+
component,
|
|
823
|
+
relationship,
|
|
824
|
+
note: note ?? ""
|
|
825
|
+
});
|
|
753
826
|
}
|
|
754
|
-
components[componentName] = entry;
|
|
755
|
-
indexComponents[componentName] = relativePath;
|
|
756
|
-
} catch (e) {
|
|
757
|
-
errors.push({
|
|
758
|
-
file: filePath,
|
|
759
|
-
error: `Failed to extract component: ${e instanceof Error ? e.message : String(e)}`
|
|
760
|
-
});
|
|
761
827
|
}
|
|
762
828
|
}
|
|
763
|
-
|
|
764
|
-
const registry = {
|
|
765
|
-
$schema: "https://fragments.dev/schema/registry-v1.json",
|
|
766
|
-
version: "1.0",
|
|
767
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
768
|
-
componentCount,
|
|
769
|
-
components,
|
|
770
|
-
categories
|
|
771
|
-
};
|
|
772
|
-
const index = {
|
|
773
|
-
version: "1.0",
|
|
774
|
-
generatedAt: registry.generatedAt,
|
|
775
|
-
components: indexComponents,
|
|
776
|
-
categories
|
|
777
|
-
};
|
|
778
|
-
return { registry, index, errors, warnings };
|
|
829
|
+
return relations;
|
|
779
830
|
}
|
|
780
|
-
function
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
if (componentsIndex === -1) {
|
|
831
|
+
function extractAIMetadata(arg, warnings) {
|
|
832
|
+
const aiProp = findProperty(arg, "ai");
|
|
833
|
+
if (!aiProp || !ts.isObjectLiteralExpression(aiProp)) {
|
|
784
834
|
return void 0;
|
|
785
835
|
}
|
|
786
|
-
const
|
|
787
|
-
|
|
788
|
-
|
|
836
|
+
const ai = {};
|
|
837
|
+
const compositionPattern = extractStringProperty(aiProp, "compositionPattern");
|
|
838
|
+
if (compositionPattern && ["compound", "simple", "controlled"].includes(compositionPattern)) {
|
|
839
|
+
ai.compositionPattern = compositionPattern;
|
|
789
840
|
}
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
return void 0;
|
|
841
|
+
const subComponents = extractStringArray(aiProp, "subComponents");
|
|
842
|
+
if (subComponents.length > 0) {
|
|
843
|
+
ai.subComponents = subComponents;
|
|
794
844
|
}
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
categoryParts.pop();
|
|
845
|
+
const requiredChildren = extractStringArray(aiProp, "requiredChildren");
|
|
846
|
+
if (requiredChildren.length > 0) {
|
|
847
|
+
ai.requiredChildren = requiredChildren;
|
|
799
848
|
}
|
|
800
|
-
|
|
801
|
-
|
|
849
|
+
const commonPatterns = extractStringArray(aiProp, "commonPatterns");
|
|
850
|
+
if (commonPatterns.length > 0) {
|
|
851
|
+
ai.commonPatterns = commonPatterns;
|
|
802
852
|
}
|
|
803
|
-
|
|
853
|
+
if (Object.keys(ai).length > 0) {
|
|
854
|
+
return ai;
|
|
855
|
+
}
|
|
856
|
+
return void 0;
|
|
804
857
|
}
|
|
805
|
-
function
|
|
806
|
-
|
|
807
|
-
|
|
858
|
+
function extractContractMetadata(arg) {
|
|
859
|
+
const contractProp = findProperty(arg, "contract");
|
|
860
|
+
if (!contractProp || !ts.isObjectLiteralExpression(contractProp)) {
|
|
861
|
+
return void 0;
|
|
808
862
|
}
|
|
809
|
-
|
|
810
|
-
|
|
863
|
+
const contract = {};
|
|
864
|
+
const propsSummary = extractStringArray(contractProp, "propsSummary");
|
|
865
|
+
if (propsSummary.length > 0) {
|
|
866
|
+
contract.propsSummary = propsSummary;
|
|
811
867
|
}
|
|
812
|
-
|
|
813
|
-
|
|
868
|
+
const a11yRules = extractStringArray(contractProp, "a11yRules");
|
|
869
|
+
if (a11yRules.length > 0) {
|
|
870
|
+
contract.a11yRules = a11yRules;
|
|
814
871
|
}
|
|
815
|
-
if (
|
|
816
|
-
return
|
|
872
|
+
if (Object.keys(contract).length > 0) {
|
|
873
|
+
return contract;
|
|
817
874
|
}
|
|
818
|
-
|
|
819
|
-
|
|
875
|
+
return void 0;
|
|
876
|
+
}
|
|
877
|
+
function extractStringProperty(obj, name) {
|
|
878
|
+
const prop = findProperty(obj, name);
|
|
879
|
+
if (prop && ts.isStringLiteral(prop)) {
|
|
880
|
+
return prop.text;
|
|
820
881
|
}
|
|
821
|
-
if (
|
|
822
|
-
return
|
|
882
|
+
if (prop && ts.isNoSubstitutionTemplateLiteral(prop)) {
|
|
883
|
+
return prop.text;
|
|
823
884
|
}
|
|
824
|
-
|
|
825
|
-
|
|
885
|
+
return null;
|
|
886
|
+
}
|
|
887
|
+
function extractBooleanProperty(obj, name) {
|
|
888
|
+
const prop = findProperty(obj, name);
|
|
889
|
+
if (prop) {
|
|
890
|
+
if (prop.kind === ts.SyntaxKind.TrueKeyword) return true;
|
|
891
|
+
if (prop.kind === ts.SyntaxKind.FalseKeyword) return false;
|
|
826
892
|
}
|
|
827
|
-
return
|
|
893
|
+
return null;
|
|
828
894
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
format = "markdown",
|
|
834
|
-
compact = false,
|
|
835
|
-
include = { props: true, relations: true, code: false }
|
|
836
|
-
} = options;
|
|
837
|
-
if (format === "json") {
|
|
838
|
-
return generateJsonContext(registry, include, compact);
|
|
895
|
+
function extractStringArray(obj, name) {
|
|
896
|
+
const prop = findProperty(obj, name);
|
|
897
|
+
if (!prop || !ts.isArrayLiteralExpression(prop)) {
|
|
898
|
+
return [];
|
|
839
899
|
}
|
|
840
|
-
|
|
900
|
+
const result = [];
|
|
901
|
+
for (const element of prop.elements) {
|
|
902
|
+
if (ts.isStringLiteral(element)) {
|
|
903
|
+
result.push(element.text);
|
|
904
|
+
} else if (ts.isNoSubstitutionTemplateLiteral(element)) {
|
|
905
|
+
result.push(element.text);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return result;
|
|
841
909
|
}
|
|
842
|
-
function
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
const componentCount = componentNames.length;
|
|
846
|
-
lines.push("# Component Library Context");
|
|
847
|
-
lines.push(`Generated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]} | Components: ${componentCount}`);
|
|
848
|
-
lines.push("");
|
|
849
|
-
lines.push("## Quick Reference");
|
|
850
|
-
lines.push("");
|
|
851
|
-
lines.push("| Component | Path | Category | Status |");
|
|
852
|
-
lines.push("|-----------|------|----------|--------|");
|
|
853
|
-
for (const name of componentNames) {
|
|
854
|
-
const entry = registry.components[name];
|
|
855
|
-
const status = entry.status || "stable";
|
|
856
|
-
const category = entry.category || "-";
|
|
857
|
-
lines.push(`| ${name} | ${entry.path} | ${category} | ${status} |`);
|
|
910
|
+
function extractLiteralValue(expr) {
|
|
911
|
+
if (ts.isStringLiteral(expr)) {
|
|
912
|
+
return expr.text;
|
|
858
913
|
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
914
|
+
if (ts.isNumericLiteral(expr)) {
|
|
915
|
+
return Number(expr.text);
|
|
916
|
+
}
|
|
917
|
+
if (expr.kind === ts.SyntaxKind.TrueKeyword) {
|
|
918
|
+
return true;
|
|
919
|
+
}
|
|
920
|
+
if (expr.kind === ts.SyntaxKind.FalseKeyword) {
|
|
921
|
+
return false;
|
|
922
|
+
}
|
|
923
|
+
if (expr.kind === ts.SyntaxKind.NullKeyword) {
|
|
924
|
+
return null;
|
|
925
|
+
}
|
|
926
|
+
if (ts.isArrayLiteralExpression(expr)) {
|
|
927
|
+
return expr.elements.map(extractLiteralValue);
|
|
928
|
+
}
|
|
929
|
+
if (ts.isObjectLiteralExpression(expr)) {
|
|
930
|
+
const obj = {};
|
|
931
|
+
for (const prop of expr.properties) {
|
|
932
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
933
|
+
obj[prop.name.text] = extractLiteralValue(prop.initializer);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
return obj;
|
|
937
|
+
}
|
|
938
|
+
return void 0;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// src/core/previewLoader.ts
|
|
942
|
+
import { existsSync as existsSync3 } from "fs";
|
|
943
|
+
import { join as join2, resolve as resolve3 } from "path";
|
|
944
|
+
var PREVIEW_FILES = [
|
|
945
|
+
"preview.tsx",
|
|
946
|
+
"preview.ts",
|
|
947
|
+
"preview.jsx",
|
|
948
|
+
"preview.js"
|
|
949
|
+
];
|
|
950
|
+
function findPreviewConfigPath(storybookDir) {
|
|
951
|
+
for (const fileName of PREVIEW_FILES) {
|
|
952
|
+
const filePath = join2(storybookDir, fileName);
|
|
953
|
+
if (existsSync3(filePath)) {
|
|
954
|
+
return filePath;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
return null;
|
|
958
|
+
}
|
|
959
|
+
function findStorybookDir(projectRoot) {
|
|
960
|
+
const possiblePaths = [
|
|
961
|
+
join2(projectRoot, ".storybook"),
|
|
962
|
+
join2(projectRoot, "storybook")
|
|
963
|
+
];
|
|
964
|
+
for (const dir of possiblePaths) {
|
|
965
|
+
if (existsSync3(dir)) {
|
|
966
|
+
return dir;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
async function loadPreviewConfig(previewPath) {
|
|
972
|
+
try {
|
|
973
|
+
const fileUrl = new URL(`file://${resolve3(previewPath)}`);
|
|
974
|
+
const module = await import(fileUrl.href);
|
|
975
|
+
const config = {
|
|
976
|
+
decorators: module.decorators ?? module.default?.decorators ?? [],
|
|
977
|
+
parameters: module.parameters ?? module.default?.parameters ?? {},
|
|
978
|
+
globalTypes: module.globalTypes ?? module.default?.globalTypes ?? {},
|
|
979
|
+
args: module.args ?? module.default?.args ?? {},
|
|
980
|
+
argTypes: module.argTypes ?? module.default?.argTypes ?? {},
|
|
981
|
+
loaders: module.loaders ?? module.default?.loaders ?? []
|
|
866
982
|
};
|
|
983
|
+
return config;
|
|
984
|
+
} catch (error) {
|
|
985
|
+
console.warn(
|
|
986
|
+
`[Fragments] Failed to load preview config from ${previewPath}:`,
|
|
987
|
+
error instanceof Error ? error.message : error
|
|
988
|
+
);
|
|
989
|
+
return {};
|
|
867
990
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
991
|
+
}
|
|
992
|
+
async function autoLoadPreviewConfig(projectRoot) {
|
|
993
|
+
const storybookDir = findStorybookDir(projectRoot);
|
|
994
|
+
if (!storybookDir) {
|
|
995
|
+
return {};
|
|
996
|
+
}
|
|
997
|
+
const previewPath = findPreviewConfigPath(storybookDir);
|
|
998
|
+
if (!previewPath) {
|
|
999
|
+
return {};
|
|
1000
|
+
}
|
|
1001
|
+
return loadPreviewConfig(previewPath);
|
|
1002
|
+
}
|
|
1003
|
+
function generatePreviewModule(previewPath) {
|
|
1004
|
+
if (!previewPath) {
|
|
1005
|
+
return `
|
|
1006
|
+
export const decorators = [];
|
|
1007
|
+
export const parameters = {};
|
|
1008
|
+
export const globalTypes = {};
|
|
1009
|
+
export const args = {};
|
|
1010
|
+
export const argTypes = {};
|
|
1011
|
+
export const loaders = [];
|
|
1012
|
+
|
|
1013
|
+
export default {
|
|
1014
|
+
decorators,
|
|
1015
|
+
parameters,
|
|
1016
|
+
globalTypes,
|
|
1017
|
+
args,
|
|
1018
|
+
argTypes,
|
|
1019
|
+
loaders,
|
|
1020
|
+
};
|
|
1021
|
+
`;
|
|
1022
|
+
}
|
|
1023
|
+
return `
|
|
1024
|
+
import * as preview from "${previewPath}";
|
|
1025
|
+
|
|
1026
|
+
export const decorators = preview.decorators ?? preview.default?.decorators ?? [];
|
|
1027
|
+
export const parameters = preview.parameters ?? preview.default?.parameters ?? {};
|
|
1028
|
+
export const globalTypes = preview.globalTypes ?? preview.default?.globalTypes ?? {};
|
|
1029
|
+
export const args = preview.args ?? preview.default?.args ?? {};
|
|
1030
|
+
export const argTypes = preview.argTypes ?? preview.default?.argTypes ?? {};
|
|
1031
|
+
export const loaders = preview.loaders ?? preview.default?.loaders ?? [];
|
|
1032
|
+
|
|
1033
|
+
export default {
|
|
1034
|
+
decorators,
|
|
1035
|
+
parameters,
|
|
1036
|
+
globalTypes,
|
|
1037
|
+
args,
|
|
1038
|
+
argTypes,
|
|
1039
|
+
loaders,
|
|
1040
|
+
};
|
|
1041
|
+
`;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// src/core/generators/typescript-extractor.ts
|
|
1045
|
+
import ts2 from "typescript";
|
|
1046
|
+
import { readFileSync } from "fs";
|
|
1047
|
+
function extractPropsFromFile(filePath) {
|
|
1048
|
+
const sourceText = readFileSync(filePath, "utf-8");
|
|
1049
|
+
return extractPropsFromSource(sourceText, filePath);
|
|
1050
|
+
}
|
|
1051
|
+
function extractPropsFromSource(sourceText, fileName = "component.tsx") {
|
|
1052
|
+
const sourceFile = ts2.createSourceFile(
|
|
1053
|
+
fileName,
|
|
1054
|
+
sourceText,
|
|
1055
|
+
ts2.ScriptTarget.Latest,
|
|
1056
|
+
true,
|
|
1057
|
+
fileName.endsWith(".tsx") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS
|
|
1058
|
+
);
|
|
1059
|
+
const result = {
|
|
1060
|
+
componentName: "",
|
|
1061
|
+
props: {},
|
|
1062
|
+
exports: [],
|
|
1063
|
+
imports: []
|
|
1064
|
+
};
|
|
1065
|
+
const propsInterfaces = /* @__PURE__ */ new Map();
|
|
1066
|
+
const componentExports = [];
|
|
1067
|
+
const defaultExports = /* @__PURE__ */ new Set();
|
|
1068
|
+
const importedModules = [];
|
|
1069
|
+
ts2.forEachChild(sourceFile, (node) => {
|
|
1070
|
+
if (ts2.isImportDeclaration(node)) {
|
|
1071
|
+
const moduleSpecifier = node.moduleSpecifier;
|
|
1072
|
+
if (ts2.isStringLiteral(moduleSpecifier)) {
|
|
1073
|
+
importedModules.push(moduleSpecifier.text);
|
|
1074
|
+
}
|
|
877
1075
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1076
|
+
if (ts2.isInterfaceDeclaration(node)) {
|
|
1077
|
+
const name = node.name.text;
|
|
1078
|
+
if (name.endsWith("Props")) {
|
|
1079
|
+
propsInterfaces.set(name, node);
|
|
1080
|
+
}
|
|
883
1081
|
}
|
|
884
|
-
if (
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
lines.push(`- ${when}`);
|
|
889
|
-
}
|
|
890
|
-
lines.push("");
|
|
1082
|
+
if (ts2.isTypeAliasDeclaration(node)) {
|
|
1083
|
+
const name = node.name.text;
|
|
1084
|
+
if (name.endsWith("Props")) {
|
|
1085
|
+
propsInterfaces.set(name, node);
|
|
891
1086
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
lines.push(`- ${item.text} \u2192 use **${item.instead}**`);
|
|
905
|
-
}
|
|
906
|
-
} else {
|
|
907
|
-
lines.push(`- ${item.text}`);
|
|
908
|
-
}
|
|
909
|
-
}
|
|
1087
|
+
}
|
|
1088
|
+
if (ts2.isFunctionDeclaration(node) && node.name) {
|
|
1089
|
+
const hasExport = node.modifiers?.some(
|
|
1090
|
+
(m) => m.kind === ts2.SyntaxKind.ExportKeyword
|
|
1091
|
+
);
|
|
1092
|
+
const hasDefault = node.modifiers?.some(
|
|
1093
|
+
(m) => m.kind === ts2.SyntaxKind.DefaultKeyword
|
|
1094
|
+
);
|
|
1095
|
+
if (hasExport) {
|
|
1096
|
+
componentExports.push(node.name.text);
|
|
1097
|
+
if (hasDefault) {
|
|
1098
|
+
defaultExports.add(node.name.text);
|
|
910
1099
|
}
|
|
911
|
-
lines.push("");
|
|
912
1100
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1101
|
+
}
|
|
1102
|
+
if (ts2.isVariableStatement(node)) {
|
|
1103
|
+
const hasExport = node.modifiers?.some(
|
|
1104
|
+
(m) => m.kind === ts2.SyntaxKind.ExportKeyword
|
|
1105
|
+
);
|
|
1106
|
+
if (hasExport) {
|
|
1107
|
+
for (const decl of node.declarationList.declarations) {
|
|
1108
|
+
if (ts2.isIdentifier(decl.name)) {
|
|
1109
|
+
componentExports.push(decl.name.text);
|
|
919
1110
|
}
|
|
920
|
-
lines.push("```tsx");
|
|
921
|
-
lines.push(pattern.code);
|
|
922
|
-
lines.push("```");
|
|
923
|
-
lines.push("");
|
|
924
1111
|
}
|
|
925
1112
|
}
|
|
926
1113
|
}
|
|
927
|
-
if (
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
for (const [propName, prop] of Object.entries(entry.props)) {
|
|
932
|
-
const type = formatPropType(prop);
|
|
933
|
-
const defaultVal = prop.default !== void 0 ? `\`${JSON.stringify(prop.default)}\`` : "-";
|
|
934
|
-
const desc = prop.description || "-";
|
|
935
|
-
const required = prop.required ? " (required)" : "";
|
|
936
|
-
lines.push(`| ${propName}${required} | ${type} | ${defaultVal} | ${desc} |`);
|
|
937
|
-
}
|
|
938
|
-
lines.push("");
|
|
939
|
-
}
|
|
940
|
-
if (fragment?.accessibility) {
|
|
941
|
-
lines.push("### Accessibility");
|
|
942
|
-
if (fragment.accessibility.role) {
|
|
943
|
-
lines.push(`**Role:** ${fragment.accessibility.role}`);
|
|
944
|
-
}
|
|
945
|
-
if (fragment.accessibility.requirements && fragment.accessibility.requirements.length > 0) {
|
|
946
|
-
for (const req of fragment.accessibility.requirements) {
|
|
947
|
-
lines.push(`- ${req}`);
|
|
1114
|
+
if (ts2.isExportDeclaration(node) && node.exportClause) {
|
|
1115
|
+
if (ts2.isNamedExports(node.exportClause)) {
|
|
1116
|
+
for (const element of node.exportClause.elements) {
|
|
1117
|
+
componentExports.push(element.name.text);
|
|
948
1118
|
}
|
|
949
1119
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
result.exports = componentExports;
|
|
1123
|
+
result.imports = importedModules;
|
|
1124
|
+
const mainComponent = componentExports.find(
|
|
1125
|
+
(name) => /^[A-Z]/.test(name) && !name.endsWith("Props")
|
|
1126
|
+
);
|
|
1127
|
+
if (!mainComponent) {
|
|
1128
|
+
return null;
|
|
1129
|
+
}
|
|
1130
|
+
result.componentName = mainComponent;
|
|
1131
|
+
result.isDefaultExport = defaultExports.has(mainComponent);
|
|
1132
|
+
const propsInterfaceName = `${mainComponent}Props`;
|
|
1133
|
+
const propsInterface = propsInterfaces.get(propsInterfaceName);
|
|
1134
|
+
if (propsInterface) {
|
|
1135
|
+
result.propsInterfaceName = propsInterfaceName;
|
|
1136
|
+
result.props = extractPropsFromInterface(propsInterface, sourceFile);
|
|
1137
|
+
}
|
|
1138
|
+
return result;
|
|
1139
|
+
}
|
|
1140
|
+
function extractPropsFromInterface(node, sourceFile) {
|
|
1141
|
+
const props = {};
|
|
1142
|
+
if (ts2.isInterfaceDeclaration(node)) {
|
|
1143
|
+
for (const member of node.members) {
|
|
1144
|
+
if (ts2.isPropertySignature(member) && member.name) {
|
|
1145
|
+
const propName = member.name.getText(sourceFile);
|
|
1146
|
+
const prop = extractPropFromMember(member, sourceFile);
|
|
1147
|
+
if (prop) {
|
|
1148
|
+
props[propName] = prop;
|
|
955
1149
|
}
|
|
956
1150
|
}
|
|
957
|
-
lines.push("");
|
|
958
1151
|
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
return path ? `${comp} (\`${path}\`)` : comp;
|
|
972
|
-
});
|
|
973
|
-
lines.push(`- **Composed with:** ${resolved.join(", ")}`);
|
|
974
|
-
}
|
|
975
|
-
if (fragment.related.usedIn && fragment.related.usedIn.length > 0) {
|
|
976
|
-
const resolved = fragment.related.usedIn.map((comp) => {
|
|
977
|
-
const path = resolveComponentPath(comp, registry);
|
|
978
|
-
return path ? `${comp} (\`${path}\`)` : comp;
|
|
979
|
-
});
|
|
980
|
-
lines.push(`- **Used in:** ${resolved.join(", ")}`);
|
|
1152
|
+
}
|
|
1153
|
+
if (ts2.isTypeAliasDeclaration(node)) {
|
|
1154
|
+
const typeNode = node.type;
|
|
1155
|
+
if (ts2.isTypeLiteralNode(typeNode)) {
|
|
1156
|
+
for (const member of typeNode.members) {
|
|
1157
|
+
if (ts2.isPropertySignature(member) && member.name) {
|
|
1158
|
+
const propName = member.name.getText(sourceFile);
|
|
1159
|
+
const prop = extractPropFromMember(member, sourceFile);
|
|
1160
|
+
if (prop) {
|
|
1161
|
+
props[propName] = prop;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
981
1164
|
}
|
|
982
|
-
lines.push("");
|
|
983
1165
|
}
|
|
984
|
-
lines.push("---");
|
|
985
|
-
lines.push("");
|
|
986
1166
|
}
|
|
987
|
-
|
|
988
|
-
return {
|
|
989
|
-
content,
|
|
990
|
-
tokenEstimate: estimateTokens(content),
|
|
991
|
-
componentCount
|
|
992
|
-
};
|
|
1167
|
+
return props;
|
|
993
1168
|
}
|
|
994
|
-
function
|
|
995
|
-
const
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
};
|
|
1004
|
-
if (entry.category) component.category = entry.category;
|
|
1005
|
-
if (entry.status) component.status = entry.status;
|
|
1006
|
-
if (entry.description || fragment?.description) {
|
|
1007
|
-
component.description = entry.description || fragment?.description;
|
|
1169
|
+
function extractPropFromMember(member, sourceFile) {
|
|
1170
|
+
const entry = {};
|
|
1171
|
+
entry.required = !member.questionToken;
|
|
1172
|
+
if (member.type) {
|
|
1173
|
+
const typeInfo = parseTypeNode(member.type, sourceFile);
|
|
1174
|
+
entry.type = typeInfo.type;
|
|
1175
|
+
entry.typeKind = typeInfo.typeKind;
|
|
1176
|
+
if (typeInfo.options) {
|
|
1177
|
+
entry.options = typeInfo.options;
|
|
1008
1178
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1179
|
+
}
|
|
1180
|
+
const jsDocComment = getJSDocComment(member);
|
|
1181
|
+
if (jsDocComment) {
|
|
1182
|
+
entry.description = jsDocComment;
|
|
1183
|
+
}
|
|
1184
|
+
const defaultValue = getJSDocDefault(member);
|
|
1185
|
+
if (defaultValue !== void 0) {
|
|
1186
|
+
entry.default = defaultValue;
|
|
1187
|
+
}
|
|
1188
|
+
return entry;
|
|
1189
|
+
}
|
|
1190
|
+
function parseTypeNode(typeNode, sourceFile) {
|
|
1191
|
+
if (typeNode.kind === ts2.SyntaxKind.StringKeyword) {
|
|
1192
|
+
return { type: "string", typeKind: "string" };
|
|
1193
|
+
}
|
|
1194
|
+
if (typeNode.kind === ts2.SyntaxKind.NumberKeyword) {
|
|
1195
|
+
return { type: "number", typeKind: "number" };
|
|
1196
|
+
}
|
|
1197
|
+
if (typeNode.kind === ts2.SyntaxKind.BooleanKeyword) {
|
|
1198
|
+
return { type: "boolean", typeKind: "boolean" };
|
|
1199
|
+
}
|
|
1200
|
+
if (ts2.isUnionTypeNode(typeNode)) {
|
|
1201
|
+
const options = [];
|
|
1202
|
+
let allLiterals = true;
|
|
1203
|
+
for (const subType of typeNode.types) {
|
|
1204
|
+
if (ts2.isLiteralTypeNode(subType)) {
|
|
1205
|
+
if (ts2.isStringLiteral(subType.literal)) {
|
|
1206
|
+
options.push(subType.literal.text);
|
|
1207
|
+
} else if (subType.literal.kind === ts2.SyntaxKind.TrueKeyword) {
|
|
1208
|
+
options.push("true");
|
|
1209
|
+
} else if (subType.literal.kind === ts2.SyntaxKind.FalseKeyword) {
|
|
1210
|
+
options.push("false");
|
|
1211
|
+
} else {
|
|
1212
|
+
allLiterals = false;
|
|
1213
|
+
}
|
|
1214
|
+
} else {
|
|
1215
|
+
allLiterals = false;
|
|
1028
1216
|
}
|
|
1029
1217
|
}
|
|
1030
|
-
if (
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
if (prop.required) component.props[propName].required = true;
|
|
1037
|
-
if (prop.default !== void 0) component.props[propName].default = prop.default;
|
|
1038
|
-
if (prop.description) component.props[propName].description = prop.description;
|
|
1039
|
-
}
|
|
1218
|
+
if (allLiterals && options.length > 0) {
|
|
1219
|
+
return {
|
|
1220
|
+
type: options.map((o) => `"${o}"`).join(" | "),
|
|
1221
|
+
typeKind: "enum",
|
|
1222
|
+
options
|
|
1223
|
+
};
|
|
1040
1224
|
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1225
|
+
return {
|
|
1226
|
+
type: typeNode.getText(sourceFile),
|
|
1227
|
+
typeKind: "union"
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
if (ts2.isFunctionTypeNode(typeNode)) {
|
|
1231
|
+
return {
|
|
1232
|
+
type: typeNode.getText(sourceFile),
|
|
1233
|
+
typeKind: "function"
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
if (ts2.isArrayTypeNode(typeNode)) {
|
|
1237
|
+
return {
|
|
1238
|
+
type: typeNode.getText(sourceFile),
|
|
1239
|
+
typeKind: "array"
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
if (ts2.isTypeReferenceNode(typeNode)) {
|
|
1243
|
+
const typeName = typeNode.typeName.getText(sourceFile);
|
|
1244
|
+
if (typeName === "ReactNode" || typeName === "React.ReactNode") {
|
|
1245
|
+
return { type: "ReactNode", typeKind: "node" };
|
|
1061
1246
|
}
|
|
1062
|
-
|
|
1247
|
+
if (typeName === "ReactElement" || typeName === "React.ReactElement") {
|
|
1248
|
+
return { type: "ReactElement", typeKind: "element" };
|
|
1249
|
+
}
|
|
1250
|
+
if (typeName === "JSX.Element") {
|
|
1251
|
+
return { type: "JSX.Element", typeKind: "element" };
|
|
1252
|
+
}
|
|
1253
|
+
return {
|
|
1254
|
+
type: typeNode.getText(sourceFile),
|
|
1255
|
+
typeKind: "object"
|
|
1256
|
+
};
|
|
1063
1257
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
components
|
|
1070
|
-
};
|
|
1071
|
-
const content = JSON.stringify(output, null, 2);
|
|
1072
|
-
return {
|
|
1073
|
-
content,
|
|
1074
|
-
tokenEstimate: estimateTokens(content),
|
|
1075
|
-
componentCount
|
|
1076
|
-
};
|
|
1077
|
-
}
|
|
1078
|
-
function resolveComponentPath(componentName, registry) {
|
|
1079
|
-
return registry.components[componentName]?.path;
|
|
1080
|
-
}
|
|
1081
|
-
function formatPropType(prop) {
|
|
1082
|
-
if (prop.options && prop.options.length > 0) {
|
|
1083
|
-
return prop.options.map((o) => `"${o}"`).join(" \\| ");
|
|
1258
|
+
if (ts2.isTypeLiteralNode(typeNode)) {
|
|
1259
|
+
return {
|
|
1260
|
+
type: typeNode.getText(sourceFile),
|
|
1261
|
+
typeKind: "object"
|
|
1262
|
+
};
|
|
1084
1263
|
}
|
|
1085
|
-
return prop.type || "unknown";
|
|
1086
|
-
}
|
|
1087
|
-
function estimateTokens(text) {
|
|
1088
|
-
return Math.ceil(text.length / 4);
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
// src/core/loader.ts
|
|
1092
|
-
import { unlink } from "fs/promises";
|
|
1093
|
-
import { dirname as dirname4, basename as basename3, join as join2 } from "path";
|
|
1094
|
-
import { pathToFileURL } from "url";
|
|
1095
|
-
import { build } from "esbuild";
|
|
1096
|
-
var DEFINE_FRAGMENT_SHIM = `
|
|
1097
|
-
export function defineFragment(def) {
|
|
1098
|
-
return def;
|
|
1099
|
-
}
|
|
1100
|
-
export function defineBlock(def) {
|
|
1101
|
-
return def;
|
|
1102
|
-
}
|
|
1103
|
-
export function defineRecipe(def) {
|
|
1104
|
-
return def;
|
|
1105
|
-
}
|
|
1106
|
-
`;
|
|
1107
|
-
function createFragmentsCoreShimPlugin() {
|
|
1108
1264
|
return {
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
build2.onResolve({ filter: /^@fragments-sdk\/cli\/core$/ }, (args) => {
|
|
1112
|
-
return {
|
|
1113
|
-
path: args.path,
|
|
1114
|
-
namespace: BRAND.vitePluginNamespace
|
|
1115
|
-
};
|
|
1116
|
-
});
|
|
1117
|
-
build2.onLoad({ filter: /.*/, namespace: BRAND.vitePluginNamespace }, () => {
|
|
1118
|
-
return {
|
|
1119
|
-
contents: DEFINE_FRAGMENT_SHIM,
|
|
1120
|
-
loader: "js"
|
|
1121
|
-
};
|
|
1122
|
-
});
|
|
1123
|
-
}
|
|
1265
|
+
type: typeNode.getText(sourceFile),
|
|
1266
|
+
typeKind: "unknown"
|
|
1124
1267
|
};
|
|
1125
1268
|
}
|
|
1126
|
-
|
|
1127
|
-
const
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
const
|
|
1131
|
-
|
|
1132
|
-
return
|
|
1133
|
-
};
|
|
1134
|
-
if (isFragmentLike(value)) {
|
|
1135
|
-
return value;
|
|
1269
|
+
function getJSDocComment(node) {
|
|
1270
|
+
const jsDocTags = ts2.getJSDocTags(node);
|
|
1271
|
+
const jsDoc = node.jsDoc;
|
|
1272
|
+
if (jsDoc && jsDoc.length > 0) {
|
|
1273
|
+
const comment = jsDoc[0].comment;
|
|
1274
|
+
if (typeof comment === "string") {
|
|
1275
|
+
return comment;
|
|
1136
1276
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
return first;
|
|
1277
|
+
if (Array.isArray(comment)) {
|
|
1278
|
+
return comment.map((c) => typeof c === "string" ? c : c.text).join("");
|
|
1140
1279
|
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
const tempFile = join2(sourceDir, `.${baseName}.fragments-temp-${Date.now()}.mjs`);
|
|
1157
|
-
try {
|
|
1158
|
-
await build({
|
|
1159
|
-
entryPoints: [absolutePath],
|
|
1160
|
-
outfile: tempFile,
|
|
1161
|
-
bundle: true,
|
|
1162
|
-
format: "esm",
|
|
1163
|
-
target: "es2022",
|
|
1164
|
-
jsx: "automatic",
|
|
1165
|
-
platform: "node",
|
|
1166
|
-
plugins: [createFragmentsCoreShimPlugin()],
|
|
1167
|
-
// Externalize all node_modules - we only need fragment metadata, not component code
|
|
1168
|
-
packages: "external",
|
|
1169
|
-
// Also explicitly list patterns for nested imports
|
|
1170
|
-
external: [
|
|
1171
|
-
// React and its subpaths
|
|
1172
|
-
"react",
|
|
1173
|
-
"react-dom",
|
|
1174
|
-
"react/*",
|
|
1175
|
-
"react-dom/*"
|
|
1176
|
-
],
|
|
1177
|
-
// Don't emit sourcemaps for temp files
|
|
1178
|
-
sourcemap: false,
|
|
1179
|
-
// Suppress warnings
|
|
1180
|
-
logLevel: "silent",
|
|
1181
|
-
// Use loader to ignore style imports
|
|
1182
|
-
loader: {
|
|
1183
|
-
".scss": "empty",
|
|
1184
|
-
".css": "empty",
|
|
1185
|
-
".svg": "empty",
|
|
1186
|
-
".png": "empty",
|
|
1187
|
-
".jpg": "empty",
|
|
1188
|
-
".jpeg": "empty",
|
|
1189
|
-
".gif": "empty",
|
|
1190
|
-
".woff": "empty",
|
|
1191
|
-
".woff2": "empty",
|
|
1192
|
-
".ttf": "empty",
|
|
1193
|
-
".eot": "empty"
|
|
1194
|
-
}
|
|
1195
|
-
});
|
|
1196
|
-
const fileUrl = pathToFileURL(tempFile).href;
|
|
1197
|
-
const module = await import(fileUrl);
|
|
1198
|
-
return unwrapFragmentExport(module.default ?? null);
|
|
1199
|
-
} finally {
|
|
1200
|
-
try {
|
|
1201
|
-
await unlink(tempFile);
|
|
1202
|
-
} catch {
|
|
1280
|
+
}
|
|
1281
|
+
return void 0;
|
|
1282
|
+
}
|
|
1283
|
+
function getJSDocDefault(node) {
|
|
1284
|
+
const jsDocTags = ts2.getJSDocTags(node);
|
|
1285
|
+
for (const tag of jsDocTags) {
|
|
1286
|
+
if (tag.tagName.text === "default") {
|
|
1287
|
+
const comment = tag.comment;
|
|
1288
|
+
if (typeof comment === "string") {
|
|
1289
|
+
try {
|
|
1290
|
+
return JSON.parse(comment);
|
|
1291
|
+
} catch {
|
|
1292
|
+
return comment;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1203
1295
|
}
|
|
1204
1296
|
}
|
|
1297
|
+
return void 0;
|
|
1205
1298
|
}
|
|
1206
1299
|
|
|
1207
|
-
// src/core/
|
|
1208
|
-
import
|
|
1209
|
-
|
|
1300
|
+
// src/core/generators/registry.ts
|
|
1301
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1302
|
+
import { relative, dirname as dirname4, basename as basename3, join as join3 } from "path";
|
|
1303
|
+
import fg2 from "fast-glob";
|
|
1304
|
+
async function generateRegistry(options) {
|
|
1305
|
+
const {
|
|
1306
|
+
projectRoot,
|
|
1307
|
+
componentPatterns = ["src/**/*.tsx", "src/**/*.ts"],
|
|
1308
|
+
storyPatterns = ["src/**/*.stories.tsx", "src/**/*.stories.ts"],
|
|
1309
|
+
fragmentsDir = join3(projectRoot, BRAND.dataDir),
|
|
1310
|
+
registryOptions = {}
|
|
1311
|
+
} = options;
|
|
1312
|
+
const {
|
|
1313
|
+
requireStory = false,
|
|
1314
|
+
publicOnly = false,
|
|
1315
|
+
categoryDepth = 1,
|
|
1316
|
+
includeProps = false,
|
|
1317
|
+
embedFragments = false
|
|
1318
|
+
} = registryOptions;
|
|
1319
|
+
const errors = [];
|
|
1210
1320
|
const warnings = [];
|
|
1211
|
-
const
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
)
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
warnings.push("No defineFragment() call found");
|
|
1222
|
-
return {
|
|
1223
|
-
componentImport: null,
|
|
1224
|
-
componentName: null,
|
|
1225
|
-
meta: {},
|
|
1226
|
-
usage: { when: [], whenNot: [] },
|
|
1227
|
-
props: {},
|
|
1228
|
-
variants: [],
|
|
1229
|
-
relations: [],
|
|
1230
|
-
warnings
|
|
1231
|
-
};
|
|
1232
|
-
}
|
|
1233
|
-
const arg = defineFragmentCall.arguments[0];
|
|
1234
|
-
if (!arg || !ts2.isObjectLiteralExpression(arg)) {
|
|
1235
|
-
warnings.push("defineFragment() argument is not an object literal");
|
|
1236
|
-
return {
|
|
1237
|
-
componentImport: null,
|
|
1238
|
-
componentName: null,
|
|
1239
|
-
meta: {},
|
|
1240
|
-
usage: { when: [], whenNot: [] },
|
|
1241
|
-
props: {},
|
|
1242
|
-
variants: [],
|
|
1243
|
-
relations: [],
|
|
1244
|
-
warnings
|
|
1245
|
-
};
|
|
1321
|
+
const storyFiles = await fg2(storyPatterns, {
|
|
1322
|
+
cwd: projectRoot,
|
|
1323
|
+
ignore: ["**/node_modules/**"],
|
|
1324
|
+
absolute: true
|
|
1325
|
+
});
|
|
1326
|
+
const storyMap = /* @__PURE__ */ new Map();
|
|
1327
|
+
for (const storyPath of storyFiles) {
|
|
1328
|
+
const storyDir = dirname4(storyPath);
|
|
1329
|
+
const storyBase = basename3(storyPath).replace(/\.stories\.(tsx?|jsx?)$/, "");
|
|
1330
|
+
storyMap.set(`${storyDir}/${storyBase}`, storyPath);
|
|
1246
1331
|
}
|
|
1247
|
-
const
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1332
|
+
const componentFiles = await fg2(componentPatterns, {
|
|
1333
|
+
cwd: projectRoot,
|
|
1334
|
+
ignore: [
|
|
1335
|
+
"**/node_modules/**",
|
|
1336
|
+
"**/*.stories.*",
|
|
1337
|
+
"**/*.test.*",
|
|
1338
|
+
"**/*.spec.*",
|
|
1339
|
+
"**/*.d.ts"
|
|
1340
|
+
],
|
|
1341
|
+
absolute: true
|
|
1342
|
+
});
|
|
1343
|
+
const fragmentPattern = join3(
|
|
1344
|
+
BRAND.dataDir,
|
|
1345
|
+
BRAND.componentsDir,
|
|
1346
|
+
`*${BRAND.fileExtension}`
|
|
1347
|
+
);
|
|
1348
|
+
const fragmentFiles = await fg2(fragmentPattern, {
|
|
1349
|
+
cwd: projectRoot,
|
|
1350
|
+
absolute: true
|
|
1351
|
+
});
|
|
1352
|
+
const fragmentMap = /* @__PURE__ */ new Map();
|
|
1353
|
+
for (const fragmentPath of fragmentFiles) {
|
|
1354
|
+
const fragmentName = basename3(fragmentPath).replace(BRAND.fileExtension, "");
|
|
1355
|
+
try {
|
|
1356
|
+
const content = readFileSync2(fragmentPath, "utf-8");
|
|
1357
|
+
const fragment = JSON.parse(content);
|
|
1358
|
+
fragmentMap.set(fragmentName, {
|
|
1359
|
+
path: relative(projectRoot, fragmentPath),
|
|
1360
|
+
fragment
|
|
1361
|
+
});
|
|
1362
|
+
} catch (e) {
|
|
1363
|
+
errors.push({
|
|
1364
|
+
file: fragmentPath,
|
|
1365
|
+
error: `Failed to parse fragment: ${e instanceof Error ? e.message : String(e)}`
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1253
1368
|
}
|
|
1254
|
-
const
|
|
1255
|
-
const
|
|
1256
|
-
const
|
|
1257
|
-
const
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1369
|
+
const components = {};
|
|
1370
|
+
const indexComponents = {};
|
|
1371
|
+
const categories = {};
|
|
1372
|
+
for (const filePath of componentFiles) {
|
|
1373
|
+
try {
|
|
1374
|
+
const extracted = extractPropsFromFile(filePath);
|
|
1375
|
+
if (!extracted || !extracted.componentName) {
|
|
1376
|
+
continue;
|
|
1377
|
+
}
|
|
1378
|
+
const componentName = extracted.componentName;
|
|
1379
|
+
const relativePath = relative(projectRoot, filePath);
|
|
1380
|
+
if (publicOnly && !extracted.exports.includes(componentName)) {
|
|
1381
|
+
continue;
|
|
1382
|
+
}
|
|
1383
|
+
const componentDir = dirname4(filePath);
|
|
1384
|
+
const baseNameWithoutExt = basename3(filePath).replace(/\.(tsx?|jsx?)$/, "");
|
|
1385
|
+
const storyPath = storyMap.get(`${componentDir}/${baseNameWithoutExt}`);
|
|
1386
|
+
if (requireStory && !storyPath) {
|
|
1387
|
+
continue;
|
|
1388
|
+
}
|
|
1389
|
+
const fragmentData = fragmentMap.get(componentName);
|
|
1390
|
+
const category = fragmentData?.fragment?.meta?.status ? void 0 : getCategoryFromPath(relativePath, categoryDepth);
|
|
1391
|
+
const hasEnrichment = fragmentData ? hasRealEnrichment(fragmentData.fragment) : false;
|
|
1392
|
+
const entry = {
|
|
1393
|
+
path: relativePath
|
|
1394
|
+
};
|
|
1395
|
+
if (storyPath) {
|
|
1396
|
+
entry.storyPath = relative(projectRoot, storyPath);
|
|
1397
|
+
}
|
|
1398
|
+
if (fragmentData) {
|
|
1399
|
+
entry.fragmentPath = fragmentData.path;
|
|
1400
|
+
if (hasEnrichment) {
|
|
1401
|
+
entry.hasEnrichment = true;
|
|
1402
|
+
}
|
|
1403
|
+
if (fragmentData.fragment.description) {
|
|
1404
|
+
entry.description = fragmentData.fragment.description;
|
|
1405
|
+
}
|
|
1406
|
+
if (fragmentData.fragment.meta?.status) {
|
|
1407
|
+
entry.status = fragmentData.fragment.meta.status;
|
|
1408
|
+
}
|
|
1409
|
+
if (embedFragments) {
|
|
1410
|
+
entry.fragment = fragmentData.fragment;
|
|
1292
1411
|
}
|
|
1293
1412
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
function visit(node) {
|
|
1301
|
-
if (ts2.isCallExpression(node)) {
|
|
1302
|
-
const expression = node.expression;
|
|
1303
|
-
if (ts2.isIdentifier(expression) && expression.text === "defineFragment") {
|
|
1304
|
-
result = node;
|
|
1305
|
-
return;
|
|
1413
|
+
if (category) {
|
|
1414
|
+
entry.category = category;
|
|
1415
|
+
if (!categories[category]) {
|
|
1416
|
+
categories[category] = [];
|
|
1417
|
+
}
|
|
1418
|
+
categories[category].push(componentName);
|
|
1306
1419
|
}
|
|
1420
|
+
if (includeProps && extracted.props) {
|
|
1421
|
+
entry.props = extracted.props;
|
|
1422
|
+
}
|
|
1423
|
+
if (extracted.exports.length > 1) {
|
|
1424
|
+
entry.exports = extracted.exports;
|
|
1425
|
+
}
|
|
1426
|
+
components[componentName] = entry;
|
|
1427
|
+
indexComponents[componentName] = relativePath;
|
|
1428
|
+
} catch (e) {
|
|
1429
|
+
errors.push({
|
|
1430
|
+
file: filePath,
|
|
1431
|
+
error: `Failed to extract component: ${e instanceof Error ? e.message : String(e)}`
|
|
1432
|
+
});
|
|
1307
1433
|
}
|
|
1308
|
-
ts2.forEachChild(node, visit);
|
|
1309
1434
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1435
|
+
const componentCount = Object.keys(components).length;
|
|
1436
|
+
const registry = {
|
|
1437
|
+
$schema: "https://fragments.dev/schema/registry-v1.json",
|
|
1438
|
+
version: "1.0",
|
|
1439
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1440
|
+
componentCount,
|
|
1441
|
+
components,
|
|
1442
|
+
categories
|
|
1443
|
+
};
|
|
1444
|
+
const index = {
|
|
1445
|
+
version: "1.0",
|
|
1446
|
+
generatedAt: registry.generatedAt,
|
|
1447
|
+
components: indexComponents,
|
|
1448
|
+
categories
|
|
1449
|
+
};
|
|
1450
|
+
return { registry, index, errors, warnings };
|
|
1312
1451
|
}
|
|
1313
|
-
function
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1452
|
+
function getCategoryFromPath(relativePath, depth = 1) {
|
|
1453
|
+
const parts = relativePath.split("/");
|
|
1454
|
+
const componentsIndex = parts.findIndex((p) => p === "components");
|
|
1455
|
+
if (componentsIndex === -1) {
|
|
1456
|
+
return void 0;
|
|
1457
|
+
}
|
|
1458
|
+
const afterComponents = parts.slice(componentsIndex + 1);
|
|
1459
|
+
if (afterComponents.length <= 1) {
|
|
1460
|
+
return void 0;
|
|
1461
|
+
}
|
|
1462
|
+
const folderName = afterComponents[0];
|
|
1463
|
+
const fileName = afterComponents[afterComponents.length - 1].replace(/\.(tsx?|jsx?)$/, "");
|
|
1464
|
+
if (afterComponents.length === 2 && folderName === fileName) {
|
|
1465
|
+
return void 0;
|
|
1466
|
+
}
|
|
1467
|
+
const categoryParts = afterComponents.slice(0, Math.min(depth, afterComponents.length - 1));
|
|
1468
|
+
const lastCategoryPart = categoryParts[categoryParts.length - 1];
|
|
1469
|
+
if (lastCategoryPart === fileName) {
|
|
1470
|
+
categoryParts.pop();
|
|
1321
1471
|
}
|
|
1322
|
-
|
|
1472
|
+
if (categoryParts.length === 0) {
|
|
1473
|
+
return void 0;
|
|
1474
|
+
}
|
|
1475
|
+
return categoryParts.join("/");
|
|
1323
1476
|
}
|
|
1324
|
-
function
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
warnings.push("No meta object found");
|
|
1328
|
-
return {};
|
|
1477
|
+
function hasRealEnrichment(fragment) {
|
|
1478
|
+
if (fragment.description && fragment.description.length > 20) {
|
|
1479
|
+
return true;
|
|
1329
1480
|
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
if (name) meta.name = name;
|
|
1333
|
-
const description = extractStringProperty(metaProp, "description");
|
|
1334
|
-
if (description) meta.description = description;
|
|
1335
|
-
const category = extractStringProperty(metaProp, "category");
|
|
1336
|
-
if (category) meta.category = category;
|
|
1337
|
-
const status = extractStringProperty(metaProp, "status");
|
|
1338
|
-
if (status) meta.status = status;
|
|
1339
|
-
const since = extractStringProperty(metaProp, "since");
|
|
1340
|
-
if (since) meta.since = since;
|
|
1341
|
-
const figma = extractStringProperty(metaProp, "figma");
|
|
1342
|
-
if (figma) meta.figma = figma;
|
|
1343
|
-
const tags = extractStringArray(metaProp, "tags");
|
|
1344
|
-
if (tags.length > 0) meta.tags = tags;
|
|
1345
|
-
const depsProp = findProperty(metaProp, "dependencies");
|
|
1346
|
-
if (depsProp && ts2.isArrayLiteralExpression(depsProp)) {
|
|
1347
|
-
const deps = extractLiteralValue(depsProp);
|
|
1348
|
-
if (Array.isArray(deps) && deps.length > 0) {
|
|
1349
|
-
meta.dependencies = deps;
|
|
1350
|
-
}
|
|
1481
|
+
if (fragment.usage?.when && fragment.usage.when.length > 0) {
|
|
1482
|
+
return true;
|
|
1351
1483
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
function extractUsage(arg, warnings) {
|
|
1355
|
-
const usageProp = findProperty(arg, "usage");
|
|
1356
|
-
if (!usageProp || !ts2.isObjectLiteralExpression(usageProp)) {
|
|
1357
|
-
return { when: [], whenNot: [] };
|
|
1484
|
+
if (fragment.usage?.doNot && fragment.usage.doNot.length > 0) {
|
|
1485
|
+
return true;
|
|
1358
1486
|
}
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
whenNot: extractStringArray(usageProp, "whenNot"),
|
|
1362
|
-
guidelines: extractStringArray(usageProp, "guidelines"),
|
|
1363
|
-
accessibility: extractStringArray(usageProp, "accessibility")
|
|
1364
|
-
};
|
|
1365
|
-
}
|
|
1366
|
-
function extractProps(arg, warnings) {
|
|
1367
|
-
const propsProp = findProperty(arg, "props");
|
|
1368
|
-
if (!propsProp || !ts2.isObjectLiteralExpression(propsProp)) {
|
|
1369
|
-
return {};
|
|
1487
|
+
if (fragment.usage?.patterns && fragment.usage.patterns.length > 0) {
|
|
1488
|
+
return true;
|
|
1370
1489
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
if (ts2.isPropertyAssignment(prop) && ts2.isIdentifier(prop.name)) {
|
|
1374
|
-
const propName = prop.name.text;
|
|
1375
|
-
const propValue = prop.initializer;
|
|
1376
|
-
if (ts2.isObjectLiteralExpression(propValue)) {
|
|
1377
|
-
props[propName] = extractPropDefinition(propValue);
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1490
|
+
if (fragment.accessibility?.requirements && fragment.accessibility.requirements.length > 0) {
|
|
1491
|
+
return true;
|
|
1380
1492
|
}
|
|
1381
|
-
|
|
1493
|
+
if (fragment.figma?.nodeId || fragment.figma?.variants) {
|
|
1494
|
+
return true;
|
|
1495
|
+
}
|
|
1496
|
+
if (fragment.related?.similar && fragment.related.similar.length > 0) {
|
|
1497
|
+
return true;
|
|
1498
|
+
}
|
|
1499
|
+
return false;
|
|
1382
1500
|
}
|
|
1383
|
-
function
|
|
1384
|
-
const
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
const
|
|
1394
|
-
|
|
1395
|
-
|
|
1501
|
+
function resolveComponentPath(componentName, registry) {
|
|
1502
|
+
const entry = registry.components[componentName];
|
|
1503
|
+
return entry?.path;
|
|
1504
|
+
}
|
|
1505
|
+
function getComponentsByCategory(category, registry) {
|
|
1506
|
+
return registry.categories?.[category] || [];
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
// src/core/generators/context.ts
|
|
1510
|
+
function generateContextMd(registry, options = {}) {
|
|
1511
|
+
const {
|
|
1512
|
+
format = "markdown",
|
|
1513
|
+
compact = false,
|
|
1514
|
+
include = { props: true, relations: true, code: false }
|
|
1515
|
+
} = options;
|
|
1516
|
+
if (format === "json") {
|
|
1517
|
+
return generateJsonContext(registry, include, compact);
|
|
1396
1518
|
}
|
|
1397
|
-
|
|
1398
|
-
if (constraints.length > 0) def.constraints = constraints;
|
|
1399
|
-
return def;
|
|
1519
|
+
return generateMarkdownContext(registry, include, compact);
|
|
1400
1520
|
}
|
|
1401
|
-
function
|
|
1402
|
-
const
|
|
1403
|
-
|
|
1404
|
-
|
|
1521
|
+
function generateMarkdownContext(registry, include, compact) {
|
|
1522
|
+
const lines = [];
|
|
1523
|
+
const componentNames = Object.keys(registry.components).sort();
|
|
1524
|
+
const componentCount = componentNames.length;
|
|
1525
|
+
lines.push("# Component Library Context");
|
|
1526
|
+
lines.push(`Generated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]} | Components: ${componentCount}`);
|
|
1527
|
+
lines.push("");
|
|
1528
|
+
lines.push("## Quick Reference");
|
|
1529
|
+
lines.push("");
|
|
1530
|
+
lines.push("| Component | Path | Category | Status |");
|
|
1531
|
+
lines.push("|-----------|------|----------|--------|");
|
|
1532
|
+
for (const name of componentNames) {
|
|
1533
|
+
const entry = registry.components[name];
|
|
1534
|
+
const status = entry.status || "stable";
|
|
1535
|
+
const category = entry.category || "-";
|
|
1536
|
+
lines.push(`| ${name} | ${entry.path} | ${category} | ${status} |`);
|
|
1405
1537
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1538
|
+
lines.push("");
|
|
1539
|
+
if (compact) {
|
|
1540
|
+
const content2 = lines.join("\n");
|
|
1541
|
+
return {
|
|
1542
|
+
content: content2,
|
|
1543
|
+
tokenEstimate: estimateTokens(content2),
|
|
1544
|
+
componentCount
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
lines.push("---");
|
|
1548
|
+
lines.push("");
|
|
1549
|
+
for (const name of componentNames) {
|
|
1550
|
+
const entry = registry.components[name];
|
|
1551
|
+
const fragment = entry.fragment;
|
|
1552
|
+
lines.push(`## ${name}`);
|
|
1553
|
+
lines.push(`**Path:** \`${entry.path}\``);
|
|
1554
|
+
if (entry.category) {
|
|
1555
|
+
lines.push(`**Category:** ${entry.category} | **Status:** ${entry.status || "stable"}`);
|
|
1556
|
+
}
|
|
1557
|
+
lines.push("");
|
|
1558
|
+
if (entry.description || fragment?.description) {
|
|
1559
|
+
lines.push("### Description");
|
|
1560
|
+
lines.push(entry.description || fragment?.description || "");
|
|
1561
|
+
lines.push("");
|
|
1562
|
+
}
|
|
1563
|
+
if (fragment?.usage) {
|
|
1564
|
+
if (fragment.usage.when && fragment.usage.when.length > 0) {
|
|
1565
|
+
lines.push("### When to Use");
|
|
1566
|
+
for (const when of fragment.usage.when) {
|
|
1567
|
+
lines.push(`- ${when}`);
|
|
1423
1568
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1569
|
+
lines.push("");
|
|
1570
|
+
}
|
|
1571
|
+
if (fragment.usage.doNot && fragment.usage.doNot.length > 0) {
|
|
1572
|
+
lines.push("### Do Not");
|
|
1573
|
+
for (const doNotItem of fragment.usage.doNot) {
|
|
1574
|
+
if (typeof doNotItem === "string") {
|
|
1575
|
+
lines.push(`- ${doNotItem}`);
|
|
1576
|
+
} else {
|
|
1577
|
+
const item = doNotItem;
|
|
1578
|
+
if (item.instead) {
|
|
1579
|
+
const alternativePath = resolveComponentPath2(item.instead, registry);
|
|
1580
|
+
if (alternativePath) {
|
|
1581
|
+
lines.push(`- ${item.text} \u2192 use **${item.instead}** (\`${alternativePath}\`)`);
|
|
1582
|
+
} else {
|
|
1583
|
+
lines.push(`- ${item.text} \u2192 use **${item.instead}**`);
|
|
1584
|
+
}
|
|
1585
|
+
} else {
|
|
1586
|
+
lines.push(`- ${item.text}`);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1427
1589
|
}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1590
|
+
lines.push("");
|
|
1591
|
+
}
|
|
1592
|
+
if (include.code && fragment.usage.patterns && fragment.usage.patterns.length > 0) {
|
|
1593
|
+
lines.push("### Patterns");
|
|
1594
|
+
for (const pattern of fragment.usage.patterns) {
|
|
1595
|
+
lines.push(`**${pattern.name}**`);
|
|
1596
|
+
if (pattern.description) {
|
|
1597
|
+
lines.push(pattern.description);
|
|
1433
1598
|
}
|
|
1599
|
+
lines.push("```tsx");
|
|
1600
|
+
lines.push(pattern.code);
|
|
1601
|
+
lines.push("```");
|
|
1602
|
+
lines.push("");
|
|
1434
1603
|
}
|
|
1435
|
-
variants.push(variant);
|
|
1436
1604
|
}
|
|
1437
1605
|
}
|
|
1606
|
+
if (include.props && entry.props && Object.keys(entry.props).length > 0) {
|
|
1607
|
+
lines.push("### Props");
|
|
1608
|
+
lines.push("| Prop | Type | Default | Description |");
|
|
1609
|
+
lines.push("|------|------|---------|-------------|");
|
|
1610
|
+
for (const [propName, prop] of Object.entries(entry.props)) {
|
|
1611
|
+
const type = formatPropType(prop);
|
|
1612
|
+
const defaultVal = prop.default !== void 0 ? `\`${JSON.stringify(prop.default)}\`` : "-";
|
|
1613
|
+
const desc = prop.description || "-";
|
|
1614
|
+
const required = prop.required ? " (required)" : "";
|
|
1615
|
+
lines.push(`| ${propName}${required} | ${type} | ${defaultVal} | ${desc} |`);
|
|
1616
|
+
}
|
|
1617
|
+
lines.push("");
|
|
1618
|
+
}
|
|
1619
|
+
if (fragment?.accessibility) {
|
|
1620
|
+
lines.push("### Accessibility");
|
|
1621
|
+
if (fragment.accessibility.role) {
|
|
1622
|
+
lines.push(`**Role:** ${fragment.accessibility.role}`);
|
|
1623
|
+
}
|
|
1624
|
+
if (fragment.accessibility.requirements && fragment.accessibility.requirements.length > 0) {
|
|
1625
|
+
for (const req of fragment.accessibility.requirements) {
|
|
1626
|
+
lines.push(`- ${req}`);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
if (fragment.accessibility.keyboard) {
|
|
1630
|
+
lines.push("");
|
|
1631
|
+
lines.push("**Keyboard:**");
|
|
1632
|
+
for (const [key, action] of Object.entries(fragment.accessibility.keyboard)) {
|
|
1633
|
+
lines.push(`- \`${key}\`: ${action}`);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
lines.push("");
|
|
1637
|
+
}
|
|
1638
|
+
if (include.relations && fragment?.related) {
|
|
1639
|
+
lines.push("### Related");
|
|
1640
|
+
if (fragment.related.similar && fragment.related.similar.length > 0) {
|
|
1641
|
+
const resolved = fragment.related.similar.map((comp) => {
|
|
1642
|
+
const path = resolveComponentPath2(comp, registry);
|
|
1643
|
+
return path ? `${comp} (\`${path}\`)` : comp;
|
|
1644
|
+
});
|
|
1645
|
+
lines.push(`- **Similar:** ${resolved.join(", ")}`);
|
|
1646
|
+
}
|
|
1647
|
+
if (fragment.related.composedWith && fragment.related.composedWith.length > 0) {
|
|
1648
|
+
const resolved = fragment.related.composedWith.map((comp) => {
|
|
1649
|
+
const path = resolveComponentPath2(comp, registry);
|
|
1650
|
+
return path ? `${comp} (\`${path}\`)` : comp;
|
|
1651
|
+
});
|
|
1652
|
+
lines.push(`- **Composed with:** ${resolved.join(", ")}`);
|
|
1653
|
+
}
|
|
1654
|
+
if (fragment.related.usedIn && fragment.related.usedIn.length > 0) {
|
|
1655
|
+
const resolved = fragment.related.usedIn.map((comp) => {
|
|
1656
|
+
const path = resolveComponentPath2(comp, registry);
|
|
1657
|
+
return path ? `${comp} (\`${path}\`)` : comp;
|
|
1658
|
+
});
|
|
1659
|
+
lines.push(`- **Used in:** ${resolved.join(", ")}`);
|
|
1660
|
+
}
|
|
1661
|
+
lines.push("");
|
|
1662
|
+
}
|
|
1663
|
+
lines.push("---");
|
|
1664
|
+
lines.push("");
|
|
1438
1665
|
}
|
|
1439
|
-
|
|
1666
|
+
const content = lines.join("\n");
|
|
1667
|
+
return {
|
|
1668
|
+
content,
|
|
1669
|
+
tokenEstimate: estimateTokens(content),
|
|
1670
|
+
componentCount
|
|
1671
|
+
};
|
|
1440
1672
|
}
|
|
1441
|
-
function
|
|
1442
|
-
const
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
if (
|
|
1452
|
-
|
|
1453
|
-
if (
|
|
1454
|
-
|
|
1673
|
+
function generateJsonContext(registry, include, compact) {
|
|
1674
|
+
const componentNames = Object.keys(registry.components).sort();
|
|
1675
|
+
const componentCount = componentNames.length;
|
|
1676
|
+
const components = {};
|
|
1677
|
+
for (const name of componentNames) {
|
|
1678
|
+
const entry = registry.components[name];
|
|
1679
|
+
const fragment = entry.fragment;
|
|
1680
|
+
const component = {
|
|
1681
|
+
path: entry.path
|
|
1682
|
+
};
|
|
1683
|
+
if (entry.category) component.category = entry.category;
|
|
1684
|
+
if (entry.status) component.status = entry.status;
|
|
1685
|
+
if (entry.description || fragment?.description) {
|
|
1686
|
+
component.description = entry.description || fragment?.description;
|
|
1455
1687
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1688
|
+
if (!compact && fragment?.usage) {
|
|
1689
|
+
if (fragment.usage.when && fragment.usage.when.length > 0) {
|
|
1690
|
+
component.whenToUse = fragment.usage.when;
|
|
1691
|
+
}
|
|
1692
|
+
if (fragment.usage.doNot && fragment.usage.doNot.length > 0) {
|
|
1693
|
+
component.doNot = fragment.usage.doNot.map((item) => {
|
|
1694
|
+
if (typeof item === "string") {
|
|
1695
|
+
return { text: item };
|
|
1696
|
+
}
|
|
1697
|
+
const doNotItem = item;
|
|
1698
|
+
const result = {
|
|
1699
|
+
text: doNotItem.text
|
|
1700
|
+
};
|
|
1701
|
+
if (doNotItem.instead) {
|
|
1702
|
+
result.instead = doNotItem.instead;
|
|
1703
|
+
result.insteadPath = resolveComponentPath2(doNotItem.instead, registry);
|
|
1704
|
+
}
|
|
1705
|
+
return result;
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1463
1708
|
}
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
code = code.slice(1, -1).trim();
|
|
1709
|
+
if (!compact && include.props && entry.props) {
|
|
1710
|
+
component.props = {};
|
|
1711
|
+
for (const [propName, prop] of Object.entries(entry.props)) {
|
|
1712
|
+
component.props[propName] = {
|
|
1713
|
+
type: formatPropType(prop)
|
|
1714
|
+
};
|
|
1715
|
+
if (prop.required) component.props[propName].required = true;
|
|
1716
|
+
if (prop.default !== void 0) component.props[propName].default = prop.default;
|
|
1717
|
+
if (prop.description) component.props[propName].description = prop.description;
|
|
1718
|
+
}
|
|
1475
1719
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
relationship,
|
|
1496
|
-
note: note ?? ""
|
|
1497
|
-
});
|
|
1720
|
+
if (!compact && include.relations && fragment?.related) {
|
|
1721
|
+
component.related = {};
|
|
1722
|
+
if (fragment.related.similar) {
|
|
1723
|
+
component.related.similar = fragment.related.similar.map((name2) => ({
|
|
1724
|
+
name: name2,
|
|
1725
|
+
path: resolveComponentPath2(name2, registry)
|
|
1726
|
+
}));
|
|
1727
|
+
}
|
|
1728
|
+
if (fragment.related.composedWith) {
|
|
1729
|
+
component.related.composedWith = fragment.related.composedWith.map((name2) => ({
|
|
1730
|
+
name: name2,
|
|
1731
|
+
path: resolveComponentPath2(name2, registry)
|
|
1732
|
+
}));
|
|
1733
|
+
}
|
|
1734
|
+
if (fragment.related.usedIn) {
|
|
1735
|
+
component.related.usedIn = fragment.related.usedIn.map((name2) => ({
|
|
1736
|
+
name: name2,
|
|
1737
|
+
path: resolveComponentPath2(name2, registry)
|
|
1738
|
+
}));
|
|
1498
1739
|
}
|
|
1499
1740
|
}
|
|
1741
|
+
components[name] = component;
|
|
1500
1742
|
}
|
|
1501
|
-
|
|
1743
|
+
const output = {
|
|
1744
|
+
version: "1.0",
|
|
1745
|
+
generatedAt: registry.generatedAt,
|
|
1746
|
+
componentCount,
|
|
1747
|
+
categories: registry.categories,
|
|
1748
|
+
components
|
|
1749
|
+
};
|
|
1750
|
+
const content = JSON.stringify(output, null, 2);
|
|
1751
|
+
return {
|
|
1752
|
+
content,
|
|
1753
|
+
tokenEstimate: estimateTokens(content),
|
|
1754
|
+
componentCount
|
|
1755
|
+
};
|
|
1502
1756
|
}
|
|
1503
|
-
function
|
|
1504
|
-
|
|
1505
|
-
if (!aiProp || !ts2.isObjectLiteralExpression(aiProp)) {
|
|
1506
|
-
return void 0;
|
|
1507
|
-
}
|
|
1508
|
-
const ai = {};
|
|
1509
|
-
const compositionPattern = extractStringProperty(aiProp, "compositionPattern");
|
|
1510
|
-
if (compositionPattern && ["compound", "simple", "controlled"].includes(compositionPattern)) {
|
|
1511
|
-
ai.compositionPattern = compositionPattern;
|
|
1512
|
-
}
|
|
1513
|
-
const subComponents = extractStringArray(aiProp, "subComponents");
|
|
1514
|
-
if (subComponents.length > 0) {
|
|
1515
|
-
ai.subComponents = subComponents;
|
|
1516
|
-
}
|
|
1517
|
-
const requiredChildren = extractStringArray(aiProp, "requiredChildren");
|
|
1518
|
-
if (requiredChildren.length > 0) {
|
|
1519
|
-
ai.requiredChildren = requiredChildren;
|
|
1520
|
-
}
|
|
1521
|
-
const commonPatterns = extractStringArray(aiProp, "commonPatterns");
|
|
1522
|
-
if (commonPatterns.length > 0) {
|
|
1523
|
-
ai.commonPatterns = commonPatterns;
|
|
1524
|
-
}
|
|
1525
|
-
if (Object.keys(ai).length > 0) {
|
|
1526
|
-
return ai;
|
|
1527
|
-
}
|
|
1528
|
-
return void 0;
|
|
1757
|
+
function resolveComponentPath2(componentName, registry) {
|
|
1758
|
+
return registry.components[componentName]?.path;
|
|
1529
1759
|
}
|
|
1530
|
-
function
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
return void 0;
|
|
1534
|
-
}
|
|
1535
|
-
const contract = {};
|
|
1536
|
-
const propsSummary = extractStringArray(contractProp, "propsSummary");
|
|
1537
|
-
if (propsSummary.length > 0) {
|
|
1538
|
-
contract.propsSummary = propsSummary;
|
|
1539
|
-
}
|
|
1540
|
-
const a11yRules = extractStringArray(contractProp, "a11yRules");
|
|
1541
|
-
if (a11yRules.length > 0) {
|
|
1542
|
-
contract.a11yRules = a11yRules;
|
|
1543
|
-
}
|
|
1544
|
-
if (Object.keys(contract).length > 0) {
|
|
1545
|
-
return contract;
|
|
1760
|
+
function formatPropType(prop) {
|
|
1761
|
+
if (prop.options && prop.options.length > 0) {
|
|
1762
|
+
return prop.options.map((o) => `"${o}"`).join(" \\| ");
|
|
1546
1763
|
}
|
|
1547
|
-
return
|
|
1764
|
+
return prop.type || "unknown";
|
|
1548
1765
|
}
|
|
1549
|
-
function
|
|
1550
|
-
|
|
1551
|
-
if (prop && ts2.isStringLiteral(prop)) {
|
|
1552
|
-
return prop.text;
|
|
1553
|
-
}
|
|
1554
|
-
if (prop && ts2.isNoSubstitutionTemplateLiteral(prop)) {
|
|
1555
|
-
return prop.text;
|
|
1556
|
-
}
|
|
1557
|
-
return null;
|
|
1766
|
+
function estimateTokens(text) {
|
|
1767
|
+
return Math.ceil(text.length / 4);
|
|
1558
1768
|
}
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1769
|
+
|
|
1770
|
+
// src/core/importAnalyzer.ts
|
|
1771
|
+
import ts3 from "typescript";
|
|
1772
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
1773
|
+
import { dirname as dirname5, basename as basename4 } from "path";
|
|
1774
|
+
function extractComponentNameFromPath(filePath) {
|
|
1775
|
+
const fileName = basename4(filePath);
|
|
1776
|
+
if (fileName === "index.tsx" || fileName === "index.ts" || fileName === "index.jsx" || fileName === "index.js") {
|
|
1777
|
+
const parentDir = basename4(dirname5(filePath));
|
|
1778
|
+
return parentDir;
|
|
1564
1779
|
}
|
|
1565
|
-
return
|
|
1780
|
+
return fileName.replace(/\.(tsx?|jsx?)$/, "").replace(/\.stories$/, "").replace(/\.fragment$/, "").replace(/\.fragment$/, "");
|
|
1566
1781
|
}
|
|
1567
|
-
function
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
return [];
|
|
1782
|
+
function isComponentImportPath(importPath) {
|
|
1783
|
+
if (!importPath.startsWith(".") && !importPath.startsWith("/")) {
|
|
1784
|
+
return false;
|
|
1571
1785
|
}
|
|
1572
|
-
const
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1786
|
+
const skipPatterns = [
|
|
1787
|
+
/\/hooks\//,
|
|
1788
|
+
/\/utils\//,
|
|
1789
|
+
/\/helpers\//,
|
|
1790
|
+
/\/lib\//,
|
|
1791
|
+
/\/types\//,
|
|
1792
|
+
/\/constants\//,
|
|
1793
|
+
/\/styles\//,
|
|
1794
|
+
/\/context\//,
|
|
1795
|
+
/\.css$/,
|
|
1796
|
+
/\.scss$/,
|
|
1797
|
+
/\.less$/,
|
|
1798
|
+
/\.json$/
|
|
1799
|
+
];
|
|
1800
|
+
for (const pattern of skipPatterns) {
|
|
1801
|
+
if (pattern.test(importPath)) {
|
|
1802
|
+
return false;
|
|
1578
1803
|
}
|
|
1579
1804
|
}
|
|
1580
|
-
return
|
|
1805
|
+
return true;
|
|
1581
1806
|
}
|
|
1582
|
-
function
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
}
|
|
1592
|
-
|
|
1593
|
-
return false;
|
|
1594
|
-
}
|
|
1595
|
-
if (expr.kind === ts2.SyntaxKind.NullKeyword) {
|
|
1596
|
-
return null;
|
|
1597
|
-
}
|
|
1598
|
-
if (ts2.isArrayLiteralExpression(expr)) {
|
|
1599
|
-
return expr.elements.map(extractLiteralValue);
|
|
1807
|
+
function isPascalCase(name) {
|
|
1808
|
+
return /^[A-Z][a-zA-Z0-9]*$/.test(name);
|
|
1809
|
+
}
|
|
1810
|
+
function analyzeFileImports(filePath) {
|
|
1811
|
+
const componentName = extractComponentNameFromPath(filePath);
|
|
1812
|
+
const imports = [];
|
|
1813
|
+
let sourceText;
|
|
1814
|
+
try {
|
|
1815
|
+
sourceText = readFileSync3(filePath, "utf-8");
|
|
1816
|
+
} catch {
|
|
1817
|
+
return { filePath, componentName, imports };
|
|
1600
1818
|
}
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1819
|
+
const sourceFile = ts3.createSourceFile(
|
|
1820
|
+
filePath,
|
|
1821
|
+
sourceText,
|
|
1822
|
+
ts3.ScriptTarget.Latest,
|
|
1823
|
+
true,
|
|
1824
|
+
filePath.endsWith(".tsx") || filePath.endsWith(".jsx") ? ts3.ScriptKind.TSX : ts3.ScriptKind.TS
|
|
1825
|
+
);
|
|
1826
|
+
ts3.forEachChild(sourceFile, (node) => {
|
|
1827
|
+
if (ts3.isImportDeclaration(node)) {
|
|
1828
|
+
const moduleSpecifier = node.moduleSpecifier;
|
|
1829
|
+
if (!ts3.isStringLiteral(moduleSpecifier)) return;
|
|
1830
|
+
const importPath = moduleSpecifier.text;
|
|
1831
|
+
if (!isComponentImportPath(importPath)) return;
|
|
1832
|
+
const importClause = node.importClause;
|
|
1833
|
+
if (!importClause) return;
|
|
1834
|
+
if (importClause.name) {
|
|
1835
|
+
const name = importClause.name.text;
|
|
1836
|
+
if (isPascalCase(name)) {
|
|
1837
|
+
imports.push({
|
|
1838
|
+
name,
|
|
1839
|
+
path: importPath,
|
|
1840
|
+
isDefault: true,
|
|
1841
|
+
isNamespace: false
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
const namedBindings = importClause.namedBindings;
|
|
1846
|
+
if (namedBindings) {
|
|
1847
|
+
if (ts3.isNamedImports(namedBindings)) {
|
|
1848
|
+
for (const element of namedBindings.elements) {
|
|
1849
|
+
const name = element.name.text;
|
|
1850
|
+
if (isPascalCase(name)) {
|
|
1851
|
+
imports.push({
|
|
1852
|
+
name,
|
|
1853
|
+
path: importPath,
|
|
1854
|
+
isDefault: false,
|
|
1855
|
+
isNamespace: false
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
} else if (ts3.isNamespaceImport(namedBindings)) {
|
|
1860
|
+
const name = namedBindings.name.text;
|
|
1861
|
+
imports.push({
|
|
1862
|
+
name,
|
|
1863
|
+
path: importPath,
|
|
1864
|
+
isDefault: false,
|
|
1865
|
+
isNamespace: true
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
});
|
|
1871
|
+
return { filePath, componentName, imports };
|
|
1872
|
+
}
|
|
1873
|
+
function buildImportGraph(filePaths) {
|
|
1874
|
+
const importedBy = /* @__PURE__ */ new Map();
|
|
1875
|
+
for (const filePath of filePaths) {
|
|
1876
|
+
const result = analyzeFileImports(filePath);
|
|
1877
|
+
for (const imp of result.imports) {
|
|
1878
|
+
if (imp.isNamespace) continue;
|
|
1879
|
+
const importedComponent = imp.name;
|
|
1880
|
+
const importingComponent = result.componentName;
|
|
1881
|
+
if (importedComponent === importingComponent) continue;
|
|
1882
|
+
const existing = importedBy.get(importedComponent) || [];
|
|
1883
|
+
if (!existing.includes(importingComponent)) {
|
|
1884
|
+
existing.push(importingComponent);
|
|
1885
|
+
importedBy.set(importedComponent, existing);
|
|
1606
1886
|
}
|
|
1607
1887
|
}
|
|
1608
|
-
return obj;
|
|
1609
1888
|
}
|
|
1610
|
-
return
|
|
1889
|
+
return importedBy;
|
|
1890
|
+
}
|
|
1891
|
+
function getImportedBy(componentName, importGraph) {
|
|
1892
|
+
return importGraph.get(componentName) || [];
|
|
1611
1893
|
}
|
|
1612
|
-
|
|
1613
|
-
// src/core/previewLoader.ts
|
|
1614
|
-
import { existsSync as existsSync4 } from "fs";
|
|
1615
|
-
import { join as join3, resolve as resolve3 } from "path";
|
|
1616
|
-
|
|
1617
|
-
// src/core/importAnalyzer.ts
|
|
1618
|
-
import ts3 from "typescript";
|
|
1619
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
1620
|
-
import { dirname as dirname5, basename as basename4 } from "path";
|
|
1621
1894
|
|
|
1622
1895
|
export {
|
|
1623
1896
|
findConfigFile,
|
|
1624
1897
|
loadConfig,
|
|
1625
1898
|
discoverBlockFiles,
|
|
1899
|
+
discoverRecipeFiles,
|
|
1626
1900
|
discoverFragmentFiles,
|
|
1627
1901
|
discoverComponentFiles,
|
|
1628
1902
|
extractComponentName,
|
|
1903
|
+
discoverComponentsFromSource,
|
|
1904
|
+
discoverComponentsFromBarrel,
|
|
1629
1905
|
discoverTokenFiles,
|
|
1906
|
+
discoverInstalledFragments,
|
|
1630
1907
|
discoverAllComponents,
|
|
1631
1908
|
loadFragmentFile,
|
|
1909
|
+
loadFragmentFiles,
|
|
1632
1910
|
parseFragmentFile,
|
|
1911
|
+
findPreviewConfigPath,
|
|
1912
|
+
findStorybookDir,
|
|
1913
|
+
loadPreviewConfig,
|
|
1914
|
+
autoLoadPreviewConfig,
|
|
1915
|
+
generatePreviewModule,
|
|
1633
1916
|
extractPropsFromFile,
|
|
1917
|
+
extractPropsFromSource,
|
|
1634
1918
|
generateRegistry,
|
|
1635
|
-
|
|
1919
|
+
resolveComponentPath,
|
|
1920
|
+
getComponentsByCategory,
|
|
1921
|
+
generateContextMd,
|
|
1922
|
+
analyzeFileImports,
|
|
1923
|
+
buildImportGraph,
|
|
1924
|
+
getImportedBy
|
|
1636
1925
|
};
|
|
1637
|
-
//# sourceMappingURL=chunk-
|
|
1926
|
+
//# sourceMappingURL=chunk-HQ6A6DTV.js.map
|