@catchdrift/cli 0.1.8 → 0.1.10
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 +58 -37
- package/src/lib/writers.mjs +27 -7
package/package.json
CHANGED
package/src/commands/init.mjs
CHANGED
|
@@ -130,23 +130,12 @@ export async function init(argv) {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
// ── Step 3a: Figma ───────────────────────────────────────────────────────────
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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),
|
|
141
|
-
})
|
|
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
|
-
}
|
|
133
|
+
// figmaFiles: [{ key: string, wipPages?: string[] }]
|
|
134
|
+
let figmaToken
|
|
135
|
+
const figmaFiles = []
|
|
148
136
|
|
|
149
|
-
|
|
137
|
+
if (sources.includes('figma')) {
|
|
138
|
+
// Token — ask once, reused for all files
|
|
150
139
|
console.log('')
|
|
151
140
|
p.log.step('Create a Figma access token — takes about 60 seconds:')
|
|
152
141
|
console.log(`
|
|
@@ -155,6 +144,7 @@ export async function init(argv) {
|
|
|
155
144
|
3. Give it a name (e.g. "Drift") and set an expiry
|
|
156
145
|
4. Enable these scopes:
|
|
157
146
|
${pc.green('✓')} File content → ${pc.bold('Read only')}
|
|
147
|
+
${pc.green('✓')} Library content → ${pc.bold('Read only')}
|
|
158
148
|
${pc.green('✓')} File comments → ${pc.bold('Write')}
|
|
159
149
|
${pc.green('✓')} File variables → ${pc.bold('Read only')}
|
|
160
150
|
5. Click ${pc.bold('Generate token')} and copy it — ${pc.yellow("you won't be able to see it again")}
|
|
@@ -169,24 +159,56 @@ export async function init(argv) {
|
|
|
169
159
|
if (p.isCancel(figmaToken)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
170
160
|
figmaToken = figmaToken?.trim() || undefined
|
|
171
161
|
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
:
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
162
|
+
// Loop: add one file at a time
|
|
163
|
+
let addingFiles = true
|
|
164
|
+
while (addingFiles) {
|
|
165
|
+
const fileLabel = figmaFiles.length === 0 ? 'Paste your Figma file URL (or just the file key)' : 'Add another Figma file URL (or key)'
|
|
166
|
+
const figmaInput = await p.text({
|
|
167
|
+
message: fileLabel,
|
|
168
|
+
placeholder: 'https://www.figma.com/design/ABC123.../My-Design-File',
|
|
169
|
+
hint: figmaFiles.length === 0
|
|
170
|
+
? 'Open your Figma file in a browser and copy the full URL from the address bar'
|
|
171
|
+
: 'Add files for each area where DS components live (e.g. Core DS, Icons, Patterns)',
|
|
172
|
+
validate: v => (!v?.trim() ? 'Required' : undefined),
|
|
173
|
+
})
|
|
174
|
+
if (p.isCancel(figmaInput)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
175
|
+
|
|
176
|
+
let fileKey = extractFigmaFileKey(figmaInput.trim())
|
|
177
|
+
if (!fileKey) {
|
|
178
|
+
p.log.warn(`Could not extract a file key — using as-is.`)
|
|
179
|
+
fileKey = figmaInput.trim()
|
|
189
180
|
}
|
|
181
|
+
|
|
182
|
+
// Fetch pages for this file
|
|
183
|
+
let wipPages
|
|
184
|
+
if (figmaToken) {
|
|
185
|
+
spinner.start('Connecting to Figma...')
|
|
186
|
+
const pages = await fetchFigmaPages(fileKey, figmaToken)
|
|
187
|
+
spinner.stop(pages
|
|
188
|
+
? pc.green(`Connected ✓ Found ${pages.length} pages`)
|
|
189
|
+
: pc.yellow('Could not reach Figma — skipping page selection for this file.')
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if (pages?.length) {
|
|
193
|
+
const selected = await p.multiselect({
|
|
194
|
+
message: 'Which pages in this file hold in-progress / not-yet-ready components?',
|
|
195
|
+
options: pages.map(name => ({ value: name, label: name })),
|
|
196
|
+
required: false,
|
|
197
|
+
})
|
|
198
|
+
if (p.isCancel(selected)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
199
|
+
wipPages = Array.isArray(selected) && selected.length ? selected : undefined
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
figmaFiles.push({ key: fileKey, wipPages })
|
|
204
|
+
|
|
205
|
+
// Ask whether to add another
|
|
206
|
+
const another = await p.confirm({
|
|
207
|
+
message: `${figmaFiles.length} file${figmaFiles.length > 1 ? 's' : ''} added. Add another Figma file?`,
|
|
208
|
+
initialValue: false,
|
|
209
|
+
})
|
|
210
|
+
if (p.isCancel(another)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
211
|
+
addingFiles = another
|
|
190
212
|
}
|
|
191
213
|
}
|
|
192
214
|
|
|
@@ -269,8 +291,7 @@ export async function init(argv) {
|
|
|
269
291
|
writeDriftConfig(cwd, {
|
|
270
292
|
storybookUrl: storybookUrl || (storybook.found ? storybook.url : undefined),
|
|
271
293
|
chromaticUrl,
|
|
272
|
-
|
|
273
|
-
figmaWIPPages,
|
|
294
|
+
figmaFiles: figmaFiles.length ? figmaFiles : undefined,
|
|
274
295
|
dsPackages,
|
|
275
296
|
threshold: Number(threshold) || 80,
|
|
276
297
|
components,
|
|
@@ -283,7 +304,7 @@ export async function init(argv) {
|
|
|
283
304
|
tools: Array.isArray(aiToolsSelected) ? aiToolsSelected : [],
|
|
284
305
|
components,
|
|
285
306
|
storybookUrl: storybookUrl || '',
|
|
286
|
-
|
|
307
|
+
figmaFiles: figmaFiles.length ? figmaFiles : undefined,
|
|
287
308
|
})
|
|
288
309
|
spinner.stop(`Written: ${rulesFiles.join(', ')}`)
|
|
289
310
|
|
|
@@ -355,7 +376,7 @@ ${pc.dim('Docs: https://catchdrift.ai · Issues: https://github.com/dyoon92/de
|
|
|
355
376
|
console.log('')
|
|
356
377
|
}
|
|
357
378
|
|
|
358
|
-
if (
|
|
379
|
+
if (figmaFiles.length > 0) {
|
|
359
380
|
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.`)
|
|
360
381
|
console.log('')
|
|
361
382
|
}
|
package/src/lib/writers.mjs
CHANGED
|
@@ -10,21 +10,38 @@ import { findAppEntry } from './detect.mjs'
|
|
|
10
10
|
|
|
11
11
|
// ── drift.config.ts ───────────────────────────────────────────────────────────
|
|
12
12
|
|
|
13
|
-
export function writeDriftConfig(cwd, { storybookUrl, chromaticUrl,
|
|
13
|
+
export function writeDriftConfig(cwd, { storybookUrl, chromaticUrl, figmaFiles, dsPackages, threshold, components }) {
|
|
14
14
|
const registry = buildComponentRegistry(components)
|
|
15
15
|
|
|
16
16
|
const dsPackagesLine = dsPackages?.length
|
|
17
17
|
? ` dsPackages: [${dsPackages.map(p => `'${p}'`).join(', ')}],`
|
|
18
18
|
: null
|
|
19
19
|
|
|
20
|
+
// Build figmaFiles block — single file gets a compact shape, multiple get an array
|
|
21
|
+
let figmaFilesBlock = null
|
|
22
|
+
if (figmaFiles?.length === 1) {
|
|
23
|
+
const f = figmaFiles[0]
|
|
24
|
+
figmaFilesBlock = ` figmaFileKey: '${f.key}',`
|
|
25
|
+
if (f.wipPages?.length) {
|
|
26
|
+
figmaFilesBlock += `\n figmaWIPPages: [${f.wipPages.map(p => `'${p}'`).join(', ')}], // components on these pages are drafts — not added to registry`
|
|
27
|
+
}
|
|
28
|
+
} else if (figmaFiles?.length > 1) {
|
|
29
|
+
const entries = figmaFiles.map(f => {
|
|
30
|
+
const wipLine = f.wipPages?.length
|
|
31
|
+
? `, wipPages: [${f.wipPages.map(p => `'${p}'`).join(', ')}]`
|
|
32
|
+
: ''
|
|
33
|
+
return ` { key: '${f.key}'${wipLine} },`
|
|
34
|
+
}).join('\n')
|
|
35
|
+
figmaFilesBlock = ` figmaFiles: [\n${entries}\n ],`
|
|
36
|
+
}
|
|
37
|
+
|
|
20
38
|
const lines = [
|
|
21
39
|
`import type { DesignDriftConfig } from '@catchdrift/overlay'`,
|
|
22
40
|
``,
|
|
23
41
|
`const config: DesignDriftConfig = {`,
|
|
24
42
|
storybookUrl ? ` storybookUrl: '${storybookUrl}',` : null,
|
|
25
43
|
chromaticUrl ? ` chromaticUrl: '${chromaticUrl}',` : null,
|
|
26
|
-
|
|
27
|
-
figmaWIPPages?.length ? ` figmaWIPPages: [${figmaWIPPages.map(p => `'${p}'`).join(', ')}], // components on these pages are drafts — not added to registry` : null,
|
|
44
|
+
figmaFilesBlock,
|
|
28
45
|
` threshold: ${threshold},`,
|
|
29
46
|
dsPackagesLine,
|
|
30
47
|
` components: {`,
|
|
@@ -61,12 +78,15 @@ function buildComponentTable(components, storybookUrl) {
|
|
|
61
78
|
].join('\n')
|
|
62
79
|
}
|
|
63
80
|
|
|
64
|
-
function buildAIRulesContent(components, storybookUrl,
|
|
81
|
+
function buildAIRulesContent(components, storybookUrl, figmaFiles) {
|
|
82
|
+
const figmaLines = figmaFiles?.length
|
|
83
|
+
? figmaFiles.map(f => `- Figma: https://figma.com/design/${f.key}`).join('\n') + '\n'
|
|
84
|
+
: ''
|
|
65
85
|
return `# Design System Rules
|
|
66
86
|
|
|
67
87
|
## Source of truth
|
|
68
88
|
- Storybook: ${storybookUrl}
|
|
69
|
-
${
|
|
89
|
+
${figmaLines}- Tokens: src/tokens/variables.css — use CSS variables only
|
|
70
90
|
|
|
71
91
|
## The #1 rule: never invent UI from scratch
|
|
72
92
|
Only use components from the table below. If a component you need is missing:
|
|
@@ -110,8 +130,8 @@ ${buildComponentTable(components, storybookUrl)}
|
|
|
110
130
|
`
|
|
111
131
|
}
|
|
112
132
|
|
|
113
|
-
export function writeAIRulesFiles(cwd, { tools, components, storybookUrl,
|
|
114
|
-
const content = buildAIRulesContent(components, storybookUrl,
|
|
133
|
+
export function writeAIRulesFiles(cwd, { tools, components, storybookUrl, figmaFiles }) {
|
|
134
|
+
const content = buildAIRulesContent(components, storybookUrl, figmaFiles)
|
|
115
135
|
const written = []
|
|
116
136
|
|
|
117
137
|
const toolFileMap = {
|