@fragments-sdk/cli 0.15.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/dist/{ai-client-I6MDWNYA.js → ai-client-LSLQGOMM.js} +1 -2
  2. package/dist/bin.js +463 -71
  3. package/dist/bin.js.map +1 -1
  4. package/dist/chunk-5JF26E55.js +1255 -0
  5. package/dist/chunk-5JF26E55.js.map +1 -0
  6. package/dist/{chunk-XJQ5BIWI.js → chunk-6SQPP47U.js} +30 -314
  7. package/dist/chunk-6SQPP47U.js.map +1 -0
  8. package/dist/{chunk-65WSVDV5.js → chunk-HQ6A6DTV.js} +1386 -1097
  9. package/dist/chunk-HQ6A6DTV.js.map +1 -0
  10. package/dist/chunk-MHIBEEW4.js +511 -0
  11. package/dist/chunk-MHIBEEW4.js.map +1 -0
  12. package/dist/{chunk-CZD3AD4Q.js → chunk-ONUP6Z4W.js} +17 -6
  13. package/dist/chunk-ONUP6Z4W.js.map +1 -0
  14. package/dist/{codebase-scanner-VOTPXRYW.js → codebase-scanner-MQHUZC2G.js} +1 -2
  15. package/dist/{converter-JLINP7CJ.js → converter-7XM3Y6NJ.js} +1 -2
  16. package/dist/{converter-JLINP7CJ.js.map → converter-7XM3Y6NJ.js.map} +1 -1
  17. package/dist/core/index.js +0 -1
  18. package/dist/create-IH4R45GE.js +806 -0
  19. package/dist/create-IH4R45GE.js.map +1 -0
  20. package/dist/{generate-A4FP5426.js → generate-PVOLUAAC.js} +3 -4
  21. package/dist/{generate-A4FP5426.js.map → generate-PVOLUAAC.js.map} +1 -1
  22. package/dist/{govern-scan-UCBZR6D6.js → govern-scan-OYFZYOQW.js} +142 -9
  23. package/dist/govern-scan-OYFZYOQW.js.map +1 -0
  24. package/dist/index.d.ts +2 -22
  25. package/dist/index.js +8 -7
  26. package/dist/index.js.map +1 -1
  27. package/dist/{init-HGSM35XA.js → init-SSGUSP7Z.js} +3 -4
  28. package/dist/{init-HGSM35XA.js.map → init-SSGUSP7Z.js.map} +1 -1
  29. package/dist/{init-cloud-MQ6GRJAZ.js → init-cloud-3DNKPWFB.js} +29 -4
  30. package/dist/{init-cloud-MQ6GRJAZ.js.map → init-cloud-3DNKPWFB.js.map} +1 -1
  31. package/dist/mcp-bin.js +1 -2
  32. package/dist/mcp-bin.js.map +1 -1
  33. package/dist/node-37AUE74M.js +65 -0
  34. package/dist/push-contracts-WY32TFP6.js +84 -0
  35. package/dist/push-contracts-WY32TFP6.js.map +1 -0
  36. package/dist/{scan-VNNKACG2.js → scan-PKSYSTRR.js} +5 -5
  37. package/dist/{scan-generate-TWRHNU5M.js → scan-generate-VY27PIOX.js} +8 -9
  38. package/dist/scan-generate-VY27PIOX.js.map +1 -0
  39. package/dist/{scanner-7LAZYPWZ.js → scanner-4KZNOXAK.js} +1 -2
  40. package/dist/{service-FHQU7YS7.js → service-QJGWUIVL.js} +16 -9
  41. package/dist/{snapshot-KQEQ6XHL.js → snapshot-WIJMEIFT.js} +1 -2
  42. package/dist/{snapshot-KQEQ6XHL.js.map → snapshot-WIJMEIFT.js.map} +1 -1
  43. package/dist/{static-viewer-63PG6FWY.js → static-viewer-7QIBQZRC.js} +1 -2
  44. package/dist/{test-UQYUCZIS.js → test-64Z5BKBA.js} +2 -3
  45. package/dist/{test-UQYUCZIS.js.map → test-64Z5BKBA.js.map} +1 -1
  46. package/dist/token-normalizer-TEPOVBPV.js +312 -0
  47. package/dist/token-normalizer-TEPOVBPV.js.map +1 -0
  48. package/dist/token-parser-32KOIOFN.js +22 -0
  49. package/dist/token-parser-32KOIOFN.js.map +1 -0
  50. package/dist/{tokens-6GYKDV6U.js → tokens-NZWFQIAB.js} +7 -7
  51. package/dist/{tokens-generate-VTZV5EEW.js → tokens-generate-5JQSJ27E.js} +1 -2
  52. package/dist/{tokens-generate-VTZV5EEW.js.map → tokens-generate-5JQSJ27E.js.map} +1 -1
  53. package/dist/tokens-push-HY3KO36V.js +148 -0
  54. package/dist/tokens-push-HY3KO36V.js.map +1 -0
  55. package/package.json +5 -3
  56. package/src/bin.ts +90 -0
  57. package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/label.contract.json +1 -1
  58. package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/primitive.contract.json +1 -1
  59. package/src/commands/__tests__/build-freshness.test.ts +231 -0
  60. package/src/commands/__tests__/create.test.ts +71 -0
  61. package/src/commands/__tests__/drift-sync.test.ts +1 -1
  62. package/src/commands/__tests__/govern.test.ts +258 -0
  63. package/src/commands/__tests__/init.test.ts +1 -1
  64. package/src/commands/__tests__/scan-generate.test.ts +1 -1
  65. package/src/commands/build.ts +54 -1
  66. package/src/commands/context.ts +1 -1
  67. package/src/commands/create.ts +536 -0
  68. package/src/commands/doctor.ts +3 -2
  69. package/src/commands/govern-scan.ts +187 -8
  70. package/src/commands/govern.ts +65 -2
  71. package/src/commands/init-cloud.ts +32 -4
  72. package/src/commands/push-contracts.ts +112 -0
  73. package/src/commands/scan-generate.ts +1 -1
  74. package/src/commands/scan.ts +13 -0
  75. package/src/commands/sync.ts +2 -2
  76. package/src/commands/tokens-push.ts +199 -0
  77. package/src/core/__tests__/token-resolver.test.ts +1 -1
  78. package/src/core/component-extractor.test.ts +1 -1
  79. package/src/core/drift-verifier.ts +1 -1
  80. package/src/core/extractor-adapter.ts +1 -1
  81. package/src/index.ts +3 -3
  82. package/src/migrate/fragment-to-contract.ts +2 -2
  83. package/src/service/index.ts +8 -0
  84. package/src/service/tailwind-v4-parser.ts +314 -0
  85. package/src/service/token-parser.ts +56 -0
  86. package/src/setup.ts +10 -39
  87. package/src/theme/__tests__/component-contrast.test.ts +2 -2
  88. package/src/theme/__tests__/serializer.test.ts +1 -1
  89. package/src/theme/generator.ts +16 -1
  90. package/src/theme/schema.ts +8 -0
  91. package/src/theme/serializer.ts +13 -9
  92. package/src/theme/types.ts +8 -0
  93. package/src/validators.ts +1 -2
  94. package/dist/chunk-65WSVDV5.js.map +0 -1
  95. package/dist/chunk-7WHVW72L.js +0 -2664
  96. package/dist/chunk-7WHVW72L.js.map +0 -1
  97. package/dist/chunk-CZD3AD4Q.js.map +0 -1
  98. package/dist/chunk-MN3TJ3D5.js +0 -695
  99. package/dist/chunk-MN3TJ3D5.js.map +0 -1
  100. package/dist/chunk-XJQ5BIWI.js.map +0 -1
  101. package/dist/chunk-Z7EY4VHE.js +0 -50
  102. package/dist/govern-scan-UCBZR6D6.js.map +0 -1
  103. package/dist/sass.node-4XJK6YBF.js +0 -130708
  104. package/dist/sass.node-4XJK6YBF.js.map +0 -1
  105. package/dist/scan-generate-TWRHNU5M.js.map +0 -1
  106. package/src/build.ts +0 -736
  107. package/src/core/auto-props.ts +0 -464
  108. package/src/core/component-extractor.ts +0 -1121
  109. package/src/core/token-resolver.ts +0 -155
  110. package/src/viewer/preview-adapter.ts +0 -116
  111. /package/dist/{ai-client-I6MDWNYA.js.map → ai-client-LSLQGOMM.js.map} +0 -0
  112. /package/dist/{chunk-Z7EY4VHE.js.map → codebase-scanner-MQHUZC2G.js.map} +0 -0
  113. /package/dist/{codebase-scanner-VOTPXRYW.js.map → node-37AUE74M.js.map} +0 -0
  114. /package/dist/{scan-VNNKACG2.js.map → scan-PKSYSTRR.js.map} +0 -0
  115. /package/dist/{scanner-7LAZYPWZ.js.map → scanner-4KZNOXAK.js.map} +0 -0
  116. /package/dist/{service-FHQU7YS7.js.map → service-QJGWUIVL.js.map} +0 -0
  117. /package/dist/{static-viewer-63PG6FWY.js.map → static-viewer-7QIBQZRC.js.map} +0 -0
  118. /package/dist/{tokens-6GYKDV6U.js.map → tokens-NZWFQIAB.js.map} +0 -0
