create-cascade 0.1.3 → 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.
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # Create Cascade App
2
+
3
+ A CLI tool to create Cascade projects with interactive framework and starter selection.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ bun create cascade
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - Create in a new folder or directly in the current folder
14
+ - Interactive framework selection (`core`, `react`, `solid`)
15
+ - Multiple built-in starter code presets per framework
16
+ - Scaffold only by default (no install, no auto-run)
17
+
18
+ ## Frameworks
19
+
20
+ - `core`: Vanilla Cascade API
21
+ - `react`: Cascade + React renderer
22
+ - `solid`: Cascade + Solid renderer
23
+
24
+ ## Starter Presets
25
+
26
+ ### Core
27
+
28
+ - `minimal`
29
+ - `counter`
30
+ - `layout`
31
+
32
+ ### React
33
+
34
+ - `minimal`
35
+ - `counter`
36
+ - `login`
37
+
38
+ ### Solid
39
+
40
+ - `minimal`
41
+ - `counter`
42
+ - `input`
43
+
44
+ ## CLI Options
45
+
46
+ ```txt
47
+ Options:
48
+ -f, --framework <name> Framework: core, react, solid
49
+ -s, --starter <name> Starter preset for selected framework
50
+ --here Use current directory
51
+ --install Run bun install after scaffolding
52
+ --start Run bun install, then bun run dev
53
+ -h, --help Show help
54
+ ```
55
+
56
+ ## Examples
57
+
58
+ ```bash
59
+ # Interactive mode
60
+ bun create cascade
61
+
62
+ # Create in current folder
63
+ bun create cascade --here
64
+
65
+ # Create React app with counter starter
66
+ bun create cascade my-app -f react -s counter
67
+
68
+ # Scaffold then install
69
+ bun create cascade my-app --install
70
+ ```
71
+
72
+ ## Behavior
73
+
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`.
77
+
78
+ ## Package
79
+
80
+ Published on npm as `create-cascade`.
package/index.js CHANGED
@@ -1,23 +1,85 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs"
4
- import { dirname, join, resolve } from "node:path"
3
+ import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs"
4
+ import { basename, resolve } from "node:path"
5
5
  import process from "node:process"
6
- import { fileURLToPath } from "node:url"
6
+ import { spawnSync } from "node:child_process"
7
+ import { createInterface } from "node:readline/promises"
8
+ import { emitKeypressEvents } from "node:readline"
7
9
 
8
- const __filename = fileURLToPath(import.meta.url)
9
- const __dirname = dirname(__filename)
10
+ const ANSI_RESET = "\x1b[0m"
11
+ const ANSI_BOLD = "\x1b[1m"
12
+ const ANSI_DIM = "\x1b[2m"
13
+ const ANSI_CYAN = "\x1b[36m"
14
+
15
+ const FRAMEWORKS = [
16
+ { id: "core", label: "Core", description: "Vanilla Cascade API with renderables" },
17
+ { id: "react", label: "React", description: "Cascade renderer with React components" },
18
+ { id: "solid", label: "Solid", description: "Cascade renderer with SolidJS components" },
19
+ ]
20
+
21
+ const STARTERS = {
22
+ core: [
23
+ { id: "minimal", label: "Minimal", description: "Single welcome message" },
24
+ { id: "counter", label: "Counter", description: "Live counter updated every second" },
25
+ { id: "layout", label: "Layout", description: "Simple boxed layout starter" },
26
+ ],
27
+ react: [
28
+ { id: "minimal", label: "Minimal", description: "Render a single text node" },
29
+ { id: "counter", label: "Counter", description: "React state with interval updates" },
30
+ { id: "login", label: "Login", description: "Small interactive login form" },
31
+ ],
32
+ solid: [
33
+ { id: "minimal", label: "Minimal", description: "Render a single text node" },
34
+ { id: "counter", label: "Counter", description: "Solid signal with interval updates" },
35
+ { id: "input", label: "Input", description: "Basic input and submit interaction" },
36
+ ],
37
+ }
10
38
 
11
39
  function parseArgs(argv) {
12
40
  const args = argv.slice(2)
13
41
  const options = {
14
- noInstall: false,
42
+ install: false,
43
+ start: false,
44
+ here: false,
45
+ framework: undefined,
46
+ starter: undefined,
47
+ help: false,
15
48
  }
16
49
  const positionals = []
17
50
 
18
- for (const arg of args) {
51
+ for (let i = 0; i < args.length; i += 1) {
52
+ const arg = args[i]
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
+ }
19
63
  if (arg === "--no-install") {
20
- options.noInstall = true
64
+ options.install = false
65
+ continue
66
+ }
67
+ if (arg === "--no-start") {
68
+ options.start = false
69
+ continue
70
+ }
71
+ if (arg === "--here") {
72
+ options.here = true
73
+ continue
74
+ }
75
+ if (arg === "--framework" || arg === "-f" || arg === "--template" || arg === "-t") {
76
+ options.framework = args[i + 1]
77
+ i += 1
78
+ continue
79
+ }
80
+ if (arg === "--starter" || arg === "-s") {
81
+ options.starter = args[i + 1]
82
+ i += 1
21
83
  continue
22
84
  }
23
85
  if (arg === "--help" || arg === "-h") {
@@ -31,12 +93,29 @@ function parseArgs(argv) {
31
93
  }
32
94
 
33
95
  function printHelp() {
34
- console.log("Usage: bun create cascade [project-name] [--no-install]")
96
+ console.log("Usage: bun create cascade [project-name] [options]")
97
+ console.log("")
98
+ console.log("Options:")
99
+ console.log(" -f, --framework <name> Framework: core, react, solid")
100
+ console.log(" -s, --starter <name> Starter preset for selected framework")
101
+ console.log(" --here Use current directory")
102
+ console.log(" --install Run bun install after scaffolding")
103
+ console.log(" --start Run bun install, then bun run dev")
104
+ console.log(" -h, --help Show help")
35
105
  console.log("")
36
106
  console.log("Examples:")
37
107
  console.log(" bun create cascade")
38
108
  console.log(" bun create cascade my-app")
39
- console.log(" bun create cascade my-app --no-install")
109
+ console.log(" bun create cascade --here")
110
+ console.log(" bun create cascade my-app -f react -s counter")
111
+ }
112
+
113
+ function normalizePackageName(name) {
114
+ return name
115
+ .trim()
116
+ .toLowerCase()
117
+ .replace(/[^a-z0-9-_]+/g, "-")
118
+ .replace(/^-+|-+$/g, "") || "cascade-app"
40
119
  }
41
120
 
42
121
  function ensureDirectoryIsEmpty(targetDir) {
@@ -49,57 +128,526 @@ function ensureDirectoryIsEmpty(targetDir) {
49
128
  }
50
129
  }
51
130
 
52
- function normalizePackageName(name) {
53
- return name
54
- .trim()
55
- .toLowerCase()
56
- .replace(/[^a-z0-9-_]+/g, "-")
57
- .replace(/^-+|-+$/g, "") || "cascade-app"
131
+ function promptLine(rl, label) {
132
+ return rl.question(label)
133
+ }
134
+
135
+ async function selectOption(rl, label, options) {
136
+ if (!process.stdin.isTTY) {
137
+ return options[0].id
138
+ }
139
+
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")
151
+ }
152
+
153
+ stdout.write(`${ANSI_BOLD}${label}${ANSI_RESET}\n`)
154
+
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
+ }
163
+
164
+ stdout.write(`${ANSI_DIM}Use Up/Down arrows and Enter${ANSI_RESET}\n`)
165
+ renderedOnce = true
166
+ }
167
+
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
+ }
176
+
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
+ }
206
+
207
+ emitKeypressEvents(stdin)
208
+ stdin.setRawMode(true)
209
+ stdin.resume()
210
+ stdin.on("keypress", onKeyPress)
211
+ render()
212
+ })
213
+ }
214
+
215
+ async function resolveFramework(rl, frameworkArg) {
216
+ if (frameworkArg) {
217
+ const found = FRAMEWORKS.find((entry) => entry.id === frameworkArg)
218
+ if (!found) {
219
+ throw new Error(`Unknown framework: ${frameworkArg}. Use core, react, or solid.`)
220
+ }
221
+ return found.id
222
+ }
223
+
224
+ if (!process.stdin.isTTY) {
225
+ return "core"
226
+ }
227
+
228
+ return selectOption(rl, "Choose a framework:", FRAMEWORKS)
58
229
  }
59
230
 
60
- function writeTemplate(targetDir, projectName) {
61
- const templateDir = join(__dirname, "template")
62
- cpSync(templateDir, targetDir, { recursive: true })
231
+ async function resolveStarter(rl, framework, starterArg) {
232
+ const choices = STARTERS[framework]
63
233
 
64
- const packageJsonPath = join(targetDir, "package.json")
65
- const packageJsonRaw = readFileSync(packageJsonPath, "utf8")
66
- const packageJson = JSON.parse(packageJsonRaw)
67
- packageJson.name = normalizePackageName(projectName)
68
- writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`)
234
+ if (starterArg) {
235
+ const found = choices.find((entry) => entry.id === starterArg)
236
+ if (!found) {
237
+ const allowed = choices.map((entry) => entry.id).join(", ")
238
+ throw new Error(`Unknown starter '${starterArg}' for ${framework}. Allowed: ${allowed}.`)
239
+ }
240
+ return found.id
241
+ }
242
+
243
+ if (!process.stdin.isTTY) {
244
+ return choices[0].id
245
+ }
246
+
247
+ return selectOption(rl, `Choose a starter for ${framework}:`, choices)
69
248
  }
