@fragments-sdk/cli 0.15.1 → 0.15.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.
@@ -0,0 +1,385 @@
1
+ import { createRequire as __banner_createRequire } from 'module'; const require = __banner_createRequire(import.meta.url);
2
+ import "./chunk-D2CDBRNU.js";
3
+ import {
4
+ BRAND
5
+ } from "./chunk-32LIWN2P.js";
6
+
7
+ // src/commands/doctor.ts
8
+ import { readFile, access } from "fs/promises";
9
+ import { join, resolve } from "path";
10
+ import pc from "picocolors";
11
+ import { NEUTRAL_PALETTES } from "@fragments-sdk/viewer/docs-data";
12
+ var VALID_NEUTRALS = NEUTRAL_PALETTES.map((p) => p.name);
13
+ var VALID_DENSITIES = ["compact", "default", "relaxed"];
14
+ var VALID_RADII = ["sharp", "subtle", "default", "rounded", "pill"];
15
+ async function checkPackageInstalled(root) {
16
+ try {
17
+ const pkgPath = join(root, "package.json");
18
+ const content = await readFile(pkgPath, "utf-8");
19
+ const pkg = JSON.parse(content);
20
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
21
+ if (allDeps["@fragments-sdk/ui"]) {
22
+ return {
23
+ name: "Package installed",
24
+ status: "pass",
25
+ message: `@fragments-sdk/ui ${allDeps["@fragments-sdk/ui"]} found in dependencies`
26
+ };
27
+ }
28
+ return {
29
+ name: "Package installed",
30
+ status: "fail",
31
+ message: "@fragments-sdk/ui not found in package.json",
32
+ fix: "npm install @fragments-sdk/ui"
33
+ };
34
+ } catch {
35
+ return {
36
+ name: "Package installed",
37
+ status: "fail",
38
+ message: "No package.json found"
39
+ };
40
+ }
41
+ }
42
+ async function checkStylesImport(root) {
43
+ const entryPatterns = [
44
+ "src/main.tsx",
45
+ "src/main.ts",
46
+ "src/index.tsx",
47
+ "src/index.ts",
48
+ "src/App.tsx",
49
+ "src/App.ts",
50
+ "app/layout.tsx",
51
+ "app/layout.ts",
52
+ "src/app/layout.tsx",
53
+ "src/app/layout.ts",
54
+ "app/root.tsx",
55
+ "pages/_app.tsx",
56
+ "pages/_app.ts"
57
+ ];
58
+ for (const pattern of entryPatterns) {
59
+ try {
60
+ const content = await readFile(join(root, pattern), "utf-8");
61
+ if (content.includes("@fragments-sdk/ui/styles")) {
62
+ return {
63
+ name: "Styles import",
64
+ status: "pass",
65
+ message: `Found styles import in ${pattern}`
66
+ };
67
+ }
68
+ if (content.includes("@fragments-sdk/ui/globals")) {
69
+ return {
70
+ name: "Styles import",
71
+ status: "warn",
72
+ message: `${pattern} uses deprecated '@fragments-sdk/ui/globals'. Use '@fragments-sdk/ui/styles' instead`,
73
+ fix: `Replace '@fragments-sdk/ui/globals' with '@fragments-sdk/ui/styles' in ${pattern}`
74
+ };
75
+ }
76
+ } catch {
77
+ }
78
+ }
79
+ const scssPatterns = [
80
+ "src/styles/globals.scss",
81
+ "src/globals.scss",
82
+ "styles/globals.scss",
83
+ "app/globals.scss",
84
+ "src/app/globals.scss",
85
+ "app/styles/globals.scss"
86
+ ];
87
+ for (const pattern of scssPatterns) {
88
+ try {
89
+ const content = await readFile(join(root, pattern), "utf-8");
90
+ if (content.includes("@fragments-sdk/ui/styles")) {
91
+ return {
92
+ name: "Styles import",
93
+ status: "pass",
94
+ message: `Found SCSS @use import in ${pattern}`
95
+ };
96
+ }
97
+ } catch {
98
+ }
99
+ }
100
+ return {
101
+ name: "Styles import",
102
+ status: "fail",
103
+ message: "No @fragments-sdk/ui/styles import found in entry files",
104
+ fix: "Add `import '@fragments-sdk/ui/styles'` to your app's entry file"
105
+ };
106
+ }
107
+ async function checkThemeProvider(root) {
108
+ const providerPatterns = [
109
+ "src/main.tsx",
110
+ "src/App.tsx",
111
+ "src/providers.tsx",
112
+ "app/layout.tsx",
113
+ "app/providers.tsx",
114
+ "src/app/layout.tsx",
115
+ "src/app/providers.tsx",
116
+ "app/root.tsx",
117
+ "pages/_app.tsx"
118
+ ];
119
+ for (const pattern of providerPatterns) {
120
+ try {
121
+ const content = await readFile(join(root, pattern), "utf-8");
122
+ if (content.includes("ThemeProvider")) {
123
+ if (content.includes("defaultTheme=") || content.includes("defaultTheme =")) {
124
+ return {
125
+ name: "ThemeProvider",
126
+ status: "warn",
127
+ message: `${pattern} uses deprecated 'defaultTheme' prop. Use 'defaultMode' instead`,
128
+ fix: `Replace 'defaultTheme' with 'defaultMode' in ${pattern}`
129
+ };
130
+ }
131
+ return {
132
+ name: "ThemeProvider",
133
+ status: "pass",
134
+ message: `ThemeProvider found in ${pattern}`
135
+ };
136
+ }
137
+ } catch {
138
+ }
139
+ }
140
+ return {
141
+ name: "ThemeProvider",
142
+ status: "warn",
143
+ message: "ThemeProvider not found in common entry files (optional but recommended)",
144
+ fix: 'Wrap your app with <ThemeProvider defaultMode="system">'
145
+ };
146
+ }
147
+ async function checkScssSeeds(root) {
148
+ const checks = [];
149
+ const scssPatterns = [
150
+ "src/styles/globals.scss",
151
+ "src/globals.scss",
152
+ "styles/globals.scss",
153
+ "app/globals.scss",
154
+ "src/app/globals.scss",
155
+ "app/styles/globals.scss"
156
+ ];
157
+ for (const pattern of scssPatterns) {
158
+ try {
159
+ const content = await readFile(join(root, pattern), "utf-8");
160
+ if (!content.includes("@fragments-sdk/ui/styles")) continue;
161
+ const standalonePattern = /^\$fui-\w+:\s*.+;$/m;
162
+ if (standalonePattern.test(content) && !content.includes("@use")) {
163
+ checks.push({
164
+ name: "SCSS syntax",
165
+ status: "fail",
166
+ message: `${pattern} uses standalone $fui- variables. Must use @use...with() syntax`,
167
+ fix: "@use '@fragments-sdk/ui/styles' with ($fui-brand: #0066ff);"
168
+ });
169
+ }
170
+ const neutralMatch = content.match(/\$fui-neutral:\s*"([^"]+)"/);
171
+ if (neutralMatch && !VALID_NEUTRALS.includes(neutralMatch[1])) {
172
+ checks.push({
173
+ name: "SCSS seed: neutral",
174
+ status: "fail",
175
+ message: `Invalid $fui-neutral: "${neutralMatch[1]}" in ${pattern}`,
176
+ fix: `Valid neutrals: ${VALID_NEUTRALS.join(", ")}`
177
+ });
178
+ }
179
+ const densityMatch = content.match(/\$fui-density:\s*"([^"]+)"/);
180
+ if (densityMatch && !VALID_DENSITIES.includes(densityMatch[1])) {
181
+ checks.push({
182
+ name: "SCSS seed: density",
183
+ status: "fail",
184
+ message: `Invalid $fui-density: "${densityMatch[1]}" in ${pattern}`,
185
+ fix: `Valid densities: ${VALID_DENSITIES.join(", ")}`
186
+ });
187
+ }
188
+ const radiusMatch = content.match(/\$fui-radius-style:\s*"([^"]+)"/);
189
+ if (radiusMatch && !VALID_RADII.includes(radiusMatch[1])) {
190
+ checks.push({
191
+ name: "SCSS seed: radius-style",
192
+ status: "fail",
193
+ message: `Invalid $fui-radius-style: "${radiusMatch[1]}" in ${pattern}`,
194
+ fix: `Valid radius styles: ${VALID_RADII.join(", ")}`
195
+ });
196
+ }
197
+ const brandMatch = content.match(/\$fui-brand:\s*(#[0-9a-fA-F]+)/);
198
+ if (brandMatch) {
199
+ const hex = brandMatch[1];
200
+ if (!/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(hex)) {
201
+ checks.push({
202
+ name: "SCSS seed: brand",
203
+ status: "fail",
204
+ message: `Invalid $fui-brand color: "${hex}" in ${pattern}`,
205
+ fix: "Must be a valid hex color (e.g., #0066ff)"
206
+ });
207
+ }
208
+ }
209
+ if (checks.length === 0) {
210
+ checks.push({
211
+ name: "SCSS seeds",
212
+ status: "pass",
213
+ message: `Seed values in ${pattern} are valid`
214
+ });
215
+ }
216
+ return checks;
217
+ } catch {
218
+ }
219
+ }
220
+ checks.push({
221
+ name: "SCSS seeds",
222
+ status: "pass",
223
+ message: "No custom SCSS seeds configured (using defaults)"
224
+ });
225
+ return checks;
226
+ }
227
+ async function checkPeerDeps(root) {
228
+ const checks = [];
229
+ try {
230
+ const pkgPath = join(root, "package.json");
231
+ const content = await readFile(pkgPath, "utf-8");
232
+ const pkg = JSON.parse(content);
233
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
234
+ if (!allDeps["react"]) {
235
+ checks.push({
236
+ name: "Peer dep: react",
237
+ status: "fail",
238
+ message: "react not found in dependencies (required)",
239
+ fix: "npm install react react-dom"
240
+ });
241
+ } else {
242
+ checks.push({
243
+ name: "Peer dep: react",
244
+ status: "pass",
245
+ message: `react ${allDeps["react"]} installed`
246
+ });
247
+ }
248
+ if (!allDeps["sass"]) {
249
+ checks.push({
250
+ name: "Peer dep: sass",
251
+ status: "warn",
252
+ message: "sass not installed (needed for custom SCSS theming)",
253
+ fix: "npm install -D sass"
254
+ });
255
+ }
256
+ const optionalPeers = [
257
+ { pkg: "recharts", components: "Chart" },
258
+ { pkg: "shiki", components: "CodeBlock" },
259
+ { pkg: "react-day-picker", components: "DatePicker" },
260
+ { pkg: "@tanstack/react-table", components: "DataTable" }
261
+ ];
262
+ for (const peer of optionalPeers) {
263
+ if (allDeps[peer.pkg]) {
264
+ checks.push({
265
+ name: `Optional dep: ${peer.pkg}`,
266
+ status: "pass",
267
+ message: `${peer.pkg} installed (enables ${peer.components})`
268
+ });
269
+ }
270
+ }
271
+ } catch {
272
+ checks.push({
273
+ name: "Peer dependencies",
274
+ status: "fail",
275
+ message: "Could not read package.json"
276
+ });
277
+ }
278
+ return checks;
279
+ }
280
+ async function checkMcpConfig(root) {
281
+ const mcpConfigPaths = [
282
+ ".mcp.json",
283
+ ".cursor/mcp.json",
284
+ ".vscode/mcp.json"
285
+ ];
286
+ for (const configPath of mcpConfigPaths) {
287
+ try {
288
+ const fullPath = join(root, configPath);
289
+ const content = await readFile(fullPath, "utf-8");
290
+ const config = JSON.parse(content);
291
+ const servers = config.mcpServers || config.servers || {};
292
+ const hasFragments = Object.values(servers).some((server) => {
293
+ const s = server;
294
+ return s.args?.some((arg) => arg.includes("@fragments-sdk/mcp")) || s.command?.includes("fragments");
295
+ });
296
+ if (hasFragments) {
297
+ return {
298
+ name: "MCP configuration",
299
+ status: "pass",
300
+ message: `Fragments MCP server configured in ${configPath}`
301
+ };
302
+ }
303
+ } catch {
304
+ }
305
+ }
306
+ return {
307
+ name: "MCP configuration",
308
+ status: "warn",
309
+ message: "No Fragments MCP server configuration found (optional)",
310
+ fix: "Run `fragments init` or add @fragments-sdk/mcp to your MCP config"
311
+ };
312
+ }
313
+ async function checkTypeScript(root) {
314
+ try {
315
+ const tsconfigPath = join(root, "tsconfig.json");
316
+ await access(tsconfigPath);
317
+ return {
318
+ name: "TypeScript",
319
+ status: "pass",
320
+ message: "tsconfig.json found"
321
+ };
322
+ } catch {
323
+ return {
324
+ name: "TypeScript",
325
+ status: "warn",
326
+ message: "No tsconfig.json found (TypeScript recommended but not required)"
327
+ };
328
+ }
329
+ }
330
+ async function doctor(options = {}) {
331
+ const root = resolve(options.root ?? process.cwd());
332
+ const checks = [];
333
+ if (!options.json) {
334
+ console.log(pc.cyan(`
335
+ ${BRAND.name} Doctor
336
+ `));
337
+ console.log(pc.dim(`Checking project at ${root}
338
+ `));
339
+ }
340
+ checks.push(await checkPackageInstalled(root));
341
+ checks.push(await checkStylesImport(root));
342
+ checks.push(await checkThemeProvider(root));
343
+ checks.push(...await checkScssSeeds(root));
344
+ checks.push(...await checkPeerDeps(root));
345
+ checks.push(await checkMcpConfig(root));
346
+ checks.push(await checkTypeScript(root));
347
+ const passed = checks.filter((c) => c.status === "pass").length;
348
+ const warned = checks.filter((c) => c.status === "warn").length;
349
+ const failed = checks.filter((c) => c.status === "fail").length;
350
+ const result = {
351
+ success: failed === 0,
352
+ checks,
353
+ passed,
354
+ warned,
355
+ failed
356
+ };
357
+ if (options.json) {
358
+ console.log(JSON.stringify(result, null, 2));
359
+ } else {
360
+ for (const check of checks) {
361
+ const icon = check.status === "pass" ? pc.green("\u2713") : check.status === "warn" ? pc.yellow("!") : pc.red("\u2717");
362
+ const msg = check.status === "pass" ? check.message : check.status === "warn" ? pc.yellow(check.message) : pc.red(check.message);
363
+ console.log(` ${icon} ${pc.bold(check.name)}: ${msg}`);
364
+ if (check.fix && check.status !== "pass") {
365
+ console.log(pc.dim(` \u2192 ${check.fix}`));
366
+ }
367
+ }
368
+ console.log();
369
+ if (failed === 0 && warned === 0) {
370
+ console.log(pc.green(`\u2713 All ${passed} checks passed \u2014 your setup looks great!`));
371
+ } else if (failed === 0) {
372
+ console.log(pc.green(`\u2713 ${passed} passed`) + pc.yellow(`, ${warned} warning(s)`));
373
+ } else {
374
+ console.log(
375
+ pc.red(`\u2717 ${failed} failed`) + (warned > 0 ? pc.yellow(`, ${warned} warning(s)`) : "") + pc.dim(`, ${passed} passed`)
376
+ );
377
+ }
378
+ console.log();
379
+ }
380
+ return result;
381
+ }
382
+ export {
383
+ doctor
384
+ };
385
+ //# sourceMappingURL=doctor-BDPMYYE6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/doctor.ts"],"sourcesContent":["/**\n * fragments doctor - Diagnose design system configuration\n *\n * Checks a consumer project for common setup issues:\n * - Missing styles import\n * - ThemeProvider not found\n * - Invalid SCSS seed values\n * - Missing peer dependencies\n * - MCP configuration\n */\n\nimport { readFile, access } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport pc from 'picocolors';\nimport { BRAND } from '../core/index.js';\nimport { NEUTRAL_PALETTES } from '@fragments-sdk/viewer/docs-data';\n\n// ============================================\n// Types\n// ============================================\n\nexport interface DoctorOptions {\n /** Project root directory (defaults to cwd) */\n root?: string;\n /** Output JSON instead of formatted text */\n json?: boolean;\n /** Auto-fix issues where possible */\n fix?: boolean;\n}\n\nexport interface DoctorCheck {\n name: string;\n status: 'pass' | 'warn' | 'fail';\n message: string;\n fix?: string;\n}\n\nexport interface DoctorResult {\n success: boolean;\n checks: DoctorCheck[];\n passed: number;\n warned: number;\n failed: number;\n}\n\n// ============================================\n// Valid seed values — derived from canonical sources\n// ============================================\n\nconst VALID_NEUTRALS = NEUTRAL_PALETTES.map((p) => p.name);\nconst VALID_DENSITIES = ['compact', 'default', 'relaxed'];\nconst VALID_RADII = ['sharp', 'subtle', 'default', 'rounded', 'pill'];\n\n// ============================================\n// Individual Checks\n// ============================================\n\nasync function checkPackageInstalled(root: string): Promise<DoctorCheck> {\n try {\n const pkgPath = join(root, 'package.json');\n const content = await readFile(pkgPath, 'utf-8');\n const pkg = JSON.parse(content);\n const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };\n\n if (allDeps['@fragments-sdk/ui']) {\n return {\n name: 'Package installed',\n status: 'pass',\n message: `@fragments-sdk/ui ${allDeps['@fragments-sdk/ui']} found in dependencies`,\n };\n }\n\n return {\n name: 'Package installed',\n status: 'fail',\n message: '@fragments-sdk/ui not found in package.json',\n fix: 'npm install @fragments-sdk/ui',\n };\n } catch {\n return {\n name: 'Package installed',\n status: 'fail',\n message: 'No package.json found',\n };\n }\n}\n\nasync function checkStylesImport(root: string): Promise<DoctorCheck> {\n const entryPatterns = [\n 'src/main.tsx', 'src/main.ts', 'src/index.tsx', 'src/index.ts',\n 'src/App.tsx', 'src/App.ts',\n 'app/layout.tsx', 'app/layout.ts',\n 'src/app/layout.tsx', 'src/app/layout.ts',\n 'app/root.tsx',\n 'pages/_app.tsx', 'pages/_app.ts',\n ];\n\n for (const pattern of entryPatterns) {\n try {\n const content = await readFile(join(root, pattern), 'utf-8');\n\n // Check for correct import\n if (content.includes(\"@fragments-sdk/ui/styles\")) {\n return {\n name: 'Styles import',\n status: 'pass',\n message: `Found styles import in ${pattern}`,\n };\n }\n\n // Check for deprecated ./globals import\n if (content.includes(\"@fragments-sdk/ui/globals\")) {\n return {\n name: 'Styles import',\n status: 'warn',\n message: `${pattern} uses deprecated '@fragments-sdk/ui/globals'. Use '@fragments-sdk/ui/styles' instead`,\n fix: `Replace '@fragments-sdk/ui/globals' with '@fragments-sdk/ui/styles' in ${pattern}`,\n };\n }\n } catch {\n // File doesn't exist, continue\n }\n }\n\n // Also check SCSS files for @use import\n const scssPatterns = [\n 'src/styles/globals.scss', 'src/globals.scss',\n 'styles/globals.scss', 'app/globals.scss',\n 'src/app/globals.scss',\n 'app/styles/globals.scss',\n ];\n\n for (const pattern of scssPatterns) {\n try {\n const content = await readFile(join(root, pattern), 'utf-8');\n if (content.includes(\"@fragments-sdk/ui/styles\")) {\n return {\n name: 'Styles import',\n status: 'pass',\n message: `Found SCSS @use import in ${pattern}`,\n };\n }\n } catch {\n // File doesn't exist, continue\n }\n }\n\n return {\n name: 'Styles import',\n status: 'fail',\n message: 'No @fragments-sdk/ui/styles import found in entry files',\n fix: \"Add `import '@fragments-sdk/ui/styles'` to your app's entry file\",\n };\n}\n\nasync function checkThemeProvider(root: string): Promise<DoctorCheck> {\n const providerPatterns = [\n 'src/main.tsx', 'src/App.tsx', 'src/providers.tsx',\n 'app/layout.tsx', 'app/providers.tsx',\n 'src/app/layout.tsx', 'src/app/providers.tsx',\n 'app/root.tsx',\n 'pages/_app.tsx',\n ];\n\n for (const pattern of providerPatterns) {\n try {\n const content = await readFile(join(root, pattern), 'utf-8');\n\n if (content.includes('ThemeProvider')) {\n // Check for deprecated defaultTheme prop\n if (content.includes('defaultTheme=') || content.includes('defaultTheme =')) {\n return {\n name: 'ThemeProvider',\n status: 'warn',\n message: `${pattern} uses deprecated 'defaultTheme' prop. Use 'defaultMode' instead`,\n fix: `Replace 'defaultTheme' with 'defaultMode' in ${pattern}`,\n };\n }\n\n return {\n name: 'ThemeProvider',\n status: 'pass',\n message: `ThemeProvider found in ${pattern}`,\n };\n }\n } catch {\n // File doesn't exist, continue\n }\n }\n\n return {\n name: 'ThemeProvider',\n status: 'warn',\n message: 'ThemeProvider not found in common entry files (optional but recommended)',\n fix: \"Wrap your app with <ThemeProvider defaultMode=\\\"system\\\">\",\n };\n}\n\nasync function checkScssSeeds(root: string): Promise<DoctorCheck[]> {\n const checks: DoctorCheck[] = [];\n\n const scssPatterns = [\n 'src/styles/globals.scss', 'src/globals.scss',\n 'styles/globals.scss', 'app/globals.scss',\n 'src/app/globals.scss',\n 'app/styles/globals.scss',\n ];\n\n for (const pattern of scssPatterns) {\n try {\n const content = await readFile(join(root, pattern), 'utf-8');\n if (!content.includes(\"@fragments-sdk/ui/styles\")) continue;\n\n // Check for standalone variable syntax (wrong)\n const standalonePattern = /^\\$fui-\\w+:\\s*.+;$/m;\n if (standalonePattern.test(content) && !content.includes('@use')) {\n checks.push({\n name: 'SCSS syntax',\n status: 'fail',\n message: `${pattern} uses standalone $fui- variables. Must use @use...with() syntax`,\n fix: \"@use '@fragments-sdk/ui/styles' with ($fui-brand: #0066ff);\",\n });\n }\n\n // Validate neutral value\n const neutralMatch = content.match(/\\$fui-neutral:\\s*\"([^\"]+)\"/);\n if (neutralMatch && !VALID_NEUTRALS.includes(neutralMatch[1])) {\n checks.push({\n name: 'SCSS seed: neutral',\n status: 'fail',\n message: `Invalid $fui-neutral: \"${neutralMatch[1]}\" in ${pattern}`,\n fix: `Valid neutrals: ${VALID_NEUTRALS.join(', ')}`,\n });\n }\n\n // Validate density value\n const densityMatch = content.match(/\\$fui-density:\\s*\"([^\"]+)\"/);\n if (densityMatch && !VALID_DENSITIES.includes(densityMatch[1])) {\n checks.push({\n name: 'SCSS seed: density',\n status: 'fail',\n message: `Invalid $fui-density: \"${densityMatch[1]}\" in ${pattern}`,\n fix: `Valid densities: ${VALID_DENSITIES.join(', ')}`,\n });\n }\n\n // Validate radius-style value\n const radiusMatch = content.match(/\\$fui-radius-style:\\s*\"([^\"]+)\"/);\n if (radiusMatch && !VALID_RADII.includes(radiusMatch[1])) {\n checks.push({\n name: 'SCSS seed: radius-style',\n status: 'fail',\n message: `Invalid $fui-radius-style: \"${radiusMatch[1]}\" in ${pattern}`,\n fix: `Valid radius styles: ${VALID_RADII.join(', ')}`,\n });\n }\n\n // Validate brand is a valid hex color\n const brandMatch = content.match(/\\$fui-brand:\\s*(#[0-9a-fA-F]+)/);\n if (brandMatch) {\n const hex = brandMatch[1];\n if (!/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(hex)) {\n checks.push({\n name: 'SCSS seed: brand',\n status: 'fail',\n message: `Invalid $fui-brand color: \"${hex}\" in ${pattern}`,\n fix: 'Must be a valid hex color (e.g., #0066ff)',\n });\n }\n }\n\n // If we found the file and parsed it, and no seed issues, add a pass\n if (checks.length === 0) {\n checks.push({\n name: 'SCSS seeds',\n status: 'pass',\n message: `Seed values in ${pattern} are valid`,\n });\n }\n\n return checks;\n } catch {\n // File doesn't exist, continue\n }\n }\n\n // No SCSS file found — not an error, just informational\n checks.push({\n name: 'SCSS seeds',\n status: 'pass',\n message: 'No custom SCSS seeds configured (using defaults)',\n });\n\n return checks;\n}\n\nasync function checkPeerDeps(root: string): Promise<DoctorCheck[]> {\n const checks: DoctorCheck[] = [];\n\n try {\n const pkgPath = join(root, 'package.json');\n const content = await readFile(pkgPath, 'utf-8');\n const pkg = JSON.parse(content);\n const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };\n\n // React is required\n if (!allDeps['react']) {\n checks.push({\n name: 'Peer dep: react',\n status: 'fail',\n message: 'react not found in dependencies (required)',\n fix: 'npm install react react-dom',\n });\n } else {\n checks.push({\n name: 'Peer dep: react',\n status: 'pass',\n message: `react ${allDeps['react']} installed`,\n });\n }\n\n // sass is needed for custom theming\n if (!allDeps['sass']) {\n checks.push({\n name: 'Peer dep: sass',\n status: 'warn',\n message: 'sass not installed (needed for custom SCSS theming)',\n fix: 'npm install -D sass',\n });\n }\n\n // Check optional peer deps that components might need\n const optionalPeers: Array<{ pkg: string; components: string }> = [\n { pkg: 'recharts', components: 'Chart' },\n { pkg: 'shiki', components: 'CodeBlock' },\n { pkg: 'react-day-picker', components: 'DatePicker' },\n { pkg: '@tanstack/react-table', components: 'DataTable' },\n ];\n\n for (const peer of optionalPeers) {\n if (allDeps[peer.pkg]) {\n checks.push({\n name: `Optional dep: ${peer.pkg}`,\n status: 'pass',\n message: `${peer.pkg} installed (enables ${peer.components})`,\n });\n }\n }\n } catch {\n checks.push({\n name: 'Peer dependencies',\n status: 'fail',\n message: 'Could not read package.json',\n });\n }\n\n return checks;\n}\n\nasync function checkMcpConfig(root: string): Promise<DoctorCheck> {\n const mcpConfigPaths = [\n '.mcp.json',\n '.cursor/mcp.json',\n '.vscode/mcp.json',\n ];\n\n for (const configPath of mcpConfigPaths) {\n try {\n const fullPath = join(root, configPath);\n const content = await readFile(fullPath, 'utf-8');\n const config = JSON.parse(content);\n\n // Check if fragments MCP server is configured\n const servers = config.mcpServers || config.servers || {};\n const hasFragments = Object.values(servers).some((server: unknown) => {\n const s = server as { command?: string; args?: string[] };\n return (\n s.args?.some((arg: string) => arg.includes('@fragments-sdk/mcp')) ||\n s.command?.includes('fragments')\n );\n });\n\n if (hasFragments) {\n return {\n name: 'MCP configuration',\n status: 'pass',\n message: `Fragments MCP server configured in ${configPath}`,\n };\n }\n } catch {\n // File doesn't exist or is invalid, continue\n }\n }\n\n return {\n name: 'MCP configuration',\n status: 'warn',\n message: 'No Fragments MCP server configuration found (optional)',\n fix: 'Run `fragments init` or add @fragments-sdk/mcp to your MCP config',\n };\n}\n\nasync function checkTypeScript(root: string): Promise<DoctorCheck> {\n try {\n const tsconfigPath = join(root, 'tsconfig.json');\n await access(tsconfigPath);\n return {\n name: 'TypeScript',\n status: 'pass',\n message: 'tsconfig.json found',\n };\n } catch {\n return {\n name: 'TypeScript',\n status: 'warn',\n message: 'No tsconfig.json found (TypeScript recommended but not required)',\n };\n }\n}\n\n// ============================================\n// Main Doctor Function\n// ============================================\n\n/**\n * Run diagnostic checks on a consumer project\n */\nexport async function doctor(\n options: DoctorOptions = {}\n): Promise<DoctorResult> {\n const root = resolve(options.root ?? process.cwd());\n const checks: DoctorCheck[] = [];\n\n if (!options.json) {\n console.log(pc.cyan(`\\n${BRAND.name} Doctor\\n`));\n console.log(pc.dim(`Checking project at ${root}\\n`));\n }\n\n // Run all checks\n checks.push(await checkPackageInstalled(root));\n checks.push(await checkStylesImport(root));\n checks.push(await checkThemeProvider(root));\n checks.push(...await checkScssSeeds(root));\n checks.push(...await checkPeerDeps(root));\n checks.push(await checkMcpConfig(root));\n checks.push(await checkTypeScript(root));\n\n const passed = checks.filter(c => c.status === 'pass').length;\n const warned = checks.filter(c => c.status === 'warn').length;\n const failed = checks.filter(c => c.status === 'fail').length;\n\n const result: DoctorResult = {\n success: failed === 0,\n checks,\n passed,\n warned,\n failed,\n };\n\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n // Print results\n for (const check of checks) {\n const icon =\n check.status === 'pass' ? pc.green('✓') :\n check.status === 'warn' ? pc.yellow('!') :\n pc.red('✗');\n\n const msg =\n check.status === 'pass' ? check.message :\n check.status === 'warn' ? pc.yellow(check.message) :\n pc.red(check.message);\n\n console.log(` ${icon} ${pc.bold(check.name)}: ${msg}`);\n\n if (check.fix && check.status !== 'pass') {\n console.log(pc.dim(` → ${check.fix}`));\n }\n }\n\n // Summary\n console.log();\n if (failed === 0 && warned === 0) {\n console.log(pc.green(`✓ All ${passed} checks passed — your setup looks great!`));\n } else if (failed === 0) {\n console.log(pc.green(`✓ ${passed} passed`) + pc.yellow(`, ${warned} warning(s)`));\n } else {\n console.log(\n pc.red(`✗ ${failed} failed`) +\n (warned > 0 ? pc.yellow(`, ${warned} warning(s)`) : '') +\n pc.dim(`, ${passed} passed`)\n );\n }\n console.log();\n }\n\n return result;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fragments-sdk/cli",
3
- "version": "0.15.1",
3
+ "version": "0.15.2",
4
4
  "license": "FSL-1.1-MIT",