package/src/bin.ts CHANGED
@@ -123,6 +123,8 @@ program
123
123
  .option('--registry', `Also generate ${BRAND.dataDir}/${BRAND.registryFile} and ${BRAND.contextFile}`)
124
124
  .option('--registry-only', `Only generate ${BRAND.dataDir}/ directory (skip ${BRAND.outFile})`)
125
125
  .option('--from-source', 'Build from source code (zero-config, no fragment files needed)')
126
+ .option('--if-needed', `Skip rebuilding when ${BRAND.outFile} is already fresh`)
127
+ .option('--check', `Check whether ${BRAND.outFile} is fresh and exit non-zero if it is stale`)
126
128
  .option('--skip-usage', 'Skip usage analysis when building from source')
127
129
  .option('--skip-storybook', 'Skip Storybook parsing when building from source')
128
130
  .option('-v, --verbose', 'Verbose output')
@@ -134,6 +136,8 @@ program
134
136
  registry: options.registry,
135
137
  registryOnly: options.registryOnly,
136
138
  fromSource: options.fromSource,
139
+ ifNeeded: options.ifNeeded,
140
+ check: options.check,
137
141
  skipUsage: options.skipUsage,
138
142
  skipStorybook: options.skipStorybook,
139
143
  verbose: options.verbose,
