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