@fragments-sdk/cli 0.7.0 → 0.7.2
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/LICENSE +77 -14
- package/dist/bin.js +247 -247
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-CVXKXVOY.js → chunk-3T6QL7IY.js} +47 -29
- package/dist/chunk-3T6QL7IY.js.map +1 -0
- package/dist/{chunk-7OPWMLOE.js → chunk-7KUSBMI4.js} +114 -112
- package/dist/chunk-7KUSBMI4.js.map +1 -0
- package/dist/{chunk-XHUDJNN3.js → chunk-DH4ETVSM.js} +18 -18
- package/dist/chunk-DH4ETVSM.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-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-TJ34N7C7.js → chunk-OOGTG5FM.js} +34 -33
- package/dist/chunk-OOGTG5FM.js.map +1 -0
- package/dist/{core-W2HYIQW6.js → core-UQXZTBFZ.js} +24 -26
- package/dist/{generate-LMTISDIJ.js → generate-GP6ZLAQB.js} +5 -5
- package/dist/generate-GP6ZLAQB.js.map +1 -0
- package/dist/index.d.ts +23 -27
- package/dist/index.js +10 -10
- package/dist/{init-7CHRKQ7P.js → init-W72WBSU2.js} +5 -5
- package/dist/{init-7CHRKQ7P.js.map → init-W72WBSU2.js.map} +1 -1
- package/dist/mcp-bin.js +73 -73
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-V54HWRDY.js +12 -0
- package/dist/{service-T2L7VLTE.js → service-PVGTYUKX.js} +6 -6
- package/dist/{static-viewer-GBR7YNF3.js → static-viewer-KILKIVN7.js} +4 -4
- package/dist/{test-OJRXNDO2.js → test-3YRYQRGV.js} +19 -19
- package/dist/test-3YRYQRGV.js.map +1 -0
- package/dist/{tokens-3BWDESVM.js → tokens-IXSQHPQK.js} +5 -5
- package/dist/{viewer-SUFOISZM.js → viewer-K42REJU2.js} +199 -199
- package/dist/viewer-K42REJU2.js.map +1 -0
- package/package.json +13 -2
- package/src/ai.ts +5 -5
- package/src/analyze.ts +11 -11
- package/src/bin.ts +1 -1
- package/src/build.ts +37 -35
- 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 +5 -5
- 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 +21 -24
- package/src/core/node.ts +5 -5
- package/src/core/parser.ts +71 -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/token-parser.ts +9 -1
- 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-UQXZTBFZ.js.map} +0 -0
- /package/dist/{scan-WY23TJCP.js.map → scan-V54HWRDY.js.map} +0 -0
- /package/dist/{service-T2L7VLTE.js.map → service-PVGTYUKX.js.map} +0 -0
- /package/dist/{static-viewer-GBR7YNF3.js.map → static-viewer-KILKIVN7.js.map} +0 -0
- /package/dist/{tokens-3BWDESVM.js.map → tokens-IXSQHPQK.js.map} +0 -0
package/src/test/index.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { resolve, join } from 'node:path';
|
|
6
6
|
import { mkdir } from 'node:fs/promises';
|
|
7
7
|
import pc from 'picocolors';
|
|
8
|
-
import type {
|
|
8
|
+
import type { FragmentsConfig } from '../core/index.js';
|
|
9
9
|
import type {
|
|
10
10
|
TestConfig,
|
|
11
11
|
DiscoveryOptions,
|
|
@@ -77,7 +77,7 @@ const DEFAULT_OPTIONS = {
|
|
|
77
77
|
* Run the test command
|
|
78
78
|
*/
|
|
79
79
|
export async function runTestCommand(
|
|
80
|
-
config:
|
|
80
|
+
config: FragmentsConfig,
|
|
81
81
|
configDir: string,
|
|
82
82
|
options: TestCommandOptions
|
|
83
83
|
): Promise<number> {
|
|
@@ -138,7 +138,7 @@ export async function runTestCommand(
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
console.log();
|
|
141
|
-
console.log(pc.dim('Add play functions to your
|
|
141
|
+
console.log(pc.dim('Add play functions to your fragment variants to enable testing.'));
|
|
142
142
|
console.log();
|
|
143
143
|
|
|
144
144
|
return opts.ci ? 0 : 0; // Not a failure if no tests
|
|
@@ -201,7 +201,7 @@ function createReporters(
|
|
|
201
201
|
reporters.push(
|
|
202
202
|
createJUnitReporter({
|
|
203
203
|
outputPath: join(outputDir, 'junit.xml'),
|
|
204
|
-
suiteName: '
|
|
204
|
+
suiteName: 'Fragments Tests',
|
|
205
205
|
})
|
|
206
206
|
);
|
|
207
207
|
break;
|
|
@@ -234,7 +234,7 @@ function createReporters(
|
|
|
234
234
|
* List available tests without running them
|
|
235
235
|
*/
|
|
236
236
|
export async function listTests(
|
|
237
|
-
config:
|
|
237
|
+
config: FragmentsConfig,
|
|
238
238
|
configDir: string,
|
|
239
239
|
options: Pick<TestCommandOptions, 'component' | 'tags' | 'grep' | 'exclude'>
|
|
240
240
|
): Promise<void> {
|
|
@@ -29,7 +29,7 @@ export function createConsoleReporter(options: ConsoleReporterOptions = {}): Tes
|
|
|
29
29
|
startTime = Date.now();
|
|
30
30
|
|
|
31
31
|
console.log();
|
|
32
|
-
console.log(pc.cyan(pc.bold('
|
|
32
|
+
console.log(pc.cyan(pc.bold('Fragments Test Runner')));
|
|
33
33
|
console.log(pc.dim(`Running ${count} test${count === 1 ? '' : 's'}...`));
|
|
34
34
|
console.log();
|
|
35
35
|
},
|
|
@@ -26,7 +26,7 @@ export interface JUnitReporterOptions {
|
|
|
26
26
|
* Create a JUnit XML reporter
|
|
27
27
|
*/
|
|
28
28
|
export function createJUnitReporter(options: JUnitReporterOptions): TestReporter {
|
|
29
|
-
const { outputPath, suiteName = '
|
|
29
|
+
const { outputPath, suiteName = 'Fragments Tests', includeProperties = true } = options;
|
|
30
30
|
|
|
31
31
|
return {
|
|
32
32
|
async onRunComplete(result: TestRunResult) {
|
package/src/test/runner.ts
CHANGED
|
@@ -383,18 +383,18 @@ async function executePlayFunction(
|
|
|
383
383
|
): Promise<{ steps: StepResult[]; error?: TestError }> {
|
|
384
384
|
const result = await page.evaluate(
|
|
385
385
|
async ({ component, variant, timeout }: { component: string; variant: string; timeout: number }) => {
|
|
386
|
-
// Find the
|
|
387
|
-
const registry = (window as unknown as {
|
|
388
|
-
.
|
|
386
|
+
// Find the fragment definition in the global registry
|
|
387
|
+
const registry = (window as unknown as { __FRAGMENTS_REGISTRY__?: Map<string, unknown> })
|
|
388
|
+
.__FRAGMENTS_REGISTRY__;
|
|
389
389
|
|
|
390
390
|
if (!registry) {
|
|
391
391
|
return {
|
|
392
392
|
steps: [],
|
|
393
|
-
error: { message: '
|
|
393
|
+
error: { message: 'Fragments registry not found. Make sure the viewer is loaded.' },
|
|
394
394
|
};
|
|
395
395
|
}
|
|
396
396
|
|
|
397
|
-
const
|
|
397
|
+
const fragment = registry.get(component) as {
|
|
398
398
|
variants?: Array<{
|
|
399
399
|
name: string;
|
|
400
400
|
play?: (ctx: {
|
|
@@ -405,14 +405,14 @@ async function executePlayFunction(
|
|
|
405
405
|
}>;
|
|
406
406
|
} | undefined;
|
|
407
407
|
|
|
408
|
-
if (!
|
|
408
|
+
if (!fragment) {
|
|
409
409
|
return {
|
|
410
410
|
steps: [],
|
|
411
411
|
error: { message: `Component "${component}" not found in registry` },
|
|
412
412
|
};
|
|
413
413
|
}
|
|
414
414
|
|
|
415
|
-
const variantDef =
|
|
415
|
+
const variantDef = fragment.variants?.find((v) => v.name === variant);
|
|
416
416
|
if (!variantDef || !variantDef.play) {
|
|
417
417
|
return {
|
|
418
418
|
steps: [],
|
package/src/test/types.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Types for the
|
|
2
|
+
* Types for the fragments test CLI command
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { PlayFunction,
|
|
5
|
+
import type { PlayFunction, FragmentVariant } from '../core/index.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* A test case discovered from
|
|
8
|
+
* A test case discovered from fragment files
|
|
9
9
|
*/
|
|
10
10
|
export interface TestCase {
|
|
11
11
|
/** Unique identifier for this test */
|
package/src/test/watch.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import { watch } from 'node:fs';
|
|
6
6
|
import { resolve, relative } from 'node:path';
|
|
7
7
|
import pc from 'picocolors';
|
|
8
|
-
import type {
|
|
9
|
-
import {
|
|
8
|
+
import type { FragmentsConfig } from '../core/index.js';
|
|
9
|
+
import { discoverFragmentFiles } from '../core/node.js';
|
|
10
10
|
import type { TestCase, RunnerOptions, TestReporter } from './types.js';
|
|
11
11
|
import { discoverTests } from './discovery.js';
|
|
12
12
|
import { runTests } from './runner.js';
|
|
@@ -24,7 +24,7 @@ export interface WatchOptions {
|
|
|
24
24
|
* Start watch mode
|
|
25
25
|
*/
|
|
26
26
|
export async function startWatchMode(
|
|
27
|
-
config:
|
|
27
|
+
config: FragmentsConfig,
|
|
28
28
|
configDir: string,
|
|
29
29
|
runnerOptions: RunnerOptions,
|
|
30
30
|
reporters: TestReporter[],
|
|
@@ -37,14 +37,14 @@ export async function startWatchMode(
|
|
|
37
37
|
const pendingFiles = new Set<string>();
|
|
38
38
|
|
|
39
39
|
// Get files to watch
|
|
40
|
-
const
|
|
40
|
+
const fragmentFiles = await discoverFragmentFiles(config, configDir);
|
|
41
41
|
const watchPaths = new Set<string>();
|
|
42
42
|
|
|
43
|
-
for (const file of
|
|
43
|
+
for (const file of fragmentFiles) {
|
|
44
44
|
watchPaths.add(file.absolutePath);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
// Also watch the directories containing
|
|
47
|
+
// Also watch the directories containing fragment files
|
|
48
48
|
const watchDirs = new Set<string>();
|
|
49
49
|
for (const path of watchPaths) {
|
|
50
50
|
const dir = resolve(path, '..');
|
|
@@ -52,7 +52,7 @@ export async function startWatchMode(
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
console.log();
|
|
55
|
-
console.log(pc.cyan(pc.bold('
|
|
55
|
+
console.log(pc.cyan(pc.bold('Fragments Test Runner - Watch Mode')));
|
|
56
56
|
console.log(pc.dim(`Watching ${watchPaths.size} files in ${watchDirs.size} directories`));
|
|
57
57
|
console.log(pc.dim('Press Ctrl+C to stop'));
|
|
58
58
|
console.log();
|
|
@@ -132,7 +132,7 @@ export async function startWatchMode(
|
|
|
132
132
|
|
|
133
133
|
const fullPath = resolve(dir, filename);
|
|
134
134
|
|
|
135
|
-
// Check if this is a
|
|
135
|
+
// Check if this is a fragment file we care about
|
|
136
136
|
if (!watchPaths.has(fullPath)) return;
|
|
137
137
|
|
|
138
138
|
// Debounce the run
|
|
@@ -185,7 +185,7 @@ export async function startWatchMode(
|
|
|
185
185
|
* Interactive watch mode with keyboard controls
|
|
186
186
|
*/
|
|
187
187
|
export async function startInteractiveWatchMode(
|
|
188
|
-
config:
|
|
188
|
+
config: FragmentsConfig,
|
|
189
189
|
configDir: string,
|
|
190
190
|
runnerOptions: RunnerOptions,
|
|
191
191
|
reporters: TestReporter[]
|
package/src/validators.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fragmentDefinitionSchema, BRAND, type FragmentsConfig } from './core/index.js';
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
discoverFragmentFiles,
|
|
4
4
|
discoverComponentFiles,
|
|
5
5
|
extractComponentName,
|
|
6
|
-
|
|
6
|
+
loadFragmentFile,
|
|
7
7
|
type DiscoveredFile,
|
|
8
8
|
} from './core/node.js';
|
|
9
9
|
|
|
@@ -25,30 +25,30 @@ export interface ValidationWarning {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Validate
|
|
28
|
+
* Validate fragment file schema
|
|
29
29
|
*/
|
|
30
30
|
export async function validateSchema(
|
|
31
|
-
config:
|
|
31
|
+
config: FragmentsConfig,
|
|
32
32
|
configDir: string
|
|
33
33
|
): Promise<ValidationResult> {
|
|
34
|
-
const files = await
|
|
34
|
+
const files = await discoverFragmentFiles(config, configDir);
|
|
35
35
|
const errors: ValidationError[] = [];
|
|
36
36
|
const warnings: ValidationWarning[] = [];
|
|
37
37
|
|
|
38
38
|
for (const file of files) {
|
|
39
39
|
try {
|
|
40
|
-
const
|
|
40
|
+
const fragment = await loadFragmentFile(file.absolutePath);
|
|
41
41
|
|
|
42
|
-
if (!
|
|
42
|
+
if (!fragment) {
|
|
43
43
|
errors.push({
|
|
44
44
|
file: file.relativePath,
|
|
45
45
|
message: 'No default export found',
|
|
46
|
-
details: `
|
|
46
|
+
details: `Fragment files must have a default export from defineFragment()`,
|
|
47
47
|
});
|
|
48
48
|
continue;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const result =
|
|
51
|
+
const result = fragmentDefinitionSchema.safeParse(fragment);
|
|
52
52
|
|
|
53
53
|
if (!result.success) {
|
|
54
54
|
const details = result.error.errors
|
|
@@ -57,14 +57,14 @@ export async function validateSchema(
|
|
|
57
57
|
|
|
58
58
|
errors.push({
|
|
59
59
|
file: file.relativePath,
|
|
60
|
-
message: 'Invalid
|
|
60
|
+
message: 'Invalid fragment schema',
|
|
61
61
|
details,
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
64
|
} catch (error) {
|
|
65
65
|
errors.push({
|
|
66
66
|
file: file.relativePath,
|
|
67
|
-
message: 'Failed to load
|
|
67
|
+
message: 'Failed to load fragment file',
|
|
68
68
|
details: error instanceof Error ? error.message : String(error),
|
|
69
69
|
});
|
|
70
70
|
}
|
|
@@ -78,13 +78,13 @@ export async function validateSchema(
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
|
-
* Validate coverage - ensure all components have
|
|
81
|
+
* Validate coverage - ensure all components have fragments
|
|
82
82
|
*/
|
|
83
83
|
export async function validateCoverage(
|
|
84
|
-
config:
|
|
84
|
+
config: FragmentsConfig,
|
|
85
85
|
configDir: string
|
|
86
86
|
): Promise<ValidationResult> {
|
|
87
|
-
const
|
|
87
|
+
const fragmentFiles = await discoverFragmentFiles(config, configDir);
|
|
88
88
|
const componentFiles = await discoverComponentFiles(config, configDir);
|
|
89
89
|
const errors: ValidationError[] = [];
|
|
90
90
|
const warnings: ValidationWarning[] = [];
|
|
@@ -92,12 +92,12 @@ export async function validateCoverage(
|
|
|
92
92
|
// Build set of documented component names
|
|
93
93
|
const documentedComponents = new Set<string>();
|
|
94
94
|
|
|
95
|
-
for (const file of
|
|
95
|
+
for (const file of fragmentFiles) {
|
|
96
96
|
try {
|
|
97
|
-
const
|
|
97
|
+
const fragment = await loadFragmentFile(file.absolutePath);
|
|
98
98
|
|
|
99
|
-
if (
|
|
100
|
-
documentedComponents.add(
|
|
99
|
+
if (fragment?.meta?.name) {
|
|
100
|
+
documentedComponents.add(fragment.meta.name);
|
|
101
101
|
}
|
|
102
102
|
} catch {
|
|
103
103
|
// Skip files that fail to load - schema validation will catch these
|
|
@@ -108,19 +108,19 @@ export async function validateCoverage(
|
|
|
108
108
|
for (const file of componentFiles) {
|
|
109
109
|
const componentName = extractComponentName(file.relativePath);
|
|
110
110
|
|
|
111
|
-
// Check if there's a corresponding
|
|
112
|
-
const
|
|
111
|
+
// Check if there's a corresponding fragment file
|
|
112
|
+
const fragmentPath = file.relativePath.replace(
|
|
113
113
|
/\.(tsx?|jsx?)$/,
|
|
114
114
|
BRAND.fileExtension
|
|
115
115
|
);
|
|
116
|
-
const
|
|
117
|
-
(s) => s.relativePath ===
|
|
116
|
+
const hasFragmentFile = fragmentFiles.some(
|
|
117
|
+
(s) => s.relativePath === fragmentPath
|
|
118
118
|
);
|
|
119
119
|
|
|
120
|
-
if (!
|
|
120
|
+
if (!hasFragmentFile && !documentedComponents.has(componentName)) {
|
|
121
121
|
warnings.push({
|
|
122
122
|
file: file.relativePath,
|
|
123
|
-
message: `Component "${componentName}" has no
|
|
123
|
+
message: `Component "${componentName}" has no fragment documentation`,
|
|
124
124
|
});
|
|
125
125
|
}
|
|
126
126
|
}
|
|
@@ -136,7 +136,7 @@ export async function validateCoverage(
|
|
|
136
136
|
* Run all validations
|
|
137
137
|
*/
|
|
138
138
|
export async function validateAll(
|
|
139
|
-
config:
|
|
139
|
+
config: FragmentsConfig,
|
|
140
140
|
configDir: string
|
|
141
141
|
): Promise<ValidationResult> {
|
|
142
142
|
const [schemaResult, coverageResult] = await Promise.all([
|
|
@@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest";
|
|
|
2
2
|
import {
|
|
3
3
|
serializeValue,
|
|
4
4
|
serializePropsToJsx,
|
|
5
|
-
|
|
5
|
+
findFragmentByName,
|
|
6
6
|
getAvailableComponents,
|
|
7
7
|
generateRenderScript,
|
|
8
8
|
} from "../render-utils.js";
|
|
@@ -112,66 +112,66 @@ describe("serializePropsToJsx", () => {
|
|
|
112
112
|
});
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
-
describe("
|
|
116
|
-
const
|
|
117
|
-
{ path: "Button.
|
|
118
|
-
{ path: "Card.
|
|
119
|
-
{ path: "Alert.
|
|
115
|
+
describe("findFragmentByName", () => {
|
|
116
|
+
const mockFragments = [
|
|
117
|
+
{ path: "Button.fragment.tsx", fragment: { meta: { name: "Button" } } },
|
|
118
|
+
{ path: "Card.fragment.tsx", fragment: { meta: { name: "Card" } } },
|
|
119
|
+
{ path: "Alert.fragment.tsx", fragment: { meta: { name: "Alert" } } },
|
|
120
120
|
];
|
|
121
121
|
|
|
122
|
-
it("finds
|
|
123
|
-
const result =
|
|
124
|
-
expect(result).toEqual({ name: "Button", path: "Button.
|
|
122
|
+
it("finds fragment by exact name", () => {
|
|
123
|
+
const result = findFragmentByName("Button", mockFragments);
|
|
124
|
+
expect(result).toEqual({ name: "Button", path: "Button.fragment.tsx" });
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
-
it("finds
|
|
128
|
-
expect(
|
|
127
|
+
it("finds fragment case-insensitively", () => {
|
|
128
|
+
expect(findFragmentByName("button", mockFragments)).toEqual({
|
|
129
129
|
name: "Button",
|
|
130
|
-
path: "Button.
|
|
130
|
+
path: "Button.fragment.tsx",
|
|
131
131
|
});
|
|
132
|
-
expect(
|
|
132
|
+
expect(findFragmentByName("BUTTON", mockFragments)).toEqual({
|
|
133
133
|
name: "Button",
|
|
134
|
-
path: "Button.
|
|
134
|
+
path: "Button.fragment.tsx",
|
|
135
135
|
});
|
|
136
|
-
expect(
|
|
136
|
+
expect(findFragmentByName("BuTtOn", mockFragments)).toEqual({
|
|
137
137
|
name: "Button",
|
|
138
|
-
path: "Button.
|
|
138
|
+
path: "Button.fragment.tsx",
|
|
139
139
|
});
|
|
140
140
|
});
|
|
141
141
|
|
|
142
142
|
it("returns null for non-existent component", () => {
|
|
143
|
-
expect(
|
|
144
|
-
expect(
|
|
143
|
+
expect(findFragmentByName("NonExistent", mockFragments)).toBeNull();
|
|
144
|
+
expect(findFragmentByName("", mockFragments)).toBeNull();
|
|
145
145
|
});
|
|
146
146
|
|
|
147
|
-
it("handles empty
|
|
148
|
-
expect(
|
|
147
|
+
it("handles empty fragments array", () => {
|
|
148
|
+
expect(findFragmentByName("Button", [])).toBeNull();
|
|
149
149
|
});
|
|
150
150
|
});
|
|
151
151
|
|
|
152
152
|
describe("getAvailableComponents", () => {
|
|
153
153
|
it("returns sorted list of component names", () => {
|
|
154
|
-
const
|
|
155
|
-
{
|
|
156
|
-
{
|
|
157
|
-
{
|
|
154
|
+
const fragments = [
|
|
155
|
+
{ fragment: { meta: { name: "Zebra" } } },
|
|
156
|
+
{ fragment: { meta: { name: "Alert" } } },
|
|
157
|
+
{ fragment: { meta: { name: "Button" } } },
|
|
158
158
|
];
|
|
159
|
-
expect(getAvailableComponents(
|
|
159
|
+
expect(getAvailableComponents(fragments)).toEqual([
|
|
160
160
|
"Alert",
|
|
161
161
|
"Button",
|
|
162
162
|
"Zebra",
|
|
163
163
|
]);
|
|
164
164
|
});
|
|
165
165
|
|
|
166
|
-
it("returns empty array for empty
|
|
166
|
+
it("returns empty array for empty fragments", () => {
|
|
167
167
|
expect(getAvailableComponents([])).toEqual([]);
|
|
168
168
|
});
|
|
169
169
|
});
|
|
170
170
|
|
|
171
171
|
describe("generateRenderScript", () => {
|
|
172
172
|
it("generates script with correct import path", () => {
|
|
173
|
-
const script = generateRenderScript("/path/to/Button.
|
|
174
|
-
expect(script).toContain('import("/path/to/Button.
|
|
173
|
+
const script = generateRenderScript("/path/to/Button.fragment.tsx", "Button", {});
|
|
174
|
+
expect(script).toContain('import("/path/to/Button.fragment.tsx")');
|
|
175
175
|
});
|
|
176
176
|
|
|
177
177
|
it("generates script with empty props object when no props", () => {
|
|
@@ -68,11 +68,11 @@ describe("@fragments/core alias resolution", () => {
|
|
|
68
68
|
expect(existsSync(corePath)).toBe(true);
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
-
it("core/index.ts exports
|
|
71
|
+
it("core/index.ts exports storyModuleToFragment", async () => {
|
|
72
72
|
const corePath = resolve(cliPackageRoot, "src/core/index.ts");
|
|
73
73
|
const content = await readFile(corePath, "utf-8");
|
|
74
|
-
// The virtual module imports {
|
|
75
|
-
expect(content).toContain("
|
|
74
|
+
// The virtual module imports { storyModuleToFragment, setPreviewConfig }
|
|
75
|
+
expect(content).toContain("storyModuleToFragment");
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
it("core/index.ts exports setPreviewConfig", async () => {
|
|
@@ -91,7 +91,7 @@ describe("virtual module @fragments/core import", () => {
|
|
|
91
91
|
|
|
92
92
|
// The generated virtual module string should reference @fragments/core
|
|
93
93
|
expect(content).toContain(
|
|
94
|
-
'import {
|
|
94
|
+
'import { storyModuleToFragment, setPreviewConfig } from "@fragments/core"'
|
|
95
95
|
);
|
|
96
96
|
});
|
|
97
97
|
|
package/src/viewer/cli/health.ts
CHANGED
|
@@ -42,9 +42,9 @@ function parseOptions(args: string[]): HealthCommandOptions {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* Load
|
|
45
|
+
* Load fragments from the project
|
|
46
46
|
*/
|
|
47
|
-
async function
|
|
47
|
+
async function loadFragments(): Promise<Array<{ path: string; fragment: any }>> {
|
|
48
48
|
const configResult = await loadConfig();
|
|
49
49
|
|
|
50
50
|
if (!configResult) {
|
|
@@ -53,28 +53,28 @@ async function loadSegments(): Promise<Array<{ path: string; segment: any }>> {
|
|
|
53
53
|
|
|
54
54
|
const { config } = configResult;
|
|
55
55
|
|
|
56
|
-
// Try to load compiled
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
if (fs.existsSync(
|
|
60
|
-
const data = JSON.parse(fs.readFileSync(
|
|
61
|
-
return Object.entries(data.
|
|
62
|
-
path:
|
|
63
|
-
|
|
64
|
-
meta:
|
|
65
|
-
usage:
|
|
66
|
-
props:
|
|
67
|
-
variants:
|
|
68
|
-
relations:
|
|
69
|
-
contract:
|
|
70
|
-
_generated:
|
|
56
|
+
// Try to load compiled fragments
|
|
57
|
+
const fragmentsPath = path.resolve(process.cwd(), config.outFile || `${BRAND.dataDir}/${BRAND.outFile}`);
|
|
58
|
+
|
|
59
|
+
if (fs.existsSync(fragmentsPath)) {
|
|
60
|
+
const data = JSON.parse(fs.readFileSync(fragmentsPath, 'utf-8'));
|
|
61
|
+
return Object.entries(data.fragments || {}).map(([name, fragment]: [string, any]) => ({
|
|
62
|
+
path: fragment.filePath || name,
|
|
63
|
+
fragment: {
|
|
64
|
+
meta: fragment.meta || { name, description: '', category: '' },
|
|
65
|
+
usage: fragment.usage || { when: [], whenNot: [] },
|
|
66
|
+
props: fragment.props || {},
|
|
67
|
+
variants: fragment.variants || [],
|
|
68
|
+
relations: fragment.relations,
|
|
69
|
+
contract: fragment.contract,
|
|
70
|
+
_generated: fragment._generated,
|
|
71
71
|
component: () => null, // Placeholder
|
|
72
72
|
},
|
|
73
73
|
}));
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
// Fallback: return empty if no compiled
|
|
77
|
-
console.log(pc.yellow(`No compiled
|
|
76
|
+
// Fallback: return empty if no compiled fragments
|
|
77
|
+
console.log(pc.yellow(`No compiled fragments found. Run \`${BRAND.cliCommand} build\` first for complete analysis.`));
|
|
78
78
|
return [];
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -200,13 +200,13 @@ export async function healthCommand(args: string[]): Promise<void> {
|
|
|
200
200
|
spinner.start();
|
|
201
201
|
|
|
202
202
|
try {
|
|
203
|
-
// Load
|
|
204
|
-
spinner.update('Loading
|
|
205
|
-
const
|
|
203
|
+
// Load fragments
|
|
204
|
+
spinner.update('Loading fragments...');
|
|
205
|
+
const fragments = await loadFragments();
|
|
206
206
|
|
|
207
|
-
if (
|
|
208
|
-
spinner.stop(false, 'No
|
|
209
|
-
console.log(pc.yellow(`\nNo components to analyze. Create
|
|
207
|
+
if (fragments.length === 0) {
|
|
208
|
+
spinner.stop(false, 'No fragments found');
|
|
209
|
+
console.log(pc.yellow(`\nNo components to analyze. Create fragment files or run \`${BRAND.cliCommand} build\`.\n`));
|
|
210
210
|
return;
|
|
211
211
|
}
|
|
212
212
|
|
|
@@ -220,7 +220,7 @@ export async function healthCommand(args: string[]): Promise<void> {
|
|
|
220
220
|
// Generate report
|
|
221
221
|
spinner.update('Generating health report...');
|
|
222
222
|
const report = generateHealthReport({
|
|
223
|
-
|
|
223
|
+
fragments,
|
|
224
224
|
usageScan,
|
|
225
225
|
// Note: Drift reports would require running the viewer and extracting styles
|
|
226
226
|
// For CLI, we focus on what can be statically analyzed
|