create-daloy 0.1.17 → 0.1.18
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 +6 -0
- package/bin/create-daloy.mjs +553 -90
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,6 +19,11 @@ The CLI is interactive when arguments are missing. It will ask you for:
|
|
|
19
19
|
- Whether to install dependencies
|
|
20
20
|
- Whether to initialize a git repository
|
|
21
21
|
|
|
22
|
+
Interactive runs use a polished terminal UI with a DaloyJS welcome banner,
|
|
23
|
+
arrow-key template and package-manager pickers, progress indicators, and a
|
|
24
|
+
boxed completion summary. Non-TTY environments and `--yes` mode keep a plain,
|
|
25
|
+
script-friendly transcript with the same decisions and next steps.
|
|
26
|
+
|
|
22
27
|
## Non-interactive usage
|
|
23
28
|
|
|
24
29
|
```bash
|
|
@@ -113,6 +118,7 @@ re-run with `--minimal`, or delete the marked blocks by hand later.
|
|
|
113
118
|
## What the CLI guarantees
|
|
114
119
|
|
|
115
120
|
- Zero runtime dependencies (uses only Node built-ins) for a clean supply-chain footprint.
|
|
121
|
+
- A modern terminal experience with Unicode/color capability detection and ASCII fallbacks.
|
|
116
122
|
- Templates are copied verbatim from this package's `templates/` directory.
|
|
117
123
|
- Files prefixed with `_` are renamed (`_gitignore` → `.gitignore`, `_npmrc` → `.npmrc`) to survive npm packing.
|
|
118
124
|
- pnpm-specific `.npmrc` hardening is kept only when you choose `pnpm`; other package managers get a clean project without unsupported config warnings.
|
package/bin/create-daloy.mjs
CHANGED
|
@@ -69,48 +69,261 @@ const NO_PACKAGE_JSON_TEMPLATES = new Set(["deno-basic"]);
|
|
|
69
69
|
// sentinels.
|
|
70
70
|
const MINIMAL_STRIP_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".mjs", ".cjs", ".md"]);
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
// ----------------------------------------------------------------------------
|
|
73
|
+
// Terminal capability detection + style primitives.
|
|
74
|
+
//
|
|
75
|
+
// Zero runtime dependencies: we hand-roll color, Unicode, and box-drawing
|
|
76
|
+
// helpers so the CLI ships fast and stays auditable.
|
|
77
|
+
// ----------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
const SUPPORTS_COLOR = process.stdout.isTTY && !process.env.NO_COLOR && process.env.TERM !== "dumb";
|
|
80
|
+
const SUPPORTS_TRUECOLOR =
|
|
81
|
+
SUPPORTS_COLOR && (process.env.COLORTERM === "truecolor" || process.env.COLORTERM === "24bit");
|
|
82
|
+
const SUPPORTS_UNICODE =
|
|
83
|
+
process.platform !== "win32" ||
|
|
84
|
+
Boolean(process.env.WT_SESSION) ||
|
|
85
|
+
process.env.TERM_PROGRAM === "vscode" ||
|
|
86
|
+
process.env.TERM === "xterm-256color";
|
|
87
|
+
|
|
88
|
+
const COLORS = SUPPORTS_COLOR
|
|
73
89
|
? {
|
|
74
90
|
reset: "\x1b[0m",
|
|
75
91
|
bold: "\x1b[1m",
|
|
76
92
|
dim: "\x1b[2m",
|
|
93
|
+
italic: "\x1b[3m",
|
|
94
|
+
underline: "\x1b[4m",
|
|
95
|
+
inverse: "\x1b[7m",
|
|
77
96
|
cyan: "\x1b[36m",
|
|
78
97
|
green: "\x1b[32m",
|
|
79
98
|
red: "\x1b[31m",
|
|
80
99
|
yellow: "\x1b[33m",
|
|
100
|
+
magenta: "\x1b[35m",
|
|
101
|
+
gray: "\x1b[90m",
|
|
102
|
+
white: "\x1b[97m",
|
|
81
103
|
}
|
|
82
|
-
: {
|
|
104
|
+
: {
|
|
105
|
+
reset: "",
|
|
106
|
+
bold: "",
|
|
107
|
+
dim: "",
|
|
108
|
+
italic: "",
|
|
109
|
+
underline: "",
|
|
110
|
+
inverse: "",
|
|
111
|
+
cyan: "",
|
|
112
|
+
green: "",
|
|
113
|
+
red: "",
|
|
114
|
+
yellow: "",
|
|
115
|
+
magenta: "",
|
|
116
|
+
gray: "",
|
|
117
|
+
white: "",
|
|
118
|
+
};
|
|
83
119
|
|
|
84
120
|
function color(code, s) {
|
|
85
121
|
return `${code}${s}${COLORS.reset}`;
|
|
86
122
|
}
|
|
87
123
|
|
|
124
|
+
function rgb(r, g, b) {
|
|
125
|
+
if (!SUPPORTS_TRUECOLOR) return "";
|
|
126
|
+
return `\x1b[38;2;${r};${g};${b}m`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function stringWidth(s) {
|
|
130
|
+
let width = 0;
|
|
131
|
+
for (const char of s.replace(/\x1b\[[0-9;]*m/g, "")) {
|
|
132
|
+
const code = char.codePointAt(0) ?? 0;
|
|
133
|
+
if (code < 0x20 || (code >= 0x7f && code < 0xa0)) continue;
|
|
134
|
+
if (
|
|
135
|
+
code >= 0x1100 &&
|
|
136
|
+
(code <= 0x115f ||
|
|
137
|
+
code === 0x2329 ||
|
|
138
|
+
code === 0x232a ||
|
|
139
|
+
(code >= 0x2e80 && code <= 0xa4cf && code !== 0x303f) ||
|
|
140
|
+
(code >= 0xac00 && code <= 0xd7a3) ||
|
|
141
|
+
(code >= 0xf900 && code <= 0xfaff) ||
|
|
142
|
+
(code >= 0xfe10 && code <= 0xfe19) ||
|
|
143
|
+
(code >= 0xfe30 && code <= 0xfe6f) ||
|
|
144
|
+
(code >= 0xff00 && code <= 0xff60) ||
|
|
145
|
+
(code >= 0xffe0 && code <= 0xffe6) ||
|
|
146
|
+
(code >= 0x1f300 && code <= 0x1faff))
|
|
147
|
+
) {
|
|
148
|
+
width += 2;
|
|
149
|
+
} else {
|
|
150
|
+
width += 1;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return width;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Unicode/ASCII glyphs used throughout the prompt UI. Symbols mirror Clack
|
|
157
|
+
// and Astro's `create` flows so users get the familiar vertical-rail
|
|
158
|
+
// experience, with safe ASCII fallbacks for legacy terminals.
|
|
159
|
+
const SYMBOLS = SUPPORTS_UNICODE
|
|
160
|
+
? {
|
|
161
|
+
stepActive: "\u25C6", // ◆
|
|
162
|
+
stepDone: "\u25C7", // ◇
|
|
163
|
+
radioOff: "\u25CB", // ○
|
|
164
|
+
radioOn: "\u25C9", // ◉
|
|
165
|
+
success: "\u2714", // ✔
|
|
166
|
+
warn: "\u26A0", // ⚠
|
|
167
|
+
error: "\u2716", // ✖
|
|
168
|
+
info: "\u2139", // ℹ
|
|
169
|
+
bar: "\u2502", // │
|
|
170
|
+
arrow: "\u2192", // →
|
|
171
|
+
pointer: "\u276F", // ❯
|
|
172
|
+
sparkle: "\u2728", // ✨
|
|
173
|
+
star: "\u2605", // ★
|
|
174
|
+
cornerTL: "\u256D", // ╭
|
|
175
|
+
cornerTR: "\u256E", // ╮
|
|
176
|
+
cornerBL: "\u2570", // ╰
|
|
177
|
+
cornerBR: "\u256F", // ╯
|
|
178
|
+
lineH: "\u2500", // ─
|
|
179
|
+
lineV: "\u2502", // │
|
|
180
|
+
}
|
|
181
|
+
: {
|
|
182
|
+
stepActive: "*",
|
|
183
|
+
stepDone: "o",
|
|
184
|
+
radioOff: "( )",
|
|
185
|
+
radioOn: "(*)",
|
|
186
|
+
success: "v",
|
|
187
|
+
warn: "!",
|
|
188
|
+
error: "x",
|
|
189
|
+
info: "i",
|
|
190
|
+
bar: "|",
|
|
191
|
+
arrow: ">",
|
|
192
|
+
pointer: ">",
|
|
193
|
+
sparkle: "*",
|
|
194
|
+
star: "*",
|
|
195
|
+
cornerTL: "+",
|
|
196
|
+
cornerTR: "+",
|
|
197
|
+
cornerBL: "+",
|
|
198
|
+
cornerBR: "+",
|
|
199
|
+
lineH: "-",
|
|
200
|
+
lineV: "|",
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// Pretty rail-printer. All interactive prompts and status lines flow through
|
|
204
|
+
// these helpers so the column with the vertical bar stays aligned.
|
|
205
|
+
const BAR = color(COLORS.gray, SYMBOLS.bar);
|
|
206
|
+
|
|
207
|
+
function printIntro(title) {
|
|
208
|
+
console.log(`${color(COLORS.cyan, SYMBOLS.cornerTL + SYMBOLS.lineH)} ${color(COLORS.bold, title)}`);
|
|
209
|
+
console.log(BAR);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function printOutro(text) {
|
|
213
|
+
console.log(`${color(COLORS.cyan, SYMBOLS.cornerBL + SYMBOLS.lineH)} ${text}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function printRailLine(text = "") {
|
|
217
|
+
console.log(`${BAR} ${text}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function printRailGap() {
|
|
221
|
+
console.log(BAR);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Render a horizontally-bounded box with a title bar. Used for the welcome
|
|
225
|
+
// banner and the final "Next steps" outro.
|
|
226
|
+
function renderBox(lines, options = {}) {
|
|
227
|
+
const innerPadding = 2;
|
|
228
|
+
const contentWidth = Math.max(40, ...lines.map((line) => stringWidth(line)));
|
|
229
|
+
const horizontal = SYMBOLS.lineH.repeat(contentWidth + innerPadding * 2);
|
|
230
|
+
const accent = options.accent ?? COLORS.cyan;
|
|
231
|
+
const top = color(accent, `${SYMBOLS.cornerTL}${horizontal}${SYMBOLS.cornerTR}`);
|
|
232
|
+
const bottom = color(accent, `${SYMBOLS.cornerBL}${horizontal}${SYMBOLS.cornerBR}`);
|
|
233
|
+
const out = [top];
|
|
234
|
+
for (const line of lines) {
|
|
235
|
+
const padding = " ".repeat(Math.max(0, contentWidth - stringWidth(line)));
|
|
236
|
+
out.push(`${color(accent, SYMBOLS.lineV)}${" ".repeat(innerPadding)}${line}${padding}${" ".repeat(innerPadding)}${color(accent, SYMBOLS.lineV)}`);
|
|
237
|
+
}
|
|
238
|
+
out.push(bottom);
|
|
239
|
+
return out.join("\n");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Block-letter "DALOY" banner with a left-to-right cyan→magenta gradient when
|
|
243
|
+
// the terminal supports truecolor. Falls back to a single bold-cyan line on
|
|
244
|
+
// non-truecolor TTYs and to plain text in dumb terminals. The shape is built
|
|
245
|
+
// from half-block characters so it stays compact (2 lines tall).
|
|
246
|
+
const LOGO_LINES = [
|
|
247
|
+
" \u2588\u2580\u2584 \u2584\u2580\u2588 \u2588 \u2588\u2580\u2588 \u2588 \u2588 ",
|
|
248
|
+
" \u2588\u2584\u2580 \u2588\u2580\u2588 \u2588\u2584\u2584 \u2588\u2584\u2588 \u2580\u2584\u2580 ",
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
function gradientLine(line, startRgb, endRgb) {
|
|
252
|
+
if (!SUPPORTS_TRUECOLOR) return color(COLORS.cyan, line);
|
|
253
|
+
const chars = [...line];
|
|
254
|
+
const max = Math.max(1, chars.length - 1);
|
|
255
|
+
let out = "";
|
|
256
|
+
for (let i = 0; i < chars.length; i += 1) {
|
|
257
|
+
const ratio = i / max;
|
|
258
|
+
const r = Math.round(startRgb[0] + (endRgb[0] - startRgb[0]) * ratio);
|
|
259
|
+
const g = Math.round(startRgb[1] + (endRgb[1] - startRgb[1]) * ratio);
|
|
260
|
+
const b = Math.round(startRgb[2] + (endRgb[2] - startRgb[2]) * ratio);
|
|
261
|
+
out += `${rgb(r, g, b)}${chars[i]}`;
|
|
262
|
+
}
|
|
263
|
+
return `${out}${COLORS.reset}`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function printBanner(version) {
|
|
267
|
+
if (!SUPPORTS_UNICODE) {
|
|
268
|
+
console.log(`\n${color(COLORS.bold, "create-daloy")} ${color(COLORS.dim, `v${version}`)}`);
|
|
269
|
+
console.log(color(COLORS.dim, "Contract-first REST APIs for Node, Bun, Deno, Vercel Edge, and Workers"));
|
|
270
|
+
console.log(color(COLORS.dim, "https://daloyjs.dev\n"));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const start = [56, 189, 248]; // sky-400
|
|
274
|
+
const end = [217, 70, 239]; // fuchsia-500
|
|
275
|
+
console.log("");
|
|
276
|
+
for (const line of LOGO_LINES) {
|
|
277
|
+
console.log(` ${gradientLine(line, start, end)}`);
|
|
278
|
+
}
|
|
279
|
+
// Build the welcome content lines (each contains its own ANSI color codes).
|
|
280
|
+
const headline = `${color(COLORS.bold, "Welcome to DaloyJS")} ${color(COLORS.gray, `\u2014 v${version}`)}`;
|
|
281
|
+
const subline = color(COLORS.dim, "Contract-first REST APIs for Node, Bun, Deno, Vercel Edge, and Workers.");
|
|
282
|
+
const docs = `${color(COLORS.gray, "docs:")} ${color(COLORS.cyan, "https://daloyjs.dev/docs")}`;
|
|
283
|
+
console.log("");
|
|
284
|
+
console.log(renderBox([headline, subline, "", docs]));
|
|
285
|
+
console.log("");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
|
|
88
289
|
function printHelp() {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
--
|
|
101
|
-
--
|
|
102
|
-
--
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
290
|
+
const heading = (text) => color(COLORS.bold + COLORS.cyan, text);
|
|
291
|
+
console.log(`
|
|
292
|
+
${color(COLORS.bold, "create-daloy")} ${color(COLORS.dim, "\u2014 scaffold a DaloyJS project")}
|
|
293
|
+
|
|
294
|
+
${heading("Usage")}
|
|
295
|
+
${color(COLORS.cyan, "pnpm")} create daloy@latest ${color(COLORS.dim, "[project-name] [options]")}
|
|
296
|
+
${color(COLORS.cyan, "npm")} create daloy@latest ${color(COLORS.dim, "[project-name] [options]")}
|
|
297
|
+
${color(COLORS.cyan, "yarn")} create daloy ${color(COLORS.dim, "[project-name] [options]")}
|
|
298
|
+
${color(COLORS.cyan, "bun")} create daloy ${color(COLORS.dim, "[project-name] [options]")}
|
|
299
|
+
|
|
300
|
+
${heading("Options")}
|
|
301
|
+
${color(COLORS.green, "--template <name>")} ${TEMPLATES.join(" | ")} ${color(COLORS.dim, "(default: node-basic)")}
|
|
302
|
+
${color(COLORS.green, "--package-manager <pm>")} ${PACKAGE_MANAGERS.join(" | ")} ${color(COLORS.dim, "(default: pnpm)")}
|
|
303
|
+
${color(COLORS.green, "--list-templates")} Print available templates and exit.
|
|
304
|
+
${color(COLORS.green, "--install / --no-install")} Install dependencies after scaffolding.
|
|
305
|
+
${color(COLORS.green, "--git / --no-git")} Initialize a git repository.
|
|
306
|
+
${color(COLORS.green, "--minimal")} Strip the bookstore + Swagger/OpenAPI demo routes.
|
|
307
|
+
${color(COLORS.green, "--force")} Overwrite an existing non-empty directory.
|
|
308
|
+
${color(COLORS.green, "--yes, -y")} Accept all defaults; never prompt.
|
|
309
|
+
${color(COLORS.green, "--help, -h")} Print this help.
|
|
310
|
+
${color(COLORS.green, "--version, -v")} Print version.
|
|
311
|
+
|
|
312
|
+
${heading("Docs")} ${color(COLORS.cyan, "https://daloyjs.dev/docs")}
|
|
106
313
|
`);
|
|
107
314
|
}
|
|
108
315
|
|
|
109
316
|
function printTemplates() {
|
|
110
|
-
console.log(
|
|
317
|
+
console.log("");
|
|
318
|
+
console.log(`${color(COLORS.cyan, SYMBOLS.sparkle)} ${color(COLORS.bold, "Available DaloyJS templates")}`);
|
|
319
|
+
console.log("");
|
|
320
|
+
const valueWidth = Math.max(...TEMPLATE_OPTIONS.map((option) => option.value.length));
|
|
111
321
|
for (const option of TEMPLATE_OPTIONS) {
|
|
112
|
-
|
|
113
|
-
|
|
322
|
+
const value = color(COLORS.cyan, option.value.padEnd(valueWidth));
|
|
323
|
+
const title = color(COLORS.bold, option.title);
|
|
324
|
+
console.log(` ${color(COLORS.gray, SYMBOLS.pointer)} ${value} ${title}`);
|
|
325
|
+
console.log(` ${color(COLORS.dim, option.description)}`);
|
|
326
|
+
console.log("");
|
|
114
327
|
}
|
|
115
328
|
}
|
|
116
329
|
|
|
@@ -157,7 +370,7 @@ function parseArgs(argv) {
|
|
|
157
370
|
else if (a?.startsWith("--pm=")) out.packageManager = a.slice("--pm=".length);
|
|
158
371
|
else if (a && !a.startsWith("-") && out.projectName === undefined) out.projectName = a;
|
|
159
372
|
else if (a) {
|
|
160
|
-
|
|
373
|
+
logError(`Unknown argument: ${a}`);
|
|
161
374
|
process.exit(1);
|
|
162
375
|
}
|
|
163
376
|
}
|
|
@@ -360,17 +573,59 @@ function run(cmd, args, cwd) {
|
|
|
360
573
|
});
|
|
361
574
|
}
|
|
362
575
|
|
|
576
|
+
// Same as `run`, but captures output so the spinner can stay clean. The
|
|
577
|
+
// transcript tail is returned so callers can replay useful failure context
|
|
578
|
+
// without buffering unbounded package-manager output in memory.
|
|
579
|
+
function runQuiet(cmd, args, cwd) {
|
|
580
|
+
return new Promise((resolve) => {
|
|
581
|
+
const maxOutputBytes = 64 * 1024;
|
|
582
|
+
let output = "";
|
|
583
|
+
const appendOutput = (chunk) => {
|
|
584
|
+
output += chunk.toString("utf8");
|
|
585
|
+
if (output.length > maxOutputBytes) output = output.slice(-maxOutputBytes);
|
|
586
|
+
};
|
|
587
|
+
const proc = spawn(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"], shell: process.platform === "win32" });
|
|
588
|
+
proc.stdout.on("data", appendOutput);
|
|
589
|
+
proc.stderr.on("data", appendOutput);
|
|
590
|
+
proc.on("exit", (code) => resolve({ code: code ?? 0, output }));
|
|
591
|
+
proc.on("error", (err) => resolve({ code: 1, output: String(err?.message ?? err) }));
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// ----------------------------------------------------------------------------
|
|
596
|
+
// Prompt primitives.
|
|
597
|
+
//
|
|
598
|
+
// `ask`/`askYesNo` use readline for resilience (paste, history, multi-line
|
|
599
|
+
// input). `askChoice` upgrades to raw-mode arrow-key navigation when stdin is
|
|
600
|
+
// a TTY, with a numbered fallback used by the readline-driven tests.
|
|
601
|
+
// ----------------------------------------------------------------------------
|
|
602
|
+
|
|
603
|
+
function printPromptHeader(question) {
|
|
604
|
+
console.log(`${color(COLORS.cyan, SYMBOLS.stepActive)} ${color(COLORS.bold, question)}`);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function printPromptResult(question, value) {
|
|
608
|
+
console.log(`${color(COLORS.green, SYMBOLS.stepDone)} ${question} ${color(COLORS.dim, SYMBOLS.arrow)} ${color(COLORS.cyan, value)}`);
|
|
609
|
+
}
|
|
610
|
+
|
|
363
611
|
async function ask(rl, question, defaultValue) {
|
|
364
|
-
|
|
365
|
-
const
|
|
366
|
-
|
|
612
|
+
printPromptHeader(question);
|
|
613
|
+
const hint = defaultValue !== undefined ? color(COLORS.dim, ` (default: ${defaultValue})`) : "";
|
|
614
|
+
const answer = (await rl.question(`${BAR} ${color(COLORS.gray, SYMBOLS.pointer)}${hint} `)).trim();
|
|
615
|
+
const value = answer.length === 0 ? defaultValue : answer;
|
|
616
|
+
// readline already echoed the prompt + answer line; emit a final summary
|
|
617
|
+
// line on the rail so the transcript reads cleanly after scroll-back.
|
|
618
|
+
printRailGap();
|
|
619
|
+
return value;
|
|
367
620
|
}
|
|
368
621
|
|
|
369
622
|
async function askYesNo(rl, question, defaultYes) {
|
|
623
|
+
printPromptHeader(question);
|
|
370
624
|
const def = defaultYes ? "Y/n" : "y/N";
|
|
371
|
-
const answer = (await rl.question(`${color(COLORS.
|
|
625
|
+
const answer = (await rl.question(`${BAR} ${color(COLORS.gray, SYMBOLS.pointer)} ${color(COLORS.dim, `(${def})`)} `))
|
|
372
626
|
.trim()
|
|
373
627
|
.toLowerCase();
|
|
628
|
+
printRailGap();
|
|
374
629
|
if (answer.length === 0) return defaultYes;
|
|
375
630
|
return answer === "y" || answer === "yes";
|
|
376
631
|
}
|
|
@@ -379,68 +634,264 @@ function optionValue(option) {
|
|
|
379
634
|
return typeof option === "string" ? option : option.value;
|
|
380
635
|
}
|
|
381
636
|
|
|
382
|
-
function
|
|
383
|
-
return typeof option === "string" ? option :
|
|
637
|
+
function optionTitle(option) {
|
|
638
|
+
return typeof option === "string" ? option : option.title;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function optionDescription(option) {
|
|
642
|
+
return typeof option === "string" ? "" : option.description ?? "";
|
|
384
643
|
}
|
|
385
644
|
|
|
645
|
+
// Arrow-key powered choice prompt. Falls back to numbered input whenever raw
|
|
646
|
+
// mode is unavailable (CI, piped stdin, integration tests).
|
|
386
647
|
async function askChoice(rl, question, choices, defaultChoice) {
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
const
|
|
648
|
+
const canRawMode = process.stdin.isTTY && typeof process.stdin.setRawMode === "function";
|
|
649
|
+
if (!canRawMode) return askChoiceNumbered(rl, question, choices, defaultChoice);
|
|
650
|
+
|
|
651
|
+
printPromptHeader(question);
|
|
652
|
+
printRailLine(color(COLORS.dim, `Use \u2191 \u2193 to navigate, Enter to confirm, type a number to jump.`));
|
|
653
|
+
|
|
654
|
+
let index = Math.max(
|
|
655
|
+
0,
|
|
656
|
+
choices.findIndex((choice) => optionValue(choice) === defaultChoice),
|
|
657
|
+
);
|
|
658
|
+
const titleWidth = Math.max(...choices.map((choice) => optionTitle(choice).length));
|
|
659
|
+
const valueWidth = Math.max(...choices.map((choice) => optionValue(choice).length));
|
|
660
|
+
|
|
661
|
+
function render(active) {
|
|
662
|
+
return choices
|
|
663
|
+
.map((choice, i) => {
|
|
664
|
+
const isActive = i === active;
|
|
665
|
+
const isDefault = optionValue(choice) === defaultChoice;
|
|
666
|
+
const marker = isActive ? color(COLORS.cyan, SYMBOLS.radioOn) : color(COLORS.gray, SYMBOLS.radioOff);
|
|
667
|
+
const titleRaw = optionTitle(choice).padEnd(titleWidth);
|
|
668
|
+
const valueRaw = optionValue(choice).padEnd(valueWidth);
|
|
669
|
+
const title = isActive ? color(COLORS.bold + COLORS.cyan, titleRaw) : color(COLORS.white, titleRaw);
|
|
670
|
+
const value = color(COLORS.dim, `(${valueRaw})`);
|
|
671
|
+
const description = optionDescription(choice);
|
|
672
|
+
const descColored = isActive ? color(COLORS.cyan, description) : color(COLORS.dim, description);
|
|
673
|
+
const recommended = isDefault ? color(COLORS.green, ` ${SYMBOLS.star} recommended`) : "";
|
|
674
|
+
return `${BAR} ${marker} ${title} ${value} ${descColored}${recommended}`;
|
|
675
|
+
})
|
|
676
|
+
.join("\n");
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Pause readline so it doesn't fight us for stdin while we're in raw mode.
|
|
680
|
+
rl.pause();
|
|
681
|
+
process.stdin.setRawMode(true);
|
|
682
|
+
process.stdin.resume();
|
|
683
|
+
process.stdin.setEncoding("utf8");
|
|
684
|
+
|
|
685
|
+
// Initial render
|
|
686
|
+
process.stdout.write(render(index) + "\n");
|
|
687
|
+
|
|
688
|
+
const result = await new Promise((resolve, reject) => {
|
|
689
|
+
function rerender(newIndex) {
|
|
690
|
+
// Move cursor up `choices.length` lines, clear them, redraw.
|
|
691
|
+
process.stdout.write(`\x1b[${choices.length}A`);
|
|
692
|
+
for (let i = 0; i < choices.length; i += 1) process.stdout.write("\x1b[2K\n");
|
|
693
|
+
process.stdout.write(`\x1b[${choices.length}A`);
|
|
694
|
+
process.stdout.write(render(newIndex) + "\n");
|
|
695
|
+
}
|
|
696
|
+
function cleanup() {
|
|
697
|
+
process.stdin.removeListener("data", onData);
|
|
698
|
+
process.stdin.setRawMode(false);
|
|
699
|
+
process.stdin.pause();
|
|
700
|
+
}
|
|
701
|
+
function onData(chunk) {
|
|
702
|
+
const data = chunk.toString();
|
|
703
|
+
// Ctrl+C / Ctrl+D — abort cleanly
|
|
704
|
+
if (data === "\u0003" || data === "\u0004") {
|
|
705
|
+
cleanup();
|
|
706
|
+
process.stdout.write("\n");
|
|
707
|
+
reject(new Error("Cancelled"));
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
// Enter
|
|
711
|
+
if (data === "\r" || data === "\n") {
|
|
712
|
+
cleanup();
|
|
713
|
+
resolve(optionValue(choices[index]));
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
// Number shortcut (1..9)
|
|
717
|
+
if (/^[1-9]$/.test(data)) {
|
|
718
|
+
const n = Number.parseInt(data, 10);
|
|
719
|
+
if (n >= 1 && n <= choices.length) {
|
|
720
|
+
index = n - 1;
|
|
721
|
+
rerender(index);
|
|
722
|
+
}
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
// Arrow keys / vim keys
|
|
726
|
+
if (data === "\u001b[A" || data === "k") {
|
|
727
|
+
index = (index - 1 + choices.length) % choices.length;
|
|
728
|
+
rerender(index);
|
|
729
|
+
} else if (data === "\u001b[B" || data === "j") {
|
|
730
|
+
index = (index + 1) % choices.length;
|
|
731
|
+
rerender(index);
|
|
732
|
+
} else if (data === "\u001b[H") {
|
|
733
|
+
index = 0;
|
|
734
|
+
rerender(index);
|
|
735
|
+
} else if (data === "\u001b[F") {
|
|
736
|
+
index = choices.length - 1;
|
|
737
|
+
rerender(index);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
process.stdin.on("data", onData);
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
// Replace the rendered list with a single confirmation line.
|
|
744
|
+
// Rendered block was choices.length lines; we also printed the hint line
|
|
745
|
+
// above the list. Move up and clear them.
|
|
746
|
+
const linesToClear = choices.length + 1; // hint + list
|
|
747
|
+
process.stdout.write(`\x1b[${linesToClear}A`);
|
|
748
|
+
for (let i = 0; i < linesToClear; i += 1) process.stdout.write("\x1b[2K\n");
|
|
749
|
+
process.stdout.write(`\x1b[${linesToClear}A`);
|
|
750
|
+
|
|
751
|
+
// Also clear the prompt header we printed at the very top.
|
|
752
|
+
process.stdout.write("\x1b[1A\x1b[2K");
|
|
753
|
+
|
|
754
|
+
printPromptResult(question, result);
|
|
755
|
+
printRailGap();
|
|
756
|
+
rl.resume();
|
|
757
|
+
return result;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
async function askChoiceNumbered(rl, question, choices, defaultChoice) {
|
|
761
|
+
printPromptHeader(question);
|
|
762
|
+
const titleWidth = Math.max(...choices.map((choice) => optionTitle(choice).length));
|
|
763
|
+
for (let i = 0; i < choices.length; i += 1) {
|
|
764
|
+
const choice = choices[i];
|
|
765
|
+
const isDefault = optionValue(choice) === defaultChoice;
|
|
766
|
+
const idx = color(COLORS.dim, `${String(i + 1).padStart(2, " ")})`);
|
|
767
|
+
const title = color(COLORS.white, optionTitle(choice).padEnd(titleWidth));
|
|
768
|
+
const value = color(COLORS.dim, `(${optionValue(choice)})`);
|
|
769
|
+
const description = color(COLORS.dim, optionDescription(choice));
|
|
770
|
+
const recommended = isDefault ? color(COLORS.green, ` ${SYMBOLS.star} recommended`) : "";
|
|
771
|
+
printRailLine(`${idx} ${title} ${value} ${description}${recommended}`);
|
|
772
|
+
}
|
|
773
|
+
const raw = (await rl.question(`${BAR} ${color(COLORS.gray, SYMBOLS.pointer)} `)).trim();
|
|
774
|
+
printRailGap();
|
|
399
775
|
if (raw.length === 0) return defaultChoice;
|
|
400
776
|
const asNumber = Number.parseInt(raw, 10);
|
|
401
777
|
if (Number.isInteger(asNumber) && asNumber >= 1 && asNumber <= choices.length) {
|
|
402
778
|
return optionValue(choices[asNumber - 1]);
|
|
403
779
|
}
|
|
404
780
|
if (choices.some((choice) => optionValue(choice) === raw)) return raw;
|
|
405
|
-
console.error(color(COLORS.red, `Invalid choice. Pick one of: ${choices.map(optionValue).join(", ")}`));
|
|
406
|
-
return
|
|
781
|
+
console.error(`${BAR} ${color(COLORS.red, `Invalid choice. Pick one of: ${choices.map(optionValue).join(", ")}`)}`);
|
|
782
|
+
return askChoiceNumbered(rl, question, choices, defaultChoice);
|
|
407
783
|
}
|
|
408
784
|
|
|
409
785
|
function logStep(message, detail) {
|
|
410
|
-
const suffix = detail ? color(COLORS.dim, ` ${detail}`) : "";
|
|
411
|
-
console.log(`${color(COLORS.green,
|
|
786
|
+
const suffix = detail ? color(COLORS.dim, ` \u2014 ${detail}`) : "";
|
|
787
|
+
console.log(`${color(COLORS.green, SYMBOLS.success)} ${message}${suffix}`);
|
|
412
788
|
}
|
|
413
789
|
|
|
414
790
|
function logWarn(message) {
|
|
415
|
-
console.warn(`${color(COLORS.yellow,
|
|
791
|
+
console.warn(`${color(COLORS.yellow, SYMBOLS.warn)} ${color(COLORS.yellow, message)}`);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function logError(message) {
|
|
795
|
+
console.error(`${color(COLORS.red, SYMBOLS.error)} ${color(COLORS.red, message)}`);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// ----------------------------------------------------------------------------
|
|
799
|
+
// Spinner — tiny braille animation for long-running steps (e.g. install).
|
|
800
|
+
// ----------------------------------------------------------------------------
|
|
801
|
+
|
|
802
|
+
const SPINNER_FRAMES = SUPPORTS_UNICODE
|
|
803
|
+
? ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"]
|
|
804
|
+
: ["|", "/", "-", "\\"];
|
|
805
|
+
|
|
806
|
+
function createSpinner(initialMessage) {
|
|
807
|
+
let message = initialMessage;
|
|
808
|
+
let frame = 0;
|
|
809
|
+
let timer = null;
|
|
810
|
+
let active = false;
|
|
811
|
+
function render() {
|
|
812
|
+
if (!process.stdout.isTTY) return;
|
|
813
|
+
process.stdout.write(`\r\x1b[2K${color(COLORS.cyan, SPINNER_FRAMES[frame])} ${message}`);
|
|
814
|
+
frame = (frame + 1) % SPINNER_FRAMES.length;
|
|
815
|
+
}
|
|
816
|
+
return {
|
|
817
|
+
start(text) {
|
|
818
|
+
if (text) message = text;
|
|
819
|
+
active = true;
|
|
820
|
+
if (process.stdout.isTTY) {
|
|
821
|
+
timer = setInterval(render, 80);
|
|
822
|
+
render();
|
|
823
|
+
} else {
|
|
824
|
+
console.log(`${color(COLORS.cyan, SYMBOLS.stepActive)} ${message}`);
|
|
825
|
+
}
|
|
826
|
+
},
|
|
827
|
+
update(text) {
|
|
828
|
+
message = text;
|
|
829
|
+
if (active && process.stdout.isTTY) render();
|
|
830
|
+
},
|
|
831
|
+
stop(text, ok = true) {
|
|
832
|
+
if (timer) clearInterval(timer);
|
|
833
|
+
timer = null;
|
|
834
|
+
active = false;
|
|
835
|
+
const symbol = ok
|
|
836
|
+
? color(COLORS.green, SYMBOLS.success)
|
|
837
|
+
: color(COLORS.red, SYMBOLS.error);
|
|
838
|
+
const finalMessage = text ?? message;
|
|
839
|
+
if (process.stdout.isTTY) {
|
|
840
|
+
process.stdout.write(`\r\x1b[2K${symbol} ${finalMessage}\n`);
|
|
841
|
+
} else {
|
|
842
|
+
console.log(`${symbol} ${finalMessage}`);
|
|
843
|
+
}
|
|
844
|
+
},
|
|
845
|
+
};
|
|
416
846
|
}
|
|
417
847
|
|
|
418
848
|
function printSummary({ projectName, template, packageManager, installDeps, skipPackageManager }) {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
849
|
+
const templateMeta = TEMPLATE_OPTIONS.find((option) => option.value === template);
|
|
850
|
+
const templateLabel = templateMeta ? `${templateMeta.title} ${color(COLORS.dim, `(${template})`)}` : template;
|
|
851
|
+
const summaryLines = [
|
|
852
|
+
`${color(COLORS.green, SYMBOLS.sparkle)} ${color(COLORS.bold, "Your DaloyJS project is ready!")}`,
|
|
853
|
+
"",
|
|
854
|
+
`${color(COLORS.gray, "Project ")} ${color(COLORS.bold, projectName)}`,
|
|
855
|
+
`${color(COLORS.gray, "Template ")} ${templateLabel}`,
|
|
856
|
+
];
|
|
857
|
+
if (skipPackageManager) {
|
|
858
|
+
summaryLines.push(`${color(COLORS.gray, "Runtime ")} ${color(COLORS.cyan, template === "deno-basic" ? "Deno" : "runtime")}`);
|
|
859
|
+
} else {
|
|
860
|
+
summaryLines.push(`${color(COLORS.gray, "Manager ")} ${color(COLORS.cyan, packageManager)}`);
|
|
861
|
+
}
|
|
862
|
+
console.log("");
|
|
863
|
+
console.log(renderBox(summaryLines, { accent: COLORS.green }));
|
|
864
|
+
console.log("");
|
|
865
|
+
|
|
866
|
+
const arrow = color(COLORS.cyan, SYMBOLS.arrow);
|
|
867
|
+
console.log(`${color(COLORS.bold, "Next steps")}`);
|
|
868
|
+
console.log(` ${arrow} ${color(COLORS.cyan, `cd ${projectName}`)}`);
|
|
869
|
+
if (skipPackageManager) {
|
|
870
|
+
console.log(` ${arrow} ${color(COLORS.cyan, "deno task dev")}`);
|
|
871
|
+
} else {
|
|
872
|
+
if (!installDeps) console.log(` ${arrow} ${color(COLORS.cyan, `${packageManager} install`)}`);
|
|
873
|
+
console.log(` ${arrow} ${color(COLORS.cyan, `${packageManager} run dev`)}`);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
console.log("");
|
|
877
|
+
console.log(`${color(COLORS.bold, "Useful commands")}`);
|
|
422
878
|
if (skipPackageManager) {
|
|
423
|
-
console.log(` ${color(COLORS.
|
|
424
|
-
console.log(
|
|
425
|
-
console.log(`
|
|
426
|
-
console.log(` deno task dev`);
|
|
427
|
-
console.log(`\n ${color(COLORS.bold, "Useful commands")}`);
|
|
428
|
-
console.log(` deno task typecheck`);
|
|
429
|
-
console.log(` deno task test`);
|
|
430
|
-
console.log(` deno task gen:openapi`);
|
|
879
|
+
console.log(` ${color(COLORS.gray, SYMBOLS.pointer)} ${color(COLORS.dim, "deno task typecheck")}`);
|
|
880
|
+
console.log(` ${color(COLORS.gray, SYMBOLS.pointer)} ${color(COLORS.dim, "deno task test")}`);
|
|
881
|
+
console.log(` ${color(COLORS.gray, SYMBOLS.pointer)} ${color(COLORS.dim, "deno task gen:openapi")}`);
|
|
431
882
|
} else {
|
|
432
|
-
console.log(` ${color(COLORS.
|
|
433
|
-
console.log(
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
console.log(`\n ${color(COLORS.bold, "Useful commands")}`);
|
|
438
|
-
console.log(` ${packageManager} run typecheck`);
|
|
439
|
-
console.log(` ${packageManager} test`);
|
|
440
|
-
if (template === "node-basic" || template === "bun-basic") console.log(` ${packageManager} run gen`);
|
|
883
|
+
console.log(` ${color(COLORS.gray, SYMBOLS.pointer)} ${color(COLORS.dim, `${packageManager} run typecheck`)}`);
|
|
884
|
+
console.log(` ${color(COLORS.gray, SYMBOLS.pointer)} ${color(COLORS.dim, `${packageManager} test`)}`);
|
|
885
|
+
if (template === "node-basic" || template === "bun-basic") {
|
|
886
|
+
console.log(` ${color(COLORS.gray, SYMBOLS.pointer)} ${color(COLORS.dim, `${packageManager} run gen`)}`);
|
|
887
|
+
}
|
|
441
888
|
}
|
|
442
|
-
|
|
443
|
-
console.log(
|
|
889
|
+
|
|
890
|
+
console.log("");
|
|
891
|
+
console.log(`${color(COLORS.gray, "Docs:")} ${color(COLORS.cyan, "https://daloyjs.dev/docs")}`);
|
|
892
|
+
console.log(`${color(COLORS.gray, "Issues:")} ${color(COLORS.cyan, "https://github.com/daloyjs/daloy/issues")}`);
|
|
893
|
+
console.log("");
|
|
894
|
+
console.log(`${color(COLORS.magenta, SYMBOLS.sparkle)} ${color(COLORS.bold, "Happy shipping!")}\n`);
|
|
444
895
|
}
|
|
445
896
|
|
|
446
897
|
async function main() {
|
|
@@ -459,14 +910,16 @@ async function main() {
|
|
|
459
910
|
process.exit(0);
|
|
460
911
|
}
|
|
461
912
|
|
|
462
|
-
|
|
463
|
-
console.log(color(COLORS.dim, "Contract-first REST APIs for Node, Vercel Edge, and Workers"));
|
|
464
|
-
console.log(color(COLORS.dim, "https://daloyjs.dev\n"));
|
|
913
|
+
printBanner(await readPkgVersion());
|
|
465
914
|
|
|
466
915
|
const detectedPm = detectPackageManager();
|
|
467
916
|
const interactive = !opts.yes && process.stdin.isTTY && process.stdout.isTTY;
|
|
468
917
|
const rl = interactive ? createInterface({ input, output }) : null;
|
|
469
918
|
|
|
919
|
+
if (interactive) {
|
|
920
|
+
printIntro("Let's set up your DaloyJS project");
|
|
921
|
+
}
|
|
922
|
+
|
|
470
923
|
try {
|
|
471
924
|
let projectName = opts.projectName;
|
|
472
925
|
if (!projectName) {
|
|
@@ -478,7 +931,7 @@ async function main() {
|
|
|
478
931
|
projectName = candidate;
|
|
479
932
|
break;
|
|
480
933
|
}
|
|
481
|
-
|
|
934
|
+
logError(valid);
|
|
482
935
|
}
|
|
483
936
|
} else {
|
|
484
937
|
projectName = "my-daloy-app";
|
|
@@ -486,7 +939,7 @@ async function main() {
|
|
|
486
939
|
}
|
|
487
940
|
const nameCheck = validateProjectName(projectName);
|
|
488
941
|
if (nameCheck !== true) {
|
|
489
|
-
|
|
942
|
+
logError(nameCheck);
|
|
490
943
|
process.exit(1);
|
|
491
944
|
}
|
|
492
945
|
|
|
@@ -495,13 +948,13 @@ async function main() {
|
|
|
495
948
|
template = rl ? await askChoice(rl, "Choose a starter template:", TEMPLATE_OPTIONS, "node-basic") : "node-basic";
|
|
496
949
|
}
|
|
497
950
|
if (!TEMPLATES.includes(template)) {
|
|
498
|
-
|
|
951
|
+
logError(`Unknown template "${template}". Available: ${TEMPLATES.join(", ")}`);
|
|
499
952
|
process.exit(1);
|
|
500
953
|
}
|
|
501
954
|
|
|
502
955
|
const templateDir = path.join(TEMPLATES_DIR, template);
|
|
503
956
|
if (!existsSync(templateDir)) {
|
|
504
|
-
|
|
957
|
+
logError(`Template "${template}" is missing from this CLI build.`);
|
|
505
958
|
process.exit(1);
|
|
506
959
|
}
|
|
507
960
|
|
|
@@ -509,12 +962,7 @@ async function main() {
|
|
|
509
962
|
if (existsSync(targetDir)) {
|
|
510
963
|
const empty = await isDirEmpty(targetDir);
|
|
511
964
|
if (!empty && !opts.force) {
|
|
512
|
-
|
|
513
|
-
color(
|
|
514
|
-
COLORS.red,
|
|
515
|
-
`Directory ${projectName} is not empty. Re-run with --force to overwrite.`,
|
|
516
|
-
),
|
|
517
|
-
);
|
|
965
|
+
logError(`Directory ${projectName} is not empty. Re-run with --force to overwrite.`);
|
|
518
966
|
process.exit(1);
|
|
519
967
|
}
|
|
520
968
|
}
|
|
@@ -531,9 +979,7 @@ async function main() {
|
|
|
531
979
|
}
|
|
532
980
|
}
|
|
533
981
|
if (!PACKAGE_MANAGERS.includes(packageManager)) {
|
|
534
|
-
|
|
535
|
-
color(COLORS.red, `Unknown --package-manager "${packageManager}". Use one of: ${PACKAGE_MANAGERS.join(", ")}`),
|
|
536
|
-
);
|
|
982
|
+
logError(`Unknown --package-manager "${packageManager}". Use one of: ${PACKAGE_MANAGERS.join(", ")}`);
|
|
537
983
|
process.exit(1);
|
|
538
984
|
}
|
|
539
985
|
|
|
@@ -553,7 +999,12 @@ async function main() {
|
|
|
553
999
|
|
|
554
1000
|
rl?.close();
|
|
555
1001
|
|
|
556
|
-
|
|
1002
|
+
if (interactive) {
|
|
1003
|
+
printOutro(color(COLORS.dim, "Configuration locked in. Building your project\u2026"));
|
|
1004
|
+
}
|
|
1005
|
+
console.log("");
|
|
1006
|
+
console.log(`${color(COLORS.cyan, SYMBOLS.sparkle)} ${color(COLORS.bold, "Scaffolding your project")}`);
|
|
1007
|
+
console.log("");
|
|
557
1008
|
|
|
558
1009
|
await mkdir(targetDir, { recursive: true });
|
|
559
1010
|
await copyTemplate(templateDir, targetDir);
|
|
@@ -582,19 +1033,31 @@ async function main() {
|
|
|
582
1033
|
}
|
|
583
1034
|
|
|
584
1035
|
if (installDeps) {
|
|
585
|
-
|
|
586
|
-
|
|
1036
|
+
const spinner = createSpinner(`Installing dependencies with ${color(COLORS.cyan, packageManager)}\u2026`);
|
|
1037
|
+
spinner.start();
|
|
1038
|
+
const { code, output: installOutput } = await runQuiet(packageManager, ["install"], targetDir);
|
|
587
1039
|
if (code !== 0) {
|
|
588
|
-
|
|
1040
|
+
spinner.stop(`${packageManager} install failed (exit ${code})`, false);
|
|
1041
|
+
// Replay the captured output so the user can see what went wrong.
|
|
1042
|
+
const tail = installOutput.split(/\r?\n/).slice(-40).join("\n");
|
|
1043
|
+
if (tail.trim().length > 0) {
|
|
1044
|
+
console.error(color(COLORS.dim, tail));
|
|
1045
|
+
}
|
|
1046
|
+
logWarn(`Retry inside ${projectName} with: ${packageManager} install`);
|
|
589
1047
|
} else {
|
|
590
|
-
|
|
1048
|
+
spinner.stop(`Installed dependencies with ${color(COLORS.cyan, packageManager)}`);
|
|
591
1049
|
}
|
|
592
1050
|
}
|
|
593
1051
|
|
|
594
1052
|
printSummary({ projectName, template, packageManager, installDeps, skipPackageManager });
|
|
595
1053
|
} catch (err) {
|
|
596
1054
|
rl?.close();
|
|
597
|
-
|
|
1055
|
+
if (err && err.message === "Cancelled") {
|
|
1056
|
+
console.log("");
|
|
1057
|
+
logWarn("Cancelled. No project was created.");
|
|
1058
|
+
process.exit(130);
|
|
1059
|
+
}
|
|
1060
|
+
logError(`Failed: ${(err && err.message) || err}`);
|
|
598
1061
|
process.exit(1);
|
|
599
1062
|
}
|
|
600
1063
|
}
|
package/package.json
CHANGED