@catchdrift/cli 0.1.6 → 0.1.8

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.6",
3
+ "version": "0.1.8",
4
4
  "description": "CLI for Drift — install, check, and manage design system coverage for any React app.",
5
5
  "keywords": [
6
6
  "design-system",
@@ -102,30 +102,81 @@ 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) {
107
+ 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.'
111
+ )
112
+ const setupSB = await p.confirm({
113
+ message: 'Set up Storybook now? (runs npx storybook@latest init)',
114
+ initialValue: true,
115
+ })
116
+ if (p.isCancel(setupSB)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
117
+
118
+ if (setupSB) {
119
+ spinner.start('Running npx storybook@latest init...')
120
+ try {
121
+ execSync('npx storybook@latest init --yes', { cwd, stdio: 'ignore' })
122
+ spinner.stop('Storybook installed — it will be available at http://localhost:6006')
123
+ sources.push('storybook')
124
+ } catch {
125
+ spinner.stop('Storybook install failed — run `npx storybook@latest init` manually, then re-run `npx catchdrift init`')
126
+ }
127
+ } else {
128
+ p.log.info('Continuing without Storybook. Add it later and re-run `npx catchdrift init` to complete the loop.')
129
+ }
130
+ }
131
+
105
132
  // ── Step 3a: Figma ───────────────────────────────────────────────────────────
106
133
  let figmaFileKey, figmaToken, figmaWIPPages
107
134
  if (sources.includes('figma')) {
108
- figmaFileKey = await p.text({
109
- message: 'Figma file key',
110
- placeholder: 'Found in figma.com/design/THIS_KEY/... (paste just the key)',
111
- validate: v => (!v?.trim() ? 'Required — paste the key from your Figma URL' : undefined),
135
+ // Accept full URL or raw key
136
+ const figmaInput = await p.text({
137
+ message: 'Paste your Figma file URL (or just the file key)',
138
+ placeholder: 'https://www.figma.com/design/ABC123.../My-Design-File',
139
+ hint: 'Open your Figma file in a browser and copy the full URL from the address bar',
140
+ validate: v => (!v?.trim() ? 'Required' : undefined),
112
141
  })
113
- if (p.isCancel(figmaFileKey)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
114
- figmaFileKey = figmaFileKey?.trim() || undefined
142
+ if (p.isCancel(figmaInput)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
143
+ figmaFileKey = extractFigmaFileKey(figmaInput.trim())
144
+ if (!figmaFileKey) {
145
+ p.log.warn(`Could not extract a file key from "${figmaInput.trim()}". Using it as-is.`)
146
+ figmaFileKey = figmaInput.trim()
147
+ }
148
+
149
+ // Token — show exact steps + required scopes
150
+ console.log('')
151
+ p.log.step('Create a Figma access token — takes about 60 seconds:')
152
+ console.log(`
153
+ 1. Open ${pc.cyan('figma.com')} → click your avatar (top-left) → ${pc.bold('Settings')}
154
+ 2. Go to the ${pc.bold('Security')} tab → click ${pc.bold('Generate new token')}
155
+ 3. Give it a name (e.g. "Drift") and set an expiry
156
+ 4. Enable these scopes:
157
+ ${pc.green('✓')} File content → ${pc.bold('Read only')}
158
+ ${pc.green('✓')} File comments → ${pc.bold('Write')}
159
+ ${pc.green('✓')} File variables → ${pc.bold('Read only')}
160
+ 5. Click ${pc.bold('Generate token')} and copy it — ${pc.yellow("you won't be able to see it again")}
161
+ `)
115
162
 
116
163
  figmaToken = await p.text({
117
- message: 'Figma personal access token',
118
- placeholder: 'figd_... (figma.com → Profile → Settings → Security → Personal access tokens)',
119
- hint: 'Used to fetch your real page list. Store in FIGMA_API_TOKEN env var — not committed to git.',
164
+ message: 'Paste your Figma token here',
165
+ placeholder: 'figd_...',
166
+ hint: 'Stored in your local FIGMA_API_TOKEN env var — never committed to git',
167
+ validate: v => (!v?.trim() ? 'Required — paste the token you just generated' : undefined),
120
168
  })
121
169
  if (p.isCancel(figmaToken)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
122
170
  figmaToken = figmaToken?.trim() || undefined
123
171
 
124
- // Fetch actual pages from Figma so the user picks from real names
172
+ // Validate token + fetch pages in one call
125
173
  if (figmaToken && figmaFileKey) {
126
- spinner.start('Fetching pages from Figma...')
174
+ spinner.start('Connecting to Figma...')
127
175
  const pages = await fetchFigmaPages(figmaFileKey, figmaToken)
128
- spinner.stop(pages ? `Found ${pages.length} pages` : 'Could not reach Figma — skipping page selection')
176
+ spinner.stop(pages
177
+ ? pc.green(`Connected ✓ Found ${pages.length} pages`)
178
+ : pc.yellow('Could not reach Figma — check your token scopes. You can re-run `npx catchdrift init` to retry.')
179
+ )
129
180
 
130
181
  if (pages?.length) {
131
182
  const selected = await p.multiselect({
@@ -312,6 +363,12 @@ ${pc.dim('Docs: https://catchdrift.ai · Issues: https://github.com/dyoon92/de
312
363
 
313
364
  // ── Helpers ───────────────────────────────────────────────────────────────────
314
365
 
366
+ function extractFigmaFileKey(input) {
367
+ // Matches: figma.com/design/KEY/... or figma.com/file/KEY/...
368
+ const match = input.match(/figma\.com\/(?:design|file)\/([a-zA-Z0-9]+)/)
369
+ return match ? match[1] : null
370
+ }
371
+
315
372
  async function fetchFigmaPages(fileKey, token) {
316
373
  try {
317
374
  const res = await fetch(`https://api.figma.com/v1/files/${fileKey}?depth=1`, {