@catchdrift/cli 0.2.1 → 0.2.3
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 +138 -35
- package/src/index.mjs +13 -13
- package/src/lib/writers.mjs +2 -2
package/package.json
CHANGED
package/src/commands/init.mjs
CHANGED
|
@@ -121,12 +121,12 @@ export async function init(argv) {
|
|
|
121
121
|
// ── Step 2c: Bootstrap foundation picker ─────────────────────────────────────
|
|
122
122
|
// Fires when user has no existing DS
|
|
123
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' },
|
|
124
|
+
{ value: 'shadcn', label: 'shadcn/ui', hint: 'Radix + Tailwind — copy-paste components', pkg: '@radix-ui/react-dialog', install: 'npx shadcn@latest init', installAuto: null, docs: 'https://ui.shadcn.com', figmaCommunity: 'shadcn/ui' },
|
|
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', installAuto: '@mui/material @emotion/react @emotion/styled', docs: 'https://mui.com/material-ui/', figmaCommunity: 'Material UI MUI' },
|
|
126
|
+
{ value: 'antd', label: 'Ant Design', hint: 'Enterprise-grade — rich component set', pkg: 'antd', install: 'npm install antd', installAuto: 'antd', docs: 'https://ant.design/components/overview/', figmaCommunity: 'Ant Design' },
|
|
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', installAuto: '@chakra-ui/react @emotion/react @emotion/styled framer-motion', docs: 'https://chakra-ui.com/docs/components', figmaCommunity: 'Chakra UI' },
|
|
128
|
+
{ value: 'radix', label: 'Radix Themes', hint: 'Headless primitives + opinionated theme layer', pkg: '@radix-ui/themes', install: 'npm install @radix-ui/themes', installAuto: '@radix-ui/themes', docs: 'https://www.radix-ui.com/themes', figmaCommunity: 'Radix 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', installAuto: '@primer/react styled-components', docs: 'https://primer.style/components', figmaCommunity: 'GitHub Primer' },
|
|
130
130
|
]
|
|
131
131
|
|
|
132
132
|
let chosenFoundation = null
|
|
@@ -146,8 +146,59 @@ export async function init(argv) {
|
|
|
146
146
|
chosenFoundation = DS_FOUNDATIONS.find(f => f.value === foundationChoice)
|
|
147
147
|
|
|
148
148
|
p.log.success(`${chosenFoundation.label} selected — Drift will track it via dsPackages.`)
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
|
|
150
|
+
// ── Auto-install the foundation ─────────────────────────────────────────────
|
|
151
|
+
if (chosenFoundation.installAuto) {
|
|
152
|
+
// Non-interactive: npm install — run automatically
|
|
153
|
+
const doInstall = await p.confirm({
|
|
154
|
+
message: `Install ${chosenFoundation.label} now? (${chosenFoundation.install})`,
|
|
155
|
+
initialValue: true,
|
|
156
|
+
})
|
|
157
|
+
if (p.isCancel(doInstall)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
158
|
+
|
|
159
|
+
if (doInstall) {
|
|
160
|
+
const pkgManager =
|
|
161
|
+
existsSync(join(cwd, 'pnpm-lock.yaml')) ? 'pnpm add' :
|
|
162
|
+
existsSync(join(cwd, 'yarn.lock')) ? 'yarn add' : 'npm install'
|
|
163
|
+
const installCmd = `${pkgManager} ${chosenFoundation.installAuto}`
|
|
164
|
+
spinner.start(`Installing ${chosenFoundation.label}...`)
|
|
165
|
+
try {
|
|
166
|
+
execSync(installCmd, { cwd, stdio: 'pipe' })
|
|
167
|
+
spinner.stop(`${chosenFoundation.label} installed ✓`)
|
|
168
|
+
} catch (err) {
|
|
169
|
+
const stderr = err.stderr?.toString() || ''
|
|
170
|
+
if (stderr.includes('EACCES') || stderr.includes('root-owned')) {
|
|
171
|
+
spinner.stop(pc.yellow(`Permission error — run: sudo chown -R $(id -u):$(id -g) ~/.npm then re-run catchdrift init`))
|
|
172
|
+
} else {
|
|
173
|
+
spinner.stop(pc.yellow(`Install failed — run manually: ${chosenFoundation.install}`))
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
// shadcn is interactive — user must run it themselves
|
|
179
|
+
p.log.info(`shadcn/ui requires its own interactive setup:\n ${pc.bold(chosenFoundation.install)}\n Run this after catchdrift finishes, then run ${pc.bold('npx @catchdrift/cli sync')}`)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── Figma for fresh users ────────────────────────────────────────────────────
|
|
183
|
+
console.log('')
|
|
184
|
+
p.log.info(
|
|
185
|
+
`Want to connect a Figma file? This lets Drift link every component back to its Figma source.\n` +
|
|
186
|
+
pc.dim(` ${chosenFoundation.label} has an official Figma community library — great starting point.\n`) +
|
|
187
|
+
pc.dim(` 1. Go to figma.com/community and search "${chosenFoundation.figmaCommunity}"\n`) +
|
|
188
|
+
pc.dim(` 2. Click "Open in Figma" to duplicate it to your workspace\n`) +
|
|
189
|
+
pc.dim(` 3. Paste the file URL below`)
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
const wantFigmaFresh = await p.confirm({
|
|
193
|
+
message: 'Connect a Figma file now?',
|
|
194
|
+
initialValue: false,
|
|
195
|
+
})
|
|
196
|
+
if (p.isCancel(wantFigmaFresh)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
197
|
+
if (wantFigmaFresh) {
|
|
198
|
+
sources.push('figma')
|
|
199
|
+
} else {
|
|
200
|
+
console.log(pc.dim(' Skipping Figma. Re-run npx @catchdrift/cli init later to add it.'))
|
|
201
|
+
}
|
|
151
202
|
}
|
|
152
203
|
|
|
153
204
|
// ── Storybook nudge — fire whenever Storybook isn't in the setup ─────────────
|
|
@@ -165,17 +216,37 @@ export async function init(argv) {
|
|
|
165
216
|
if (p.isCancel(setupSB)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
166
217
|
|
|
167
218
|
if (setupSB) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
219
|
+
// Detect root-owned npm cache before spawning Storybook
|
|
220
|
+
const cacheOk = checkNpmCache()
|
|
221
|
+
if (!cacheOk) {
|
|
222
|
+
p.log.warn(
|
|
223
|
+
'npm cache is owned by root — Storybook install will fail.\n' +
|
|
224
|
+
pc.bold(' Fix it first by running:\n') +
|
|
225
|
+
` sudo chown -R $(id -u):$(id -g) ~/.npm\n` +
|
|
226
|
+
` Then re-run: ${pc.bold('npx @catchdrift/cli init')}`
|
|
227
|
+
)
|
|
228
|
+
} else {
|
|
229
|
+
console.log(pc.dim('\n Running npx storybook@latest init — follow the prompts:\n'))
|
|
230
|
+
try {
|
|
231
|
+
// Use inherit so the user can interact with Storybook's framework prompts
|
|
232
|
+
execSync('npx storybook@latest init', { cwd, stdio: 'inherit' })
|
|
233
|
+
p.log.success('Storybook installed — run `npm run storybook` to start it on :6006')
|
|
234
|
+
sources.push('storybook')
|
|
235
|
+
} catch (err) {
|
|
236
|
+
const stderr = (err.stderr?.toString() || '') + (err.stdout?.toString() || '')
|
|
237
|
+
if (stderr.includes('EACCES') || stderr.includes('root-owned')) {
|
|
238
|
+
p.log.warn(
|
|
239
|
+
'npm cache permission error.\n' +
|
|
240
|
+
` Fix: sudo chown -R $(id -u):$(id -g) ~/.npm\n` +
|
|
241
|
+
` Then re-run: npx @catchdrift/cli init`
|
|
242
|
+
)
|
|
243
|
+
} else {
|
|
244
|
+
p.log.warn('Storybook install failed or was cancelled. Run `npx storybook@latest init` manually when ready.')
|
|
245
|
+
}
|
|
246
|
+
}
|
|
176
247
|
}
|
|
177
248
|
} else {
|
|
178
|
-
p.log.info('Skipping Storybook. Run `npx storybook@latest init` when ready, then re-run `npx catchdrift init`.')
|
|
249
|
+
p.log.info('Skipping Storybook. Run `npx storybook@latest init` when ready, then re-run `npx @catchdrift/cli init`.')
|
|
179
250
|
}
|
|
180
251
|
}
|
|
181
252
|
|
|
@@ -188,19 +259,38 @@ export async function init(argv) {
|
|
|
188
259
|
if (p.isCancel(installNow)) { p.cancel('Setup cancelled.'); process.exit(EXIT_CANCELED) }
|
|
189
260
|
|
|
190
261
|
if (installNow) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
262
|
+
const cacheOk = checkNpmCache()
|
|
263
|
+
if (!cacheOk) {
|
|
264
|
+
p.log.warn(
|
|
265
|
+
'npm cache is owned by root — Storybook install will fail.\n' +
|
|
266
|
+
pc.bold(' Fix it first:\n') +
|
|
267
|
+
` sudo chown -R $(id -u):$(id -g) ~/.npm\n` +
|
|
268
|
+
` Then re-run: ${pc.bold('npx @catchdrift/cli init')}`
|
|
269
|
+
)
|
|
198
270
|
sources.splice(sources.indexOf('storybook'), 1)
|
|
271
|
+
} else {
|
|
272
|
+
console.log(pc.dim('\n Running npx storybook@latest init — follow the prompts:\n'))
|
|
273
|
+
try {
|
|
274
|
+
execSync('npx storybook@latest init', { cwd, stdio: 'inherit' })
|
|
275
|
+
p.log.success('Storybook installed — run `npm run storybook` to start it on :6006')
|
|
276
|
+
} catch (err) {
|
|
277
|
+
const stderr = (err.stderr?.toString() || '') + (err.stdout?.toString() || '')
|
|
278
|
+
if (stderr.includes('EACCES') || stderr.includes('root-owned')) {
|
|
279
|
+
p.log.warn(
|
|
280
|
+
'npm cache permission error.\n' +
|
|
281
|
+
` Fix: sudo chown -R $(id -u):$(id -g) ~/.npm\n` +
|
|
282
|
+
` Then re-run: npx @catchdrift/cli init`
|
|
283
|
+
)
|
|
284
|
+
} else {
|
|
285
|
+
p.log.warn('Storybook install failed or was cancelled. Run `npx storybook@latest init` manually when ready.')
|
|
286
|
+
}
|
|
287
|
+
sources.splice(sources.indexOf('storybook'), 1)
|
|
288
|
+
}
|
|
199
289
|
}
|
|
200
290
|
} else {
|
|
201
291
|
// User declined — remove from sources so the URL step is skipped
|
|
202
292
|
sources.splice(sources.indexOf('storybook'), 1)
|
|
203
|
-
p.log.info('Skipping Storybook setup. Re-run `npx catchdrift init` after installing it.')
|
|
293
|
+
p.log.info('Skipping Storybook setup. Re-run `npx @catchdrift/cli init` after installing it.')
|
|
204
294
|
}
|
|
205
295
|
}
|
|
206
296
|
|
|
@@ -494,17 +584,18 @@ ${pc.bold('What was set up:')}
|
|
|
494
584
|
console.log(`\n${pc.bold('Do this now:')}`)
|
|
495
585
|
|
|
496
586
|
if (chosenFoundation) {
|
|
587
|
+
const alreadyInstalled = chosenFoundation.installAuto !== null
|
|
497
588
|
console.log(`
|
|
498
589
|
You chose ${pc.bold(chosenFoundation.label)} as your foundation.
|
|
499
|
-
|
|
590
|
+
${alreadyInstalled ? '' : `
|
|
500
591
|
${pc.cyan('1.')} Install it:
|
|
501
|
-
${pc.bold(chosenFoundation.install)}
|
|
502
|
-
${pc.cyan('2.')} Run: ${pc.bold('npx catchdrift sync')}
|
|
592
|
+
${pc.bold(chosenFoundation.install)}`}
|
|
593
|
+
${alreadyInstalled ? pc.cyan('1.') : pc.cyan('2.')} Run: ${pc.bold('npx @catchdrift/cli sync')}
|
|
503
594
|
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
|
|
595
|
+
${alreadyInstalled ? pc.cyan('2.') : pc.cyan('3.')} Run: ${pc.bold('npm run dev')} → press ${pc.bold('D')} → see live coverage
|
|
505
596
|
|
|
506
597
|
${pc.dim('Docs + live preview: ' + chosenFoundation.docs)}
|
|
507
|
-
${pc.dim('Figma community library: search "' + chosenFoundation.
|
|
598
|
+
${pc.dim('Figma community library: search "' + chosenFoundation.figmaCommunity + '" at figma.com/community')}
|
|
508
599
|
`)
|
|
509
600
|
} else if (figmaFiles.length > 0 && skillFiles.length > 0) {
|
|
510
601
|
console.log(`
|
|
@@ -524,7 +615,7 @@ ${pc.bold('What was set up:')}
|
|
|
524
615
|
`)
|
|
525
616
|
} else if (mergedDsPackages?.length) {
|
|
526
617
|
console.log(`
|
|
527
|
-
${pc.cyan('1.')} Run: ${pc.bold('npx catchdrift sync')}
|
|
618
|
+
${pc.cyan('1.')} Run: ${pc.bold('npx @catchdrift/cli sync')}
|
|
528
619
|
Scans your codebase for imports from ${mergedDsPackages.join(', ')} and registers them.
|
|
529
620
|
${pc.cyan('2.')} Run: ${pc.bold('npm run dev')} → press ${pc.bold('D')} → see live coverage
|
|
530
621
|
`)
|
|
@@ -533,8 +624,8 @@ ${pc.bold('What was set up:')}
|
|
|
533
624
|
${pc.cyan('1.')} Run: ${pc.bold('npm run dev')} → press ${pc.bold('D')} → the overlay opens
|
|
534
625
|
Coverage will show 0% until you register your DS components.
|
|
535
626
|
${pc.cyan('2.')} Add your components to ${pc.bold('drift.config.ts')} or connect a source:
|
|
536
|
-
• Figma: re-run ${pc.bold('npx catchdrift init')} and select Figma
|
|
537
|
-
• npm: add ${pc.bold('dsPackages')} to drift.config.ts, then run ${pc.bold('npx catchdrift sync')}
|
|
627
|
+
• Figma: re-run ${pc.bold('npx @catchdrift/cli init')} and select Figma
|
|
628
|
+
• npm: add ${pc.bold('dsPackages')} to drift.config.ts, then run ${pc.bold('npx @catchdrift/cli sync')}
|
|
538
629
|
`)
|
|
539
630
|
}
|
|
540
631
|
|
|
@@ -548,12 +639,12 @@ ${pc.bold('What was set up:')}
|
|
|
548
639
|
${pc.blue('/drift-context')} Check DS health — run this before starting work
|
|
549
640
|
${pc.blue('/drift-scaffold')} Build a new screen using only DS components
|
|
550
641
|
${pc.blue('/drift-sync')} Update registry after Figma or Storybook changes
|
|
551
|
-
${pc.blue('npx catchdrift check')} Check coverage before submitting a PR
|
|
642
|
+
${pc.blue('npx @catchdrift/cli check')} Check coverage before submitting a PR
|
|
552
643
|
`)
|
|
553
644
|
|
|
554
645
|
if (!sources.includes('storybook') && !storybook.found) {
|
|
555
646
|
console.log(`${pc.yellow('Note:')} Storybook isn't set up yet. Run ${pc.bold('npx storybook@latest init')} when ready,`)
|
|
556
|
-
console.log(` then re-run ${pc.bold('npx catchdrift init')} to complete the coverage loop.\n`)
|
|
647
|
+
console.log(` then re-run ${pc.bold('npx @catchdrift/cli init')} to complete the coverage loop.\n`)
|
|
557
648
|
}
|
|
558
649
|
|
|
559
650
|
if (!patchedShort) {
|
|
@@ -569,6 +660,18 @@ ${pc.bold('What was set up:')}
|
|
|
569
660
|
|
|
570
661
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
571
662
|
|
|
663
|
+
/** Returns false if the npm cache is root-owned (common after sudo npm install -g) */
|
|
664
|
+
function checkNpmCache() {
|
|
665
|
+
try {
|
|
666
|
+
execSync('npm cache verify', { stdio: 'pipe', timeout: 8000 })
|
|
667
|
+
return true
|
|
668
|
+
} catch (err) {
|
|
669
|
+
const msg = (err.stderr?.toString() || '') + (err.stdout?.toString() || '')
|
|
670
|
+
if (msg.includes('EACCES') || msg.includes('root-owned')) return false
|
|
671
|
+
return true // other errors are unrelated to ownership
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
572
675
|
function extractFigmaFileKey(input) {
|
|
573
676
|
// Matches: figma.com/design/KEY/... or figma.com/file/KEY/...
|
|
574
677
|
const match = input.match(/figma\.com\/(?:design|file)\/([a-zA-Z0-9]+)/)
|
package/src/index.mjs
CHANGED
|
@@ -5,30 +5,30 @@
|
|
|
5
5
|
|
|
6
6
|
import pc from 'picocolors'
|
|
7
7
|
|
|
8
|
-
const VERSION = '0.2.
|
|
8
|
+
const VERSION = '0.2.3'
|
|
9
9
|
|
|
10
10
|
const HELP = `
|
|
11
11
|
${pc.bold(pc.blue('catchdrift'))} — Design system compliance for teams shipping with AI
|
|
12
12
|
|
|
13
13
|
${pc.bold('Usage:')}
|
|
14
|
-
npx catchdrift init Install Drift into your React project
|
|
15
|
-
npx catchdrift sync Auto-discover DS components from dsPackages
|
|
16
|
-
npx catchdrift check Run headless drift scan (requires running app)
|
|
17
|
-
npx catchdrift status Show DS coverage snapshot from config
|
|
18
|
-
npx catchdrift spec List all .drift-spec.md files
|
|
19
|
-
npx catchdrift spec validate Validate specs against implementation
|
|
20
|
-
npx catchdrift spec show <file> Show parsed spec details
|
|
14
|
+
npx @catchdrift/cli init Install Drift into your React project
|
|
15
|
+
npx @catchdrift/cli sync Auto-discover DS components from dsPackages
|
|
16
|
+
npx @catchdrift/cli check Run headless drift scan (requires running app)
|
|
17
|
+
npx @catchdrift/cli status Show DS coverage snapshot from config
|
|
18
|
+
npx @catchdrift/cli spec List all .drift-spec.md files
|
|
19
|
+
npx @catchdrift/cli spec validate Validate specs against implementation
|
|
20
|
+
npx @catchdrift/cli spec show <file> Show parsed spec details
|
|
21
21
|
|
|
22
22
|
${pc.bold('Options:')}
|
|
23
23
|
--help, -h Show this help
|
|
24
24
|
--version, -v Show version
|
|
25
25
|
|
|
26
26
|
${pc.bold('Examples:')}
|
|
27
|
-
npx catchdrift init
|
|
28
|
-
npx catchdrift sync
|
|
29
|
-
npx catchdrift check --url http://localhost:5173 --threshold 80
|
|
30
|
-
npx catchdrift status
|
|
31
|
-
npx catchdrift spec validate
|
|
27
|
+
npx @catchdrift/cli init
|
|
28
|
+
npx @catchdrift/cli sync
|
|
29
|
+
npx @catchdrift/cli check --url http://localhost:5173 --threshold 80
|
|
30
|
+
npx @catchdrift/cli status
|
|
31
|
+
npx @catchdrift/cli spec validate
|
|
32
32
|
|
|
33
33
|
${pc.dim('Docs: https://catchdrift.ai · GitHub: https://github.com/dyoon92/design-drift')}
|
|
34
34
|
`
|
package/src/lib/writers.mjs
CHANGED
|
@@ -62,7 +62,7 @@ export function writeDriftConfig(cwd, { storybookUrl, chromaticUrl, figmaFiles,
|
|
|
62
62
|
` threshold: ${threshold},`,
|
|
63
63
|
dsPackagesLine,
|
|
64
64
|
` components: {`,
|
|
65
|
-
registry || ` // Auto-populated by \`npx catchdrift sync\` — or add manually:`,
|
|
65
|
+
registry || ` // Auto-populated by \`npx @catchdrift/cli sync\` — or add manually:`,
|
|
66
66
|
` // Button: { storyPath: 'primitives-button--primary' },`,
|
|
67
67
|
` },`,
|
|
68
68
|
` // approvedGaps: {},`,
|
|
@@ -85,7 +85,7 @@ export function writeEnvLocal(cwd, { figmaToken }) {
|
|
|
85
85
|
if (existing.includes('FIGMA_TOKEN=')) return
|
|
86
86
|
writeFileSync(envPath, existing.trimEnd() + '\n' + line, 'utf8')
|
|
87
87
|
} else {
|
|
88
|
-
writeFileSync(envPath, `# Drift — Figma token (auto-generated by npx catchdrift init)\n${line}`, 'utf8')
|
|
88
|
+
writeFileSync(envPath, `# Drift — Figma token (auto-generated by npx @catchdrift/cli init)\n${line}`, 'utf8')
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|