create-cascade 0.1.4 → 0.1.6
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 +8 -7
- package/index.js +203 -59
- 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
|
-
-
|
|
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
|
-
--
|
|
52
|
-
--
|
|
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
|
|
69
|
-
bun create cascade my-app --
|
|
68
|
+
# Scaffold then install
|
|
69
|
+
bun create cascade my-app --install
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
## Behavior
|
|
73
73
|
|
|
74
|
-
-
|
|
75
|
-
-
|
|
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" },
|
|
@@ -14,17 +20,17 @@ const FRAMEWORKS = [
|
|
|
14
20
|
|
|
15
21
|
const STARTERS = {
|
|
16
22
|
core: [
|
|
17
|
-
{ id: "minimal", label: "Minimal", description: "
|
|
23
|
+
{ id: "minimal", label: "Minimal", description: "Welcome panel with quick next steps" },
|
|
18
24
|
{ id: "counter", label: "Counter", description: "Live counter updated every second" },
|
|
19
25
|
{ id: "layout", label: "Layout", description: "Simple boxed layout starter" },
|
|
20
26
|
],
|
|
21
27
|
react: [
|
|
22
|
-
{ id: "minimal", label: "Minimal", description: "
|
|
28
|
+
{ id: "minimal", label: "Minimal", description: "Welcome panel with quick next steps" },
|
|
23
29
|
{ id: "counter", label: "Counter", description: "React state with interval updates" },
|
|
24
30
|
{ id: "login", label: "Login", description: "Small interactive login form" },
|
|
25
31
|
],
|
|
26
32
|
solid: [
|
|
27
|
-
{ id: "minimal", label: "Minimal", description: "
|
|
33
|
+
{ id: "minimal", label: "Minimal", description: "Welcome panel with quick next steps" },
|
|
28
34
|
{ id: "counter", label: "Counter", description: "Solid signal with interval updates" },
|
|
29
35
|
{ id: "input", label: "Input", description: "Basic input and submit interaction" },
|
|
30
36
|
],
|
|
@@ -33,8 +39,8 @@ const STARTERS = {
|
|
|
33
39
|
function parseArgs(argv) {
|
|
34
40
|
const args = argv.slice(2)
|
|
35
41
|
const options = {
|
|
36
|
-
|
|
37
|
-
|
|
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.
|
|
64
|
+
options.install = false
|
|
50
65
|
continue
|
|
51
66
|
}
|
|
52
67
|
if (arg === "--no-start") {
|
|
53
|
-
options.
|
|
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(" --
|
|
88
|
-
console.log(" --
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
139
|
-
if (options.here) {
|
|
140
|
-
return "."
|
|
141
|
-
}
|
|
153
|
+
stdout.write(`${ANSI_BOLD}${label}${ANSI_RESET}\n`)
|
|
142
154
|
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
148
|
-
|
|
164
|
+
stdout.write(`${ANSI_DIM}Use Up/Down arrows and Enter${ANSI_RESET}\n`)
|
|
165
|
+
renderedOnce = true
|
|
149
166
|
}
|
|
150
167
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
157
|
-
|
|
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
|
-
|
|
161
|
-
|
|
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",
|
|
@@ -221,6 +312,8 @@ function getPackageJson(projectName, framework) {
|
|
|
221
312
|
type: "module",
|
|
222
313
|
scripts: {
|
|
223
314
|
dev: `bun run ${entry}`,
|
|
315
|
+
start: `bun run ${entry}`,
|
|
316
|
+
typecheck: "bunx tsc --noEmit",
|
|
224
317
|
},
|
|
225
318
|
dependencies,
|
|
226
319
|
}
|
|
@@ -232,7 +325,10 @@ function getTsConfig(framework) {
|
|
|
232
325
|
module: "ESNext",
|
|
233
326
|
moduleResolution: "Bundler",
|
|
234
327
|
strict: true,
|
|
328
|
+
noEmit: true,
|
|
329
|
+
verbatimModuleSyntax: true,
|
|
235
330
|
skipLibCheck: true,
|
|
331
|
+
types: ["bun-types"],
|
|
236
332
|
}
|
|
237
333
|
|
|
238
334
|
if (framework === "react") {
|
|
@@ -267,17 +363,41 @@ function getTsConfig(framework) {
|
|
|
267
363
|
function getSource(framework, starter) {
|
|
268
364
|
const sources = {
|
|
269
365
|
core: {
|
|
270
|
-
minimal: `import { TextRenderable, createCliRenderer } from "@cascadetui/core"
|
|
366
|
+
minimal: `import { BoxRenderable, TextRenderable, createCliRenderer } from "@cascadetui/core"
|
|
271
367
|
|
|
272
368
|
const renderer = await createCliRenderer({ exitOnCtrlC: true })
|
|
273
369
|
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
370
|
+
const panel = new BoxRenderable(renderer, {
|
|
371
|
+
border: true,
|
|
372
|
+
borderStyle: "single",
|
|
373
|
+
padding: 1,
|
|
374
|
+
margin: 1,
|
|
375
|
+
width: 56,
|
|
376
|
+
height: 8,
|
|
377
|
+
flexDirection: "column",
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
const title = new TextRenderable(renderer, {
|
|
381
|
+
content: "Hello from Cascade Core",
|
|
277
382
|
fg: "#00ff99",
|
|
278
383
|
})
|
|
279
384
|
|
|
280
|
-
renderer
|
|
385
|
+
const subtitle = new TextRenderable(renderer, {
|
|
386
|
+
content: "Run bun run dev after edits to refresh your app",
|
|
387
|
+
marginTop: 1,
|
|
388
|
+
fg: "#cccccc",
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
const hint = new TextRenderable(renderer, {
|
|
392
|
+
content: "Press Ctrl+C to exit",
|
|
393
|
+
marginTop: 1,
|
|
394
|
+
fg: "#999999",
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
panel.add(title)
|
|
398
|
+
panel.add(subtitle)
|
|
399
|
+
panel.add(hint)
|
|
400
|
+
renderer.root.add(panel)
|
|
281
401
|
`,
|
|
282
402
|
counter: `import { TextRenderable, createCliRenderer } from "@cascadetui/core"
|
|
283
403
|
|
|
@@ -332,7 +452,13 @@ renderer.root.add(container)
|
|
|
332
452
|
import { createRoot } from "@cascadetui/react"
|
|
333
453
|
|
|
334
454
|
function App() {
|
|
335
|
-
return
|
|
455
|
+
return (
|
|
456
|
+
<box style={{ border: true, borderStyle: "single", padding: 1, margin: 1, width: 56, height: 8, flexDirection: "column" }}>
|
|
457
|
+
<text content="Hello from Cascade React" fg="#00ff99" />
|
|
458
|
+
<text content="Edit src/index.tsx and run bun run dev" marginTop={1} fg="#cccccc" />
|
|
459
|
+
<text content="Press Ctrl+C to exit" marginTop={1} fg="#999999" />
|
|
460
|
+
</box>
|
|
461
|
+
)
|
|
336
462
|
}
|
|
337
463
|
|
|
338
464
|
const renderer = await createCliRenderer({ exitOnCtrlC: true })
|
|
@@ -405,7 +531,13 @@ createRoot(renderer).render(<App />)
|
|
|
405
531
|
solid: {
|
|
406
532
|
minimal: `import { render } from "@cascadetui/solid"
|
|
407
533
|
|
|
408
|
-
const App = () =>
|
|
534
|
+
const App = () => (
|
|
535
|
+
<box style={{ border: true, borderStyle: "single", padding: 1, margin: 1, width: 56, height: 8, flexDirection: "column" }}>
|
|
536
|
+
<text content="Hello from Cascade Solid" fg="#00ff99" />
|
|
537
|
+
<text content="Edit src/index.tsx and run bun run dev" marginTop={1} fg="#cccccc" />
|
|
538
|
+
<text content="Press Ctrl+C to exit" marginTop={1} fg="#999999" />
|
|
539
|
+
</box>
|
|
540
|
+
)
|
|
409
541
|
|
|
410
542
|
render(App, { exitOnCtrlC: true })
|
|
411
543
|
`,
|
|
@@ -502,13 +634,27 @@ async function main() {
|
|
|
502
634
|
})
|
|
503
635
|
|
|
504
636
|
try {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
637
|
+
let framework
|
|
638
|
+
let projectName
|
|
639
|
+
let location
|
|
640
|
+
let starter
|
|
641
|
+
|
|
642
|
+
if (shouldRunInteractiveWizard(options, positionals)) {
|
|
643
|
+
framework = await resolveFramework(rl, undefined)
|
|
644
|
+
projectName = await resolveProjectName(rl, [], "cascade-app")
|
|
645
|
+
location = await resolveLocation(rl, { ...options, here: false }, [])
|
|
646
|
+
starter = await resolveStarter(rl, framework, undefined)
|
|
647
|
+
} else {
|
|
648
|
+
framework = await resolveFramework(rl, options.framework)
|
|
649
|
+
location = await resolveLocation(rl, options, positionals)
|
|
650
|
+
const defaultName = location === "here" ? basename(process.cwd()) : "cascade-app"
|
|
651
|
+
projectName = await resolveProjectName(rl, positionals, defaultName)
|
|
652
|
+
starter = await resolveStarter(rl, framework, options.starter)
|
|
653
|
+
}
|
|
508
654
|
|
|
509
|
-
const usingCurrentDirectory =
|
|
510
|
-
const targetDir = usingCurrentDirectory ? process.cwd() : resolve(process.cwd(),
|
|
511
|
-
const packageNameSeed =
|
|
655
|
+
const usingCurrentDirectory = location === "here"
|
|
656
|
+
const targetDir = usingCurrentDirectory ? process.cwd() : resolve(process.cwd(), projectName)
|
|
657
|
+
const packageNameSeed = projectName
|
|
512
658
|
|
|
513
659
|
writeProject(targetDir, packageNameSeed, framework, starter)
|
|
514
660
|
|
|
@@ -517,13 +663,13 @@ async function main() {
|
|
|
517
663
|
console.log(`Framework: ${framework}`)
|
|
518
664
|
console.log(`Starter: ${starter}`)
|
|
519
665
|
|
|
520
|
-
if (
|
|
666
|
+
if (options.install) {
|
|
521
667
|
console.log("")
|
|
522
668
|
console.log("Installing dependencies with bun...")
|
|
523
669
|
runCommand("bun", ["install"], targetDir)
|
|
524
670
|
}
|
|
525
671
|
|
|
526
|
-
if (
|
|
672
|
+
if (options.start) {
|
|
527
673
|
console.log("")
|
|
528
674
|
console.log("Starting the project...")
|
|
529
675
|
runCommand("bun", ["run", "dev"], targetDir)
|
|
@@ -533,11 +679,9 @@ async function main() {
|
|
|
533
679
|
console.log("")
|
|
534
680
|
console.log("Next steps:")
|
|
535
681
|
if (!usingCurrentDirectory) {
|
|
536
|
-
console.log(` cd ${
|
|
537
|
-
}
|
|
538
|
-
if (options.noInstall) {
|
|
539
|
-
console.log(" bun install")
|
|
682
|
+
console.log(` cd ${projectName}`)
|
|
540
683
|
}
|
|
684
|
+
console.log(" bun install")
|
|
541
685
|
console.log(" bun run dev")
|
|
542
686
|
} finally {
|
|
543
687
|
rl.close()
|