@catchdrift/cli 0.1.16 → 0.1.18

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.16",
3
+ "version": "0.1.18",
4
4
  "description": "CLI for Drift — install, check, and manage design system coverage for any React app.",
5
5
  "keywords": [
6
6
  "design-system",
@@ -20,7 +20,7 @@
20
20
  * will skip the "Open in Storybook" link for that component.
21
21
  */
22
22
 
23
- import { readFileSync, writeFileSync, readdirSync } from 'fs'
23
+ import { readFileSync, writeFileSync, readdirSync, existsSync } from 'fs'
24
24
  import { join, resolve } from 'path'
25
25
  import { fileURLToPath } from 'url'
26
26
 
@@ -29,13 +29,26 @@ const ROOT = resolve(__dir, '..')
29
29
 
30
30
  // ─── Read config ─────────────────────────────────────────────────────────────
31
31
 
32
- const configPath = join(ROOT, 'src/ds-coverage/config.ts')
32
+ // Check drift.config.ts at cwd first (created by npx catchdrift init),
33
+ // then fall back to the legacy monorepo path.
34
+ const cwd = process.cwd()
35
+ const configCandidates = [
36
+ join(cwd, 'drift.config.ts'),
37
+ join(cwd, 'src/ds-coverage/config.ts'),
38
+ join(ROOT, 'src/ds-coverage/config.ts'),
39
+ ]
40
+ const configPath = configCandidates.find(p => existsSync(p))
33
41
  let configSrc
34
42
 
43
+ if (!configPath) {
44
+ console.error('[drift-sync] No drift.config.ts found. Run `npx catchdrift init` first.')
45
+ process.exit(1)
46
+ }
47
+
35
48
  try {
36
49
  configSrc = readFileSync(configPath, 'utf8')
37
50
  } catch {
38
- console.error('[drift-sync] Could not read src/ds-coverage/config.ts. Make sure you\'re running this from the project root.')
51
+ console.error(`[drift-sync] Could not read ${configPath}`)
39
52
  process.exit(1)
40
53
  }
41
54
 
@@ -46,15 +59,34 @@ const dsPackages = pkgMatch
46
59
  : []
47
60
 
48
61
  if (dsPackages.length === 0) {
49
- console.log(`
50
- [drift-sync] No dsPackages found in config.ts.
62
+ // Check if the user has a Figma or Storybook setup instead
63
+ const hasFigma = configSrc.includes('figmaFileKey') || configSrc.includes('figmaFiles')
64
+ const hasStorybook = configSrc.includes('storybookUrl')
51
65
 
52
- Add this to src/ds-coverage/config.ts:
66
+ if (hasFigma || hasStorybook) {
67
+ console.log(`
68
+ [drift-sync] Your DS is connected via ${[hasFigma && 'Figma', hasStorybook && 'Storybook'].filter(Boolean).join(' + ')}, not an npm package.
69
+
70
+ To populate your component registry:
71
+ ${hasFigma ? ' Figma: open Claude Code and run /drift-sync figma' : ''}
72
+ ${hasStorybook ? ' Storybook: make sure Storybook is running, then re-run npx catchdrift sync --storybook' : ''}
73
+
74
+ catchdrift sync (this command) is for scanning imports from a DS npm package.
75
+ To use it, add dsPackages to drift.config.ts:
53
76
 
54
77
  dsPackages: ['@your/component-library'],
78
+ `.trim())
79
+ } else {
80
+ console.log(`
81
+ [drift-sync] No dsPackages found in drift.config.ts.
55
82
 
56
- Then run npm run drift-sync again.
57
- `.trim())
83
+ Add this to drift.config.ts:
84
+
85
+ dsPackages: ['@your/component-library'],
86
+
87
+ Then run npx catchdrift sync again.
88
+ `.trim())
89
+ }
58
90
  process.exit(0)
59
91
  }
60
92
 
@@ -163,14 +195,14 @@ const updated = configSrc.replace(
163
195
  )
164
196
 
165
197
  if (updated === configSrc) {
166
- console.warn('[drift-sync] Could not locate the components block in config.ts. Make sure it ends with `},` on its own line.')
198
+ console.warn('[drift-sync] Could not locate the components block in drift.config.ts. Make sure it ends with `},` on its own line.')
167
199
  process.exit(1)
168
200
  }
169
201
 
170
202
  writeFileSync(configPath, updated, 'utf8')
171
203
 
