create-cascade 0.1.4 → 0.1.5

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.
Files changed (3) hide show
  1. package/README.md +8 -7
  2. package/index.js +152 -49
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -13,7 +13,7 @@ bun create cascade
13
13
  - Create in a new folder or directly in the current folder
14
14
  - Interactive framework selection (`core`, `react`, `solid`)
15
15
  - Multiple built-in starter code presets per framework
16
- - Optional direct install and immediate app start
16
+ - Scaffold only by default (no install, no auto-run)
17
17
 
18
18
  ## Frameworks
19
19
 
@@ -48,8 +48,8 @@ Options:
48
48
  -f, --framework <name> Framework: core, react, solid
49
49
  -s, --starter <name> Starter preset for selected framework
50
50
  --here Use current directory
51
- --no-install Skip bun install
52
- --no-start Skip bun run dev after install
51
+ --install Run bun install after scaffolding
52
+ --start Run bun install, then bun run dev
53
53
  -h, --help Show help
54
54
  ```
55
55
 
@@ -65,14 +65,15 @@ bun create cascade --here
65
65
  # Create React app with counter starter
66
66
  bun create cascade my-app -f react -s counter
67
67
 
68
- # Scaffold only, no install
69
- bun create cascade my-app --no-install
68
+ # Scaffold then install
69
+ bun create cascade my-app --install
70
70
  ```
71
71
 
72
72
  ## Behavior
73
73
 
74
- - If installation is enabled, the CLI runs `bun install` in the target directory.
75
- - If installation is enabled and `--no-start` is not set, the CLI starts the app with `bun run dev`.
74
+ - By default, the CLI only scaffolds files.
75
+ - With `--install`, it runs `bun install` in the target directory.
76
+ - With `--start`, it runs `bun install` then `bun run dev`.
76
77
 
77
78
  ## Package
78
79
 
package/index.js CHANGED
@@ -5,6 +5,12 @@ import { basename, resolve } from "node:path"
5
5
  import process from "node:process"
6
6
  import { spawnSync } from "node:child_process"
7
7
  import { createInterface } from "node:readline/promises"
8
+ import { emitKeypressEvents } from "node:readline"
9
+
10
+ const ANSI_RESET = "\x1b[0m"
11
+ const ANSI_BOLD = "\x1b[1m"
12
+ const ANSI_DIM = "\x1b[2m"
13
+ const ANSI_CYAN = "\x1b[36m"
8
14
 
