@fragments-sdk/cli 0.11.1 → 0.12.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/ai-client-I6MDWNYA.js +21 -0
- package/dist/bin.js +275 -368
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-PW7QTQA6.js → chunk-4OC7FTJB.js} +2 -2
- package/dist/{chunk-HRFUSSZI.js → chunk-AM4MRTMN.js} +2 -2
- package/dist/{chunk-5G3VZH43.js → chunk-GVDSFQ4E.js} +281 -351
- package/dist/chunk-GVDSFQ4E.js.map +1 -0
- package/dist/chunk-JJ2VRTBU.js +626 -0
- package/dist/chunk-JJ2VRTBU.js.map +1 -0
- package/dist/{chunk-D5PYOXEI.js → chunk-LVWFOLUZ.js} +148 -13
- package/dist/{chunk-D5PYOXEI.js.map → chunk-LVWFOLUZ.js.map} +1 -1
- package/dist/{chunk-WXSR2II7.js → chunk-OQKMEFOS.js} +58 -6
- package/dist/chunk-OQKMEFOS.js.map +1 -0
- package/dist/chunk-SXTKFDCR.js +104 -0
- package/dist/chunk-SXTKFDCR.js.map +1 -0
- package/dist/chunk-T5OMVL7E.js +443 -0
- package/dist/chunk-T5OMVL7E.js.map +1 -0
- package/dist/{chunk-ZM4ZQZWZ.js → chunk-TPWGL2XS.js} +39 -37
- package/dist/chunk-TPWGL2XS.js.map +1 -0
- package/dist/{chunk-OQO55NKV.js → chunk-WFS63PCW.js} +85 -11
- package/dist/chunk-WFS63PCW.js.map +1 -0
- package/dist/core/index.js +9 -1
- package/dist/{discovery-NEOY4MPN.js → discovery-ZJQSXF56.js} +3 -3
- package/dist/{generate-FBHSXR3D.js → generate-RJFS2JWA.js} +4 -4
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/init-ZSX3NRCZ.js +636 -0
- package/dist/init-ZSX3NRCZ.js.map +1 -0
- package/dist/mcp-bin.js +2 -2
- package/dist/{scan-CJF2DOQW.js → scan-3PMCJ4RB.js} +6 -6
- package/dist/scan-generate-SYU4PYZD.js +1115 -0
- package/dist/scan-generate-SYU4PYZD.js.map +1 -0
- package/dist/{service-TQYWY65E.js → service-VMGNJZ42.js} +3 -3
- package/dist/{snapshot-SV2JOFZH.js → snapshot-XOISO2IS.js} +2 -2
- package/dist/{static-viewer-NUBFPKWH.js → static-viewer-5GXH2MGE.js} +3 -3
- package/dist/static-viewer-5GXH2MGE.js.map +1 -0
- package/dist/{test-Z5LVO724.js → test-SI4NSHQX.js} +4 -4
- package/dist/{tokens-CE46OTMD.js → tokens-T6SIVUT5.js} +5 -5
- package/dist/{viewer-DLLJIMCK.js → viewer-7ZEAFBVN.js} +13 -13
- package/package.json +4 -4
- package/src/ai-client.ts +156 -0
- package/src/bin.ts +44 -2
- package/src/build.ts +95 -33
- package/src/commands/__tests__/drift-sync.test.ts +252 -0
- package/src/commands/__tests__/scan-generate.test.ts +497 -45
- package/src/commands/enhance.ts +11 -35
- package/src/commands/init.ts +288 -260
- package/src/commands/scan-generate.ts +740 -139
- package/src/commands/scan.ts +37 -32
- package/src/commands/setup.ts +143 -52
- package/src/commands/sync.ts +357 -0
- package/src/commands/validate.ts +43 -1
- package/src/core/component-extractor.test.ts +282 -0
- package/src/core/component-extractor.ts +1030 -0
- package/src/core/discovery.ts +93 -7
- package/src/service/enhance/props-extractor.ts +235 -13
- package/src/validators.ts +236 -0
- package/dist/chunk-5G3VZH43.js.map +0 -1
- package/dist/chunk-OQO55NKV.js.map +0 -1
- package/dist/chunk-WXSR2II7.js.map +0 -1
- package/dist/chunk-ZM4ZQZWZ.js.map +0 -1
- package/dist/init-UFGK5TCN.js +0 -867
- package/dist/init-UFGK5TCN.js.map +0 -1
- package/dist/scan-generate-SJAN5MVI.js +0 -691
- package/dist/scan-generate-SJAN5MVI.js.map +0 -1
- package/src/ai.ts +0 -266
- package/src/commands/init-framework.ts +0 -414
- package/src/mcp/bin.ts +0 -36
- package/src/migrate/bin.ts +0 -114
- package/src/theme/index.ts +0 -77
- package/src/viewer/bin.ts +0 -86
- package/src/viewer/cli/health.ts +0 -256
- package/src/viewer/cli/index.ts +0 -33
- package/src/viewer/cli/scan.ts +0 -124
- package/src/viewer/cli/utils.ts +0 -174
- /package/dist/{discovery-NEOY4MPN.js.map → ai-client-I6MDWNYA.js.map} +0 -0
- /package/dist/{chunk-PW7QTQA6.js.map → chunk-4OC7FTJB.js.map} +0 -0
- /package/dist/{chunk-HRFUSSZI.js.map → chunk-AM4MRTMN.js.map} +0 -0
- /package/dist/{scan-CJF2DOQW.js.map → discovery-ZJQSXF56.js.map} +0 -0
- /package/dist/{generate-FBHSXR3D.js.map → generate-RJFS2JWA.js.map} +0 -0
- /package/dist/{service-TQYWY65E.js.map → scan-3PMCJ4RB.js.map} +0 -0
- /package/dist/{static-viewer-NUBFPKWH.js.map → service-VMGNJZ42.js.map} +0 -0
- /package/dist/{snapshot-SV2JOFZH.js.map → snapshot-XOISO2IS.js.map} +0 -0
- /package/dist/{test-Z5LVO724.js.map → test-SI4NSHQX.js.map} +0 -0
- /package/dist/{tokens-CE46OTMD.js.map → tokens-T6SIVUT5.js.map} +0 -0
- /package/dist/{viewer-DLLJIMCK.js.map → viewer-7ZEAFBVN.js.map} +0 -0
package/src/commands/init.ts
CHANGED
|
@@ -1,37 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* fragments init -
|
|
2
|
+
* fragments init - Zero-config project initialization
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 3. Components found (no stories) → Auto-generate documentation
|
|
8
|
-
* 4. Fresh project → Guided setup with example component
|
|
4
|
+
* Default: auto-detect everything, zero prompts, instant setup.
|
|
5
|
+
* --configure: interactive mode for theme seeds, snapshots, etc.
|
|
6
|
+
* --scan <path>: scan external component library, generate fragment files.
|
|
9
7
|
*/
|
|
10
8
|
|
|
11
9
|
import { readFile, writeFile, mkdir, access } from "node:fs/promises";
|
|
12
|
-
import { resolve, join, relative
|
|
10
|
+
import { resolve, join, relative } from "node:path";
|
|
13
11
|
import { spawn } from "node:child_process";
|
|
14
12
|
import pc from "picocolors";
|
|
15
13
|
import { BRAND } from "../core/index.js";
|
|
16
14
|
import fg from "fast-glob";
|
|
17
|
-
import { input, confirm, select } from "@inquirer/prompts";
|
|
18
15
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
detectSetupFramework,
|
|
17
|
+
findEntryFile,
|
|
18
|
+
addStylesImport,
|
|
19
|
+
addThemeProvider,
|
|
20
|
+
addTranspilePackages,
|
|
21
|
+
} from "./setup.js";
|
|
23
22
|
|
|
24
23
|
export interface InitOptions {
|
|
25
24
|
/** Project root directory */
|
|
26
25
|
projectRoot?: string;
|
|
27
26
|
/** Force overwrite existing config */
|
|
28
27
|
force?: boolean;
|
|
29
|
-
/** Non-interactive mode - auto-detect and use defaults */
|
|
28
|
+
/** Non-interactive mode - auto-detect and use defaults (legacy, now the default behavior) */
|
|
30
29
|
yes?: boolean;
|
|
31
30
|
/** Explicit framework override */
|
|
32
31
|
framework?: string;
|
|
33
32
|
/** Path to scan for components (enables scan mode) */
|
|
34
33
|
scan?: string;
|
|
34
|
+
/** Enable interactive configuration (theme seeds, snapshots, etc.) */
|
|
35
|
+
configure?: boolean;
|
|
36
|
+
/** Use AI to fill knowledge fields during --scan */
|
|
37
|
+
enrich?: boolean;
|
|
38
|
+
/** Show what --enrich would generate without calling API */
|
|
39
|
+
dryRun?: boolean;
|
|
40
|
+
/** AI provider for enrichment: anthropic or openai */
|
|
41
|
+
provider?: 'anthropic' | 'openai';
|
|
42
|
+
/** API key for AI enrichment */
|
|
43
|
+
apiKey?: string;
|
|
44
|
+
/** Override AI model for enrichment */
|
|
45
|
+
model?: string;
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
export interface InitResult {
|
|
@@ -49,14 +60,13 @@ interface DetectionResult {
|
|
|
49
60
|
hasConfig: boolean;
|
|
50
61
|
configPath: string | null;
|
|
51
62
|
suggestedComponentPath: string;
|
|
63
|
+
scenario: "stories" | "components" | "fresh";
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
/**
|
|
55
67
|
* Detect what exists in the project
|
|
56
68
|
*/
|
|
57
69
|
async function detectProject(projectRoot: string): Promise<DetectionResult> {
|
|
58
|
-
console.log(pc.dim("\nScanning project...\n"));
|
|
59
|
-
|
|
60
70
|
// Check for existing config
|
|
61
71
|
const configPath = join(projectRoot, BRAND.configFile);
|
|
62
72
|
const legacyConfigPath = join(projectRoot, BRAND.legacyConfigFile);
|
|
@@ -127,12 +137,16 @@ async function detectProject(projectRoot: string): Promise<DetectionResult> {
|
|
|
127
137
|
}
|
|
128
138
|
}
|
|
129
139
|
|
|
140
|
+
const scenario: DetectionResult["scenario"] =
|
|
141
|
+
storyFiles.length > 0 ? "stories" : likelyComponents.length > 0 ? "components" : "fresh";
|
|
142
|
+
|
|
130
143
|
return {
|
|
131
144
|
storyFiles,
|
|
132
145
|
componentFiles: likelyComponents,
|
|
133
146
|
hasConfig,
|
|
134
147
|
configPath: foundConfigPath,
|
|
135
148
|
suggestedComponentPath,
|
|
149
|
+
scenario,
|
|
136
150
|
};
|
|
137
151
|
}
|
|
138
152
|
|
|
@@ -328,52 +342,6 @@ export default defineFragment({
|
|
|
328
342
|
`;
|
|
329
343
|
}
|
|
330
344
|
|
|
331
|
-
/**
|
|
332
|
-
* Convert a filename to PascalCase component name
|
|
333
|
-
*/
|
|
334
|
-
function toPascalCase(str: string): string {
|
|
335
|
-
return str
|
|
336
|
-
.replace(/[-_.](\w)/g, (_, c) => c.toUpperCase())
|
|
337
|
-
.replace(/^\w/, (c) => c.toUpperCase());
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Generate a minimal fragment stub for a discovered component
|
|
342
|
-
*/
|
|
343
|
-
function generateFragmentStub(componentName: string, importPath: string): string {
|
|
344
|
-
return `import React from 'react';
|
|
345
|
-
import { defineFragment } from '@fragments-sdk/cli/core';
|
|
346
|
-
import { ${componentName} } from '${importPath}';
|
|
347
|
-
|
|
348
|
-
export default defineFragment({
|
|
349
|
-
component: ${componentName},
|
|
350
|
-
|
|
351
|
-
meta: {
|
|
352
|
-
name: '${componentName}',
|
|
353
|
-
description: '${componentName} component',
|
|
354
|
-
category: 'general',
|
|
355
|
-
status: 'beta',
|
|
356
|
-
},
|
|
357
|
-
|
|
358
|
-
usage: {
|
|
359
|
-
when: ['TODO: describe when to use ${componentName}'],
|
|
360
|
-
whenNot: ['TODO: describe when not to use ${componentName}'],
|
|
361
|
-
},
|
|
362
|
-
|
|
363
|
-
props: {},
|
|
364
|
-
|
|
365
|
-
variants: [
|
|
366
|
-
{
|
|
367
|
-
name: 'Default',
|
|
368
|
-
description: 'Default ${componentName}',
|
|
369
|
-
code: \`<${componentName} />\`,
|
|
370
|
-
render: () => <${componentName} />,
|
|
371
|
-
},
|
|
372
|
-
],
|
|
373
|
-
});
|
|
374
|
-
`;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
345
|
/**
|
|
378
346
|
* Start the dev server
|
|
379
347
|
*/
|
|
@@ -395,7 +363,142 @@ function startDevServer(projectRoot: string): void {
|
|
|
395
363
|
}
|
|
396
364
|
|
|
397
365
|
/**
|
|
398
|
-
*
|
|
366
|
+
* Map init-framework's Framework type to setup's Framework type
|
|
367
|
+
*/
|
|
368
|
+
function mapFrameworkLabel(framework: string): string {
|
|
369
|
+
const labels: Record<string, string> = {
|
|
370
|
+
"nextjs-app": "Next.js (App Router)",
|
|
371
|
+
"nextjs-pages": "Next.js (Pages Router)",
|
|
372
|
+
"vite": "Vite",
|
|
373
|
+
"remix": "Remix",
|
|
374
|
+
"astro": "Astro",
|
|
375
|
+
};
|
|
376
|
+
return labels[framework] || "Unknown";
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// ============================================
|
|
380
|
+
// Interactive configuration (--configure)
|
|
381
|
+
// ============================================
|
|
382
|
+
|
|
383
|
+
interface ConfigureResult {
|
|
384
|
+
componentPath: string;
|
|
385
|
+
createExample: boolean;
|
|
386
|
+
startServer: boolean;
|
|
387
|
+
themeBlock: string;
|
|
388
|
+
snapshotsBlock: string;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async function runInteractiveConfigure(
|
|
392
|
+
detection: DetectionResult,
|
|
393
|
+
scenario: "stories" | "components" | "fresh"
|
|
394
|
+
): Promise<ConfigureResult> {
|
|
395
|
+
const { input, confirm, select } = await import("@inquirer/prompts");
|
|
396
|
+
|
|
397
|
+
const componentPath = await input({
|
|
398
|
+
message: "Where are your components located?",
|
|
399
|
+
default: detection.suggestedComponentPath,
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
let createExample = scenario === "fresh";
|
|
403
|
+
if (scenario === "fresh") {
|
|
404
|
+
createExample = await confirm({
|
|
405
|
+
message: "Create an example Button component to get started?",
|
|
406
|
+
default: true,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Theme seed configuration
|
|
411
|
+
let themeBlock = "";
|
|
412
|
+
const configureTheme = await confirm({
|
|
413
|
+
message: "Configure theme seeds? (brand color, density, radius)",
|
|
414
|
+
default: false,
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
if (configureTheme) {
|
|
418
|
+
const brand = await input({
|
|
419
|
+
message: "Brand color (hex)",
|
|
420
|
+
default: "#18181b",
|
|
421
|
+
validate: (v) => /^#[0-9a-fA-F]{6}$/.test(v) || "Enter a valid hex color (e.g., #6366f1)",
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
const neutral = await select({
|
|
425
|
+
message: "Neutral palette",
|
|
426
|
+
choices: [
|
|
427
|
+
{ value: "stone", name: "Stone (warm gray)" },
|
|
428
|
+
{ value: "ice", name: "Ice (cool blue-gray)" },
|
|
429
|
+
{ value: "earth", name: "Earth (olive/khaki)" },
|
|
430
|
+
{ value: "sand", name: "Sand (warm beige)" },
|
|
431
|
+
{ value: "fire", name: "Fire (warm red-gray)" },
|
|
432
|
+
],
|
|
433
|
+
default: "stone",
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
const density = await select({
|
|
437
|
+
message: "Spacing density",
|
|
438
|
+
choices: [
|
|
439
|
+
{ value: "compact", name: "Compact (tighter spacing)" },
|
|
440
|
+
{ value: "default", name: "Default" },
|
|
441
|
+
{ value: "relaxed", name: "Relaxed (more breathing room)" },
|
|
442
|
+
],
|
|
443
|
+
default: "default",
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const radiusStyle = await select({
|
|
447
|
+
message: "Border radius style",
|
|
448
|
+
choices: [
|
|
449
|
+
{ value: "sharp", name: "Sharp (0px)" },
|
|
450
|
+
{ value: "subtle", name: "Subtle (2px)" },
|
|
451
|
+
{ value: "default", name: "Default (6px)" },
|
|
452
|
+
{ value: "rounded", name: "Rounded (10px)" },
|
|
453
|
+
{ value: "pill", name: "Pill (999px)" },
|
|
454
|
+
],
|
|
455
|
+
default: "default",
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// Build theme config block — only include non-default values
|
|
459
|
+
const themeEntries: string[] = [];
|
|
460
|
+
if (brand !== "#18181b") themeEntries.push(` brand: '${brand}'`);
|
|
461
|
+
if (neutral !== "stone") themeEntries.push(` neutral: '${neutral}'`);
|
|
462
|
+
if (density !== "default") themeEntries.push(` density: '${density}'`);
|
|
463
|
+
if (radiusStyle !== "default") themeEntries.push(` radiusStyle: '${radiusStyle}'`);
|
|
464
|
+
|
|
465
|
+
if (themeEntries.length > 0) {
|
|
466
|
+
themeBlock = `\n // Theme seed values (derives 120+ CSS custom properties)\n theme: {\n${themeEntries.join(",\n")},\n },\n`;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Snapshot toggle
|
|
471
|
+
let snapshotsBlock = "";
|
|
472
|
+
const enableSnapshots = await confirm({
|
|
473
|
+
message: "Enable visual snapshot tests per component variant?",
|
|
474
|
+
default: false,
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
if (enableSnapshots) {
|
|
478
|
+
snapshotsBlock = `\n // Visual snapshot testing\n snapshots: {\n enabled: true,\n },\n`;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Ask about starting the server
|
|
482
|
+
const startServer = await confirm({
|
|
483
|
+
message: "Start the viewer now?",
|
|
484
|
+
default: true,
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
return {
|
|
488
|
+
componentPath,
|
|
489
|
+
createExample,
|
|
490
|
+
startServer,
|
|
491
|
+
themeBlock,
|
|
492
|
+
snapshotsBlock,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// ============================================
|
|
497
|
+
// Main init function
|
|
498
|
+
// ============================================
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Main init function - zero-config by default, interactive with --configure
|
|
399
502
|
*/
|
|
400
503
|
export async function init(options: InitOptions = {}): Promise<InitResult> {
|
|
401
504
|
const projectRoot = resolve(options.projectRoot || process.cwd());
|
|
@@ -425,6 +528,11 @@ export async function init(options: InitOptions = {}): Promise<InitResult> {
|
|
|
425
528
|
scanPath,
|
|
426
529
|
force: options.force,
|
|
427
530
|
verbose: true,
|
|
531
|
+
enrich: options.enrich,
|
|
532
|
+
dryRun: options.dryRun,
|
|
533
|
+
provider: options.provider,
|
|
534
|
+
apiKey: options.apiKey,
|
|
535
|
+
model: options.model,
|
|
428
536
|
});
|
|
429
537
|
|
|
430
538
|
// Create config pointing at the scanned path
|
|
@@ -446,9 +554,16 @@ export async function init(options: InitOptions = {}): Promise<InitResult> {
|
|
|
446
554
|
// Next steps
|
|
447
555
|
if (scanResult.success) {
|
|
448
556
|
console.log(pc.cyan("Next steps:"));
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
557
|
+
if (options.enrich && !options.dryRun) {
|
|
558
|
+
console.log(` 1. Review AI-enriched fields (usage.when, a11yRules, scenarioTags) in generated files`);
|
|
559
|
+
if (scanResult.generated.some(g => g.todoCount > 0)) {
|
|
560
|
+
console.log(` 2. Search remaining ${pc.bold("TODO:")} markers and fill in human knowledge`);
|
|
561
|
+
}
|
|
562
|
+
} else {
|
|
563
|
+
console.log(` 1. Search generated files for ${pc.bold("TODO:")} markers and fill in human knowledge`);
|
|
564
|
+
}
|
|
565
|
+
console.log(` ${options.enrich ? '3' : '2'}. Run ${pc.bold(`${BRAND.cliCommand} dev`)} to preview your components`);
|
|
566
|
+
console.log(` ${options.enrich ? '4' : '3'}. Run ${pc.bold(`${BRAND.cliCommand} build`)} to compile fragments.json`);
|
|
452
567
|
console.log();
|
|
453
568
|
}
|
|
454
569
|
|
|
@@ -465,184 +580,76 @@ export async function init(options: InitOptions = {}): Promise<InitResult> {
|
|
|
465
580
|
};
|
|
466
581
|
}
|
|
467
582
|
|
|
468
|
-
console.log(pc.cyan(`\n
|
|
583
|
+
console.log(pc.cyan(`\n${BRAND.name} init\n`));
|
|
469
584
|
|
|
470
585
|
// Step 1: Detect what exists
|
|
471
586
|
const detection = await detectProject(projectRoot);
|
|
472
587
|
|
|
473
588
|
// Check for existing config
|
|
474
589
|
if (detection.hasConfig && !options.force) {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
};
|
|
492
|
-
}
|
|
590
|
+
if (options.configure) {
|
|
591
|
+
// In configure mode, warn but continue
|
|
592
|
+
console.log(pc.yellow(` ! Config exists: ${BRAND.configFile} (will overwrite)`));
|
|
593
|
+
} else {
|
|
594
|
+
console.log(pc.green(` ✓ Already initialized`) + pc.dim(` (${BRAND.configFile} exists)`));
|
|
595
|
+
console.log();
|
|
596
|
+
console.log(pc.dim(` Run ${pc.bold(`${BRAND.cliCommand} init --force`)} to reinitialize`));
|
|
597
|
+
console.log(pc.dim(` Run ${pc.bold(`${BRAND.cliCommand} init --configure`)} to customize theme, snapshots, etc.`));
|
|
598
|
+
console.log();
|
|
599
|
+
return {
|
|
600
|
+
success: true,
|
|
601
|
+
scenario: detection.scenario,
|
|
602
|
+
storiesFound: detection.storyFiles.length,
|
|
603
|
+
componentsFound: detection.componentFiles.length,
|
|
604
|
+
errors: [],
|
|
605
|
+
};
|
|
493
606
|
}
|
|
494
607
|
}
|
|
495
608
|
|
|
496
|
-
// Step 2: Determine scenario
|
|
609
|
+
// Step 2: Determine scenario
|
|
497
610
|
let scenario: "stories" | "components" | "fresh";
|
|
498
611
|
|
|
499
612
|
if (detection.storyFiles.length > 0) {
|
|
500
613
|
scenario = "stories";
|
|
501
|
-
console.log(pc.green(
|
|
502
|
-
console.log(pc.dim(` ${detection.storyFiles.slice(0, 3).join("\n ")}`));
|
|
503
|
-
if (detection.storyFiles.length > 3) {
|
|
504
|
-
console.log(pc.dim(` ... and ${detection.storyFiles.length - 3} more`));
|
|
505
|
-
}
|
|
506
|
-
console.log();
|
|
507
|
-
console.log(
|
|
508
|
-
pc.cyan("Great news! ") +
|
|
509
|
-
"Fragments can load your existing stories automatically."
|
|
510
|
-
);
|
|
614
|
+
console.log(pc.green(` ✓ Found ${detection.storyFiles.length} Storybook stories`));
|
|
511
615
|
} else if (detection.componentFiles.length > 0) {
|
|
512
616
|
scenario = "components";
|
|
513
|
-
console.log(pc.green(
|
|
514
|
-
console.log(pc.dim(` ${detection.componentFiles.slice(0, 3).join("\n ")}`));
|
|
515
|
-
if (detection.componentFiles.length > 3) {
|
|
516
|
-
console.log(pc.dim(` ... and ${detection.componentFiles.length - 3} more`));
|
|
517
|
-
}
|
|
518
|
-
console.log();
|
|
519
|
-
console.log(
|
|
520
|
-
pc.cyan("No stories found, but that's fine! ") +
|
|
521
|
-
"Fragments can auto-generate documentation from your TypeScript."
|
|
522
|
-
);
|
|
617
|
+
console.log(pc.green(` ✓ Found ${detection.componentFiles.length} components`) + pc.dim(` in ${detection.suggestedComponentPath}`));
|
|
523
618
|
} else {
|
|
524
619
|
scenario = "fresh";
|
|
525
|
-
console.log(pc.
|
|
526
|
-
console.log();
|
|
527
|
-
console.log(pc.cyan("Let's create your first fragment!"));
|
|
620
|
+
console.log(pc.dim(` · No existing components found`));
|
|
528
621
|
}
|
|
529
622
|
|
|
530
|
-
|
|
623
|
+
// Step 3: Detect framework
|
|
624
|
+
const framework = await detectSetupFramework(projectRoot);
|
|
625
|
+
console.log(pc.green(` ✓ Detected ${mapFrameworkLabel(framework)}`));
|
|
531
626
|
|
|
532
|
-
// Step
|
|
627
|
+
// Step 4: Branch — interactive configure or fast path
|
|
533
628
|
let componentPath = detection.suggestedComponentPath;
|
|
534
|
-
let runScan = scenario === "components" || scenario === "stories";
|
|
535
629
|
let createExample = scenario === "fresh";
|
|
536
630
|
let startServer = false;
|
|
537
631
|
let themeBlock = "";
|
|
538
632
|
let snapshotsBlock = "";
|
|
539
633
|
|
|
540
|
-
if (
|
|
541
|
-
|
|
542
|
-
componentPath =
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
if (scenario === "fresh") {
|
|
548
|
-
// Fresh project - ask about example
|
|
549
|
-
createExample = await confirm({
|
|
550
|
-
message: "Create an example Button component to get started?",
|
|
551
|
-
default: true,
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// Theme seed configuration
|
|
556
|
-
const configureTheme = await confirm({
|
|
557
|
-
message: "Configure theme seeds? (brand color, density, radius)",
|
|
558
|
-
default: false,
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
if (configureTheme) {
|
|
562
|
-
const brand = await input({
|
|
563
|
-
message: "Brand color (hex)",
|
|
564
|
-
default: "#18181b",
|
|
565
|
-
validate: (v) => /^#[0-9a-fA-F]{6}$/.test(v) || "Enter a valid hex color (e.g., #6366f1)",
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
const neutral = await select({
|
|
569
|
-
message: "Neutral palette",
|
|
570
|
-
choices: [
|
|
571
|
-
{ value: "stone", name: "Stone (warm gray)" },
|
|
572
|
-
{ value: "ice", name: "Ice (cool blue-gray)" },
|
|
573
|
-
{ value: "earth", name: "Earth (olive/khaki)" },
|
|
574
|
-
{ value: "sand", name: "Sand (warm beige)" },
|
|
575
|
-
{ value: "fire", name: "Fire (warm red-gray)" },
|
|
576
|
-
],
|
|
577
|
-
default: "stone",
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
const density = await select({
|
|
581
|
-
message: "Spacing density",
|
|
582
|
-
choices: [
|
|
583
|
-
{ value: "compact", name: "Compact (tighter spacing)" },
|
|
584
|
-
{ value: "default", name: "Default" },
|
|
585
|
-
{ value: "relaxed", name: "Relaxed (more breathing room)" },
|
|
586
|
-
],
|
|
587
|
-
default: "default",
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
const radiusStyle = await select({
|
|
591
|
-
message: "Border radius style",
|
|
592
|
-
choices: [
|
|
593
|
-
{ value: "sharp", name: "Sharp (0px)" },
|
|
594
|
-
{ value: "subtle", name: "Subtle (2px)" },
|
|
595
|
-
{ value: "default", name: "Default (6px)" },
|
|
596
|
-
{ value: "rounded", name: "Rounded (10px)" },
|
|
597
|
-
{ value: "pill", name: "Pill (999px)" },
|
|
598
|
-
],
|
|
599
|
-
default: "default",
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
// Build theme config block — only include non-default values
|
|
603
|
-
const themeEntries: string[] = [];
|
|
604
|
-
if (brand !== "#18181b") themeEntries.push(` brand: '${brand}'`);
|
|
605
|
-
if (neutral !== "stone") themeEntries.push(` neutral: '${neutral}'`);
|
|
606
|
-
if (density !== "default") themeEntries.push(` density: '${density}'`);
|
|
607
|
-
if (radiusStyle !== "default") themeEntries.push(` radiusStyle: '${radiusStyle}'`);
|
|
608
|
-
|
|
609
|
-
if (themeEntries.length > 0) {
|
|
610
|
-
themeBlock = `\n // Theme seed values (derives 120+ CSS custom properties)\n theme: {\n${themeEntries.join(",\n")},\n },\n`;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
// Snapshot toggle
|
|
615
|
-
const enableSnapshots = await confirm({
|
|
616
|
-
message: "Enable visual snapshot tests per component variant?",
|
|
617
|
-
default: false,
|
|
618
|
-
});
|
|
619
|
-
|
|
620
|
-
if (enableSnapshots) {
|
|
621
|
-
snapshotsBlock = `\n // Visual snapshot testing\n snapshots: {\n enabled: true,\n },\n`;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
// Ask about starting the server
|
|
625
|
-
startServer = await confirm({
|
|
626
|
-
message: "Start the viewer now?",
|
|
627
|
-
default: true,
|
|
628
|
-
});
|
|
634
|
+
if (options.configure) {
|
|
635
|
+
const config = await runInteractiveConfigure(detection, scenario);
|
|
636
|
+
componentPath = config.componentPath;
|
|
637
|
+
createExample = config.createExample;
|
|
638
|
+
startServer = config.startServer;
|
|
639
|
+
themeBlock = config.themeBlock;
|
|
640
|
+
snapshotsBlock = config.snapshotsBlock;
|
|
629
641
|
}
|
|
630
642
|
|
|
631
|
-
// Step
|
|
632
|
-
console.log(pc.dim("\nCreating configuration...\n"));
|
|
633
|
-
|
|
634
|
-
// Build include patterns
|
|
643
|
+
// Step 5: Create configuration file
|
|
635
644
|
const includePaths: string[] = [
|
|
636
645
|
`${componentPath}/**/*.fragment.tsx`,
|
|
637
646
|
];
|
|
638
647
|
|
|
639
|
-
// If Storybook stories detected, also include them for direct rendering
|
|
640
648
|
if (scenario === 'stories') {
|
|
641
649
|
includePaths.push(`${componentPath}/**/*.stories.tsx`);
|
|
642
650
|
includePaths.push(`${componentPath}/**/*.stories.ts`);
|
|
643
651
|
}
|
|
644
652
|
|
|
645
|
-
// Create config file
|
|
646
653
|
const configPath = join(projectRoot, BRAND.configFile);
|
|
647
654
|
const configContent = generateConfig({
|
|
648
655
|
includePaths,
|
|
@@ -654,30 +661,71 @@ export async function init(options: InitOptions = {}): Promise<InitResult> {
|
|
|
654
661
|
|
|
655
662
|
try {
|
|
656
663
|
await writeFile(configPath, configContent, "utf-8");
|
|
657
|
-
console.log(pc.green(
|
|
664
|
+
console.log(pc.green(` ✓ Created ${BRAND.configFile}`));
|
|
658
665
|
} catch (e) {
|
|
659
666
|
errors.push(`Failed to create config: ${e}`);
|
|
660
667
|
}
|
|
661
668
|
|
|
662
|
-
// Step
|
|
669
|
+
// Step 6: Auto-inject styles + framework config
|
|
670
|
+
const entryFile = await findEntryFile(projectRoot, framework);
|
|
671
|
+
|
|
672
|
+
if (entryFile) {
|
|
673
|
+
try {
|
|
674
|
+
const stylesResult = await addStylesImport(projectRoot, entryFile);
|
|
675
|
+
if (stylesResult.modified) {
|
|
676
|
+
console.log(pc.green(` ✓ Added styles import to ${entryFile}`));
|
|
677
|
+
} else {
|
|
678
|
+
console.log(pc.dim(` · ${stylesResult.message}`));
|
|
679
|
+
}
|
|
680
|
+
} catch (e) {
|
|
681
|
+
errors.push(`Failed to add styles import: ${e instanceof Error ? e.message : e}`);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
try {
|
|
685
|
+
const providerResult = await addThemeProvider(projectRoot, entryFile, framework);
|
|
686
|
+
if (providerResult.modified) {
|
|
687
|
+
console.log(pc.green(` ✓ Added ThemeProvider to ${entryFile}`));
|
|
688
|
+
} else {
|
|
689
|
+
console.log(pc.dim(` · ${providerResult.message}`));
|
|
690
|
+
}
|
|
691
|
+
} catch (e) {
|
|
692
|
+
errors.push(`Failed to add ThemeProvider: ${e instanceof Error ? e.message : e}`);
|
|
693
|
+
}
|
|
694
|
+
} else {
|
|
695
|
+
console.log(pc.yellow(` ! Could not detect entry file — add styles import manually`));
|
|
696
|
+
console.log(pc.dim(` import '@fragments-sdk/ui/styles'`));
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Next.js: add transpilePackages
|
|
700
|
+
if (framework === 'nextjs-app' || framework === 'nextjs-pages') {
|
|
701
|
+
try {
|
|
702
|
+
const transpileResult = await addTranspilePackages(projectRoot);
|
|
703
|
+
if (transpileResult.modified) {
|
|
704
|
+
console.log(pc.green(` ✓ ${transpileResult.message}`));
|
|
705
|
+
} else {
|
|
706
|
+
console.log(pc.dim(` · ${transpileResult.message}`));
|
|
707
|
+
}
|
|
708
|
+
} catch (e) {
|
|
709
|
+
errors.push(`Failed to update next.config: ${e instanceof Error ? e.message : e}`);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Step 7: Handle scenario-specific setup
|
|
663
714
|
if (scenario === "fresh" && createExample) {
|
|
664
|
-
// Create example component
|
|
665
715
|
const exampleDir = join(projectRoot, componentPath, "Button");
|
|
666
716
|
|
|
667
717
|
try {
|
|
668
718
|
await mkdir(exampleDir, { recursive: true });
|
|
669
719
|
|
|
670
|
-
// Write Button.tsx
|
|
671
720
|
await writeFile(
|
|
672
721
|
join(exampleDir, "Button.tsx"),
|
|
673
722
|
generateExampleComponent(),
|
|
674
723
|
"utf-8"
|
|
675
724
|
);
|
|
676
725
|
console.log(
|
|
677
|
-
pc.green(
|
|
726
|
+
pc.green(` ✓ Created ${relative(projectRoot, join(exampleDir, "Button.tsx"))}`)
|
|
678
727
|
);
|
|
679
728
|
|
|
680
|
-
// Write Button.fragment.tsx
|
|
681
729
|
await writeFile(
|
|
682
730
|
join(exampleDir, "Button.fragment.tsx"),
|
|
683
731
|
generateExampleFragment(),
|
|
@@ -685,7 +733,7 @@ export async function init(options: InitOptions = {}): Promise<InitResult> {
|
|
|
685
733
|
);
|
|
686
734
|
console.log(
|
|
687
735
|
pc.green(
|
|
688
|
-
|
|
736
|
+
` ✓ Created ${relative(projectRoot, join(exampleDir, "Button.fragment.tsx"))}`
|
|
689
737
|
)
|
|
690
738
|
);
|
|
691
739
|
} catch (e) {
|
|
@@ -693,70 +741,50 @@ export async function init(options: InitOptions = {}): Promise<InitResult> {
|
|
|
693
741
|
}
|
|
694
742
|
}
|
|
695
743
|
|
|
696
|
-
if (
|
|
697
|
-
// Run scan to generate fragments.json from source code
|
|
698
|
-
console.log(pc.dim("\nScanning source code for documentation...\n"));
|
|
744
|
+
if (scenario === "components" || scenario === "stories") {
|
|
699
745
|
try {
|
|
700
746
|
const { scan } = await import("./scan.js");
|
|
701
|
-
await scan({
|
|
747
|
+
const scanResult = await scan({
|
|
702
748
|
config: configPath,
|
|
703
749
|
verbose: false,
|
|
750
|
+
quiet: true,
|
|
704
751
|
});
|
|
705
|
-
|
|
752
|
+
if (scanResult.success) {
|
|
753
|
+
console.log(pc.green(` ✓ Generated fragments.json`) + pc.dim(` (${scanResult.componentCount} components)`));
|
|
754
|
+
} else {
|
|
755
|
+
console.log(pc.dim(` · Auto-documentation will run when you start the dev server`));
|
|
756
|
+
}
|
|
757
|
+
} catch {
|
|
706
758
|
console.log(
|
|
707
|
-
pc.
|
|
759
|
+
pc.dim(` · Auto-documentation will run when you start the dev server`)
|
|
708
760
|
);
|
|
709
761
|
}
|
|
710
762
|
}
|
|
711
763
|
|
|
712
|
-
// Step
|
|
713
|
-
console.log(
|
|
714
|
-
|
|
715
|
-
const frameworkOverride = options.framework as Framework | undefined;
|
|
716
|
-
const frameworkResult = await setupFramework({
|
|
717
|
-
projectRoot,
|
|
718
|
-
framework: frameworkOverride,
|
|
719
|
-
});
|
|
720
|
-
|
|
721
|
-
if (frameworkResult.filesCreated.length > 0) {
|
|
722
|
-
for (const file of frameworkResult.filesCreated) {
|
|
723
|
-
console.log(pc.green(`✓ Created ${file}`));
|
|
724
|
-
}
|
|
725
|
-
}
|
|
764
|
+
// Step 8: Summary
|
|
765
|
+
console.log();
|
|
726
766
|
|
|
727
|
-
if (
|
|
728
|
-
|
|
729
|
-
|
|
767
|
+
if (errors.length > 0) {
|
|
768
|
+
console.log(pc.red(` ${errors.length} error(s) occurred:`));
|
|
769
|
+
for (const error of errors) {
|
|
770
|
+
console.log(pc.red(` - ${error}`));
|
|
730
771
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
const pkgs = frameworkResult.packagesToInstall.join(" ");
|
|
735
|
-
console.log(
|
|
736
|
-
pc.yellow(`\n⚠ Install required dependencies: `) +
|
|
737
|
-
pc.bold(`pnpm add -D ${pkgs}`)
|
|
738
|
-
);
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
for (const warning of frameworkResult.warnings) {
|
|
742
|
-
console.log(pc.yellow(` Note: ${warning}`));
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
// Step 7: Show next steps or start server
|
|
746
|
-
if (errors.length === 0) {
|
|
747
|
-
console.log(pc.green("\n✓ Setup complete!\n"));
|
|
772
|
+
console.log();
|
|
773
|
+
} else {
|
|
774
|
+
console.log(pc.green(` Done!`) + pc.dim(` Setup complete.\n`));
|
|
748
775
|
|
|
749
776
|
if (startServer) {
|
|
750
|
-
console.log(pc.cyan("Starting viewer...\n"));
|
|
751
777
|
startDevServer(projectRoot);
|
|
752
778
|
} else {
|
|
753
|
-
console.log(pc.
|
|
754
|
-
console.log(`
|
|
755
|
-
if (scenario === "fresh") {
|
|
756
|
-
console.log(` 2. Edit ${pc.bold(`${componentPath}/Button/Button.fragment.tsx`)}`);
|
|
757
|
-
}
|
|
758
|
-
console.log(` 3. Run ${pc.bold(`${BRAND.cliCommand} generate`)} to create fragment files for your components`);
|
|
779
|
+
console.log(` ${pc.bold("Get started:")}`);
|
|
780
|
+
console.log(` ${pc.dim("$")} ${BRAND.cliCommand} dev`);
|
|
759
781
|
console.log();
|
|
782
|
+
|
|
783
|
+
if (!options.configure) {
|
|
784
|
+
console.log(` ${pc.bold("Customize:")} theme seeds, snapshots, and more`);
|
|
785
|
+
console.log(` ${pc.dim("$")} ${BRAND.cliCommand} init --configure`);
|
|
786
|
+
console.log();
|
|
787
|
+
}
|
|
760
788
|
}
|
|
761
789
|
}
|
|
762
790
|
|