@@ -791,6 +795,47 @@ program
791
795
  }
792
796
  });
793
797
 
798
+ // ============================================================================
799
+ // CREATE COMMAND
800
+ // ============================================================================
801
+ program
802
+ .command('create')
803
+ .argument('[name]', 'Project name')
804
+ .description('Create a new project with Fragments UI and your custom theme')
805
+ .option('-t, --template <template>', 'Framework template (nextjs, vite)', 'nextjs')
806
+ .option('--pm <manager>', 'Package manager (npm, pnpm, yarn, bun)')
807
+ .option('--theme <encoded>', 'Encoded theme string')
808
+ .option('--preset <id>', 'Theme preset ID from usefragments.com/create')
809
+ .option('--brand <color>', 'Brand color hex (e.g., #6366f1)')
810
+ .option('--scss', 'Use SCSS output (installs sass)')
811
+ .option('--mcp', 'Configure MCP server for AI tooling')
812
+ .option('-y, --yes', 'Skip interactive prompts')
813
+ .option('--no-git', 'Skip git initialization')
814
+ .action(async (name, options) => {
815
+ try {
816
+ const { create } = await import('./commands/create.js');
817
+ const result = await create({
818
+ name,
819
+ template: options.template,
820
+ packageManager: options.pm,
821
+ theme: options.theme,
822
+ preset: options.preset,
823
+ brand: options.brand,
824
+ scss: options.scss,
825
+ mcp: options.mcp,
826
+ yes: options.yes,
827
+ noGit: !options.git,
828
+ });
829
+ if (!result.success) {
830
+ if (result.error) console.error(pc.red(`Error: ${result.error}`));
831
+ process.exit(1);
832
+ }
833
+ } catch (error) {
834
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
835
+ process.exit(1);
836
+ }
837
+ });
838
+
794
839
  // ============================================================================
795
840
  // SETUP COMMAND
796
841
  // ============================================================================
@@ -989,6 +1034,28 @@ tokensCmd
989
1034
  }
990
1035
  });
991
1036
 