70
249
 
71
- function main() {
72
- try {
73
- const { options, positionals } = parseArgs(process.argv)
74
- if (options.help) {
75
- printHelp()
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
+
290
+ function getPackageJson(projectName, framework) {
291
+ const dependencies = {
292
+ "@cascadetui/core": "latest",
293
+ }
294
+
295
+ if (framework === "react") {
296
+ dependencies["@cascadetui/react"] = "latest"
297
+ dependencies.react = "latest"
298
+ }
299
+
300
+ if (framework === "solid") {
301
+ dependencies["@cascadetui/solid"] = "latest"
302
+ dependencies["solid-js"] = "latest"
303
+ }
304
+
305
+ const isJsx = framework !== "core"
306
+ const entry = isJsx ? "src/index.tsx" : "src/index.ts"
307
+
308
+ return {
309
+ name: normalizePackageName(projectName),
310
+ version: "0.0.1",
311
+ private: true,
312
+ type: "module",
313
+ scripts: {
314
+ dev: `bun run ${entry}`,
315
+ },
316
+ dependencies,
317
+ }
318
+ }
319
+
320
+ function getTsConfig(framework) {
321
+ const base = {
322
+ target: "ESNext",
323
+ module: "ESNext",
324
+ moduleResolution: "Bundler",
325
+ strict: true,
326
+ skipLibCheck: true,
327
+ }
328
+
329
+ if (framework === "react") {
330
+ return {
331
+ compilerOptions: {
332
+ ...base,
333
+ lib: ["ESNext", "DOM"],
334
+ jsx: "react-jsx",
335
+ jsxImportSource: "@cascadetui/react",
336
+ },
337
+ include: ["src"],
338
+ }
339
+ }
340
+
341
+ if (framework === "solid") {
342
+ return {
343
+ compilerOptions: {
344
+ ...base,
345
+ jsx: "preserve",
346
+ jsxImportSource: "@cascadetui/solid",
347
+ },
348
+ include: ["src"],
349
+ }
350
+ }
351
+
352
+ return {
353
+ compilerOptions: base,
354
+ include: ["src"],
355
+ }
356
+ }
357
+
358
+ function getSource(framework, starter) {
359
+ const sources = {
360
+ core: {
361
+ minimal: `import { TextRenderable, createCliRenderer } from "@cascadetui/core"
362
+
363
+ const renderer = await createCliRenderer({ exitOnCtrlC: true })
364
+
365
+ const text = new TextRenderable(renderer, {
366
+ content: "Hello from Cascade",
367
+ margin: 2,
368
+ fg: "#00ff99",
369
+ })
370
+
371
+ renderer.root.add(text)
372
+ `,
373
+ counter: `import { TextRenderable, createCliRenderer } from "@cascadetui/core"
374
+
375
+ const renderer = await createCliRenderer({ exitOnCtrlC: true })
376
+ let count = 0
377
+
378
+ const text = new TextRenderable(renderer, {
379
+ content: "Count: 0",
380
+ margin: 2,
381
+ fg: "#00ff99",
382
+ })
383
+
384
+ renderer.root.add(text)
385
+
386
+ setInterval(() => {
387
+ count += 1
388
+ text.content = \`Count: \${count}\`
389
+ }, 1000)
390
+ `,
391
+ layout: `import { BoxRenderable, TextRenderable, createCliRenderer } from "@cascadetui/core"
392
+
393
+ const renderer = await createCliRenderer({ exitOnCtrlC: true })
394
+
395
+ const container = new BoxRenderable(renderer, {
396
+ border: true,
397
+ borderStyle: "single",
398
+ padding: 1,
399
+ margin: 1,
400
+ width: 50,
401
+ height: 9,
402
+ flexDirection: "column",
403
+ })
404
+
405
+ const title = new TextRenderable(renderer, {
406
+ content: "Cascade Core Starter",
407
+ fg: "#00ff99",
408
+ })
409
+
410
+ const subtitle = new TextRenderable(renderer, {
411
+ content: "Press Ctrl+C to exit",
412
+ marginTop: 1,
413
+ fg: "#cccccc",
414
+ })
415
+
416
+ container.add(title)
417
+ container.add(subtitle)
418
+ renderer.root.add(container)
419
+ `,
420
+ },
421
+ react: {
422
+ minimal: `import { createCliRenderer } from "@cascadetui/core"
423
+ import { createRoot } from "@cascadetui/react"
424
+
425
+ function App() {
426
+ return <text content="Hello from Cascade + React" fg="#00ff99" />
427
+ }
428
+
429
+ const renderer = await createCliRenderer({ exitOnCtrlC: true })
430
+ createRoot(renderer).render(<App />)
431
+ `,
432
+ counter: `import { createCliRenderer } from "@cascadetui/core"
433
+ import { createRoot } from "@cascadetui/react"
434
+ import { useEffect, useState } from "react"
435
+
436
+ function App() {
437
+ const [count, setCount] = useState(0)
438
+
439
+ useEffect(() => {
440
+ const timer = setInterval(() => setCount((value) => value + 1), 1000)
441
+ return () => clearInterval(timer)
442
+ }, [])
443
+
444
+ return (
445
+ <box style={{ border: true, padding: 1, margin: 1 }}>
446
+ <text content={\`React counter: \${count}\`} fg="#00ff99" />
447
+ </box>
448
+ )
449
+ }
450
+
451
+ const renderer = await createCliRenderer({ exitOnCtrlC: true })
452
+ createRoot(renderer).render(<App />)
453
+ `,
454
+ login: `import { createCliRenderer } from "@cascadetui/core"
455
+ import { createRoot, useKeyboard } from "@cascadetui/react"
456
+ import { useState } from "react"
457
+
458
+ function App() {
459
+ const [username, setUsername] = useState("")
460
+ const [password, setPassword] = useState("")
461
+ const [focused, setFocused] = useState("username")
462
+ const [status, setStatus] = useState("idle")
463
+
464
+ useKeyboard((key) => {
465
+ if (key.name === "tab") {
466
+ setFocused((value) => (value === "username" ? "password" : "username"))
467
+ }
468
+ })
469
+
470
+ const submit = () => {
471
+ if (username === "admin" && password === "secret") {
472
+ setStatus("success")
76
473
  return
77
474
  }
475
+ setStatus("invalid")
476
+ }
477
+
478
+ return (
479
+ <box style={{ padding: 2, flexDirection: "column" }}>
480
+ <text content="Cascade Login" fg="#00ff99" />
481
+ <box title="Username" style={{ border: true, width: 40, height: 3, marginTop: 1 }}>
482
+ <input focused={focused === "username"} placeholder="admin" onInput={setUsername} onSubmit={submit} />
483
+ </box>
484
+ <box title="Password" style={{ border: true, width: 40, height: 3, marginTop: 1 }}>
485
+ <input focused={focused === "password"} placeholder="secret" onInput={setPassword} onSubmit={submit} />
486
+ </box>
487
+ <text content={\`Status: \${status}\`} marginTop={1} fg={status === "success" ? "green" : "yellow"} />
488
+ </box>
489
+ )
490
+ }
491
+
492
+ const renderer = await createCliRenderer({ exitOnCtrlC: true })
493
+ createRoot(renderer).render(<App />)
494
+ `,
495
+ },
496
+ solid: {
497
+ minimal: `import { render } from "@cascadetui/solid"
78
498
 
79
- const projectName = positionals[0] ?? "cascade-app"
80
- const targetDir = resolve(process.cwd(), projectName)
499
+ const App = () => <text content="Hello from Cascade + Solid" fg="#00ff99" />
81
500
 
82
- mkdirSync(targetDir, { recursive: true })
83
- ensureDirectoryIsEmpty(targetDir)
84
- writeTemplate(targetDir, projectName)
501
+ render(App, { exitOnCtrlC: true })
502
+ `,
503
+ counter: `import { render } from "@cascadetui/solid"
504
+ import { createSignal, onCleanup } from "solid-js"
505
+
506
+ const App = () => {
507
+ const [count, setCount] = createSignal(0)
508
+ const timer = setInterval(() => setCount((value) => value + 1), 1000)
509
+ onCleanup(() => clearInterval(timer))
510
+
511
+ return (
512
+ <box style={{ border: true, padding: 1, margin: 1 }}>
513
+ <text content={\`Solid counter: \${count()}\`} fg="#00ff99" />
514
+ </box>
515
+ )
516
+ }
517
+
518
+ render(App, { exitOnCtrlC: true })
519
+ `,
520
+ input: `import { render } from "@cascadetui/solid"
521
+ import { createSignal } from "solid-js"
522
+
523
+ const App = () => {
524
+ const [value, setValue] = createSignal("")
525
+ const [submitted, setSubmitted] = createSignal("")
526
+
527
+ return (
528
+ <box style={{ padding: 2, flexDirection: "column" }}>
529
+ <text content="Cascade Solid Input" fg="#00ff99" />
530
+ <box title="Message" style={{ border: true, width: 40, height: 3, marginTop: 1 }}>
531
+ <input
532
+ focused
533
+ placeholder="Type something..."
534
+ onInput={setValue}
535
+ onSubmit={(nextValue) => setSubmitted(nextValue)}
536
+ />
537
+ </box>
538
+ <text content={\`Current: \${value()}\`} marginTop={1} />
539
+ <text content={\`Submitted: \${submitted() || "-"}\`} />
540
+ </box>
541
+ )
542
+ }
543
+
544
+ render(App, { exitOnCtrlC: true })
545
+ `,
546
+ },
547
+ }
548
+
549
+ return sources[framework][starter]
550
+ }
551
+
552
+ function writeProject(targetDir, projectName, framework, starter) {
553
+ mkdirSync(targetDir, { recursive: true })
554
+ ensureDirectoryIsEmpty(targetDir)
555
+ mkdirSync(resolve(targetDir, "src"), { recursive: true })
556
+
557
+ const packageJson = getPackageJson(projectName, framework)
558
+ const tsconfig = getTsConfig(framework)
559
+ const isJsx = framework !== "core"
560
+ const fileName = isJsx ? "index.tsx" : "index.ts"
561
+ const source = getSource(framework, starter)
562
+
563
+ writeFileSync(resolve(targetDir, "package.json"), `${JSON.stringify(packageJson, null, 2)}\n`)
564
+ writeFileSync(resolve(targetDir, "tsconfig.json"), `${JSON.stringify(tsconfig, null, 2)}\n`)
565
+ writeFileSync(resolve(targetDir, "src", fileName), source)
566
+
567
+ if (framework === "solid") {
568
+ writeFileSync(resolve(targetDir, "bunfig.toml"), 'preload = ["@cascadetui/solid/preload"]\n')
569
+ }
570
+ }
571
+
572
+ function runCommand(command, args, cwd) {
573
+ const result = spawnSync(command, args, {
574
+ cwd,
575
+ stdio: "inherit",
576
+ shell: process.platform === "win32",
577
+ })
578
+ if (result.status !== 0) {
579
+ throw new Error(`Command failed: ${command} ${args.join(" ")}`)
580
+ }
581
+ }
582
+
583
+ async function main() {
584
+ const { options, positionals } = parseArgs(process.argv)
585
+ if (options.help) {
586
+ printHelp()
587
+ return
588
+ }
589
+
590
+ const rl = createInterface({
591
+ input: process.stdin,
592
+ output: process.stdout,
593
+ })
594
+
595
+ try {
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
+ }
613
+
614
+ const usingCurrentDirectory = location === "here"
615
+ const targetDir = usingCurrentDirectory ? process.cwd() : resolve(process.cwd(), projectName)
616
+ const packageNameSeed = projectName
617
+
618
+ writeProject(targetDir, packageNameSeed, framework, starter)
85
619
 
86
620
  console.log("")
87
- console.log(`Created project in ${targetDir}`)
621
+ console.log(`Created Cascade project in ${targetDir}`)
622
+ console.log(`Framework: ${framework}`)
623
+ console.log(`Starter: ${starter}`)
624
+
625
+ if (options.install) {
626
+ console.log("")
627
+ console.log("Installing dependencies with bun...")
628
+ runCommand("bun", ["install"], targetDir)
629
+ }
630
+
631
+ if (options.start) {
632
+ console.log("")
633
+ console.log("Starting the project...")
634
+ runCommand("bun", ["run", "dev"], targetDir)
635
+ return
636
+ }
637
+
88
638
  console.log("")
89
639
  console.log("Next steps:")
90
- if (projectName !== ".") {
640
+ if (!usingCurrentDirectory) {
91
641
  console.log(` cd ${projectName}`)
92
642
  }
93
- if (options.noInstall) {
94
- console.log(" bun install")
95
- } else {
96
- console.log(" bun install")
97
- }
643
+ console.log(" bun install")
98
644
  console.log(" bun run dev")
99
- } catch (error) {
100
- console.error(error instanceof Error ? error.message : String(error))
101
- process.exit(1)
645
+ } finally {
646
+ rl.close()
102
647
  }
103
648
  }
104
649
 
105
- main()
650
+ main().catch((error) => {
651
+ console.error(error instanceof Error ? error.message : String(error))
652
+ process.exit(1)
653
+ })
package/package.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "name": "create-cascade",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Create a new Cascade TUI project",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/kirosnn/cascade",
9
+ "url": "git+https://github.com/kirosnn/cascade.git",
10
10
  "directory": "packages/create-cascade"
11
11
  },
12
12
  "bin": {
13
- "create-cascade": "./index.js"
13
+ "create-cascade": "index.js"
14
14
  },
15
15
  "files": [
16
16
  "index.js",
17
- "template"
17
+ "README.md"
18
18
  ],
19
19
  "scripts": {
20
- "publish": "bun scripts/publish.ts"
20
+ "publish:pkg": "bun scripts/publish.ts"
21
21
  }
22
22
  }
@@ -1,12 +0,0 @@
1
- {
2
- "name": "cascade-app",
3
- "version": "0.0.1",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "dev": "bun run src/index.ts"
8
- },
9
- "dependencies": {
10
- "@cascadetui/core": "latest"
11
- }
12
- }
@@ -1,12 +0,0 @@
1
- import { createCliRenderer, TextRenderable } from "@cascadetui/core"
2
-
3
- const renderer = createCliRenderer()
4
-
5
- const app = new TextRenderable({
6
- content: "Hello from Cascade",
7
- x: 2,
8
- y: 1,
9
- })
10
-
11
- renderer.root.add(app)
12
- renderer.start()
@@ -1,10 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "module": "ESNext",
5
- "moduleResolution": "Bundler",
6
- "strict": true,
7
- "skipLibCheck": true
8
- },
9
- "include": ["src"]
10
- }