@catchdrift/cli 0.1.19 → 0.1.21
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/package.json +1 -1
- package/src/commands/init.mjs +58 -50
- package/src/lib/writers.mjs +27 -1
package/package.json
CHANGED
package/src/commands/init.mjs
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from '../lib/storybook.mjs'
|
|
32
32
|
import {
|
|
33
33
|
writeDriftConfig,
|
|
34
|
+
writeEnvLocal,
|
|
34
35
|
writeAIRulesFiles,
|
|
35
36
|
writeClaudeSkills,
|
|
36
37
|
patchAppEntry,
|
|
@@ -339,7 +340,9 @@ export async function init(argv) {
|
|
|
339
340
|
dsPackages,
|
|
340
341
|
threshold: Number(threshold) || 80,
|
|
341
342
|
components,
|
|
343
|
+
framework,
|
|
342
344
|
})
|
|
345
|
+
if (figmaToken) writeEnvLocal(cwd, { figmaToken })
|
|
343
346
|
spinner.stop('drift.config.ts written')
|
|
344
347
|
|
|
345
348
|
// ── Step 7: Write AI rules files ─────────────────────────────────────────────
|
|
@@ -397,67 +400,72 @@ export async function init(argv) {
|
|
|
397
400
|
console.log('')
|
|
398
401
|
p.outro(pc.green('Drift is set up ✓'))
|
|
399
402
|
|
|
400
|
-
//
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
}
|
|
403
|
+
// ── What was set up (human-readable) ────────────────────────────────────────
|
|
404
|
+
const patchedShort = patched ? patched.replace(cwd + '/', '').replace(cwd, '') : null
|
|
405
|
+
console.log(`
|
|
406
|
+
${pc.bold('What was set up:')}
|
|
407
|
+
${pc.green('✓')} Overlay added to your app ${patchedShort ? pc.dim('(' + patchedShort + ')') : pc.yellow('— see manual step below')}
|
|
408
|
+
${pc.green('✓')} AI rules written ${pc.dim('— your AI tools will use DS components by default')}
|
|
409
|
+
${pc.green('✓')} Claude skills added ${pc.dim('— /drift-sync, /drift-scaffold, /drift-context, ...')}
|
|
410
|
+
${addCI ? pc.green('✓') + ' GitHub check added ' + pc.dim('— every PR will show a coverage score') : pc.dim('○ GitHub check skipped')}`)
|
|
408
411
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
nextSteps.push(` ${pc.cyan('git push')} First PR will show a drift delta comment`)
|
|
412
|
+
// ── Immediate next step — specific to what was configured ───────────────────
|
|
413
|
+
console.log(`\n${pc.bold('Do this now:')}`)
|
|
412
414
|
|
|
413
|
-
if (
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
415
|
+
if (figmaFiles.length > 0 && skillFiles.length > 0) {
|
|
416
|
+
console.log(`
|
|
417
|
+
Your components are in Figma. Pull them into the registry now:
|
|
417
418
|
|
|
418
|
-
|
|
419
|
-
${pc.
|
|
420
|
-
|
|
421
|
-
${
|
|
422
|
-
${skillFiles.length ? `.claude/commands/ ${pc.dim('Claude Code skills (/drift-sync, /drift-scaffold, etc.)')}` : ''}
|
|
423
|
-
${addCI ? '.github/workflows/drift-check.yml ' + pc.dim('CI drift check on every PR') : ''}
|
|
424
|
-
${patched ? patched.padEnd(34) + pc.dim('DriftOverlay added (dev-only)') : ''}
|
|
425
|
-
|
|
426
|
-
${pc.bold('Next steps:')}
|
|
427
|
-
${nextSteps.join('\n')}
|
|
428
|
-
|
|
429
|
-
${pc.bold('Your team\'s daily commands (Claude Code):')}
|
|
430
|
-
${pc.blue('/drift-context')} ${pc.dim('See DS state at a glance — run this first')}
|
|
431
|
-
${pc.blue('/drift-prd')} ${pc.dim('Generate a component inventory for a PRD')}
|
|
432
|
-
${pc.blue('/drift-scaffold')} ${pc.dim('Scaffold a new screen using only DS components')}
|
|
433
|
-
${pc.blue('/drift check')} ${pc.dim('Verify coverage before submitting a PR')}
|
|
434
|
-
${pc.blue('/drift-sync')} ${pc.dim('Re-sync registry after adding DS components')}
|
|
435
|
-
${pc.blue('/drift fix <X>')} ${pc.dim('Migrate a custom component to its DS equivalent')}
|
|
436
|
-
|
|
437
|
-
${pc.dim('Docs: https://catchdrift.ai · Issues: https://github.com/dyoon92/design-drift/issues')}
|
|
419
|
+
${pc.cyan('1.')} Open ${pc.bold('Claude Code')} in this project folder
|
|
420
|
+
${pc.cyan('2.')} Run: ${pc.blue('/drift-sync figma')}
|
|
421
|
+
This reads your Figma file and registers all your DS components.
|
|
422
|
+
${pc.cyan('3.')} Run: ${pc.bold('npm run dev')} → press ${pc.bold('D')} → see live coverage
|
|
438
423
|
`)
|
|
424
|
+
} else if (sources.includes('storybook') && Object.keys(components).length > 0) {
|
|
425
|
+
console.log(`
|
|
426
|
+
Your components were imported from Storybook ✓
|
|
439
427
|
|
|
440
|
-
|
|
441
|
-
|
|
428
|
+
${pc.cyan('1.')} Run: ${pc.bold('npm run dev')}
|
|
429
|
+
${pc.cyan('2.')} Press ${pc.bold('D')} to open the Drift overlay — you should see real coverage.
|
|
430
|
+
`)
|
|
431
|
+
} else if (dsPackages?.length) {
|
|
432
|
+
console.log(`
|
|
433
|
+
${pc.cyan('1.')} Run: ${pc.bold('npx catchdrift sync')}
|
|
434
|
+
Scans your codebase for imports from ${dsPackages.join(', ')} and registers them.
|
|
435
|
+
${pc.cyan('2.')} Run: ${pc.bold('npm run dev')} → press ${pc.bold('D')} → see live coverage
|
|
436
|
+
`)
|
|
437
|
+
} else {
|
|
438
|
+
console.log(`
|
|
439
|
+
${pc.cyan('1.')} Run: ${pc.bold('npm run dev')} → press ${pc.bold('D')} → the overlay opens
|
|
440
|
+
Coverage will show 0% until you register your DS components.
|
|
441
|
+
${pc.cyan('2.')} Add your components to ${pc.bold('drift.config.ts')} or connect a source:
|
|
442
|
+
• Figma: re-run ${pc.bold('npx catchdrift init')} and select Figma
|
|
443
|
+
• npm: add ${pc.bold('dsPackages')} to drift.config.ts, then run ${pc.bold('npx catchdrift sync')}
|
|
442
444
|
`)
|
|
443
|
-
console.log(` ${pc.dim('// src/main.tsx')}`)
|
|
444
|
-
console.log(` import { DriftOverlay } from '@catchdrift/overlay'`)
|
|
445
|
-
console.log(` import driftConfig from '../drift.config'`)
|
|
446
|
-
console.log('')
|
|
447
|
-
console.log(` ${pc.dim('// Last child in your root render:')}`)
|
|
448
|
-
console.log(` {import.meta.env.DEV && <DriftOverlay config={driftConfig} />}`)
|
|
449
|
-
console.log('')
|
|
450
445
|
}
|
|
451
446
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
447
|
+
// ── Ongoing daily commands ───────────────────────────────────────────────────
|
|
448
|
+
console.log(`${pc.bold('Daily workflow (Claude Code):')}
|
|
449
|
+
${pc.blue('/drift-context')} Check DS health — run this before starting work
|
|
450
|
+
${pc.blue('/drift-scaffold')} Build a new screen using only DS components
|
|
451
|
+
${pc.blue('/drift-sync')} Update registry after Figma or Storybook changes
|
|
452
|
+
${pc.blue('npx catchdrift check')} Check coverage before submitting a PR
|
|
453
|
+
`)
|
|
454
|
+
|
|
455
|
+
if (!sources.includes('storybook') && !storybook.found) {
|
|
456
|
+
console.log(`${pc.yellow('Note:')} Storybook isn't set up yet. Run ${pc.bold('npx storybook@latest init')} when ready,`)
|
|
457
|
+
console.log(` then re-run ${pc.bold('npx catchdrift init')} to complete the coverage loop.\n`)
|
|
455
458
|
}
|
|
456
459
|
|
|
457
|
-
if (
|
|
458
|
-
console.log(`${pc.
|
|
459
|
-
console.log('')
|
|
460
|
+
if (!patchedShort) {
|
|
461
|
+
console.log(`${pc.yellow('Manual step needed')} — add the overlay to your app entry point:`)
|
|
462
|
+
console.log(` import { DriftOverlay } from '@catchdrift/overlay'`)
|
|
463
|
+
console.log(` import driftConfig from './drift.config'`)
|
|
464
|
+
console.log(` // inside your root render, as the last child:`)
|
|
465
|
+
console.log(` {import.meta.env.DEV && <DriftOverlay config={driftConfig} />}\n`)
|
|
460
466
|
}
|
|
467
|
+
|
|
468
|
+
console.log(pc.dim('Docs: https://catchdrift.ai · Issues: https://github.com/dyoon92/design-drift/issues'))
|
|
461
469
|
}
|
|
462
470
|
|
|
463
471
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
package/src/lib/writers.mjs
CHANGED
|
@@ -13,7 +13,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
|
13
13
|
|
|
14
14
|
// ── drift.config.ts ───────────────────────────────────────────────────────────
|
|
15
15
|
|
|
16
|
-
export function writeDriftConfig(cwd, { storybookUrl, chromaticUrl, figmaFiles, dsPackages, threshold, components }) {
|
|
16
|
+
export function writeDriftConfig(cwd, { storybookUrl, chromaticUrl, figmaFiles, dsPackages, threshold, components, framework }) {
|
|
17
17
|
const registry = buildComponentRegistry(components)
|
|
18
18
|
|
|
19
19
|
const dsPackagesLine = dsPackages?.length
|
|
@@ -22,6 +22,7 @@ export function writeDriftConfig(cwd, { storybookUrl, chromaticUrl, figmaFiles,
|
|
|
22
22
|
|
|
23
23
|
// Build figmaFiles block — single file gets a compact shape, multiple get an array
|
|
24
24
|
let figmaFilesBlock = null
|
|
25
|
+
const hasFigma = figmaFiles?.length > 0
|
|
25
26
|
if (figmaFiles?.length === 1) {
|
|
26
27
|
const f = figmaFiles[0]
|
|
27
28
|
figmaFilesBlock = ` figmaFileKey: '${f.key}',`
|
|
@@ -38,6 +39,15 @@ export function writeDriftConfig(cwd, { storybookUrl, chromaticUrl, figmaFiles,
|
|
|
38
39
|
figmaFilesBlock = ` figmaFiles: [\n${entries}\n ],`
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
// Figma token — read from env var at runtime so it's never committed to git
|
|
43
|
+
let figmaTokenLine = null
|
|
44
|
+
if (hasFigma) {
|
|
45
|
+
const envExpr = framework === 'nextjs'
|
|
46
|
+
? `process.env.NEXT_PUBLIC_FIGMA_TOKEN`
|
|
47
|
+
: `import.meta.env.VITE_FIGMA_TOKEN`
|
|
48
|
+
figmaTokenLine = ` figmaToken: ${envExpr}, // set in .env.local — never commit your token`
|
|
49
|
+
}
|
|
50
|
+
|
|
41
51
|
const lines = [
|
|
42
52
|
`import type { DriftConfig } from '@catchdrift/overlay'`,
|
|
43
53
|
``,
|
|
@@ -45,6 +55,7 @@ export function writeDriftConfig(cwd, { storybookUrl, chromaticUrl, figmaFiles,
|
|
|
45
55
|
storybookUrl ? ` storybookUrl: '${storybookUrl}',` : null,
|
|
46
56
|
chromaticUrl ? ` chromaticUrl: '${chromaticUrl}',` : null,
|
|
47
57
|
figmaFilesBlock,
|
|
58
|
+
figmaTokenLine,
|
|
48
59
|
` threshold: ${threshold},`,
|
|
49
60
|
dsPackagesLine,
|
|
50
61
|
` components: {`,
|
|
@@ -60,6 +71,21 @@ export function writeDriftConfig(cwd, { storybookUrl, chromaticUrl, figmaFiles,
|
|
|
60
71
|
writeFileSync(join(cwd, 'drift.config.ts'), lines, 'utf8')
|
|
61
72
|
}
|
|
62
73
|
|
|
74
|
+
export function writeEnvLocal(cwd, { figmaToken }) {
|
|
75
|
+
if (!figmaToken) return
|
|
76
|
+
const envPath = join(cwd, '.env.local')
|
|
77
|
+
const line = `VITE_FIGMA_TOKEN=${figmaToken}\nNEXT_PUBLIC_FIGMA_TOKEN=${figmaToken}\n`
|
|
78
|
+
|
|
79
|
+
if (existsSync(envPath)) {
|
|
80
|
+
const existing = readFileSync(envPath, 'utf8')
|
|
81
|
+
// Don't duplicate if already set
|
|
82
|
+
if (existing.includes('FIGMA_TOKEN=')) return
|
|
83
|
+
writeFileSync(envPath, existing.trimEnd() + '\n' + line, 'utf8')
|
|
84
|
+
} else {
|
|
85
|
+
writeFileSync(envPath, `# Drift — Figma token (auto-generated by npx catchdrift init)\n${line}`, 'utf8')
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
63
89
|
// ── AI rules files ────────────────────────────────────────────────────────────
|
|
64
90
|
|
|
65
91
|
const COMPONENT_TABLE_START = '<!-- drift:components-start -->'
|