1037
+ tokensCmd
1038
+ .command('push')
1039
+ .description('Push code tokens to Fragments Cloud for drift comparison')
1040
+ .option('-c, --config <path>', 'Path to fragments config file')
1041
+ .option('--tailwind-v4 <path>', 'Path to Tailwind v4 CSS file with @theme block')
1042
+ .option('--dry-run', 'Parse and display tokens without pushing')
1043
+ .option('--verbose', 'Show detailed output')
1044
+ .action(async (options) => {
1045
+ try {
1046
+ const { tokensPush } = await import('./commands/tokens-push.js');
1047
+ await tokensPush({
1048
+ config: options.config,
1049
+ tailwindV4: options.tailwindV4,
1050
+ dryRun: options.dryRun,
1051
+ verbose: options.verbose,
1052
+ });
1053
+ } catch (error) {
1054
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
1055
+ process.exit(1);
1056
+ }
1057
+ });
1058
+
992
1059
  // ============================================================================
993
1060
  // GENERATE COMMAND
994
1061
  // ============================================================================
@@ -1361,5 +1428,28 @@ governCmd
1361
1428
  }
1362
1429
  });
1363
1430
 
1431
+ governCmd
1432
+ .command('push-contracts')
1433
+ .description('Push component contracts to Fragments Cloud')
1434
+ .option('-i, --input <path>', 'Path to fragments.json (default: ./fragments.json)')
1435
+ .option('--url <url>', 'Fragments Cloud URL')
1436
+ .option('--api-key <key>', 'API key (default: FRAGMENTS_API_KEY env var)')
1437
+ .option('-q, --quiet', 'Suppress non-error output')
1438
+ .action(async (options) => {
1439
+ try {
1440
+ const { pushContracts } = await import('./commands/push-contracts.js');
1441
+ const { exitCode } = await pushContracts({
1442
+ input: options.input,
1443
+ url: options.url,
1444
+ apiKey: options.apiKey,
1445
+ quiet: options.quiet,
1446
+ });
1447
+ process.exit(exitCode);
1448
+ } catch (error) {
1449
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
1450
+ process.exit(1);
1451
+ }
1452
+ });
1453
+
1364
1454
  // Parse command line arguments
1365
1455
  program.parse();
@@ -37,6 +37,6 @@
37
37
  "source": "extracted",
38
38
  "verified": false,
39
39
  "frameworkSupport": "native",
40
- "extractedAt": "2026-03-13T23:33:02.488Z"
40
+ "extractedAt": "2026-03-22T23:45:29.606Z"
41
41
  }
42
42
  }
@@ -15,6 +15,6 @@
15
15
  "source": "extracted",
16
16
  "verified": false,
17
17
  "frameworkSupport": "native",
18
- "extractedAt": "2026-03-13T23:33:02.489Z"
18
+ "extractedAt": "2026-03-22T23:45:29.607Z"
19
19
  }
20
20
  }
