@catchdrift/cli 0.1.22 → 0.2.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/package.json +1 -1
- package/src/commands/init.mjs +118 -19
- package/src/index.mjs +1 -1
- package/src/lib/writers.mjs +3 -1
package/package.json
CHANGED
package/src/commands/init.mjs
CHANGED
|
@@ -85,24 +85,70 @@ export async function init(argv) {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
// ── Step 2:
|
|
88
|
+
// ── Step 2: Do they have a DS at all? ────────────────────────────────────────
|
|
89
89
|
console.log('')
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const dsSources = await p.multiselect({
|
|
94
|
-
message: 'DS source (you can pick more than one)',
|
|
95
|
-
options: [
|
|
96
|
-
{ value: 'figma', label: 'Figma', hint: 'components published in a Figma file' },
|
|
97
|
-
{ value: 'storybook', label: 'Storybook', hint: 'stories at a URL' },
|
|
98
|
-
{ value: 'package', label: 'npm package / path', hint: 'e.g. @acme/ui or ./src/components' },
|
|
99
|
-
{ value: 'manual', label: 'I\'ll type component names myself', hint: 'you can always run /drift-sync later' },
|
|
100
|
-
],
|
|
101
|
-
required: false,
|
|
90
|
+
const hasDS = await p.confirm({
|
|
91
|
+
message: 'Do you already have a design system?',
|
|
92
|
+
initialValue: true,
|
|
102
93
|
})
|
|
103
|
-
if (p.isCancel(
|
|
94
|
+
if (p.isCancel(hasDS)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
95
|
+
|
|
96
|
+
let dsSources = []
|
|
97
|
+
if (hasDS) {
|
|
98
|
+
// ── Step 2b: Where does it live? ───────────────────────────────────────────
|
|
99
|
+
console.log('')
|
|
100
|
+
p.log.step('Where does your design system live? Select all that apply.')
|
|
101
|
+
console.log('')
|
|
102
|
+
|
|
103
|
+
const picked = await p.multiselect({
|
|
104
|
+
message: 'DS source (you can pick more than one)',
|
|
105
|
+
options: [
|
|
106
|
+
{ value: 'figma', label: 'Figma', hint: 'components published in a Figma file' },
|
|
107
|
+
{ value: 'storybook', label: 'Storybook', hint: 'stories at a URL' },
|
|
108
|
+
{ value: 'package', label: 'npm package / path', hint: 'e.g. @acme/ui or ./src/components' },
|
|
109
|
+
{ value: 'manual', label: 'Type names manually', hint: 'you can always run /drift-sync later' },
|
|
110
|
+
],
|
|
111
|
+
required: false,
|
|
112
|
+
})
|
|
113
|
+
if (p.isCancel(picked)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
114
|
+
dsSources = Array.isArray(picked) ? picked : []
|
|
115
|
+
} else {
|
|
116
|
+
dsSources = ['fresh']
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const sources = dsSources
|
|
120
|
+
|
|
121
|
+
// ── Step 2c: Bootstrap foundation picker ─────────────────────────────────────
|
|
122
|
+
// Fires when user has no existing DS
|
|
123
|
+
const DS_FOUNDATIONS = [
|
|
124
|
+
{ value: 'shadcn', label: 'shadcn/ui', hint: 'Radix + Tailwind — copy-paste components', pkg: '@radix-ui/react-dialog', install: 'npx shadcn@latest init', docs: 'https://ui.shadcn.com' },
|
|
125
|
+
{ value: 'mui', label: 'Material UI', hint: 'Google Material Design — battle-tested ecosystem', pkg: '@mui/material', install: 'npm install @mui/material @emotion/react @emotion/styled', docs: 'https://mui.com/material-ui/' },
|
|
126
|
+
{ value: 'antd', label: 'Ant Design', hint: 'Enterprise-grade — rich component set', pkg: 'antd', install: 'npm install antd', docs: 'https://ant.design/components/overview/' },
|
|
127
|
+
{ value: 'chakra', label: 'Chakra UI', hint: 'Accessible, composable — great DX', pkg: '@chakra-ui/react', install: 'npm install @chakra-ui/react @emotion/react @emotion/styled framer-motion', docs: 'https://chakra-ui.com/docs/components' },
|
|
128
|
+
{ value: 'radix', label: 'Radix Themes', hint: 'Headless primitives + opinionated theme layer', pkg: '@radix-ui/themes', install: 'npm install @radix-ui/themes', docs: 'https://www.radix-ui.com/themes' },
|
|
129
|
+
{ value: 'primer', label: 'Primer (GitHub)', hint: "GitHub's DS — clean, minimal, production-grade", pkg: '@primer/react', install: 'npm install @primer/react styled-components', docs: 'https://primer.style/components' },
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
let chosenFoundation = null
|
|
133
|
+
if (sources.includes('fresh')) {
|
|
134
|
+
p.log.info(`No DS yet — let's pick a component foundation to track.`)
|
|
135
|
+
console.log(pc.dim(` Preview any of these at their docs before deciding:\n`))
|
|
136
|
+
DS_FOUNDATIONS.forEach(f => {
|
|
137
|
+
console.log(` ${pc.bold(f.label.padEnd(18))} ${pc.dim(f.hint)}`)
|
|
138
|
+
console.log(` ${pc.dim(' '.repeat(18) + f.docs)}\n`)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
const foundationChoice = await p.select({
|
|
142
|
+
message: 'Which foundation will your team use?',
|
|
143
|
+
options: DS_FOUNDATIONS.map(f => ({ value: f.value, label: f.label, hint: f.hint })),
|
|
144
|
+
})
|
|
145
|
+
if (p.isCancel(foundationChoice)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
146
|
+
chosenFoundation = DS_FOUNDATIONS.find(f => f.value === foundationChoice)
|
|
104
147
|
|
|
105
|
-
|
|
148
|
+
p.log.success(`${chosenFoundation.label} selected — Drift will track it via dsPackages.`)
|
|
149
|
+
p.log.info(`Install it now or later:\n ${pc.bold(chosenFoundation.install)}`)
|
|
150
|
+
p.log.info(`Docs + live preview: ${pc.cyan(chosenFoundation.docs)}`)
|
|
151
|
+
}
|
|
106
152
|
|
|
107
153
|
// ── Storybook nudge — fire whenever Storybook isn't in the setup ─────────────
|
|
108
154
|
const storybookNeeded = !sources.includes('storybook') && !storybook.found
|
|
@@ -329,7 +375,40 @@ export async function init(argv) {
|
|
|
329
375
|
})
|
|
330
376
|
if (p.isCancel(aiToolsSelected)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
331
377
|
|
|
378
|
+
// ── Step 5b: Jira integration (optional) ─────────────────────────────────────
|
|
379
|
+
let jiraBaseUrl, jiraProjectKey
|
|
380
|
+
const wantJira = await p.confirm({
|
|
381
|
+
message: 'Connect Jira? (lets the overlay create tickets directly — skip to add later)',
|
|
382
|
+
initialValue: false,
|
|
383
|
+
})
|
|
384
|
+
if (p.isCancel(wantJira)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
385
|
+
|
|
386
|
+
if (wantJira) {
|
|
387
|
+
const jiraUrl = await p.text({
|
|
388
|
+
message: 'Jira base URL',
|
|
389
|
+
placeholder: 'https://yourcompany.atlassian.net',
|
|
390
|
+
validate: v => (!v?.trim() ? 'Required' : undefined),
|
|
391
|
+
})
|
|
392
|
+
if (p.isCancel(jiraUrl)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
393
|
+
jiraBaseUrl = jiraUrl?.trim().replace(/\/$/, '')
|
|
394
|
+
|
|
395
|
+
const jiraKey = await p.text({
|
|
396
|
+
message: 'Jira project key',
|
|
397
|
+
placeholder: 'DS',
|
|
398
|
+
defaultValue: 'DS',
|
|
399
|
+
})
|
|
400
|
+
if (p.isCancel(jiraKey)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
401
|
+
jiraProjectKey = jiraKey?.trim() || 'DS'
|
|
402
|
+
|
|
403
|
+
p.log.info(`Jira credentials saved to drift.config.ts.\n Add your API token in the Drift overlay → Settings → Integrations.`)
|
|
404
|
+
}
|
|
405
|
+
|
|
332
406
|
// ── Step 6: Write drift.config.ts ────────────────────────────────────────────
|
|
407
|
+
// If fresh/bootstrap, merge chosenFoundation's package into dsPackages
|
|
408
|
+
const mergedDsPackages = chosenFoundation
|
|
409
|
+
? [chosenFoundation.pkg, ...(dsPackages ?? [])].filter(Boolean)
|
|
410
|
+
: dsPackages
|
|
411
|
+
|
|
333
412
|
const components = { ...sbComponents }
|
|
334
413
|
|
|
335
414
|
spinner.start('Writing drift.config.ts...')
|
|
@@ -337,10 +416,12 @@ export async function init(argv) {
|
|
|
337
416
|
storybookUrl: storybookUrl || (storybook.found ? storybook.url : undefined),
|
|
338
417
|
chromaticUrl,
|
|
339
418
|
figmaFiles: figmaFiles.length ? figmaFiles : undefined,
|
|
340
|
-
dsPackages,
|
|
419
|
+
dsPackages: mergedDsPackages,
|
|
341
420
|
threshold: Number(threshold) || 80,
|
|
342
421
|
components,
|
|
343
422
|
framework,
|
|
423
|
+
jiraBaseUrl,
|
|
424
|
+
jiraProjectKey,
|
|
344
425
|
})
|
|
345
426
|
if (figmaToken) writeEnvLocal(cwd, { figmaToken })
|
|
346
427
|
spinner.stop('drift.config.ts written')
|
|
@@ -412,7 +493,20 @@ ${pc.bold('What was set up:')}
|
|
|
412
493
|
// ── Immediate next step — specific to what was configured ───────────────────
|
|
413
494
|
console.log(`\n${pc.bold('Do this now:')}`)
|
|
414
495
|
|
|
415
|
-
if (
|
|
496
|
+
if (chosenFoundation) {
|
|
497
|
+
console.log(`
|
|
498
|
+
You chose ${pc.bold(chosenFoundation.label)} as your foundation.
|
|
499
|
+
|
|
500
|
+
${pc.cyan('1.')} Install it:
|
|
501
|
+
${pc.bold(chosenFoundation.install)}
|
|
502
|
+
${pc.cyan('2.')} Run: ${pc.bold('npx catchdrift sync')}
|
|
503
|
+
Scans your codebase for imports from ${pc.bold(chosenFoundation.pkg)} and registers them.
|
|
504
|
+
${pc.cyan('3.')} Run: ${pc.bold('npm run dev')} → press ${pc.bold('D')} → see live coverage
|
|
505
|
+
|
|
506
|
+
${pc.dim('Docs + live preview: ' + chosenFoundation.docs)}
|
|
507
|
+
${pc.dim('Figma community library: search "' + chosenFoundation.label + '" at figma.com/community')}
|
|
508
|
+
`)
|
|
509
|
+
} else if (figmaFiles.length > 0 && skillFiles.length > 0) {
|
|
416
510
|
console.log(`
|
|
417
511
|
Your components are in Figma. Pull them into the registry now:
|
|
418
512
|
|
|
@@ -428,10 +522,10 @@ ${pc.bold('What was set up:')}
|
|
|
428
522
|
${pc.cyan('1.')} Run: ${pc.bold('npm run dev')}
|
|
429
523
|
${pc.cyan('2.')} Press ${pc.bold('D')} to open the Drift overlay — you should see real coverage.
|
|
430
524
|
`)
|
|
431
|
-
} else if (
|
|
525
|
+
} else if (mergedDsPackages?.length) {
|
|
432
526
|
console.log(`
|
|
433
527
|
${pc.cyan('1.')} Run: ${pc.bold('npx catchdrift sync')}
|
|
434
|
-
Scans your codebase for imports from ${
|
|
528
|
+
Scans your codebase for imports from ${mergedDsPackages.join(', ')} and registers them.
|
|
435
529
|
${pc.cyan('2.')} Run: ${pc.bold('npm run dev')} → press ${pc.bold('D')} → see live coverage
|
|
436
530
|
`)
|
|
437
531
|
} else {
|
|
@@ -444,6 +538,11 @@ ${pc.bold('What was set up:')}
|
|
|
444
538
|
`)
|
|
445
539
|
}
|
|
446
540
|
|
|
541
|
+
if (jiraBaseUrl) {
|
|
542
|
+
console.log(` ${pc.dim('Jira:')} Connected to ${pc.bold(jiraBaseUrl)} (project: ${pc.bold(jiraProjectKey)})`)
|
|
543
|
+
console.log(` ${pc.dim(' → Add your API token in the overlay → Settings → Integrations')}\n`)
|
|
544
|
+
}
|
|
545
|
+
|
|
447
546
|
// ── Ongoing daily commands ───────────────────────────────────────────────────
|
|
448
547
|
console.log(`${pc.bold('Daily workflow (Claude Code):')}
|
|
449
548
|
${pc.blue('/drift-context')} Check DS health — run this before starting work
|
package/src/index.mjs
CHANGED
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, framework }) {
|
|
16
|
+
export function writeDriftConfig(cwd, { storybookUrl, chromaticUrl, figmaFiles, dsPackages, threshold, components, framework, jiraBaseUrl, jiraProjectKey }) {
|
|
17
17
|
const registry = buildComponentRegistry(components)
|
|
18
18
|
|
|
19
19
|
const dsPackagesLine = dsPackages?.length
|
|
@@ -57,6 +57,8 @@ export function writeDriftConfig(cwd, { storybookUrl, chromaticUrl, figmaFiles,
|
|
|
57
57
|
chromaticUrl ? ` chromaticUrl: '${chromaticUrl}',` : null,
|
|
58
58
|
figmaFilesBlock,
|
|
59
59
|
figmaTokenLine,
|
|
60
|
+
jiraBaseUrl ? ` jiraBaseUrl: '${jiraBaseUrl}',` : null,
|
|
61
|
+
jiraProjectKey ? ` jiraProjectKey: '${jiraProjectKey}',` : null,
|
|
60
62
|
` threshold: ${threshold},`,
|
|
61
63
|
dsPackagesLine,
|
|
62
64
|
` components: {`,
|