@fragments-sdk/cli 0.14.3 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -3
- package/dist/bin.js +4290 -3754
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-TXFCEDOC.js → chunk-2WXKALIG.js} +2 -2
- package/dist/{chunk-I34BC3CU.js → chunk-32LIWN2P.js} +1006 -3
- package/dist/chunk-32LIWN2P.js.map +1 -0
- package/dist/{chunk-55KERLWL.js → chunk-65WSVDV5.js} +314 -89
- package/dist/chunk-65WSVDV5.js.map +1 -0
- package/dist/chunk-7DZC4YEV.js +294 -0
- package/dist/chunk-7DZC4YEV.js.map +1 -0
- package/dist/{chunk-LOYS64QS.js → chunk-7WHVW72L.js} +230 -19
- package/dist/chunk-7WHVW72L.js.map +1 -0
- package/dist/{chunk-PJT5IZ37.js → chunk-BJE3425I.js} +19 -52
- package/dist/{chunk-PJT5IZ37.js.map → chunk-BJE3425I.js.map} +1 -1
- package/dist/{chunk-5A6X2Y73.js → chunk-CZD3AD4Q.js} +12 -11
- package/dist/chunk-CZD3AD4Q.js.map +1 -0
- package/dist/{chunk-EYXVAMEX.js → chunk-MN3TJ3D5.js} +72 -3
- package/dist/chunk-MN3TJ3D5.js.map +1 -0
- package/dist/chunk-QCN35LJU.js +630 -0
- package/dist/chunk-QCN35LJU.js.map +1 -0
- package/dist/chunk-T47OLCSF.js +36 -0
- package/dist/chunk-T47OLCSF.js.map +1 -0
- package/dist/{chunk-APTQIBS5.js → chunk-XJQ5BIWI.js} +144 -1049
- package/dist/chunk-XJQ5BIWI.js.map +1 -0
- package/dist/codebase-scanner-VOTPXRYW.js +22 -0
- package/dist/converter-JLINP7CJ.js +34 -0
- package/dist/converter-JLINP7CJ.js.map +1 -0
- package/dist/core/index.js +43 -1
- package/dist/{generate-RYWIPDN2.js → generate-A4FP5426.js} +3 -4
- package/dist/{generate-RYWIPDN2.js.map → generate-A4FP5426.js.map} +1 -1
- package/dist/govern-scan-UCBZR6D6.js +280 -0
- package/dist/govern-scan-UCBZR6D6.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +11 -11
- package/dist/{init-WRUSW7R5.js → init-HGSM35XA.js} +131 -128
- package/dist/init-HGSM35XA.js.map +1 -0
- package/dist/{init-cloud-REQ3XLHO.js → init-cloud-MQ6GRJAZ.js} +2 -2
- package/dist/mcp-bin.js +5 -36
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-VNNKACG2.js +15 -0
- package/dist/{scan-generate-TFZVL3BT.js → scan-generate-TWRHNU5M.js} +335 -46
- package/dist/scan-generate-TWRHNU5M.js.map +1 -0
- package/dist/scanner-7LAZYPWZ.js +13 -0
- package/dist/{service-HKJ6B7P7.js → service-FHQU7YS7.js} +27 -23
- package/dist/{snapshot-C5DYIGIV.js → snapshot-KQEQ6XHL.js} +2 -2
- package/dist/{static-viewer-DUVC4UIM.js → static-viewer-63PG6FWY.js} +3 -3
- package/dist/static-viewer-63PG6FWY.js.map +1 -0
- package/dist/{test-JW7JIDFG.js → test-UQYUCZIS.js} +4 -6
- package/dist/{test-JW7JIDFG.js.map → test-UQYUCZIS.js.map} +1 -1
- package/dist/{tokens-KE73G5JC.js → tokens-6GYKDV6U.js} +6 -5
- package/dist/{tokens-KE73G5JC.js.map → tokens-6GYKDV6U.js.map} +1 -1
- package/dist/tokens-generate-VTZV5EEW.js +86 -0
- package/dist/tokens-generate-VTZV5EEW.js.map +1 -0
- package/package.json +6 -6
- package/src/bin.ts +210 -48
- package/src/build.ts +130 -6
- package/src/commands/__fixtures__/shadcn-label-wrapper/package.json +7 -0
- package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/label.contract.json +42 -0
- package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/label.tsx +11 -0
- package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/primitive.contract.json +20 -0
- package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/primitive.tsx +14 -0
- package/src/commands/__fixtures__/shadcn-label-wrapper/tsconfig.app.json +23 -0
- package/src/commands/__tests__/init.test.ts +113 -0
- package/src/commands/__tests__/scan-generate.test.ts +188 -69
- package/src/commands/__tests__/verify.test.ts +91 -0
- package/src/commands/discover.ts +151 -0
- package/src/commands/enhance.ts +3 -1
- package/src/commands/govern-scan.ts +386 -0
- package/src/commands/govern.ts +2 -2
- package/src/commands/init.ts +152 -28
- package/src/commands/inspect.ts +290 -0
- package/src/commands/migrate-contract.ts +85 -0
- package/src/commands/scan-generate.ts +438 -50
- package/src/commands/scan.ts +1 -0
- package/src/commands/setup.ts +27 -50
- package/src/commands/tokens-generate.ts +113 -0
- package/src/commands/verify.ts +195 -1
- package/src/core/__fixtures__/shadcn-input/input.tsx +7 -0
- package/src/core/__fixtures__/shadcn-input/tsconfig.json +14 -0
- package/src/core/__fixtures__/shadcn-label/label.tsx +11 -0
- package/src/core/__fixtures__/shadcn-label/primitive.tsx +14 -0
- package/src/core/__fixtures__/shadcn-label/tsconfig.json +14 -0
- package/src/core/__fixtures__/shadcn-radix-label/label.tsx +11 -0
- package/src/core/__fixtures__/shadcn-radix-label/node_modules/radix-ui/index.d.ts +12 -0
- package/src/core/__fixtures__/shadcn-radix-label/tsconfig.json +14 -0
- package/src/core/__tests__/contract-parity.test.ts +316 -0
- package/src/core/component-extractor.test.ts +39 -0
- package/src/core/component-extractor.ts +92 -1
- package/src/core/config.ts +2 -1
- package/src/core/discovery.ts +13 -2
- package/src/core/drift-verifier.ts +123 -0
- package/src/core/extractor-adapter.ts +80 -0
- package/src/mcp/__tests__/projectFields.test.ts +1 -1
- package/src/mcp/utils.ts +1 -50
- package/src/migrate/converter.ts +3 -3
- package/src/migrate/fragment-to-contract.ts +253 -0
- package/src/migrate/report.ts +1 -1
- package/src/scripts/token-benchmark.ts +121 -0
- package/src/service/__tests__/props-extractor.test.ts +94 -0
- package/src/service/__tests__/token-normalizer.test.ts +690 -0
- package/src/service/ast-utils.ts +4 -23
- package/src/service/babel-config.ts +23 -0
- package/src/service/enhance/converter.ts +61 -0
- package/src/service/enhance/props-extractor.ts +25 -8
- package/src/service/enhance/scanner.ts +5 -24
- package/src/service/snippet-validation.ts +9 -3
- package/src/service/token-normalizer.ts +510 -0
- package/src/shared/index.ts +1 -0
- package/src/shared/project-fields.ts +46 -0
- package/src/viewer/__tests__/viewer-integration.test.ts +8 -8
- package/src/viewer/preview-adapter.ts +116 -0
- package/src/viewer/style-utils.ts +27 -412
- package/src/viewer/vite-plugin.ts +2 -2
- package/dist/chunk-55KERLWL.js.map +0 -1
- package/dist/chunk-5A6X2Y73.js.map +0 -1
- package/dist/chunk-APTQIBS5.js.map +0 -1
- package/dist/chunk-EYXVAMEX.js.map +0 -1
- package/dist/chunk-I34BC3CU.js.map +0 -1
- package/dist/chunk-LOYS64QS.js.map +0 -1
- package/dist/chunk-ZKTFKHWN.js +0 -324
- package/dist/chunk-ZKTFKHWN.js.map +0 -1
- package/dist/discovery-VDANZAJ2.js +0 -28
- package/dist/init-WRUSW7R5.js.map +0 -1
- package/dist/scan-YJHQIRKG.js +0 -14
- package/dist/scan-generate-TFZVL3BT.js.map +0 -1
- package/dist/viewer-2TZS3NDL.js +0 -2730
- package/dist/viewer-2TZS3NDL.js.map +0 -1
- package/src/commands/dev.ts +0 -107
- /package/dist/{chunk-TXFCEDOC.js.map → chunk-2WXKALIG.js.map} +0 -0
- /package/dist/{discovery-VDANZAJ2.js.map → codebase-scanner-VOTPXRYW.js.map} +0 -0
- /package/dist/{init-cloud-REQ3XLHO.js.map → init-cloud-MQ6GRJAZ.js.map} +0 -0
- /package/dist/{scan-YJHQIRKG.js.map → scan-VNNKACG2.js.map} +0 -0
- /package/dist/{service-HKJ6B7P7.js.map → scanner-7LAZYPWZ.js.map} +0 -0
- /package/dist/{static-viewer-DUVC4UIM.js.map → service-FHQU7YS7.js.map} +0 -0
- /package/dist/{snapshot-C5DYIGIV.js.map → snapshot-KQEQ6XHL.js.map} +0 -0
|
@@ -1,22 +1,145 @@
|
|
|
1
1
|
import { createRequire as __banner_createRequire } from 'module'; const require = __banner_createRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
createComponentExtractor
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-55KERLWL.js";
|
|
4
|
+
} from "./chunk-MN3TJ3D5.js";
|
|
6
5
|
import {
|
|
7
6
|
discoverAllComponents
|
|
8
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-65WSVDV5.js";
|
|
9
8
|
import "./chunk-D2CDBRNU.js";
|
|
10
9
|
import {
|
|
11
10
|
BRAND
|
|
12
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-32LIWN2P.js";
|
|
13
12
|
import "./chunk-Z7EY4VHE.js";
|
|
14
13
|
|
|
15
14
|
// src/commands/scan-generate.ts
|
|
16
15
|
import { readFile, writeFile, access, mkdir } from "fs/promises";
|
|
17
16
|
import { resolve, basename, dirname, relative, join } from "path";
|
|
17
|
+
import { execSync } from "child_process";
|
|
18
18
|
import * as ts from "typescript";
|
|
19
19
|
import pc from "picocolors";
|
|
20
|
+
async function findNearestAncestor(scanPath, targetFile) {
|
|
21
|
+
let dir = resolve(scanPath);
|
|
22
|
+
for (let i = 0; i < 20; i++) {
|
|
23
|
+
try {
|
|
24
|
+
await access(join(dir, targetFile));
|
|
25
|
+
return dir;
|
|
26
|
+
} catch {
|
|
27
|
+
const parent = dirname(dir);
|
|
28
|
+
if (parent === dir) break;
|
|
29
|
+
dir = parent;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
async function findNearestPackageJsonDir(scanPath) {
|
|
35
|
+
return findNearestAncestor(scanPath, "package.json");
|
|
36
|
+
}
|
|
37
|
+
async function detectPackageManagerForProject(projectDir) {
|
|
38
|
+
let dir = resolve(projectDir);
|
|
39
|
+
for (let i = 0; i < 20; i++) {
|
|
40
|
+
for (const [fileName, manager] of [
|
|
41
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
42
|
+
["pnpm-workspace.yaml", "pnpm"],
|
|
43
|
+
["yarn.lock", "yarn"],
|
|
44
|
+
["package-lock.json", "npm"]
|
|
45
|
+
]) {
|
|
46
|
+
try {
|
|
47
|
+
await access(join(dir, fileName));
|
|
48
|
+
return { manager, lockfileDir: dir };
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const parent = dirname(dir);
|
|
53
|
+
if (parent === dir) break;
|
|
54
|
+
dir = parent;
|
|
55
|
+
}
|
|
56
|
+
return { manager: "npm", lockfileDir: null };
|
|
57
|
+
}
|
|
58
|
+
function resolveCoreInstallCommand(manager) {
|
|
59
|
+
switch (manager) {
|
|
60
|
+
case "pnpm":
|
|
61
|
+
return "pnpm add -D @fragments-sdk/core";
|
|
62
|
+
case "yarn":
|
|
63
|
+
return "yarn add -D @fragments-sdk/core";
|
|
64
|
+
default:
|
|
65
|
+
return "npm install -D @fragments-sdk/core";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function ensureCoreDependency(scanPath) {
|
|
69
|
+
const projectDir = await findNearestPackageJsonDir(scanPath);
|
|
70
|
+
if (!projectDir) return;
|
|
71
|
+
const pkgJsonPath = join(projectDir, "package.json");
|
|
72
|
+
try {
|
|
73
|
+
const pkgRaw = await readFile(pkgJsonPath, "utf-8");
|
|
74
|
+
const pkg = JSON.parse(pkgRaw);
|
|
75
|
+
const deps = pkg.dependencies || {};
|
|
76
|
+
const devDeps = pkg.devDependencies || {};
|
|
77
|
+
if (deps["@fragments-sdk/core"] || devDeps["@fragments-sdk/core"]) {
|
|
78
|
+
console.log(pc.dim(" \xB7 @fragments-sdk/core already installed"));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const { manager } = await detectPackageManagerForProject(projectDir);
|
|
82
|
+
const cmd = resolveCoreInstallCommand(manager);
|
|
83
|
+
execSync(cmd, { cwd: projectDir, stdio: "ignore" });
|
|
84
|
+
console.log(pc.green(" \u2713 Installed @fragments-sdk/core"));
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.log(
|
|
87
|
+
pc.yellow(
|
|
88
|
+
` \u26A0 Could not auto-install @fragments-sdk/core: ${e instanceof Error ? e.message : String(e)}`
|
|
89
|
+
)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function excludeFragmentsFromTsconfig(scanPath) {
|
|
94
|
+
const projectDir = await findNearestPackageJsonDir(scanPath);
|
|
95
|
+
if (!projectDir) return;
|
|
96
|
+
let tsconfigPath = null;
|
|
97
|
+
let tsconfigName = "";
|
|
98
|
+
for (const name of ["tsconfig.app.json", "tsconfig.json"]) {
|
|
99
|
+
const candidate = join(projectDir, name);
|
|
100
|
+
try {
|
|
101
|
+
await access(candidate);
|
|
102
|
+
tsconfigPath = candidate;
|
|
103
|
+
tsconfigName = name;
|
|
104
|
+
break;
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (!tsconfigPath) return;
|
|
109
|
+
try {
|
|
110
|
+
const raw = await readFile(tsconfigPath, "utf-8");
|
|
111
|
+
const parsed = ts.parseConfigFileTextToJson(tsconfigPath, raw);
|
|
112
|
+
if (parsed.error || !parsed.config) {
|
|
113
|
+
throw new Error(parsed.error?.messageText?.toString() ?? "Unable to parse tsconfig");
|
|
114
|
+
}
|
|
115
|
+
const config = parsed.config;
|
|
116
|
+
const exclude = Array.isArray(config.exclude) ? [...config.exclude] : [];
|
|
117
|
+
const ext = BRAND.fileExtension;
|
|
118
|
+
const alreadyExcluded = exclude.some(
|
|
119
|
+
(pattern) => pattern.includes(`*${ext}`) || pattern.includes("*.fragment.*") || pattern.includes("*.contract.*")
|
|
120
|
+
);
|
|
121
|
+
if (alreadyExcluded) {
|
|
122
|
+
console.log(
|
|
123
|
+
pc.dim(` \xB7 ${ext} already excluded in ${tsconfigName}`)
|
|
124
|
+
);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
exclude.push(`**/*${ext}`);
|
|
128
|
+
config.exclude = exclude;
|
|
129
|
+
const updated = `${JSON.stringify(config, null, 2)}
|
|
130
|
+
`;
|
|
131
|
+
await writeFile(tsconfigPath, updated, "utf-8");
|
|
132
|
+
console.log(
|
|
133
|
+
pc.green(` \u2713 Added ${ext} exclusion to ${tsconfigName}`)
|
|
134
|
+
);
|
|
135
|
+
} catch (e) {
|
|
136
|
+
console.log(
|
|
137
|
+
pc.yellow(
|
|
138
|
+
` \u26A0 Could not update ${tsconfigName}: ${e instanceof Error ? e.message : String(e)}`
|
|
139
|
+
)
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
20
143
|
async function scanGenerate(options) {
|
|
21
144
|
const scanPath = resolve(options.scanPath);
|
|
22
145
|
const generated = [];
|
|
@@ -168,13 +291,26 @@ Phase ${options.enrich ? "4" : "3"}: Generating fragment files...`));
|
|
|
168
291
|
comp.sourcePath,
|
|
169
292
|
componentBaseName
|
|
170
293
|
);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
294
|
+
let content;
|
|
295
|
+
if (fragmentPath.endsWith(".contract.json")) {
|
|
296
|
+
content = generateContractJsonFromScan(
|
|
297
|
+
comp.name,
|
|
298
|
+
componentBaseName,
|
|
299
|
+
data,
|
|
300
|
+
confidence,
|
|
301
|
+
comp.sourcePath,
|
|
302
|
+
scanPath,
|
|
303
|
+
enrichment
|
|
304
|
+
);
|
|
305
|
+
} else {
|
|
306
|
+
content = generateFragmentWithTodos(
|
|
307
|
+
comp.name,
|
|
308
|
+
importPath,
|
|
309
|
+
data,
|
|
310
|
+
confidence,
|
|
311
|
+
enrichment
|
|
312
|
+
);
|
|
313
|
+
}
|
|
178
314
|
await writeFile(fragmentPath, content, "utf-8");
|
|
179
315
|
const relPath = relative(process.cwd(), fragmentPath);
|
|
180
316
|
generated.push({
|
|
@@ -196,6 +332,10 @@ Phase ${options.enrich ? "4" : "3"}: Generating fragment files...`));
|
|
|
196
332
|
console.log(pc.red(` \u2717 ${comp.name}: ${e instanceof Error ? e.message : String(e)}`));
|
|
197
333
|
}
|
|
198
334
|
}
|
|
335
|
+
if (generated.length > 0) {
|
|
336
|
+
await ensureCoreDependency(scanPath);
|
|
337
|
+
await excludeFragmentsFromTsconfig(scanPath);
|
|
338
|
+
}
|
|
199
339
|
const avgConfidence = generated.length > 0 ? Math.round(
|
|
200
340
|
generated.reduce((sum, g) => sum + g.confidence, 0) / generated.length
|
|
201
341
|
) : 0;
|
|
@@ -450,12 +590,12 @@ Rules:
|
|
|
450
590
|
}
|
|
451
591
|
function buildEnrichmentUserPrompt(name, data) {
|
|
452
592
|
const props = data.meta?.props ?? {};
|
|
453
|
-
const
|
|
593
|
+
const fragmentProps = getFragmentPropEntries(props);
|
|
454
594
|
const composition = data.meta?.composition ?? null;
|
|
455
595
|
const description = data.meta?.description || "";
|
|
456
596
|
const category = inferCategoryFromMeta(
|
|
457
597
|
name,
|
|
458
|
-
Object.fromEntries(
|
|
598
|
+
Object.fromEntries(fragmentProps)
|
|
459
599
|
);
|
|
460
600
|
const lines = [
|
|
461
601
|
`Component: ${name}`,
|
|
@@ -464,10 +604,10 @@ function buildEnrichmentUserPrompt(name, data) {
|
|
|
464
604
|
if (description) {
|
|
465
605
|
lines.push(`Description: ${description}`);
|
|
466
606
|
}
|
|
467
|
-
if (
|
|
607
|
+
if (fragmentProps.length > 0) {
|
|
468
608
|
lines.push("");
|
|
469
609
|
lines.push("Props:");
|
|
470
|
-
for (const [propName, prop] of
|
|
610
|
+
for (const [propName, prop] of fragmentProps) {
|
|
471
611
|
let propLine = ` - ${propName}: ${prop.typeKind}`;
|
|
472
612
|
if (prop.values && prop.values.length > 0) {
|
|
473
613
|
propLine += ` (${prop.values.join(" | ")})`;
|
|
@@ -606,8 +746,8 @@ function calculateFieldConfidence(data, enrichment) {
|
|
|
606
746
|
let score = 0;
|
|
607
747
|
const todoFields = [];
|
|
608
748
|
const props = data.meta?.props ?? {};
|
|
609
|
-
const
|
|
610
|
-
const hasProps =
|
|
749
|
+
const fragmentPropEntries = getFragmentPropEntries(props);
|
|
750
|
+
const hasProps = fragmentPropEntries.length > 0;
|
|
611
751
|
if (hasProps) {
|
|
612
752
|
score += 30;
|
|
613
753
|
}
|
|
@@ -616,10 +756,8 @@ function calculateFieldConfidence(data, enrichment) {
|
|
|
616
756
|
} else {
|
|
617
757
|
todoFields.push("meta.description");
|
|
618
758
|
}
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
);
|
|
622
|
-
const category = inferCategoryFromMeta(data.component.name, localProps);
|
|
759
|
+
const fragmentProps = Object.fromEntries(fragmentPropEntries);
|
|
760
|
+
const category = inferCategoryFromMeta(data.component.name, fragmentProps);
|
|
623
761
|
if (category !== "Components") {
|
|
624
762
|
score += 10;
|
|
625
763
|
} else {
|
|
@@ -629,7 +767,7 @@ function calculateFieldConfidence(data, enrichment) {
|
|
|
629
767
|
score += 25;
|
|
630
768
|
}
|
|
631
769
|
if (hasProps) {
|
|
632
|
-
const allResolved =
|
|
770
|
+
const allResolved = fragmentPropEntries.every(([_, p]) => p.typeKind !== "custom");
|
|
633
771
|
if (allResolved) {
|
|
634
772
|
score += 10;
|
|
635
773
|
}
|
|
@@ -638,7 +776,7 @@ function calculateFieldConfidence(data, enrichment) {
|
|
|
638
776
|
score += 10;
|
|
639
777
|
}
|
|
640
778
|
if (hasProps) {
|
|
641
|
-
const hasDefaults =
|
|
779
|
+
const hasDefaults = fragmentPropEntries.some(([_, p]) => p.default !== void 0);
|
|
642
780
|
if (hasDefaults) {
|
|
643
781
|
score += 5;
|
|
644
782
|
}
|
|
@@ -792,13 +930,55 @@ function inferAccessibilityFromMeta(props) {
|
|
|
792
930
|
if (requirements.length > 0) accessibility.requirements = requirements;
|
|
793
931
|
return accessibility;
|
|
794
932
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
933
|
+
var INHERITED_PROP_PRIORITY = [
|
|
934
|
+
"htmlFor",
|
|
935
|
+
"type",
|
|
936
|
+
"value",
|
|
937
|
+
"defaultValue",
|
|
938
|
+
"checked",
|
|
939
|
+
"defaultChecked",
|
|
940
|
+
"disabled",
|
|
941
|
+
"required",
|
|
942
|
+
"placeholder",
|
|
943
|
+
"name",
|
|
944
|
+
"id",
|
|
945
|
+
"form",
|
|
946
|
+
"accept",
|
|
947
|
+
"multiple",
|
|
948
|
+
"min",
|
|
949
|
+
"max",
|
|
950
|
+
"step",
|
|
951
|
+
"pattern",
|
|
952
|
+
"role",
|
|
953
|
+
"children"
|
|
954
|
+
];
|
|
955
|
+
function getFragmentPropEntries(props) {
|
|
956
|
+
const entries = Object.entries(props);
|
|
957
|
+
const localEntries = entries.filter(([_, prop]) => prop.source === "local");
|
|
958
|
+
if (localEntries.length > 0) {
|
|
959
|
+
return localEntries;
|
|
960
|
+
}
|
|
961
|
+
const prioritized = new Set(INHERITED_PROP_PRIORITY);
|
|
962
|
+
const inheritedEntries = entries.filter(([name, prop]) => {
|
|
963
|
+
if (prop.source !== "inherited") return false;
|
|
964
|
+
if (name === "key" || name === "ref" || name === "className" || name === "style") return false;
|
|
965
|
+
if (name.startsWith("on")) return false;
|
|
966
|
+
if (name.startsWith("aria-") || name.startsWith("data-")) return false;
|
|
967
|
+
return prioritized.has(name);
|
|
968
|
+
}).sort(([nameA], [nameB]) => {
|
|
969
|
+
const rankA = INHERITED_PROP_PRIORITY.indexOf(nameA);
|
|
970
|
+
const rankB = INHERITED_PROP_PRIORITY.indexOf(nameB);
|
|
971
|
+
return rankA - rankB;
|
|
972
|
+
});
|
|
973
|
+
return inheritedEntries.slice(0, 8);
|
|
974
|
+
}
|
|
975
|
+
function buildContractBlock(componentName, props, composition, accessibility, compoundPartRefs = []) {
|
|
976
|
+
const relevantEntries = getFragmentPropEntries(props);
|
|
977
|
+
if (relevantEntries.length === 0 && !composition && !accessibility.requirements?.length) {
|
|
798
978
|
return null;
|
|
799
979
|
}
|
|
800
980
|
const contract = { propsSummary: [] };
|
|
801
|
-
for (const [name, prop] of
|
|
981
|
+
for (const [name, prop] of relevantEntries) {
|
|
802
982
|
let summary = name + ": ";
|
|
803
983
|
if (prop.typeKind === "enum" && prop.values && prop.values.length > 0) {
|
|
804
984
|
summary += prop.values.join(" | ");
|
|
@@ -825,7 +1005,8 @@ function buildContractBlock(componentName, props, composition, accessibility) {
|
|
|
825
1005
|
contract.compoundChildren = children;
|
|
826
1006
|
}
|
|
827
1007
|
if (composition && composition.parts.length > 0) {
|
|
828
|
-
const
|
|
1008
|
+
const resolvedParts = compoundPartRefs.length > 0 ? compoundPartRefs : composition.parts.map((part) => ({ partName: part.name, tagName: `${componentName}.${part.name}` }));
|
|
1009
|
+
const innerLines = resolvedParts.map((part) => ` <${part.tagName}>...</${part.tagName}>`).join("\n");
|
|
829
1010
|
contract.canonicalUsage = [
|
|
830
1011
|
`<${componentName}>
|
|
831
1012
|
${innerLines}
|
|
@@ -909,20 +1090,53 @@ function computeImportPath(fragmentDir, sourcePath, componentBaseName) {
|
|
|
909
1090
|
function escapeQuotes(str) {
|
|
910
1091
|
return str.replace(/'/g, "\\'");
|
|
911
1092
|
}
|
|
1093
|
+
function resolveCompoundPartReferences(componentName, composition, exportNames) {
|
|
1094
|
+
if (!composition || composition.parts.length === 0) return [];
|
|
1095
|
+
return composition.parts.map((part) => {
|
|
1096
|
+
const directExportName = `${componentName}${part.name}`;
|
|
1097
|
+
if (exportNames.includes(directExportName)) {
|
|
1098
|
+
return {
|
|
1099
|
+
partName: part.name,
|
|
1100
|
+
tagName: directExportName,
|
|
1101
|
+
importName: directExportName
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
return {
|
|
1105
|
+
partName: part.name,
|
|
1106
|
+
tagName: `${componentName}.${part.name}`
|
|
1107
|
+
};
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
912
1110
|
function generateFragmentWithTodos(componentName, importPath, data, confidence, enrichment) {
|
|
913
1111
|
const props = data.meta?.props ?? {};
|
|
914
1112
|
const localProps = Object.fromEntries(
|
|
915
1113
|
Object.entries(props).filter(([_, p]) => p.source === "local")
|
|
916
1114
|
);
|
|
1115
|
+
const fragmentProps = Object.fromEntries(getFragmentPropEntries(props));
|
|
1116
|
+
const inferenceProps = Object.keys(localProps).length > 0 ? localProps : fragmentProps;
|
|
917
1117
|
const composition = data.meta?.composition ?? null;
|
|
918
|
-
const description = data.meta?.description || inferDescriptionFromMeta(componentName,
|
|
1118
|
+
const description = data.meta?.description || inferDescriptionFromMeta(componentName, inferenceProps);
|
|
919
1119
|
const descriptionTodo = data.meta?.description ? "" : " // TODO: Review description";
|
|
920
|
-
const category = inferCategoryFromMeta(componentName,
|
|
1120
|
+
const category = inferCategoryFromMeta(componentName, inferenceProps);
|
|
921
1121
|
const categoryTodo = category === "Components" ? " // TODO: Set correct category" : "";
|
|
922
1122
|
const status = inferStatus(data.component.sourcePath);
|
|
923
|
-
const accessibility = inferAccessibilityFromMeta(
|
|
1123
|
+
const accessibility = inferAccessibilityFromMeta(inferenceProps);
|
|
1124
|
+
const exportNames = data.meta?.exports ?? [];
|
|
1125
|
+
const compoundPartRefs = resolveCompoundPartReferences(componentName, composition, exportNames);
|
|
1126
|
+
const importNames = Array.from(
|
|
1127
|
+
/* @__PURE__ */ new Set([
|
|
1128
|
+
componentName,
|
|
1129
|
+
...compoundPartRefs.flatMap((part) => part.importName ? [part.importName] : [])
|
|
1130
|
+
])
|
|
1131
|
+
);
|
|
924
1132
|
const propsBlock = buildPropsBlockFromMeta(props);
|
|
925
|
-
const contract = buildContractBlock(
|
|
1133
|
+
const contract = buildContractBlock(
|
|
1134
|
+
componentName,
|
|
1135
|
+
props,
|
|
1136
|
+
composition,
|
|
1137
|
+
accessibility,
|
|
1138
|
+
compoundPartRefs
|
|
1139
|
+
);
|
|
926
1140
|
if (contract && enrichment) {
|
|
927
1141
|
if (enrichment.a11yRules.length > 0) {
|
|
928
1142
|
contract.a11yRules = enrichment.a11yRules;
|
|
@@ -950,17 +1164,17 @@ function generateFragmentWithTodos(componentName, importPath, data, confidence,
|
|
|
950
1164
|
const variantsBlock = buildVariantsBlock(
|
|
951
1165
|
componentName,
|
|
952
1166
|
data.storyVariants,
|
|
953
|
-
composition
|
|
1167
|
+
composition,
|
|
1168
|
+
compoundPartRefs
|
|
954
1169
|
);
|
|
955
|
-
const aiBlock = buildAIBlock(composition);
|
|
1170
|
+
const aiBlock = buildAIBlock(composition, compoundPartRefs);
|
|
956
1171
|
const provenanceBlock = buildProvenanceBlock(confidence, props);
|
|
957
1172
|
const compoundComment = composition && composition.parts.length > 0 ? `
|
|
958
1173
|
// Compound sub-components detected: ${composition.parts.map((p) => p.name).join(", ")}` : "";
|
|
959
1174
|
return `// Auto-generated by fragments init --scan | Confidence: ${confidence.score}/100
|
|
960
1175
|
// ${confidence.todoFields.length} TODO(s) \u2014 search for "TODO:" and fill in human knowledge${compoundComment}
|
|
961
|
-
import React from 'react';
|
|
962
1176
|
import { defineFragment } from '@fragments-sdk/core';
|
|
963
|
-
import { ${
|
|
1177
|
+
import { ${importNames.join(", ")} } from '${importPath}';
|
|
964
1178
|
|
|
965
1179
|
export default defineFragment({
|
|
966
1180
|
component: ${componentName},
|
|
@@ -983,7 +1197,7 @@ ${provenanceBlock}});
|
|
|
983
1197
|
`;
|
|
984
1198
|
}
|
|
985
1199
|
function buildPropsBlockFromMeta(props) {
|
|
986
|
-
const entries =
|
|
1200
|
+
const entries = getFragmentPropEntries(props);
|
|
987
1201
|
if (entries.length === 0) return "{}";
|
|
988
1202
|
const lines = entries.map(([name, prop]) => {
|
|
989
1203
|
const type = prop.typeKind;
|
|
@@ -1009,12 +1223,12 @@ ${parts.join(",\n")},
|
|
|
1009
1223
|
${lines.join("\n")}
|
|
1010
1224
|
}`;
|
|
1011
1225
|
}
|
|
1012
|
-
function buildVariantsBlock(componentName, storyVariants, composition) {
|
|
1226
|
+
function buildVariantsBlock(componentName, storyVariants, composition, compoundPartRefs = []) {
|
|
1013
1227
|
const entries = [];
|
|
1014
1228
|
const hasDefault = storyVariants.some((v) => v.name === "Default");
|
|
1015
1229
|
if (!hasDefault) {
|
|
1016
1230
|
if (composition && composition.pattern === "compound" && composition.parts.length > 0) {
|
|
1017
|
-
entries.push(formatCompoundVariantEntry(componentName, composition));
|
|
1231
|
+
entries.push(formatCompoundVariantEntry(componentName, composition, compoundPartRefs));
|
|
1018
1232
|
} else {
|
|
1019
1233
|
entries.push(formatVariantEntry(componentName, "Default", `Default ${componentName}`, {}));
|
|
1020
1234
|
}
|
|
@@ -1041,9 +1255,9 @@ function formatVariantEntry(componentName, name, description, args) {
|
|
|
1041
1255
|
render: () => ${jsxCode},
|
|
1042
1256
|
},`;
|
|
1043
1257
|
}
|
|
1044
|
-
function formatCompoundVariantEntry(componentName, composition) {
|
|
1045
|
-
const
|
|
1046
|
-
const innerJsx =
|
|
1258
|
+
function formatCompoundVariantEntry(componentName, composition, compoundPartRefs = []) {
|
|
1259
|
+
const resolvedParts = compoundPartRefs.length > 0 ? compoundPartRefs : composition.parts.map((part) => ({ partName: part.name, tagName: `${componentName}.${part.name}` }));
|
|
1260
|
+
const innerJsx = resolvedParts.map((part) => ` <${part.tagName}>...</${part.tagName}>`).join("\n");
|
|
1047
1261
|
const jsxCode = `<${componentName}>
|
|
1048
1262
|
${innerJsx}
|
|
1049
1263
|
</${componentName}>`;
|
|
@@ -1056,12 +1270,16 @@ ${innerJsx}
|
|
|
1056
1270
|
),
|
|
1057
1271
|
},`;
|
|
1058
1272
|
}
|
|
1059
|
-
function buildAIBlock(composition) {
|
|
1273
|
+
function buildAIBlock(composition, compoundPartRefs = []) {
|
|
1060
1274
|
if (!composition || composition.parts.length === 0) return "";
|
|
1061
|
-
const
|
|
1275
|
+
const resolvedParts = compoundPartRefs.length > 0 ? compoundPartRefs : composition.parts.map((part) => ({
|
|
1276
|
+
partName: part.name,
|
|
1277
|
+
tagName: `Component.${part.name}`
|
|
1278
|
+
}));
|
|
1279
|
+
const subComponents = resolvedParts.map((part) => `'${part.importName ?? part.partName}'`).join(", ");
|
|
1062
1280
|
const pattern = `
|
|
1063
1281
|
<Component>
|
|
1064
|
-
${
|
|
1282
|
+
${resolvedParts.map((part) => ` <${part.tagName}>...</${part.tagName}>`).join("\n")}
|
|
1065
1283
|
</Component>`;
|
|
1066
1284
|
return `
|
|
1067
1285
|
|
|
@@ -1100,6 +1318,73 @@ function buildJsxString(componentName, args) {
|
|
|
1100
1318
|
}
|
|
1101
1319
|
return `<${componentName}${propsStr} />`;
|
|
1102
1320
|
}
|
|
1321
|
+
function generateContractJsonFromScan(componentName, _baseName, data, confidence, sourcePath, scanPath, enrichment) {
|
|
1322
|
+
const props = data.meta?.props ?? {};
|
|
1323
|
+
const localProps = Object.fromEntries(
|
|
1324
|
+
Object.entries(props).filter(([, p]) => p.source === "local")
|
|
1325
|
+
);
|
|
1326
|
+
const effectiveProps = Object.keys(localProps).length > 0 ? localProps : props;
|
|
1327
|
+
const composition = data.meta?.composition ?? null;
|
|
1328
|
+
const description = data.meta?.description || `${componentName} component`;
|
|
1329
|
+
const category = inferCategoryFromMeta(componentName, effectiveProps);
|
|
1330
|
+
const propsSchema = {};
|
|
1331
|
+
for (const [name, prop] of Object.entries(effectiveProps)) {
|
|
1332
|
+
propsSchema[name] = {
|
|
1333
|
+
type: prop.typeKind,
|
|
1334
|
+
description: prop.description ?? "",
|
|
1335
|
+
...prop.values?.length && { values: prop.values },
|
|
1336
|
+
...prop.default !== void 0 && { default: prop.default },
|
|
1337
|
+
...prop.required && { required: true }
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
const propsSummary = Object.entries(effectiveProps).map(([name, prop]) => {
|
|
1341
|
+
let summary = `${name}: `;
|
|
1342
|
+
if (prop.values?.length) {
|
|
1343
|
+
summary += prop.values.join("|");
|
|
1344
|
+
} else {
|
|
1345
|
+
summary += prop.typeKind;
|
|
1346
|
+
}
|
|
1347
|
+
if (prop.default !== void 0) summary += ` (default: ${prop.default})`;
|
|
1348
|
+
if (prop.required) summary += " (required)";
|
|
1349
|
+
return summary;
|
|
1350
|
+
});
|
|
1351
|
+
let ai;
|
|
1352
|
+
if (composition) {
|
|
1353
|
+
ai = {
|
|
1354
|
+
compositionPattern: composition.pattern,
|
|
1355
|
+
subComponents: composition.parts.map((p) => p.name),
|
|
1356
|
+
...composition.required.length > 0 && { requiredChildren: composition.required }
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
const relativeSourcePath = relative(scanPath, sourcePath);
|
|
1360
|
+
const usage = enrichment ? {
|
|
1361
|
+
when: enrichment.when ?? [],
|
|
1362
|
+
whenNot: enrichment.whenNot ?? [],
|
|
1363
|
+
...enrichment.guidelines?.length && { guidelines: enrichment.guidelines }
|
|
1364
|
+
} : {
|
|
1365
|
+
when: [],
|
|
1366
|
+
whenNot: []
|
|
1367
|
+
};
|
|
1368
|
+
const contract = {
|
|
1369
|
+
$schema: "https://usefragments.com/schemas/contract.v1.json",
|
|
1370
|
+
name: componentName,
|
|
1371
|
+
description,
|
|
1372
|
+
category,
|
|
1373
|
+
sourcePath: relativeSourcePath,
|
|
1374
|
+
exportName: componentName,
|
|
1375
|
+
propsSummary,
|
|
1376
|
+
props: propsSchema,
|
|
1377
|
+
usage,
|
|
1378
|
+
...ai && { ai },
|
|
1379
|
+
provenance: {
|
|
1380
|
+
source: "extracted",
|
|
1381
|
+
verified: confidence.score >= 70,
|
|
1382
|
+
frameworkSupport: "native",
|
|
1383
|
+
extractedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
return JSON.stringify(contract, null, 2) + "\n";
|
|
1387
|
+
}
|
|
1103
1388
|
export {
|
|
1104
1389
|
buildEnrichedUsageBlock,
|
|
1105
1390
|
buildEnrichmentSystemPrompt,
|
|
@@ -1107,9 +1392,13 @@ export {
|
|
|
1107
1392
|
calculateFieldConfidence,
|
|
1108
1393
|
detectCompoundComponents,
|
|
1109
1394
|
detectCompoundComponentsFromSource,
|
|
1395
|
+
detectPackageManagerForProject,
|
|
1396
|
+
ensureCoreDependency,
|
|
1397
|
+
excludeFragmentsFromTsconfig,
|
|
1110
1398
|
extractComponentJSDoc,
|
|
1111
1399
|
extractComponentJSDocFromSource,
|
|
1112
1400
|
parseEnrichmentResponse,
|
|
1401
|
+
resolveCoreInstallCommand,
|
|
1113
1402
|
scanGenerate
|
|
1114
1403
|
};
|
|
1115
|
-
//# sourceMappingURL=scan-generate-
|
|
1404
|
+
//# sourceMappingURL=scan-generate-TWRHNU5M.js.map
|