5
5
  "description": "CLI, MCP server, and dev tools for Fragments design system",
6
6
  "author": "Conan McNicholl",
@@ -50,16 +50,31 @@
50
50
  "dist",
51
51
  "src"
52
52
  ],
53
+ "scripts": {
54
+ "build": "tsup",
55
+ "dev": "tsup --watch",
56
+ "lint": "eslint src",
57
+ "test": "vitest run",
58
+ "typecheck": "tsc --noEmit",
59
+ "clean": "rm -rf dist"
60
+ },
53
61
  "scarfSettings": {
54
62
  "defaultOptIn": true
55
63
  },
56
64
  "dependencies": {
57
65
  "@anthropic-ai/sdk": "^0.71.2",
66
+ "@fragments-sdk/compiler": "workspace:*",
67
+ "@fragments-sdk/extract": "workspace:*",
58
68
  "@babel/generator": "^7.23.6",
59
69
  "@babel/parser": "^7.23.6",
60
70
  "@babel/traverse": "^7.23.6",
61
71
  "@babel/types": "^7.23.6",
62
72
  "@figma/rest-api-spec": "^0.35.0",
73
+ "@fragments-sdk/context": "workspace:*",
74
+ "@fragments-sdk/core": "workspace:*",
75
+ "@fragments-sdk/govern": "workspace:^",
76
+ "@fragments-sdk/viewer": "workspace:*",
77
+ "@fragments-sdk/webmcp": "workspace:*",
63
78
  "@inquirer/prompts": "^7.2.1",
64
79
  "@modelcontextprotocol/sdk": "^1.0.0",
65
80
  "@phosphor-icons/react": "^2.1.10",
@@ -80,14 +95,7 @@
80
95
  "shiki": "^3.21.0",
81
96
  "vite": "^6.0.0",
82
97
  "vite-plugin-svgr": "^4.5.0",
83
- "zod": "^3.24.1",
84
- "@fragments-sdk/compiler": "0.2.0",
85
- "@fragments-sdk/context": "0.6.0",
86
- "@fragments-sdk/core": "2.0.0",
87
- "@fragments-sdk/viewer": "0.2.8",
88
- "@fragments-sdk/webmcp": "3.0.0",
89
- "@fragments-sdk/extract": "0.1.0",
90
- "@fragments-sdk/govern": "^0.3.0"
98
+ "zod": "^3.24.1"
91
99
  },