9
15
  const FRAMEWORKS = [
10
16
  { id: "core", label: "Core", description: "Vanilla Cascade API with renderables" },
@@ -33,8 +39,8 @@ const STARTERS = {
33
39
  function parseArgs(argv) {
34
40
  const args = argv.slice(2)
35
41
  const options = {
36
- noInstall: false,
37
- noStart: false,
42
+ install: false,
43
+ start: false,
38
44
  here: false,
39
45
  framework: undefined,
40
46
  starter: undefined,
@@ -45,12 +51,21 @@ function parseArgs(argv) {
45
51
  for (let i = 0; i < args.length; i += 1) {
46
52
  const arg = args[i]
47
53
 
54
+ if (arg === "--install") {
55
+ options.install = true
56
+ continue
57
+ }
58
+ if (arg === "--start") {
59
+ options.start = true
60
+ options.install = true
61
+ continue
62
+ }
48
63
  if (arg === "--no-install") {
49
- options.noInstall = true
64
+ options.install = false
50
65
  continue
51
66
  }
52
67
  if (arg === "--no-start") {
53
- options.noStart = true
68
+ options.start = false
54
69
  continue
55
70
  }
56
71
  if (arg === "--here") {
@@ -84,8 +99,8 @@ function printHelp() {
84
99
  console.log(" -f, --framework <name> Framework: core, react, solid")
85
100
  console.log(" -s, --starter <name> Starter preset for selected framework")
86
101
  console.log(" --here Use current directory")
87
- console.log(" --no-install Skip bun install")
88
- console.log(" --no-start Skip bun run dev after install")
102
+ console.log(" --install Run bun install after scaffolding")
103
+ console.log(" --start Run bun install, then bun run dev")
89
104
  console.log(" -h, --help Show help")
90
105
  console.log("")
91
106
  console.log("Examples:")
@@ -118,47 +133,83 @@ function promptLine(rl, label) {
118
133
  }
119
134
 
120
135
  async function selectOption(rl, label, options) {
121
- console.log("")
122
- console.log(label)
123
- for (let i = 0; i < options.length; i += 1) {
124
- const option = options[i]
125
- console.log(` ${i + 1}. ${option.label} (${option.id}) - ${option.description}`)
136
+ if (!process.stdin.isTTY) {
137
+ return options[0].id
126
138
  }
127
139
 
128
- while (true) {
129
- const raw = (await promptLine(rl, "Select a number: ")).trim()
130
- const index = Number(raw)
131
- if (Number.isInteger(index) && index >= 1 && index <= options.length) {
132
- return options[index - 1].id
140
+ const stdin = process.stdin
141
+ const stdout = process.stdout
142
+ let selectedIndex = 0
143
+ const totalLines = options.length + 2
144
+ let renderedOnce = false
145
+
146
+ const render = () => {
147
+ if (renderedOnce) {
148
+ stdout.write(`\x1b[${totalLines}F`)
149
+ } else {
150
+ stdout.write("\n")
133
151
  }
134
- console.log("Invalid choice. Try again.")
135
- }
136
- }
137
152
 
138
- async function resolveProjectName(rl, options, positionals) {
139
- if (options.here) {
140
- return "."
141
- }
153
+ stdout.write(`${ANSI_BOLD}${label}${ANSI_RESET}\n`)
142
154
 
143
- if (positionals[0]) {
144
- return positionals[0]
145
- }
155
+ for (let i = 0; i < options.length; i += 1) {
156
+ const option = options[i]
157
+ const isSelected = i === selectedIndex
158
+ const prefix = isSelected ? `${ANSI_BOLD}${ANSI_CYAN}>${ANSI_RESET}` : " "
159
+ const styleStart = isSelected ? `${ANSI_BOLD}${ANSI_CYAN}` : ""
160
+ const styleEnd = isSelected ? ANSI_RESET : ""
161
+ stdout.write(`${prefix} ${styleStart}${option.label}${styleEnd} ${ANSI_DIM}(${option.id}) - ${option.description}${ANSI_RESET}\n`)
162
+ }
146
163
 
147
- if (!process.stdin.isTTY) {
148
- return "cascade-app"
164
+ stdout.write(`${ANSI_DIM}Use Up/Down arrows and Enter${ANSI_RESET}\n`)
165
+ renderedOnce = true
149
166
  }
150
167
 
151
- const locationChoice = await selectOption(rl, "Where should the project be created?", [
152
- { id: "new", label: "New folder", description: "Create and use a new project directory" },
153
- { id: "here", label: "Current folder", description: "Use the current directory directly" },
154
- ])
168
+ return new Promise((resolve, reject) => {
169
+ const cleanup = () => {
170
+ stdin.off("keypress", onKeyPress)
171
+ if (stdin.isTTY) {
172
+ stdin.setRawMode(false)
173
+ }
174
+ stdout.write("\n")
175
+ }
155
176
 
156
- if (locationChoice === "here") {
157
- return "."
158
- }
177
+ const onKeyPress = (_, key) => {
178
+ if (!key) {
179
+ return
180
+ }
181
+
182
+ if (key.ctrl && key.name === "c") {
183
+ cleanup()
184
+ reject(new Error("Operation cancelled"))
185
+ return
186
+ }
187
+
188
+ if (key.name === "up") {
189
+ selectedIndex = selectedIndex === 0 ? options.length - 1 : selectedIndex - 1
190
+ render()
191
+ return
192
+ }
193
+
194
+ if (key.name === "down") {
195
+ selectedIndex = selectedIndex === options.length - 1 ? 0 : selectedIndex + 1
196
+ render()
197
+ return
198
+ }
199
+
200
+ if (key.name === "return") {
201
+ const selected = options[selectedIndex]
202
+ cleanup()
203
+ resolve(selected.id)
204
+ }
205
+ }
159
206
 
160
- const input = (await promptLine(rl, "Project name (default: cascade-app): ")).trim()
161
- return input || "cascade-app"
207
+ emitKeypressEvents(stdin)
208
+ stdin.setRawMode(true)
209
+ stdin.resume()
210
+ stdin.on("keypress", onKeyPress)
211
+ render()
212
+ })
162
213
  }
163
214
 
164
215
  async function resolveFramework(rl, frameworkArg) {
@@ -196,6 +247,46 @@ async function resolveStarter(rl, framework, starterArg) {
196
247
  return selectOption(rl, `Choose a starter for ${framework}:`, choices)
197
248
  }
198
249
 
250
+ async function resolveLocation(rl, options, positionals) {
251
+ if (options.here) {
252
+ return "here"
253
+ }
254
+
255
+ if (positionals[0]) {
256
+ return "new"
257
+ }
258
+
259
+ if (!process.stdin.isTTY) {
260
+ return "new"
261
+ }
262
+
263
+ return selectOption(rl, "Where should the project be created?", [
264
+ { id: "new", label: "New folder", description: "Create and use a new project directory" },
265
+ { id: "here", label: "Current folder", description: "Use the current directory directly" },
266
+ ])
267
+ }
268
+
269
+ async function resolveProjectName(rl, positionals, defaultName = "cascade-app") {
270
+ if (positionals[0]) {
271
+ return positionals[0]
272
+ }
273
+
274
+ if (!process.stdin.isTTY) {
275
+ return defaultName
276
+ }
277
+
278
+ const input = (await promptLine(rl, `Project name (default: ${defaultName}): `)).trim()
279
+ return input || defaultName
280
+ }
281
+
282
+ function shouldRunInteractiveWizard(options, positionals) {
283
+ if (!process.stdin.isTTY) {
284
+ return false
285
+ }
286
+
287
+ return !positionals[0] && !options.here && !options.framework && !options.starter
288
+ }
289
+
199
290
  function getPackageJson(projectName, framework) {
200
291
  const dependencies = {
201
292
  "@cascadetui/core": "latest",
@@ -502,13 +593,27 @@ async function main() {
502
593
  })
503
594
 
504
595
  try {
505
- const rawProjectName = await resolveProjectName(rl, options, positionals)
506
- const framework = await resolveFramework(rl, options.framework)
507
- const starter = await resolveStarter(rl, framework, options.starter)
596
+ let framework
597
+ let projectName
598
+ let location
599
+ let starter
600
+
601
+ if (shouldRunInteractiveWizard(options, positionals)) {
602
+ framework = await resolveFramework(rl, undefined)
603
+ projectName = await resolveProjectName(rl, [], "cascade-app")
604
+ location = await resolveLocation(rl, { ...options, here: false }, [])
605
+ starter = await resolveStarter(rl, framework, undefined)
606
+ } else {
607
+ framework = await resolveFramework(rl, options.framework)
608
+ location = await resolveLocation(rl, options, positionals)
609
+ const defaultName = location === "here" ? basename(process.cwd()) : "cascade-app"
610
+ projectName = await resolveProjectName(rl, positionals, defaultName)
611
+ starter = await resolveStarter(rl, framework, options.starter)
612
+ }
508
613
 
509
- const usingCurrentDirectory = rawProjectName === "."
510
- const targetDir = usingCurrentDirectory ? process.cwd() : resolve(process.cwd(), rawProjectName)
511
- const packageNameSeed = usingCurrentDirectory ? basename(targetDir) : rawProjectName
614
+ const usingCurrentDirectory = location === "here"
615
+ const targetDir = usingCurrentDirectory ? process.cwd() : resolve(process.cwd(), projectName)
616
+ const packageNameSeed = projectName
512
617
 
513
618
  writeProject(targetDir, packageNameSeed, framework, starter)
514
619
 
@@ -517,13 +622,13 @@ async function main() {
517
622
  console.log(`Framework: ${framework}`)
518
623
  console.log(`Starter: ${starter}`)
519
624
 
520
- if (!options.noInstall) {
625
+ if (options.install) {
521
626
  console.log("")
522
627
  console.log("Installing dependencies with bun...")
523
628
  runCommand("bun", ["install"], targetDir)
524
629
  }
525
630
 
526
- if (!options.noInstall && !options.noStart) {
631
+ if (options.start) {
527
632
  console.log("")
528
633
  console.log("Starting the project...")
529
634
  runCommand("bun", ["run", "dev"], targetDir)
@@ -533,11 +638,9 @@ async function main() {
533
638
  console.log("")
534
639
  console.log("Next steps:")
535
640
  if (!usingCurrentDirectory) {
536
- console.log(` cd ${rawProjectName}`)
537
- }
538
- if (options.noInstall) {
539
- console.log(" bun install")
641
+ console.log(` cd ${projectName}`)
540
642
  }
643
+ console.log(" bun install")
541
644
  console.log(" bun run dev")
542
645
  } finally {
543
646
  rl.close()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-cascade",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Create a new Cascade TUI project",
5
5
  "type": "module",
6
6
  "license": "MIT",