@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 +1 -1
- package/scripts/drift-sync.mjs +42 -10
- package/src/commands/check.mjs +4 -2
- package/src/commands/init.mjs +56 -17
- package/src/lib/storybook.mjs +4 -1
- package/src/lib/writers.mjs +2 -2
package/package.json
CHANGED
package/scripts/drift-sync.mjs
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
|
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
|
package/src/commands/check.mjs
CHANGED
|
@@ -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
|
|
package/src/commands/init.mjs
CHANGED
|
@@ -102,30 +102,57 @@ export async function init(argv) {
|
|
|
102
102
|
|
|
103
103
|
const sources = Array.isArray(dsSources) ? dsSources : []
|
|
104
104
|
|
|
105
|
-
// ── Storybook nudge —
|
|
106
|
-
|
|
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
|
|
109
|
-
'
|
|
110
|
-
'
|
|
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?
|
|
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
|
-
|
|
120
|
+
console.log(pc.dim('\n Running npx storybook@latest init — follow the prompts:\n'))
|
|
120
121
|
try {
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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('
|
|
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
|
-
|
|
372
|
-
|
|
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
|
-
|
|
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')}
|
package/src/lib/storybook.mjs
CHANGED
|
@@ -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]) =>
|
|
44
|
+
.map(([name, meta]) => {
|
|
45
|
+
const entry = meta.storyPath ? `{ storyPath: '${meta.storyPath}' }` : '{}'
|
|
46
|
+
return ` ${name}: ${entry},`
|
|
47
|
+
})
|
|
45
48
|
.join('\n')
|
|
46
49
|
}
|
package/src/lib/writers.mjs
CHANGED
|
@@ -36,9 +36,9 @@ export function writeDriftConfig(cwd, { storybookUrl, chromaticUrl, figmaFiles,
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
const lines = [
|
|
39
|
-
`import type {
|
|
39
|
+
`import type { DriftConfig } from '@catchdrift/overlay'`,
|
|
40
40
|
``,
|
|
41
|
-
`const config:
|
|
41
|
+
`const config: DriftConfig = {`,
|
|
42
42
|
storybookUrl ? ` storybookUrl: '${storybookUrl}',` : null,
|
|
43
43
|
chromaticUrl ? ` chromaticUrl: '${chromaticUrl}',` : null,
|
|
44
44
|
figmaFilesBlock,
|