92
100
  "devDependencies": {
93
101
  "@types/babel__generator": "^7.6.8",
@@ -120,13 +128,5 @@
120
128
  "react-dom": {
121
129
  "optional": true
122
130
  }
123
- },
124
- "scripts": {
125
- "build": "tsup",
126
- "dev": "tsup --watch",
127
- "lint": "eslint src",
128
- "test": "vitest run",
129
- "typecheck": "tsc --noEmit",
130
- "clean": "rm -rf dist"
131
131
  }
132
- }
132
+ }
package/src/bin.ts CHANGED
@@ -40,7 +40,9 @@ import { graph } from './commands/graph.js';
40
40
  import { inspect } from './commands/inspect.js';
41
41
  import { discover } from './commands/discover.js';
42
42
  import { perf } from './commands/perf.js';
43
- import { doctor } from './commands/doctor.js';
43
+ // doctor is imported lazily — it pulls @fragments-sdk/viewer/docs-data which
44
+ // ships as raw TS source and cannot be resolved by Node at startup.
45
+ type DoctorFn = typeof import('./commands/doctor.js')['doctor'];
44
46
  import { setup } from './commands/setup.js';
45
47
  import { sync } from './commands/sync.js';
46
48
  import { governCheck, governInit, governReport, governConnect } from './commands/govern.js';
