@catchdrift/cli 0.1.8 → 0.1.9
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 +57 -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(`
|
|
@@ -169,24 +158,56 @@ export async function init(argv) {
|
|
|
169
158
|
if (p.isCancel(figmaToken)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
170
159
|
figmaToken = figmaToken?.trim() || undefined
|
|
171
160
|
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
:
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
161
|
+
// Loop: add one file at a time
|
|
162
|
+
let addingFiles = true
|
|
163
|
+
while (addingFiles) {
|
|
164
|
+
const fileLabel = figmaFiles.length === 0 ? 'Paste your Figma file URL (or just the file key)' : 'Add another Figma file URL (or key)'
|
|
165
|
+
const figmaInput = await p.text({
|
|
166
|
+
message: fileLabel,
|
|
167
|
+
placeholder: 'https://www.figma.com/design/ABC123.../My-Design-File',
|
|
168
|
+
hint: figmaFiles.length === 0
|
|
169
|
+
? 'Open your Figma file in a browser and copy the full URL from the address bar'
|
|
170
|
+
: 'Add files for each area where DS components live (e.g. Core DS, Icons, Patterns)',
|
|
171
|
+
validate: v => (!v?.trim() ? 'Required' : undefined),
|
|
172
|
+
})
|
|
173
|
+
if (p.isCancel(figmaInput)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
174
|
+
|
|
175
|
+
let fileKey = extractFigmaFileKey(figmaInput.trim())
|
|
176
|
+
if (!fileKey) {
|
|
177
|
+
p.log.warn(`Could not extract a file key — using as-is.`)
|
|
178
|
+
fileKey = figmaInput.trim()
|
|
189
179
|
}
|
|
180
|
+
|
|
181
|
+
// Fetch pages for this file
|
|
182
|
+
let wipPages
|
|
183
|
+
if (figmaToken) {
|
|
184
|
+
spinner.start('Connecting to Figma...')
|
|
185
|
+
const pages = await fetchFigmaPages(fileKey, figmaToken)
|
|
186
|
+
spinner.stop(pages
|
|
187
|
+
? pc.green(`Connected ✓ Found ${pages.length} pages`)
|
|
188
|
+
: pc.yellow('Could not reach Figma — skipping page selection for this file.')
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
if (pages?.length) {
|
|
192
|
+
const selected = await p.multiselect({
|
|
193
|
+
message: 'Which pages in this file hold in-progress / not-yet-ready components?',
|
|
194
|
+
options: pages.map(name => ({ value: name, label: name })),
|
|
195
|
+
required: false,
|
|
196
|
+
})
|
|
197
|
+
if (p.isCancel(selected)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
198
|
+
wipPages = Array.isArray(selected) && selected.length ? selected : undefined
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
figmaFiles.push({ key: fileKey, wipPages })
|
|
203
|
+
|
|
204
|
+
// Ask whether to add another
|
|
205
|
+
const another = await p.confirm({
|
|
206
|
+
message: `${figmaFiles.length} file${figmaFiles.length > 1 ? 's' : ''} added. Add another Figma file?`,
|
|
207
|
+
initialValue: false,
|
|
208
|
+
})
|
|
209
|
+
if (p.isCancel(another)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
210
|
+
addingFiles = another
|
|
190
211
|
}
|
|
191
212
|
}
|
|
192
213
|
|
|
@@ -269,8 +290,7 @@ export async function init(argv) {
|
|
|
269
290
|
writeDriftConfig(cwd, {
|
|
270
291
|
storybookUrl: storybookUrl || (storybook.found ? storybook.url : undefined),
|
|
271
292
|
chromaticUrl,
|
|
272
|
-
|
|
273
|
-
figmaWIPPages,
|
|
293
|
+
figmaFiles: figmaFiles.length ? figmaFiles : undefined,
|
|
274
294
|
dsPackages,
|
|
275
295
|
threshold: Number(threshold) || 80,
|
|
276
296
|
components,
|
|
@@ -283,7 +303,7 @@ export async function init(argv) {
|
|
|
283
303
|
tools: Array.isArray(aiToolsSelected) ? aiToolsSelected : [],
|
|
284
304
|
components,
|
|
285
305
|
storybookUrl: storybookUrl || '',
|
|
286
|
-
|
|
306
|
+
figmaFiles: figmaFiles.length ? figmaFiles : undefined,
|
|
287
307
|
})
|
|
288
308
|
spinner.stop(`Written: ${rulesFiles.join(', ')}`)
|
|
289
309
|
|
|
@@ -355,7 +375,7 @@ ${pc.dim('Docs: https://catchdrift.ai · Issues: https://github.com/dyoon92/de
|
|
|
355
375
|
console.log('')
|
|
356
376
|
}
|
|
357
377
|
|
|
358
|
-
if (
|
|
378
|
+
if (figmaFiles.length > 0) {
|
|
359
379
|
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
380
|
console.log('')
|
|
361
381
|
}
|
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 = {
|