create-daloy 0.1.16 → 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 +15 -0
- package/bin/create-daloy.mjs +581 -98
- package/package.json +1 -1
- package/templates/bun-basic/AGENTS.md +20 -0
- package/templates/bun-basic/SKILL.md +68 -0
- package/templates/cloudflare-worker/AGENTS.md +20 -0
- package/templates/cloudflare-worker/SKILL.md +68 -0
- package/templates/cloudflare-worker/package.json +1 -1
- package/templates/deno-basic/AGENTS.md +22 -0
- package/templates/deno-basic/SKILL.md +71 -0
- package/templates/node-basic/AGENTS.md +22 -0
- package/templates/node-basic/SKILL.md +70 -0
- package/templates/node-basic/package.json +4 -4
- package/templates/vercel-edge/AGENTS.md +20 -0
- package/templates/vercel-edge/SKILL.md +64 -0
- package/templates/vercel-edge/package.json +1 -1
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
|
}
|
|
@@ -221,16 +434,33 @@ async function patchPackageJson(dir, projectName, packageManager) {
|
|
|
221
434
|
await writeFile(file, JSON.stringify(json, null, 2) + "\n", "utf8");
|
|
222
435
|
}
|
|
223
436
|
|
|
224
|
-
async function
|
|
437
|
+
async function patchTemplateTextFiles(dir, packageManager) {
|
|
225
438
|
if (packageManager === "pnpm") return;
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
439
|
+
for (const fileName of ["README.md", "AGENTS.md", "SKILL.md"]) {
|
|
440
|
+
const file = path.join(dir, fileName);
|
|
441
|
+
if (!existsSync(file)) continue;
|
|
442
|
+
const raw = await readFile(file, "utf8");
|
|
443
|
+
const next = rewritePackageManagerText(raw, packageManager);
|
|
444
|
+
if (next !== raw) await writeFile(file, next, "utf8");
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function rewritePackageManagerText(raw, packageManager) {
|
|
449
|
+
return raw
|
|
450
|
+
.replace(
|
|
451
|
+
"Package manager: pnpm (use `pnpm` unless the project's `package.json` was rewritten for npm/yarn/bun).",
|
|
452
|
+
`Package manager: ${packageManager}.`,
|
|
453
|
+
)
|
|
230
454
|
.replace(/\bpnpm install\b/g, `${packageManager} install`)
|
|
455
|
+
.replace(/\bpnpm gen:openapi\b/g, `${packageManager} run gen:openapi`)
|
|
456
|
+
.replace(/\bpnpm gen:client\b/g, `${packageManager} run gen:client`)
|
|
457
|
+
.replace(/\bpnpm typecheck\b/g, `${packageManager} run typecheck`)
|
|
458
|
+
.replace(/\bpnpm build\b/g, `${packageManager} run build`)
|
|
459
|
+
.replace(/\bpnpm deploy\b/g, `${packageManager} run deploy`)
|
|
231
460
|
.replace(/\bpnpm dev\b/g, `${packageManager} run dev`)
|
|
232
461
|
.replace(/\bpnpm gen\b/g, `${packageManager} run gen`)
|
|
233
|
-
.replace(/\bpnpm
|
|
462
|
+
.replace(/\bpnpm test\b/g, `${packageManager} test`)
|
|
463
|
+
.replace(/\bpnpm audit\b/g, `${packageManager} audit`)
|
|
234
464
|
.replace(
|
|
235
465
|
"- Hardened `.npmrc` for safer installs.",
|
|
236
466
|
`- Package-manager scripts adjusted for ${packageManager}.`,
|
|
@@ -238,8 +468,11 @@ async function patchReadme(dir, packageManager) {
|
|
|
238
468
|
.replace(
|
|
239
469
|
"- Hey API codegen wired to `pnpm gen`.",
|
|
240
470
|
`- Hey API codegen wired to \`${packageManager} run gen\`.`,
|
|
471
|
+
)
|
|
472
|
+
.replace(
|
|
473
|
+
"- Do not add runtime dependencies without checking the hardened `.npmrc` (installs wait 24h after publish by default).",
|
|
474
|
+
`- Add runtime dependencies with \`${packageManager} install <package>\` and rerun the quality gates after dependency changes.`,
|
|
241
475
|
);
|
|
242
|
-
await writeFile(file, next, "utf8");
|
|
243
476
|
}
|
|
244
477
|
|
|
245
478
|
/**
|
|
@@ -340,17 +573,59 @@ function run(cmd, args, cwd) {
|
|
|
340
573
|
});
|
|
341
574
|
}
|
|
342
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
|
+
|
|
343
611
|
async function ask(rl, question, defaultValue) {
|
|
344
|
-
|
|
345
|
-
const
|
|
346
|
-
|
|
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;
|
|
347
620
|
}
|
|
348
621
|
|
|
349
622
|
async function askYesNo(rl, question, defaultYes) {
|
|
623
|
+
printPromptHeader(question);
|
|
350
624
|
const def = defaultYes ? "Y/n" : "y/N";
|
|
351
|
-
const answer = (await rl.question(`${color(COLORS.
|
|
625
|
+
const answer = (await rl.question(`${BAR} ${color(COLORS.gray, SYMBOLS.pointer)} ${color(COLORS.dim, `(${def})`)} `))
|
|
352
626
|
.trim()
|
|
353
627
|
.toLowerCase();
|
|
628
|
+
printRailGap();
|
|
354
629
|
if (answer.length === 0) return defaultYes;
|
|
355
630
|
return answer === "y" || answer === "yes";
|
|
356
631
|
}
|
|
@@ -359,68 +634,264 @@ function optionValue(option) {
|
|
|
359
634
|
return typeof option === "string" ? option : option.value;
|
|
360
635
|
}
|
|
361
636
|
|
|
362
|
-
function
|
|
363
|
-
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 ?? "";
|
|
364
643
|
}
|
|
365
644
|
|
|
645
|
+
// Arrow-key powered choice prompt. Falls back to numbered input whenever raw
|
|
646
|
+
// mode is unavailable (CI, piped stdin, integration tests).
|
|
366
647
|
async function askChoice(rl, question, choices, defaultChoice) {
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
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();
|
|
379
775
|
if (raw.length === 0) return defaultChoice;
|
|
380
776
|
const asNumber = Number.parseInt(raw, 10);
|
|
381
777
|
if (Number.isInteger(asNumber) && asNumber >= 1 && asNumber <= choices.length) {
|
|
382
778
|
return optionValue(choices[asNumber - 1]);
|
|
383
779
|
}
|
|
384
780
|
if (choices.some((choice) => optionValue(choice) === raw)) return raw;
|
|
385
|
-
console.error(color(COLORS.red, `Invalid choice. Pick one of: ${choices.map(optionValue).join(", ")}`));
|
|
386
|
-
return
|
|
781
|
+
console.error(`${BAR} ${color(COLORS.red, `Invalid choice. Pick one of: ${choices.map(optionValue).join(", ")}`)}`);
|
|
782
|
+
return askChoiceNumbered(rl, question, choices, defaultChoice);
|
|
387
783
|
}
|
|
388
784
|
|
|
389
785
|
function logStep(message, detail) {
|
|
390
|
-
const suffix = detail ? color(COLORS.dim, ` ${detail}`) : "";
|
|
391
|
-
console.log(`${color(COLORS.green,
|
|
786
|
+
const suffix = detail ? color(COLORS.dim, ` \u2014 ${detail}`) : "";
|
|
787
|
+
console.log(`${color(COLORS.green, SYMBOLS.success)} ${message}${suffix}`);
|
|
392
788
|
}
|
|
393
789
|
|
|
394
790
|
function logWarn(message) {
|
|
395
|
-
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
|
+
};
|
|
396
846
|
}
|
|
397
847
|
|
|
398
848
|
function printSummary({ projectName, template, packageManager, installDeps, skipPackageManager }) {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
+
];
|
|
402
857
|
if (skipPackageManager) {
|
|
403
|
-
|
|
404
|
-
console.log(`\n ${color(COLORS.bold, "Next steps")}`);
|
|
405
|
-
console.log(` cd ${projectName}`);
|
|
406
|
-
console.log(` deno task dev`);
|
|
407
|
-
console.log(`\n ${color(COLORS.bold, "Useful commands")}`);
|
|
408
|
-
console.log(` deno task typecheck`);
|
|
409
|
-
console.log(` deno task test`);
|
|
410
|
-
console.log(` deno task gen:openapi`);
|
|
858
|
+
summaryLines.push(`${color(COLORS.gray, "Runtime ")} ${color(COLORS.cyan, template === "deno-basic" ? "Deno" : "runtime")}`);
|
|
411
859
|
} else {
|
|
412
|
-
|
|
413
|
-
console.log(`\n ${color(COLORS.bold, "Next steps")}`);
|
|
414
|
-
console.log(` cd ${projectName}`);
|
|
415
|
-
if (!installDeps) console.log(` ${packageManager} install`);
|
|
416
|
-
console.log(` ${packageManager} run dev`);
|
|
417
|
-
console.log(`\n ${color(COLORS.bold, "Useful commands")}`);
|
|
418
|
-
console.log(` ${packageManager} run typecheck`);
|
|
419
|
-
console.log(` ${packageManager} test`);
|
|
420
|
-
if (template === "node-basic" || template === "bun-basic") console.log(` ${packageManager} run gen`);
|
|
860
|
+
summaryLines.push(`${color(COLORS.gray, "Manager ")} ${color(COLORS.cyan, packageManager)}`);
|
|
421
861
|
}
|
|
422
|
-
console.log(
|
|
423
|
-
console.log(
|
|
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")}`);
|
|
878
|
+
if (skipPackageManager) {
|
|
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")}`);
|
|
882
|
+
} else {
|
|
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
|
+
}
|
|
888
|
+
}
|
|
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`);
|
|
424
895
|
}
|
|
425
896
|
|
|
426
897
|
async function main() {
|
|
@@ -439,14 +910,16 @@ async function main() {
|
|
|
439
910
|
process.exit(0);
|
|
440
911
|
}
|
|
441
912
|
|
|
442
|
-
|
|
443
|
-
console.log(color(COLORS.dim, "Contract-first REST APIs for Node, Vercel Edge, and Workers"));
|
|
444
|
-
console.log(color(COLORS.dim, "https://daloyjs.dev\n"));
|
|
913
|
+
printBanner(await readPkgVersion());
|
|
445
914
|
|
|
446
915
|
const detectedPm = detectPackageManager();
|
|
447
916
|
const interactive = !opts.yes && process.stdin.isTTY && process.stdout.isTTY;
|
|
448
917
|
const rl = interactive ? createInterface({ input, output }) : null;
|
|
449
918
|
|
|
919
|
+
if (interactive) {
|
|
920
|
+
printIntro("Let's set up your DaloyJS project");
|
|
921
|
+
}
|
|
922
|
+
|
|
450
923
|
try {
|
|
451
924
|
let projectName = opts.projectName;
|
|
452
925
|
if (!projectName) {
|
|
@@ -458,7 +931,7 @@ async function main() {
|
|
|
458
931
|
projectName = candidate;
|
|
459
932
|
break;
|
|
460
933
|
}
|
|
461
|
-
|
|
934
|
+
logError(valid);
|
|
462
935
|
}
|
|
463
936
|
} else {
|
|
464
937
|
projectName = "my-daloy-app";
|
|
@@ -466,7 +939,7 @@ async function main() {
|
|
|
466
939
|
}
|
|
467
940
|
const nameCheck = validateProjectName(projectName);
|
|
468
941
|
if (nameCheck !== true) {
|
|
469
|
-
|
|
942
|
+
logError(nameCheck);
|
|
470
943
|
process.exit(1);
|
|
471
944
|
}
|
|
472
945
|
|
|
@@ -475,13 +948,13 @@ async function main() {
|
|
|
475
948
|
template = rl ? await askChoice(rl, "Choose a starter template:", TEMPLATE_OPTIONS, "node-basic") : "node-basic";
|
|
476
949
|
}
|
|
477
950
|
if (!TEMPLATES.includes(template)) {
|
|
478
|
-
|
|
951
|
+
logError(`Unknown template "${template}". Available: ${TEMPLATES.join(", ")}`);
|
|
479
952
|
process.exit(1);
|
|
480
953
|
}
|
|
481
954
|
|
|
482
955
|
const templateDir = path.join(TEMPLATES_DIR, template);
|
|
483
956
|
if (!existsSync(templateDir)) {
|
|
484
|
-
|
|
957
|
+
logError(`Template "${template}" is missing from this CLI build.`);
|
|
485
958
|
process.exit(1);
|
|
486
959
|
}
|
|
487
960
|
|
|
@@ -489,12 +962,7 @@ async function main() {
|
|
|
489
962
|
if (existsSync(targetDir)) {
|
|
490
963
|
const empty = await isDirEmpty(targetDir);
|
|
491
964
|
if (!empty && !opts.force) {
|
|
492
|
-
|
|
493
|
-
color(
|
|
494
|
-
COLORS.red,
|
|
495
|
-
`Directory ${projectName} is not empty. Re-run with --force to overwrite.`,
|
|
496
|
-
),
|
|
497
|
-
);
|
|
965
|
+
logError(`Directory ${projectName} is not empty. Re-run with --force to overwrite.`);
|
|
498
966
|
process.exit(1);
|
|
499
967
|
}
|
|
500
968
|
}
|
|
@@ -511,9 +979,7 @@ async function main() {
|
|
|
511
979
|
}
|
|
512
980
|
}
|
|
513
981
|
if (!PACKAGE_MANAGERS.includes(packageManager)) {
|
|
514
|
-
|
|
515
|
-
color(COLORS.red, `Unknown --package-manager "${packageManager}". Use one of: ${PACKAGE_MANAGERS.join(", ")}`),
|
|
516
|
-
);
|
|
982
|
+
logError(`Unknown --package-manager "${packageManager}". Use one of: ${PACKAGE_MANAGERS.join(", ")}`);
|
|
517
983
|
process.exit(1);
|
|
518
984
|
}
|
|
519
985
|
|
|
@@ -533,7 +999,12 @@ async function main() {
|
|
|
533
999
|
|
|
534
1000
|
rl?.close();
|
|
535
1001
|
|
|
536
|
-
|
|
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("");
|
|
537
1008
|
|
|
538
1009
|
await mkdir(targetDir, { recursive: true });
|
|
539
1010
|
await copyTemplate(templateDir, targetDir);
|
|
@@ -545,7 +1016,7 @@ async function main() {
|
|
|
545
1016
|
if (!skipPackageManager) {
|
|
546
1017
|
await patchPackageJson(targetDir, projectName, packageManager);
|
|
547
1018
|
logStep("Package metadata written", projectName);
|
|
548
|
-
await
|
|
1019
|
+
await patchTemplateTextFiles(targetDir, packageManager);
|
|
549
1020
|
await normalizePackageManagerFiles(targetDir, packageManager);
|
|
550
1021
|
if (packageManager !== "pnpm") {
|
|
551
1022
|
logStep("Package-manager config normalized", packageManager);
|
|
@@ -562,19 +1033,31 @@ async function main() {
|
|
|
562
1033
|
}
|
|
563
1034
|
|
|
564
1035
|
if (installDeps) {
|
|
565
|
-
|
|
566
|
-
|
|
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);
|
|
567
1039
|
if (code !== 0) {
|
|
568
|
-
|
|
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`);
|
|
569
1047
|
} else {
|
|
570
|
-
|
|
1048
|
+
spinner.stop(`Installed dependencies with ${color(COLORS.cyan, packageManager)}`);
|
|
571
1049
|
}
|
|
572
1050
|
}
|
|
573
1051
|
|
|
574
1052
|
printSummary({ projectName, template, packageManager, installDeps, skipPackageManager });
|
|
575
1053
|
} catch (err) {
|
|
576
1054
|
rl?.close();
|
|
577
|
-
|
|
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}`);
|
|
578
1061
|
process.exit(1);
|
|
579
1062
|
}
|
|
580
1063
|
}
|