create-catalyst-app 1.0.2-canary.1 → 1.0.2-canary.2
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 +5 -1
- package/scripts/cli.cjs +251 -124
- package/scripts/errors.cjs +2 -2
- package/templates/none-js/package.json +2 -1
- package/templates/none-ts/package.json +2 -1
- package/templates/redux-js/package.json +2 -1
- package/templates/redux-ts/package.json +2 -1
- package/templates/rtk-js/package.json +2 -1
- package/templates/rtk-ts/package.json +2 -1
- package/test/cli-require-esm.test.ts +38 -0
- /package/templates/tailwind/{postcss.config.js → postcss.config.cjs} +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-catalyst-app",
|
|
3
3
|
"bin": "scripts/cli.cjs",
|
|
4
|
-
"version": "1.0.2-canary.
|
|
4
|
+
"version": "1.0.2-canary.2",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=20.12.0"
|
|
7
|
+
},
|
|
5
8
|
"description": "cli package to scaffold Catalyst application",
|
|
6
9
|
"repository": {
|
|
7
10
|
"type": "git",
|
|
@@ -15,6 +18,7 @@
|
|
|
15
18
|
"test:types": "tsc --noEmit"
|
|
16
19
|
},
|
|
17
20
|
"dependencies": {
|
|
21
|
+
"@clack/prompts": "^1.7.0",
|
|
18
22
|
"ansis": "^4.3.1",
|
|
19
23
|
"commander": "^8.2.0",
|
|
20
24
|
"jscodeshift": "^0.16.0",
|
package/scripts/cli.cjs
CHANGED
|
@@ -2,14 +2,25 @@
|
|
|
2
2
|
const { execFileSync, execSync } = require("child_process")
|
|
3
3
|
const Commander = require("commander")
|
|
4
4
|
const { Option } = require("commander")
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
// @clack/prompts is ESM-only. require() of ESM is only on by default from
|
|
6
|
+
// Node 20.19 / 22.12 (engines allows >=20.12), so it is imported lazily in the
|
|
7
|
+
// scaffold action -- the only path that uses it.
|
|
8
|
+
/** @type {typeof import("@clack/prompts")} */
|
|
9
|
+
let clack
|
|
10
|
+
const { red, green, cyan, gray, bold } = require("ansis")
|
|
7
11
|
const tar = require("tar")
|
|
8
12
|
const path = require("path")
|
|
9
13
|
const fs = require("fs")
|
|
10
14
|
var validate = require("validate-npm-package-name")
|
|
11
15
|
const packageJson = require("../package.json")
|
|
12
|
-
const {
|
|
16
|
+
const {
|
|
17
|
+
CCAError,
|
|
18
|
+
createError,
|
|
19
|
+
wrapForeignError,
|
|
20
|
+
formatError,
|
|
21
|
+
resolveOutputMode,
|
|
22
|
+
getDebugEnvInfo,
|
|
23
|
+
} = require("./errors.cjs")
|
|
13
24
|
const outputMode = resolveOutputMode(process.argv)
|
|
14
25
|
// Colored (red) prefix only applies in default mode — verbose/debug boxes
|
|
15
26
|
// own their coloring internally (see errors.cjs#box).
|
|
@@ -18,6 +29,15 @@ const printError = (err) => {
|
|
|
18
29
|
const formatted = formatError(err, outputMode, debugEnv)
|
|
19
30
|
console.error(outputMode === "default" ? red(formatted) : formatted)
|
|
20
31
|
}
|
|
32
|
+
// Inside a clack session: close it with a one-line reason tagged with the
|
|
33
|
+
// error's code and its docs link, on stderr like every other error.
|
|
34
|
+
// --verbose/--debug follow it with the registry's full report, which carries
|
|
35
|
+
// the link itself.
|
|
36
|
+
const cancelWithError = (err, message = err.message) => {
|
|
37
|
+
const docs = outputMode === "default" && err.docUrl ? `\n ${gray(`Docs: ${err.docUrl}`)}` : ""
|
|
38
|
+
clack.cancel(`${message} ${gray(`[${err.code}]`)}${docs}`, { output: process.stderr })
|
|
39
|
+
if (outputMode !== "default") printError(err)
|
|
40
|
+
}
|
|
21
41
|
const packageRoot = path.join(__dirname, "..")
|
|
22
42
|
const executable = (command) => (process.platform === "win32" ? `${command}.cmd` : command)
|
|
23
43
|
|
|
@@ -54,15 +74,20 @@ const program = new Commander.Command()
|
|
|
54
74
|
return
|
|
55
75
|
}
|
|
56
76
|
|
|
57
|
-
|
|
77
|
+
clack = await import("@clack/prompts")
|
|
78
|
+
|
|
79
|
+
// Same identity as every other Catalyst command: accent-coloured
|
|
80
|
+
// name, dim version.
|
|
81
|
+
clack.intro(`${cyan(bold("create-catalyst-app"))} ${gray(packageJson.version)}`)
|
|
58
82
|
|
|
59
83
|
// Use options provided through commander or prompt the user
|
|
60
84
|
validateOptions(cmd)
|
|
61
85
|
|
|
62
86
|
if (cmd.yes) {
|
|
63
|
-
|
|
87
|
+
clack.log.info("Using default configuration")
|
|
64
88
|
config = {
|
|
65
|
-
|
|
89
|
+
// A name given on the command line still wins over the default.
|
|
90
|
+
folderName: folderName || "my-app",
|
|
66
91
|
language: "js",
|
|
67
92
|
tailWindSupport: false,
|
|
68
93
|
description: "Default catalyst app",
|
|
@@ -72,30 +97,36 @@ const program = new Commander.Command()
|
|
|
72
97
|
}
|
|
73
98
|
|
|
74
99
|
const projectName = config.folderName || (await promptProjectName())
|
|
75
|
-
|
|
100
|
+
|
|
101
|
+
// Still needed for --yes and for a name passed as an argument:
|
|
102
|
+
// those paths never reach the prompt's own validation.
|
|
103
|
+
const isNameValid = validate(projectName)
|
|
76
104
|
if (!isNameValid.validForNewPackages) {
|
|
77
|
-
const reasons = [...(isNameValid
|
|
78
|
-
|
|
79
|
-
createError("CCA-001", {
|
|
80
|
-
|
|
81
|
-
})
|
|
105
|
+
const reasons = [...(isNameValid.errors || []), ...(isNameValid.warnings || [])]
|
|
106
|
+
cancelWithError(
|
|
107
|
+
createError("CCA-001", { details: reasons.length ? reasons.join("; ") : undefined }),
|
|
108
|
+
`"${projectName}" is not a valid package name: ${reasons.join(", ")}`
|
|
82
109
|
)
|
|
83
110
|
process.exit(1)
|
|
84
111
|
}
|
|
85
112
|
let projectPath = path.join(process.cwd(), projectName)
|
|
86
113
|
if (fs.existsSync(projectPath)) {
|
|
87
|
-
|
|
114
|
+
cancelWithError(
|
|
88
115
|
createError("CCA-002", {
|
|
89
|
-
message: `${projectName} already exists
|
|
116
|
+
message: `${projectName} already exists — choose another name or remove it.`,
|
|
90
117
|
})
|
|
91
118
|
)
|
|
92
119
|
process.exit(1)
|
|
93
120
|
}
|
|
94
121
|
const projectDescription = config.description || (await promptDescription())
|
|
95
122
|
const language = config.language || (await promptTypescript())
|
|
96
|
-
|
|
123
|
+
// These are booleans, so `!== null` collapsed a configured `false`
|
|
124
|
+
// into `true` -- `--yes` installed Tailwind against its own default.
|
|
125
|
+
// Only fall through to the prompt when nothing was configured.
|
|
126
|
+
const tailWindSupport =
|
|
127
|
+
config.tailWindSupport != null ? config.tailWindSupport : await promptTailwind()
|
|
97
128
|
const stateManagement = config.stateManagement || (await promptStateManagement())
|
|
98
|
-
const mcpSupport = config.mcpSupport
|
|
129
|
+
const mcpSupport = config.mcpSupport != null ? config.mcpSupport : await promptMcp()
|
|
99
130
|
|
|
100
131
|
// Define mapping of options to repository suffixes
|
|
101
132
|
const repositorySuffixes = {
|
|
@@ -126,39 +157,30 @@ const program = new Commander.Command()
|
|
|
126
157
|
extractSubdirectory(packageFilePath)
|
|
127
158
|
createGitignore(projectName)
|
|
128
159
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
)
|
|
160
|
+
// npm's install output is hundreds of lines nobody reads
|
|
161
|
+
// when it works. Fold it behind a spinner, and print it in
|
|
162
|
+
// full only if the step actually fails.
|
|
163
|
+
runStep(`Installing dependencies`, () => {
|
|
164
|
+
runQuiet(
|
|
165
|
+
`cd ${projectName} && npm i && npm pkg set name=${projectName} ${projectDescription ? `description="${projectDescription}"` : ""} && git init --quiet`
|
|
166
|
+
)
|
|
167
|
+
})
|
|
133
168
|
|
|
134
169
|
if (tailWindSupport) {
|
|
135
|
-
|
|
136
|
-
|
|
170
|
+
runStep("Adding Tailwind CSS", () => {
|
|
171
|
+
runQuiet(
|
|
172
|
+
`cd ${projectName} && npm i tailwindcss@4.1.4 @tailwindcss/postcss@4.1.4`
|
|
173
|
+
)
|
|
137
174
|
})
|
|
138
175
|
}
|
|
139
176
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
console.log(`\n${green(bold("Success!"))} created ${projectName} at ${projectPath}`)
|
|
148
|
-
console.log("Inside this directory, you can run the following commands.")
|
|
149
|
-
|
|
150
|
-
console.log(cyan(bold(" \n npm run start")))
|
|
151
|
-
console.log(" Starts the development server ")
|
|
152
|
-
|
|
153
|
-
console.log(cyan(bold("\n npm run build")))
|
|
154
|
-
console.log(" Bundles the app for production ")
|
|
155
|
-
|
|
156
|
-
console.log(cyan(bold("\n npm run serve")))
|
|
157
|
-
console.log(" Serves the production build ")
|
|
158
|
-
|
|
159
|
-
console.log("\nWe suggest you to begin, by running")
|
|
160
|
-
console.log(` ${cyan("cd")} ${projectName} && ${cyan("npm start")} \n\n`)
|
|
177
|
+
runStep("Creating initial commit", () => {
|
|
178
|
+
runQuiet(
|
|
179
|
+
`cd ${projectName} && git add . && git commit -m "initial commit from Create Catalyst App"`
|
|
180
|
+
)
|
|
181
|
+
})
|
|
161
182
|
|
|
183
|
+
let mcpEntry = null
|
|
162
184
|
if (mcpSupport) {
|
|
163
185
|
const newMcpDir = path.join(
|
|
164
186
|
process.cwd(),
|
|
@@ -167,11 +189,45 @@ const program = new Commander.Command()
|
|
|
167
189
|
"catalyst-core",
|
|
168
190
|
"mcp_v2"
|
|
169
191
|
)
|
|
170
|
-
|
|
192
|
+
// Same treatment as the install: the setup script's
|
|
193
|
+
// own transcript (npm warnings, audit notices, DB
|
|
194
|
+
// paths) only matters when it fails.
|
|
195
|
+
const transcript = runStep("Setting up the MCP server", () =>
|
|
196
|
+
runMcpSetup(newMcpDir, path.join(process.cwd(), projectName), { quiet: true })
|
|
197
|
+
)
|
|
198
|
+
for (const warning of mcpWarnings(transcript)) clack.log.warn(warning)
|
|
199
|
+
mcpEntry = path.join(newMcpDir, "mcp.js")
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
clack.note(
|
|
203
|
+
[
|
|
204
|
+
`${cyan("npm start".padEnd(16))}${gray("Start the development server")}`,
|
|
205
|
+
`${cyan("npm run build".padEnd(16))}${gray("Bundle the app for production")}`,
|
|
206
|
+
`${cyan("npm run serve".padEnd(16))}${gray("Serve the production build")}`,
|
|
207
|
+
].join("\n"),
|
|
208
|
+
`Created ${bold(projectName)}`
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if (mcpEntry) {
|
|
212
|
+
// The one thing from the MCP setup a person has to act
|
|
213
|
+
// on: the entry to paste into their MCP client.
|
|
214
|
+
clack.note(
|
|
215
|
+
[
|
|
216
|
+
gray("Add to your MCP client (Claude, Cursor, Windsurf):"),
|
|
217
|
+
`"catalyst-mcp": {`,
|
|
218
|
+
` "command": "node",`,
|
|
219
|
+
` "args": ["${mcpEntry}"]`,
|
|
220
|
+
`}`,
|
|
221
|
+
].join("\n"),
|
|
222
|
+
"MCP server"
|
|
223
|
+
)
|
|
171
224
|
}
|
|
225
|
+
|
|
226
|
+
clack.outro(`Next: ${cyan(`cd ${projectName}`)} && ${cyan("npm start")}`)
|
|
172
227
|
} catch (error) {
|
|
228
|
+
// The step that failed already printed its own output.
|
|
173
229
|
const catalystError = error instanceof CCAError ? error : wrapForeignError(error)
|
|
174
|
-
|
|
230
|
+
cancelWithError(catalystError, `Could not create ${projectName}: ${error.message}`)
|
|
175
231
|
process.exit(1)
|
|
176
232
|
} finally {
|
|
177
233
|
deleteDirectory(tempDir)
|
|
@@ -238,8 +294,6 @@ const program = new Commander.Command()
|
|
|
238
294
|
} catch (e) {
|
|
239
295
|
throw createError("CCA-007", { cause: e })
|
|
240
296
|
}
|
|
241
|
-
|
|
242
|
-
console.log(cyan(`Run cd ${projectName} && npm start to get started.`))
|
|
243
297
|
}
|
|
244
298
|
} catch (error) {
|
|
245
299
|
const catalystError = error instanceof CCAError ? error : wrapForeignError(error)
|
|
@@ -286,7 +340,13 @@ program
|
|
|
286
340
|
|
|
287
341
|
program.parse(process.argv)
|
|
288
342
|
|
|
289
|
-
|
|
343
|
+
/**
|
|
344
|
+
* @param {string} mcpDir
|
|
345
|
+
* @param {string} [cwd]
|
|
346
|
+
* @param {{quiet?: boolean}} [options] - quiet captures the output and returns
|
|
347
|
+
* it (printed only on failure); otherwise it streams, for `catalyst-mcp`.
|
|
348
|
+
*/
|
|
349
|
+
function runMcpSetup(mcpDir, cwd = process.cwd(), { quiet = false } = {}) {
|
|
290
350
|
const pkgPath = `${mcpDir}${path.sep}package.json`
|
|
291
351
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"))
|
|
292
352
|
pkg.dependencies["better-sqlite3"] = "^12.8.0"
|
|
@@ -295,113 +355,180 @@ function runMcpSetup(mcpDir, cwd = process.cwd()) {
|
|
|
295
355
|
const mcpNodeModules = `${mcpDir}${path.sep}node_modules`
|
|
296
356
|
let needsInstall = !fs.existsSync(mcpNodeModules)
|
|
297
357
|
|
|
358
|
+
const say = (message) => (quiet ? null : console.log(cyan(message)))
|
|
359
|
+
|
|
298
360
|
if (!needsInstall) {
|
|
299
361
|
try {
|
|
300
362
|
execSync(`node -e "require('better-sqlite3')"`, { cwd: mcpDir, stdio: "pipe" })
|
|
301
363
|
} catch {
|
|
302
|
-
|
|
364
|
+
say("Detected incompatible better-sqlite3 build. Reinstalling...")
|
|
303
365
|
fs.rmSync(mcpNodeModules, { recursive: true, force: true })
|
|
304
366
|
needsInstall = true
|
|
305
367
|
}
|
|
306
368
|
}
|
|
307
369
|
|
|
308
|
-
if (
|
|
309
|
-
|
|
310
|
-
|
|
370
|
+
if (!quiet) {
|
|
371
|
+
if (needsInstall) {
|
|
372
|
+
say("Installing mcp_v2 dependencies...")
|
|
373
|
+
execSync("npm install", { cwd: mcpDir, stdio: "inherit" })
|
|
374
|
+
}
|
|
375
|
+
execFileSync(process.execPath, [`${mcpDir}${path.sep}setup.js`], { cwd, stdio: "inherit" })
|
|
376
|
+
return ""
|
|
311
377
|
}
|
|
312
378
|
|
|
313
|
-
|
|
379
|
+
let transcript = ""
|
|
380
|
+
if (needsInstall) transcript += runQuiet(`npm install --no-audit --no-fund`, { cwd: mcpDir })
|
|
381
|
+
transcript += runQuiet(`"${process.execPath}" "${mcpDir}${path.sep}setup.js"`, { cwd })
|
|
382
|
+
return transcript
|
|
314
383
|
}
|
|
315
384
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
385
|
+
/**
|
|
386
|
+
* Warnings worth surfacing from an otherwise-hidden MCP setup transcript, e.g.
|
|
387
|
+
* a knowledge-base sync that could not reach GitHub. The setup marks them ⚠.
|
|
388
|
+
*/
|
|
389
|
+
function mcpWarnings(transcript) {
|
|
390
|
+
const lines = String(transcript || "").split("\n")
|
|
391
|
+
const warnings = []
|
|
392
|
+
for (const [index, line] of lines.entries()) {
|
|
393
|
+
const match = /^\s*⚠\s*(.+)$/.exec(line)
|
|
394
|
+
if (!match) continue
|
|
395
|
+
const next = lines[index + 1]?.trim()
|
|
396
|
+
warnings.push(next && !/^[⚠✓]/.test(next) ? `${match[1]}. ${next}` : match[1])
|
|
397
|
+
}
|
|
398
|
+
return warnings
|
|
399
|
+
}
|
|
327
400
|
|
|
328
|
-
|
|
401
|
+
/**
|
|
402
|
+
* Run a command with its output captured rather than inherited.
|
|
403
|
+
*
|
|
404
|
+
* On failure the captured output is printed, so a broken install still shows
|
|
405
|
+
* npm's actual error instead of just a non-zero exit code.
|
|
406
|
+
*/
|
|
407
|
+
function runQuiet(command, { cwd } = {}) {
|
|
408
|
+
try {
|
|
409
|
+
// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - Internal scaffolding commands built from a validated project name.
|
|
410
|
+
return execSync(command, { stdio: ["ignore", "pipe", "pipe"], encoding: "utf8", cwd })
|
|
411
|
+
} catch (error) {
|
|
412
|
+
const details = [error.stdout, error.stderr].filter(Boolean).join("\n").trim()
|
|
413
|
+
if (details) {
|
|
414
|
+
process.stderr.write(`\n${details}\n`)
|
|
415
|
+
}
|
|
416
|
+
throw error
|
|
417
|
+
}
|
|
329
418
|
}
|
|
330
|
-
async function promptProjectName() {
|
|
331
|
-
const res = await prompts({
|
|
332
|
-
type: "text",
|
|
333
|
-
name: "path",
|
|
334
|
-
message: "What is your project named?",
|
|
335
|
-
initial: "my-app",
|
|
336
|
-
})
|
|
337
419
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
420
|
+
/**
|
|
421
|
+
* Announce a step, run it, and report the outcome.
|
|
422
|
+
*
|
|
423
|
+
* Deliberately not clack's spinner: the work below is synchronous (execSync),
|
|
424
|
+
* so a spinner's timer never fires and the user stares at one frozen frame for
|
|
425
|
+
* the whole of `npm i` -- which reads as a hang. A static line that says what
|
|
426
|
+
* is happening is honest; a stopped animation is not.
|
|
427
|
+
*/
|
|
428
|
+
function runStep(message, fn) {
|
|
429
|
+
// Announced before, not after: the work below is synchronous (execSync),
|
|
430
|
+
// so nothing else can report progress while `npm i` runs for minutes. One
|
|
431
|
+
// line per step -- the failure branch is what changes the outcome, and it
|
|
432
|
+
// says so explicitly.
|
|
433
|
+
clack.log.step(message)
|
|
434
|
+
try {
|
|
435
|
+
return fn()
|
|
436
|
+
} catch (error) {
|
|
437
|
+
clack.log.error(`${message} failed`)
|
|
438
|
+
throw error
|
|
341
439
|
}
|
|
440
|
+
}
|
|
342
441
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
442
|
+
/**
|
|
443
|
+
* Exit cleanly when the user hits Ctrl-C.
|
|
444
|
+
*
|
|
445
|
+
* The previous prompt library returned undefined on cancel and the scaffolder
|
|
446
|
+
* carried on with it, producing a half-configured project from an answer the
|
|
447
|
+
* user never gave.
|
|
448
|
+
*/
|
|
449
|
+
function exitIfCancelled(value) {
|
|
450
|
+
if (clack.isCancel(value)) {
|
|
451
|
+
clack.cancel("Cancelled — nothing was created.")
|
|
452
|
+
process.exit(130)
|
|
352
453
|
}
|
|
454
|
+
return value
|
|
353
455
|
}
|
|
354
456
|
|
|
355
|
-
async function
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
457
|
+
async function promptStateManagement() {
|
|
458
|
+
return exitIfCancelled(
|
|
459
|
+
await clack.select({
|
|
460
|
+
message: "Choose state management",
|
|
461
|
+
options: [
|
|
462
|
+
{ value: "none", label: "None" },
|
|
463
|
+
{ value: "redux", label: "Redux" },
|
|
464
|
+
{ value: "rtk", label: "Redux Toolkit (RTK)" },
|
|
465
|
+
],
|
|
466
|
+
initialValue: "none",
|
|
467
|
+
})
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async function promptProjectName() {
|
|
472
|
+
return exitIfCancelled(
|
|
473
|
+
await clack.text({
|
|
474
|
+
message: "What is your project named?",
|
|
475
|
+
placeholder: "my-app",
|
|
476
|
+
defaultValue: "my-app",
|
|
477
|
+
validate(value) {
|
|
478
|
+
const name = (value || "my-app").trim()
|
|
479
|
+
const result = validate(name)
|
|
480
|
+
if (!result.validForNewPackages) {
|
|
481
|
+
return (result.errors || result.warnings || ["Invalid package name"])[0]
|
|
482
|
+
}
|
|
483
|
+
if (fs.existsSync(path.join(process.cwd(), name))) {
|
|
484
|
+
return `${name} already exists in this directory`
|
|
485
|
+
}
|
|
486
|
+
return undefined
|
|
487
|
+
},
|
|
488
|
+
})
|
|
489
|
+
).trim()
|
|
490
|
+
}
|
|
361
491
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
492
|
+
async function promptDescription() {
|
|
493
|
+
const value = exitIfCancelled(
|
|
494
|
+
await clack.text({
|
|
495
|
+
message: "What is your project description?",
|
|
496
|
+
placeholder: "optional",
|
|
497
|
+
defaultValue: "",
|
|
498
|
+
})
|
|
499
|
+
)
|
|
500
|
+
return value ? value.trim() : null
|
|
365
501
|
}
|
|
366
502
|
|
|
367
503
|
async function promptTypescript() {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
return response.typescript
|
|
504
|
+
return exitIfCancelled(
|
|
505
|
+
await clack.select({
|
|
506
|
+
message: "Would you like to use TypeScript?",
|
|
507
|
+
options: [
|
|
508
|
+
{ value: "js", label: "JavaScript" },
|
|
509
|
+
{ value: "ts", label: "TypeScript" },
|
|
510
|
+
],
|
|
511
|
+
initialValue: "js",
|
|
512
|
+
})
|
|
513
|
+
)
|
|
379
514
|
}
|
|
380
515
|
|
|
381
516
|
async function promptTailwind() {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
{ title: "No", value: false },
|
|
389
|
-
],
|
|
390
|
-
})
|
|
391
|
-
return response.tailwind
|
|
517
|
+
return exitIfCancelled(
|
|
518
|
+
await clack.confirm({
|
|
519
|
+
message: "Would you like to use Tailwind CSS?",
|
|
520
|
+
initialValue: false,
|
|
521
|
+
})
|
|
522
|
+
)
|
|
392
523
|
}
|
|
393
524
|
|
|
394
525
|
async function promptMcp() {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
{ title: "No", value: false },
|
|
402
|
-
],
|
|
403
|
-
})
|
|
404
|
-
return response.mcp
|
|
526
|
+
return exitIfCancelled(
|
|
527
|
+
await clack.confirm({
|
|
528
|
+
message: "Would you like to set up an MCP server?",
|
|
529
|
+
initialValue: true,
|
|
530
|
+
})
|
|
531
|
+
)
|
|
405
532
|
}
|
|
406
533
|
|
|
407
534
|
function validateOptions(cmd) {
|
package/scripts/errors.cjs
CHANGED
|
@@ -193,7 +193,7 @@ function formatCauseChain(cause) {
|
|
|
193
193
|
*/
|
|
194
194
|
function formatVerbose(err) {
|
|
195
195
|
const lines = [
|
|
196
|
-
pc.bold(
|
|
196
|
+
pc.bold(err.message),
|
|
197
197
|
`Code: ${err.code}`,
|
|
198
198
|
`Category: ${err.category || "UNKNOWN"}`,
|
|
199
199
|
`Time: ${new Date().toISOString()}`,
|
|
@@ -221,7 +221,7 @@ function formatVerbose(err) {
|
|
|
221
221
|
*/
|
|
222
222
|
function formatDebug(err, env) {
|
|
223
223
|
const lines = [
|
|
224
|
-
pc.bold(
|
|
224
|
+
pc.bold(err.message),
|
|
225
225
|
`Code: ${err.code}`,
|
|
226
226
|
`Category: ${err.category || "UNKNOWN"}`,
|
|
227
227
|
`Time: ${new Date().toISOString()}`,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-catalyst-app-starter",
|
|
3
3
|
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"scripts": {
|
|
5
6
|
"start": "catalyst start",
|
|
6
7
|
"build": "catalyst build",
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
"@routes": "src/js/routes/"
|
|
23
24
|
},
|
|
24
25
|
"dependencies": {
|
|
25
|
-
"catalyst-core": "1.0.2-canary.
|
|
26
|
+
"catalyst-core": "1.0.2-canary.2",
|
|
26
27
|
"react": "19.0.0",
|
|
27
28
|
"react-dom": "19.0.0",
|
|
28
29
|
"react-router": "^7.18.2"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-catalyst-app-starter",
|
|
3
3
|
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"scripts": {
|
|
5
6
|
"start": "catalyst start",
|
|
6
7
|
"build": "npm run typecheck && catalyst build",
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
"@routes": "src/js/routes/"
|
|
24
25
|
},
|
|
25
26
|
"dependencies": {
|
|
26
|
-
"catalyst-core": "1.0.2-canary.
|
|
27
|
+
"catalyst-core": "1.0.2-canary.2",
|
|
27
28
|
"react": "19.0.0",
|
|
28
29
|
"react-dom": "19.0.0",
|
|
29
30
|
"react-router": "^7.18.2"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-catalyst-app-starter",
|
|
3
3
|
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"scripts": {
|
|
5
6
|
"start": "catalyst start",
|
|
6
7
|
"build": "catalyst build",
|
|
@@ -25,7 +26,7 @@
|
|
|
25
26
|
"dependencies": {
|
|
26
27
|
"@reduxjs/toolkit": "^2.9.0",
|
|
27
28
|
"react-redux": "^9.2.0",
|
|
28
|
-
"catalyst-core": "1.0.2-canary.
|
|
29
|
+
"catalyst-core": "1.0.2-canary.2",
|
|
29
30
|
"react": "19.0.0",
|
|
30
31
|
"react-dom": "19.0.0",
|
|
31
32
|
"react-router": "^7.18.2",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-catalyst-app-starter",
|
|
3
3
|
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"scripts": {
|
|
5
6
|
"start": "catalyst start",
|
|
6
7
|
"build": "npm run typecheck && catalyst build",
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
"dependencies": {
|
|
27
28
|
"@reduxjs/toolkit": "^2.9.0",
|
|
28
29
|
"react-redux": "^9.2.0",
|
|
29
|
-
"catalyst-core": "1.0.2-canary.
|
|
30
|
+
"catalyst-core": "1.0.2-canary.2",
|
|
30
31
|
"react": "19.0.0",
|
|
31
32
|
"react-dom": "19.0.0",
|
|
32
33
|
"react-router": "^7.18.2",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-catalyst-app-starter",
|
|
3
3
|
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"scripts": {
|
|
5
6
|
"start": "catalyst start",
|
|
6
7
|
"build": "catalyst build",
|
|
@@ -25,7 +26,7 @@
|
|
|
25
26
|
"dependencies": {
|
|
26
27
|
"@reduxjs/toolkit": "^2.9.0",
|
|
27
28
|
"react-redux": "^9.2.0",
|
|
28
|
-
"catalyst-core": "1.0.2-canary.
|
|
29
|
+
"catalyst-core": "1.0.2-canary.2",
|
|
29
30
|
"react": "19.0.0",
|
|
30
31
|
"react-dom": "19.0.0",
|
|
31
32
|
"react-router": "^7.18.2",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-catalyst-app-starter",
|
|
3
3
|
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"scripts": {
|
|
5
6
|
"start": "catalyst start",
|
|
6
7
|
"build": "npm run typecheck && catalyst build",
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
"dependencies": {
|
|
27
28
|
"@reduxjs/toolkit": "^2.9.0",
|
|
28
29
|
"react-redux": "^9.2.0",
|
|
29
|
-
"catalyst-core": "1.0.2-canary.
|
|
30
|
+
"catalyst-core": "1.0.2-canary.2",
|
|
30
31
|
"react": "19.0.0",
|
|
31
32
|
"react-dom": "19.0.0",
|
|
32
33
|
"react-router": "^7.18.2",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// engines allows Node >=20.12, but require() of an ES module is only on by
|
|
2
|
+
// default from 20.19 / 22.12. On older runtimes a top-level require() of an
|
|
3
|
+
// ESM-only dependency (e.g. @clack/prompts) kills `npx create-catalyst-app`
|
|
4
|
+
// with ERR_REQUIRE_ESM before it prints anything. Simulate those runtimes by
|
|
5
|
+
// turning require(esm) off and check the CLI still starts.
|
|
6
|
+
const { spawnSync }: typeof import("child_process") = require("child_process")
|
|
7
|
+
const path: typeof import("path") = require("path")
|
|
8
|
+
|
|
9
|
+
const cli = path.join(__dirname, "..", "scripts", "cli.cjs")
|
|
10
|
+
const canDisableRequireEsm = process.allowedNodeEnvironmentFlags.has("--no-experimental-require-module")
|
|
11
|
+
|
|
12
|
+
describe.runIf(canDisableRequireEsm)("cli.cjs without require(esm)", () => {
|
|
13
|
+
it("prints --help", () => {
|
|
14
|
+
const result = spawnSync(process.execPath, ["--no-experimental-require-module", cli, "--help"], {
|
|
15
|
+
encoding: "utf8",
|
|
16
|
+
})
|
|
17
|
+
expect(result.stderr).not.toContain("ERR_REQUIRE_ESM")
|
|
18
|
+
expect(result.status).toBe(0)
|
|
19
|
+
expect(result.stdout).toContain("Usage:")
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it("loads the prompt library when scaffolding", () => {
|
|
23
|
+
// An invalid name fails validation right after the prompt library is
|
|
24
|
+
// imported, so nothing is created.
|
|
25
|
+
const result = spawnSync(
|
|
26
|
+
process.execPath,
|
|
27
|
+
["--no-experimental-require-module", cli, "Bad Name", "-y"],
|
|
28
|
+
{
|
|
29
|
+
encoding: "utf8",
|
|
30
|
+
env: { ...process.env, NO_COLOR: "1" },
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
const output = result.stdout + result.stderr
|
|
34
|
+
expect(output).not.toContain("ERR_REQUIRE_ESM")
|
|
35
|
+
expect(output).toContain("CCA-001")
|
|
36
|
+
expect(result.status).toBe(1)
|
|
37
|
+
})
|
|
38
|
+
})
|
|
File without changes
|