create-better-t-stack 3.12.7 → 3.13.0
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/dist/cli.mjs +1 -1
- package/dist/index.d.mts +13 -53
- package/dist/index.mjs +1 -1
- package/dist/{src-DNjxspj9.mjs → src-DyVk_RBS.mjs} +677 -272
- package/package.json +3 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { t as __reExport } from "./chunk-Dt3mZKp0.mjs";
|
|
3
|
-
import { autocompleteMultiselect, cancel, confirm, group,
|
|
3
|
+
import { autocompleteMultiselect, cancel, confirm, group, intro, isCancel, log, multiselect, outro, select, spinner, text } from "@clack/prompts";
|
|
4
4
|
import { createRouterClient, os } from "@orpc/server";
|
|
5
5
|
import pc from "picocolors";
|
|
6
6
|
import { createCli } from "trpc-cli";
|
|
@@ -9,6 +9,8 @@ import consola, { consola as consola$1 } from "consola";
|
|
|
9
9
|
import fs from "fs-extra";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
|
|
13
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
12
14
|
import gradient from "gradient-string";
|
|
13
15
|
import * as JSONC from "jsonc-parser";
|
|
14
16
|
import { $, execa } from "execa";
|
|
@@ -215,18 +217,30 @@ const WEB_FRAMEWORKS = [
|
|
|
215
217
|
|
|
216
218
|
//#endregion
|
|
217
219
|
//#region src/utils/errors.ts
|
|
220
|
+
var UserCancelledError = class extends Error {
|
|
221
|
+
constructor(message = "Operation cancelled") {
|
|
222
|
+
super(message);
|
|
223
|
+
this.name = "UserCancelledError";
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
var CLIError = class extends Error {
|
|
227
|
+
constructor(message) {
|
|
228
|
+
super(message);
|
|
229
|
+
this.name = "CLIError";
|
|
230
|
+
}
|
|
231
|
+
};
|
|
218
232
|
function exitWithError(message) {
|
|
219
233
|
consola.error(pc.red(message));
|
|
220
|
-
throw new
|
|
234
|
+
throw new CLIError(message);
|
|
221
235
|
}
|
|
222
236
|
function exitCancelled(message = "Operation cancelled") {
|
|
223
237
|
cancel(pc.red(message));
|
|
224
|
-
throw new
|
|
238
|
+
throw new UserCancelledError(message);
|
|
225
239
|
}
|
|
226
240
|
function handleError(error, fallbackMessage) {
|
|
227
241
|
const message = error instanceof Error ? error.message : fallbackMessage || String(error);
|
|
228
242
|
consola.error(pc.red(message));
|
|
229
|
-
throw new Error(message);
|
|
243
|
+
throw error instanceof Error ? error : new Error(message);
|
|
230
244
|
}
|
|
231
245
|
|
|
232
246
|
//#endregion
|
|
@@ -362,6 +376,295 @@ function validateExamplesCompatibility(examples, backend, database, frontend, ap
|
|
|
362
376
|
}
|
|
363
377
|
}
|
|
364
378
|
|
|
379
|
+
//#endregion
|
|
380
|
+
//#region src/utils/context.ts
|
|
381
|
+
const cliStorage = new AsyncLocalStorage();
|
|
382
|
+
function defaultContext() {
|
|
383
|
+
return {
|
|
384
|
+
navigation: {
|
|
385
|
+
isFirstPrompt: false,
|
|
386
|
+
lastPromptShownUI: false
|
|
387
|
+
},
|
|
388
|
+
silent: false,
|
|
389
|
+
verbose: false
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function getContext() {
|
|
393
|
+
const ctx = cliStorage.getStore();
|
|
394
|
+
if (!ctx) return defaultContext();
|
|
395
|
+
return ctx;
|
|
396
|
+
}
|
|
397
|
+
function tryGetContext() {
|
|
398
|
+
return cliStorage.getStore();
|
|
399
|
+
}
|
|
400
|
+
function isSilent() {
|
|
401
|
+
return getContext().silent;
|
|
402
|
+
}
|
|
403
|
+
function isFirstPrompt() {
|
|
404
|
+
return getContext().navigation.isFirstPrompt;
|
|
405
|
+
}
|
|
406
|
+
function didLastPromptShowUI() {
|
|
407
|
+
return getContext().navigation.lastPromptShownUI;
|
|
408
|
+
}
|
|
409
|
+
function setIsFirstPrompt$1(value) {
|
|
410
|
+
const ctx = tryGetContext();
|
|
411
|
+
if (ctx) ctx.navigation.isFirstPrompt = value;
|
|
412
|
+
}
|
|
413
|
+
function setLastPromptShownUI(value) {
|
|
414
|
+
const ctx = tryGetContext();
|
|
415
|
+
if (ctx) ctx.navigation.lastPromptShownUI = value;
|
|
416
|
+
}
|
|
417
|
+
async function runWithContextAsync(options, fn) {
|
|
418
|
+
const ctx = {
|
|
419
|
+
navigation: {
|
|
420
|
+
isFirstPrompt: false,
|
|
421
|
+
lastPromptShownUI: false
|
|
422
|
+
},
|
|
423
|
+
silent: options.silent ?? false,
|
|
424
|
+
verbose: options.verbose ?? false,
|
|
425
|
+
projectDir: options.projectDir,
|
|
426
|
+
projectName: options.projectName,
|
|
427
|
+
packageManager: options.packageManager
|
|
428
|
+
};
|
|
429
|
+
return cliStorage.run(ctx, fn);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
//#endregion
|
|
433
|
+
//#region src/utils/navigation.ts
|
|
434
|
+
/**
|
|
435
|
+
* Navigation symbols and utilities for prompt navigation
|
|
436
|
+
*/
|
|
437
|
+
const GO_BACK_SYMBOL = Symbol("clack:goBack");
|
|
438
|
+
function isGoBack(value) {
|
|
439
|
+
return value === GO_BACK_SYMBOL;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
//#endregion
|
|
443
|
+
//#region src/prompts/navigable.ts
|
|
444
|
+
/**
|
|
445
|
+
* Navigable prompt wrappers using @clack/core
|
|
446
|
+
* These prompts return GO_BACK_SYMBOL when 'b' is pressed (instead of canceling)
|
|
447
|
+
*/
|
|
448
|
+
const unicode = process.platform !== "win32";
|
|
449
|
+
const S_STEP_ACTIVE = unicode ? "◆" : "*";
|
|
450
|
+
const S_STEP_CANCEL = unicode ? "■" : "x";
|
|
451
|
+
const S_STEP_ERROR = unicode ? "▲" : "x";
|
|
452
|
+
const S_STEP_SUBMIT = unicode ? "◇" : "o";
|
|
453
|
+
const S_BAR = unicode ? "│" : "|";
|
|
454
|
+
const S_BAR_END = unicode ? "└" : "—";
|
|
455
|
+
const S_RADIO_ACTIVE = unicode ? "●" : ">";
|
|
456
|
+
const S_RADIO_INACTIVE = unicode ? "○" : " ";
|
|
457
|
+
const S_CHECKBOX_ACTIVE = unicode ? "◻" : "[•]";
|
|
458
|
+
const S_CHECKBOX_SELECTED = unicode ? "◼" : "[+]";
|
|
459
|
+
const S_CHECKBOX_INACTIVE = unicode ? "◻" : "[ ]";
|
|
460
|
+
function symbol(state) {
|
|
461
|
+
switch (state) {
|
|
462
|
+
case "initial":
|
|
463
|
+
case "active": return pc.cyan(S_STEP_ACTIVE);
|
|
464
|
+
case "cancel": return pc.red(S_STEP_CANCEL);
|
|
465
|
+
case "error": return pc.yellow(S_STEP_ERROR);
|
|
466
|
+
case "submit": return pc.green(S_STEP_SUBMIT);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
const KEYBOARD_HINT = pc.dim(`${pc.gray("↑/↓")} navigate • ${pc.gray("enter")} confirm • ${pc.gray("b")} back • ${pc.gray("ctrl+c")} cancel`);
|
|
470
|
+
const KEYBOARD_HINT_FIRST = pc.dim(`${pc.gray("↑/↓")} navigate • ${pc.gray("enter")} confirm • ${pc.gray("ctrl+c")} cancel`);
|
|
471
|
+
const KEYBOARD_HINT_MULTI = pc.dim(`${pc.gray("↑/↓")} navigate • ${pc.gray("space")} select • ${pc.gray("enter")} confirm • ${pc.gray("b")} back • ${pc.gray("ctrl+c")} cancel`);
|
|
472
|
+
const KEYBOARD_HINT_MULTI_FIRST = pc.dim(`${pc.gray("↑/↓")} navigate • ${pc.gray("space")} select • ${pc.gray("enter")} confirm • ${pc.gray("ctrl+c")} cancel`);
|
|
473
|
+
const setIsFirstPrompt = setIsFirstPrompt$1;
|
|
474
|
+
function getHint() {
|
|
475
|
+
return isFirstPrompt() ? KEYBOARD_HINT_FIRST : KEYBOARD_HINT;
|
|
476
|
+
}
|
|
477
|
+
function getMultiHint() {
|
|
478
|
+
return isFirstPrompt() ? KEYBOARD_HINT_MULTI_FIRST : KEYBOARD_HINT_MULTI;
|
|
479
|
+
}
|
|
480
|
+
async function runWithNavigation(prompt) {
|
|
481
|
+
let goBack = false;
|
|
482
|
+
prompt.on("key", (char) => {
|
|
483
|
+
if (char === "b" && !isFirstPrompt()) {
|
|
484
|
+
goBack = true;
|
|
485
|
+
prompt.state = "cancel";
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
setLastPromptShownUI(true);
|
|
489
|
+
const result = await prompt.prompt();
|
|
490
|
+
return goBack ? GO_BACK_SYMBOL : result;
|
|
491
|
+
}
|
|
492
|
+
async function navigableSelect(opts) {
|
|
493
|
+
const opt = (option, state) => {
|
|
494
|
+
const label = option.label ?? String(option.value);
|
|
495
|
+
switch (state) {
|
|
496
|
+
case "disabled": return `${pc.gray(S_RADIO_INACTIVE)} ${pc.gray(label)}${option.hint ? ` ${pc.dim(`(${option.hint ?? "disabled"})`)}` : ""}`;
|
|
497
|
+
case "selected": return `${pc.dim(label)}`;
|
|
498
|
+
case "active": return `${pc.green(S_RADIO_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
499
|
+
case "cancelled": return `${pc.strikethrough(pc.dim(label))}`;
|
|
500
|
+
default: return `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(label)}`;
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
return runWithNavigation(new SelectPrompt({
|
|
504
|
+
options: opts.options,
|
|
505
|
+
initialValue: opts.initialValue,
|
|
506
|
+
render() {
|
|
507
|
+
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
508
|
+
switch (this.state) {
|
|
509
|
+
case "submit": return `${title}${pc.gray(S_BAR)} ${opt(this.options[this.cursor], "selected")}`;
|
|
510
|
+
case "cancel": return `${title}${pc.gray(S_BAR)} ${opt(this.options[this.cursor], "cancelled")}\n${pc.gray(S_BAR)}`;
|
|
511
|
+
default: {
|
|
512
|
+
const optionsText = this.options.map((option, i) => opt(option, option.disabled ? "disabled" : i === this.cursor ? "active" : "inactive")).join(`\n${pc.cyan(S_BAR)} `);
|
|
513
|
+
const hint = `\n${pc.gray(S_BAR)} ${getHint()}`;
|
|
514
|
+
return `${title}${pc.cyan(S_BAR)} ${optionsText}\n${pc.cyan(S_BAR_END)}${hint}\n`;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}));
|
|
519
|
+
}
|
|
520
|
+
async function navigableMultiselect(opts) {
|
|
521
|
+
const required = opts.required ?? true;
|
|
522
|
+
const opt = (option, state) => {
|
|
523
|
+
const label = option.label ?? String(option.value);
|
|
524
|
+
if (state === "disabled") return `${pc.gray(S_CHECKBOX_INACTIVE)} ${pc.strikethrough(pc.gray(label))}${option.hint ? ` ${pc.dim(`(${option.hint ?? "disabled"})`)}` : ""}`;
|
|
525
|
+
if (state === "active") return `${pc.cyan(S_CHECKBOX_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
526
|
+
if (state === "selected") return `${pc.green(S_CHECKBOX_SELECTED)} ${pc.dim(label)}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
527
|
+
if (state === "cancelled") return `${pc.strikethrough(pc.dim(label))}`;
|
|
528
|
+
if (state === "active-selected") return `${pc.green(S_CHECKBOX_SELECTED)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
529
|
+
if (state === "submitted") return `${pc.dim(label)}`;
|
|
530
|
+
return `${pc.dim(S_CHECKBOX_INACTIVE)} ${pc.dim(label)}`;
|
|
531
|
+
};
|
|
532
|
+
return runWithNavigation(new MultiSelectPrompt({
|
|
533
|
+
options: opts.options,
|
|
534
|
+
initialValues: opts.initialValues,
|
|
535
|
+
required,
|
|
536
|
+
validate(selected) {
|
|
537
|
+
if (required && (selected === void 0 || selected.length === 0)) return `Please select at least one option.\n${pc.reset(pc.dim(`Press ${pc.gray(pc.bgWhite(pc.inverse(" space ")))} to select, ${pc.gray(pc.bgWhite(pc.inverse(" enter ")))} to submit`))}`;
|
|
538
|
+
},
|
|
539
|
+
render() {
|
|
540
|
+
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
541
|
+
const value = this.value ?? [];
|
|
542
|
+
const styleOption = (option, active) => {
|
|
543
|
+
if (option.disabled) return opt(option, "disabled");
|
|
544
|
+
const selected = value.includes(option.value);
|
|
545
|
+
if (active && selected) return opt(option, "active-selected");
|
|
546
|
+
if (selected) return opt(option, "selected");
|
|
547
|
+
return opt(option, active ? "active" : "inactive");
|
|
548
|
+
};
|
|
549
|
+
switch (this.state) {
|
|
550
|
+
case "submit": {
|
|
551
|
+
const submitText = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "submitted")).join(pc.dim(", ")) || pc.dim("none");
|
|
552
|
+
return `${title}${pc.gray(S_BAR)} ${submitText}`;
|
|
553
|
+
}
|
|
554
|
+
case "cancel": {
|
|
555
|
+
const label = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "cancelled")).join(pc.dim(", "));
|
|
556
|
+
return `${title}${pc.gray(S_BAR)} ${label}\n${pc.gray(S_BAR)}`;
|
|
557
|
+
}
|
|
558
|
+
case "error": {
|
|
559
|
+
const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc.yellow(S_BAR_END)} ${pc.yellow(ln)}` : ` ${ln}`).join("\n");
|
|
560
|
+
const optionsText = this.options.map((option, i) => styleOption(option, i === this.cursor)).join(`\n${pc.yellow(S_BAR)} `);
|
|
561
|
+
return `${title}${pc.yellow(S_BAR)} ${optionsText}\n${footer}\n`;
|
|
562
|
+
}
|
|
563
|
+
default: {
|
|
564
|
+
const optionsText = this.options.map((option, i) => styleOption(option, i === this.cursor)).join(`\n${pc.cyan(S_BAR)} `);
|
|
565
|
+
const hint = `\n${pc.gray(S_BAR)} ${getMultiHint()}`;
|
|
566
|
+
return `${title}${pc.cyan(S_BAR)} ${optionsText}\n${pc.cyan(S_BAR_END)}${hint}\n`;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}));
|
|
571
|
+
}
|
|
572
|
+
async function navigableConfirm(opts) {
|
|
573
|
+
const active = opts.active ?? "Yes";
|
|
574
|
+
const inactive = opts.inactive ?? "No";
|
|
575
|
+
return runWithNavigation(new ConfirmPrompt({
|
|
576
|
+
active,
|
|
577
|
+
inactive,
|
|
578
|
+
initialValue: opts.initialValue ?? true,
|
|
579
|
+
render() {
|
|
580
|
+
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
581
|
+
const value = this.value ? active : inactive;
|
|
582
|
+
switch (this.state) {
|
|
583
|
+
case "submit": return `${title}${pc.gray(S_BAR)} ${pc.dim(value)}`;
|
|
584
|
+
case "cancel": return `${title}${pc.gray(S_BAR)} ${pc.strikethrough(pc.dim(value))}\n${pc.gray(S_BAR)}`;
|
|
585
|
+
default: {
|
|
586
|
+
const hint = `\n${pc.gray(S_BAR)} ${getHint()}`;
|
|
587
|
+
return `${title}${pc.cyan(S_BAR)} ${this.value ? `${pc.green(S_RADIO_ACTIVE)} ${active}` : `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(active)}`} ${pc.dim("/")} ${!this.value ? `${pc.green(S_RADIO_ACTIVE)} ${inactive}` : `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(inactive)}`}\n${pc.cyan(S_BAR_END)}${hint}\n`;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}));
|
|
592
|
+
}
|
|
593
|
+
async function navigableGroupMultiselect(opts) {
|
|
594
|
+
const required = opts.required ?? true;
|
|
595
|
+
const opt = (option, state, options = []) => {
|
|
596
|
+
const label = option.label ?? String(option.value);
|
|
597
|
+
const isItem = typeof option.group === "string";
|
|
598
|
+
const next = isItem && (options[options.indexOf(option) + 1] ?? { group: true });
|
|
599
|
+
const isLast = isItem && next && next.group === true;
|
|
600
|
+
const prefix = isItem ? `${isLast ? S_BAR_END : S_BAR} ` : "";
|
|
601
|
+
if (state === "active") return `${pc.dim(prefix)}${pc.cyan(S_CHECKBOX_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
602
|
+
if (state === "group-active") return `${prefix}${pc.cyan(S_CHECKBOX_ACTIVE)} ${pc.dim(label)}`;
|
|
603
|
+
if (state === "group-active-selected") return `${prefix}${pc.green(S_CHECKBOX_SELECTED)} ${pc.dim(label)}`;
|
|
604
|
+
if (state === "selected") {
|
|
605
|
+
const selectedCheckbox = isItem ? pc.green(S_CHECKBOX_SELECTED) : "";
|
|
606
|
+
return `${pc.dim(prefix)}${selectedCheckbox} ${pc.dim(label)}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
607
|
+
}
|
|
608
|
+
if (state === "cancelled") return `${pc.strikethrough(pc.dim(label))}`;
|
|
609
|
+
if (state === "active-selected") return `${pc.dim(prefix)}${pc.green(S_CHECKBOX_SELECTED)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
610
|
+
if (state === "submitted") return `${pc.dim(label)}`;
|
|
611
|
+
const unselectedCheckbox = isItem ? pc.dim(S_CHECKBOX_INACTIVE) : "";
|
|
612
|
+
return `${pc.dim(prefix)}${unselectedCheckbox} ${pc.dim(label)}`;
|
|
613
|
+
};
|
|
614
|
+
return runWithNavigation(new GroupMultiSelectPrompt({
|
|
615
|
+
options: opts.options,
|
|
616
|
+
initialValues: opts.initialValues,
|
|
617
|
+
required,
|
|
618
|
+
selectableGroups: true,
|
|
619
|
+
validate(selected) {
|
|
620
|
+
if (required && (selected === void 0 || selected.length === 0)) return `Please select at least one option.\n${pc.reset(pc.dim(`Press ${pc.gray(pc.bgWhite(pc.inverse(" space ")))} to select, ${pc.gray(pc.bgWhite(pc.inverse(" enter ")))} to submit`))}`;
|
|
621
|
+
},
|
|
622
|
+
render() {
|
|
623
|
+
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
624
|
+
const value = this.value ?? [];
|
|
625
|
+
switch (this.state) {
|
|
626
|
+
case "submit": {
|
|
627
|
+
const selectedOptions = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "submitted"));
|
|
628
|
+
const optionsText = selectedOptions.length === 0 ? "" : ` ${selectedOptions.join(pc.dim(", "))}`;
|
|
629
|
+
return `${title}${pc.gray(S_BAR)}${optionsText}`;
|
|
630
|
+
}
|
|
631
|
+
case "cancel": {
|
|
632
|
+
const label = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "cancelled")).join(pc.dim(", "));
|
|
633
|
+
return `${title}${pc.gray(S_BAR)} ${label.trim() ? `${label}\n${pc.gray(S_BAR)}` : ""}`;
|
|
634
|
+
}
|
|
635
|
+
case "error": {
|
|
636
|
+
const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc.yellow(S_BAR_END)} ${pc.yellow(ln)}` : ` ${ln}`).join("\n");
|
|
637
|
+
const optionsText = this.options.map((option, i, options) => {
|
|
638
|
+
const selected = value.includes(option.value) || option.group === true && this.isGroupSelected(`${option.value}`);
|
|
639
|
+
const active = i === this.cursor;
|
|
640
|
+
if (!active && typeof option.group === "string" && this.options[this.cursor].value === option.group) return opt(option, selected ? "group-active-selected" : "group-active", options);
|
|
641
|
+
if (active && selected) return opt(option, "active-selected", options);
|
|
642
|
+
if (selected) return opt(option, "selected", options);
|
|
643
|
+
return opt(option, active ? "active" : "inactive", options);
|
|
644
|
+
}).join(`\n${pc.yellow(S_BAR)} `);
|
|
645
|
+
return `${title}${pc.yellow(S_BAR)} ${optionsText}\n${footer}\n`;
|
|
646
|
+
}
|
|
647
|
+
default: {
|
|
648
|
+
const optionsText = this.options.map((option, i, options) => {
|
|
649
|
+
const selected = value.includes(option.value) || option.group === true && this.isGroupSelected(`${option.value}`);
|
|
650
|
+
const active = i === this.cursor;
|
|
651
|
+
const groupActive = !active && typeof option.group === "string" && this.options[this.cursor].value === option.group;
|
|
652
|
+
let optionText = "";
|
|
653
|
+
if (groupActive) optionText = opt(option, selected ? "group-active-selected" : "group-active", options);
|
|
654
|
+
else if (active && selected) optionText = opt(option, "active-selected", options);
|
|
655
|
+
else if (selected) optionText = opt(option, "selected", options);
|
|
656
|
+
else optionText = opt(option, active ? "active" : "inactive", options);
|
|
657
|
+
return `${i !== 0 && !optionText.startsWith("\n") ? " " : ""}${optionText}`;
|
|
658
|
+
}).join(`\n${pc.cyan(S_BAR)}`);
|
|
659
|
+
const optionsPrefix = optionsText.startsWith("\n") ? "" : " ";
|
|
660
|
+
const hint = `\n${pc.gray(S_BAR)} ${getMultiHint()}`;
|
|
661
|
+
return `${title}${pc.cyan(S_BAR)}${optionsPrefix}${optionsText}\n${pc.cyan(S_BAR_END)}${hint}\n`;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}));
|
|
666
|
+
}
|
|
667
|
+
|
|
365
668
|
//#endregion
|
|
366
669
|
//#region src/prompts/addons.ts
|
|
367
670
|
function getAddonDisplay(addon) {
|
|
@@ -473,14 +776,13 @@ async function getAddonsChoice(addons, frontends, auth) {
|
|
|
473
776
|
});
|
|
474
777
|
}
|
|
475
778
|
});
|
|
476
|
-
const response = await
|
|
779
|
+
const response = await navigableGroupMultiselect({
|
|
477
780
|
message: "Select addons",
|
|
478
781
|
options: groupedOptions,
|
|
479
782
|
initialValues: DEFAULT_CONFIG.addons.filter((addonValue) => Object.values(groupedOptions).some((options) => options.some((opt) => opt.value === addonValue))),
|
|
480
|
-
required: false
|
|
481
|
-
selectableGroups: false
|
|
783
|
+
required: false
|
|
482
784
|
});
|
|
483
|
-
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
785
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
484
786
|
return response;
|
|
485
787
|
}
|
|
486
788
|
async function getAddonsToAdd(frontend, existingAddons = [], auth) {
|
|
@@ -512,13 +814,12 @@ async function getAddonsToAdd(frontend, existingAddons = [], auth) {
|
|
|
512
814
|
}
|
|
513
815
|
});
|
|
514
816
|
if (Object.keys(groupedOptions).length === 0) return [];
|
|
515
|
-
const response = await
|
|
817
|
+
const response = await navigableGroupMultiselect({
|
|
516
818
|
message: "Select addons to add",
|
|
517
819
|
options: groupedOptions,
|
|
518
|
-
required: false
|
|
519
|
-
selectableGroups: false
|
|
820
|
+
required: false
|
|
520
821
|
});
|
|
521
|
-
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
822
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
522
823
|
return response;
|
|
523
824
|
}
|
|
524
825
|
|
|
@@ -541,12 +842,12 @@ async function getApiChoice(Api, frontend, backend) {
|
|
|
541
842
|
label: "None",
|
|
542
843
|
hint: "No API layer (e.g. for full-stack frameworks like Next.js with Route Handlers)"
|
|
543
844
|
});
|
|
544
|
-
const apiType = await
|
|
845
|
+
const apiType = await navigableSelect({
|
|
545
846
|
message: "Select API type",
|
|
546
847
|
options: apiOptions,
|
|
547
848
|
initialValue: apiOptions[0].value
|
|
548
849
|
});
|
|
549
|
-
if (isCancel(apiType)) return exitCancelled("Operation cancelled");
|
|
850
|
+
if (isCancel$1(apiType)) return exitCancelled("Operation cancelled");
|
|
550
851
|
return apiType;
|
|
551
852
|
}
|
|
552
853
|
|
|
@@ -590,15 +891,15 @@ async function getAuthChoice(auth, backend, frontend) {
|
|
|
590
891
|
label: "None",
|
|
591
892
|
hint: "No auth"
|
|
592
893
|
});
|
|
593
|
-
const response$1 = await
|
|
894
|
+
const response$1 = await navigableSelect({
|
|
594
895
|
message: "Select authentication provider",
|
|
595
896
|
options,
|
|
596
897
|
initialValue: "none"
|
|
597
898
|
});
|
|
598
|
-
if (isCancel(response$1)) return exitCancelled("Operation cancelled");
|
|
899
|
+
if (isCancel$1(response$1)) return exitCancelled("Operation cancelled");
|
|
599
900
|
return response$1;
|
|
600
901
|
}
|
|
601
|
-
const response = await
|
|
902
|
+
const response = await navigableSelect({
|
|
602
903
|
message: "Select authentication provider",
|
|
603
904
|
options: [{
|
|
604
905
|
value: "better-auth",
|
|
@@ -610,7 +911,7 @@ async function getAuthChoice(auth, backend, frontend) {
|
|
|
610
911
|
}],
|
|
611
912
|
initialValue: DEFAULT_CONFIG.auth
|
|
612
913
|
});
|
|
613
|
-
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
914
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
614
915
|
return response;
|
|
615
916
|
}
|
|
616
917
|
|
|
@@ -654,12 +955,12 @@ async function getBackendFrameworkChoice(backendFramework, frontends) {
|
|
|
654
955
|
label: "None",
|
|
655
956
|
hint: "No backend server"
|
|
656
957
|
});
|
|
657
|
-
const response = await
|
|
958
|
+
const response = await navigableSelect({
|
|
658
959
|
message: "Select backend",
|
|
659
960
|
options: backendOptions,
|
|
660
961
|
initialValue: hasFullstackFrontend ? "self" : DEFAULT_CONFIG.backend
|
|
661
962
|
});
|
|
662
|
-
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
963
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
663
964
|
return response;
|
|
664
965
|
}
|
|
665
966
|
|
|
@@ -695,12 +996,12 @@ async function getDatabaseChoice(database, backend, runtime) {
|
|
|
695
996
|
label: "MongoDB",
|
|
696
997
|
hint: "open-source NoSQL database that stores data in JSON-like documents called BSON"
|
|
697
998
|
});
|
|
698
|
-
const response = await
|
|
999
|
+
const response = await navigableSelect({
|
|
699
1000
|
message: "Select database",
|
|
700
1001
|
options: databaseOptions,
|
|
701
1002
|
initialValue: DEFAULT_CONFIG.database
|
|
702
1003
|
});
|
|
703
|
-
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
1004
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
704
1005
|
return response;
|
|
705
1006
|
}
|
|
706
1007
|
|
|
@@ -795,12 +1096,12 @@ async function getDBSetupChoice(databaseType, dbSetup, _orm, backend, runtime) {
|
|
|
795
1096
|
}
|
|
796
1097
|
];
|
|
797
1098
|
else return "none";
|
|
798
|
-
const response = await
|
|
1099
|
+
const response = await navigableSelect({
|
|
799
1100
|
message: `Select ${databaseType} setup option`,
|
|
800
1101
|
options,
|
|
801
1102
|
initialValue: "none"
|
|
802
1103
|
});
|
|
803
|
-
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
1104
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
804
1105
|
return response;
|
|
805
1106
|
}
|
|
806
1107
|
|
|
@@ -822,13 +1123,13 @@ async function getExamplesChoice(examples, database, frontends, backend, api) {
|
|
|
822
1123
|
hint: "A simple AI chat interface using AI SDK"
|
|
823
1124
|
});
|
|
824
1125
|
if (options.length === 0) return [];
|
|
825
|
-
response = await
|
|
1126
|
+
response = await navigableMultiselect({
|
|
826
1127
|
message: "Include examples",
|
|
827
1128
|
options,
|
|
828
1129
|
required: false,
|
|
829
1130
|
initialValues: DEFAULT_CONFIG.examples?.filter((ex) => options.some((o) => o.value === ex))
|
|
830
1131
|
});
|
|
831
|
-
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
1132
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
832
1133
|
return response;
|
|
833
1134
|
}
|
|
834
1135
|
|
|
@@ -836,104 +1137,124 @@ async function getExamplesChoice(examples, database, frontends, backend, api) {
|
|
|
836
1137
|
//#region src/prompts/frontend.ts
|
|
837
1138
|
async function getFrontendChoice(frontendOptions, backend, auth) {
|
|
838
1139
|
if (frontendOptions !== void 0) return frontendOptions;
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
const result = [];
|
|
855
|
-
if (frontendTypes.includes("web")) {
|
|
856
|
-
const webFramework = await select({
|
|
857
|
-
message: "Choose web",
|
|
858
|
-
options: [
|
|
859
|
-
{
|
|
860
|
-
value: "tanstack-router",
|
|
861
|
-
label: "TanStack Router",
|
|
862
|
-
hint: "Modern and scalable routing for React Applications"
|
|
863
|
-
},
|
|
864
|
-
{
|
|
865
|
-
value: "react-router",
|
|
866
|
-
label: "React Router",
|
|
867
|
-
hint: "A user‑obsessed, standards‑focused, multi‑strategy router"
|
|
868
|
-
},
|
|
869
|
-
{
|
|
870
|
-
value: "next",
|
|
871
|
-
label: "Next.js",
|
|
872
|
-
hint: "The React Framework for the Web"
|
|
873
|
-
},
|
|
874
|
-
{
|
|
875
|
-
value: "nuxt",
|
|
876
|
-
label: "Nuxt",
|
|
877
|
-
hint: "The Progressive Web Framework for Vue.js"
|
|
878
|
-
},
|
|
879
|
-
{
|
|
880
|
-
value: "svelte",
|
|
881
|
-
label: "Svelte",
|
|
882
|
-
hint: "web development for the rest of us"
|
|
883
|
-
},
|
|
884
|
-
{
|
|
885
|
-
value: "solid",
|
|
886
|
-
label: "Solid",
|
|
887
|
-
hint: "Simple and performant reactivity for building user interfaces"
|
|
888
|
-
},
|
|
889
|
-
{
|
|
890
|
-
value: "tanstack-start",
|
|
891
|
-
label: "TanStack Start",
|
|
892
|
-
hint: "SSR, Server Functions, API Routes and more with TanStack Router"
|
|
893
|
-
}
|
|
894
|
-
].filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth)),
|
|
895
|
-
initialValue: DEFAULT_CONFIG.frontend[0]
|
|
896
|
-
});
|
|
897
|
-
if (isCancel(webFramework)) return exitCancelled("Operation cancelled");
|
|
898
|
-
result.push(webFramework);
|
|
899
|
-
}
|
|
900
|
-
if (frontendTypes.includes("native")) {
|
|
901
|
-
const nativeFramework = await select({
|
|
902
|
-
message: "Choose native",
|
|
903
|
-
options: [
|
|
904
|
-
{
|
|
905
|
-
value: "native-bare",
|
|
906
|
-
label: "Bare",
|
|
907
|
-
hint: "Bare Expo without styling library"
|
|
908
|
-
},
|
|
909
|
-
{
|
|
910
|
-
value: "native-uniwind",
|
|
911
|
-
label: "Uniwind",
|
|
912
|
-
hint: "Fastest Tailwind bindings for React Native with HeroUI Native"
|
|
913
|
-
},
|
|
914
|
-
{
|
|
915
|
-
value: "native-unistyles",
|
|
916
|
-
label: "Unistyles",
|
|
917
|
-
hint: "Consistent styling for React Native"
|
|
918
|
-
}
|
|
919
|
-
],
|
|
920
|
-
initialValue: "native-bare"
|
|
1140
|
+
while (true) {
|
|
1141
|
+
const wasFirstPrompt = isFirstPrompt();
|
|
1142
|
+
const frontendTypes = await navigableMultiselect({
|
|
1143
|
+
message: "Select project type",
|
|
1144
|
+
options: [{
|
|
1145
|
+
value: "web",
|
|
1146
|
+
label: "Web",
|
|
1147
|
+
hint: "React, Vue or Svelte Web Application"
|
|
1148
|
+
}, {
|
|
1149
|
+
value: "native",
|
|
1150
|
+
label: "Native",
|
|
1151
|
+
hint: "Create a React Native/Expo app"
|
|
1152
|
+
}],
|
|
1153
|
+
required: false,
|
|
1154
|
+
initialValues: ["web"]
|
|
921
1155
|
});
|
|
922
|
-
if (
|
|
923
|
-
|
|
1156
|
+
if (isGoBack(frontendTypes)) return GO_BACK_SYMBOL;
|
|
1157
|
+
if (isCancel$1(frontendTypes)) return exitCancelled("Operation cancelled");
|
|
1158
|
+
setIsFirstPrompt(false);
|
|
1159
|
+
const result = [];
|
|
1160
|
+
let shouldRestart = false;
|
|
1161
|
+
if (frontendTypes.includes("web")) {
|
|
1162
|
+
const webFramework = await navigableSelect({
|
|
1163
|
+
message: "Choose web",
|
|
1164
|
+
options: [
|
|
1165
|
+
{
|
|
1166
|
+
value: "tanstack-router",
|
|
1167
|
+
label: "TanStack Router",
|
|
1168
|
+
hint: "Modern and scalable routing for React Applications"
|
|
1169
|
+
},
|
|
1170
|
+
{
|
|
1171
|
+
value: "react-router",
|
|
1172
|
+
label: "React Router",
|
|
1173
|
+
hint: "A user‑obsessed, standards‑focused, multi‑strategy router"
|
|
1174
|
+
},
|
|
1175
|
+
{
|
|
1176
|
+
value: "next",
|
|
1177
|
+
label: "Next.js",
|
|
1178
|
+
hint: "The React Framework for the Web"
|
|
1179
|
+
},
|
|
1180
|
+
{
|
|
1181
|
+
value: "nuxt",
|
|
1182
|
+
label: "Nuxt",
|
|
1183
|
+
hint: "The Progressive Web Framework for Vue.js"
|
|
1184
|
+
},
|
|
1185
|
+
{
|
|
1186
|
+
value: "svelte",
|
|
1187
|
+
label: "Svelte",
|
|
1188
|
+
hint: "web development for the rest of us"
|
|
1189
|
+
},
|
|
1190
|
+
{
|
|
1191
|
+
value: "solid",
|
|
1192
|
+
label: "Solid",
|
|
1193
|
+
hint: "Simple and performant reactivity for building user interfaces"
|
|
1194
|
+
},
|
|
1195
|
+
{
|
|
1196
|
+
value: "tanstack-start",
|
|
1197
|
+
label: "TanStack Start",
|
|
1198
|
+
hint: "SSR, Server Functions, API Routes and more with TanStack Router"
|
|
1199
|
+
}
|
|
1200
|
+
].filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth)),
|
|
1201
|
+
initialValue: DEFAULT_CONFIG.frontend[0]
|
|
1202
|
+
});
|
|
1203
|
+
if (isGoBack(webFramework)) shouldRestart = true;
|
|
1204
|
+
else if (isCancel$1(webFramework)) return exitCancelled("Operation cancelled");
|
|
1205
|
+
else result.push(webFramework);
|
|
1206
|
+
}
|
|
1207
|
+
if (shouldRestart) {
|
|
1208
|
+
setIsFirstPrompt(wasFirstPrompt);
|
|
1209
|
+
continue;
|
|
1210
|
+
}
|
|
1211
|
+
if (frontendTypes.includes("native")) {
|
|
1212
|
+
const nativeFramework = await navigableSelect({
|
|
1213
|
+
message: "Choose native",
|
|
1214
|
+
options: [
|
|
1215
|
+
{
|
|
1216
|
+
value: "native-bare",
|
|
1217
|
+
label: "Bare",
|
|
1218
|
+
hint: "Bare Expo without styling library"
|
|
1219
|
+
},
|
|
1220
|
+
{
|
|
1221
|
+
value: "native-uniwind",
|
|
1222
|
+
label: "Uniwind",
|
|
1223
|
+
hint: "Fastest Tailwind bindings for React Native with HeroUI Native"
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
value: "native-unistyles",
|
|
1227
|
+
label: "Unistyles",
|
|
1228
|
+
hint: "Consistent styling for React Native"
|
|
1229
|
+
}
|
|
1230
|
+
],
|
|
1231
|
+
initialValue: "native-bare"
|
|
1232
|
+
});
|
|
1233
|
+
if (isGoBack(nativeFramework)) if (frontendTypes.includes("web")) shouldRestart = true;
|
|
1234
|
+
else {
|
|
1235
|
+
setIsFirstPrompt(wasFirstPrompt);
|
|
1236
|
+
continue;
|
|
1237
|
+
}
|
|
1238
|
+
else if (isCancel$1(nativeFramework)) return exitCancelled("Operation cancelled");
|
|
1239
|
+
else result.push(nativeFramework);
|
|
1240
|
+
}
|
|
1241
|
+
if (shouldRestart) {
|
|
1242
|
+
setIsFirstPrompt(wasFirstPrompt);
|
|
1243
|
+
continue;
|
|
1244
|
+
}
|
|
1245
|
+
return result;
|
|
924
1246
|
}
|
|
925
|
-
return result;
|
|
926
1247
|
}
|
|
927
1248
|
|
|
928
1249
|
//#endregion
|
|
929
1250
|
//#region src/prompts/git.ts
|
|
930
1251
|
async function getGitChoice(git) {
|
|
931
1252
|
if (git !== void 0) return git;
|
|
932
|
-
const response = await
|
|
1253
|
+
const response = await navigableConfirm({
|
|
933
1254
|
message: "Initialize git repository?",
|
|
934
1255
|
initialValue: DEFAULT_CONFIG.git
|
|
935
1256
|
});
|
|
936
|
-
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
1257
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
937
1258
|
return response;
|
|
938
1259
|
}
|
|
939
1260
|
|
|
@@ -941,14 +1262,71 @@ async function getGitChoice(git) {
|
|
|
941
1262
|
//#region src/prompts/install.ts
|
|
942
1263
|
async function getinstallChoice(install) {
|
|
943
1264
|
if (install !== void 0) return install;
|
|
944
|
-
const response = await
|
|
1265
|
+
const response = await navigableConfirm({
|
|
945
1266
|
message: "Install dependencies?",
|
|
946
1267
|
initialValue: DEFAULT_CONFIG.install
|
|
947
1268
|
});
|
|
948
|
-
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
1269
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
949
1270
|
return response;
|
|
950
1271
|
}
|
|
951
1272
|
|
|
1273
|
+
//#endregion
|
|
1274
|
+
//#region src/prompts/navigable-group.ts
|
|
1275
|
+
/**
|
|
1276
|
+
* Navigable group - a group of prompts that allows going back
|
|
1277
|
+
*/
|
|
1278
|
+
/**
|
|
1279
|
+
* Define a group of prompts that supports going back to previous prompts.
|
|
1280
|
+
* Returns a result object with all the values, or handles cancel/go-back navigation.
|
|
1281
|
+
*/
|
|
1282
|
+
async function navigableGroup(prompts, opts) {
|
|
1283
|
+
const results = {};
|
|
1284
|
+
const promptNames = Object.keys(prompts);
|
|
1285
|
+
let currentIndex = 0;
|
|
1286
|
+
let goingBack = false;
|
|
1287
|
+
while (currentIndex < promptNames.length) {
|
|
1288
|
+
const name = promptNames[currentIndex];
|
|
1289
|
+
const prompt = prompts[name];
|
|
1290
|
+
setIsFirstPrompt$1(currentIndex === 0);
|
|
1291
|
+
setLastPromptShownUI(false);
|
|
1292
|
+
const result = await prompt({ results })?.catch((e) => {
|
|
1293
|
+
throw e;
|
|
1294
|
+
});
|
|
1295
|
+
if (isGoBack(result)) {
|
|
1296
|
+
goingBack = true;
|
|
1297
|
+
if (currentIndex > 0) {
|
|
1298
|
+
const prevName = promptNames[currentIndex - 1];
|
|
1299
|
+
delete results[prevName];
|
|
1300
|
+
currentIndex--;
|
|
1301
|
+
continue;
|
|
1302
|
+
}
|
|
1303
|
+
goingBack = false;
|
|
1304
|
+
continue;
|
|
1305
|
+
}
|
|
1306
|
+
if (isCancel$1(result)) {
|
|
1307
|
+
if (typeof opts?.onCancel === "function") {
|
|
1308
|
+
results[name] = "canceled";
|
|
1309
|
+
opts.onCancel({ results });
|
|
1310
|
+
}
|
|
1311
|
+
setIsFirstPrompt$1(false);
|
|
1312
|
+
return results;
|
|
1313
|
+
}
|
|
1314
|
+
if (goingBack && !didLastPromptShowUI()) {
|
|
1315
|
+
if (currentIndex > 0) {
|
|
1316
|
+
const prevName = promptNames[currentIndex - 1];
|
|
1317
|
+
delete results[prevName];
|
|
1318
|
+
currentIndex--;
|
|
1319
|
+
continue;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
goingBack = false;
|
|
1323
|
+
results[name] = result;
|
|
1324
|
+
currentIndex++;
|
|
1325
|
+
}
|
|
1326
|
+
setIsFirstPrompt$1(false);
|
|
1327
|
+
return results;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
952
1330
|
//#endregion
|
|
953
1331
|
//#region src/prompts/orm.ts
|
|
954
1332
|
const ormOptions = {
|
|
@@ -972,12 +1350,12 @@ async function getORMChoice(orm, hasDatabase, database, backend, runtime) {
|
|
|
972
1350
|
if (backend === "convex") return "none";
|
|
973
1351
|
if (!hasDatabase) return "none";
|
|
974
1352
|
if (orm !== void 0) return orm;
|
|
975
|
-
const response = await
|
|
1353
|
+
const response = await navigableSelect({
|
|
976
1354
|
message: "Select ORM",
|
|
977
1355
|
options: database === "mongodb" ? [ormOptions.prisma, ormOptions.mongoose] : [ormOptions.drizzle, ormOptions.prisma],
|
|
978
1356
|
initialValue: database === "mongodb" ? "prisma" : runtime === "workers" ? "drizzle" : DEFAULT_CONFIG.orm
|
|
979
1357
|
});
|
|
980
|
-
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
1358
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
981
1359
|
return response;
|
|
982
1360
|
}
|
|
983
1361
|
|
|
@@ -985,13 +1363,13 @@ async function getORMChoice(orm, hasDatabase, database, backend, runtime) {
|
|
|
985
1363
|
//#region src/prompts/package-manager.ts
|
|
986
1364
|
async function getPackageManagerChoice(packageManager) {
|
|
987
1365
|
if (packageManager !== void 0) return packageManager;
|
|
988
|
-
const response = await
|
|
1366
|
+
const response = await navigableSelect({
|
|
989
1367
|
message: "Choose package manager",
|
|
990
1368
|
options: [
|
|
991
1369
|
{
|
|
992
1370
|
value: "npm",
|
|
993
1371
|
label: "npm",
|
|
994
|
-
hint: "
|
|
1372
|
+
hint: "not recommended"
|
|
995
1373
|
},
|
|
996
1374
|
{
|
|
997
1375
|
value: "pnpm",
|
|
@@ -1006,7 +1384,7 @@ async function getPackageManagerChoice(packageManager) {
|
|
|
1006
1384
|
],
|
|
1007
1385
|
initialValue: getUserPkgManager()
|
|
1008
1386
|
});
|
|
1009
|
-
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
1387
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
1010
1388
|
return response;
|
|
1011
1389
|
}
|
|
1012
1390
|
|
|
@@ -1016,7 +1394,7 @@ async function getPaymentsChoice(payments, auth, backend, frontends) {
|
|
|
1016
1394
|
if (payments !== void 0) return payments;
|
|
1017
1395
|
if (backend === "none") return "none";
|
|
1018
1396
|
if (!(auth === "better-auth" && backend !== "convex" && (frontends?.length === 0 || splitFrontends(frontends).web.length > 0))) return "none";
|
|
1019
|
-
const response = await
|
|
1397
|
+
const response = await navigableSelect({
|
|
1020
1398
|
message: "Select payments provider",
|
|
1021
1399
|
options: [{
|
|
1022
1400
|
value: "polar",
|
|
@@ -1029,7 +1407,7 @@ async function getPaymentsChoice(payments, auth, backend, frontends) {
|
|
|
1029
1407
|
}],
|
|
1030
1408
|
initialValue: DEFAULT_CONFIG.payments
|
|
1031
1409
|
});
|
|
1032
|
-
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
1410
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
1033
1411
|
return response;
|
|
1034
1412
|
}
|
|
1035
1413
|
|
|
@@ -1052,12 +1430,12 @@ async function getRuntimeChoice(runtime, backend) {
|
|
|
1052
1430
|
label: "Cloudflare Workers",
|
|
1053
1431
|
hint: "Edge runtime on Cloudflare's global network"
|
|
1054
1432
|
});
|
|
1055
|
-
const response = await
|
|
1433
|
+
const response = await navigableSelect({
|
|
1056
1434
|
message: "Select runtime",
|
|
1057
1435
|
options: runtimeOptions,
|
|
1058
1436
|
initialValue: DEFAULT_CONFIG.runtime
|
|
1059
1437
|
});
|
|
1060
|
-
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
1438
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
1061
1439
|
return response;
|
|
1062
1440
|
}
|
|
1063
1441
|
|
|
@@ -1095,12 +1473,12 @@ async function getServerDeploymentToAdd(runtime, existingDeployment, backend) {
|
|
|
1095
1473
|
}
|
|
1096
1474
|
if (existingDeployment && existingDeployment !== "none") return "none";
|
|
1097
1475
|
if (options.length === 0) return "none";
|
|
1098
|
-
const response = await
|
|
1476
|
+
const response = await navigableSelect({
|
|
1099
1477
|
message: "Select server deployment",
|
|
1100
1478
|
options,
|
|
1101
1479
|
initialValue: DEFAULT_CONFIG.serverDeploy
|
|
1102
1480
|
});
|
|
1103
|
-
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
1481
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
1104
1482
|
return response;
|
|
1105
1483
|
}
|
|
1106
1484
|
|
|
@@ -1122,7 +1500,7 @@ function getDeploymentDisplay(deployment) {
|
|
|
1122
1500
|
async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []) {
|
|
1123
1501
|
if (deployment !== void 0) return deployment;
|
|
1124
1502
|
if (!hasWebFrontend(frontend)) return "none";
|
|
1125
|
-
const response = await
|
|
1503
|
+
const response = await navigableSelect({
|
|
1126
1504
|
message: "Select web deployment",
|
|
1127
1505
|
options: ["cloudflare", "none"].map((deploy) => {
|
|
1128
1506
|
const { label, hint } = getDeploymentDisplay(deploy);
|
|
@@ -1134,7 +1512,7 @@ async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []
|
|
|
1134
1512
|
}),
|
|
1135
1513
|
initialValue: DEFAULT_CONFIG.webDeploy
|
|
1136
1514
|
});
|
|
1137
|
-
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
1515
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
1138
1516
|
return response;
|
|
1139
1517
|
}
|
|
1140
1518
|
async function getDeploymentToAdd(frontend, existingDeployment) {
|
|
@@ -1155,19 +1533,19 @@ async function getDeploymentToAdd(frontend, existingDeployment) {
|
|
|
1155
1533
|
hint: "Skip deployment setup"
|
|
1156
1534
|
});
|
|
1157
1535
|
if (options.length === 0) return "none";
|
|
1158
|
-
const response = await
|
|
1536
|
+
const response = await navigableSelect({
|
|
1159
1537
|
message: "Select web deployment",
|
|
1160
1538
|
options,
|
|
1161
1539
|
initialValue: DEFAULT_CONFIG.webDeploy
|
|
1162
1540
|
});
|
|
1163
|
-
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
1541
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
1164
1542
|
return response;
|
|
1165
1543
|
}
|
|
1166
1544
|
|
|
1167
1545
|
//#endregion
|
|
1168
1546
|
//#region src/prompts/config-prompts.ts
|
|
1169
1547
|
async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
1170
|
-
const result = await
|
|
1548
|
+
const result = await navigableGroup({
|
|
1171
1549
|
frontend: () => getFrontendChoice(flags.frontend, flags.backend, flags.auth),
|
|
1172
1550
|
backend: ({ results }) => getBackendFrameworkChoice(flags.backend, results.frontend),
|
|
1173
1551
|
runtime: ({ results }) => getRuntimeChoice(flags.runtime, results.backend),
|
|
@@ -1383,14 +1761,14 @@ function generateReproducibleCommand(config) {
|
|
|
1383
1761
|
|
|
1384
1762
|
//#endregion
|
|
1385
1763
|
//#region src/utils/project-directory.ts
|
|
1386
|
-
async function handleDirectoryConflict(currentPathInput
|
|
1764
|
+
async function handleDirectoryConflict(currentPathInput) {
|
|
1387
1765
|
while (true) {
|
|
1388
1766
|
const resolvedPath = path.resolve(process.cwd(), currentPathInput);
|
|
1389
1767
|
if (!(await fs.pathExists(resolvedPath) && (await fs.readdir(resolvedPath)).length > 0)) return {
|
|
1390
1768
|
finalPathInput: currentPathInput,
|
|
1391
1769
|
shouldClearDirectory: false
|
|
1392
1770
|
};
|
|
1393
|
-
if (
|
|
1771
|
+
if (isSilent()) throw new Error(`Directory "${currentPathInput}" already exists and is not empty. In silent mode, please provide a different project name or clear the directory manually.`);
|
|
1394
1772
|
log.warn(`Directory "${pc.yellow(currentPathInput)}" already exists and is not empty.`);
|
|
1395
1773
|
const action = await select({
|
|
1396
1774
|
message: "What would you like to do?",
|
|
@@ -1497,11 +1875,7 @@ const catppuccinTheme = {
|
|
|
1497
1875
|
const renderTitle = () => {
|
|
1498
1876
|
const terminalWidth = process.stdout.columns || 80;
|
|
1499
1877
|
const titleLines = TITLE_TEXT.split("\n");
|
|
1500
|
-
if (terminalWidth < Math.max(...titleLines.map((line) => line.length))) console.log(gradient(Object.values(catppuccinTheme)).multiline(`
|
|
1501
|
-
╔══════════════════╗
|
|
1502
|
-
║ Better T Stack ║
|
|
1503
|
-
╚══════════════════╝
|
|
1504
|
-
`));
|
|
1878
|
+
if (terminalWidth < Math.max(...titleLines.map((line) => line.length))) console.log(gradient(Object.values(catppuccinTheme)).multiline(`Better T Stack`));
|
|
1505
1879
|
else console.log(gradient(Object.values(catppuccinTheme)).multiline(TITLE_TEXT));
|
|
1506
1880
|
};
|
|
1507
1881
|
|
|
@@ -6799,7 +7173,6 @@ async function updateConvexPackageJson(projectDir, options) {
|
|
|
6799
7173
|
//#endregion
|
|
6800
7174
|
//#region src/helpers/core/create-project.ts
|
|
6801
7175
|
async function createProject(options, cliInput = {}) {
|
|
6802
|
-
const { silent = false } = cliInput;
|
|
6803
7176
|
const projectDir = options.projectDir;
|
|
6804
7177
|
const isConvex = options.backend === "convex";
|
|
6805
7178
|
const isSelfBackend = options.backend === "self";
|
|
@@ -6835,23 +7208,23 @@ async function createProject(options, cliInput = {}) {
|
|
|
6835
7208
|
await setupCatalogs(projectDir, options);
|
|
6836
7209
|
await createReadme(projectDir, options);
|
|
6837
7210
|
await writeBtsConfig(options);
|
|
6838
|
-
if (!
|
|
7211
|
+
if (!isSilent()) log.success("Project template successfully scaffolded!");
|
|
6839
7212
|
if (options.install) await installDependencies({
|
|
6840
7213
|
projectDir,
|
|
6841
7214
|
packageManager: options.packageManager
|
|
6842
7215
|
});
|
|
6843
7216
|
await initializeGit(projectDir, options.git);
|
|
6844
|
-
if (!
|
|
7217
|
+
if (!isSilent()) await displayPostInstallInstructions({
|
|
6845
7218
|
...options,
|
|
6846
7219
|
depsInstalled: options.install
|
|
6847
7220
|
});
|
|
6848
7221
|
return projectDir;
|
|
6849
7222
|
} catch (error) {
|
|
6850
7223
|
if (error instanceof Error) {
|
|
6851
|
-
if (!
|
|
7224
|
+
if (!isSilent()) console.error(error.stack);
|
|
6852
7225
|
exitWithError(`Error during project creation: ${error.message}`);
|
|
6853
7226
|
} else {
|
|
6854
|
-
if (!
|
|
7227
|
+
if (!isSilent()) console.error(error);
|
|
6855
7228
|
exitWithError(`An unexpected error occurred: ${String(error)}`);
|
|
6856
7229
|
}
|
|
6857
7230
|
}
|
|
@@ -6861,139 +7234,169 @@ async function createProject(options, cliInput = {}) {
|
|
|
6861
7234
|
//#region src/helpers/core/command-handlers.ts
|
|
6862
7235
|
async function createProjectHandler(input, options = {}) {
|
|
6863
7236
|
const { silent = false } = options;
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
...input,
|
|
6928
|
-
projectDirectory: input.projectName
|
|
6929
|
-
};
|
|
6930
|
-
const providedFlags = getProvidedFlags(originalInput);
|
|
6931
|
-
let cliInput = originalInput;
|
|
6932
|
-
if (input.template && input.template !== "none") {
|
|
6933
|
-
const templateConfig = getTemplateConfig(input.template);
|
|
6934
|
-
if (templateConfig) {
|
|
6935
|
-
const templateName = input.template.toUpperCase();
|
|
6936
|
-
const templateDescription = getTemplateDescription(input.template);
|
|
6937
|
-
if (!silent) {
|
|
6938
|
-
log.message(pc.bold(pc.cyan(`Using template: ${pc.white(templateName)}`)));
|
|
6939
|
-
log.message(pc.dim(` ${templateDescription}`));
|
|
7237
|
+
return runWithContextAsync({ silent }, async () => {
|
|
7238
|
+
const startTime = Date.now();
|
|
7239
|
+
const timeScaffolded = (/* @__PURE__ */ new Date()).toISOString();
|
|
7240
|
+
try {
|
|
7241
|
+
if (!isSilent() && input.renderTitle !== false) renderTitle();
|
|
7242
|
+
if (!isSilent()) intro(pc.magenta("Creating a new Better-T-Stack project"));
|
|
7243
|
+
if (!isSilent() && input.yolo) consola.fatal("YOLO mode enabled - skipping checks. Things may break!");
|
|
7244
|
+
let currentPathInput;
|
|
7245
|
+
if (input.yes && input.projectName) currentPathInput = input.projectName;
|
|
7246
|
+
else if (input.yes) {
|
|
7247
|
+
const defaultConfig = getDefaultConfig();
|
|
7248
|
+
let defaultName = defaultConfig.relativePath;
|
|
7249
|
+
let counter = 1;
|
|
7250
|
+
while (await fs.pathExists(path.resolve(process.cwd(), defaultName)) && (await fs.readdir(path.resolve(process.cwd(), defaultName))).length > 0) {
|
|
7251
|
+
defaultName = `${defaultConfig.projectName}-${counter}`;
|
|
7252
|
+
counter++;
|
|
7253
|
+
}
|
|
7254
|
+
currentPathInput = defaultName;
|
|
7255
|
+
} else currentPathInput = await getProjectName(input.projectName);
|
|
7256
|
+
let finalPathInput;
|
|
7257
|
+
let shouldClearDirectory;
|
|
7258
|
+
try {
|
|
7259
|
+
if (input.directoryConflict) {
|
|
7260
|
+
const result = await handleDirectoryConflictProgrammatically(currentPathInput, input.directoryConflict);
|
|
7261
|
+
finalPathInput = result.finalPathInput;
|
|
7262
|
+
shouldClearDirectory = result.shouldClearDirectory;
|
|
7263
|
+
} else {
|
|
7264
|
+
const result = await handleDirectoryConflict(currentPathInput);
|
|
7265
|
+
finalPathInput = result.finalPathInput;
|
|
7266
|
+
shouldClearDirectory = result.shouldClearDirectory;
|
|
7267
|
+
}
|
|
7268
|
+
} catch (error) {
|
|
7269
|
+
if (error instanceof UserCancelledError || error instanceof CLIError) throw error;
|
|
7270
|
+
return {
|
|
7271
|
+
success: false,
|
|
7272
|
+
projectConfig: {
|
|
7273
|
+
projectName: "",
|
|
7274
|
+
projectDir: "",
|
|
7275
|
+
relativePath: "",
|
|
7276
|
+
database: "none",
|
|
7277
|
+
orm: "none",
|
|
7278
|
+
backend: "none",
|
|
7279
|
+
runtime: "none",
|
|
7280
|
+
frontend: [],
|
|
7281
|
+
addons: [],
|
|
7282
|
+
examples: [],
|
|
7283
|
+
auth: "none",
|
|
7284
|
+
payments: "none",
|
|
7285
|
+
git: false,
|
|
7286
|
+
packageManager: "npm",
|
|
7287
|
+
install: false,
|
|
7288
|
+
dbSetup: "none",
|
|
7289
|
+
api: "none",
|
|
7290
|
+
webDeploy: "none",
|
|
7291
|
+
serverDeploy: "none"
|
|
7292
|
+
},
|
|
7293
|
+
reproducibleCommand: "",
|
|
7294
|
+
timeScaffolded,
|
|
7295
|
+
elapsedTimeMs: Date.now() - startTime,
|
|
7296
|
+
projectDirectory: "",
|
|
7297
|
+
relativePath: "",
|
|
7298
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7299
|
+
};
|
|
6940
7300
|
}
|
|
6941
|
-
const
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
...userOverrides,
|
|
6946
|
-
template: input.template,
|
|
6947
|
-
projectDirectory: originalInput.projectDirectory
|
|
7301
|
+
const { finalResolvedPath, finalBaseName } = await setupProjectDirectory(finalPathInput, shouldClearDirectory);
|
|
7302
|
+
const originalInput = {
|
|
7303
|
+
...input,
|
|
7304
|
+
projectDirectory: input.projectName
|
|
6948
7305
|
};
|
|
7306
|
+
const providedFlags = getProvidedFlags(originalInput);
|
|
7307
|
+
let cliInput = originalInput;
|
|
7308
|
+
if (input.template && input.template !== "none") {
|
|
7309
|
+
const templateConfig = getTemplateConfig(input.template);
|
|
7310
|
+
if (templateConfig) {
|
|
7311
|
+
const templateName = input.template.toUpperCase();
|
|
7312
|
+
const templateDescription = getTemplateDescription(input.template);
|
|
7313
|
+
if (!isSilent()) {
|
|
7314
|
+
log.message(pc.bold(pc.cyan(`Using template: ${pc.white(templateName)}`)));
|
|
7315
|
+
log.message(pc.dim(` ${templateDescription}`));
|
|
7316
|
+
}
|
|
7317
|
+
const userOverrides = {};
|
|
7318
|
+
for (const [key, value] of Object.entries(originalInput)) if (value !== void 0) userOverrides[key] = value;
|
|
7319
|
+
cliInput = {
|
|
7320
|
+
...templateConfig,
|
|
7321
|
+
...userOverrides,
|
|
7322
|
+
template: input.template,
|
|
7323
|
+
projectDirectory: originalInput.projectDirectory
|
|
7324
|
+
};
|
|
7325
|
+
}
|
|
7326
|
+
}
|
|
7327
|
+
let config;
|
|
7328
|
+
if (cliInput.yes) {
|
|
7329
|
+
const flagConfig = processProvidedFlagsWithoutValidation(cliInput, finalBaseName);
|
|
7330
|
+
config = {
|
|
7331
|
+
...getDefaultConfig(),
|
|
7332
|
+
...flagConfig,
|
|
7333
|
+
projectName: finalBaseName,
|
|
7334
|
+
projectDir: finalResolvedPath,
|
|
7335
|
+
relativePath: finalPathInput
|
|
7336
|
+
};
|
|
7337
|
+
validateConfigCompatibility(config, providedFlags, cliInput);
|
|
7338
|
+
if (!isSilent()) {
|
|
7339
|
+
log.info(pc.yellow("Using default/flag options (config prompts skipped):"));
|
|
7340
|
+
log.message(displayConfig(config));
|
|
7341
|
+
}
|
|
7342
|
+
} else {
|
|
7343
|
+
const flagConfig = processAndValidateFlags(cliInput, providedFlags, finalBaseName);
|
|
7344
|
+
const { projectName: _projectNameFromFlags, ...otherFlags } = flagConfig;
|
|
7345
|
+
if (!isSilent() && Object.keys(otherFlags).length > 0) {
|
|
7346
|
+
log.info(pc.yellow("Using these pre-selected options:"));
|
|
7347
|
+
log.message(displayConfig(otherFlags));
|
|
7348
|
+
log.message("");
|
|
7349
|
+
}
|
|
7350
|
+
config = await gatherConfig(flagConfig, finalBaseName, finalResolvedPath, finalPathInput);
|
|
7351
|
+
}
|
|
7352
|
+
await createProject(config, { manualDb: cliInput.manualDb ?? input.manualDb });
|
|
7353
|
+
const reproducibleCommand = generateReproducibleCommand(config);
|
|
7354
|
+
if (!isSilent()) log.success(pc.blue(`You can reproduce this setup with the following command:\n${reproducibleCommand}`));
|
|
7355
|
+
await trackProjectCreation(config, input.disableAnalytics);
|
|
7356
|
+
const elapsedTimeMs = Date.now() - startTime;
|
|
7357
|
+
if (!isSilent()) {
|
|
7358
|
+
const elapsedTimeInSeconds = (elapsedTimeMs / 1e3).toFixed(2);
|
|
7359
|
+
outro(pc.magenta(`Project created successfully in ${pc.bold(elapsedTimeInSeconds)} seconds!`));
|
|
7360
|
+
}
|
|
7361
|
+
return {
|
|
7362
|
+
success: true,
|
|
7363
|
+
projectConfig: config,
|
|
7364
|
+
reproducibleCommand,
|
|
7365
|
+
timeScaffolded,
|
|
7366
|
+
elapsedTimeMs,
|
|
7367
|
+
projectDirectory: config.projectDir,
|
|
7368
|
+
relativePath: config.relativePath
|
|
7369
|
+
};
|
|
7370
|
+
} catch (error) {
|
|
7371
|
+
if (error instanceof UserCancelledError) {
|
|
7372
|
+
if (isSilent()) return {
|
|
7373
|
+
success: false,
|
|
7374
|
+
error: error.message,
|
|
7375
|
+
projectConfig: {},
|
|
7376
|
+
reproducibleCommand: "",
|
|
7377
|
+
timeScaffolded,
|
|
7378
|
+
elapsedTimeMs: Date.now() - startTime,
|
|
7379
|
+
projectDirectory: "",
|
|
7380
|
+
relativePath: ""
|
|
7381
|
+
};
|
|
7382
|
+
return;
|
|
7383
|
+
}
|
|
7384
|
+
if (error instanceof CLIError) {
|
|
7385
|
+
if (isSilent()) return {
|
|
7386
|
+
success: false,
|
|
7387
|
+
error: error.message,
|
|
7388
|
+
projectConfig: {},
|
|
7389
|
+
reproducibleCommand: "",
|
|
7390
|
+
timeScaffolded,
|
|
7391
|
+
elapsedTimeMs: Date.now() - startTime,
|
|
7392
|
+
projectDirectory: "",
|
|
7393
|
+
relativePath: ""
|
|
7394
|
+
};
|
|
7395
|
+
throw error;
|
|
7396
|
+
}
|
|
7397
|
+
throw error;
|
|
6949
7398
|
}
|
|
6950
|
-
}
|
|
6951
|
-
let config;
|
|
6952
|
-
if (cliInput.yes) {
|
|
6953
|
-
const flagConfig = processProvidedFlagsWithoutValidation(cliInput, finalBaseName);
|
|
6954
|
-
config = {
|
|
6955
|
-
...getDefaultConfig(),
|
|
6956
|
-
...flagConfig,
|
|
6957
|
-
projectName: finalBaseName,
|
|
6958
|
-
projectDir: finalResolvedPath,
|
|
6959
|
-
relativePath: finalPathInput
|
|
6960
|
-
};
|
|
6961
|
-
validateConfigCompatibility(config, providedFlags, cliInput);
|
|
6962
|
-
if (!silent) {
|
|
6963
|
-
log.info(pc.yellow("Using default/flag options (config prompts skipped):"));
|
|
6964
|
-
log.message(displayConfig(config));
|
|
6965
|
-
}
|
|
6966
|
-
} else {
|
|
6967
|
-
const flagConfig = processAndValidateFlags(cliInput, providedFlags, finalBaseName);
|
|
6968
|
-
const { projectName: _projectNameFromFlags, ...otherFlags } = flagConfig;
|
|
6969
|
-
if (!silent && Object.keys(otherFlags).length > 0) {
|
|
6970
|
-
log.info(pc.yellow("Using these pre-selected options:"));
|
|
6971
|
-
log.message(displayConfig(otherFlags));
|
|
6972
|
-
log.message("");
|
|
6973
|
-
}
|
|
6974
|
-
config = await gatherConfig(flagConfig, finalBaseName, finalResolvedPath, finalPathInput);
|
|
6975
|
-
}
|
|
6976
|
-
await createProject(config, {
|
|
6977
|
-
manualDb: cliInput.manualDb ?? input.manualDb,
|
|
6978
|
-
silent
|
|
6979
7399
|
});
|
|
6980
|
-
const reproducibleCommand = generateReproducibleCommand(config);
|
|
6981
|
-
if (!silent) log.success(pc.blue(`You can reproduce this setup with the following command:\n${reproducibleCommand}`));
|
|
6982
|
-
await trackProjectCreation(config, input.disableAnalytics);
|
|
6983
|
-
const elapsedTimeMs = Date.now() - startTime;
|
|
6984
|
-
if (!silent) {
|
|
6985
|
-
const elapsedTimeInSeconds = (elapsedTimeMs / 1e3).toFixed(2);
|
|
6986
|
-
outro(pc.magenta(`Project created successfully in ${pc.bold(elapsedTimeInSeconds)} seconds!`));
|
|
6987
|
-
}
|
|
6988
|
-
return {
|
|
6989
|
-
success: true,
|
|
6990
|
-
projectConfig: config,
|
|
6991
|
-
reproducibleCommand,
|
|
6992
|
-
timeScaffolded,
|
|
6993
|
-
elapsedTimeMs,
|
|
6994
|
-
projectDirectory: config.projectDir,
|
|
6995
|
-
relativePath: config.relativePath
|
|
6996
|
-
};
|
|
6997
7400
|
}
|
|
6998
7401
|
async function handleDirectoryConflictProgrammatically(currentPathInput, strategy) {
|
|
6999
7402
|
const currentPath = path.resolve(process.cwd(), currentPathInput);
|
|
@@ -7088,6 +7491,8 @@ async function addAddonsHandler(input) {
|
|
|
7088
7491
|
else log.info(`Run ${pc.bold(`${packageManager} install`)} to install dependencies`);
|
|
7089
7492
|
outro("Add command completed successfully!");
|
|
7090
7493
|
} catch (error) {
|
|
7494
|
+
if (error instanceof UserCancelledError) return;
|
|
7495
|
+
if (error instanceof CLIError) throw error;
|
|
7091
7496
|
handleError(error, "Failed to add addons or deployment");
|
|
7092
7497
|
}
|
|
7093
7498
|
}
|