@fragments-sdk/cli 0.7.0 → 0.7.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/bin.js +245 -245
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-XHUDJNN3.js → chunk-32VIEOQY.js} +18 -18
- package/dist/chunk-32VIEOQY.js.map +1 -0
- package/dist/{chunk-CVXKXVOY.js → chunk-5ITIP3ES.js} +27 -27
- package/dist/chunk-5ITIP3ES.js.map +1 -0
- package/dist/{chunk-RVRTRESS.js → chunk-DQHWLAUV.js} +29 -29
- package/dist/chunk-DQHWLAUV.js.map +1 -0
- package/dist/{chunk-TJ34N7C7.js → chunk-GCZMFLDI.js} +30 -32
- package/dist/chunk-GCZMFLDI.js.map +1 -0
- package/dist/{chunk-6JBGU74P.js → chunk-GHYYFAQN.js} +23 -23
- package/dist/chunk-GHYYFAQN.js.map +1 -0
- package/dist/{chunk-NWQ4CJOQ.js → chunk-GKX2HPZ6.js} +40 -40
- package/dist/chunk-GKX2HPZ6.js.map +1 -0
- package/dist/{chunk-7OPWMLOE.js → chunk-U6VTHBNI.js} +110 -110
- package/dist/chunk-U6VTHBNI.js.map +1 -0
- package/dist/{core-W2HYIQW6.js → core-SFHPYR5H.js} +24 -26
- package/dist/{generate-LMTISDIJ.js → generate-54GJAWUY.js} +5 -5
- package/dist/generate-54GJAWUY.js.map +1 -0
- package/dist/index.d.ts +23 -27
- package/dist/index.js +10 -10
- package/dist/{init-7CHRKQ7P.js → init-EIM5WNMP.js} +5 -5
- package/dist/{init-7CHRKQ7P.js.map → init-EIM5WNMP.js.map} +1 -1
- package/dist/mcp-bin.js +73 -73
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-KQBKUS64.js +12 -0
- package/dist/{service-T2L7VLTE.js → service-ED2LNCTU.js} +6 -6
- package/dist/{static-viewer-GBR7YNF3.js → static-viewer-Q4F4QP5M.js} +4 -4
- package/dist/{test-OJRXNDO2.js → test-6VN2DA3S.js} +19 -19
- package/dist/test-6VN2DA3S.js.map +1 -0
- package/dist/{tokens-3BWDESVM.js → tokens-P2B7ZAM3.js} +5 -5
- package/dist/{viewer-SUFOISZM.js → viewer-GM7IQPPB.js} +199 -199
- package/dist/viewer-GM7IQPPB.js.map +1 -0
- package/package.json +2 -2
- package/src/ai.ts +5 -5
- package/src/analyze.ts +11 -11
- package/src/bin.ts +1 -1
- package/src/build.ts +33 -33
- package/src/commands/a11y.ts +6 -6
- package/src/commands/add.ts +11 -11
- package/src/commands/audit.ts +4 -4
- package/src/commands/baseline.ts +3 -3
- package/src/commands/build.ts +8 -8
- package/src/commands/compare.ts +20 -20
- package/src/commands/context.ts +16 -16
- package/src/commands/enhance.ts +36 -36
- package/src/commands/generate.ts +1 -1
- package/src/commands/graph.ts +3 -3
- package/src/commands/init.ts +1 -1
- package/src/commands/link/figma.ts +82 -82
- package/src/commands/link/index.ts +3 -3
- package/src/commands/link/storybook.ts +9 -9
- package/src/commands/list.ts +2 -2
- package/src/commands/reset.ts +15 -15
- package/src/commands/scan.ts +27 -27
- package/src/commands/storygen.ts +24 -24
- package/src/commands/validate.ts +2 -2
- package/src/commands/verify.ts +8 -8
- package/src/core/auto-props.ts +4 -4
- package/src/core/composition.test.ts +36 -36
- package/src/core/composition.ts +19 -19
- package/src/core/config.ts +6 -6
- package/src/core/{defineSegment.ts → defineFragment.ts} +16 -22
- package/src/core/discovery.ts +6 -6
- package/src/core/figma.ts +2 -2
- package/src/core/graph-extractor.test.ts +77 -77
- package/src/core/graph-extractor.ts +32 -32
- package/src/core/importAnalyzer.ts +1 -1
- package/src/core/index.ts +22 -23
- package/src/core/loader.ts +22 -22
- package/src/core/node.ts +5 -5
- package/src/core/parser.ts +31 -31
- package/src/core/previewLoader.ts +1 -1
- package/src/core/schema.ts +16 -16
- package/src/core/storyAdapter.test.ts +87 -87
- package/src/core/storyAdapter.ts +16 -16
- package/src/core/types.ts +21 -26
- package/src/diff.ts +22 -22
- package/src/index.ts +2 -2
- package/src/mcp/server.ts +80 -80
- package/src/migrate/__tests__/utils/utils.test.ts +3 -3
- package/src/migrate/bin.ts +4 -4
- package/src/migrate/converter.ts +16 -16
- package/src/migrate/index.ts +3 -3
- package/src/migrate/migrate.ts +3 -3
- package/src/migrate/parser.ts +8 -8
- package/src/migrate/report.ts +2 -2
- package/src/migrate/types.ts +4 -4
- package/src/screenshot.ts +22 -22
- package/src/service/__tests__/props-extractor.test.ts +15 -15
- package/src/service/analytics.ts +39 -39
- package/src/service/enhance/codebase-scanner.ts +1 -1
- package/src/service/enhance/index.ts +1 -1
- package/src/service/enhance/props-extractor.ts +2 -2
- package/src/service/enhance/types.ts +2 -2
- package/src/service/index.ts +2 -2
- package/src/service/metrics-store.ts +1 -1
- package/src/service/patch-generator.ts +1 -1
- package/src/setup.ts +52 -52
- package/src/shared/dev-server-client.ts +7 -7
- package/src/shared/fragment-loader.ts +59 -0
- package/src/shared/index.ts +1 -1
- package/src/shared/types.ts +4 -4
- package/src/static-viewer.ts +35 -35
- package/src/test/discovery.ts +6 -6
- package/src/test/index.ts +5 -5
- package/src/test/reporters/console.ts +1 -1
- package/src/test/reporters/junit.ts +1 -1
- package/src/test/runner.ts +7 -7
- package/src/test/types.ts +3 -3
- package/src/test/watch.ts +9 -9
- package/src/validators.ts +26 -26
- package/src/viewer/__tests__/render-utils.test.ts +28 -28
- package/src/viewer/__tests__/viewer-integration.test.ts +4 -4
- package/src/viewer/cli/health.ts +26 -26
- package/src/viewer/components/App.tsx +79 -79
- package/src/viewer/components/BottomPanel.tsx +17 -17
- package/src/viewer/components/CodePanel.tsx +3 -3
- package/src/viewer/components/CommandPalette.tsx +11 -11
- package/src/viewer/components/ComponentGraph.tsx +28 -28
- package/src/viewer/components/ComponentHeader.tsx +2 -2
- package/src/viewer/components/ContractPanel.tsx +6 -6
- package/src/viewer/components/FigmaEmbed.tsx +9 -9
- package/src/viewer/components/HealthDashboard.tsx +17 -17
- package/src/viewer/components/InteractionsPanel.tsx +2 -2
- package/src/viewer/components/IsolatedPreviewFrame.tsx +6 -6
- package/src/viewer/components/IsolatedRender.tsx +10 -10
- package/src/viewer/components/LeftSidebar.tsx +28 -28
- package/src/viewer/components/MultiViewportPreview.tsx +14 -14
- package/src/viewer/components/PreviewArea.tsx +11 -11
- package/src/viewer/components/PreviewFrameHost.tsx +51 -51
- package/src/viewer/components/RightSidebar.tsx +9 -9
- package/src/viewer/components/Sidebar.tsx +17 -17
- package/src/viewer/components/StoryRenderer.tsx +2 -2
- package/src/viewer/components/TokenStylePanel.tsx +1 -1
- package/src/viewer/components/UsageSection.tsx +2 -2
- package/src/viewer/components/VariantMatrix.tsx +11 -11
- package/src/viewer/components/VariantRenderer.tsx +3 -3
- package/src/viewer/components/VariantTabs.tsx +2 -2
- package/src/viewer/components/_future/CreatePage.tsx +6 -6
- package/src/viewer/composition-renderer.ts +11 -11
- package/src/viewer/entry.tsx +40 -40
- package/src/viewer/hooks/useFigmaIntegration.ts +1 -1
- package/src/viewer/hooks/usePreviewBridge.ts +5 -5
- package/src/viewer/hooks/useUrlState.ts +6 -6
- package/src/viewer/index.ts +2 -2
- package/src/viewer/intelligence/healthReport.ts +17 -17
- package/src/viewer/intelligence/styleDrift.ts +1 -1
- package/src/viewer/intelligence/usageScanner.ts +1 -1
- package/src/viewer/render-template.html +1 -1
- package/src/viewer/render-utils.ts +21 -21
- package/src/viewer/server.ts +18 -18
- package/src/viewer/utils/detectRelationships.ts +22 -22
- package/src/viewer/vite-plugin.ts +213 -213
- package/dist/chunk-6JBGU74P.js.map +0 -1
- package/dist/chunk-7OPWMLOE.js.map +0 -1
- package/dist/chunk-CVXKXVOY.js.map +0 -1
- package/dist/chunk-NWQ4CJOQ.js.map +0 -1
- package/dist/chunk-RVRTRESS.js.map +0 -1
- package/dist/chunk-TJ34N7C7.js.map +0 -1
- package/dist/chunk-XHUDJNN3.js.map +0 -1
- package/dist/generate-LMTISDIJ.js.map +0 -1
- package/dist/scan-WY23TJCP.js +0 -12
- package/dist/test-OJRXNDO2.js.map +0 -1
- package/dist/viewer-SUFOISZM.js.map +0 -1
- package/src/shared/segment-loader.ts +0 -59
- /package/dist/{core-W2HYIQW6.js.map → core-SFHPYR5H.js.map} +0 -0
- /package/dist/{scan-WY23TJCP.js.map → scan-KQBKUS64.js.map} +0 -0
- /package/dist/{service-T2L7VLTE.js.map → service-ED2LNCTU.js.map} +0 -0
- /package/dist/{static-viewer-GBR7YNF3.js.map → static-viewer-Q4F4QP5M.js.map} +0 -0
- /package/dist/{tokens-3BWDESVM.js.map → tokens-P2B7ZAM3.js.map} +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* fragments link - Link external resources to
|
|
2
|
+
* fragments link - Link external resources to fragments
|
|
3
3
|
*
|
|
4
4
|
* This module provides subcommands for:
|
|
5
|
-
* - figma: Link Figma components to
|
|
6
|
-
* - storybook: Bootstrap
|
|
5
|
+
* - figma: Link Figma components to fragments
|
|
6
|
+
* - storybook: Bootstrap fragments from Storybook stories
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
export { linkFigma, type LinkFigmaOptions, type LinkFigmaResult } from './figma.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* fragments link storybook - Bootstrap
|
|
2
|
+
* fragments link storybook - Bootstrap fragments from Storybook stories
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { writeFile, mkdir } from 'node:fs/promises';
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
detectStorybookConfig,
|
|
11
11
|
discoverStoryFiles as discoverStorybookFiles,
|
|
12
12
|
parseStoryFile,
|
|
13
|
-
|
|
13
|
+
convertToFragment,
|
|
14
14
|
} from '../../migrate/index.js';
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
export interface LinkStorybookOptions {
|
|
20
20
|
/** Path to .storybook/main.* config */
|
|
21
21
|
config?: string;
|
|
22
|
-
/** Output directory for
|
|
22
|
+
/** Output directory for fragment files */
|
|
23
23
|
out?: string;
|
|
24
24
|
/** Skip confirmation prompts */
|
|
25
25
|
yes?: boolean;
|
|
@@ -114,7 +114,7 @@ export async function linkStorybook(
|
|
|
114
114
|
|
|
115
115
|
try {
|
|
116
116
|
const parsed = await parseStoryFile(storyFile);
|
|
117
|
-
const result =
|
|
117
|
+
const result = convertToFragment(parsed);
|
|
118
118
|
|
|
119
119
|
// Determine output path
|
|
120
120
|
const outputFile = out
|
|
@@ -177,7 +177,7 @@ export async function linkStorybook(
|
|
|
177
177
|
|
|
178
178
|
// Dry run stops here
|
|
179
179
|
if (dryRun) {
|
|
180
|
-
console.log(pc.dim(`\n${previews.length}
|
|
180
|
+
console.log(pc.dim(`\n${previews.length} fragment file(s) would be created`));
|
|
181
181
|
console.log(pc.yellow('\n[Dry run - no files were written]'));
|
|
182
182
|
return { success: true, generated: 0 };
|
|
183
183
|
}
|
|
@@ -186,7 +186,7 @@ export async function linkStorybook(
|
|
|
186
186
|
let selectedPreviews = previews;
|
|
187
187
|
|
|
188
188
|
if (yes) {
|
|
189
|
-
console.log(pc.dim(`\n${previews.length}
|
|
189
|
+
console.log(pc.dim(`\n${previews.length} fragment file(s) will be created`));
|
|
190
190
|
} else {
|
|
191
191
|
const { checkbox } = await import('@inquirer/prompts');
|
|
192
192
|
|
|
@@ -222,7 +222,7 @@ export async function linkStorybook(
|
|
|
222
222
|
|
|
223
223
|
// Generate files
|
|
224
224
|
const genTotal = selectedPreviews.length;
|
|
225
|
-
console.log(pc.dim(`\nGenerating ${genTotal}
|
|
225
|
+
console.log(pc.dim(`\nGenerating ${genTotal} fragment file(s)...\n`));
|
|
226
226
|
|
|
227
227
|
let generated = 0;
|
|
228
228
|
let genErrors = 0;
|
|
@@ -231,7 +231,7 @@ export async function linkStorybook(
|
|
|
231
231
|
const storyFile = join(projectRoot, preview.sourceFile);
|
|
232
232
|
try {
|
|
233
233
|
const parsed = await parseStoryFile(storyFile);
|
|
234
|
-
const result =
|
|
234
|
+
const result = convertToFragment(parsed);
|
|
235
235
|
|
|
236
236
|
// Determine output path
|
|
237
237
|
const outputFile = out
|
|
@@ -252,7 +252,7 @@ export async function linkStorybook(
|
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
console.log(pc.green(`\n✓ Generated ${generated}
|
|
255
|
+
console.log(pc.green(`\n✓ Generated ${generated} fragment file(s)\n`));
|
|
256
256
|
|
|
257
257
|
// Next steps
|
|
258
258
|
console.log(pc.dim('───────────────────────────────────────'));
|
package/src/commands/list.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import pc from 'picocolors';
|
|
6
6
|
import { BRAND } from '../core/index.js';
|
|
7
|
-
import { loadConfig,
|
|
7
|
+
import { loadConfig, discoverFragmentFiles } from '../core/node.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Options for list command
|
|
@@ -30,7 +30,7 @@ export interface ListResult {
|
|
|
30
30
|
*/
|
|
31
31
|
export async function list(options: ListOptions = {}): Promise<ListResult> {
|
|
32
32
|
const { config, configDir } = await loadConfig(options.config);
|
|
33
|
-
const files = await
|
|
33
|
+
const files = await discoverFragmentFiles(config, configDir);
|
|
34
34
|
|
|
35
35
|
console.log(pc.cyan(`\n${BRAND.name} - Discovered Fragments\n`));
|
|
36
36
|
|
package/src/commands/reset.ts
CHANGED
|
@@ -42,7 +42,7 @@ export async function reset(options: ResetOptions = {}): Promise<ResetResult> {
|
|
|
42
42
|
const filesToDelete: string[] = [];
|
|
43
43
|
const dirsToDelete: string[] = [];
|
|
44
44
|
|
|
45
|
-
// Check data directory (.
|
|
45
|
+
// Check data directory (.fragments/)
|
|
46
46
|
const dataDir = join(projectRoot, BRAND.dataDir);
|
|
47
47
|
try {
|
|
48
48
|
const dataDirStat = await stat(dataDir);
|
|
@@ -53,8 +53,8 @@ export async function reset(options: ResetOptions = {}): Promise<ResetResult> {
|
|
|
53
53
|
// Directory doesn't exist
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
// Check for
|
|
57
|
-
const defaultOutFile = join(projectRoot, '
|
|
56
|
+
// Check for fragments.json (default output file)
|
|
57
|
+
const defaultOutFile = join(projectRoot, 'fragments.json');
|
|
58
58
|
try {
|
|
59
59
|
const fileStat = await stat(defaultOutFile);
|
|
60
60
|
if (fileStat.isFile()) {
|
|
@@ -65,10 +65,10 @@ export async function reset(options: ResetOptions = {}): Promise<ResetResult> {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// Try to load config for custom outFile and include patterns
|
|
68
|
-
let
|
|
68
|
+
let fragmentPatterns = [`**/*${BRAND.fileExtension}`];
|
|
69
69
|
try {
|
|
70
70
|
const { config } = await loadConfig();
|
|
71
|
-
if (config.outFile && config.outFile !== '
|
|
71
|
+
if (config.outFile && config.outFile !== 'fragments.json') {
|
|
72
72
|
const customOutFile = join(projectRoot, config.outFile);
|
|
73
73
|
try {
|
|
74
74
|
const fileStat = await stat(customOutFile);
|
|
@@ -81,15 +81,15 @@ export async function reset(options: ResetOptions = {}): Promise<ResetResult> {
|
|
|
81
81
|
}
|
|
82
82
|
// Use config include patterns if available
|
|
83
83
|
if (config.include && config.include.length > 0) {
|
|
84
|
-
|
|
84
|
+
fragmentPatterns = config.include;
|
|
85
85
|
}
|
|
86
86
|
} catch {
|
|
87
87
|
// No config file, use defaults
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
// Find all
|
|
90
|
+
// Find all fragment files (*.fragment.tsx)
|
|
91
91
|
console.log(pc.dim('Scanning for generated files...\n'));
|
|
92
|
-
for (const pattern of
|
|
92
|
+
for (const pattern of fragmentPatterns) {
|
|
93
93
|
const matches = await fg(pattern, {
|
|
94
94
|
cwd: projectRoot,
|
|
95
95
|
ignore: ['**/node_modules/**'],
|
|
@@ -130,23 +130,23 @@ export async function reset(options: ResetOptions = {}): Promise<ResetResult> {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
// Group files by type for cleaner output
|
|
133
|
-
const
|
|
133
|
+
const fragmentFiles = filesToDelete.filter((f) => f.endsWith(BRAND.fileExtension));
|
|
134
134
|
const mdxFilesFound = filesToDelete.filter((f) => f.endsWith('.mdx'));
|
|
135
135
|
const otherFiles = filesToDelete.filter(
|
|
136
136
|
(f) => !f.endsWith(BRAND.fileExtension) && !f.endsWith('.mdx')
|
|
137
137
|
);
|
|
138
138
|
|
|
139
|
-
if (
|
|
140
|
-
console.log(` 📄 ${
|
|
141
|
-
if (
|
|
142
|
-
for (const f of
|
|
139
|
+
if (fragmentFiles.length > 0) {
|
|
140
|
+
console.log(` 📄 ${fragmentFiles.length} fragment file(s) (*${BRAND.fileExtension})`);
|
|
141
|
+
if (fragmentFiles.length <= 5) {
|
|
142
|
+
for (const f of fragmentFiles) {
|
|
143
143
|
console.log(pc.dim(` ${relative(projectRoot, f)}`));
|
|
144
144
|
}
|
|
145
145
|
} else {
|
|
146
|
-
for (const f of
|
|
146
|
+
for (const f of fragmentFiles.slice(0, 3)) {
|
|
147
147
|
console.log(pc.dim(` ${relative(projectRoot, f)}`));
|
|
148
148
|
}
|
|
149
|
-
console.log(pc.dim(` ... and ${
|
|
149
|
+
console.log(pc.dim(` ... and ${fragmentFiles.length - 3} more`));
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
package/src/commands/scan.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* fragments scan - Zero-config
|
|
2
|
+
* fragments scan - Zero-config fragments.json generation from source code
|
|
3
3
|
*
|
|
4
4
|
* Automatically extracts component documentation by:
|
|
5
5
|
* 1. Discovering components from source files and barrel exports
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 3. Scanning codebase for usage patterns
|
|
8
8
|
* 4. Parsing Storybook stories for examples
|
|
9
9
|
* 5. Inferring component relationships from usage data
|
|
10
|
-
* 6. Generating complete
|
|
10
|
+
* 6. Generating complete fragments.json without manual documentation
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { writeFile, mkdir } from "node:fs/promises";
|
|
@@ -15,8 +15,8 @@ import { resolve, join, dirname, relative } from "node:path";
|
|
|
15
15
|
import pc from "picocolors";
|
|
16
16
|
import {
|
|
17
17
|
BRAND,
|
|
18
|
-
type
|
|
19
|
-
type
|
|
18
|
+
type CompiledFragmentsFile,
|
|
19
|
+
type CompiledFragment,
|
|
20
20
|
type PropDefinition,
|
|
21
21
|
} from "../core/index.js";
|
|
22
22
|
import {
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
generateComponentContext,
|
|
33
33
|
generateEnhancementSuggestions,
|
|
34
34
|
filterBoilerplate,
|
|
35
|
-
|
|
35
|
+
convertToFragmentProps,
|
|
36
36
|
type UsageAnalysis,
|
|
37
37
|
type ComponentRelation,
|
|
38
38
|
type PropsExtractionResult,
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
export interface ScanOptions {
|
|
43
43
|
/** Path to config file */
|
|
44
44
|
config?: string;
|
|
45
|
-
/** Output file path (default:
|
|
45
|
+
/** Output file path (default: fragments.json) */
|
|
46
46
|
output?: string;
|
|
47
47
|
/** Component patterns to scan */
|
|
48
48
|
componentPatterns?: string[];
|
|
@@ -71,7 +71,7 @@ export interface ScanResult {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* Scan codebase and generate
|
|
74
|
+
* Scan codebase and generate fragments.json directly from source
|
|
75
75
|
*/
|
|
76
76
|
export async function scan(options: ScanOptions = {}): Promise<ScanResult> {
|
|
77
77
|
const startTime = Date.now();
|
|
@@ -86,17 +86,17 @@ export async function scan(options: ScanOptions = {}): Promise<ScanResult> {
|
|
|
86
86
|
try {
|
|
87
87
|
const loaded = await loadConfig(options.config);
|
|
88
88
|
configDir = loaded.configDir;
|
|
89
|
-
outputFile = options.output || loaded.config.outFile || "
|
|
89
|
+
outputFile = options.output || loaded.config.outFile || "fragments.json";
|
|
90
90
|
componentPatterns = options.componentPatterns || loaded.config.components;
|
|
91
91
|
} catch {
|
|
92
92
|
// No config file, use defaults
|
|
93
93
|
configDir = process.cwd();
|
|
94
|
-
outputFile = options.output || "
|
|
94
|
+
outputFile = options.output || "fragments.json";
|
|
95
95
|
componentPatterns = options.componentPatterns;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
console.log(pc.cyan(`\n${BRAND.name} Scan\n`));
|
|
99
|
-
console.log(pc.dim("Zero-config
|
|
99
|
+
console.log(pc.dim("Zero-config fragments.json generation from source code\n"));
|
|
100
100
|
|
|
101
101
|
// Phase 1: Discover components
|
|
102
102
|
console.log(pc.dim("Phase 1: Discovering components..."));
|
|
@@ -133,7 +133,7 @@ export async function scan(options: ScanOptions = {}): Promise<ScanResult> {
|
|
|
133
133
|
|
|
134
134
|
// Phase 2: Extract props from TypeScript
|
|
135
135
|
console.log(pc.dim("\nPhase 2: Extracting props from TypeScript..."));
|
|
136
|
-
const propsMap = new Map<string, ReturnType<typeof
|
|
136
|
+
const propsMap = new Map<string, ReturnType<typeof convertToFragmentProps>>();
|
|
137
137
|
const propsResults = new Map<string, PropsExtractionResult>();
|
|
138
138
|
let propsExtracted = 0;
|
|
139
139
|
|
|
@@ -146,7 +146,7 @@ export async function scan(options: ScanOptions = {}): Promise<ScanResult> {
|
|
|
146
146
|
propsResults.set(comp.name, extraction);
|
|
147
147
|
|
|
148
148
|
if (extraction.success && extraction.props.length > 0) {
|
|
149
|
-
propsMap.set(comp.name,
|
|
149
|
+
propsMap.set(comp.name, convertToFragmentProps(extraction.props));
|
|
150
150
|
propsExtracted++;
|
|
151
151
|
}
|
|
152
152
|
} catch (e) {
|
|
@@ -238,13 +238,13 @@ export async function scan(options: ScanOptions = {}): Promise<ScanResult> {
|
|
|
238
238
|
console.log(pc.dim("\nPhase 4: Skipping Storybook parsing"));
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
// Phase 5: Generate
|
|
242
|
-
console.log(pc.dim("\nPhase 5: Generating
|
|
243
|
-
const
|
|
241
|
+
// Phase 5: Generate fragments
|
|
242
|
+
console.log(pc.dim("\nPhase 5: Generating fragments..."));
|
|
243
|
+
const fragments: Record<string, CompiledFragment> = {};
|
|
244
244
|
|
|
245
245
|
for (const comp of components) {
|
|
246
246
|
try {
|
|
247
|
-
const
|
|
247
|
+
const fragment = generateFragmentFromData(
|
|
248
248
|
comp,
|
|
249
249
|
configDir,
|
|
250
250
|
propsMap.get(comp.name),
|
|
@@ -253,7 +253,7 @@ export async function scan(options: ScanOptions = {}): Promise<ScanResult> {
|
|
|
253
253
|
storiesMap.get(comp.name)
|
|
254
254
|
);
|
|
255
255
|
|
|
256
|
-
|
|
256
|
+
fragments[comp.name] = fragment;
|
|
257
257
|
} catch (e) {
|
|
258
258
|
errors.push({
|
|
259
259
|
component: comp.name,
|
|
@@ -266,10 +266,10 @@ export async function scan(options: ScanOptions = {}): Promise<ScanResult> {
|
|
|
266
266
|
const outputPath = resolve(configDir, outputFile);
|
|
267
267
|
await mkdir(dirname(outputPath), { recursive: true });
|
|
268
268
|
|
|
269
|
-
const output:
|
|
269
|
+
const output: CompiledFragmentsFile = {
|
|
270
270
|
version: "1.0.0",
|
|
271
271
|
generatedAt: new Date().toISOString(),
|
|
272
|
-
|
|
272
|
+
fragments,
|
|
273
273
|
};
|
|
274
274
|
|
|
275
275
|
await writeFile(outputPath, JSON.stringify(output, null, 2));
|
|
@@ -278,9 +278,9 @@ export async function scan(options: ScanOptions = {}): Promise<ScanResult> {
|
|
|
278
278
|
|
|
279
279
|
// Summary
|
|
280
280
|
console.log(pc.dim("\n────────────────────────────────────────"));
|
|
281
|
-
console.log(pc.green(`\n✓ Generated
|
|
281
|
+
console.log(pc.green(`\n✓ Generated fragments.json in ${elapsed}s`));
|
|
282
282
|
console.log(pc.dim(` Output: ${relative(process.cwd(), outputPath)}`));
|
|
283
|
-
console.log(pc.dim(` Components: ${Object.keys(
|
|
283
|
+
console.log(pc.dim(` Components: ${Object.keys(fragments).length}`));
|
|
284
284
|
console.log(pc.dim(` Props extracted: ${propsExtracted}`));
|
|
285
285
|
console.log(pc.dim(` Usages found: ${usagesFound}`));
|
|
286
286
|
console.log(pc.dim(` Relations inferred: ${allRelations.size}`));
|
|
@@ -307,7 +307,7 @@ export async function scan(options: ScanOptions = {}): Promise<ScanResult> {
|
|
|
307
307
|
return {
|
|
308
308
|
success: errors.length === 0,
|
|
309
309
|
outputPath,
|
|
310
|
-
componentCount: Object.keys(
|
|
310
|
+
componentCount: Object.keys(fragments).length,
|
|
311
311
|
propsExtracted,
|
|
312
312
|
usagesFound,
|
|
313
313
|
relationsInferred: allRelations.size,
|
|
@@ -318,16 +318,16 @@ export async function scan(options: ScanOptions = {}): Promise<ScanResult> {
|
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
/**
|
|
321
|
-
* Generate a
|
|
321
|
+
* Generate a CompiledFragment from extracted data
|
|
322
322
|
*/
|
|
323
|
-
function
|
|
323
|
+
function generateFragmentFromData(
|
|
324
324
|
comp: DiscoveredComponent,
|
|
325
325
|
configDir: string,
|
|
326
326
|
props: Record<string, PropDefinition> | undefined,
|
|
327
327
|
usageAnalysis: UsageAnalysis["components"][string] | undefined,
|
|
328
328
|
relations: ComponentRelation[] | undefined,
|
|
329
329
|
storyFile: ParsedStoryFile | undefined
|
|
330
|
-
):
|
|
330
|
+
): CompiledFragment {
|
|
331
331
|
// Generate context for AI suggestions
|
|
332
332
|
const context = generateComponentContext(
|
|
333
333
|
comp.name,
|
|
@@ -352,7 +352,7 @@ function generateSegmentFromData(
|
|
|
352
352
|
const status = inferStatus(comp.relativePath);
|
|
353
353
|
|
|
354
354
|
// Build variants from stories
|
|
355
|
-
const variants:
|
|
355
|
+
const variants: CompiledFragment["variants"] = [];
|
|
356
356
|
if (storyFile?.stories) {
|
|
357
357
|
for (const story of storyFile.stories) {
|
|
358
358
|
variants.push({
|
|
@@ -364,7 +364,7 @@ function generateSegmentFromData(
|
|
|
364
364
|
}
|
|
365
365
|
|
|
366
366
|
// Build relations
|
|
367
|
-
const compiledRelations:
|
|
367
|
+
const compiledRelations: CompiledFragment["relations"] = [];
|
|
368
368
|
if (relations && relations.length > 0) {
|
|
369
369
|
for (const rel of relations.slice(0, 10)) {
|
|
370
370
|
compiledRelations.push({
|
package/src/commands/storygen.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* fragments storygen - Generate Storybook stories from
|
|
2
|
+
* fragments storygen - Generate Storybook stories from fragment definitions
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { writeFile, mkdir } from 'node:fs/promises';
|
|
6
6
|
import { resolve, join, relative } from 'node:path';
|
|
7
7
|
import pc from 'picocolors';
|
|
8
8
|
import { BRAND } from '../core/index.js';
|
|
9
|
-
import type {
|
|
10
|
-
import { loadConfig,
|
|
9
|
+
import type { FragmentDefinition } from '../core/index.js';
|
|
10
|
+
import { loadConfig, discoverFragmentFiles, loadFragmentFile } from '../core/node.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Options for storygen command
|
|
@@ -17,7 +17,7 @@ export interface StorygenOptions {
|
|
|
17
17
|
config?: string;
|
|
18
18
|
/** Output directory */
|
|
19
19
|
output?: string;
|
|
20
|
-
/** Watch for
|
|
20
|
+
/** Watch for fragment changes and regenerate */
|
|
21
21
|
watch?: boolean;
|
|
22
22
|
/** Story format (csf3) */
|
|
23
23
|
format?: string;
|
|
@@ -42,11 +42,11 @@ export async function storygen(options: StorygenOptions = {}): Promise<StorygenR
|
|
|
42
42
|
|
|
43
43
|
console.log(pc.cyan(`\n${BRAND.name} Story Generator\n`));
|
|
44
44
|
|
|
45
|
-
// Discover
|
|
46
|
-
const
|
|
45
|
+
// Discover fragment files
|
|
46
|
+
const fragmentFiles = await discoverFragmentFiles(config, configDir);
|
|
47
47
|
|
|
48
|
-
if (
|
|
49
|
-
console.log(pc.yellow('No
|
|
48
|
+
if (fragmentFiles.length === 0) {
|
|
49
|
+
console.log(pc.yellow('No fragment files found.\n'));
|
|
50
50
|
return { success: true, generated: 0, outputDir: output };
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -55,14 +55,14 @@ export async function storygen(options: StorygenOptions = {}): Promise<StorygenR
|
|
|
55
55
|
|
|
56
56
|
let generated = 0;
|
|
57
57
|
|
|
58
|
-
// Generate function for a single
|
|
58
|
+
// Generate function for a single fragment
|
|
59
59
|
const generateStory = async (file: { absolutePath: string; relativePath: string }) => {
|
|
60
60
|
try {
|
|
61
|
-
const
|
|
62
|
-
if (!
|
|
61
|
+
const fragment = await loadFragmentFile(file.absolutePath);
|
|
62
|
+
if (!fragment) return false;
|
|
63
63
|
|
|
64
|
-
const storyContent = generateCSF3Story(
|
|
65
|
-
const storyName = `${
|
|
64
|
+
const storyContent = generateCSF3Story(fragment, file.relativePath);
|
|
65
|
+
const storyName = `${fragment.meta.name}.stories.tsx`;
|
|
66
66
|
const storyPath = join(outputDir, storyName);
|
|
67
67
|
|
|
68
68
|
await writeFile(storyPath, storyContent);
|
|
@@ -77,7 +77,7 @@ export async function storygen(options: StorygenOptions = {}): Promise<StorygenR
|
|
|
77
77
|
// Initial generation
|
|
78
78
|
console.log(pc.dim(`Generating stories to ${relative(process.cwd(), outputDir)}/\n`));
|
|
79
79
|
|
|
80
|
-
for (const file of
|
|
80
|
+
for (const file of fragmentFiles) {
|
|
81
81
|
if (await generateStory(file)) {
|
|
82
82
|
generated++;
|
|
83
83
|
}
|
|
@@ -88,10 +88,10 @@ export async function storygen(options: StorygenOptions = {}): Promise<StorygenR
|
|
|
88
88
|
|
|
89
89
|
// Watch mode
|
|
90
90
|
if (watch) {
|
|
91
|
-
console.log(pc.dim('Watching for
|
|
91
|
+
console.log(pc.dim('Watching for fragment changes... (Ctrl+C to stop)\n'));
|
|
92
92
|
|
|
93
93
|
const chokidar = await import('chokidar');
|
|
94
|
-
const patterns =
|
|
94
|
+
const patterns = fragmentFiles.map(f => f.absolutePath);
|
|
95
95
|
|
|
96
96
|
const watcher = chokidar.watch(patterns, {
|
|
97
97
|
ignoreInitial: true,
|
|
@@ -99,7 +99,7 @@ export async function storygen(options: StorygenOptions = {}): Promise<StorygenR
|
|
|
99
99
|
});
|
|
100
100
|
|
|
101
101
|
watcher.on('change', async (changedPath: string) => {
|
|
102
|
-
const file =
|
|
102
|
+
const file = fragmentFiles.find(f => f.absolutePath === changedPath);
|
|
103
103
|
if (file) {
|
|
104
104
|
console.log(pc.dim(`\nChanged: ${relative(process.cwd(), changedPath)}`));
|
|
105
105
|
await generateStory(file);
|
|
@@ -109,10 +109,10 @@ export async function storygen(options: StorygenOptions = {}): Promise<StorygenR
|
|
|
109
109
|
watcher.on('add', async (addedPath: string) => {
|
|
110
110
|
console.log(pc.dim(`\nAdded: ${relative(process.cwd(), addedPath)}`));
|
|
111
111
|
// Re-discover to get the new file's relative path
|
|
112
|
-
const newFiles = await
|
|
112
|
+
const newFiles = await discoverFragmentFiles(config, configDir);
|
|
113
113
|
const file = newFiles.find(f => f.absolutePath === addedPath);
|
|
114
114
|
if (file) {
|
|
115
|
-
|
|
115
|
+
fragmentFiles.push(file);
|
|
116
116
|
await generateStory(file);
|
|
117
117
|
}
|
|
118
118
|
});
|
|
@@ -125,10 +125,10 @@ export async function storygen(options: StorygenOptions = {}): Promise<StorygenR
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
|
-
* Generate a CSF3 story file from a
|
|
128
|
+
* Generate a CSF3 story file from a fragment definition
|
|
129
129
|
*/
|
|
130
|
-
function generateCSF3Story(
|
|
131
|
-
const { meta, variants, props, usage } =
|
|
130
|
+
function generateCSF3Story(fragment: FragmentDefinition, relativePath: string): string {
|
|
131
|
+
const { meta, variants, props, usage } = fragment;
|
|
132
132
|
const componentName = meta.name;
|
|
133
133
|
|
|
134
134
|
// Build argTypes from props
|
|
@@ -178,11 +178,11 @@ ${usage.whenNot?.map(w => `- ${w}`).join('\n') || ''}`
|
|
|
178
178
|
return `/**
|
|
179
179
|
* Auto-generated Storybook stories from ${relativePath}
|
|
180
180
|
*
|
|
181
|
-
* DO NOT EDIT - regenerate with:
|
|
181
|
+
* DO NOT EDIT - regenerate with: fragments storygen
|
|
182
182
|
*/
|
|
183
183
|
|
|
184
184
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
185
|
-
import { ${componentName} } from '${relativePath.replace(/\.
|
|
185
|
+
import { ${componentName} } from '${relativePath.replace(/\.fragment\.tsx$/, '/index.js')}';
|
|
186
186
|
|
|
187
187
|
const meta: Meta<typeof ${componentName}> = {
|
|
188
188
|
title: '${meta.category ? `${meta.category.charAt(0).toUpperCase() + meta.category.slice(1)}/` : ''}${componentName}',
|
package/src/commands/validate.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* fragments validate - Validate
|
|
2
|
+
* fragments validate - Validate fragment files
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import pc from 'picocolors';
|
|
@@ -13,7 +13,7 @@ import { validateSchema, validateCoverage, validateAll } from '../validators.js'
|
|
|
13
13
|
export interface ValidateOptions {
|
|
14
14
|
/** Path to config file */
|
|
15
15
|
config?: string;
|
|
16
|
-
/** Validate
|
|
16
|
+
/** Validate fragment schema only */
|
|
17
17
|
schema?: boolean;
|
|
18
18
|
/** Validate coverage only */
|
|
19
19
|
coverage?: boolean;
|
package/src/commands/verify.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* fragments verify - Verify component compliance for CI pipelines
|
|
3
3
|
*
|
|
4
|
-
* Uses the /
|
|
4
|
+
* Uses the /fragments/compliance endpoint to get real token compliance data
|
|
5
5
|
* based on computed styles compared against the design token registry.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
DevServerConnectionError,
|
|
14
14
|
type ComplianceResult,
|
|
15
15
|
type ViolationItem,
|
|
16
|
-
type
|
|
16
|
+
type FragmentInfo,
|
|
17
17
|
} from '../shared/index.js';
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -95,16 +95,16 @@ export async function verify(
|
|
|
95
95
|
);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
// Fetch all
|
|
99
|
-
let
|
|
98
|
+
// Fetch all fragments
|
|
99
|
+
let fragments = await client.getFragments();
|
|
100
100
|
|
|
101
101
|
// Filter by component if specified
|
|
102
102
|
if (component) {
|
|
103
|
-
|
|
103
|
+
fragments = fragments.filter(
|
|
104
104
|
s => s.name.toLowerCase() === component.toLowerCase()
|
|
105
105
|
);
|
|
106
106
|
|
|
107
|
-
if (
|
|
107
|
+
if (fragments.length === 0) {
|
|
108
108
|
const error = { error: `Component "${component}" not found` };
|
|
109
109
|
if (ci) {
|
|
110
110
|
console.log(JSON.stringify(error));
|
|
@@ -115,8 +115,8 @@ export async function verify(
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
// Check compliance for each
|
|
119
|
-
for (const seg of
|
|
118
|
+
// Check compliance for each fragment
|
|
119
|
+
for (const seg of fragments) {
|
|
120
120
|
try {
|
|
121
121
|
// Get real compliance from the server
|
|
122
122
|
const complianceResult = await client.getCompliance({
|
package/src/core/auto-props.ts
CHANGED
|
@@ -64,18 +64,18 @@ function resolveModulePath(basePath: string): string | null {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* Resolve a component source file from the
|
|
67
|
+
* Resolve a component source file from the fragment file path + component import path.
|
|
68
68
|
* Supports relative imports like ".", "./Button", "../components/Button".
|
|
69
69
|
*/
|
|
70
70
|
export function resolveComponentSourcePath(
|
|
71
|
-
|
|
71
|
+
fragmentFileAbsolutePath: string,
|
|
72
72
|
componentImportPath: string | null
|
|
73
73
|
): string | null {
|
|
74
74
|
if (!componentImportPath) return null;
|
|
75
75
|
if (!componentImportPath.startsWith(".")) return null;
|
|
76
76
|
|
|
77
|
-
const
|
|
78
|
-
const basePath = resolve(
|
|
77
|
+
const fragmentDir = dirname(fragmentFileAbsolutePath);
|
|
78
|
+
const basePath = resolve(fragmentDir, componentImportPath);
|
|
79
79
|
return resolveModulePath(basePath);
|
|
80
80
|
}
|
|
81
81
|
|