@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@catchdrift/cli",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "CLI for Drift — install, check, and manage design system coverage for any React app.",
5
5
  "keywords": [
6
6
  "design-system",
@@ -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
- // Build next steps dynamically based on what was configured
401
- const nextSteps = []
402
-
403
- if (dsPackages?.length) {
404
- nextSteps.push(` ${pc.cyan('npx catchdrift sync')} Auto-populate registry from ${dsPackages.join(', ')}`)
405
- } else if (!sources.includes('storybook') || Object.keys(components).length === 0) {
406
- nextSteps.push(` ${pc.cyan('npx catchdrift sync')} Auto-populate registry once you have a DS package`)
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
- nextSteps.push(` ${pc.cyan('npm run dev')} Start your app, then press ${pc.bold('D')} to open Drift`)
410
- nextSteps.push(` ${pc.cyan('npx catchdrift check')} Run a coverage scan ${pc.dim('(app must be running)')}`)
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 (!sources.includes('storybook') && !storybook.found) {
414
- nextSteps.push(`\n ${pc.yellow('Storybook not set up')} — run ${pc.bold('npx storybook@latest init')} when ready,`)
415
- nextSteps.push(` then re-run ${pc.bold('npx catchdrift init')} to complete the coverage loop.`)
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
- console.log(`
419
- ${pc.bold('What was created:')}
420
- drift.config.ts ${pc.dim('DS component registry')}
421
- ${rulesFiles.map(f => f.padEnd(34)).join('\n ')}${pc.dim('AI constraints')}
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
- if (!patched) {
441
- console.log(`${pc.yellow('Manual step needed')} add DriftOverlay to your app entry point:
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
- if (dsPackages?.length) {
453
- console.log(`${pc.blue('Tip:')} Run ${pc.bold('npx catchdrift sync')} to auto-populate your component registry from ${dsPackages.join(', ')}.`)
454
- console.log('')
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 (figmaFiles.length > 0) {
458
- console.log(`${pc.blue('Tip:')} Open the Drift overlay (press D), go to Settings, and paste your Figma personal access token to enable Figma component sync.`)
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 ───────────────────────────────────────────────────────────────────
@@ -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 -->'