@@ -1296,6 +1298,7 @@ program
1296
1298
  .option('--fix', 'Auto-fix issues where possible')
1297
1299
  .action(async (options) => {
1298
1300
  try {
1301
+ const { doctor } = await import('./commands/doctor.js');
1299
1302
  const result = await doctor({
1300
1303
  root: options.root,
1301
1304
  json: options.json,
@@ -37,6 +37,6 @@
37
37
  "source": "extracted",
38
38
  "verified": false,
39
39
  "frameworkSupport": "native",
40
- "extractedAt": "2026-03-22T23:45:29.606Z"
40
+ "extractedAt": "2026-03-25T18:56:02.138Z"
41
41
  }
42
42
  }
@@ -15,6 +15,6 @@
15
15
  "source": "extracted",
16
16
  "verified": false,
17
17
  "frameworkSupport": "native",
18
- "extractedAt": "2026-03-22T23:45:29.607Z"
18
+ "extractedAt": "2026-03-25T18:56:02.138Z"
19
19
  }
20
20
  }
@@ -13,10 +13,18 @@ describe("init", () => {
13
13
  });
14
14
 
15
15
  afterAll(async () => {
16
- await rm(tmpDir, { recursive: true, force: true });
16
+ // Retry cleanup CI runners can race with async handles
17
+ for (let i = 0; i < 3; i++) {
18
+ try {
19
+ await rm(tmpDir, { recursive: true, force: true });
20
+ break;
21
+ } catch {
22
+ await new Promise((r) => setTimeout(r, 200));
23
+ }
24
+ }
17
25
  });
18
26
 
19
- it("skips Fragments UI runtime injection for shadcn-style projects", { timeout: 10_000 }, async () => {
27
+ it("skips Fragments UI runtime injection for shadcn-style projects", async () => {
20
28
  const projectDir = join(tmpDir, "shadcn-app");
21
29
  await mkdir(join(projectDir, "src", "components", "ui"), { recursive: true });
22
30