@@ -0,0 +1,231 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
2
+ import { mkdtemp, mkdir, rm, utimes, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+
6
+ import { getFragmentsBuildInputs, getFragmentsJsonStatus, getGeneratorVersion } from '@fragments-sdk/compiler';
7
+ import type { FragmentsConfig } from '@fragments-sdk/core';
8
+
9
+ describe('getFragmentsJsonStatus', () => {
10
+ let tmpDir: string;
11
+
12
+ beforeAll(async () => {
13
+ tmpDir = await mkdtemp(join(tmpdir(), 'build-freshness-test-'));
14
+ });
15
+
16
+ afterAll(async () => {
17
+ await rm(tmpDir, { recursive: true, force: true });
18
+ });
19
+
20
+ async function createProject(projectName: string) {
21
+ const projectDir = join(tmpDir, projectName);
22
+ const srcDir = join(projectDir, 'src', 'components');
23
+ await mkdir(srcDir, { recursive: true });
24
+
25
+ const configPath = join(projectDir, 'fragments.config.ts');
26
+ const fragmentPath = join(srcDir, 'Button.contract.json');
27
+ const componentPath = join(srcDir, 'Button.tsx');
28
+ const outputPath = join(projectDir, 'fragments.json');
29
+
30
+ await writeFile(
31
+ configPath,
32
+ `export default {
33
+ include: ['src/**/*.contract.json'],
34
+ exclude: [],
35
+ components: ['src/**/*.tsx'],
36
+ framework: 'react',
37
+ outFile: 'fragments.json',
38
+ };
39
+ `
40
+ );
41
+
42
+ await writeFile(
43
+ fragmentPath,
44
+ JSON.stringify(
45
+ {
46
+ name: 'Button',
47
+ description: 'Button component',
48
+ category: 'actions',
49
+ variants: [
50
+ {
51
+ name: 'Default',
52
+ code: '<Button>Press</Button>',
53
+ },
54
+ ],
55
+ sourcePath: 'src/components/Button.tsx',
56
+ exportName: 'Button',
57
+ },
58
+ null,
59
+ 2
60
+ )
61
+ );
62
+
63
+ await writeFile(
64
+ componentPath,
65
+ `export function Button() {
66
+ return null;
67
+ }
68
+ `
69
+ );
70
+
71
+ const config: FragmentsConfig = {
72
+ include: ['src/**/*.contract.json'],
73
+ exclude: [],
74
+ components: ['src/**/*.tsx'],
75
+ framework: 'react',
76
+ outFile: 'fragments.json',
77
+ };
78
+
79
+ return {
80
+ config,
81
+ projectDir,
82
+ configPath,
83
+ fragmentPath,
84
+ componentPath,
85
+ outputPath,
86
+ };
87
+ }
88
+
89
+ it('reports missing output files', async () => {
90
+ const project = await createProject('missing-output');
91
+
92
+ const status = await getFragmentsJsonStatus(project.config, project.projectDir, {
93
+ configPath: project.configPath,
94
+ });
95
+
96
+ expect(status.missing).toBe(true);
97
+ expect(status.stale).toBe(false);
98
+ expect(status.reason).toBe('missing output file');
99
+ });
100
+
101
+ it('accepts a fresh fragments.json with matching inputs and generator version', async () => {
102
+ const project = await createProject('fresh-output');
103
+ const generatorVersion = await getGeneratorVersion();
104
+ const buildInputs = await getFragmentsBuildInputs(project.config, project.projectDir, {
105
+ configPath: project.configPath,
106
+ });
107
+
108
+ await writeFile(
109
+ project.outputPath,
110
+ JSON.stringify(
111
+ {
112
+ version: '1.0.0',
113
+ generatedAt: new Date().toISOString(),
114
+ generatorVersion,
115
+ buildInputs: buildInputs.relativePaths,
116
+ fragments: {},
117
+ },
118
+ null,
119
+ 2
120
+ )
121
+ );
122
+
123
+ const now = new Date();
124
+ const earlier = new Date(now.getTime() - 10_000);
125
+ await utimes(project.configPath, earlier, earlier);
126
+ await utimes(project.fragmentPath, earlier, earlier);
127
+ await utimes(project.componentPath, earlier, earlier);
128
+ await utimes(project.outputPath, now, now);
129
+
130
+ const status = await getFragmentsJsonStatus(project.config, project.projectDir, {
131
+ configPath: project.configPath,
132
+ });
133
+
134
+ expect(status.missing).toBe(false);
135
+ expect(status.stale).toBe(false);
136
+ expect(status.reason).toBeNull();
137
+ });
138
+
139
+ it('marks fragments.json stale when a new input file is added', async () => {
140
+ const project = await createProject('added-input');
141
+ const generatorVersion = await getGeneratorVersion();
142
+ const buildInputs = await getFragmentsBuildInputs(project.config, project.projectDir, {
143
+ configPath: project.configPath,
144
+ });
145
+
146
+ // Write fragments.json with current inputs
147
+ await writeFile(
148
+ project.outputPath,
149
+ JSON.stringify(
150
+ {
151
+ version: '1.0.0',
152
+ generatedAt: new Date().toISOString(),
153
+ generatorVersion,
154
+ buildInputs: buildInputs.relativePaths,
155
+ fragments: {},
156
+ },
157
+ null,
158
+ 2
159
+ )
160
+ );
161
+
162
+ // Add a new component file — buildInputs list will differ
163
+ const newComponentPath = join(project.projectDir, 'src', 'components', 'Input.tsx');
164
+ await writeFile(newComponentPath, 'export function Input() { return null; }\n');
165
+
166
+ const status = await getFragmentsJsonStatus(project.config, project.projectDir, {
167
+ configPath: project.configPath,
168
+ });
169
+
170
+ expect(status.stale).toBe(true);
171
+ expect(status.reason).toContain('build inputs changed');
172
+ });
173
+
174
+ it('marks legacy fragments.json without build metadata as stale', async () => {
175
+ const project = await createProject('legacy-output');
176
+ const generatorVersion = await getGeneratorVersion();
177
+
178
+ await writeFile(
179
+ project.outputPath,
180
+ JSON.stringify(
181
+ {
182
+ version: '1.0.0',
183
+ generatedAt: new Date().toISOString(),
184
+ generatorVersion,
185
+ fragments: {},
186
+ },
187
+ null,
188
+ 2
189
+ )
190
+ );
191
+
192
+ const status = await getFragmentsJsonStatus(project.config, project.projectDir, {
193
+ configPath: project.configPath,
194
+ });
195
+
196
+ expect(status.stale).toBe(true);
197
+ expect(status.reason).toBe('missing build input metadata');
198
+ });
199
+
200
+ it('marks fragments.json stale when an input file disappears', async () => {
201
+ const project = await createProject('deleted-input');
202
+ const generatorVersion = await getGeneratorVersion();
203
+ const buildInputs = await getFragmentsBuildInputs(project.config, project.projectDir, {
204
+ configPath: project.configPath,
205
+ });
206
+
207
+ await writeFile(
208
+ project.outputPath,
209
+ JSON.stringify(
210
+ {
211
+ version: '1.0.0',
212
+ generatedAt: new Date().toISOString(),
213
+ generatorVersion,
214
+ buildInputs: buildInputs.relativePaths,
215
+ fragments: {},
216
+ },
217
+ null,
218
+ 2
219
+ )
220
+ );
221
+
222
+ await rm(project.componentPath);
223
+
224
+ const status = await getFragmentsJsonStatus(project.config, project.projectDir, {
225
+ configPath: project.configPath,
226
+ });
227
+
228
+ expect(status.stale).toBe(true);
229
+ expect(status.reason).toContain('build inputs changed');
230
+ });
231
+ });
@@ -0,0 +1,71 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
2
+ import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+
6
+ import {
7
+ addNextTranspilePackages,
8
+ generateNextjsLayout,
9
+ generateNextjsPage,
10
+ generateNextjsProviders,
11
+ } from "../create.js";
12
+
13
+ describe("create command templates", () => {
14
+ let tmpDir: string;
15
+
16
+ beforeAll(async () => {
17
+ tmpDir = await mkdtemp(join(tmpdir(), "fragments-create-test-"));
18
+ });
19
+
20
+ afterAll(async () => {
21
+ await rm(tmpDir, { recursive: true, force: true });
22
+ });
23
+
24
+ it("generates a Next.js layout that routes providers through a client wrapper", () => {
25
+ const layout = generateNextjsLayout("../styles/theme.css");
26
+
27
+ expect(layout).toContain("import { Providers } from './providers';");
28
+ expect(layout).toContain("<Providers>{children}</Providers>");
29
+ expect(layout).not.toContain("ThemeProvider");
30
+ });
31
+
32
+ it("generates a client providers module for Next.js", () => {
33
+ const providers = generateNextjsProviders();
34
+
35
+ expect(providers).toContain("'use client';");
36
+ expect(providers).toContain("ThemeProvider");
37
+ expect(providers).toContain("ToastProvider");
38
+ });
39
+
40
+ it("marks the sample Next.js page as a client component", () => {
41
+ const page = generateNextjsPage();
42
+
43
+ expect(page).toContain("'use client';");
44
+ expect(page).toContain("import { Button, Card, Stack, Text, Input } from '@fragments-sdk/ui';");
45
+ });
46
+
47
+ it("adds transpilePackages for @fragments-sdk/ui to next.config.ts", async () => {
48
+ const projectDir = join(tmpDir, "next-app");
49
+ await mkdir(projectDir, { recursive: true });
50
+ const configPath = join(projectDir, "next.config.ts");
51
+
52
+ await writeFile(
53
+ configPath,
54
+ [
55
+ 'import type { NextConfig } from "next";',
56
+ "",
57
+ "const nextConfig: NextConfig = {",
58
+ " /* config options here */",
59
+ "};",
60
+ "",
61
+ "export default nextConfig;",
62
+ "",
63
+ ].join("\n"),
64
+ );
65
+
66
+ addNextTranspilePackages(projectDir);
67
+
68
+ const updated = await readFile(configPath, "utf-8");
69
+ expect(updated).toContain("transpilePackages: ['@fragments-sdk/ui']");
70
+ });
71
+ });
@@ -3,7 +3,7 @@ import { mkdtemp, writeFile, mkdir, rm, readFile } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { diffProps, validateDrift, type DriftItem } from '../../validators.js';
6
- import type { PropMeta } from '../../core/component-extractor.js';
6
+ import type { PropMeta } from '@fragments-sdk/extract';
7
7
  import type { FragmentsConfig } from '@fragments-sdk/core';
8
8
 
9
9
  // ---------------------------------------------------------------------------