@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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Metrics Store
|
|
3
3
|
*
|
|
4
4
|
* Persists compliance metrics over time for trend analysis.
|
|
5
|
-
* Stores snapshots in .
|
|
5
|
+
* Stores snapshots in .fragments/metrics/ directory.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { readFile, writeFile, mkdir, readdir } from "node:fs/promises";
|
|
@@ -47,7 +47,7 @@ export interface PatchGenerationResult {
|
|
|
47
47
|
* Options for patch generation
|
|
48
48
|
*/
|
|
49
49
|
export interface PatchGenerationOptions {
|
|
50
|
-
/** Source file path (if known from
|
|
50
|
+
/** Source file path (if known from fragment metadata) */
|
|
51
51
|
sourceFile?: string;
|
|
52
52
|
|
|
53
53
|
/** Style type hint */
|
package/src/setup.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import pc from 'picocolors';
|
|
2
2
|
import { BRAND } from './core/index.js';
|
|
3
|
-
import { loadConfig,
|
|
4
|
-
import {
|
|
3
|
+
import { loadConfig, discoverFragmentFiles } from './core/node.js';
|
|
4
|
+
import { buildFragments } from './build.js';
|
|
5
5
|
import {
|
|
6
6
|
detectStorybookConfig,
|
|
7
7
|
discoverStoryFiles as discoverStorybookFiles,
|
|
8
8
|
parseStoryFile,
|
|
9
|
-
|
|
9
|
+
convertToFragment,
|
|
10
10
|
} from './migrate/index.js';
|
|
11
11
|
|
|
12
12
|
export interface SetupOptions {
|
|
@@ -18,68 +18,68 @@ export interface SetupOptions {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export interface SetupResult {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
fragmentFilesCreated: number;
|
|
22
|
+
fragmentsBuilt: number;
|
|
23
23
|
figmaLinked: number;
|
|
24
24
|
errors: string[];
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
interface
|
|
27
|
+
interface FragmentInfo {
|
|
28
28
|
name: string;
|
|
29
29
|
filePath: string;
|
|
30
30
|
hasFigma: boolean;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Check if
|
|
34
|
+
* Check if fragments.json exists and is newer than all fragment files.
|
|
35
35
|
*/
|
|
36
|
-
async function
|
|
36
|
+
async function isFragmentsJsonStale(configDir: string, outFile: string): Promise<{ stale: boolean; missing: boolean }> {
|
|
37
37
|
const fs = await import('node:fs/promises');
|
|
38
38
|
const path = await import('node:path');
|
|
39
39
|
const fg = await import('fast-glob');
|
|
40
40
|
|
|
41
|
-
const
|
|
41
|
+
const fragmentsJsonPath = path.join(configDir, outFile);
|
|
42
42
|
|
|
43
43
|
try {
|
|
44
|
-
const
|
|
44
|
+
const fragmentsJsonStat = await fs.stat(fragmentsJsonPath);
|
|
45
45
|
|
|
46
|
-
// Find all
|
|
47
|
-
const
|
|
46
|
+
// Find all fragment files
|
|
47
|
+
const fragmentFiles = await fg.default(`**/*${BRAND.fileExtension}`, {
|
|
48
48
|
cwd: configDir,
|
|
49
49
|
ignore: ['**/node_modules/**'],
|
|
50
50
|
absolute: true,
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
// Check if any
|
|
54
|
-
for (const file of
|
|
53
|
+
// Check if any fragment file is newer than fragments.json
|
|
54
|
+
for (const file of fragmentFiles) {
|
|
55
55
|
const stat = await fs.stat(file);
|
|
56
|
-
if (stat.mtimeMs >
|
|
56
|
+
if (stat.mtimeMs > fragmentsJsonStat.mtimeMs) {
|
|
57
57
|
return { stale: true, missing: false };
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
return { stale: false, missing: false };
|
|
62
62
|
} catch {
|
|
63
|
-
//
|
|
63
|
+
// fragments.json doesn't exist
|
|
64
64
|
return { stale: false, missing: true };
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
|
-
* Load
|
|
69
|
+
* Load fragment files and check which ones have Figma links.
|
|
70
70
|
*/
|
|
71
|
-
async function
|
|
71
|
+
async function loadFragmentInfo(fragmentFiles: Array<{ absolutePath: string; relativePath: string }>): Promise<FragmentInfo[]> {
|
|
72
72
|
const fs = await import('node:fs/promises');
|
|
73
|
-
const
|
|
73
|
+
const fragments: FragmentInfo[] = [];
|
|
74
74
|
|
|
75
|
-
for (const file of
|
|
75
|
+
for (const file of fragmentFiles) {
|
|
76
76
|
try {
|
|
77
77
|
const content = await fs.readFile(file.absolutePath, 'utf-8');
|
|
78
78
|
const nameMatch = content.match(/name:\s*['"]([^'"]+)['"]/);
|
|
79
79
|
const hasFigma = /meta:\s*\{[^}]*figma:\s*['"]https?:/.test(content);
|
|
80
80
|
|
|
81
81
|
if (nameMatch) {
|
|
82
|
-
|
|
82
|
+
fragments.push({
|
|
83
83
|
name: nameMatch[1],
|
|
84
84
|
filePath: file.absolutePath,
|
|
85
85
|
hasFigma,
|
|
@@ -90,24 +90,24 @@ async function loadSegmentInfo(segmentFiles: Array<{ absolutePath: string; relat
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
return
|
|
93
|
+
return fragments;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
|
-
* Run auto-setup for
|
|
97
|
+
* Run auto-setup for fragments dev server.
|
|
98
98
|
*
|
|
99
99
|
* This function:
|
|
100
|
-
* 1. Checks for
|
|
101
|
-
* 2. Builds
|
|
102
|
-
* 3. Links Figma if configured but no
|
|
100
|
+
* 1. Checks for fragment files, imports from Storybook if none found
|
|
101
|
+
* 2. Builds fragments.json if missing or stale
|
|
102
|
+
* 3. Links Figma if configured but no fragments are linked
|
|
103
103
|
*/
|
|
104
104
|
export async function runSetup(options: SetupOptions = {}): Promise<SetupResult> {
|
|
105
105
|
const fs = await import('node:fs/promises');
|
|
106
106
|
const path = await import('node:path');
|
|
107
107
|
|
|
108
108
|
const result: SetupResult = {
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
fragmentFilesCreated: 0,
|
|
110
|
+
fragmentsBuilt: 0,
|
|
111
111
|
figmaLinked: 0,
|
|
112
112
|
errors: [],
|
|
113
113
|
};
|
|
@@ -120,11 +120,11 @@ export async function runSetup(options: SetupOptions = {}): Promise<SetupResult>
|
|
|
120
120
|
// Load config
|
|
121
121
|
const { config, configDir } = await loadConfig(options.configPath);
|
|
122
122
|
|
|
123
|
-
// Step 1: Check for
|
|
123
|
+
// Step 1: Check for fragment files
|
|
124
124
|
log(pc.dim('Checking for fragment files...'));
|
|
125
|
-
let
|
|
125
|
+
let fragmentFiles = await discoverFragmentFiles(config, configDir);
|
|
126
126
|
|
|
127
|
-
if (
|
|
127
|
+
if (fragmentFiles.length === 0 && !options.skipStorybook) {
|
|
128
128
|
// No fragment files - check for Storybook
|
|
129
129
|
log(pc.yellow('\n No fragment files found'));
|
|
130
130
|
|
|
@@ -141,43 +141,43 @@ export async function runSetup(options: SetupOptions = {}): Promise<SetupResult>
|
|
|
141
141
|
for (const storyFile of storyFiles) {
|
|
142
142
|
try {
|
|
143
143
|
const parsed = await parseStoryFile(storyFile);
|
|
144
|
-
const
|
|
144
|
+
const fragmentResult = convertToFragment(parsed);
|
|
145
145
|
|
|
146
146
|
// Create directory and write file
|
|
147
|
-
await fs.mkdir(path.dirname(
|
|
148
|
-
await fs.writeFile(
|
|
147
|
+
await fs.mkdir(path.dirname(fragmentResult.outputFile), { recursive: true });
|
|
148
|
+
await fs.writeFile(fragmentResult.outputFile, fragmentResult.code);
|
|
149
149
|
converted++;
|
|
150
150
|
} catch {
|
|
151
151
|
// Skip files that can't be converted
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
result.
|
|
155
|
+
result.fragmentFilesCreated = converted;
|
|
156
156
|
log(pc.green(` Generated ${converted} fragment file(s)`));
|
|
157
157
|
|
|
158
|
-
// Refresh
|
|
159
|
-
|
|
158
|
+
// Refresh fragment files list
|
|
159
|
+
fragmentFiles = await discoverFragmentFiles(config, configDir);
|
|
160
160
|
}
|
|
161
161
|
} else {
|
|
162
162
|
log(pc.dim(' No Storybook config found'));
|
|
163
163
|
log(pc.dim(` Run ${pc.cyan(`${BRAND.cliCommand} add <ComponentName>`)} to create your first fragment`));
|
|
164
164
|
}
|
|
165
|
-
} else if (
|
|
166
|
-
log(pc.green(` Found ${
|
|
165
|
+
} else if (fragmentFiles.length > 0) {
|
|
166
|
+
log(pc.green(` Found ${fragmentFiles.length} fragment file(s)`));
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
// Step 2: Build fragments.json if needed
|
|
170
|
-
if (
|
|
170
|
+
if (fragmentFiles.length > 0 && !options.skipBuild) {
|
|
171
171
|
const outFile = config.outFile || BRAND.outFile;
|
|
172
|
-
const { stale, missing } = await
|
|
172
|
+
const { stale, missing } = await isFragmentsJsonStale(configDir, outFile);
|
|
173
173
|
|
|
174
174
|
if (missing || stale) {
|
|
175
175
|
const reason = missing ? 'Building' : 'Rebuilding';
|
|
176
176
|
log(pc.dim(`\n${reason} ${BRAND.outFile}...`));
|
|
177
177
|
|
|
178
178
|
try {
|
|
179
|
-
const buildResult = await
|
|
180
|
-
result.
|
|
179
|
+
const buildResult = await buildFragments(config, configDir);
|
|
180
|
+
result.fragmentsBuilt = buildResult.fragmentCount;
|
|
181
181
|
|
|
182
182
|
if (buildResult.errors.length > 0) {
|
|
183
183
|
for (const err of buildResult.errors) {
|
|
@@ -185,7 +185,7 @@ export async function runSetup(options: SetupOptions = {}): Promise<SetupResult>
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
log(pc.green(` Built ${buildResult.
|
|
188
|
+
log(pc.green(` Built ${buildResult.fragmentCount} fragment(s)`));
|
|
189
189
|
} catch (error) {
|
|
190
190
|
result.errors.push(`Build failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
191
191
|
}
|
|
@@ -196,16 +196,16 @@ export async function runSetup(options: SetupOptions = {}): Promise<SetupResult>
|
|
|
196
196
|
|
|
197
197
|
// Step 3: Link Figma if configured
|
|
198
198
|
if (!options.skipFigma && config.figmaFile && process.env.FIGMA_ACCESS_TOKEN) {
|
|
199
|
-
const
|
|
200
|
-
const linkedCount =
|
|
199
|
+
const fragments = await loadFragmentInfo(fragmentFiles);
|
|
200
|
+
const linkedCount = fragments.filter((s) => s.hasFigma).length;
|
|
201
201
|
|
|
202
|
-
if (linkedCount === 0 &&
|
|
202
|
+
if (linkedCount === 0 && fragments.length > 0) {
|
|
203
203
|
log(pc.dim('\n Figma configured but no fragments linked'));
|
|
204
204
|
log(pc.dim(` Run ${pc.cyan(`${BRAND.cliCommand} link figma --auto`)} to auto-link components`));
|
|
205
205
|
// Note: We don't auto-link here because it requires API calls and user may want control
|
|
206
206
|
// But we inform them about the option
|
|
207
207
|
} else if (linkedCount > 0) {
|
|
208
|
-
log(pc.dim(`\n ${linkedCount}/${
|
|
208
|
+
log(pc.dim(`\n ${linkedCount}/${fragments.length} fragment(s) linked to Figma`));
|
|
209
209
|
}
|
|
210
210
|
} else if (!options.skipFigma && config.figmaFile && !process.env.FIGMA_ACCESS_TOKEN) {
|
|
211
211
|
log(pc.dim('\n Figma file configured but FIGMA_ACCESS_TOKEN not set'));
|
|
@@ -224,12 +224,12 @@ export async function runSetup(options: SetupOptions = {}): Promise<SetupResult>
|
|
|
224
224
|
export function printSetupSummary(result: SetupResult): void {
|
|
225
225
|
console.log();
|
|
226
226
|
|
|
227
|
-
if (result.
|
|
228
|
-
console.log(pc.green(` Imported ${result.
|
|
227
|
+
if (result.fragmentFilesCreated > 0) {
|
|
228
|
+
console.log(pc.green(` Imported ${result.fragmentFilesCreated} fragments from Storybook`));
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
-
if (result.
|
|
232
|
-
console.log(pc.green(` Built ${result.
|
|
231
|
+
if (result.fragmentsBuilt > 0) {
|
|
232
|
+
console.log(pc.green(` Built ${result.fragmentsBuilt} fragments`));
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
if (result.errors.length > 0) {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type {
|
|
9
9
|
ComplianceResult,
|
|
10
|
-
|
|
10
|
+
FragmentInfo,
|
|
11
11
|
ContextData,
|
|
12
12
|
ViolationItem,
|
|
13
13
|
A11yResult,
|
|
@@ -52,7 +52,7 @@ export class DevServerClient {
|
|
|
52
52
|
const controller = new AbortController();
|
|
53
53
|
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
54
54
|
|
|
55
|
-
const response = await fetch(`${this.baseUrl}/
|
|
55
|
+
const response = await fetch(`${this.baseUrl}/fragments/context?format=json`, {
|
|
56
56
|
signal: controller.signal,
|
|
57
57
|
});
|
|
58
58
|
|
|
@@ -64,15 +64,15 @@ export class DevServerClient {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* Get all
|
|
67
|
+
* Get all fragments from the context endpoint
|
|
68
68
|
*/
|
|
69
|
-
async
|
|
70
|
-
const response = await this.fetch('/
|
|
69
|
+
async getFragments(): Promise<FragmentInfo[]> {
|
|
70
|
+
const response = await this.fetch('/fragments/context?format=json');
|
|
71
71
|
const data = await response.json() as {
|
|
72
72
|
components: Record<string, { category?: string; description?: string; status?: string; figma?: string }>;
|
|
73
73
|
};
|
|
74
74
|
|
|
75
|
-
// Transform object to array of
|
|
75
|
+
// Transform object to array of FragmentInfo
|
|
76
76
|
const components = data.components || {};
|
|
77
77
|
return Object.entries(components).map(([name, info]) => ({
|
|
78
78
|
name,
|
|
@@ -87,7 +87,7 @@ export class DevServerClient {
|
|
|
87
87
|
* Get compliance data for a component
|
|
88
88
|
*/
|
|
89
89
|
async getCompliance(request: ComplianceRequest): Promise<ComplianceResult> {
|
|
90
|
-
const response = await this.fetch('/
|
|
90
|
+
const response = await this.fetch('/fragments/compliance', {
|
|
91
91
|
method: 'POST',
|
|
92
92
|
headers: { 'Content-Type': 'application/json' },
|
|
93
93
|
body: JSON.stringify(request),
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fragment Loader
|
|
3
|
+
*
|
|
4
|
+
* Utilities for loading fragment data from the dev server or fragments.json
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { FragmentInfo } from './types.js';
|
|
8
|
+
import { DevServerClient, createDevServerClient } from './dev-server-client.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Options for loading fragments
|
|
12
|
+
*/
|
|
13
|
+
export interface LoadFragmentsOptions {
|
|
14
|
+
/** Dev server port */
|
|
15
|
+
port?: number | string;
|
|
16
|
+
/** Filter by component name */
|
|
17
|
+
component?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Load fragments from the dev server
|
|
22
|
+
*/
|
|
23
|
+
export async function loadFragments(options: LoadFragmentsOptions = {}): Promise<FragmentInfo[]> {
|
|
24
|
+
const { port = 6006, component } = options;
|
|
25
|
+
|
|
26
|
+
const client = createDevServerClient(port);
|
|
27
|
+
let fragments = await client.getFragments();
|
|
28
|
+
|
|
29
|
+
// Filter by component if specified
|
|
30
|
+
if (component) {
|
|
31
|
+
fragments = fragments.filter(
|
|
32
|
+
s => s.name.toLowerCase() === component.toLowerCase()
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return fragments;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Load a single fragment by name
|
|
41
|
+
*/
|
|
42
|
+
export async function loadFragment(
|
|
43
|
+
name: string,
|
|
44
|
+
options: { port?: number | string } = {}
|
|
45
|
+
): Promise<FragmentInfo | null> {
|
|
46
|
+
const fragments = await loadFragments({ ...options, component: name });
|
|
47
|
+
return fragments.length > 0 ? fragments[0] : null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check if a fragment exists
|
|
52
|
+
*/
|
|
53
|
+
export async function fragmentExists(
|
|
54
|
+
name: string,
|
|
55
|
+
options: { port?: number | string } = {}
|
|
56
|
+
): Promise<boolean> {
|
|
57
|
+
const fragment = await loadFragment(name, options);
|
|
58
|
+
return fragment !== null;
|
|
59
|
+
}
|
package/src/shared/index.ts
CHANGED
package/src/shared/types.ts
CHANGED
|
@@ -81,9 +81,9 @@ export interface ComplianceResult {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
|
-
*
|
|
84
|
+
* Fragment information from context endpoint
|
|
85
85
|
*/
|
|
86
|
-
export interface
|
|
86
|
+
export interface FragmentInfo {
|
|
87
87
|
/** Component name */
|
|
88
88
|
name: string;
|
|
89
89
|
/** Component category */
|
|
@@ -107,11 +107,11 @@ export interface CommandResult {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
|
-
* Context data from the /
|
|
110
|
+
* Context data from the /fragments/context endpoint
|
|
111
111
|
*/
|
|
112
112
|
export interface ContextData {
|
|
113
113
|
/** List of components */
|
|
114
|
-
components:
|
|
114
|
+
components: FragmentInfo[];
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
/**
|
package/src/static-viewer.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
-
import type {
|
|
3
|
+
import type { CompiledFragmentsFile } from "./core/index.js";
|
|
4
4
|
import { BRAND } from "./core/index.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Generate a static HTML viewer for the
|
|
7
|
+
* Generate a static HTML viewer for the fragments.json file.
|
|
8
8
|
* This viewer shows component documentation without needing to render actual components.
|
|
9
9
|
*/
|
|
10
|
-
export function generateStaticViewer(data:
|
|
11
|
-
const
|
|
10
|
+
export function generateStaticViewer(data: CompiledFragmentsFile): string {
|
|
11
|
+
const fragments = Object.values(data.fragments);
|
|
12
12
|
|
|
13
13
|
// Group by category
|
|
14
|
-
const categories = new Map<string, typeof
|
|
15
|
-
for (const
|
|
16
|
-
const category =
|
|
14
|
+
const categories = new Map<string, typeof fragments>();
|
|
15
|
+
for (const fragment of fragments) {
|
|
16
|
+
const category = fragment.meta.category ?? "Uncategorized";
|
|
17
17
|
if (!categories.has(category)) {
|
|
18
18
|
categories.set(category, []);
|
|
19
19
|
}
|
|
20
|
-
categories.get(category)!.push(
|
|
20
|
+
categories.get(category)!.push(fragment);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
// Sort categories and components
|
|
@@ -426,11 +426,11 @@ export function generateStaticViewer(data: CompiledSegmentsFile): string {
|
|
|
426
426
|
|
|
427
427
|
<div class="sidebar-stats">
|
|
428
428
|
<div class="stat">
|
|
429
|
-
<div class="stat-value">${
|
|
429
|
+
<div class="stat-value">${fragments.length}</div>
|
|
430
430
|
<div class="stat-label">Components</div>
|
|
431
431
|
</div>
|
|
432
432
|
<div class="stat">
|
|
433
|
-
<div class="stat-value">${
|
|
433
|
+
<div class="stat-value">${fragments.reduce(
|
|
434
434
|
(sum, s) => sum + s.variants.length,
|
|
435
435
|
0
|
|
436
436
|
)}</div>
|
|
@@ -465,34 +465,34 @@ export function generateStaticViewer(data: CompiledSegmentsFile): string {
|
|
|
465
465
|
|
|
466
466
|
<main class="main" id="main">
|
|
467
467
|
${
|
|
468
|
-
|
|
468
|
+
fragments.length === 0
|
|
469
469
|
? `
|
|
470
470
|
<div class="empty-state">
|
|
471
471
|
<h2>No Components Found</h2>
|
|
472
|
-
<p>Run <code>
|
|
472
|
+
<p>Run <code>fragments build</code> to compile your fragment files.</p>
|
|
473
473
|
</div>
|
|
474
474
|
`
|
|
475
|
-
:
|
|
475
|
+
: fragments
|
|
476
476
|
.sort((a, b) => a.meta.name.localeCompare(b.meta.name))
|
|
477
477
|
.map(
|
|
478
|
-
(
|
|
479
|
-
<section class="component-section" id="${
|
|
478
|
+
(fragment) => `
|
|
479
|
+
<section class="component-section" id="${fragment.meta.name}">
|
|
480
480
|
<div class="component-header">
|
|
481
|
-
<h2 class="component-name">${
|
|
482
|
-
<p class="component-description">${
|
|
481
|
+
<h2 class="component-name">${fragment.meta.name}</h2>
|
|
482
|
+
<p class="component-description">${fragment.meta.description}</p>
|
|
483
483
|
<div class="component-meta">
|
|
484
484
|
${
|
|
485
|
-
|
|
486
|
-
? `<span class="meta-badge status-${
|
|
485
|
+
fragment.meta.status
|
|
486
|
+
? `<span class="meta-badge status-${fragment.meta.status}">${fragment.meta.status}</span>`
|
|
487
487
|
: ""
|
|
488
488
|
}
|
|
489
489
|
${
|
|
490
|
-
|
|
491
|
-
? `<span class="meta-badge">${
|
|
490
|
+
fragment.meta.category
|
|
491
|
+
? `<span class="meta-badge">${fragment.meta.category}</span>`
|
|
492
492
|
: ""
|
|
493
493
|
}
|
|
494
494
|
${
|
|
495
|
-
|
|
495
|
+
fragment.meta.tags
|
|
496
496
|
?.map((tag) => `<span class="meta-badge">${tag}</span>`)
|
|
497
497
|
.join("") ?? ""
|
|
498
498
|
}
|
|
@@ -500,15 +500,15 @@ export function generateStaticViewer(data: CompiledSegmentsFile): string {
|
|
|
500
500
|
</div>
|
|
501
501
|
|
|
502
502
|
${
|
|
503
|
-
|
|
503
|
+
fragment.usage
|
|
504
504
|
? `
|
|
505
505
|
<div class="section">
|
|
506
506
|
<h3 class="section-title">Usage Guidelines</h3>
|
|
507
507
|
${
|
|
508
|
-
|
|
508
|
+
fragment.usage.when?.length
|
|
509
509
|
? `
|
|
510
510
|
<ul class="usage-list">
|
|
511
|
-
${
|
|
511
|
+
${fragment.usage.when
|
|
512
512
|
.map((item) => `<li class="usage-item when">${item}</li>`)
|
|
513
513
|
.join("")}
|
|
514
514
|
</ul>
|
|
@@ -516,10 +516,10 @@ export function generateStaticViewer(data: CompiledSegmentsFile): string {
|
|
|
516
516
|
: ""
|
|
517
517
|
}
|
|
518
518
|
${
|
|
519
|
-
|
|
519
|
+
fragment.usage.whenNot?.length
|
|
520
520
|
? `
|
|
521
521
|
<ul class="usage-list" style="margin-top: 12px;">
|
|
522
|
-
${
|
|
522
|
+
${fragment.usage.whenNot
|
|
523
523
|
.map(
|
|
524
524
|
(item) => `<li class="usage-item when-not">${item}</li>`
|
|
525
525
|
)
|
|
@@ -534,7 +534,7 @@ export function generateStaticViewer(data: CompiledSegmentsFile): string {
|
|
|
534
534
|
}
|
|
535
535
|
|
|
536
536
|
${
|
|
537
|
-
|
|
537
|
+
fragment.props && Object.keys(fragment.props).length
|
|
538
538
|
? `
|
|
539
539
|
<div class="section">
|
|
540
540
|
<h3 class="section-title">Props</h3>
|
|
@@ -548,7 +548,7 @@ export function generateStaticViewer(data: CompiledSegmentsFile): string {
|
|
|
548
548
|
</tr>
|
|
549
549
|
</thead>
|
|
550
550
|
<tbody>
|
|
551
|
-
${Object.entries(
|
|
551
|
+
${Object.entries(fragment.props)
|
|
552
552
|
.map(
|
|
553
553
|
([name, prop]) => `
|
|
554
554
|
<tr>
|
|
@@ -579,12 +579,12 @@ export function generateStaticViewer(data: CompiledSegmentsFile): string {
|
|
|
579
579
|
}
|
|
580
580
|
|
|
581
581
|
${
|
|
582
|
-
|
|
582
|
+
fragment.variants?.length
|
|
583
583
|
? `
|
|
584
584
|
<div class="section">
|
|
585
585
|
<h3 class="section-title">Variants</h3>
|
|
586
586
|
<div class="variants-grid">
|
|
587
|
-
${
|
|
587
|
+
${fragment.variants
|
|
588
588
|
.map(
|
|
589
589
|
(variant) => `
|
|
590
590
|
<div class="variant-card">
|
|
@@ -612,12 +612,12 @@ export function generateStaticViewer(data: CompiledSegmentsFile): string {
|
|
|
612
612
|
}
|
|
613
613
|
|
|
614
614
|
${
|
|
615
|
-
|
|
615
|
+
fragment.relations?.length
|
|
616
616
|
? `
|
|
617
617
|
<div class="section">
|
|
618
618
|
<h3 class="section-title">Related Components</h3>
|
|
619
619
|
<div class="relations-list">
|
|
620
|
-
${
|
|
620
|
+
${fragment.relations
|
|
621
621
|
.map(
|
|
622
622
|
(rel) =>
|
|
623
623
|
`<a href="#${
|
|
@@ -704,12 +704,12 @@ function escapeHtml(str: string): string {
|
|
|
704
704
|
}
|
|
705
705
|
|
|
706
706
|
/**
|
|
707
|
-
* Load
|
|
707
|
+
* Load fragments.json and generate static viewer
|
|
708
708
|
*/
|
|
709
709
|
export async function generateViewerFromJson(
|
|
710
710
|
jsonPath: string
|
|
711
711
|
): Promise<string> {
|
|
712
712
|
const content = await readFile(jsonPath, "utf-8");
|
|
713
|
-
const data = JSON.parse(content) as
|
|
713
|
+
const data = JSON.parse(content) as CompiledFragmentsFile;
|
|
714
714
|
return generateStaticViewer(data);
|
|
715
715
|
}
|
package/src/test/discovery.ts
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { readFile } from 'node:fs/promises';
|
|
6
|
-
import type {
|
|
7
|
-
import {
|
|
6
|
+
import type { FragmentsConfig } from '../core/index.js';
|
|
7
|
+
import { discoverFragmentFiles, parseFragmentFile } from '../core/node.js';
|
|
8
8
|
import type { TestCase, DiscoveryOptions } from './types.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Discovered
|
|
11
|
+
* Discovered fragment with play function metadata
|
|
12
12
|
*/
|
|
13
13
|
interface DiscoveredVariant {
|
|
14
14
|
component: string;
|
|
@@ -23,17 +23,17 @@ interface DiscoveredVariant {
|
|
|
23
23
|
* Discover all variants with play functions
|
|
24
24
|
*/
|
|
25
25
|
export async function discoverTests(
|
|
26
|
-
config:
|
|
26
|
+
config: FragmentsConfig,
|
|
27
27
|
configDir: string,
|
|
28
28
|
options: DiscoveryOptions = {}
|
|
29
29
|
): Promise<TestCase[]> {
|
|
30
|
-
const files = await
|
|
30
|
+
const files = await discoverFragmentFiles(config, configDir);
|
|
31
31
|
const variants: DiscoveredVariant[] = [];
|
|
32
32
|
|
|
33
33
|
for (const file of files) {
|
|
34
34
|
try {
|
|
35
35
|
const content = await readFile(file.absolutePath, 'utf-8');
|
|
36
|
-
const parsed =
|
|
36
|
+
const parsed = parseFragmentFile(content, file.relativePath);
|
|
37
37
|
|
|
38
38
|
if (!parsed.meta.name) continue;
|
|
39
39
|
|