172
204
  console.log(`
173
- [drift-sync] Done. Updated src/ds-coverage/config.ts with ${sortedNames.length} components.
205
+ [drift-sync] Done. Updated ${configPath.replace(cwd + '/', '')} with ${sortedNames.length} components.
174
206
 
175
207
  Next steps:
176
208
  • If you have a Storybook site, add storyPath values to get "Open in Storybook" links
@@ -24,13 +24,15 @@ const __dirname = dirname(fileURLToPath(import.meta.url))
24
24
  const CHECKER = resolve(__dirname, '../../scripts/drift-check.mjs')
25
25
 
26
26
  export async function check(argv) {
27
+ console.log(pc.dim('Scanning running app for DS coverage...'))
28
+ console.log(pc.dim('Make sure your dev server is running (npm run dev) before this completes.\n'))
29
+
27
30
  // Warn if running via npx (ephemeral cache) — Playwright browser reinstalls on every npx clear
28
31
  const isNpx = process.env.npm_execpath?.includes('_npx') || process.argv[1]?.includes('_npx')
29
32
  if (isNpx) {
30
33
  console.log(pc.dim(
31
34
  'Tip: install locally for faster runs and persistent Playwright cache:\n' +
32
- ' npm install -D catchdrift\n' +
33
- ' npx catchdrift check (uses local install)\n'
35
+ ' npm install -D catchdrift\n'
34
36
  ))
35
37
  }
36
38
 
@@ -102,30 +102,57 @@ export async function init(argv) {
102
102
 
103
103
  const sources = Array.isArray(dsSources) ? dsSources : []
104
104
 
105
- // ── Storybook nudge — needed to close the Figma code loop ─────────────────
106
- if (sources.includes('figma') && !sources.includes('storybook') && !storybook.found) {
105
+ // ── Storybook nudge — fire whenever Storybook isn't in the setup ─────────────
106
+ const storybookNeeded = !sources.includes('storybook') && !storybook.found
107
+ if (storybookNeeded && sources.length > 0) {
107
108
  p.log.warn(
108
- 'Storybook is needed to complete the loop.\n' +
109
- ' Without it, the overlay has no way to identify which components are from your DS.\n' +
110
- ' Figma tells Drift what exists in design Storybook tells it what exists in code.'
109
+ 'Storybook is needed to close the design → code loop.\n' +
110
+ ' It lets Drift identify which components are from your DS vs. custom-built.\n' +
111
+ ' Without it, coverage will show 0% until you manually list components.'
111
112
  )
112
113
  const setupSB = await p.confirm({
113
- message: 'Set up Storybook now? (runs npx storybook@latest init)',
114
+ message: 'Set up Storybook now?',
114
115
  initialValue: true,
115
116
  })
116
117
  if (p.isCancel(setupSB)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
117
118
 
118
119
  if (setupSB) {
119
- spinner.start('Running npx storybook@latest init...')
120
+ console.log(pc.dim('\n Running npx storybook@latest init — follow the prompts:\n'))
120
121
  try {
121
- execSync('npx storybook@latest init --yes', { cwd, stdio: 'ignore' })
122
- spinner.stop('Storybook installed it will be available at http://localhost:6006')
122
+ // Use inherit so the user can interact with Storybook's framework prompts
123
+ execSync('npx storybook@latest init', { cwd, stdio: 'inherit' })
124
+ p.log.success('Storybook installed — run `npm run storybook` to start it on :6006')
123
125
  sources.push('storybook')
124
126
  } catch {
125
- spinner.stop('Storybook install failed run `npx storybook@latest init` manually, then re-run `npx catchdrift init`')
127
+ p.log.warn('Storybook install failed or was cancelled. Run `npx storybook@latest init` manually when ready.')
126
128
  }
127
129
  } else {
128
- p.log.info('Continuing without Storybook. Add it later and re-run `npx catchdrift init` to complete the loop.')
130
+ p.log.info('Skipping Storybook. Run `npx storybook@latest init` when ready, then re-run `npx catchdrift init`.')
131
+ }
132
+ }
133
+
134
+ // ── If user selected Storybook but it wasn't detected, offer to install ──────
135
+ if (sources.includes('storybook') && !storybook.found) {
136
+ const installNow = await p.confirm({
137
+ message: 'Storybook wasn\'t detected in this project. Install it now?',
138
+ initialValue: true,
139
+ })
140
+ if (p.isCancel(installNow)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
141
+
142
+ if (installNow) {
143
+ console.log(pc.dim('\n Running npx storybook@latest init — follow the prompts:\n'))
144
+ try {
145
+ execSync('npx storybook@latest init', { cwd, stdio: 'inherit' })
146
+ p.log.success('Storybook installed — run `npm run storybook` to start it on :6006')
147
+ } catch {
148
+ p.log.warn('Storybook install failed or was cancelled. Run `npx storybook@latest init` manually when ready.')
149
+ // Remove storybook from sources so we don't ask for a URL that doesn't exist yet
150
+ sources.splice(sources.indexOf('storybook'), 1)
151
+ }
152
+ } else {
153
+ // User declined — remove from sources so the URL step is skipped
154
+ sources.splice(sources.indexOf('storybook'), 1)
155
+ p.log.info('Skipping Storybook setup. Re-run `npx catchdrift init` after installing it.')
129
156
  }
130
157
  }
131
158
 
@@ -368,9 +395,23 @@ export async function init(argv) {
368
395
  console.log('')
369
396
  p.outro(pc.green('Drift is set up ✓'))
370
397
 
371
- const syncHint = dsPackages?.length
372
- ? `\n ${pc.cyan('npx catchdrift sync')} Auto-discover components from ${dsPackages.join(', ')}`
373
- : ''
398
+ // Build next steps dynamically based on what was configured
399
+ const nextSteps = []
400
+
401
+ if (dsPackages?.length) {
402
+ nextSteps.push(` ${pc.cyan('npx catchdrift sync')} Auto-populate registry from ${dsPackages.join(', ')}`)
403
+ } else if (!sources.includes('storybook') || Object.keys(components).length === 0) {
404
+ nextSteps.push(` ${pc.cyan('npx catchdrift sync')} Auto-populate registry once you have a DS package`)
405
+ }
406
+
407
+ nextSteps.push(` ${pc.cyan('npm run dev')} Start your app, then press ${pc.bold('D')} to open Drift`)
408
+ nextSteps.push(` ${pc.cyan('npx catchdrift check')} Run a coverage scan ${pc.dim('(app must be running)')}`)
409
+ nextSteps.push(` ${pc.cyan('git push')} First PR will show a drift delta comment`)
410
+
411
+ if (!sources.includes('storybook') && !storybook.found) {
412
+ nextSteps.push(`\n ${pc.yellow('Storybook not set up')} — run ${pc.bold('npx storybook@latest init')} when ready,`)
413
+ nextSteps.push(` then re-run ${pc.bold('npx catchdrift init')} to complete the coverage loop.`)
414
+ }
374
415
 
375
416
  console.log(`
376
417
  ${pc.bold('What was created:')}
@@ -380,9 +421,7 @@ ${pc.bold('What was created:')}
380
421
  ${patched ? patched.padEnd(34) + pc.dim('DriftOverlay added (dev-only)') : ''}
381
422
 
382
423
  ${pc.bold('Next steps:')}
383
- ${pc.cyan('npm run dev')} Open your app, press ${pc.bold('D')} to see Drift${syncHint}
384
- ${pc.cyan('npx catchdrift check')} Run a coverage scan (requires running app)
385
- ${pc.cyan('git push')} First PR will show a drift delta comment
424
+ ${nextSteps.join('\n')}
386
425
 
387
426
  ${pc.bold('Your team\'s daily commands (Claude Code):')}
388
427
  ${pc.blue('/drift-context')} ${pc.dim('See DS state at a glance — run this first')}
@@ -41,6 +41,9 @@ export async function fetchStorybookComponents(storybookUrl) {
41
41
 
42
42
  export function buildComponentRegistry(components) {
43
43
  return Object.entries(components)
44
- .map(([name, meta]) => ` ${name}: { storyPath: '${meta.storyPath}' },`)
44
+ .map(([name, meta]) => {
45
+ const entry = meta.storyPath ? `{ storyPath: '${meta.storyPath}' }` : '{}'
46
+ return ` ${name}: ${entry},`
47
+ })
45
48
  .join('\n')
46
49
  }
@@ -36,9 +36,9 @@ export function writeDriftConfig(cwd, { storybookUrl, chromaticUrl, figmaFiles,
36
36
  }
37
37
 
38
38
  const lines = [
39
- `import type { DesignDriftConfig } from '@catchdrift/overlay'`,
39
+ `import type { DriftConfig } from '@catchdrift/overlay'`,
40
40
  ``,
41
- `const config: DesignDriftConfig = {`,
41
+ `const config: DriftConfig = {`,
42
42
  storybookUrl ? ` storybookUrl: '${storybookUrl}',` : null,
43
43
  chromaticUrl ? ` chromaticUrl: '${chromaticUrl}',` : null,
44
44
  figmaFilesBlock,