create-kubojs 0.2.0 → 0.2.1
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 +23 -21
- package/dist/cli.mjs +2 -2
- package/dist/index.d.mts +15 -49
- package/dist/index.mjs +1 -1
- package/dist/{src-CNqGe5RA.mjs → src-CvkZZND8.mjs} +431 -1459
- package/package.json +3 -3
|
@@ -11,18 +11,40 @@ import path from "node:path";
|
|
|
11
11
|
import envPaths from "env-paths";
|
|
12
12
|
import fs from "fs-extra";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
|
-
import { desktopWebFrontends as desktopWebFrontends$3 } from "@kubojs/types";
|
|
15
|
-
import { EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, GeneratorError, GeneratorError as GeneratorError$1, TEMPLATE_COUNT, VirtualFileSystem, VirtualFileSystem as VirtualFileSystem$1, dependencyVersionMap, generate, generate as generate$1, generateReproducibleCommand, processAddonTemplates, processAddonsDeps,
|
|
14
|
+
import { desktopWebFrontends as desktopWebFrontends$3, getPaymentCompatibilityIssue, normalizePayments } from "@kubojs/types";
|
|
15
|
+
import { EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, GeneratorError, GeneratorError as GeneratorError$1, TEMPLATE_COUNT, VirtualFileSystem, VirtualFileSystem as VirtualFileSystem$1, dependencyVersionMap, generate, generate as generate$1, generateReproducibleCommand, processAddonTemplates, processAddonsDeps, processEnvVariables, processPackageConfigs, processTemplateString, processTurboConfig, processVitePlusConfig } from "@kubojs/template-generator";
|
|
16
16
|
import { consola, createConsola } from "consola";
|
|
17
17
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
18
18
|
import gradient from "gradient-string";
|
|
19
19
|
import { $, execa } from "execa";
|
|
20
20
|
import { writeTree } from "@kubojs/template-generator/fs-writer";
|
|
21
|
-
import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
|
|
21
|
+
import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, TextPrompt, isCancel as isCancel$1, isCancel as isCancel$2 } from "@clack/core";
|
|
22
22
|
import { applyEdits, modify, parse } from "jsonc-parser";
|
|
23
23
|
import os from "node:os";
|
|
24
24
|
import { format } from "oxfmt";
|
|
25
25
|
import { Generator, configSchema } from "@tanstack/router-generator";
|
|
26
|
+
//#region src/utils/cli-colors.ts
|
|
27
|
+
const KUBO_COLORS = {
|
|
28
|
+
signal: "#c49314",
|
|
29
|
+
bright: "#d6a72b",
|
|
30
|
+
orange: "#e08a2e",
|
|
31
|
+
cream: "#f2ede0"
|
|
32
|
+
};
|
|
33
|
+
const supportsTrueColor = process.env.NO_COLOR === void 0 && process.env.FORCE_COLOR !== "0" && (process.env.FORCE_COLOR !== void 0 || process.stdout.isTTY === true);
|
|
34
|
+
function createColor(hex, fallback) {
|
|
35
|
+
if (!supportsTrueColor) return fallback;
|
|
36
|
+
const red = Number.parseInt(hex.slice(1, 3), 16);
|
|
37
|
+
const green = Number.parseInt(hex.slice(3, 5), 16);
|
|
38
|
+
const blue = Number.parseInt(hex.slice(5, 7), 16);
|
|
39
|
+
return (text) => `\u001B[38;2;${red};${green};${blue}m${text}\u001B[39m`;
|
|
40
|
+
}
|
|
41
|
+
const cliColors = {
|
|
42
|
+
signal: createColor(KUBO_COLORS.signal, pc.yellow),
|
|
43
|
+
bright: createColor(KUBO_COLORS.bright, pc.yellow),
|
|
44
|
+
orange: createColor(KUBO_COLORS.orange, pc.yellow),
|
|
45
|
+
cream: createColor(KUBO_COLORS.cream, pc.white)
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
26
48
|
//#region src/utils/get-package-manager.ts
|
|
27
49
|
const getUserPkgManager = () => {
|
|
28
50
|
const userAgent = process.env.npm_config_user_agent;
|
|
@@ -42,7 +64,7 @@ const DEFAULT_CONFIG_BASE = {
|
|
|
42
64
|
database: "sqlite",
|
|
43
65
|
orm: "drizzle",
|
|
44
66
|
auth: "better-auth",
|
|
45
|
-
payments:
|
|
67
|
+
payments: [],
|
|
46
68
|
observability: ["getmonitor"],
|
|
47
69
|
communication: "none",
|
|
48
70
|
addons: ["turborepo"],
|
|
@@ -63,6 +85,8 @@ function getDefaultConfig() {
|
|
|
63
85
|
projectDir: path.resolve(process.cwd(), DEFAULT_CONFIG_BASE.projectName),
|
|
64
86
|
packageManager: getUserPkgManager(),
|
|
65
87
|
frontend: [...DEFAULT_CONFIG_BASE.frontend],
|
|
88
|
+
payments: [...DEFAULT_CONFIG_BASE.payments],
|
|
89
|
+
observability: [...DEFAULT_CONFIG_BASE.observability],
|
|
66
90
|
addons: [...DEFAULT_CONFIG_BASE.addons],
|
|
67
91
|
examples: [...DEFAULT_CONFIG_BASE.examples],
|
|
68
92
|
testing: [...DEFAULT_CONFIG_BASE.testing]
|
|
@@ -82,17 +106,12 @@ const ADDON_COMPATIBILITY = {
|
|
|
82
106
|
husky: [],
|
|
83
107
|
lefthook: [],
|
|
84
108
|
turborepo: [],
|
|
85
|
-
nx: [],
|
|
86
109
|
"vite-plus": [],
|
|
87
|
-
starlight: [],
|
|
88
|
-
ultracite: [],
|
|
89
110
|
mcp: [],
|
|
90
111
|
oxlint: [],
|
|
91
|
-
fumadocs: [],
|
|
92
112
|
opentui: [],
|
|
93
|
-
wxt: [],
|
|
94
113
|
skills: [],
|
|
95
|
-
|
|
114
|
+
"s3-storage": [],
|
|
96
115
|
none: []
|
|
97
116
|
};
|
|
98
117
|
//#endregion
|
|
@@ -429,24 +448,26 @@ const TITLE_TEXT = `
|
|
|
429
448
|
██║ ██╗╚██████╔╝██████╔╝╚██████╔╝
|
|
430
449
|
╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝
|
|
431
450
|
`;
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
451
|
+
const KUBO_TITLE_COLORS = [
|
|
452
|
+
"#D6A72B",
|
|
453
|
+
"#F5D76E",
|
|
454
|
+
"#E0B43E",
|
|
455
|
+
"#E08A2E",
|
|
456
|
+
"#E0B43E",
|
|
457
|
+
"#E8A43A",
|
|
458
|
+
"#E08A2E",
|
|
459
|
+
"#E08A2E",
|
|
460
|
+
"#C49314",
|
|
461
|
+
"#E8C978",
|
|
462
|
+
"#E5D3A5"
|
|
463
|
+
];
|
|
445
464
|
const renderTitle = () => {
|
|
446
465
|
const terminalWidth = process.stdout.columns || 80;
|
|
447
466
|
const titleLines = TITLE_TEXT.split("\n");
|
|
448
|
-
if (terminalWidth < Math.max(...titleLines.map((line) => line.length)))
|
|
449
|
-
|
|
467
|
+
if (terminalWidth < Math.max(...titleLines.map((line) => line.length))) {
|
|
468
|
+
const simplifiedTitle = `Kubo`;
|
|
469
|
+
console.log(gradient([...KUBO_TITLE_COLORS]).multiline(simplifiedTitle));
|
|
470
|
+
} else console.log(gradient([...KUBO_TITLE_COLORS]).multiline(TITLE_TEXT));
|
|
450
471
|
};
|
|
451
472
|
//#endregion
|
|
452
473
|
//#region src/commands/history.ts
|
|
@@ -493,10 +514,10 @@ async function historyHandler(input) {
|
|
|
493
514
|
return;
|
|
494
515
|
}
|
|
495
516
|
renderTitle();
|
|
496
|
-
intro(
|
|
517
|
+
intro(cliColors.signal(`Project History (${entries.length} entries)`));
|
|
497
518
|
for (const [index, entry] of entries.entries()) {
|
|
498
519
|
const num = pc.dim(`${index + 1}.`);
|
|
499
|
-
const name =
|
|
520
|
+
const name = cliColors.bright(pc.bold(entry.projectName));
|
|
500
521
|
const stack = pc.dim(formatStackSummary(entry));
|
|
501
522
|
log.message(`${num} ${name}`);
|
|
502
523
|
log.message(` ${pc.dim("Created:")} ${formatDate(entry.createdAt)}`);
|
|
@@ -529,7 +550,7 @@ async function openExternalUrl(url, successMessage) {
|
|
|
529
550
|
if ((await Result.tryPromise({
|
|
530
551
|
try: () => openUrl(url),
|
|
531
552
|
catch: () => null
|
|
532
|
-
})).isOk()) log.success(
|
|
553
|
+
})).isOk()) log.success(cliColors.signal(successMessage));
|
|
533
554
|
else log.message(`Please visit ${url}`);
|
|
534
555
|
}
|
|
535
556
|
async function openDocsCommand() {
|
|
@@ -557,17 +578,9 @@ const WEB_FRAMEWORKS = [
|
|
|
557
578
|
];
|
|
558
579
|
//#endregion
|
|
559
580
|
//#region src/utils/compatibility-rules.ts
|
|
560
|
-
const TASK_RUNNER_ADDONS$1 = [
|
|
561
|
-
"turborepo",
|
|
562
|
-
"nx",
|
|
563
|
-
"vite-plus"
|
|
564
|
-
];
|
|
581
|
+
const TASK_RUNNER_ADDONS$1 = ["turborepo", "vite-plus"];
|
|
565
582
|
/** Mutually exclusive code-quality linters (one slot). */
|
|
566
|
-
const LINTER_ADDONS = [
|
|
567
|
-
"biome",
|
|
568
|
-
"oxlint",
|
|
569
|
-
"ultracite"
|
|
570
|
-
];
|
|
583
|
+
const LINTER_ADDONS = ["biome", "oxlint"];
|
|
571
584
|
const STATIC_DESKTOP_ADDONS = ["tauri", "electrobun"];
|
|
572
585
|
function isLinterAddon(addon) {
|
|
573
586
|
return LINTER_ADDONS.includes(addon);
|
|
@@ -639,23 +652,6 @@ const FULLSTACK_FRONTENDS$1 = [
|
|
|
639
652
|
"svelte",
|
|
640
653
|
"astro"
|
|
641
654
|
];
|
|
642
|
-
const EVLOG_SERVER_BACKENDS = [
|
|
643
|
-
"hono",
|
|
644
|
-
"express",
|
|
645
|
-
"fastify",
|
|
646
|
-
"elysia"
|
|
647
|
-
];
|
|
648
|
-
const EVLOG_FULLSTACK_FRONTENDS = FULLSTACK_FRONTENDS$1;
|
|
649
|
-
const evlogCompatibilityMessage = "evlog addon supports Hono, Express, Fastify, Elysia, or backend self with Next.js, TanStack Start, Nuxt, SvelteKit, or Astro. Convex and backend none are not supported yet.";
|
|
650
|
-
function supportsEvlogAddon(frontend = [], backend, _runtime) {
|
|
651
|
-
if (!backend) return true;
|
|
652
|
-
if (EVLOG_SERVER_BACKENDS.includes(backend)) return true;
|
|
653
|
-
if (backend === "self") {
|
|
654
|
-
if (frontend.length === 0) return true;
|
|
655
|
-
return frontend.some((f) => EVLOG_FULLSTACK_FRONTENDS.includes(f));
|
|
656
|
-
}
|
|
657
|
-
return false;
|
|
658
|
-
}
|
|
659
655
|
function validateSelfBackendCompatibility(providedFlags, options, config) {
|
|
660
656
|
const backend = config.backend || options.backend;
|
|
661
657
|
const frontends = config.frontend || options.frontend || [];
|
|
@@ -701,7 +697,7 @@ function isFrontendAllowedWithBackend(frontend, backend, auth) {
|
|
|
701
697
|
function supportsConvexBetterAuth(frontends = []) {
|
|
702
698
|
return frontends.some((frontend) => CONVEX_BETTER_AUTH_SUPPORTED_FRONTENDS.includes(frontend));
|
|
703
699
|
}
|
|
704
|
-
function allowedApisForFrontends(frontends = []) {
|
|
700
|
+
function allowedApisForFrontends(frontends = [], backend) {
|
|
705
701
|
const includesNuxt = frontends.includes("nuxt");
|
|
706
702
|
const includesSvelte = frontends.includes("svelte");
|
|
707
703
|
const includesSolid = frontends.includes("solid");
|
|
@@ -711,9 +707,17 @@ function allowedApisForFrontends(frontends = []) {
|
|
|
711
707
|
"orpc",
|
|
712
708
|
"none"
|
|
713
709
|
];
|
|
714
|
-
if (
|
|
710
|
+
if (backend === "hono") base.splice(2, 0, "orval");
|
|
711
|
+
if (includesNuxt || includesSvelte || includesSolid || includesAstro) return backend === "hono" ? [
|
|
712
|
+
"orpc",
|
|
713
|
+
"orval",
|
|
714
|
+
"none"
|
|
715
|
+
] : ["orpc", "none"];
|
|
715
716
|
return base;
|
|
716
717
|
}
|
|
718
|
+
function isApiCompatibleWithBackend(api, backend) {
|
|
719
|
+
return api !== "orval" || backend === "hono";
|
|
720
|
+
}
|
|
717
721
|
function isExampleTodoAllowed(backend, database, api) {
|
|
718
722
|
if (backend === "convex") return true;
|
|
719
723
|
if (database === "none" || api === "none") return false;
|
|
@@ -773,10 +777,6 @@ function validateDockerWebDeployDesktopAddons(webDeploy, addons, frontend, backe
|
|
|
773
777
|
return validationErr$1(`'--web-deploy docker' is not compatible with the ${desktopAddons.join(", ")} addon on '${affected}' because desktop addons switch the web build to a static export, which the docker image cannot serve. Remove the addon or use a static-serving frontend (tanstack-router, solid).`);
|
|
774
778
|
}
|
|
775
779
|
function validateAddonCompatibility(addon, frontend, auth, backend, runtime) {
|
|
776
|
-
if (addon === "evlog" && !supportsEvlogAddon(frontend, backend, runtime)) return {
|
|
777
|
-
isCompatible: false,
|
|
778
|
-
reason: evlogCompatibilityMessage
|
|
779
|
-
};
|
|
780
780
|
if (STATIC_DESKTOP_ADDONS.includes(addon) && auth === "clerk" && frontend.includes("react-router")) return {
|
|
781
781
|
isCompatible: false,
|
|
782
782
|
reason: `${addon} addon forces React Router into a static export, but Clerk on React Router requires SSR middleware. Remove the addon or use a different auth/frontend.`
|
|
@@ -789,9 +789,13 @@ function validateAddonCompatibility(addon, frontend, auth, backend, runtime) {
|
|
|
789
789
|
isCompatible: false,
|
|
790
790
|
reason: "tauri addon is not compatible with Convex Better Auth on Next.js or TanStack Start because those templates use server auth bootstrap and cannot be exported as static desktop assets."
|
|
791
791
|
};
|
|
792
|
+
if (!(addon in ADDON_COMPATIBILITY)) return {
|
|
793
|
+
isCompatible: false,
|
|
794
|
+
reason: `${addon} is not a supported addon`
|
|
795
|
+
};
|
|
792
796
|
const compatibleFrontends = ADDON_COMPATIBILITY[addon];
|
|
793
797
|
if (compatibleFrontends.length > 0) {
|
|
794
|
-
if (!frontend.some((f) => compatibleFrontends.
|
|
798
|
+
if (!frontend.some((f) => compatibleFrontends.some((compatibleFrontend) => compatibleFrontend === f))) return {
|
|
795
799
|
isCompatible: false,
|
|
796
800
|
reason: `${addon} addon requires one of these frontends: ${compatibleFrontends.join(", ")}`
|
|
797
801
|
};
|
|
@@ -808,8 +812,8 @@ function getCompatibleAddons(allAddons, frontend, existingAddons = [], auth, bac
|
|
|
808
812
|
});
|
|
809
813
|
}
|
|
810
814
|
function validateAddonsAgainstFrontends(addons = [], frontends = [], auth, backend, runtime) {
|
|
811
|
-
if (addons.filter((addon) => TASK_RUNNER_ADDONS$1.includes(addon)).length > 1) return validationErr$1("Cannot combine 'turborepo'
|
|
812
|
-
if ([...new Set(addons.filter((addon) => LINTER_ADDONS.includes(addon)))].length > 1) return validationErr$1("Cannot combine 'biome'
|
|
815
|
+
if (addons.filter((addon) => TASK_RUNNER_ADDONS$1.includes(addon)).length > 1) return validationErr$1("Cannot combine 'turborepo' and 'vite-plus' addons. Choose one task runner.");
|
|
816
|
+
if ([...new Set(addons.filter((addon) => LINTER_ADDONS.includes(addon)))].length > 1) return validationErr$1("Cannot combine 'biome' and 'oxlint' addons. Choose one code-quality linter.");
|
|
813
817
|
for (const addon of addons) {
|
|
814
818
|
if (addon === "none") continue;
|
|
815
819
|
const { isCompatible, reason } = validateAddonCompatibility(addon, frontends, auth, backend, runtime);
|
|
@@ -822,21 +826,29 @@ function validateAddonsAgainstConfig(addons = [], config) {
|
|
|
822
826
|
}
|
|
823
827
|
function validateCommunicationCompatibility(communication, backend) {
|
|
824
828
|
if (!communication || communication === "none") return Result.ok(void 0);
|
|
825
|
-
if ((communication === "resend" || communication === "notifique") && backend === "none") return validationErr$1(`${communication === "notifique" ? "Notifique" : "Resend"} communication requires a server backend. Please choose a backend or use '--communication none'.`);
|
|
829
|
+
if ((communication === "resend" || communication === "notifique" || communication === "arara") && backend === "none") return validationErr$1(`${communication === "notifique" ? "Notifique" : communication === "arara" ? "AraraHQ" : "Resend"} communication requires a server backend. Please choose a backend or use '--communication none'.`);
|
|
830
|
+
return Result.ok(void 0);
|
|
831
|
+
}
|
|
832
|
+
function validateAraraRuntimeCompatibility(communication, backend, runtime, serverDeploy) {
|
|
833
|
+
if (communication !== "arara" || backend === "convex") return Result.ok(void 0);
|
|
834
|
+
if (runtime === "workers" || serverDeploy === "cloudflare") return validationErr$1("AraraHQ requires the official Node SDK and is not compatible with Edge/Workers runtimes. Use a Node/Bun server deployment or Convex Node Action.");
|
|
826
835
|
return Result.ok(void 0);
|
|
827
836
|
}
|
|
828
837
|
function validatePaymentsCompatibility(payments, auth, backend, frontends = [], orm, database) {
|
|
829
|
-
if (!payments || payments ===
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
if (
|
|
838
|
+
if (!payments || payments.length === 0) return Result.ok(void 0);
|
|
839
|
+
for (const provider of payments) {
|
|
840
|
+
const issue = getPaymentCompatibilityIssue({
|
|
841
|
+
provider,
|
|
842
|
+
backend,
|
|
843
|
+
frontends,
|
|
844
|
+
database,
|
|
845
|
+
orm
|
|
846
|
+
});
|
|
847
|
+
const label = provider === "stripe" ? "Stripe" : "AbacatePay";
|
|
848
|
+
if (issue === "convex-unsupported") return validationErr$1(`${label} payments is not compatible with '--backend convex'. Please use a server backend or backend 'self'.`);
|
|
849
|
+
if (issue === "requires-web-frontend") return validationErr$1(`${label} payments requires a web frontend. Please choose next, tanstack-start, tanstack-router, react-router, nuxt, svelte, solid, or astro.`);
|
|
850
|
+
if (issue === "native-only-unsupported") return validationErr$1(`${label} payments is not compatible with native-only frontends. Please add a supported web frontend or leave payments unselected.`);
|
|
851
|
+
if (issue === "sql-database-required") return validationErr$1("AbacatePay payments v1 requires a SQL database with Drizzle or Prisma. MongoDB and Mongoose are not supported.");
|
|
840
852
|
}
|
|
841
853
|
return Result.ok(void 0);
|
|
842
854
|
}
|
|
@@ -871,12 +883,12 @@ function isGoBack(value) {
|
|
|
871
883
|
* These prompts return GO_BACK_SYMBOL when 'b' is pressed (instead of canceling)
|
|
872
884
|
*/
|
|
873
885
|
const unicode = process.platform !== "win32";
|
|
874
|
-
const S_STEP_ACTIVE = unicode ? "◆" : "*";
|
|
886
|
+
const S_STEP_ACTIVE$1 = unicode ? "◆" : "*";
|
|
875
887
|
const S_STEP_CANCEL = unicode ? "■" : "x";
|
|
876
888
|
const S_STEP_ERROR = unicode ? "▲" : "x";
|
|
877
889
|
const S_STEP_SUBMIT = unicode ? "◇" : "o";
|
|
878
|
-
const S_BAR = unicode ? "│" : "|";
|
|
879
|
-
const S_BAR_END = unicode ? "└" : "—";
|
|
890
|
+
const S_BAR$1 = unicode ? "│" : "|";
|
|
891
|
+
const S_BAR_END$1 = unicode ? "└" : "—";
|
|
880
892
|
const S_RADIO_ACTIVE = unicode ? "●" : ">";
|
|
881
893
|
const S_RADIO_INACTIVE = unicode ? "○" : " ";
|
|
882
894
|
const S_CHECKBOX_ACTIVE = unicode ? "◻" : "[•]";
|
|
@@ -885,10 +897,10 @@ const S_CHECKBOX_INACTIVE = unicode ? "◻" : "[ ]";
|
|
|
885
897
|
function symbol(state) {
|
|
886
898
|
switch (state) {
|
|
887
899
|
case "initial":
|
|
888
|
-
case "active": return
|
|
900
|
+
case "active": return cliColors.orange(S_STEP_ACTIVE$1);
|
|
889
901
|
case "cancel": return pc.red(S_STEP_CANCEL);
|
|
890
902
|
case "error": return pc.yellow(S_STEP_ERROR);
|
|
891
|
-
case "submit": return
|
|
903
|
+
case "submit": return cliColors.signal(S_STEP_SUBMIT);
|
|
892
904
|
}
|
|
893
905
|
}
|
|
894
906
|
const KEYBOARD_HINT = pc.dim(`${pc.gray("↑/↓")} navigate • ${pc.gray("enter")} confirm • ${pc.gray("b")} back • ${pc.gray("ctrl+c")} cancel`);
|
|
@@ -923,7 +935,7 @@ async function navigableSelect(opts) {
|
|
|
923
935
|
switch (state) {
|
|
924
936
|
case "disabled": return `${pc.gray(S_RADIO_INACTIVE)} ${pc.gray(label)}${option.hint ? ` ${pc.dim(`(${option.hint ?? "disabled"})`)}` : ""}`;
|
|
925
937
|
case "selected": return `${pc.dim(label)}`;
|
|
926
|
-
case "active": return `${
|
|
938
|
+
case "active": return `${cliColors.bright(S_RADIO_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
927
939
|
case "cancelled": return `${pc.strikethrough(pc.dim(label))}`;
|
|
928
940
|
default: return `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(label)}`;
|
|
929
941
|
}
|
|
@@ -932,14 +944,14 @@ async function navigableSelect(opts) {
|
|
|
932
944
|
options: opts.options,
|
|
933
945
|
initialValue: opts.initialValue,
|
|
934
946
|
render() {
|
|
935
|
-
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
947
|
+
const title = `${pc.gray(S_BAR$1)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
936
948
|
switch (this.state) {
|
|
937
|
-
case "submit": return `${title}${pc.gray(S_BAR)} ${opt(this.options[this.cursor], "selected")}`;
|
|
938
|
-
case "cancel": return `${title}${pc.gray(S_BAR)} ${opt(this.options[this.cursor], "cancelled")}\n${pc.gray(S_BAR)}`;
|
|
949
|
+
case "submit": return `${title}${pc.gray(S_BAR$1)} ${opt(this.options[this.cursor], "selected")}`;
|
|
950
|
+
case "cancel": return `${title}${pc.gray(S_BAR$1)} ${opt(this.options[this.cursor], "cancelled")}\n${pc.gray(S_BAR$1)}`;
|
|
939
951
|
default: {
|
|
940
|
-
const optionsText = this.options.map((option, i) => opt(option, option.disabled ? "disabled" : i === this.cursor ? "active" : "inactive")).join(`\n${
|
|
941
|
-
const hint = `\n${pc.gray(S_BAR)} ${getHint()}`;
|
|
942
|
-
return `${title}${
|
|
952
|
+
const optionsText = this.options.map((option, i) => opt(option, option.disabled ? "disabled" : i === this.cursor ? "active" : "inactive")).join(`\n${cliColors.signal(S_BAR$1)} `);
|
|
953
|
+
const hint = `\n${pc.gray(S_BAR$1)} ${getHint()}`;
|
|
954
|
+
return `${title}${cliColors.signal(S_BAR$1)} ${optionsText}\n${cliColors.signal(S_BAR_END$1)}${hint}\n`;
|
|
943
955
|
}
|
|
944
956
|
}
|
|
945
957
|
}
|
|
@@ -950,10 +962,10 @@ async function navigableMultiselect(opts) {
|
|
|
950
962
|
const opt = (option, state) => {
|
|
951
963
|
const label = option.label ?? String(option.value);
|
|
952
964
|
if (state === "disabled") return `${pc.gray(S_CHECKBOX_INACTIVE)} ${pc.strikethrough(pc.gray(label))}${option.hint ? ` ${pc.dim(`(${option.hint ?? "disabled"})`)}` : ""}`;
|
|
953
|
-
if (state === "active") return `${
|
|
954
|
-
if (state === "selected") return `${
|
|
965
|
+
if (state === "active") return `${cliColors.orange(S_CHECKBOX_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
966
|
+
if (state === "selected") return `${cliColors.bright(S_CHECKBOX_SELECTED)} ${pc.dim(label)}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
955
967
|
if (state === "cancelled") return `${pc.strikethrough(pc.dim(label))}`;
|
|
956
|
-
if (state === "active-selected") return `${
|
|
968
|
+
if (state === "active-selected") return `${cliColors.bright(S_CHECKBOX_SELECTED)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
957
969
|
if (state === "submitted") return `${pc.dim(label)}`;
|
|
958
970
|
return `${pc.dim(S_CHECKBOX_INACTIVE)} ${pc.dim(label)}`;
|
|
959
971
|
};
|
|
@@ -966,7 +978,7 @@ async function navigableMultiselect(opts) {
|
|
|
966
978
|
return normalizeValidationMessage(opts.validate?.(selected));
|
|
967
979
|
},
|
|
968
980
|
render() {
|
|
969
|
-
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
981
|
+
const title = `${pc.gray(S_BAR$1)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
970
982
|
const value = this.value ?? [];
|
|
971
983
|
const styleOption = (option, active) => {
|
|
972
984
|
if (option.disabled) return opt(option, "disabled");
|
|
@@ -978,21 +990,21 @@ async function navigableMultiselect(opts) {
|
|
|
978
990
|
switch (this.state) {
|
|
979
991
|
case "submit": {
|
|
980
992
|
const submitText = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "submitted")).join(pc.dim(", ")) || pc.dim("none");
|
|
981
|
-
return `${title}${pc.gray(S_BAR)} ${submitText}`;
|
|
993
|
+
return `${title}${pc.gray(S_BAR$1)} ${submitText}`;
|
|
982
994
|
}
|
|
983
995
|
case "cancel": {
|
|
984
996
|
const label = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "cancelled")).join(pc.dim(", "));
|
|
985
|
-
return `${title}${pc.gray(S_BAR)} ${label}\n${pc.gray(S_BAR)}`;
|
|
997
|
+
return `${title}${pc.gray(S_BAR$1)} ${label}\n${pc.gray(S_BAR$1)}`;
|
|
986
998
|
}
|
|
987
999
|
case "error": {
|
|
988
|
-
const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc.yellow(S_BAR_END)} ${pc.yellow(ln)}` : ` ${ln}`).join("\n");
|
|
989
|
-
const optionsText = this.options.map((option, i) => styleOption(option, i === this.cursor)).join(`\n${pc.yellow(S_BAR)} `);
|
|
990
|
-
return `${title}${pc.yellow(S_BAR)} ${optionsText}\n${footer}\n`;
|
|
1000
|
+
const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc.yellow(S_BAR_END$1)} ${pc.yellow(ln)}` : ` ${ln}`).join("\n");
|
|
1001
|
+
const optionsText = this.options.map((option, i) => styleOption(option, i === this.cursor)).join(`\n${pc.yellow(S_BAR$1)} `);
|
|
1002
|
+
return `${title}${pc.yellow(S_BAR$1)} ${optionsText}\n${footer}\n`;
|
|
991
1003
|
}
|
|
992
1004
|
default: {
|
|
993
|
-
const optionsText = this.options.map((option, i) => styleOption(option, i === this.cursor)).join(`\n${
|
|
994
|
-
const hint = `\n${pc.gray(S_BAR)} ${getMultiHint()}`;
|
|
995
|
-
return `${title}${
|
|
1005
|
+
const optionsText = this.options.map((option, i) => styleOption(option, i === this.cursor)).join(`\n${cliColors.signal(S_BAR$1)} `);
|
|
1006
|
+
const hint = `\n${pc.gray(S_BAR$1)} ${getMultiHint()}`;
|
|
1007
|
+
return `${title}${cliColors.signal(S_BAR$1)} ${optionsText}\n${cliColors.signal(S_BAR_END$1)}${hint}\n`;
|
|
996
1008
|
}
|
|
997
1009
|
}
|
|
998
1010
|
}
|
|
@@ -1006,14 +1018,14 @@ async function navigableConfirm(opts) {
|
|
|
1006
1018
|
inactive,
|
|
1007
1019
|
initialValue: opts.initialValue ?? true,
|
|
1008
1020
|
render() {
|
|
1009
|
-
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1021
|
+
const title = `${pc.gray(S_BAR$1)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1010
1022
|
const value = this.value ? active : inactive;
|
|
1011
1023
|
switch (this.state) {
|
|
1012
|
-
case "submit": return `${title}${pc.gray(S_BAR)} ${pc.dim(value)}`;
|
|
1013
|
-
case "cancel": return `${title}${pc.gray(S_BAR)} ${pc.strikethrough(pc.dim(value))}\n${pc.gray(S_BAR)}`;
|
|
1024
|
+
case "submit": return `${title}${pc.gray(S_BAR$1)} ${pc.dim(value)}`;
|
|
1025
|
+
case "cancel": return `${title}${pc.gray(S_BAR$1)} ${pc.strikethrough(pc.dim(value))}\n${pc.gray(S_BAR$1)}`;
|
|
1014
1026
|
default: {
|
|
1015
|
-
const hint = `\n${pc.gray(S_BAR)} ${getHint()}`;
|
|
1016
|
-
return `${title}${
|
|
1027
|
+
const hint = `\n${pc.gray(S_BAR$1)} ${getHint()}`;
|
|
1028
|
+
return `${title}${cliColors.signal(S_BAR$1)} ${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${cliColors.signal(S_BAR_END$1)}${hint}\n`;
|
|
1017
1029
|
}
|
|
1018
1030
|
}
|
|
1019
1031
|
}
|
|
@@ -1026,16 +1038,16 @@ async function navigableGroupMultiselect(opts) {
|
|
|
1026
1038
|
const isItem = typeof option.group === "string";
|
|
1027
1039
|
const next = isItem && (options[options.indexOf(option) + 1] ?? { group: true });
|
|
1028
1040
|
const isLast = isItem && next && next.group === true;
|
|
1029
|
-
const prefix = isItem ? `${isLast ? S_BAR_END : S_BAR} ` : "";
|
|
1030
|
-
if (state === "active") return `${pc.dim(prefix)}${
|
|
1031
|
-
if (state === "group-active") return `${prefix}${
|
|
1032
|
-
if (state === "group-active-selected") return `${prefix}${
|
|
1041
|
+
const prefix = isItem ? `${isLast ? S_BAR_END$1 : S_BAR$1} ` : "";
|
|
1042
|
+
if (state === "active") return `${pc.dim(prefix)}${cliColors.orange(S_CHECKBOX_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
1043
|
+
if (state === "group-active") return `${prefix}${cliColors.orange(S_CHECKBOX_ACTIVE)} ${pc.dim(label)}`;
|
|
1044
|
+
if (state === "group-active-selected") return `${prefix}${cliColors.bright(S_CHECKBOX_SELECTED)} ${pc.dim(label)}`;
|
|
1033
1045
|
if (state === "selected") {
|
|
1034
|
-
const selectedCheckbox = isItem ?
|
|
1046
|
+
const selectedCheckbox = isItem ? cliColors.bright(S_CHECKBOX_SELECTED) : "";
|
|
1035
1047
|
return `${pc.dim(prefix)}${selectedCheckbox} ${pc.dim(label)}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
1036
1048
|
}
|
|
1037
1049
|
if (state === "cancelled") return `${pc.strikethrough(pc.dim(label))}`;
|
|
1038
|
-
if (state === "active-selected") return `${pc.dim(prefix)}${
|
|
1050
|
+
if (state === "active-selected") return `${pc.dim(prefix)}${cliColors.bright(S_CHECKBOX_SELECTED)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
1039
1051
|
if (state === "submitted") return `${pc.dim(label)}`;
|
|
1040
1052
|
const unselectedCheckbox = isItem ? pc.dim(S_CHECKBOX_INACTIVE) : "";
|
|
1041
1053
|
return `${pc.dim(prefix)}${unselectedCheckbox} ${pc.dim(label)}`;
|
|
@@ -1050,20 +1062,20 @@ async function navigableGroupMultiselect(opts) {
|
|
|
1050
1062
|
return normalizeValidationMessage(opts.validate?.(selected));
|
|
1051
1063
|
},
|
|
1052
1064
|
render() {
|
|
1053
|
-
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1065
|
+
const title = `${pc.gray(S_BAR$1)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1054
1066
|
const value = this.value ?? [];
|
|
1055
1067
|
switch (this.state) {
|
|
1056
1068
|
case "submit": {
|
|
1057
1069
|
const selectedOptions = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "submitted"));
|
|
1058
1070
|
const optionsText = selectedOptions.length === 0 ? "" : ` ${selectedOptions.join(pc.dim(", "))}`;
|
|
1059
|
-
return `${title}${pc.gray(S_BAR)}${optionsText}`;
|
|
1071
|
+
return `${title}${pc.gray(S_BAR$1)}${optionsText}`;
|
|
1060
1072
|
}
|
|
1061
1073
|
case "cancel": {
|
|
1062
1074
|
const label = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "cancelled")).join(pc.dim(", "));
|
|
1063
|
-
return `${title}${pc.gray(S_BAR)} ${label.trim() ? `${label}\n${pc.gray(S_BAR)}` : ""}`;
|
|
1075
|
+
return `${title}${pc.gray(S_BAR$1)} ${label.trim() ? `${label}\n${pc.gray(S_BAR$1)}` : ""}`;
|
|
1064
1076
|
}
|
|
1065
1077
|
case "error": {
|
|
1066
|
-
const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc.yellow(S_BAR_END)} ${pc.yellow(ln)}` : ` ${ln}`).join("\n");
|
|
1078
|
+
const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc.yellow(S_BAR_END$1)} ${pc.yellow(ln)}` : ` ${ln}`).join("\n");
|
|
1067
1079
|
const optionsText = this.options.map((option, i, options) => {
|
|
1068
1080
|
const selected = value.includes(option.value) || option.group === true && this.isGroupSelected(`${option.value}`);
|
|
1069
1081
|
const active = i === this.cursor;
|
|
@@ -1071,8 +1083,8 @@ async function navigableGroupMultiselect(opts) {
|
|
|
1071
1083
|
if (active && selected) return opt(option, "active-selected", options);
|
|
1072
1084
|
if (selected) return opt(option, "selected", options);
|
|
1073
1085
|
return opt(option, active ? "active" : "inactive", options);
|
|
1074
|
-
}).join(`\n${pc.yellow(S_BAR)} `);
|
|
1075
|
-
return `${title}${pc.yellow(S_BAR)} ${optionsText}\n${footer}\n`;
|
|
1086
|
+
}).join(`\n${pc.yellow(S_BAR$1)} `);
|
|
1087
|
+
return `${title}${pc.yellow(S_BAR$1)} ${optionsText}\n${footer}\n`;
|
|
1076
1088
|
}
|
|
1077
1089
|
default: {
|
|
1078
1090
|
const optionsText = this.options.map((option, i, options) => {
|
|
@@ -1085,10 +1097,10 @@ async function navigableGroupMultiselect(opts) {
|
|
|
1085
1097
|
else if (selected) optionText = opt(option, "selected", options);
|
|
1086
1098
|
else optionText = opt(option, active ? "active" : "inactive", options);
|
|
1087
1099
|
return `${i !== 0 && !optionText.startsWith("\n") ? " " : ""}${optionText}`;
|
|
1088
|
-
}).join(`\n${
|
|
1100
|
+
}).join(`\n${cliColors.signal(S_BAR$1)}`);
|
|
1089
1101
|
const optionsPrefix = optionsText.startsWith("\n") ? "" : " ";
|
|
1090
|
-
const hint = `\n${pc.gray(S_BAR)} ${getMultiHint()}`;
|
|
1091
|
-
return `${title}${
|
|
1102
|
+
const hint = `\n${pc.gray(S_BAR$1)} ${getMultiHint()}`;
|
|
1103
|
+
return `${title}${cliColors.signal(S_BAR$1)}${optionsPrefix}${optionsText}\n${cliColors.signal(S_BAR_END$1)}${hint}\n`;
|
|
1092
1104
|
}
|
|
1093
1105
|
}
|
|
1094
1106
|
}
|
|
@@ -1108,10 +1120,6 @@ function getAddonDisplay(addon) {
|
|
|
1108
1120
|
label = "Turborepo";
|
|
1109
1121
|
hint = "High-performance build system";
|
|
1110
1122
|
break;
|
|
1111
|
-
case "nx":
|
|
1112
|
-
label = "Nx";
|
|
1113
|
-
hint = "Smart monorepo orchestration and task graph";
|
|
1114
|
-
break;
|
|
1115
1123
|
case "vite-plus":
|
|
1116
1124
|
label = "Vite+";
|
|
1117
1125
|
hint = "Unified Vite toolchain and workspace task runner";
|
|
@@ -1136,10 +1144,6 @@ function getAddonDisplay(addon) {
|
|
|
1136
1144
|
label = "Oxlint";
|
|
1137
1145
|
hint = "Oxlint + Oxfmt (linting & formatting)";
|
|
1138
1146
|
break;
|
|
1139
|
-
case "ultracite":
|
|
1140
|
-
label = "Ultracite";
|
|
1141
|
-
hint = "Zero-config preset for Biome or Oxlint with AI integration";
|
|
1142
|
-
break;
|
|
1143
1147
|
case "lefthook":
|
|
1144
1148
|
label = "Lefthook";
|
|
1145
1149
|
hint = "Fast and powerful Git hooks manager";
|
|
@@ -1148,22 +1152,10 @@ function getAddonDisplay(addon) {
|
|
|
1148
1152
|
label = "Husky";
|
|
1149
1153
|
hint = "Modern native Git hooks made easy";
|
|
1150
1154
|
break;
|
|
1151
|
-
case "starlight":
|
|
1152
|
-
label = "Starlight";
|
|
1153
|
-
hint = "Build stellar docs with astro";
|
|
1154
|
-
break;
|
|
1155
|
-
case "fumadocs":
|
|
1156
|
-
label = "Fumadocs";
|
|
1157
|
-
hint = "Build excellent documentation site";
|
|
1158
|
-
break;
|
|
1159
1155
|
case "opentui":
|
|
1160
1156
|
label = "OpenTUI";
|
|
1161
1157
|
hint = "Build terminal user interfaces";
|
|
1162
1158
|
break;
|
|
1163
|
-
case "wxt":
|
|
1164
|
-
label = "WXT";
|
|
1165
|
-
hint = "Build browser extensions";
|
|
1166
|
-
break;
|
|
1167
1159
|
case "skills":
|
|
1168
1160
|
label = "Skills";
|
|
1169
1161
|
hint = "AI coding agent skills for your stack";
|
|
@@ -1172,9 +1164,9 @@ function getAddonDisplay(addon) {
|
|
|
1172
1164
|
label = "MCP";
|
|
1173
1165
|
hint = "Install MCP servers, including kubojs, via add-mcp";
|
|
1174
1166
|
break;
|
|
1175
|
-
case "
|
|
1176
|
-
label = "
|
|
1177
|
-
hint = "
|
|
1167
|
+
case "s3-storage":
|
|
1168
|
+
label = "S3-compatible Storage";
|
|
1169
|
+
hint = "Add object storage for AWS S3, Cloudflare R2, MinIO, or Backblaze";
|
|
1178
1170
|
break;
|
|
1179
1171
|
default:
|
|
1180
1172
|
label = addon;
|
|
@@ -1186,27 +1178,20 @@ function getAddonDisplay(addon) {
|
|
|
1186
1178
|
};
|
|
1187
1179
|
}
|
|
1188
1180
|
const ADDON_GROUPS = {
|
|
1189
|
-
"Monorepo & Tasks": [
|
|
1190
|
-
"turborepo",
|
|
1191
|
-
"nx",
|
|
1192
|
-
"vite-plus"
|
|
1193
|
-
],
|
|
1181
|
+
"Monorepo & Tasks": ["turborepo", "vite-plus"],
|
|
1194
1182
|
"Code Quality": [
|
|
1195
1183
|
"biome",
|
|
1196
1184
|
"oxlint",
|
|
1197
|
-
"ultracite",
|
|
1198
1185
|
"husky",
|
|
1199
1186
|
"lefthook"
|
|
1200
1187
|
],
|
|
1201
|
-
Documentation: ["starlight", "fumadocs"],
|
|
1202
1188
|
"Platform Extensions": [
|
|
1203
1189
|
"pwa",
|
|
1204
1190
|
"tauri",
|
|
1205
1191
|
"electrobun",
|
|
1206
1192
|
"opentui",
|
|
1207
|
-
"
|
|
1193
|
+
"s3-storage"
|
|
1208
1194
|
],
|
|
1209
|
-
Observability: ["evlog"],
|
|
1210
1195
|
"AI & Agent Tools": ["skills", "mcp"]
|
|
1211
1196
|
};
|
|
1212
1197
|
function createGroupedOptions() {
|
|
@@ -1231,16 +1216,8 @@ function sortAndPruneGroupedOptions(groupedOptions) {
|
|
|
1231
1216
|
});
|
|
1232
1217
|
}
|
|
1233
1218
|
function validateAddonSelection(selected) {
|
|
1234
|
-
if ((selected?.filter((addon) => [
|
|
1235
|
-
|
|
1236
|
-
"nx",
|
|
1237
|
-
"vite-plus"
|
|
1238
|
-
].includes(addon)) ?? []).length > 1) return "Choose Turborepo, Nx, or Vite+ as your task runner, not more than one.";
|
|
1239
|
-
if ((selected?.filter((addon) => [
|
|
1240
|
-
"biome",
|
|
1241
|
-
"oxlint",
|
|
1242
|
-
"ultracite"
|
|
1243
|
-
].includes(addon)) ?? []).length > 1) return "Choose Biome, Oxlint, or Ultracite as your code-quality tool, not more than one.";
|
|
1219
|
+
if ((selected?.filter((addon) => ["turborepo", "vite-plus"].includes(addon)) ?? []).length > 1) return "Choose Turborepo or Vite+ as your task runner, not both.";
|
|
1220
|
+
if ((selected?.filter((addon) => ["biome", "oxlint"].includes(addon)) ?? []).length > 1) return "Choose Biome or Oxlint as your code-quality tool, not both.";
|
|
1244
1221
|
}
|
|
1245
1222
|
async function getAddonsChoice(addons, frontends, auth, backend, runtime, previousValue) {
|
|
1246
1223
|
if (addons !== void 0) return addons;
|
|
@@ -1265,7 +1242,7 @@ async function getAddonsChoice(addons, frontends, auth, backend, runtime, previo
|
|
|
1265
1242
|
required: false,
|
|
1266
1243
|
validate: validateAddonSelection
|
|
1267
1244
|
});
|
|
1268
|
-
if (isCancel$
|
|
1245
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
1269
1246
|
return response;
|
|
1270
1247
|
}
|
|
1271
1248
|
async function getAddonsToAdd(config) {
|
|
@@ -1288,7 +1265,7 @@ async function getAddonsToAdd(config) {
|
|
|
1288
1265
|
required: false,
|
|
1289
1266
|
validate: validateAddonSelection
|
|
1290
1267
|
});
|
|
1291
|
-
if (isCancel$
|
|
1268
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
1292
1269
|
return response;
|
|
1293
1270
|
}
|
|
1294
1271
|
//#endregion
|
|
@@ -1364,515 +1341,6 @@ const addPackageDependency = async (opts) => {
|
|
|
1364
1341
|
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
1365
1342
|
};
|
|
1366
1343
|
//#endregion
|
|
1367
|
-
//#region src/helpers/addons/evlog-setup.ts
|
|
1368
|
-
const evlogBackends = [
|
|
1369
|
-
"hono",
|
|
1370
|
-
"express",
|
|
1371
|
-
"fastify",
|
|
1372
|
-
"elysia"
|
|
1373
|
-
];
|
|
1374
|
-
const evlogWebFrontends = [
|
|
1375
|
-
"next",
|
|
1376
|
-
"nuxt",
|
|
1377
|
-
"svelte",
|
|
1378
|
-
"tanstack-start",
|
|
1379
|
-
"astro"
|
|
1380
|
-
];
|
|
1381
|
-
function isEvlogBackend(backend) {
|
|
1382
|
-
return evlogBackends.includes(backend);
|
|
1383
|
-
}
|
|
1384
|
-
function getEvlogWebFrontend(frontends) {
|
|
1385
|
-
return frontends.find((frontend) => evlogWebFrontends.includes(frontend));
|
|
1386
|
-
}
|
|
1387
|
-
function shouldIdentifyWebAuth(config) {
|
|
1388
|
-
return config.auth === "better-auth" && config.backend === "self";
|
|
1389
|
-
}
|
|
1390
|
-
function prependMissingImports(content, imports) {
|
|
1391
|
-
const missingImports = imports.filter((line) => !content.includes(line));
|
|
1392
|
-
if (missingImports.length === 0) return content;
|
|
1393
|
-
const importBlock = `${missingImports.join("\n")}\n`;
|
|
1394
|
-
const referenceMatch = content.match(/^(?:\/\/\/ <reference[^\n]*>\n)+/);
|
|
1395
|
-
if (referenceMatch) return `${referenceMatch[0]}${importBlock}${content.slice(referenceMatch[0].length)}`;
|
|
1396
|
-
return `${importBlock}${content}`;
|
|
1397
|
-
}
|
|
1398
|
-
function addNamedImport(content, moduleName, names) {
|
|
1399
|
-
const importRegex = new RegExp(`import \\{([^}]+)\\} from "${moduleName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}";`);
|
|
1400
|
-
const match = content.match(importRegex);
|
|
1401
|
-
if (!match) return prependMissingImports(content, [`import { ${names.join(", ")} } from "${moduleName}";`]);
|
|
1402
|
-
const nextNames = [...match[1].split(",").map((name) => name.trim()).filter(Boolean)];
|
|
1403
|
-
for (const name of names) if (!nextNames.includes(name)) nextNames.push(name);
|
|
1404
|
-
return content.replace(match[0], `import { ${nextNames.join(", ")} } from "${moduleName}";`);
|
|
1405
|
-
}
|
|
1406
|
-
function insertBeforeOnce(content, marker, snippet, alreadyPresent) {
|
|
1407
|
-
if (content.includes(alreadyPresent)) return content;
|
|
1408
|
-
if (!content.includes(marker)) return content;
|
|
1409
|
-
return content.replace(marker, `${snippet}${marker}`);
|
|
1410
|
-
}
|
|
1411
|
-
function insertAfterOnce(content, marker, snippet, alreadyPresent) {
|
|
1412
|
-
if (content.includes(alreadyPresent)) return content;
|
|
1413
|
-
if (!content.includes(marker)) return content;
|
|
1414
|
-
return content.replace(marker, `${marker}${snippet}`);
|
|
1415
|
-
}
|
|
1416
|
-
async function writeFileIfChanged(filePath, content) {
|
|
1417
|
-
if ((await fs.pathExists(filePath) ? await fs.readFile(filePath, "utf-8") : void 0) === content) return;
|
|
1418
|
-
await fs.ensureDir(path.dirname(filePath));
|
|
1419
|
-
await fs.writeFile(filePath, content);
|
|
1420
|
-
}
|
|
1421
|
-
async function updateFileIfExists(filePath, update) {
|
|
1422
|
-
if (!await fs.pathExists(filePath)) return;
|
|
1423
|
-
const content = await fs.readFile(filePath, "utf-8");
|
|
1424
|
-
const nextContent = update(content);
|
|
1425
|
-
if (nextContent !== content) await fs.writeFile(filePath, nextContent);
|
|
1426
|
-
}
|
|
1427
|
-
function usesCreateAuthFactory(config) {
|
|
1428
|
-
return config.runtime === "workers" || config.serverDeploy === "cloudflare" || config.backend === "self" && config.webDeploy === "cloudflare";
|
|
1429
|
-
}
|
|
1430
|
-
function getAuthImportLine(config) {
|
|
1431
|
-
return usesCreateAuthFactory(config) ? `import { createAuth } from "@${config.projectName}/auth";` : `import { auth } from "@${config.projectName}/auth";`;
|
|
1432
|
-
}
|
|
1433
|
-
function getAuthExpression(config) {
|
|
1434
|
-
return usesCreateAuthFactory(config) ? "createAuth()" : "auth";
|
|
1435
|
-
}
|
|
1436
|
-
function addEvlogBetterAuthServerSetup(content, backend, authExpression) {
|
|
1437
|
-
let nextContent = addNamedImport(content, "evlog/better-auth", ["createAuthMiddleware", "type BetterAuthInstance"]);
|
|
1438
|
-
const usesAuthFactory = authExpression.endsWith("()");
|
|
1439
|
-
const evlogAuthExpression = `${authExpression} as BetterAuthInstance`;
|
|
1440
|
-
const authOptions = "{ exclude: [\"/api/auth/**\"], maskEmail: true }";
|
|
1441
|
-
const identifySnippet = usesAuthFactory ? "" : `const identifyUser = createAuthMiddleware(${evlogAuthExpression}, ${authOptions});\n\n`;
|
|
1442
|
-
const identifyUserSetup = usesAuthFactory ? `\n\tconst identifyUser = createAuthMiddleware(${evlogAuthExpression}, ${authOptions});` : "";
|
|
1443
|
-
if (backend === "hono") {
|
|
1444
|
-
nextContent = insertBeforeOnce(nextContent, "const app = new Hono", identifySnippet, "createAuthMiddleware(");
|
|
1445
|
-
return insertAfterOnce(nextContent, "app.use(evlog());", `\napp.use("*", async (c, next) => {${identifyUserSetup}\n\tawait identifyUser(c.get("log"), c.req.raw.headers, c.req.path);\n\tawait next();\n});`, "identifyUser(c.get(\"log\")");
|
|
1446
|
-
}
|
|
1447
|
-
if (backend === "express") {
|
|
1448
|
-
nextContent = insertBeforeOnce(nextContent, "const app = express();", identifySnippet, "createAuthMiddleware(");
|
|
1449
|
-
return insertAfterOnce(nextContent, "app.use(evlog());", `\napp.use(async (req, _res, next) => {${identifyUserSetup}\n\tawait identifyUser(req.log, req.headers, req.path);\n\tnext();\n});`, "identifyUser(req.log");
|
|
1450
|
-
}
|
|
1451
|
-
if (backend === "fastify") {
|
|
1452
|
-
nextContent = addNamedImport(nextContent, "evlog/fastify", ["useLogger"]);
|
|
1453
|
-
nextContent = insertBeforeOnce(nextContent, "const fastify = Fastify", identifySnippet, "createAuthMiddleware(");
|
|
1454
|
-
return insertAfterOnce(nextContent, "fastify.register(evlog);", `\nfastify.addHook("preHandler", async (request) => {${identifyUserSetup}\n\tawait identifyUser(useLogger(), request.headers, request.url);\n});`, "identifyUser(useLogger()");
|
|
1455
|
-
}
|
|
1456
|
-
nextContent = insertBeforeOnce(nextContent, "new Elysia", identifySnippet, "createAuthMiddleware(");
|
|
1457
|
-
return insertAfterOnce(nextContent, ".use(evlog())", `\n\t.derive(async ({ request, log }) => {${identifyUserSetup.replace(/\n\t/g, "\n ")}\n\t\tawait identifyUser(log, request.headers, new URL(request.url).pathname);\n\t\treturn {};\n\t})`, "identifyUser(log");
|
|
1458
|
-
}
|
|
1459
|
-
function addEvlogServerSetup(content, backend, serviceName) {
|
|
1460
|
-
const initSnippet = `initLogger({\n\tenv: { service: "${serviceName}" },\n});\n\n`;
|
|
1461
|
-
if (backend === "hono") {
|
|
1462
|
-
let nextContent = prependMissingImports(content, ["import { initLogger } from \"evlog\";", "import { evlog, type EvlogVariables } from \"evlog/hono\";"]);
|
|
1463
|
-
nextContent = insertBeforeOnce(nextContent, "const app = new Hono", initSnippet, "initLogger({");
|
|
1464
|
-
nextContent = nextContent.replace("const app = new Hono();", "const app = new Hono<EvlogVariables>();");
|
|
1465
|
-
nextContent = nextContent.replace("import { logger } from \"hono/logger\";\n", "").replace(/\napp\.use\(logger\(\)\);/, "");
|
|
1466
|
-
return insertAfterOnce(nextContent, "const app = new Hono<EvlogVariables>();", "\n\napp.use(evlog());", "app.use(evlog());");
|
|
1467
|
-
}
|
|
1468
|
-
if (backend === "express") {
|
|
1469
|
-
let nextContent = prependMissingImports(content, ["import { initLogger } from \"evlog\";", "import { evlog } from \"evlog/express\";"]);
|
|
1470
|
-
nextContent = insertBeforeOnce(nextContent, "const app = express();", initSnippet, "initLogger({");
|
|
1471
|
-
return insertAfterOnce(nextContent, "const app = express();", "\n\napp.use(evlog());", "app.use(evlog());");
|
|
1472
|
-
}
|
|
1473
|
-
if (backend === "fastify") {
|
|
1474
|
-
let nextContent = prependMissingImports(content, ["import { initLogger } from \"evlog\";", "import { evlog } from \"evlog/fastify\";"]);
|
|
1475
|
-
nextContent = insertBeforeOnce(nextContent, "const fastify = Fastify", initSnippet, "initLogger({");
|
|
1476
|
-
return insertBeforeOnce(nextContent, "fastify.register(fastifyCors", "fastify.register(evlog);\n", "fastify.register(evlog);");
|
|
1477
|
-
}
|
|
1478
|
-
let nextContent = prependMissingImports(content, ["import { initLogger } from \"evlog\";", "import { evlog } from \"evlog/elysia\";"]);
|
|
1479
|
-
nextContent = insertBeforeOnce(nextContent, "new Elysia", initSnippet, "initLogger({");
|
|
1480
|
-
for (const marker of ["new Elysia({ adapter: node() })", "new Elysia()"]) nextContent = insertAfterOnce(nextContent, marker, "\n .use(evlog())", ".use(evlog())");
|
|
1481
|
-
return nextContent;
|
|
1482
|
-
}
|
|
1483
|
-
function addNuxtEvlogSetup(content, serviceName) {
|
|
1484
|
-
let nextContent = content;
|
|
1485
|
-
if (!nextContent.includes("\"evlog/nuxt\"") && !nextContent.includes("'evlog/nuxt'")) nextContent = nextContent.replace(/modules:\s*\[/, (match) => `${match}\n "evlog/nuxt",`);
|
|
1486
|
-
if (!nextContent.includes("evlog:")) nextContent = nextContent.replace(/\n\}\)\s*$/, (match) => {
|
|
1487
|
-
const contentBeforeConfigClose = nextContent.slice(0, -match.length);
|
|
1488
|
-
return `${!/[,{]\s*$/.test(contentBeforeConfigClose) ? "," : ""}\n evlog: {\n env: { service: "${serviceName}" },\n },\n})`;
|
|
1489
|
-
});
|
|
1490
|
-
return nextContent;
|
|
1491
|
-
}
|
|
1492
|
-
function addSvelteViteEvlogSetup(content, serviceName) {
|
|
1493
|
-
let nextContent = prependMissingImports(content, ["import evlog from \"evlog/vite\";"]);
|
|
1494
|
-
if (nextContent.includes("evlog({")) return nextContent;
|
|
1495
|
-
return nextContent.replace("plugins: [tailwindcss(), sveltekit()],", `plugins: [\n tailwindcss(),\n sveltekit(),\n evlog({ service: "${serviceName}" }),\n ],`);
|
|
1496
|
-
}
|
|
1497
|
-
function addSvelteHooksEvlogSetup(content) {
|
|
1498
|
-
let nextContent = prependMissingImports(content, ["import { createEvlogHooks } from \"evlog/sveltekit\";"]);
|
|
1499
|
-
if (!nextContent.includes("export const handle") && !nextContent.includes("const authHandle")) {
|
|
1500
|
-
if (!nextContent.includes("createEvlogHooks()")) nextContent = `${nextContent.trimEnd()}\n\nexport const { handle, handleError } = createEvlogHooks();\n`;
|
|
1501
|
-
return nextContent;
|
|
1502
|
-
}
|
|
1503
|
-
nextContent = prependMissingImports(nextContent, ["import { sequence } from \"@sveltejs/kit/hooks\";"]);
|
|
1504
|
-
if (!nextContent.includes("const { handle: evlogHandle, handleError }")) nextContent = nextContent.replace(/((?:import .+\n)+)/, `$1\nconst { handle: evlogHandle, handleError } = createEvlogHooks();\n\n`);
|
|
1505
|
-
nextContent = nextContent.replace(/export const handle(:\s*Handle)?\s*=\s*async/, (_match, typeAnnotation) => `const authHandle${typeAnnotation ?? ""} = async`);
|
|
1506
|
-
if (!nextContent.includes("sequence(evlogHandle, authHandle)")) nextContent = `${nextContent.trimEnd()}\n\nexport const handle = sequence(evlogHandle as Handle, authHandle);\nexport { handleError };\n`;
|
|
1507
|
-
return nextContent;
|
|
1508
|
-
}
|
|
1509
|
-
function addSvelteLocalsType(content) {
|
|
1510
|
-
let nextContent = prependMissingImports(content, ["import type { RequestLogger } from \"evlog\";"]);
|
|
1511
|
-
if (nextContent.includes("log: RequestLogger")) return nextContent;
|
|
1512
|
-
if (nextContent.includes("// interface Locals {}")) return nextContent.replace("// interface Locals {}", "interface Locals {\n log: RequestLogger;\n }");
|
|
1513
|
-
return nextContent.replace("namespace App {", "namespace App {\n interface Locals {\n log: RequestLogger;\n }\n");
|
|
1514
|
-
}
|
|
1515
|
-
function addTanstackStartRootEvlogSetup(content) {
|
|
1516
|
-
let nextContent = prependMissingImports(content, ["import { createMiddleware } from \"@tanstack/react-start\";", "import { evlogErrorHandler } from \"evlog/nitro/v3\";"]);
|
|
1517
|
-
const middlewareEntry = "createMiddleware().server(evlogErrorHandler)";
|
|
1518
|
-
if (nextContent.includes(`middleware: [${middlewareEntry}]`)) return nextContent;
|
|
1519
|
-
if (nextContent.includes("middleware: [")) return nextContent.replace("middleware: [", `middleware: [${middlewareEntry}, `);
|
|
1520
|
-
if (/server:\s*{/.test(nextContent)) return nextContent.replace(/server:\s*{\n/, `server: {\n middleware: [${middlewareEntry}],\n`);
|
|
1521
|
-
return nextContent.replace("head: () => ({", `server: {\n middleware: [${middlewareEntry}],\n },\n\n head: () => ({`);
|
|
1522
|
-
}
|
|
1523
|
-
function addAstroMiddlewareEvlogSetup(content, serviceName) {
|
|
1524
|
-
let nextContent = prependMissingImports(content, ["import { createRequestLogger, initLogger } from \"evlog\";"]);
|
|
1525
|
-
const initSnippet = `initLogger({\n env: { service: "${serviceName}" },\n});\n\n`;
|
|
1526
|
-
nextContent = insertBeforeOnce(nextContent, "export const onRequest", initSnippet, "initLogger({");
|
|
1527
|
-
if (nextContent.includes("createRequestLogger({")) return nextContent;
|
|
1528
|
-
const contextMarker = "export const onRequest = defineMiddleware(async (context, next) => {";
|
|
1529
|
-
if (nextContent.includes(contextMarker)) {
|
|
1530
|
-
nextContent = insertAfterOnce(nextContent, contextMarker, `\n const url = new URL(context.request.url);\n const log = createRequestLogger({\n method: context.request.method,\n path: url.pathname,\n });\n\n context.locals.log = log;\n`, "const log = createRequestLogger({");
|
|
1531
|
-
return nextContent.replace("return next();", "const response = await next();\n log.emit();\n return response;");
|
|
1532
|
-
}
|
|
1533
|
-
const localsMarker = "export const onRequest = defineMiddleware(async ({ request, locals }, next) => {";
|
|
1534
|
-
if (nextContent.includes(localsMarker)) {
|
|
1535
|
-
nextContent = insertAfterOnce(nextContent, localsMarker, `\n const url = new URL(request.url);\n const log = createRequestLogger({\n method: request.method,\n path: url.pathname,\n });\n\n locals.log = log;\n`, "const log = createRequestLogger({");
|
|
1536
|
-
return nextContent.replace("return next();", "const response = await next();\n log.emit();\n return response;");
|
|
1537
|
-
}
|
|
1538
|
-
return nextContent;
|
|
1539
|
-
}
|
|
1540
|
-
function addAstroLocalsType(content) {
|
|
1541
|
-
let nextContent = prependMissingImports(content, ["import type { RequestLogger } from \"evlog\";"]);
|
|
1542
|
-
if (nextContent.includes("log: RequestLogger")) return nextContent;
|
|
1543
|
-
if (nextContent.includes("interface Locals {")) return nextContent.replace("interface Locals {", "interface Locals {\n log: RequestLogger;");
|
|
1544
|
-
if (nextContent.includes("declare namespace App {")) return nextContent.replace("declare namespace App {", "declare namespace App {\n interface Locals {\n log: RequestLogger;\n }\n");
|
|
1545
|
-
return `${nextContent.trimEnd()}\n\ndeclare namespace App {\n interface Locals {\n log: RequestLogger;\n }\n}\n`;
|
|
1546
|
-
}
|
|
1547
|
-
function addNextRouteWrappers(content) {
|
|
1548
|
-
let nextContent = prependMissingImports(content, ["import { withEvlog } from \"@/lib/evlog\";"]);
|
|
1549
|
-
if (nextContent.includes("withEvlog(handler)") || nextContent.includes("withEvlog(handleRequest)")) return nextContent;
|
|
1550
|
-
nextContent = nextContent.replace("export { handler as GET, handler as POST };", "export const GET = withEvlog(handler);\nexport const POST = withEvlog(handler);");
|
|
1551
|
-
for (const method of [
|
|
1552
|
-
"GET",
|
|
1553
|
-
"POST",
|
|
1554
|
-
"PUT",
|
|
1555
|
-
"PATCH",
|
|
1556
|
-
"DELETE"
|
|
1557
|
-
]) nextContent = nextContent.replace(`export const ${method} = handleRequest;`, `export const ${method} = withEvlog(handleRequest);`);
|
|
1558
|
-
return nextContent;
|
|
1559
|
-
}
|
|
1560
|
-
function addNextAiEvlogSetup(content) {
|
|
1561
|
-
let nextContent = addNamedImport(content, "@/lib/evlog", ["withEvlog"]);
|
|
1562
|
-
if (!nextContent.includes("withEvlog(async (req: Request)")) {
|
|
1563
|
-
nextContent = nextContent.replace("export async function POST(req: Request) {", "export const POST = withEvlog(async (req: Request) => {");
|
|
1564
|
-
if (nextContent.includes("export const POST = withEvlog(async (req: Request) => {")) nextContent = nextContent.replace(/\n}\s*$/, "\n});\n");
|
|
1565
|
-
}
|
|
1566
|
-
return nextContent;
|
|
1567
|
-
}
|
|
1568
|
-
function addNextBetterAuthToRoute(content) {
|
|
1569
|
-
let nextContent = addNamedImport(content, "@/lib/evlog-auth", ["identifyEvlogUser"]);
|
|
1570
|
-
nextContent = nextContent.replace("function handler(req:", "async function handler(req:");
|
|
1571
|
-
for (const marker of [
|
|
1572
|
-
"async function handler(req: NextRequest) {",
|
|
1573
|
-
"async function handleRequest(req: NextRequest) {",
|
|
1574
|
-
"export const POST = withEvlog(async (req: Request) => {"
|
|
1575
|
-
]) nextContent = insertAfterOnce(nextContent, marker, "\n await identifyEvlogUser(req);", "identifyEvlogUser(req)");
|
|
1576
|
-
return nextContent;
|
|
1577
|
-
}
|
|
1578
|
-
function addSvelteBetterAuthEvlogSetup(content, config) {
|
|
1579
|
-
if (!content.includes("authHandle") || content.includes("evlogAuthHandle")) return content;
|
|
1580
|
-
let nextContent = addNamedImport(content, "evlog/better-auth", ["createAuthMiddleware", "type BetterAuthInstance"]);
|
|
1581
|
-
if (!nextContent.includes(`@${config.projectName}/auth`)) nextContent = prependMissingImports(nextContent, [getAuthImportLine(config)]);
|
|
1582
|
-
if (usesCreateAuthFactory(config) && config.webDeploy === "cloudflare" && !nextContent.includes(`@${config.projectName}/env/server`)) nextContent = prependMissingImports(nextContent, [`import { env as localEnv } from "@${config.projectName}/env/server";`]);
|
|
1583
|
-
const authExpression = getAuthExpression(config);
|
|
1584
|
-
const authOptions = "{ exclude: [\"/api/auth/**\"], maskEmail: true }";
|
|
1585
|
-
const authHandleSnippet = usesCreateAuthFactory(config) && config.webDeploy === "cloudflare" ? `const evlogAuthHandle: Handle = async ({ event, resolve }) => {\n\tif (building) {\n\t\treturn resolve(event);\n\t}\n\n\tconst authEnv = event.platform?.env ?? localEnv;\n\tconst identifyUser = createAuthMiddleware(createAuth(authEnv) as BetterAuthInstance, ${authOptions});\n\tawait identifyUser(event.locals.log, event.request.headers, event.url.pathname);\n\treturn resolve(event);\n};\n\n` : `const identifyUser = createAuthMiddleware(${authExpression} as BetterAuthInstance, ${authOptions});\n\nconst evlogAuthHandle: Handle = async ({ event, resolve }) => {\n\tawait identifyUser(event.locals.log, event.request.headers, event.url.pathname);\n\treturn resolve(event);\n};\n\n`;
|
|
1586
|
-
nextContent = insertAfterOnce(nextContent, "const { handle: evlogHandle, handleError } = createEvlogHooks();\n\n", authHandleSnippet, "evlogAuthHandle");
|
|
1587
|
-
return nextContent.replace("sequence(evlogHandle as Handle, authHandle)", "sequence(evlogHandle as Handle, evlogAuthHandle, authHandle)").replace("sequence(evlogHandle, authHandle)", "sequence(evlogHandle as Handle, evlogAuthHandle, authHandle)");
|
|
1588
|
-
}
|
|
1589
|
-
function addAstroBetterAuthEvlogSetup(content, config) {
|
|
1590
|
-
if (content.includes("createAuthMiddleware(")) return content;
|
|
1591
|
-
let nextContent = addNamedImport(content, "evlog/better-auth", ["createAuthMiddleware", "type BetterAuthInstance"]);
|
|
1592
|
-
if (!nextContent.includes(`@${config.projectName}/auth`)) nextContent = prependMissingImports(nextContent, [getAuthImportLine(config)]);
|
|
1593
|
-
const authExpression = getAuthExpression(config);
|
|
1594
|
-
const authOptions = "{ exclude: [\"/api/auth/**\"], maskEmail: true }";
|
|
1595
|
-
const usesFactory = usesCreateAuthFactory(config);
|
|
1596
|
-
if (!usesFactory) nextContent = insertBeforeOnce(nextContent, "export const onRequest", `const identifyUser = createAuthMiddleware(${authExpression} as BetterAuthInstance, ${authOptions});\n\n`, "const identifyUser = createAuthMiddleware(");
|
|
1597
|
-
for (const marker of ["context.locals.log = log;", "locals.log = log;"]) {
|
|
1598
|
-
if (!nextContent.includes(marker)) continue;
|
|
1599
|
-
const requestExpression = marker.startsWith("context") ? "context.request" : "request";
|
|
1600
|
-
const identifySnippet = usesFactory ? `\n\n const identifyUser = createAuthMiddleware(${authExpression} as BetterAuthInstance, ${authOptions});\n await identifyUser(log, ${requestExpression}.headers, url.pathname);` : `\n\n await identifyUser(log, ${requestExpression}.headers, url.pathname);`;
|
|
1601
|
-
return insertAfterOnce(nextContent, marker, identifySnippet, "identifyUser(log");
|
|
1602
|
-
}
|
|
1603
|
-
return nextContent;
|
|
1604
|
-
}
|
|
1605
|
-
function getNextEvlogFile(serviceName) {
|
|
1606
|
-
return `import { createEvlog } from "evlog/next";
|
|
1607
|
-
import { createInstrumentation } from "evlog/next/instrumentation/create";
|
|
1608
|
-
|
|
1609
|
-
export const { withEvlog, useLogger, log, createError } = createEvlog({
|
|
1610
|
-
service: "${serviceName}",
|
|
1611
|
-
});
|
|
1612
|
-
|
|
1613
|
-
export const { register, onRequestError } = createInstrumentation({
|
|
1614
|
-
service: "${serviceName}",
|
|
1615
|
-
});
|
|
1616
|
-
`;
|
|
1617
|
-
}
|
|
1618
|
-
function getNextInstrumentationFile() {
|
|
1619
|
-
return `import { defineNodeInstrumentation } from "evlog/next/instrumentation";
|
|
1620
|
-
|
|
1621
|
-
export const { register, onRequestError } = defineNodeInstrumentation(() => import("./src/lib/evlog"));
|
|
1622
|
-
`;
|
|
1623
|
-
}
|
|
1624
|
-
function getNextProxyFile() {
|
|
1625
|
-
return `import { evlogMiddleware } from "evlog/next";
|
|
1626
|
-
|
|
1627
|
-
export const proxy = evlogMiddleware();
|
|
1628
|
-
|
|
1629
|
-
export const config = {
|
|
1630
|
-
matcher: ["/api/:path*"],
|
|
1631
|
-
};
|
|
1632
|
-
`;
|
|
1633
|
-
}
|
|
1634
|
-
function getNextEvlogAuthFile(config) {
|
|
1635
|
-
if (usesCreateAuthFactory(config)) return `${getAuthImportLine(config)}
|
|
1636
|
-
import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
|
|
1637
|
-
import { useLogger } from "@/lib/evlog";
|
|
1638
|
-
|
|
1639
|
-
export async function identifyEvlogUser(request: Request) {
|
|
1640
|
-
const identifyUser = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {
|
|
1641
|
-
exclude: ["/api/auth/**"],
|
|
1642
|
-
maskEmail: true,
|
|
1643
|
-
});
|
|
1644
|
-
await identifyUser(useLogger(), request.headers, new URL(request.url).pathname);
|
|
1645
|
-
}
|
|
1646
|
-
`;
|
|
1647
|
-
return `${getAuthImportLine(config)}
|
|
1648
|
-
import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
|
|
1649
|
-
import { useLogger } from "@/lib/evlog";
|
|
1650
|
-
|
|
1651
|
-
const identifyUser = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {
|
|
1652
|
-
exclude: ["/api/auth/**"],
|
|
1653
|
-
maskEmail: true,
|
|
1654
|
-
});
|
|
1655
|
-
|
|
1656
|
-
export async function identifyEvlogUser(request: Request) {
|
|
1657
|
-
await identifyUser(useLogger(), request.headers, new URL(request.url).pathname);
|
|
1658
|
-
}
|
|
1659
|
-
`;
|
|
1660
|
-
}
|
|
1661
|
-
function getNitroEvlogAuthPluginFile(config) {
|
|
1662
|
-
if (usesCreateAuthFactory(config)) return `${getAuthImportLine(config)}
|
|
1663
|
-
import { createAuthIdentifier, type BetterAuthInstance } from "evlog/better-auth";
|
|
1664
|
-
|
|
1665
|
-
export default defineNitroPlugin((nitroApp) => {
|
|
1666
|
-
nitroApp.hooks.hook("request", async (event) => {
|
|
1667
|
-
const identify = createAuthIdentifier(${getAuthExpression(config)} as BetterAuthInstance, {
|
|
1668
|
-
exclude: ["/api/auth/**"],
|
|
1669
|
-
maskEmail: true,
|
|
1670
|
-
});
|
|
1671
|
-
await identify(event);
|
|
1672
|
-
});
|
|
1673
|
-
});
|
|
1674
|
-
`;
|
|
1675
|
-
return `${getAuthImportLine(config)}
|
|
1676
|
-
import { createAuthIdentifier, type BetterAuthInstance } from "evlog/better-auth";
|
|
1677
|
-
|
|
1678
|
-
export default defineNitroPlugin((nitroApp) => {
|
|
1679
|
-
nitroApp.hooks.hook(
|
|
1680
|
-
"request",
|
|
1681
|
-
createAuthIdentifier(${getAuthExpression(config)} as BetterAuthInstance, {
|
|
1682
|
-
exclude: ["/api/auth/**"],
|
|
1683
|
-
maskEmail: true,
|
|
1684
|
-
}),
|
|
1685
|
-
);
|
|
1686
|
-
});
|
|
1687
|
-
`;
|
|
1688
|
-
}
|
|
1689
|
-
function getNuxtEvlogAuthMiddlewareFile(config) {
|
|
1690
|
-
if (usesCreateAuthFactory(config)) return `${getAuthImportLine(config)}
|
|
1691
|
-
import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
|
|
1692
|
-
|
|
1693
|
-
export default defineEventHandler(async (event) => {
|
|
1694
|
-
if (!event.context.log) return;
|
|
1695
|
-
const identify = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {
|
|
1696
|
-
exclude: ["/api/auth/**"],
|
|
1697
|
-
maskEmail: true,
|
|
1698
|
-
});
|
|
1699
|
-
await identify(event.context.log, event.headers, event.path);
|
|
1700
|
-
});
|
|
1701
|
-
`;
|
|
1702
|
-
return `${getAuthImportLine(config)}
|
|
1703
|
-
import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
|
|
1704
|
-
|
|
1705
|
-
const identify = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {
|
|
1706
|
-
exclude: ["/api/auth/**"],
|
|
1707
|
-
maskEmail: true,
|
|
1708
|
-
});
|
|
1709
|
-
|
|
1710
|
-
export default defineEventHandler(async (event) => {
|
|
1711
|
-
if (!event.context.log) return;
|
|
1712
|
-
await identify(event.context.log, event.headers, event.path);
|
|
1713
|
-
});
|
|
1714
|
-
`;
|
|
1715
|
-
}
|
|
1716
|
-
function getTanstackNitroConfigFile(serviceName) {
|
|
1717
|
-
return `import { defineConfig } from "nitro";
|
|
1718
|
-
import evlog from "evlog/nitro/v3";
|
|
1719
|
-
|
|
1720
|
-
export default defineConfig({
|
|
1721
|
-
experimental: {
|
|
1722
|
-
asyncContext: true,
|
|
1723
|
-
},
|
|
1724
|
-
modules: [
|
|
1725
|
-
evlog({
|
|
1726
|
-
env: { service: "${serviceName}" },
|
|
1727
|
-
}),
|
|
1728
|
-
],
|
|
1729
|
-
});
|
|
1730
|
-
`;
|
|
1731
|
-
}
|
|
1732
|
-
function getAstroMiddlewareFile(serviceName) {
|
|
1733
|
-
return `import { defineMiddleware } from "astro:middleware";
|
|
1734
|
-
import { createRequestLogger, initLogger } from "evlog";
|
|
1735
|
-
|
|
1736
|
-
initLogger({
|
|
1737
|
-
env: { service: "${serviceName}" },
|
|
1738
|
-
});
|
|
1739
|
-
|
|
1740
|
-
export const onRequest = defineMiddleware(async ({ request, locals }, next) => {
|
|
1741
|
-
const url = new URL(request.url);
|
|
1742
|
-
const log = createRequestLogger({
|
|
1743
|
-
method: request.method,
|
|
1744
|
-
path: url.pathname,
|
|
1745
|
-
});
|
|
1746
|
-
|
|
1747
|
-
locals.log = log;
|
|
1748
|
-
|
|
1749
|
-
try {
|
|
1750
|
-
const response = await next();
|
|
1751
|
-
log.emit();
|
|
1752
|
-
return response;
|
|
1753
|
-
} catch (error) {
|
|
1754
|
-
log.error(error instanceof Error ? error : new Error(String(error)));
|
|
1755
|
-
log.emit();
|
|
1756
|
-
throw error;
|
|
1757
|
-
}
|
|
1758
|
-
});
|
|
1759
|
-
`;
|
|
1760
|
-
}
|
|
1761
|
-
function getAstroEnvFile() {
|
|
1762
|
-
return `/// <reference types="astro/client" />
|
|
1763
|
-
|
|
1764
|
-
import type { RequestLogger } from "evlog";
|
|
1765
|
-
|
|
1766
|
-
declare namespace App {
|
|
1767
|
-
interface Locals {
|
|
1768
|
-
log: RequestLogger;
|
|
1769
|
-
}
|
|
1770
|
-
}
|
|
1771
|
-
`;
|
|
1772
|
-
}
|
|
1773
|
-
async function setupNextEvlog(config, serviceName) {
|
|
1774
|
-
const webDir = path.join(config.projectDir, "apps/web");
|
|
1775
|
-
const evlogPath = path.join(webDir, "src/lib/evlog.ts");
|
|
1776
|
-
if (!await fs.pathExists(evlogPath)) await writeFileIfChanged(evlogPath, getNextEvlogFile(serviceName));
|
|
1777
|
-
const identifyWebAuth = shouldIdentifyWebAuth(config);
|
|
1778
|
-
if (identifyWebAuth) {
|
|
1779
|
-
const evlogAuthPath = path.join(webDir, "src/lib/evlog-auth.ts");
|
|
1780
|
-
if (!await fs.pathExists(evlogAuthPath)) await writeFileIfChanged(evlogAuthPath, getNextEvlogAuthFile(config));
|
|
1781
|
-
}
|
|
1782
|
-
const instrumentationPath = path.join(webDir, "instrumentation.ts");
|
|
1783
|
-
if (!await fs.pathExists(instrumentationPath)) await writeFileIfChanged(instrumentationPath, getNextInstrumentationFile());
|
|
1784
|
-
const proxyPath = path.join(webDir, "src/proxy.ts");
|
|
1785
|
-
const rootProxyPath = path.join(webDir, "proxy.ts");
|
|
1786
|
-
if (!await fs.pathExists(proxyPath) && !await fs.pathExists(rootProxyPath)) await writeFileIfChanged(proxyPath, getNextProxyFile());
|
|
1787
|
-
const updateNextApiRoute = (content) => {
|
|
1788
|
-
let nextContent = addNextRouteWrappers(content);
|
|
1789
|
-
if (identifyWebAuth) nextContent = addNextBetterAuthToRoute(nextContent);
|
|
1790
|
-
return nextContent;
|
|
1791
|
-
};
|
|
1792
|
-
await updateFileIfExists(path.join(webDir, "src/app/api/trpc/[trpc]/route.ts"), updateNextApiRoute);
|
|
1793
|
-
await updateFileIfExists(path.join(webDir, "src/app/api/rpc/[[...rest]]/route.ts"), updateNextApiRoute);
|
|
1794
|
-
if (config.examples.includes("ai")) await updateFileIfExists(path.join(webDir, "src/app/api/ai/route.ts"), (content) => {
|
|
1795
|
-
let nextContent = addNextAiEvlogSetup(content);
|
|
1796
|
-
if (identifyWebAuth) nextContent = addNextBetterAuthToRoute(nextContent);
|
|
1797
|
-
return nextContent;
|
|
1798
|
-
});
|
|
1799
|
-
}
|
|
1800
|
-
async function setupNuxtEvlog(config, serviceName) {
|
|
1801
|
-
const webDir = path.join(config.projectDir, "apps/web");
|
|
1802
|
-
await updateFileIfExists(path.join(webDir, "nuxt.config.ts"), (content) => addNuxtEvlogSetup(content, serviceName));
|
|
1803
|
-
if (shouldIdentifyWebAuth(config)) {
|
|
1804
|
-
const oldAuthPluginPath = path.join(webDir, "server/plugins/evlog-auth.ts");
|
|
1805
|
-
if (await fs.pathExists(oldAuthPluginPath)) {
|
|
1806
|
-
if ((await fs.readFile(oldAuthPluginPath, "utf-8")).includes("evlog/better-auth")) await fs.remove(oldAuthPluginPath);
|
|
1807
|
-
}
|
|
1808
|
-
const authMiddlewarePath = path.join(webDir, "server/middleware/evlog-auth.ts");
|
|
1809
|
-
if (!await fs.pathExists(authMiddlewarePath)) await writeFileIfChanged(authMiddlewarePath, getNuxtEvlogAuthMiddlewareFile(config));
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
async function setupSvelteEvlog(config, serviceName) {
|
|
1813
|
-
const webDir = path.join(config.projectDir, "apps/web");
|
|
1814
|
-
await updateFileIfExists(path.join(webDir, "vite.config.ts"), (content) => addSvelteViteEvlogSetup(content, serviceName));
|
|
1815
|
-
const hooksPath = path.join(webDir, "src/hooks.server.ts");
|
|
1816
|
-
if (await fs.pathExists(hooksPath)) await updateFileIfExists(hooksPath, addSvelteHooksEvlogSetup);
|
|
1817
|
-
else await writeFileIfChanged(hooksPath, `import { createEvlogHooks } from "evlog/sveltekit";
|
|
1818
|
-
|
|
1819
|
-
export const { handle, handleError } = createEvlogHooks();
|
|
1820
|
-
`);
|
|
1821
|
-
await updateFileIfExists(path.join(webDir, "src/app.d.ts"), addSvelteLocalsType);
|
|
1822
|
-
if (shouldIdentifyWebAuth(config)) await updateFileIfExists(path.join(webDir, "src/hooks.server.ts"), (content) => addSvelteBetterAuthEvlogSetup(content, config));
|
|
1823
|
-
}
|
|
1824
|
-
async function setupTanstackStartEvlog(config, serviceName) {
|
|
1825
|
-
const webDir = path.join(config.projectDir, "apps/web");
|
|
1826
|
-
const nitroConfigPath = path.join(webDir, "nitro.config.ts");
|
|
1827
|
-
if (!await fs.pathExists(nitroConfigPath)) await writeFileIfChanged(nitroConfigPath, getTanstackNitroConfigFile(serviceName));
|
|
1828
|
-
await updateFileIfExists(path.join(webDir, "src/routes/__root.tsx"), addTanstackStartRootEvlogSetup);
|
|
1829
|
-
if (shouldIdentifyWebAuth(config)) {
|
|
1830
|
-
const authPluginPath = path.join(webDir, "server/plugins/evlog-auth.ts");
|
|
1831
|
-
if (!await fs.pathExists(authPluginPath)) await writeFileIfChanged(authPluginPath, getNitroEvlogAuthPluginFile(config));
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
async function setupAstroEvlog(config, serviceName) {
|
|
1835
|
-
const webDir = path.join(config.projectDir, "apps/web");
|
|
1836
|
-
const middlewarePath = path.join(webDir, "src/middleware.ts");
|
|
1837
|
-
if (!await fs.pathExists(middlewarePath)) await writeFileIfChanged(middlewarePath, getAstroMiddlewareFile(serviceName));
|
|
1838
|
-
else await updateFileIfExists(middlewarePath, (content) => addAstroMiddlewareEvlogSetup(content, serviceName));
|
|
1839
|
-
const envPath = path.join(webDir, "src/env.d.ts");
|
|
1840
|
-
if (!await fs.pathExists(envPath)) await writeFileIfChanged(envPath, getAstroEnvFile());
|
|
1841
|
-
else await updateFileIfExists(envPath, addAstroLocalsType);
|
|
1842
|
-
if (shouldIdentifyWebAuth(config)) await updateFileIfExists(middlewarePath, (content) => addAstroBetterAuthEvlogSetup(content, config));
|
|
1843
|
-
}
|
|
1844
|
-
async function setupEvlogWeb(config) {
|
|
1845
|
-
const frontend = getEvlogWebFrontend(config.frontend);
|
|
1846
|
-
if (!frontend) return;
|
|
1847
|
-
const serviceName = `${config.projectName}-web`;
|
|
1848
|
-
if (frontend === "next") await setupNextEvlog(config, serviceName);
|
|
1849
|
-
else if (frontend === "nuxt") await setupNuxtEvlog(config, serviceName);
|
|
1850
|
-
else if (frontend === "svelte") await setupSvelteEvlog(config, serviceName);
|
|
1851
|
-
else if (frontend === "tanstack-start") await setupTanstackStartEvlog(config, serviceName);
|
|
1852
|
-
else if (frontend === "astro") await setupAstroEvlog(config, serviceName);
|
|
1853
|
-
}
|
|
1854
|
-
async function setupEvlog(config) {
|
|
1855
|
-
return Result.tryPromise({
|
|
1856
|
-
try: async () => {
|
|
1857
|
-
if (isEvlogBackend(config.backend)) {
|
|
1858
|
-
const serverIndexPath = path.join(config.projectDir, "apps/server/src/index.ts");
|
|
1859
|
-
if (await fs.pathExists(serverIndexPath)) {
|
|
1860
|
-
const content = await fs.readFile(serverIndexPath, "utf-8");
|
|
1861
|
-
let nextContent = addEvlogServerSetup(content, config.backend, `${config.projectName}-server`);
|
|
1862
|
-
if (config.auth === "better-auth") nextContent = addEvlogBetterAuthServerSetup(nextContent, config.backend, getAuthExpression(config));
|
|
1863
|
-
if (nextContent !== content) await fs.writeFile(serverIndexPath, nextContent);
|
|
1864
|
-
}
|
|
1865
|
-
}
|
|
1866
|
-
await setupEvlogWeb(config);
|
|
1867
|
-
},
|
|
1868
|
-
catch: (error) => new AddonSetupError({
|
|
1869
|
-
addon: "evlog",
|
|
1870
|
-
message: `Failed to set up evlog: ${error instanceof Error ? error.message : String(error)}`,
|
|
1871
|
-
cause: error
|
|
1872
|
-
})
|
|
1873
|
-
});
|
|
1874
|
-
}
|
|
1875
|
-
//#endregion
|
|
1876
1344
|
//#region src/prompts/navigable-group.ts
|
|
1877
1345
|
/**
|
|
1878
1346
|
* Navigable group - a group of prompts that allows going back
|
|
@@ -1915,7 +1383,7 @@ async function navigableGroup(prompts, opts) {
|
|
|
1915
1383
|
goingBack = false;
|
|
1916
1384
|
continue;
|
|
1917
1385
|
}
|
|
1918
|
-
if (isCancel$
|
|
1386
|
+
if (isCancel$2(result)) {
|
|
1919
1387
|
if (typeof opts?.onCancel === "function") {
|
|
1920
1388
|
results[name] = "canceled";
|
|
1921
1389
|
opts.onCancel({ results });
|
|
@@ -2036,206 +1504,6 @@ function getPackageRunnerPrefix(packageManager) {
|
|
|
2036
1504
|
}
|
|
2037
1505
|
}
|
|
2038
1506
|
//#endregion
|
|
2039
|
-
//#region src/helpers/addons/fumadocs-setup.ts
|
|
2040
|
-
const TEMPLATES$2 = {
|
|
2041
|
-
"next-mdx": {
|
|
2042
|
-
label: "Next.js: Fumadocs MDX",
|
|
2043
|
-
hint: "recommended",
|
|
2044
|
-
value: "+next+fuma-docs-mdx"
|
|
2045
|
-
},
|
|
2046
|
-
"next-mdx-static": {
|
|
2047
|
-
label: "Next.js Static: Fumadocs MDX",
|
|
2048
|
-
value: "+next+fuma-docs-mdx+static"
|
|
2049
|
-
},
|
|
2050
|
-
waku: {
|
|
2051
|
-
label: "Waku: Fumadocs MDX",
|
|
2052
|
-
value: "waku"
|
|
2053
|
-
},
|
|
2054
|
-
"react-router": {
|
|
2055
|
-
label: "React Router: Fumadocs MDX (not RSC)",
|
|
2056
|
-
value: "react-router"
|
|
2057
|
-
},
|
|
2058
|
-
"react-router-spa": {
|
|
2059
|
-
label: "React Router SPA: Fumadocs MDX (not RSC)",
|
|
2060
|
-
hint: "SPA mode allows you to host the site statically, compatible with a CDN.",
|
|
2061
|
-
value: "react-router-spa"
|
|
2062
|
-
},
|
|
2063
|
-
"tanstack-start": {
|
|
2064
|
-
label: "Tanstack Start: Fumadocs MDX (not RSC)",
|
|
2065
|
-
value: "tanstack-start"
|
|
2066
|
-
},
|
|
2067
|
-
"tanstack-start-spa": {
|
|
2068
|
-
label: "Tanstack Start SPA: Fumadocs MDX (not RSC)",
|
|
2069
|
-
hint: "SPA mode allows you to host the site statically, compatible with a CDN.",
|
|
2070
|
-
value: "tanstack-start-spa"
|
|
2071
|
-
}
|
|
2072
|
-
};
|
|
2073
|
-
const DEFAULT_TEMPLATE$2 = "next-mdx";
|
|
2074
|
-
const DEFAULT_DEV_PORT$1 = 4e3;
|
|
2075
|
-
function aiChatDisabledForTemplate(template) {
|
|
2076
|
-
return template === "next-mdx-static" || template.endsWith("-spa");
|
|
2077
|
-
}
|
|
2078
|
-
function getFumadocsLinter(addons) {
|
|
2079
|
-
if (addons.includes("oxlint")) return "oxlint";
|
|
2080
|
-
if (addons.includes("biome") || addons.includes("ultracite")) return "biome";
|
|
2081
|
-
if (addons.includes("vite-plus")) return "oxlint";
|
|
2082
|
-
}
|
|
2083
|
-
function getFumadocsAddonContext(currentAddons, persistedAddons) {
|
|
2084
|
-
return Array.from(new Set([...persistedAddons ?? [], ...currentAddons]));
|
|
2085
|
-
}
|
|
2086
|
-
async function setupFumadocs(config) {
|
|
2087
|
-
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
2088
|
-
const { packageManager, projectDir } = config;
|
|
2089
|
-
cliLog.info("Setting up Fumadocs...");
|
|
2090
|
-
const configuredOptions = config.addonOptions?.fumadocs;
|
|
2091
|
-
let template = configuredOptions?.template;
|
|
2092
|
-
let search = configuredOptions?.search;
|
|
2093
|
-
let ogImage = configuredOptions?.ogImage;
|
|
2094
|
-
let aiChat = configuredOptions?.aiChat;
|
|
2095
|
-
if (isSilent()) template = template ?? DEFAULT_TEMPLATE$2;
|
|
2096
|
-
else {
|
|
2097
|
-
const promptResult = await Result.tryPromise({
|
|
2098
|
-
try: () => navigableGroup({
|
|
2099
|
-
template: async () => {
|
|
2100
|
-
if (template !== void 0) return template;
|
|
2101
|
-
return navigableSelect({
|
|
2102
|
-
message: "Choose a template",
|
|
2103
|
-
options: Object.entries(TEMPLATES$2).map(([key, t]) => ({
|
|
2104
|
-
value: key,
|
|
2105
|
-
label: t.label,
|
|
2106
|
-
hint: "hint" in t ? t.hint : void 0
|
|
2107
|
-
})),
|
|
2108
|
-
initialValue: DEFAULT_TEMPLATE$2
|
|
2109
|
-
});
|
|
2110
|
-
},
|
|
2111
|
-
search: async () => {
|
|
2112
|
-
if (search !== void 0) return search;
|
|
2113
|
-
return navigableSelect({
|
|
2114
|
-
message: "Choose a search solution?",
|
|
2115
|
-
options: [{
|
|
2116
|
-
value: "orama",
|
|
2117
|
-
label: "Default",
|
|
2118
|
-
hint: "local search powered by Orama, recommended"
|
|
2119
|
-
}, {
|
|
2120
|
-
value: "orama-cloud",
|
|
2121
|
-
label: "Orama Cloud",
|
|
2122
|
-
hint: "3rd party search solution, signup needed"
|
|
2123
|
-
}],
|
|
2124
|
-
initialValue: "orama"
|
|
2125
|
-
});
|
|
2126
|
-
},
|
|
2127
|
-
ogImage: async ({ results }) => {
|
|
2128
|
-
if (ogImage !== void 0) return ogImage;
|
|
2129
|
-
if (!(results.template ?? template ?? DEFAULT_TEMPLATE$2).startsWith("next-")) return "skip";
|
|
2130
|
-
return navigableSelect({
|
|
2131
|
-
message: "Configure Open Graph Image generation?",
|
|
2132
|
-
options: [{
|
|
2133
|
-
value: "next-og",
|
|
2134
|
-
label: "next/og",
|
|
2135
|
-
hint: "Next.js built-in solution"
|
|
2136
|
-
}, {
|
|
2137
|
-
value: "takumi",
|
|
2138
|
-
label: "Takumi",
|
|
2139
|
-
hint: "Output WebP format, framework-agnostic"
|
|
2140
|
-
}],
|
|
2141
|
-
initialValue: "next-og"
|
|
2142
|
-
});
|
|
2143
|
-
},
|
|
2144
|
-
aiChat: async ({ results }) => {
|
|
2145
|
-
if (aiChat !== void 0) return aiChat;
|
|
2146
|
-
if (aiChatDisabledForTemplate(results.template ?? template ?? DEFAULT_TEMPLATE$2)) return "none";
|
|
2147
|
-
return navigableSelect({
|
|
2148
|
-
message: "Configure AI Chat?",
|
|
2149
|
-
options: [
|
|
2150
|
-
{
|
|
2151
|
-
value: "none",
|
|
2152
|
-
label: "No"
|
|
2153
|
-
},
|
|
2154
|
-
{
|
|
2155
|
-
value: "openrouter",
|
|
2156
|
-
label: "AI SDK",
|
|
2157
|
-
hint: "default to OpenRouter"
|
|
2158
|
-
},
|
|
2159
|
-
{
|
|
2160
|
-
value: "llmgateway",
|
|
2161
|
-
label: "LLM Gateway"
|
|
2162
|
-
},
|
|
2163
|
-
{
|
|
2164
|
-
value: "inkeep",
|
|
2165
|
-
label: "Inkeep AI",
|
|
2166
|
-
hint: "API key required"
|
|
2167
|
-
}
|
|
2168
|
-
],
|
|
2169
|
-
initialValue: "none"
|
|
2170
|
-
});
|
|
2171
|
-
}
|
|
2172
|
-
}),
|
|
2173
|
-
catch: (e) => new AddonSetupError({
|
|
2174
|
-
addon: "fumadocs",
|
|
2175
|
-
message: `Failed to run Fumadocs prompts: ${e instanceof Error ? e.message : String(e)}`,
|
|
2176
|
-
cause: e
|
|
2177
|
-
})
|
|
2178
|
-
});
|
|
2179
|
-
if (promptResult.isErr()) return Result.err(promptResult.error);
|
|
2180
|
-
const results = promptResult.value;
|
|
2181
|
-
if (results.template === void 0 || results.search === void 0 || results.ogImage === void 0 || results.aiChat === void 0) return userCancelled("Operation cancelled");
|
|
2182
|
-
template = results.template;
|
|
2183
|
-
search = results.search;
|
|
2184
|
-
ogImage = results.ogImage === "skip" ? void 0 : results.ogImage;
|
|
2185
|
-
aiChat = results.aiChat === "none" ? void 0 : results.aiChat;
|
|
2186
|
-
}
|
|
2187
|
-
if (!template) return userCancelled("Operation cancelled");
|
|
2188
|
-
const isNextTemplate = template.startsWith("next-");
|
|
2189
|
-
if (!isNextTemplate) ogImage = void 0;
|
|
2190
|
-
if (aiChatDisabledForTemplate(template)) aiChat = void 0;
|
|
2191
|
-
const templateArg = TEMPLATES$2[template].value;
|
|
2192
|
-
const devPort = configuredOptions?.devPort ?? DEFAULT_DEV_PORT$1;
|
|
2193
|
-
const options = [
|
|
2194
|
-
`--template ${templateArg}`,
|
|
2195
|
-
`--pm ${packageManager}`,
|
|
2196
|
-
"--no-git"
|
|
2197
|
-
];
|
|
2198
|
-
if (isNextTemplate) options.push("--src");
|
|
2199
|
-
const persistedConfig = await readKubojsConfig(projectDir);
|
|
2200
|
-
const linter = getFumadocsLinter(getFumadocsAddonContext(config.addons, persistedConfig?.addons));
|
|
2201
|
-
if (linter) options.push(`--linter ${linter}`);
|
|
2202
|
-
if (search) options.push(`--search ${search}`);
|
|
2203
|
-
if (ogImage) options.push(`--og-image ${ogImage}`);
|
|
2204
|
-
if (aiChat) options.push(`--ai-chat ${aiChat}`);
|
|
2205
|
-
const args = getPackageExecutionArgs(packageManager, `create-fumadocs-app@latest fumadocs ${options.join(" ")}`);
|
|
2206
|
-
const appsDir = path.join(projectDir, "apps");
|
|
2207
|
-
await fs.ensureDir(appsDir);
|
|
2208
|
-
const s = createSpinner();
|
|
2209
|
-
s.start("Running Fumadocs create command...");
|
|
2210
|
-
const result = await Result.tryPromise({
|
|
2211
|
-
try: async () => {
|
|
2212
|
-
await $({
|
|
2213
|
-
cwd: appsDir,
|
|
2214
|
-
env: { CI: "true" }
|
|
2215
|
-
})`${args}`;
|
|
2216
|
-
const fumadocsDir = path.join(projectDir, "apps", "fumadocs");
|
|
2217
|
-
const packageJsonPath = path.join(fumadocsDir, "package.json");
|
|
2218
|
-
if (await fs.pathExists(packageJsonPath)) {
|
|
2219
|
-
const packageJson = await fs.readJson(packageJsonPath);
|
|
2220
|
-
packageJson.name = "fumadocs";
|
|
2221
|
-
if (packageJson.scripts?.dev) packageJson.scripts.dev = `${packageJson.scripts.dev} --port=${devPort}`;
|
|
2222
|
-
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2223
|
-
}
|
|
2224
|
-
},
|
|
2225
|
-
catch: (e) => new AddonSetupError({
|
|
2226
|
-
addon: "fumadocs",
|
|
2227
|
-
message: `Failed to set up Fumadocs: ${e instanceof Error ? e.message : String(e)}`,
|
|
2228
|
-
cause: e
|
|
2229
|
-
})
|
|
2230
|
-
});
|
|
2231
|
-
if (result.isErr()) {
|
|
2232
|
-
s.stop("Failed to set up Fumadocs");
|
|
2233
|
-
return result;
|
|
2234
|
-
}
|
|
2235
|
-
s.stop("Fumadocs setup complete!");
|
|
2236
|
-
return Result.ok(void 0);
|
|
2237
|
-
}
|
|
2238
|
-
//#endregion
|
|
2239
1507
|
//#region src/helpers/addons/mcp-setup.ts
|
|
2240
1508
|
const MCP_AGENTS = [
|
|
2241
1509
|
{
|
|
@@ -2310,7 +1578,7 @@ const MCP_AGENTS = [
|
|
|
2310
1578
|
}
|
|
2311
1579
|
];
|
|
2312
1580
|
const DEFAULT_SCOPE$1 = "project";
|
|
2313
|
-
const DEFAULT_AGENTS$
|
|
1581
|
+
const DEFAULT_AGENTS$1 = [
|
|
2314
1582
|
"cursor",
|
|
2315
1583
|
"claude-code",
|
|
2316
1584
|
"vscode"
|
|
@@ -2434,7 +1702,6 @@ function getAllMcpServers(config) {
|
|
|
2434
1702
|
function getRecommendedMcpServers(config, scope) {
|
|
2435
1703
|
const serversByKey = new Map(getAllMcpServers(config).map((server) => [server.key, server]));
|
|
2436
1704
|
const recommendedServerKeys = ["kubojs", "context7"];
|
|
2437
|
-
if (scope === "project" && config.addons.includes("nx")) recommendedServerKeys.push("nx");
|
|
2438
1705
|
if (config.runtime === "workers" || config.webDeploy === "cloudflare" || config.serverDeploy === "cloudflare") recommendedServerKeys.push("cloudflare-docs");
|
|
2439
1706
|
if (config.backend === "convex") recommendedServerKeys.push("convex");
|
|
2440
1707
|
if (hasReactBasedFrontend$1(config.frontend)) recommendedServerKeys.push("shadcn");
|
|
@@ -2473,7 +1740,7 @@ async function setupMcp(config) {
|
|
|
2473
1740
|
selectedServerKeys = configuredServerKeys?.filter((k) => availableServerKeys.has(k)) ?? serverOptions;
|
|
2474
1741
|
if (selectedServerKeys.length === 0) return Result.ok(void 0);
|
|
2475
1742
|
const agentOptions = filterAgentsForScope(scope);
|
|
2476
|
-
const defaultAgents = uniqueValues$1(DEFAULT_AGENTS$
|
|
1743
|
+
const defaultAgents = uniqueValues$1(DEFAULT_AGENTS$1.filter((a) => agentOptions.some((o) => o.value === a)));
|
|
2477
1744
|
selectedAgents = configuredAgents?.filter((a) => agentOptions.some((o) => o.value === a)) ?? defaultAgents;
|
|
2478
1745
|
if (selectedAgents.length === 0) return Result.ok(void 0);
|
|
2479
1746
|
} else {
|
|
@@ -2516,7 +1783,7 @@ async function setupMcp(config) {
|
|
|
2516
1783
|
if (currentServers !== void 0 && currentServers.length === 0) return [];
|
|
2517
1784
|
const agentOpts = filterAgentsForScope(currentScope);
|
|
2518
1785
|
if (agentOpts.length === 0) return [];
|
|
2519
|
-
const defaults = uniqueValues$1(DEFAULT_AGENTS$
|
|
1786
|
+
const defaults = uniqueValues$1(DEFAULT_AGENTS$1.filter((a) => agentOpts.some((o) => o.value === a)));
|
|
2520
1787
|
if (configuredAgents !== void 0) return configuredAgents.filter((a) => agentOpts.some((o) => o.value === a));
|
|
2521
1788
|
return navigableMultiselect({
|
|
2522
1789
|
message: "Select agents to install MCP servers to",
|
|
@@ -2762,7 +2029,7 @@ const AVAILABLE_AGENTS = [
|
|
|
2762
2029
|
}
|
|
2763
2030
|
];
|
|
2764
2031
|
const DEFAULT_SCOPE = "project";
|
|
2765
|
-
const DEFAULT_AGENTS
|
|
2032
|
+
const DEFAULT_AGENTS = [
|
|
2766
2033
|
"cursor",
|
|
2767
2034
|
"claude-code",
|
|
2768
2035
|
"github-copilot"
|
|
@@ -2797,8 +2064,6 @@ function getRecommendedSourceKeys(config) {
|
|
|
2797
2064
|
if (backend === "elysia") sources.push("elysiajs/skills");
|
|
2798
2065
|
if (backend === "convex") sources.push("waynesutton/convexskills");
|
|
2799
2066
|
if (addons.includes("opentui")) sources.push("msmps/opentui-skill");
|
|
2800
|
-
if (addons.includes("ultracite")) sources.push("haydenbleasel/ultracite");
|
|
2801
|
-
if (addons.includes("evlog")) sources.push("https://www.evlog.dev");
|
|
2802
2067
|
return sources;
|
|
2803
2068
|
}
|
|
2804
2069
|
const CURATED_SKILLS_BY_SOURCE = {
|
|
@@ -2910,7 +2175,7 @@ async function setupSkills(config) {
|
|
|
2910
2175
|
scope = configuredScope ?? DEFAULT_SCOPE;
|
|
2911
2176
|
selectedSkills = configuredSelections !== void 0 ? configuredSelections.flatMap((selection) => selection.skills.map((skill) => `${selection.source}::${skill}`)) : allSkillValues;
|
|
2912
2177
|
if (selectedSkills.length === 0) return Result.ok(void 0);
|
|
2913
|
-
selectedAgents = configuredAgents ? [...configuredAgents] : [...DEFAULT_AGENTS
|
|
2178
|
+
selectedAgents = configuredAgents ? [...configuredAgents] : [...DEFAULT_AGENTS];
|
|
2914
2179
|
if (selectedAgents.length === 0) return Result.ok(void 0);
|
|
2915
2180
|
} else {
|
|
2916
2181
|
const results = await navigableGroup({
|
|
@@ -2947,7 +2212,7 @@ async function setupSkills(config) {
|
|
|
2947
2212
|
message: "Select agents to install skills to",
|
|
2948
2213
|
options: AVAILABLE_AGENTS,
|
|
2949
2214
|
required: false,
|
|
2950
|
-
initialValues: [...DEFAULT_AGENTS
|
|
2215
|
+
initialValues: [...DEFAULT_AGENTS]
|
|
2951
2216
|
});
|
|
2952
2217
|
}
|
|
2953
2218
|
});
|
|
@@ -2996,44 +2261,6 @@ async function setupSkills(config) {
|
|
|
2996
2261
|
return Result.ok(void 0);
|
|
2997
2262
|
}
|
|
2998
2263
|
//#endregion
|
|
2999
|
-
//#region src/helpers/addons/starlight-setup.ts
|
|
3000
|
-
async function setupStarlight(config) {
|
|
3001
|
-
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
3002
|
-
const { packageManager, projectDir } = config;
|
|
3003
|
-
const s = createSpinner();
|
|
3004
|
-
s.start("Setting up Starlight docs...");
|
|
3005
|
-
const args = getPackageExecutionArgs(packageManager, `create-astro@latest ${[
|
|
3006
|
-
"docs",
|
|
3007
|
-
"--template",
|
|
3008
|
-
"starlight",
|
|
3009
|
-
"--yes",
|
|
3010
|
-
"--no-install",
|
|
3011
|
-
"--no-git",
|
|
3012
|
-
"--skip-houston"
|
|
3013
|
-
].join(" ")}`);
|
|
3014
|
-
const appsDir = path.join(projectDir, "apps");
|
|
3015
|
-
await fs.ensureDir(appsDir);
|
|
3016
|
-
const result = await Result.tryPromise({
|
|
3017
|
-
try: async () => {
|
|
3018
|
-
await $({
|
|
3019
|
-
cwd: appsDir,
|
|
3020
|
-
env: { CI: "true" }
|
|
3021
|
-
})`${args}`;
|
|
3022
|
-
},
|
|
3023
|
-
catch: (e) => new AddonSetupError({
|
|
3024
|
-
addon: "starlight",
|
|
3025
|
-
message: `Failed to set up Starlight docs: ${e instanceof Error ? e.message : String(e)}`,
|
|
3026
|
-
cause: e
|
|
3027
|
-
})
|
|
3028
|
-
});
|
|
3029
|
-
if (result.isErr()) {
|
|
3030
|
-
s.stop("Failed to set up Starlight docs");
|
|
3031
|
-
return result;
|
|
3032
|
-
}
|
|
3033
|
-
s.stop("Starlight docs setup successfully!");
|
|
3034
|
-
return Result.ok(void 0);
|
|
3035
|
-
}
|
|
3036
|
-
//#endregion
|
|
3037
2264
|
//#region src/helpers/addons/tauri-setup.ts
|
|
3038
2265
|
function getWebFrontend(frontend) {
|
|
3039
2266
|
return frontend.find((value) => types_exports.desktopWebFrontends.includes(value));
|
|
@@ -3114,7 +2341,7 @@ async function setupTauri(config) {
|
|
|
3114
2341
|
}
|
|
3115
2342
|
//#endregion
|
|
3116
2343
|
//#region src/helpers/addons/tui-setup.ts
|
|
3117
|
-
const TEMPLATES
|
|
2344
|
+
const TEMPLATES = {
|
|
3118
2345
|
core: {
|
|
3119
2346
|
label: "Core",
|
|
3120
2347
|
hint: "Basic OpenTUI template"
|
|
@@ -3128,7 +2355,7 @@ const TEMPLATES$1 = {
|
|
|
3128
2355
|
hint: "SolidJS-based OpenTUI template"
|
|
3129
2356
|
}
|
|
3130
2357
|
};
|
|
3131
|
-
const DEFAULT_TEMPLATE
|
|
2358
|
+
const DEFAULT_TEMPLATE = "core";
|
|
3132
2359
|
const TUI_LOCKFILES = [
|
|
3133
2360
|
"bun.lock",
|
|
3134
2361
|
"package-lock.json",
|
|
@@ -3138,7 +2365,7 @@ const TUI_LOCKFILES = [
|
|
|
3138
2365
|
function resolveTuiTemplate(config) {
|
|
3139
2366
|
const configuredTemplate = config.addonOptions?.opentui?.template;
|
|
3140
2367
|
if (configuredTemplate) return configuredTemplate;
|
|
3141
|
-
if (isSilent()) return DEFAULT_TEMPLATE
|
|
2368
|
+
if (isSilent()) return DEFAULT_TEMPLATE;
|
|
3142
2369
|
}
|
|
3143
2370
|
async function setupTui(config) {
|
|
3144
2371
|
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
@@ -3149,14 +2376,14 @@ async function setupTui(config) {
|
|
|
3149
2376
|
setIsFirstPrompt(true);
|
|
3150
2377
|
const selectedTemplate = await navigableSelect({
|
|
3151
2378
|
message: "Choose a template",
|
|
3152
|
-
options: Object.entries(TEMPLATES
|
|
2379
|
+
options: Object.entries(TEMPLATES).map(([key, templateOption]) => ({
|
|
3153
2380
|
value: key,
|
|
3154
2381
|
label: templateOption.label,
|
|
3155
2382
|
hint: templateOption.hint
|
|
3156
2383
|
})),
|
|
3157
|
-
initialValue: DEFAULT_TEMPLATE
|
|
2384
|
+
initialValue: DEFAULT_TEMPLATE
|
|
3158
2385
|
});
|
|
3159
|
-
if (isCancel$
|
|
2386
|
+
if (isCancel$2(selectedTemplate)) return userCancelled("Operation cancelled");
|
|
3160
2387
|
template = selectedTemplate;
|
|
3161
2388
|
}
|
|
3162
2389
|
const args = getPackageExecutionArgs(packageManager, `create-tui@latest --template ${template} --no-git --no-install tui`);
|
|
@@ -3234,324 +2461,6 @@ async function postProcessTuiWorkspace(tuiDir) {
|
|
|
3234
2461
|
return Result.ok(void 0);
|
|
3235
2462
|
}
|
|
3236
2463
|
//#endregion
|
|
3237
|
-
//#region src/helpers/addons/ultracite-setup.ts
|
|
3238
|
-
const LINTERS = {
|
|
3239
|
-
biome: { label: "Biome (Recommended)" },
|
|
3240
|
-
oxlint: { label: "Oxlint + Oxfmt" }
|
|
3241
|
-
};
|
|
3242
|
-
const AGENTS = {
|
|
3243
|
-
universal: { label: "Universal (AGENTS.md — covers all agents)" },
|
|
3244
|
-
claude: { label: "Claude Code" },
|
|
3245
|
-
codex: { label: "Codex" },
|
|
3246
|
-
jules: { label: "Jules" },
|
|
3247
|
-
replit: { label: "Replit Agent" },
|
|
3248
|
-
devin: { label: "Devin" },
|
|
3249
|
-
lovable: { label: "Lovable" },
|
|
3250
|
-
zencoder: { label: "Zencoder" },
|
|
3251
|
-
ona: { label: "Ona" },
|
|
3252
|
-
openclaw: { label: "OpenClaw" },
|
|
3253
|
-
continue: { label: "Continue" },
|
|
3254
|
-
"snowflake-cortex": { label: "Snowflake Cortex" },
|
|
3255
|
-
deepagents: { label: "Deepagents" },
|
|
3256
|
-
qoder: { label: "Qoder" },
|
|
3257
|
-
"kimi-cli": { label: "Kimi CLI" },
|
|
3258
|
-
mcpjam: { label: "MCPJam" },
|
|
3259
|
-
mux: { label: "Mux" },
|
|
3260
|
-
pi: { label: "Pi" },
|
|
3261
|
-
adal: { label: "AdaL" },
|
|
3262
|
-
copilot: { label: "GitHub Copilot" },
|
|
3263
|
-
cline: { label: "Cline" },
|
|
3264
|
-
amp: { label: "AMP" },
|
|
3265
|
-
aider: { label: "Aider" },
|
|
3266
|
-
"firebase-studio": { label: "Firebase Studio" },
|
|
3267
|
-
"open-hands": { label: "OpenHands" },
|
|
3268
|
-
gemini: { label: "Gemini" },
|
|
3269
|
-
junie: { label: "Junie" },
|
|
3270
|
-
augmentcode: { label: "Augment Code" },
|
|
3271
|
-
bob: { label: "IBM Bob" },
|
|
3272
|
-
"kilo-code": { label: "Kilo Code" },
|
|
3273
|
-
goose: { label: "Goose" },
|
|
3274
|
-
"roo-code": { label: "Roo Code" },
|
|
3275
|
-
warp: { label: "Warp" },
|
|
3276
|
-
droid: { label: "Droid" },
|
|
3277
|
-
opencode: { label: "OpenCode" },
|
|
3278
|
-
crush: { label: "Crush" },
|
|
3279
|
-
qwen: { label: "Qwen Code" },
|
|
3280
|
-
"amazon-q-cli": { label: "Amazon Q CLI" },
|
|
3281
|
-
firebender: { label: "Firebender" },
|
|
3282
|
-
"cursor-cli": { label: "Cursor CLI" },
|
|
3283
|
-
"mistral-vibe": { label: "Mistral Vibe" },
|
|
3284
|
-
vercel: { label: "Vercel Agent" }
|
|
3285
|
-
};
|
|
3286
|
-
const HOOKS = {
|
|
3287
|
-
cursor: { label: "Cursor" },
|
|
3288
|
-
windsurf: { label: "Windsurf" },
|
|
3289
|
-
codebuddy: { label: "CodeBuddy" },
|
|
3290
|
-
claude: { label: "Claude Code" },
|
|
3291
|
-
copilot: { label: "GitHub Copilot" }
|
|
3292
|
-
};
|
|
3293
|
-
const ULTRACITE_VERSION = "7.9.3";
|
|
3294
|
-
const DEFAULT_LINTER = "biome";
|
|
3295
|
-
const DEFAULT_EDITORS = ["vscode"];
|
|
3296
|
-
const DEFAULT_AGENTS = ["universal"];
|
|
3297
|
-
const DEFAULT_HOOKS = [];
|
|
3298
|
-
function getFrameworksFromFrontend(frontend) {
|
|
3299
|
-
const frameworkMap = {
|
|
3300
|
-
"tanstack-router": ["react", "tanstack"],
|
|
3301
|
-
"react-router": ["react", "remix"],
|
|
3302
|
-
"tanstack-start": ["react", "tanstack"],
|
|
3303
|
-
next: ["react", "next"],
|
|
3304
|
-
nuxt: ["vue"],
|
|
3305
|
-
"native-bare": ["react"],
|
|
3306
|
-
"native-uniwind": ["react"],
|
|
3307
|
-
"native-unistyles": ["react"],
|
|
3308
|
-
svelte: ["svelte"],
|
|
3309
|
-
solid: ["solid"],
|
|
3310
|
-
astro: ["astro"]
|
|
3311
|
-
};
|
|
3312
|
-
const frameworks = /* @__PURE__ */ new Set();
|
|
3313
|
-
for (const f of frontend) for (const framework of frameworkMap[f] ?? []) frameworks.add(framework);
|
|
3314
|
-
return Array.from(frameworks);
|
|
3315
|
-
}
|
|
3316
|
-
function buildUltraciteInitArgs({ packageManager, linter, frameworks, editors, agents, hooks, gitHooks }) {
|
|
3317
|
-
const ultraciteArgs = [
|
|
3318
|
-
"init",
|
|
3319
|
-
"--pm",
|
|
3320
|
-
packageManager,
|
|
3321
|
-
"--linter",
|
|
3322
|
-
linter
|
|
3323
|
-
];
|
|
3324
|
-
if (frameworks.length > 0) ultraciteArgs.push("--frameworks", ...frameworks);
|
|
3325
|
-
if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
|
|
3326
|
-
if (agents.length > 0) ultraciteArgs.push("--agents", ...agents);
|
|
3327
|
-
if (hooks.length > 0) ultraciteArgs.push("--hooks", ...hooks);
|
|
3328
|
-
if (gitHooks.length > 0) ultraciteArgs.push("--integrations", ...gitHooks);
|
|
3329
|
-
return [
|
|
3330
|
-
...getPackageRunnerPrefix(packageManager),
|
|
3331
|
-
`ultracite@${ULTRACITE_VERSION}`,
|
|
3332
|
-
...ultraciteArgs,
|
|
3333
|
-
"--skip-install",
|
|
3334
|
-
"--quiet"
|
|
3335
|
-
];
|
|
3336
|
-
}
|
|
3337
|
-
async function removeHuskySampleHook(projectDir) {
|
|
3338
|
-
const hookPath = path.join(projectDir, ".husky", "pre-commit");
|
|
3339
|
-
if (!await fs.pathExists(hookPath)) return;
|
|
3340
|
-
const content = await fs.readFile(hookPath, "utf-8");
|
|
3341
|
-
const cleaned = content.split("\n").filter((line) => !/^(?:npm|pnpm|yarn|bun|deno)(?: run)? test$/.test(line.trim())).join("\n");
|
|
3342
|
-
if (cleaned !== content) await fs.writeFile(hookPath, cleaned);
|
|
3343
|
-
}
|
|
3344
|
-
async function setupUltracite(config, gitHooks) {
|
|
3345
|
-
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
3346
|
-
const { packageManager, projectDir, frontend } = config;
|
|
3347
|
-
cliLog.info("Setting up Ultracite...");
|
|
3348
|
-
const configuredOptions = config.addonOptions?.ultracite;
|
|
3349
|
-
let linter = configuredOptions?.linter;
|
|
3350
|
-
let editors = configuredOptions?.editors;
|
|
3351
|
-
let agents = configuredOptions?.agents;
|
|
3352
|
-
let hooks = configuredOptions?.hooks;
|
|
3353
|
-
if (!linter || !editors || !agents || !hooks) if (isSilent()) {
|
|
3354
|
-
linter = linter ?? DEFAULT_LINTER;
|
|
3355
|
-
editors = editors ?? [...DEFAULT_EDITORS];
|
|
3356
|
-
agents = agents ?? [...DEFAULT_AGENTS];
|
|
3357
|
-
hooks = hooks ?? [...DEFAULT_HOOKS];
|
|
3358
|
-
} else {
|
|
3359
|
-
const results = await navigableGroup({
|
|
3360
|
-
linter: async () => {
|
|
3361
|
-
if (linter !== void 0) return linter;
|
|
3362
|
-
return navigableSelect({
|
|
3363
|
-
message: "Which linter do you want to use?",
|
|
3364
|
-
options: Object.entries(LINTERS).map(([key, linterOption]) => ({
|
|
3365
|
-
value: key,
|
|
3366
|
-
label: linterOption.label
|
|
3367
|
-
})),
|
|
3368
|
-
initialValue: linter ?? DEFAULT_LINTER
|
|
3369
|
-
});
|
|
3370
|
-
},
|
|
3371
|
-
editors: async () => {
|
|
3372
|
-
if (editors !== void 0) return editors;
|
|
3373
|
-
return navigableMultiselect({
|
|
3374
|
-
message: "Which editors do you want to configure (recommended)?",
|
|
3375
|
-
required: false,
|
|
3376
|
-
options: [{
|
|
3377
|
-
value: "vscode",
|
|
3378
|
-
label: "VSCode / Cursor / Windsurf"
|
|
3379
|
-
}, {
|
|
3380
|
-
value: "zed",
|
|
3381
|
-
label: "Zed"
|
|
3382
|
-
}]
|
|
3383
|
-
});
|
|
3384
|
-
},
|
|
3385
|
-
agents: async () => {
|
|
3386
|
-
if (agents !== void 0) return agents;
|
|
3387
|
-
return navigableMultiselect({
|
|
3388
|
-
message: "Which agent files do you want to add (optional)?",
|
|
3389
|
-
required: false,
|
|
3390
|
-
options: Object.entries(AGENTS).map(([key, agent]) => ({
|
|
3391
|
-
value: key,
|
|
3392
|
-
label: agent.label
|
|
3393
|
-
}))
|
|
3394
|
-
});
|
|
3395
|
-
},
|
|
3396
|
-
hooks: async () => {
|
|
3397
|
-
if (hooks !== void 0) return hooks;
|
|
3398
|
-
return navigableMultiselect({
|
|
3399
|
-
message: "Which agent hooks do you want to enable (optional)?",
|
|
3400
|
-
required: false,
|
|
3401
|
-
options: Object.entries(HOOKS).map(([key, hook]) => ({
|
|
3402
|
-
value: key,
|
|
3403
|
-
label: hook.label
|
|
3404
|
-
}))
|
|
3405
|
-
});
|
|
3406
|
-
}
|
|
3407
|
-
});
|
|
3408
|
-
if (results.linter === void 0 || results.editors === void 0 || results.agents === void 0 || results.hooks === void 0) return userCancelled("Operation cancelled");
|
|
3409
|
-
linter = results.linter;
|
|
3410
|
-
editors = results.editors;
|
|
3411
|
-
agents = results.agents;
|
|
3412
|
-
hooks = results.hooks;
|
|
3413
|
-
}
|
|
3414
|
-
const frameworks = getFrameworksFromFrontend(frontend);
|
|
3415
|
-
const args = buildUltraciteInitArgs({
|
|
3416
|
-
packageManager,
|
|
3417
|
-
linter,
|
|
3418
|
-
frameworks,
|
|
3419
|
-
editors,
|
|
3420
|
-
agents,
|
|
3421
|
-
hooks,
|
|
3422
|
-
gitHooks
|
|
3423
|
-
});
|
|
3424
|
-
const s = createSpinner();
|
|
3425
|
-
s.start("Running Ultracite init command...");
|
|
3426
|
-
const initResult = await Result.tryPromise({
|
|
3427
|
-
try: async () => {
|
|
3428
|
-
await $({
|
|
3429
|
-
cwd: projectDir,
|
|
3430
|
-
env: { CI: "true" }
|
|
3431
|
-
})`${args}`;
|
|
3432
|
-
},
|
|
3433
|
-
catch: (e) => {
|
|
3434
|
-
s.stop(pc.red("Failed to run Ultracite init command"));
|
|
3435
|
-
return new AddonSetupError({
|
|
3436
|
-
addon: "ultracite",
|
|
3437
|
-
message: `Failed to set up Ultracite: ${e instanceof Error ? e.message : String(e)}`,
|
|
3438
|
-
cause: e
|
|
3439
|
-
});
|
|
3440
|
-
}
|
|
3441
|
-
});
|
|
3442
|
-
if (initResult.isErr()) {
|
|
3443
|
-
cliLog.error(pc.red("Failed to set up Ultracite"));
|
|
3444
|
-
return initResult;
|
|
3445
|
-
}
|
|
3446
|
-
if (gitHooks.includes("husky")) await Result.tryPromise({
|
|
3447
|
-
try: () => removeHuskySampleHook(projectDir),
|
|
3448
|
-
catch: (e) => e
|
|
3449
|
-
});
|
|
3450
|
-
s.stop("Ultracite setup successfully!");
|
|
3451
|
-
return Result.ok(void 0);
|
|
3452
|
-
}
|
|
3453
|
-
//#endregion
|
|
3454
|
-
//#region src/helpers/addons/wxt-setup.ts
|
|
3455
|
-
const TEMPLATES = {
|
|
3456
|
-
vanilla: {
|
|
3457
|
-
label: "Vanilla",
|
|
3458
|
-
hint: "Vanilla JavaScript template"
|
|
3459
|
-
},
|
|
3460
|
-
vue: {
|
|
3461
|
-
label: "Vue",
|
|
3462
|
-
hint: "Vue.js template"
|
|
3463
|
-
},
|
|
3464
|
-
react: {
|
|
3465
|
-
label: "React",
|
|
3466
|
-
hint: "React template"
|
|
3467
|
-
},
|
|
3468
|
-
solid: {
|
|
3469
|
-
label: "Solid",
|
|
3470
|
-
hint: "SolidJS template"
|
|
3471
|
-
},
|
|
3472
|
-
svelte: {
|
|
3473
|
-
label: "Svelte",
|
|
3474
|
-
hint: "Svelte template"
|
|
3475
|
-
}
|
|
3476
|
-
};
|
|
3477
|
-
const DEFAULT_TEMPLATE = "react";
|
|
3478
|
-
const DEFAULT_DEV_PORT = 5555;
|
|
3479
|
-
async function setupWxt(config) {
|
|
3480
|
-
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
3481
|
-
const { packageManager, projectDir } = config;
|
|
3482
|
-
cliLog.info("Setting up WXT...");
|
|
3483
|
-
const configuredOptions = config.addonOptions?.wxt;
|
|
3484
|
-
let template = configuredOptions?.template;
|
|
3485
|
-
if (!template) if (isSilent()) template = DEFAULT_TEMPLATE;
|
|
3486
|
-
else {
|
|
3487
|
-
setIsFirstPrompt(true);
|
|
3488
|
-
const selectedTemplate = await navigableSelect({
|
|
3489
|
-
message: "Choose a template",
|
|
3490
|
-
options: Object.entries(TEMPLATES).map(([key, templateOption]) => ({
|
|
3491
|
-
value: key,
|
|
3492
|
-
label: templateOption.label,
|
|
3493
|
-
hint: templateOption.hint
|
|
3494
|
-
})),
|
|
3495
|
-
initialValue: DEFAULT_TEMPLATE
|
|
3496
|
-
});
|
|
3497
|
-
if (isCancel$1(selectedTemplate)) return userCancelled("Operation cancelled");
|
|
3498
|
-
template = selectedTemplate;
|
|
3499
|
-
}
|
|
3500
|
-
const devPort = configuredOptions?.devPort ?? DEFAULT_DEV_PORT;
|
|
3501
|
-
const args = getPackageExecutionArgs(packageManager, `wxt@latest init extension --template ${template} --pm ${packageManager}`);
|
|
3502
|
-
const appsDir = path.join(projectDir, "apps");
|
|
3503
|
-
const ensureDirResult = await Result.tryPromise({
|
|
3504
|
-
try: () => fs.ensureDir(appsDir),
|
|
3505
|
-
catch: (e) => new AddonSetupError({
|
|
3506
|
-
addon: "wxt",
|
|
3507
|
-
message: `Failed to create directory: ${e instanceof Error ? e.message : String(e)}`,
|
|
3508
|
-
cause: e
|
|
3509
|
-
})
|
|
3510
|
-
});
|
|
3511
|
-
if (ensureDirResult.isErr()) return ensureDirResult;
|
|
3512
|
-
const s = createSpinner();
|
|
3513
|
-
s.start("Running WXT init command...");
|
|
3514
|
-
const initResult = await Result.tryPromise({
|
|
3515
|
-
try: async () => {
|
|
3516
|
-
await $({
|
|
3517
|
-
cwd: appsDir,
|
|
3518
|
-
env: { CI: "true" }
|
|
3519
|
-
})`${args}`;
|
|
3520
|
-
},
|
|
3521
|
-
catch: (e) => {
|
|
3522
|
-
s.stop(pc.red("Failed to run WXT init command"));
|
|
3523
|
-
return new AddonSetupError({
|
|
3524
|
-
addon: "wxt",
|
|
3525
|
-
message: `Failed to set up WXT: ${e instanceof Error ? e.message : String(e)}`,
|
|
3526
|
-
cause: e
|
|
3527
|
-
});
|
|
3528
|
-
}
|
|
3529
|
-
});
|
|
3530
|
-
if (initResult.isErr()) {
|
|
3531
|
-
cliLog.error(pc.red("Failed to set up WXT"));
|
|
3532
|
-
return initResult;
|
|
3533
|
-
}
|
|
3534
|
-
const extensionDir = path.join(projectDir, "apps", "extension");
|
|
3535
|
-
const packageJsonPath = path.join(extensionDir, "package.json");
|
|
3536
|
-
if ((await Result.tryPromise({
|
|
3537
|
-
try: async () => {
|
|
3538
|
-
if (await fs.pathExists(packageJsonPath)) {
|
|
3539
|
-
const packageJson = await fs.readJson(packageJsonPath);
|
|
3540
|
-
packageJson.name = "extension";
|
|
3541
|
-
if (packageJson.scripts?.dev) packageJson.scripts.dev = `${packageJson.scripts.dev} --port ${devPort}`;
|
|
3542
|
-
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
3543
|
-
}
|
|
3544
|
-
},
|
|
3545
|
-
catch: (e) => new AddonSetupError({
|
|
3546
|
-
addon: "wxt",
|
|
3547
|
-
message: `Failed to update package.json: ${e instanceof Error ? e.message : String(e)}`,
|
|
3548
|
-
cause: e
|
|
3549
|
-
})
|
|
3550
|
-
})).isErr()) cliLog.warn(pc.yellow("WXT setup completed but failed to update package.json"));
|
|
3551
|
-
s.stop("WXT setup complete!");
|
|
3552
|
-
return Result.ok(void 0);
|
|
3553
|
-
}
|
|
3554
|
-
//#endregion
|
|
3555
2464
|
//#region src/helpers/addons/addons-setup.ts
|
|
3556
2465
|
async function runSetup(setupFn) {
|
|
3557
2466
|
const result = await setupFn();
|
|
@@ -3572,41 +2481,27 @@ async function runAddonStep(addon, step) {
|
|
|
3572
2481
|
if (result.isErr()) cliConsola.error(pc.red(result.error.message));
|
|
3573
2482
|
}
|
|
3574
2483
|
async function setupAddons(config) {
|
|
3575
|
-
const { addons,
|
|
2484
|
+
const { addons, frontend, projectDir } = config;
|
|
3576
2485
|
const hasWebFrontend = frontend.some((value) => types_exports.desktopWebFrontends.includes(value));
|
|
3577
2486
|
if (addons.includes("tauri") && hasWebFrontend) await runSetup(() => setupTauri(config));
|
|
3578
|
-
const
|
|
3579
|
-
const
|
|
3580
|
-
const hasBiome = !hasUltracite && !hasOxlint && addons.includes("biome");
|
|
2487
|
+
const hasOxlint = addons.includes("oxlint");
|
|
2488
|
+
const hasBiome = !hasOxlint && addons.includes("biome");
|
|
3581
2489
|
const hasHusky = addons.includes("husky");
|
|
3582
2490
|
const hasLefthook = addons.includes("lefthook");
|
|
3583
2491
|
const hasVitePlus = addons.includes("vite-plus");
|
|
3584
|
-
if (
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
if (
|
|
3591
|
-
|
|
3592
|
-
if (
|
|
3593
|
-
let linter;
|
|
3594
|
-
if (hasOxlint) linter = "oxlint";
|
|
3595
|
-
else if (hasBiome) linter = "biome";
|
|
3596
|
-
else if (hasVitePlus) linter = "vite-plus";
|
|
3597
|
-
if (hasHusky) await runAddonStep("husky", () => setupHusky(projectDir, linter));
|
|
3598
|
-
if (hasLefthook) await runAddonStep("lefthook", () => setupLefthook(projectDir));
|
|
3599
|
-
}
|
|
2492
|
+
if (hasBiome) await runAddonStep("biome", () => setupBiome(projectDir));
|
|
2493
|
+
else if (hasOxlint) await runSetup(() => setupOxlint(projectDir, config.packageManager));
|
|
2494
|
+
if (hasHusky || hasLefthook) {
|
|
2495
|
+
let linter;
|
|
2496
|
+
if (hasOxlint) linter = "oxlint";
|
|
2497
|
+
else if (hasBiome) linter = "biome";
|
|
2498
|
+
else if (hasVitePlus) linter = "vite-plus";
|
|
2499
|
+
if (hasHusky) await runAddonStep("husky", () => setupHusky(projectDir, linter));
|
|
2500
|
+
if (hasLefthook) await runAddonStep("lefthook", () => setupLefthook(projectDir));
|
|
3600
2501
|
}
|
|
3601
|
-
if (addons.includes("starlight")) await runSetup(() => setupStarlight(config));
|
|
3602
|
-
if (addons.includes("fumadocs")) await runSetup(() => setupFumadocs(config));
|
|
3603
2502
|
if (addons.includes("opentui")) await runSetup(() => setupTui(config));
|
|
3604
|
-
if (addons.includes("wxt")) await runSetup(() => setupWxt(config));
|
|
3605
2503
|
if (addons.includes("skills")) await runSetup(() => setupSkills(config));
|
|
3606
2504
|
if (addons.includes("mcp")) await runSetup(() => setupMcp(config));
|
|
3607
|
-
if (addons.includes("evlog")) await runSetup(() => setupEvlog(config));
|
|
3608
|
-
if (testing.includes("vitest")) await runAddonStep("vitest", () => setupVitest(projectDir));
|
|
3609
|
-
if (testing.includes("playwright")) await runAddonStep("playwright", () => setupPlaywright(projectDir));
|
|
3610
2505
|
}
|
|
3611
2506
|
async function setupBiome(projectDir) {
|
|
3612
2507
|
await addPackageDependency({
|
|
@@ -3679,6 +2574,20 @@ async function setupTesting(projectDir, testing) {
|
|
|
3679
2574
|
await updateScripts(projectDir, { "test:e2e": "playwright test" });
|
|
3680
2575
|
}
|
|
3681
2576
|
}
|
|
2577
|
+
/** Best-effort browser install after deps; never fails project creation. */
|
|
2578
|
+
async function installPlaywrightBrowsers(projectDir, packageManager) {
|
|
2579
|
+
if (shouldSkipExternalCommands()) return;
|
|
2580
|
+
const s = createSpinner();
|
|
2581
|
+
s.start("Installing Playwright browsers...");
|
|
2582
|
+
const args = getPackageExecutionArgs(packageManager, "playwright install");
|
|
2583
|
+
try {
|
|
2584
|
+
await $({ cwd: projectDir })`${args}`;
|
|
2585
|
+
s.stop(pc.green("Playwright browsers installed"));
|
|
2586
|
+
} catch (e) {
|
|
2587
|
+
s.stop(pc.yellow("Playwright browser install skipped (run playwright install later)"));
|
|
2588
|
+
cliLog.info(pc.dim(`Playwright browsers were not installed automatically: ${e instanceof Error ? e.message : String(e)}`));
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
3682
2591
|
//#endregion
|
|
3683
2592
|
//#region src/utils/config-processing.ts
|
|
3684
2593
|
function processArrayOption(options) {
|
|
@@ -3700,7 +2609,7 @@ function processFlags(options, projectName) {
|
|
|
3700
2609
|
if (options.database) config.database = options.database;
|
|
3701
2610
|
if (options.orm) config.orm = options.orm;
|
|
3702
2611
|
if (options.auth !== void 0) config.auth = options.auth;
|
|
3703
|
-
if (options.payments !== void 0) config.payments = options.payments;
|
|
2612
|
+
if (options.payments !== void 0) config.payments = normalizePayments(options.payments);
|
|
3704
2613
|
if (options.observability !== void 0) config.observability = normalizeObservability(options.observability);
|
|
3705
2614
|
if (options.disableObservability) config.observability = [];
|
|
3706
2615
|
if (options.communication !== void 0) config.communication = options.communication;
|
|
@@ -3761,6 +2670,7 @@ async function detectProjectConfig(projectDir) {
|
|
|
3761
2670
|
frontend: kubojsConfig.frontend,
|
|
3762
2671
|
addons: kubojsConfig.addons,
|
|
3763
2672
|
examples: kubojsConfig.examples,
|
|
2673
|
+
testing: kubojsConfig.testing,
|
|
3764
2674
|
auth: kubojsConfig.auth,
|
|
3765
2675
|
payments: kubojsConfig.payments,
|
|
3766
2676
|
observability: normalizeObservability(kubojsConfig.observability),
|
|
@@ -3808,9 +2718,8 @@ const ADD_PACKAGE_JSON_PATHS = [
|
|
|
3808
2718
|
"apps/web/package.json",
|
|
3809
2719
|
"apps/native/package.json",
|
|
3810
2720
|
"apps/desktop/package.json",
|
|
3811
|
-
"apps/fumadocs/package.json",
|
|
3812
2721
|
"apps/docs/package.json",
|
|
3813
|
-
"
|
|
2722
|
+
"apps/api/package.json",
|
|
3814
2723
|
"packages/db/package.json",
|
|
3815
2724
|
"packages/auth/package.json",
|
|
3816
2725
|
"packages/backend/package.json",
|
|
@@ -3820,17 +2729,21 @@ const ADD_PACKAGE_JSON_PATHS = [
|
|
|
3820
2729
|
"packages/ui/package.json"
|
|
3821
2730
|
];
|
|
3822
2731
|
const ADD_TEXT_FILE_PATHS = ["apps/web/vite.config.ts", "lefthook.yml"];
|
|
2732
|
+
const ADD_ENV_FILE_PATHS = [
|
|
2733
|
+
".env",
|
|
2734
|
+
"apps/web/.env",
|
|
2735
|
+
"apps/server/.env",
|
|
2736
|
+
"apps/native/.env",
|
|
2737
|
+
"packages/backend/.env.local",
|
|
2738
|
+
"packages/infra/.env"
|
|
2739
|
+
];
|
|
3823
2740
|
const HOOK_ADDONS = ["husky", "lefthook"];
|
|
3824
2741
|
const HOOK_LINTER_ADDONS = [
|
|
3825
2742
|
"biome",
|
|
3826
2743
|
"oxlint",
|
|
3827
2744
|
"vite-plus"
|
|
3828
2745
|
];
|
|
3829
|
-
const TASK_RUNNER_ADDONS = [
|
|
3830
|
-
"turborepo",
|
|
3831
|
-
"nx",
|
|
3832
|
-
"vite-plus"
|
|
3833
|
-
];
|
|
2746
|
+
const TASK_RUNNER_ADDONS = ["turborepo", "vite-plus"];
|
|
3834
2747
|
function mergeAddonOptions(existingAddonOptions, nextAddonOptions) {
|
|
3835
2748
|
if (!existingAddonOptions && !nextAddonOptions) return;
|
|
3836
2749
|
const mergedAddonOptions = { ...existingAddonOptions };
|
|
@@ -3868,11 +2781,8 @@ async function cleanupRemovedLinters(projectDir, removedAddons) {
|
|
|
3868
2781
|
const packageJson = await fs.readJson(packageJsonPath);
|
|
3869
2782
|
packageJson.devDependencies = packageJson.devDependencies ?? {};
|
|
3870
2783
|
for (const removed of removedLinters) {
|
|
3871
|
-
if (removed === "biome"
|
|
3872
|
-
|
|
3873
|
-
delete packageJson.devDependencies.ultracite;
|
|
3874
|
-
}
|
|
3875
|
-
if (removed === "oxlint" || removed === "ultracite") {
|
|
2784
|
+
if (removed === "biome") delete packageJson.devDependencies["@biomejs/biome"];
|
|
2785
|
+
if (removed === "oxlint") {
|
|
3876
2786
|
delete packageJson.devDependencies.oxlint;
|
|
3877
2787
|
delete packageJson.devDependencies.oxfmt;
|
|
3878
2788
|
}
|
|
@@ -3880,7 +2790,6 @@ async function cleanupRemovedLinters(projectDir, removedAddons) {
|
|
|
3880
2790
|
if (typeof packageJson.scripts?.check === "string" && removedLinters.some((linter) => {
|
|
3881
2791
|
if (linter === "biome") return packageJson.scripts.check.includes("biome");
|
|
3882
2792
|
if (linter === "oxlint") return packageJson.scripts.check.includes("oxlint");
|
|
3883
|
-
if (linter === "ultracite") return packageJson.scripts.check.includes("ultracite");
|
|
3884
2793
|
return false;
|
|
3885
2794
|
})) delete packageJson.scripts.check;
|
|
3886
2795
|
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
@@ -3936,7 +2845,7 @@ async function addHandlerInternal(input) {
|
|
|
3936
2845
|
}));
|
|
3937
2846
|
if (!isSilent()) {
|
|
3938
2847
|
renderTitle();
|
|
3939
|
-
intro(
|
|
2848
|
+
intro(cliColors.signal("Add addons to your kubojs project"));
|
|
3940
2849
|
}
|
|
3941
2850
|
const existingConfig = await detectProjectConfig(projectDir);
|
|
3942
2851
|
if (!existingConfig) return Result.err(new CLIError({ message: `No kubojs project found in ${projectDir}. Make sure kubojs.jsonrc exists.` }));
|
|
@@ -3969,7 +2878,7 @@ async function addHandlerInternal(input) {
|
|
|
3969
2878
|
if (selectedAddons.length === 0) {
|
|
3970
2879
|
if (!isSilent()) {
|
|
3971
2880
|
log.info(pc.dim("No addons selected."));
|
|
3972
|
-
outro(
|
|
2881
|
+
outro(cliColors.signal("Nothing to add."));
|
|
3973
2882
|
}
|
|
3974
2883
|
return Result.ok({
|
|
3975
2884
|
success: true,
|
|
@@ -3980,10 +2889,11 @@ async function addHandlerInternal(input) {
|
|
|
3980
2889
|
addonsToAdd = selectedAddons;
|
|
3981
2890
|
}
|
|
3982
2891
|
const { updatedAddons, removedAddons } = mergeAddonsExclusive(existingConfig.addons, addonsToAdd);
|
|
2892
|
+
const updatedTesting = existingConfig.testing ?? [];
|
|
3983
2893
|
const addonsValidationResult = validateAddonsAgainstConfig(updatedAddons, existingConfig);
|
|
3984
2894
|
if (addonsValidationResult.isErr()) return Result.err(new CLIError({ message: addonsValidationResult.error.message }));
|
|
3985
2895
|
if (!isSilent()) {
|
|
3986
|
-
log.info(
|
|
2896
|
+
log.info(cliColors.bright(`Adding addons: ${addonsToAdd.join(", ")}`));
|
|
3987
2897
|
if (removedAddons.length > 0) log.info(pc.dim(`Replacing exclusive addons: ${removedAddons.join(", ")}`));
|
|
3988
2898
|
}
|
|
3989
2899
|
const mergedAddonOptions = mergeAddonOptions(existingConfig.addonOptions, input.addonOptions);
|
|
@@ -4003,6 +2913,7 @@ async function addHandlerInternal(input) {
|
|
|
4003
2913
|
payments: existingConfig.payments,
|
|
4004
2914
|
observability: existingConfig.observability,
|
|
4005
2915
|
communication: existingConfig.communication ?? "none",
|
|
2916
|
+
testing: updatedTesting,
|
|
4006
2917
|
git: false,
|
|
4007
2918
|
packageManager: input.packageManager || existingConfig.packageManager,
|
|
4008
2919
|
install: input.install ?? false,
|
|
@@ -4031,10 +2942,17 @@ async function addHandlerInternal(input) {
|
|
|
4031
2942
|
vfs.writeFile(filePath, content);
|
|
4032
2943
|
}
|
|
4033
2944
|
}
|
|
2945
|
+
for (const filePath of ADD_ENV_FILE_PATHS) {
|
|
2946
|
+
const fullPath = path.join(projectDir, filePath);
|
|
2947
|
+
if (await fs.pathExists(fullPath)) {
|
|
2948
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
2949
|
+
vfs.writeFile(filePath, content);
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
4034
2952
|
await processAddonTemplates(vfs, EMBEDDED_TEMPLATES, config);
|
|
4035
2953
|
processAddonsDeps(vfs, config);
|
|
2954
|
+
processEnvVariables(vfs, updatedConfig);
|
|
4036
2955
|
if (addonsToAdd.includes("turborepo")) processTurboConfig(vfs, updatedConfig);
|
|
4037
|
-
if (addonsToAdd.includes("nx")) processNxConfig(vfs, updatedConfig);
|
|
4038
2956
|
if (addonsToAdd.includes("vite-plus")) processVitePlusConfig(vfs, updatedConfig);
|
|
4039
2957
|
if (updatedAddons.some((addon) => TASK_RUNNER_ADDONS.includes(addon))) processPackageConfigs(vfs, updatedConfig);
|
|
4040
2958
|
if (updatedAddons.includes("vite-plus")) updateViteConfigImportsForVitePlus(vfs);
|
|
@@ -4049,7 +2967,7 @@ async function addHandlerInternal(input) {
|
|
|
4049
2967
|
if (!isSilent()) {
|
|
4050
2968
|
log.success(pc.green("Dry run validation passed. No addon files were written."));
|
|
4051
2969
|
log.info(pc.dim(`Planned addon files: ${vfs.getFileCount()}`));
|
|
4052
|
-
outro(
|
|
2970
|
+
outro(cliColors.signal("Dry run complete."));
|
|
4053
2971
|
}
|
|
4054
2972
|
return Result.ok({
|
|
4055
2973
|
success: true,
|
|
@@ -4087,7 +3005,8 @@ async function addHandlerInternal(input) {
|
|
|
4087
3005
|
if (testingSetupResult.isErr()) return Result.err(testingSetupResult.error);
|
|
4088
3006
|
await updateKubojsConfig(projectDir, {
|
|
4089
3007
|
addons: updatedAddons,
|
|
4090
|
-
addonOptions: updatedConfig.addonOptions
|
|
3008
|
+
addonOptions: updatedConfig.addonOptions,
|
|
3009
|
+
testing: updatedTesting
|
|
4091
3010
|
});
|
|
4092
3011
|
if (input.install) {
|
|
4093
3012
|
if (!isSilent()) log.info(pc.dim("Installing dependencies..."));
|
|
@@ -4099,7 +3018,7 @@ async function addHandlerInternal(input) {
|
|
|
4099
3018
|
if (!isSilent()) {
|
|
4100
3019
|
log.success(pc.green(`Successfully added: ${addonsToAdd.join(", ")}`));
|
|
4101
3020
|
if (!input.install) log.info(pc.yellow(`Run '${config.packageManager === "npm" ? "npm install" : `${config.packageManager} install`}' to install new dependencies.`));
|
|
4102
|
-
outro(
|
|
3021
|
+
outro(cliColors.signal("Addons added successfully!"));
|
|
4103
3022
|
}
|
|
4104
3023
|
return Result.ok({
|
|
4105
3024
|
success: true,
|
|
@@ -4112,7 +3031,7 @@ async function addHandlerInternal(input) {
|
|
|
4112
3031
|
//#region src/prompts/api.ts
|
|
4113
3032
|
async function getApiChoice(Api, frontend, backend, previousValue) {
|
|
4114
3033
|
if (backend === "convex" || backend === "none") return "none";
|
|
4115
|
-
const allowed = allowedApisForFrontends(frontend ?? []);
|
|
3034
|
+
const allowed = allowedApisForFrontends(frontend ?? [], backend);
|
|
4116
3035
|
if (Api) {
|
|
4117
3036
|
const compat = validateApiFrontendCompatibility(Api, frontend ?? []);
|
|
4118
3037
|
if (compat.isErr()) throw compat.error;
|
|
@@ -4126,6 +3045,10 @@ async function getApiChoice(Api, frontend, backend, previousValue) {
|
|
|
4126
3045
|
value: "orpc",
|
|
4127
3046
|
label: "oRPC",
|
|
4128
3047
|
hint: "End-to-end type-safe APIs that adhere to OpenAPI standards"
|
|
3048
|
+
} : a === "orval" ? {
|
|
3049
|
+
value: "orval",
|
|
3050
|
+
label: "Orval",
|
|
3051
|
+
hint: "OpenAPI REST APIs with generated Hono handlers and Fetch clients"
|
|
4129
3052
|
} : {
|
|
4130
3053
|
value: "none",
|
|
4131
3054
|
label: "None",
|
|
@@ -4136,7 +3059,7 @@ async function getApiChoice(Api, frontend, backend, previousValue) {
|
|
|
4136
3059
|
options: apiOptions,
|
|
4137
3060
|
initialValue: preferValidInitial(apiOptions, previousValue, apiOptions[0].value)
|
|
4138
3061
|
});
|
|
4139
|
-
if (isCancel$
|
|
3062
|
+
if (isCancel$2(apiType)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4140
3063
|
return apiType;
|
|
4141
3064
|
}
|
|
4142
3065
|
//#endregion
|
|
@@ -4188,7 +3111,7 @@ async function getAuthChoice(auth, backend, frontend = [], previousValue) {
|
|
|
4188
3111
|
options,
|
|
4189
3112
|
initialValue: preferValidInitial(options, previousValue, options.some((option) => option.value === DEFAULT_CONFIG.auth) ? DEFAULT_CONFIG.auth : "none")
|
|
4190
3113
|
});
|
|
4191
|
-
if (isCancel$
|
|
3114
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4192
3115
|
return response;
|
|
4193
3116
|
}
|
|
4194
3117
|
//#endregion
|
|
@@ -4242,7 +3165,7 @@ async function getBackendFrameworkChoice(backendFramework, frontends, previousVa
|
|
|
4242
3165
|
options: backendOptions,
|
|
4243
3166
|
initialValue: preferValidInitial(backendOptions, previousValue, hasFullstackFrontend ? "self" : DEFAULT_CONFIG.backend)
|
|
4244
3167
|
});
|
|
4245
|
-
if (isCancel$
|
|
3168
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4246
3169
|
return response;
|
|
4247
3170
|
}
|
|
4248
3171
|
//#endregion
|
|
@@ -4265,6 +3188,11 @@ async function getCommunicationChoice(communication, backend, previousValue) {
|
|
|
4265
3188
|
value: "notifique",
|
|
4266
3189
|
label: "Notifique",
|
|
4267
3190
|
hint: "Omnichannel BR messaging API"
|
|
3191
|
+
},
|
|
3192
|
+
{
|
|
3193
|
+
value: "arara",
|
|
3194
|
+
label: "AraraHQ",
|
|
3195
|
+
hint: "WhatsApp messaging via the official Node SDK"
|
|
4268
3196
|
}
|
|
4269
3197
|
];
|
|
4270
3198
|
const response = await navigableSelect({
|
|
@@ -4272,7 +3200,7 @@ async function getCommunicationChoice(communication, backend, previousValue) {
|
|
|
4272
3200
|
options,
|
|
4273
3201
|
initialValue: preferValidInitial(options, previousValue, DEFAULT_CONFIG.communication)
|
|
4274
3202
|
});
|
|
4275
|
-
if (isCancel$
|
|
3203
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4276
3204
|
return response;
|
|
4277
3205
|
}
|
|
4278
3206
|
//#endregion
|
|
@@ -4312,7 +3240,7 @@ async function getDatabaseChoice(database, backend, runtime, previousValue) {
|
|
|
4312
3240
|
options: databaseOptions,
|
|
4313
3241
|
initialValue: preferValidInitial(databaseOptions, previousValue, DEFAULT_CONFIG.database)
|
|
4314
3242
|
});
|
|
4315
|
-
if (isCancel$
|
|
3243
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4316
3244
|
return response;
|
|
4317
3245
|
}
|
|
4318
3246
|
//#endregion
|
|
@@ -4411,7 +3339,7 @@ async function getDBSetupChoice(databaseType, dbSetup, _orm, backend, runtime, p
|
|
|
4411
3339
|
options,
|
|
4412
3340
|
initialValue: preferValidInitial(options, previousValue, "none")
|
|
4413
3341
|
});
|
|
4414
|
-
if (isCancel$
|
|
3342
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4415
3343
|
return response;
|
|
4416
3344
|
}
|
|
4417
3345
|
//#endregion
|
|
@@ -4438,7 +3366,7 @@ async function getExamplesChoice(examples, database, frontends, backend, api, pr
|
|
|
4438
3366
|
required: false,
|
|
4439
3367
|
initialValues: (previousValue ?? DEFAULT_CONFIG.examples)?.filter((ex) => options.some((o) => o.value === ex))
|
|
4440
3368
|
});
|
|
4441
|
-
if (isCancel$
|
|
3369
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4442
3370
|
return response;
|
|
4443
3371
|
}
|
|
4444
3372
|
//#endregion
|
|
@@ -4474,7 +3402,7 @@ async function getFrontendChoice(frontendOptions, backend, auth, previousValue)
|
|
|
4474
3402
|
initialValues: previousValue ? [...previousWeb ? ["web"] : [], ...previousNative ? ["native"] : []] : ["web"]
|
|
4475
3403
|
});
|
|
4476
3404
|
if (isGoBack(frontendTypes)) return GO_BACK_SYMBOL;
|
|
4477
|
-
if (isCancel$
|
|
3405
|
+
if (isCancel$2(frontendTypes)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4478
3406
|
setIsFirstPrompt(false);
|
|
4479
3407
|
const result = [];
|
|
4480
3408
|
let shouldRestart = false;
|
|
@@ -4527,7 +3455,7 @@ async function getFrontendChoice(frontendOptions, backend, auth, previousValue)
|
|
|
4527
3455
|
initialValue: preferValidInitial(webOptions, previousWeb, DEFAULT_CONFIG.frontend[0])
|
|
4528
3456
|
});
|
|
4529
3457
|
if (isGoBack(webFramework)) shouldRestart = true;
|
|
4530
|
-
else if (isCancel$
|
|
3458
|
+
else if (isCancel$2(webFramework)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4531
3459
|
else result.push(webFramework);
|
|
4532
3460
|
}
|
|
4533
3461
|
if (shouldRestart) {
|
|
@@ -4561,7 +3489,7 @@ async function getFrontendChoice(frontendOptions, backend, auth, previousValue)
|
|
|
4561
3489
|
setIsFirstPrompt(wasFirstPrompt);
|
|
4562
3490
|
continue;
|
|
4563
3491
|
}
|
|
4564
|
-
else if (isCancel$
|
|
3492
|
+
else if (isCancel$2(nativeFramework)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4565
3493
|
else result.push(nativeFramework);
|
|
4566
3494
|
}
|
|
4567
3495
|
if (shouldRestart) {
|
|
@@ -4579,7 +3507,7 @@ async function getGitChoice(git, previousValue) {
|
|
|
4579
3507
|
message: "Initialize git repository?",
|
|
4580
3508
|
initialValue: previousValue ?? DEFAULT_CONFIG.git
|
|
4581
3509
|
});
|
|
4582
|
-
if (isCancel$
|
|
3510
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4583
3511
|
return response;
|
|
4584
3512
|
}
|
|
4585
3513
|
//#endregion
|
|
@@ -4590,7 +3518,7 @@ async function getinstallChoice(install, previousValue) {
|
|
|
4590
3518
|
message: "Install dependencies?",
|
|
4591
3519
|
initialValue: previousValue ?? DEFAULT_CONFIG.install
|
|
4592
3520
|
});
|
|
4593
|
-
if (isCancel$
|
|
3521
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4594
3522
|
return response;
|
|
4595
3523
|
}
|
|
4596
3524
|
//#endregion
|
|
@@ -4612,7 +3540,7 @@ async function getObservabilityChoice(observability, previousValue) {
|
|
|
4612
3540
|
required: false,
|
|
4613
3541
|
initialValues: previousValue ?? DEFAULT_CONFIG.observability
|
|
4614
3542
|
});
|
|
4615
|
-
if (isCancel$
|
|
3543
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4616
3544
|
return response;
|
|
4617
3545
|
}
|
|
4618
3546
|
//#endregion
|
|
@@ -4730,7 +3658,7 @@ function validateBackendNoneConstraints(config, providedFlags) {
|
|
|
4730
3658
|
if (has("orm") && config.orm !== "none") return validationErr("Backend 'none' requires '--orm none'. Please remove the --orm flag or set it to 'none'.");
|
|
4731
3659
|
if (has("api") && config.api !== "none") return validationErr("Backend 'none' requires '--api none'. Please remove the --api flag or set it to 'none'.");
|
|
4732
3660
|
if (has("auth") && config.auth !== "none") return validationErr("Backend 'none' requires '--auth none'. Please remove the --auth flag or set it to 'none'.");
|
|
4733
|
-
if (has("payments") && config.payments
|
|
3661
|
+
if (has("payments") && (config.payments?.length ?? 0) > 0) return validationErr("Backend 'none' requires '--payments none'. Please remove the --payments flag or set it to 'none'.");
|
|
4734
3662
|
if (has("communication") && config.communication !== "none") return validationErr("Backend 'none' requires '--communication none'. Please remove the --communication flag or set it to 'none'.");
|
|
4735
3663
|
if (has("dbSetup") && config.dbSetup !== "none") return validationErr("Backend 'none' requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.");
|
|
4736
3664
|
if (has("serverDeploy") && config.serverDeploy !== "none") return validationErr("Backend 'none' requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.");
|
|
@@ -4779,6 +3707,8 @@ function validateFrontendConstraints(config, providedFlags) {
|
|
|
4779
3707
|
return Result.ok(void 0);
|
|
4780
3708
|
}
|
|
4781
3709
|
function validateApiConstraints(config, options) {
|
|
3710
|
+
if (!isApiCompatibleWithBackend(config.api, config.backend)) return validationErr("Orval API requires the Hono backend in this version. Please use '--backend hono' or choose tRPC/oRPC.");
|
|
3711
|
+
if (config.api === "orval" && options.examples?.includes("todo")) return validationErr("The Orval API layer does not support the generated todo example yet. Please remove 'todo' from --examples or choose tRPC/oRPC.");
|
|
4782
3712
|
if (config.api === "none") {
|
|
4783
3713
|
if (options.examples?.includes("todo") && options.backend !== "convex" && options.backend !== "none") return validationErr("Cannot use '--examples todo' when '--api' is set to 'none'. The todo example requires an API layer. Please remove 'todo' from --examples or choose an API type.");
|
|
4784
3714
|
}
|
|
@@ -4811,6 +3741,7 @@ function validateFullConfig(config, providedFlags, options) {
|
|
|
4811
3741
|
yield* validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
|
|
4812
3742
|
yield* validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend ?? [], config.orm, config.database);
|
|
4813
3743
|
yield* validateCommunicationCompatibility(config.communication, config.backend);
|
|
3744
|
+
yield* validateAraraRuntimeCompatibility(config.communication, config.backend, config.runtime, config.serverDeploy);
|
|
4814
3745
|
return Result.ok(void 0);
|
|
4815
3746
|
});
|
|
4816
3747
|
}
|
|
@@ -4822,6 +3753,7 @@ function validateConfigForProgrammaticUse(config) {
|
|
|
4822
3753
|
yield* validateTestingAgainstFrontends(config.testing ?? [], config.frontend ?? []);
|
|
4823
3754
|
yield* validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend, config.orm, config.database);
|
|
4824
3755
|
yield* validateCommunicationCompatibility(config.communication, config.backend);
|
|
3756
|
+
yield* validateAraraRuntimeCompatibility(config.communication, config.backend, config.runtime, config.serverDeploy);
|
|
4825
3757
|
if (config.addons && config.addons.length > 0) yield* validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth, config.backend, config.runtime);
|
|
4826
3758
|
yield* validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
|
|
4827
3759
|
return Result.ok(void 0);
|
|
@@ -4860,7 +3792,7 @@ async function getORMChoice(orm, hasDatabase, database, backend, runtime, previo
|
|
|
4860
3792
|
options,
|
|
4861
3793
|
initialValue: preferValidInitial(options, previousValue, database === "mongodb" ? "prisma" : runtime === "workers" ? "drizzle" : DEFAULT_CONFIG.orm)
|
|
4862
3794
|
});
|
|
4863
|
-
if (isCancel$
|
|
3795
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4864
3796
|
return response;
|
|
4865
3797
|
}
|
|
4866
3798
|
//#endregion
|
|
@@ -4889,47 +3821,48 @@ async function getPackageManagerChoice(packageManager, previousValue) {
|
|
|
4889
3821
|
],
|
|
4890
3822
|
initialValue: previousValue ?? detectedPackageManager
|
|
4891
3823
|
});
|
|
4892
|
-
if (isCancel$
|
|
3824
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4893
3825
|
return response;
|
|
4894
3826
|
}
|
|
4895
3827
|
//#endregion
|
|
4896
3828
|
//#region src/prompts/payments.ts
|
|
4897
3829
|
async function getPaymentsChoice(payments, auth, backend, frontends = [], previousValue) {
|
|
4898
3830
|
if (payments !== void 0) return payments;
|
|
4899
|
-
if (backend === "none") return
|
|
3831
|
+
if (backend === "none") return [];
|
|
4900
3832
|
const options = [{
|
|
4901
|
-
value: "none",
|
|
4902
|
-
label: "None",
|
|
4903
|
-
hint: "No payments integration"
|
|
4904
|
-
}, {
|
|
4905
3833
|
value: "abacatepay",
|
|
4906
3834
|
label: "AbacatePay",
|
|
4907
3835
|
hint: "Hosted checkout with webhook reconciliation"
|
|
3836
|
+
}, {
|
|
3837
|
+
value: "stripe",
|
|
3838
|
+
label: "Stripe",
|
|
3839
|
+
hint: "Embedded Checkout with a fulfillment webhook"
|
|
4908
3840
|
}];
|
|
4909
|
-
const
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
"
|
|
4916
|
-
"
|
|
4917
|
-
"
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
3841
|
+
const getDisabledReason = (provider) => {
|
|
3842
|
+
const issue = getPaymentCompatibilityIssue({
|
|
3843
|
+
provider,
|
|
3844
|
+
backend,
|
|
3845
|
+
frontends
|
|
3846
|
+
});
|
|
3847
|
+
if (issue === "requires-web-frontend") return "Requires a web frontend";
|
|
3848
|
+
if (issue === "convex-unsupported") return "Not supported with Convex";
|
|
3849
|
+
if (issue === "native-only-unsupported") return "Not supported with native-only frontends";
|
|
3850
|
+
if (issue === "sql-database-required") return "Requires a SQL database with Prisma or Drizzle";
|
|
3851
|
+
};
|
|
3852
|
+
const response = await navigableMultiselect({
|
|
3853
|
+
message: "Select payments providers",
|
|
3854
|
+
options: options.map((option) => {
|
|
3855
|
+
const disabledReason = getDisabledReason(option.value);
|
|
3856
|
+
return {
|
|
3857
|
+
...option,
|
|
3858
|
+
hint: disabledReason ?? option.hint,
|
|
3859
|
+
disabled: disabledReason !== void 0
|
|
3860
|
+
};
|
|
3861
|
+
}),
|
|
3862
|
+
required: false,
|
|
3863
|
+
initialValues: previousValue ?? DEFAULT_CONFIG.payments
|
|
4931
3864
|
});
|
|
4932
|
-
if (isCancel$
|
|
3865
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4933
3866
|
return response;
|
|
4934
3867
|
}
|
|
4935
3868
|
//#endregion
|
|
@@ -4956,7 +3889,7 @@ async function getRuntimeChoice(runtime, backend, previousValue) {
|
|
|
4956
3889
|
options: runtimeOptions,
|
|
4957
3890
|
initialValue: preferValidInitial(runtimeOptions, previousValue, DEFAULT_CONFIG.runtime)
|
|
4958
3891
|
});
|
|
4959
|
-
if (isCancel$
|
|
3892
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4960
3893
|
return response;
|
|
4961
3894
|
}
|
|
4962
3895
|
//#endregion
|
|
@@ -5014,7 +3947,7 @@ async function getServerDeploymentChoice(deployment, runtime, backend, _webDeplo
|
|
|
5014
3947
|
options,
|
|
5015
3948
|
initialValue: preferValidInitial(options, previousValue, DEFAULT_CONFIG.serverDeploy)
|
|
5016
3949
|
});
|
|
5017
|
-
if (isCancel$
|
|
3950
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
5018
3951
|
return response;
|
|
5019
3952
|
}
|
|
5020
3953
|
//#endregion
|
|
@@ -5037,7 +3970,7 @@ async function getTestingChoice(testing, frontends, previousValue) {
|
|
|
5037
3970
|
required: false,
|
|
5038
3971
|
initialValues: (previousValue ?? DEFAULT_CONFIG.testing)?.filter((t) => options.some((o) => o.value === t))
|
|
5039
3972
|
});
|
|
5040
|
-
if (isCancel$
|
|
3973
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
5041
3974
|
return response;
|
|
5042
3975
|
}
|
|
5043
3976
|
//#endregion
|
|
@@ -5090,7 +4023,7 @@ async function getDeploymentChoice(deployment, _runtime, backend, frontend = [],
|
|
|
5090
4023
|
options,
|
|
5091
4024
|
initialValue: preferValidInitial(options, previousValue, DEFAULT_CONFIG.webDeploy)
|
|
5092
4025
|
});
|
|
5093
|
-
if (isCancel$
|
|
4026
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
5094
4027
|
return response;
|
|
5095
4028
|
}
|
|
5096
4029
|
//#endregion
|
|
@@ -5174,6 +4107,32 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
5174
4107
|
}
|
|
5175
4108
|
//#endregion
|
|
5176
4109
|
//#region src/prompts/project-name.ts
|
|
4110
|
+
const S_BAR = "│";
|
|
4111
|
+
const S_BAR_END = "└";
|
|
4112
|
+
const S_STEP_ACTIVE = "◆";
|
|
4113
|
+
function kuboText(options) {
|
|
4114
|
+
return new TextPrompt({
|
|
4115
|
+
validate: options.validate,
|
|
4116
|
+
placeholder: options.placeholder,
|
|
4117
|
+
defaultValue: options.defaultValue,
|
|
4118
|
+
initialValue: options.initialValue,
|
|
4119
|
+
render() {
|
|
4120
|
+
const title = `${cliColors.signal(S_BAR)}\n${cliColors.bright(S_STEP_ACTIVE)} ${options.message}\n`;
|
|
4121
|
+
const placeholder = options.placeholder ? pc.inverse(options.placeholder[0]) + pc.dim(options.placeholder.slice(1)) : pc.inverse(pc.hidden("_"));
|
|
4122
|
+
const input = this.userInput ? this.userInputWithCursor : placeholder;
|
|
4123
|
+
const value = this.value ?? "";
|
|
4124
|
+
switch (this.state) {
|
|
4125
|
+
case "error": {
|
|
4126
|
+
const error = this.error ? ` ${pc.yellow(this.error)}` : "";
|
|
4127
|
+
return `${title.trim()}\n${cliColors.signal(S_BAR)} ${input}\n${pc.yellow(S_BAR_END)}${error}\n`;
|
|
4128
|
+
}
|
|
4129
|
+
case "submit": return `${title}${pc.gray(S_BAR)}${value ? ` ${pc.dim(value)}` : ""}`;
|
|
4130
|
+
case "cancel": return `${title}${pc.gray(S_BAR)}${value ? ` ${pc.strikethrough(pc.dim(value))}` : ""}${value.trim() ? `\n${pc.gray(S_BAR)}` : ""}`;
|
|
4131
|
+
default: return `${title}${cliColors.signal(S_BAR)} ${input}\n${cliColors.signal(S_BAR_END)}\n`;
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
}).prompt();
|
|
4135
|
+
}
|
|
5177
4136
|
function isPathWithinCwd$1(targetPath) {
|
|
5178
4137
|
const resolved = path.resolve(targetPath);
|
|
5179
4138
|
const rel = path.relative(process.cwd(), resolved);
|
|
@@ -5201,7 +4160,7 @@ async function getProjectName(initialName) {
|
|
|
5201
4160
|
counter++;
|
|
5202
4161
|
}
|
|
5203
4162
|
while (!isValid) {
|
|
5204
|
-
const response = await
|
|
4163
|
+
const response = await kuboText({
|
|
5205
4164
|
message: "Enter your project name or path (relative to current directory)",
|
|
5206
4165
|
placeholder: defaultName,
|
|
5207
4166
|
initialValue: initialName,
|
|
@@ -5215,7 +4174,7 @@ async function getProjectName(initialName) {
|
|
|
5215
4174
|
}
|
|
5216
4175
|
}
|
|
5217
4176
|
});
|
|
5218
|
-
if (isCancel(response)) throw new UserCancelledError({ message: "Operation cancelled." });
|
|
4177
|
+
if (isCancel$1(response)) throw new UserCancelledError({ message: "Operation cancelled." });
|
|
5219
4178
|
projectPath = response || defaultName;
|
|
5220
4179
|
isValid = true;
|
|
5221
4180
|
}
|
|
@@ -5256,48 +4215,48 @@ async function trackProjectCreation(config, disableAnalytics = false) {
|
|
|
5256
4215
|
//#region src/utils/display-config.ts
|
|
5257
4216
|
function displayConfig(config) {
|
|
5258
4217
|
const configDisplay = [];
|
|
5259
|
-
if (config.projectName) configDisplay.push(`${
|
|
4218
|
+
if (config.projectName) configDisplay.push(`${cliColors.signal("Project Name:")} ${config.projectName}`);
|
|
5260
4219
|
if (config.frontend !== void 0) {
|
|
5261
4220
|
const frontend = Array.isArray(config.frontend) ? config.frontend : [config.frontend];
|
|
5262
4221
|
const frontendText = frontend.length > 0 && frontend[0] !== void 0 ? frontend.join(", ") : "none";
|
|
5263
|
-
configDisplay.push(`${
|
|
5264
|
-
}
|
|
5265
|
-
if (config.backend !== void 0) configDisplay.push(`${
|
|
5266
|
-
if (config.runtime !== void 0) configDisplay.push(`${
|
|
5267
|
-
if (config.api !== void 0) configDisplay.push(`${
|
|
5268
|
-
if (config.database !== void 0) configDisplay.push(`${
|
|
5269
|
-
if (config.orm !== void 0) configDisplay.push(`${
|
|
5270
|
-
if (config.auth !== void 0) configDisplay.push(`${
|
|
5271
|
-
if (config.payments !== void 0) configDisplay.push(`${
|
|
5272
|
-
if (config.communication !== void 0) configDisplay.push(`${
|
|
5273
|
-
if (config.observability !== void 0) configDisplay.push(`${
|
|
4222
|
+
configDisplay.push(`${cliColors.signal("Frontend:")} ${frontendText}`);
|
|
4223
|
+
}
|
|
4224
|
+
if (config.backend !== void 0) configDisplay.push(`${cliColors.signal("Backend:")} ${String(config.backend)}`);
|
|
4225
|
+
if (config.runtime !== void 0) configDisplay.push(`${cliColors.signal("Runtime:")} ${String(config.runtime)}`);
|
|
4226
|
+
if (config.api !== void 0) configDisplay.push(`${cliColors.signal("API:")} ${String(config.api)}`);
|
|
4227
|
+
if (config.database !== void 0) configDisplay.push(`${cliColors.signal("Database:")} ${String(config.database)}`);
|
|
4228
|
+
if (config.orm !== void 0) configDisplay.push(`${cliColors.signal("ORM:")} ${String(config.orm)}`);
|
|
4229
|
+
if (config.auth !== void 0) configDisplay.push(`${cliColors.signal("Auth:")} ${String(config.auth)}`);
|
|
4230
|
+
if (config.payments !== void 0) configDisplay.push(`${cliColors.signal("Payments:")} ${config.payments.join(", ") || "none"}`);
|
|
4231
|
+
if (config.communication !== void 0) configDisplay.push(`${cliColors.signal("Communication:")} ${String(config.communication)}`);
|
|
4232
|
+
if (config.observability !== void 0) configDisplay.push(`${cliColors.signal("Observability:")} ${config.observability.join(", ") || "none"}`);
|
|
5274
4233
|
if (config.addons !== void 0) {
|
|
5275
4234
|
const addons = Array.isArray(config.addons) ? config.addons : [config.addons];
|
|
5276
4235
|
const addonsText = addons.length > 0 && addons[0] !== void 0 ? addons.join(", ") : "none";
|
|
5277
|
-
configDisplay.push(`${
|
|
4236
|
+
configDisplay.push(`${cliColors.signal("Addons:")} ${addonsText}`);
|
|
5278
4237
|
}
|
|
5279
4238
|
if (config.examples !== void 0) {
|
|
5280
4239
|
const examples = Array.isArray(config.examples) ? config.examples : [config.examples];
|
|
5281
4240
|
const examplesText = examples.length > 0 && examples[0] !== void 0 ? examples.join(", ") : "none";
|
|
5282
|
-
configDisplay.push(`${
|
|
4241
|
+
configDisplay.push(`${cliColors.signal("Examples:")} ${examplesText}`);
|
|
5283
4242
|
}
|
|
5284
4243
|
if (config.testing !== void 0) {
|
|
5285
4244
|
const testing = Array.isArray(config.testing) ? config.testing : [config.testing];
|
|
5286
4245
|
const testingText = testing.length > 0 && testing[0] !== void 0 ? testing.join(", ") : "none";
|
|
5287
|
-
configDisplay.push(`${
|
|
4246
|
+
configDisplay.push(`${cliColors.signal("Testing:")} ${testingText}`);
|
|
5288
4247
|
}
|
|
5289
4248
|
if (config.git !== void 0) {
|
|
5290
4249
|
const gitText = typeof config.git === "boolean" ? config.git ? "Yes" : "No" : String(config.git);
|
|
5291
|
-
configDisplay.push(`${
|
|
4250
|
+
configDisplay.push(`${cliColors.signal("Git Init:")} ${gitText}`);
|
|
5292
4251
|
}
|
|
5293
|
-
if (config.packageManager !== void 0) configDisplay.push(`${
|
|
4252
|
+
if (config.packageManager !== void 0) configDisplay.push(`${cliColors.signal("Package Manager:")} ${String(config.packageManager)}`);
|
|
5294
4253
|
if (config.install !== void 0) {
|
|
5295
4254
|
const installText = typeof config.install === "boolean" ? config.install ? "Yes" : "No" : String(config.install);
|
|
5296
|
-
configDisplay.push(`${
|
|
4255
|
+
configDisplay.push(`${cliColors.signal("Install Dependencies:")} ${installText}`);
|
|
5297
4256
|
}
|
|
5298
|
-
if (config.dbSetup !== void 0) configDisplay.push(`${
|
|
5299
|
-
if (config.webDeploy !== void 0) configDisplay.push(`${
|
|
5300
|
-
if (config.serverDeploy !== void 0) configDisplay.push(`${
|
|
4257
|
+
if (config.dbSetup !== void 0) configDisplay.push(`${cliColors.signal("Database Setup:")} ${String(config.dbSetup)}`);
|
|
4258
|
+
if (config.webDeploy !== void 0) configDisplay.push(`${cliColors.signal("Web Deployment:")} ${String(config.webDeploy)}`);
|
|
4259
|
+
if (config.serverDeploy !== void 0) configDisplay.push(`${cliColors.signal("Server Deployment:")} ${String(config.serverDeploy)}`);
|
|
5301
4260
|
if (configDisplay.length === 0) return pc.yellow("No configuration selected.");
|
|
5302
4261
|
return configDisplay.join("\n");
|
|
5303
4262
|
}
|
|
@@ -5427,7 +4386,7 @@ const TEMPLATE_PRESETS = {
|
|
|
5427
4386
|
frontend: ["react-router"],
|
|
5428
4387
|
api: "orpc",
|
|
5429
4388
|
auth: "better-auth",
|
|
5430
|
-
payments:
|
|
4389
|
+
payments: [],
|
|
5431
4390
|
observability: ["getmonitor"],
|
|
5432
4391
|
communication: "none",
|
|
5433
4392
|
addons: ["turborepo"],
|
|
@@ -5444,7 +4403,7 @@ const TEMPLATE_PRESETS = {
|
|
|
5444
4403
|
frontend: ["tanstack-router"],
|
|
5445
4404
|
api: "trpc",
|
|
5446
4405
|
auth: "better-auth",
|
|
5447
|
-
payments:
|
|
4406
|
+
payments: [],
|
|
5448
4407
|
observability: ["getmonitor"],
|
|
5449
4408
|
communication: "none",
|
|
5450
4409
|
addons: ["turborepo"],
|
|
@@ -5461,7 +4420,7 @@ const TEMPLATE_PRESETS = {
|
|
|
5461
4420
|
frontend: ["next"],
|
|
5462
4421
|
api: "trpc",
|
|
5463
4422
|
auth: "better-auth",
|
|
5464
|
-
payments:
|
|
4423
|
+
payments: [],
|
|
5465
4424
|
observability: ["getmonitor"],
|
|
5466
4425
|
communication: "none",
|
|
5467
4426
|
addons: ["biome", "turborepo"],
|
|
@@ -5478,7 +4437,7 @@ const TEMPLATE_PRESETS = {
|
|
|
5478
4437
|
frontend: ["native-uniwind"],
|
|
5479
4438
|
api: "none",
|
|
5480
4439
|
auth: "none",
|
|
5481
|
-
payments:
|
|
4440
|
+
payments: [],
|
|
5482
4441
|
observability: [],
|
|
5483
4442
|
communication: "none",
|
|
5484
4443
|
addons: ["none"],
|
|
@@ -5614,7 +4573,7 @@ function shouldSkipFile(fileName) {
|
|
|
5614
4573
|
}
|
|
5615
4574
|
function shouldUseBiomeFormatter(addons) {
|
|
5616
4575
|
if (!addons) return false;
|
|
5617
|
-
return addons.includes("biome")
|
|
4576
|
+
return addons.includes("biome");
|
|
5618
4577
|
}
|
|
5619
4578
|
async function formatCode(filePath, content) {
|
|
5620
4579
|
const result = await Result.tryPromise({
|
|
@@ -5670,7 +4629,7 @@ async function formatProjectWithBiome(projectDir) {
|
|
|
5670
4629
|
}
|
|
5671
4630
|
/**
|
|
5672
4631
|
* Post-scaffold format.
|
|
5673
|
-
* - biome
|
|
4632
|
+
* - biome → Biome `check --write` (not oxfmt)
|
|
5674
4633
|
* - oxlint / none → oxfmt
|
|
5675
4634
|
* Always skips kubojs.jsonrc, lockfiles, and binary extensions (oxfmt path).
|
|
5676
4635
|
*/
|
|
@@ -6118,7 +5077,7 @@ async function writeEnvFile$2(projectDir, backend, config) {
|
|
|
6118
5077
|
}
|
|
6119
5078
|
async function setupWithNeonDb(projectDir, packageManager, backend) {
|
|
6120
5079
|
const s = createSpinner();
|
|
6121
|
-
s.start("Creating Neon database using
|
|
5080
|
+
s.start("Creating Neon database using neon-new...");
|
|
6122
5081
|
const targetApp = backend === "self" ? "apps/web" : "apps/server";
|
|
6123
5082
|
const targetDir = path.join(projectDir, targetApp);
|
|
6124
5083
|
const ensureDirResult = await Result.tryPromise({
|
|
@@ -6133,17 +5092,17 @@ async function setupWithNeonDb(projectDir, packageManager, backend) {
|
|
|
6133
5092
|
s.stop(pc.red("Failed to create directory"));
|
|
6134
5093
|
return ensureDirResult;
|
|
6135
5094
|
}
|
|
6136
|
-
const packageArgs = getPackageExecutionArgs(packageManager, `
|
|
5095
|
+
const packageArgs = getPackageExecutionArgs(packageManager, `neon-new@latest --yes --ref "sbA3tIe"`);
|
|
6137
5096
|
return Result.tryPromise({
|
|
6138
5097
|
try: async () => {
|
|
6139
5098
|
await $({ cwd: targetDir })`${packageArgs}`;
|
|
6140
5099
|
s.stop(pc.green("Neon database created successfully!"));
|
|
6141
5100
|
},
|
|
6142
5101
|
catch: (e) => {
|
|
6143
|
-
s.stop(pc.red("Failed to create database with
|
|
5102
|
+
s.stop(pc.red("Failed to create database with neon-new"));
|
|
6144
5103
|
return new DatabaseSetupError({
|
|
6145
5104
|
provider: "neon",
|
|
6146
|
-
message: `Failed to create database with
|
|
5105
|
+
message: `Failed to create database with neon-new: ${e instanceof Error ? e.message : String(e)}`,
|
|
6147
5106
|
cause: e
|
|
6148
5107
|
});
|
|
6149
5108
|
}
|
|
@@ -6209,7 +5168,7 @@ async function setupNeonPostgres(config, cliInput) {
|
|
|
6209
5168
|
const promptedSetupMethod = await select({
|
|
6210
5169
|
message: "Choose your Neon setup method:",
|
|
6211
5170
|
options: [{
|
|
6212
|
-
label: "Quick setup with
|
|
5171
|
+
label: "Quick setup with neon-new",
|
|
6213
5172
|
value: "neondb",
|
|
6214
5173
|
hint: "fastest, no auth required"
|
|
6215
5174
|
}, {
|
|
@@ -7141,6 +6100,10 @@ async function getDockerStatus(database) {
|
|
|
7141
6100
|
}
|
|
7142
6101
|
//#endregion
|
|
7143
6102
|
//#region src/helpers/core/post-installation.ts
|
|
6103
|
+
const pc$1 = {
|
|
6104
|
+
...pc,
|
|
6105
|
+
cyan: cliColors.signal
|
|
6106
|
+
};
|
|
7144
6107
|
function getDesktopStaticBuildNote(frontend) {
|
|
7145
6108
|
const staticBuildFrontends = new Map([
|
|
7146
6109
|
["tanstack-start", "TanStack Start"],
|
|
@@ -7151,7 +6114,7 @@ function getDesktopStaticBuildNote(frontend) {
|
|
|
7151
6114
|
]);
|
|
7152
6115
|
const staticBuildFrontend = frontend.find((value) => staticBuildFrontends.has(value));
|
|
7153
6116
|
if (!staticBuildFrontend) return "";
|
|
7154
|
-
return `${pc.yellow("NOTE:")} Desktop builds package static web assets.\n ${staticBuildFrontends.get(staticBuildFrontend)} needs a static/export web build before desktop packaging will work.`;
|
|
6117
|
+
return `${pc$1.yellow("NOTE:")} Desktop builds package static web assets.\n ${staticBuildFrontends.get(staticBuildFrontend)} needs a static/export web build before desktop packaging will work.`;
|
|
7155
6118
|
}
|
|
7156
6119
|
async function displayPostInstallInstructions(config) {
|
|
7157
6120
|
const { api, database, relativePath, packageManager, depsInstalled, orm, addons, testing, runtime, frontend, backend, dbSetup, webDeploy, serverDeploy, observability, communication } = config;
|
|
@@ -7173,50 +6136,52 @@ async function displayPostInstallInstructions(config) {
|
|
|
7173
6136
|
const lintingInstructions = hasGitHooksOrLinting ? getLintingInstructions(runCmd) : "";
|
|
7174
6137
|
const nativeInstructions = (frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles")) && backend !== "none" ? getNativeInstructions(isConvex, isBackendSelf, frontend || [], runCmd) : "";
|
|
7175
6138
|
const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
|
|
7176
|
-
const
|
|
7177
|
-
const playwrightInstructions = testing?.includes("playwright") ? getPlaywrightInstructions(runCmd) : "";
|
|
6139
|
+
const playwrightInstructions = testing?.includes("playwright") ? getPlaywrightInstructions(runCmd, packageManager) : "";
|
|
7178
6140
|
const clerkInstructions = config.auth === "clerk" ? getClerkInstructions(frontend || [], backend, api) : "";
|
|
7179
6141
|
const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend);
|
|
7180
6142
|
const getMonitorInstructions = observability.includes("getmonitor") ? getGetMonitorInstructions() : "";
|
|
7181
6143
|
const resendInstructions = communication === "resend" ? getResendInstructions() : "";
|
|
7182
6144
|
const notifiqueInstructions = communication === "notifique" ? getNotifiqueInstructions() : "";
|
|
6145
|
+
const araraInstructions = communication === "arara" ? getAraraInstructions(backend) : "";
|
|
7183
6146
|
const hasWeb = frontend?.some((f) => types_exports.desktopWebFrontends.includes(f));
|
|
7184
6147
|
const hasNative = frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles");
|
|
7185
6148
|
const webPort = (0, types_exports.getWebPort)(frontend ?? []);
|
|
7186
6149
|
const betterAuthConvexInstructions = isConvex && config.auth === "better-auth" ? getBetterAuthConvexInstructions(hasWeb ?? false, webPort, packageManager, runCmd) : "";
|
|
7187
6150
|
const bunWebNativeWarning = packageManager === "bun" && hasNative && hasWeb ? getBunWebNativeWarning() : "";
|
|
7188
6151
|
const noOrmWarning = !isConvex && database !== "none" && orm === "none" ? getNoOrmWarning() : "";
|
|
7189
|
-
let output = `${pc.bold("Next steps")}\n${pc.cyan("1.")} ${cdCmd}\n`;
|
|
6152
|
+
let output = `${pc$1.bold("Next steps")}\n${pc$1.cyan("1.")} ${cdCmd}\n`;
|
|
7190
6153
|
let stepCounter = 2;
|
|
7191
|
-
if (!depsInstalled) output += `${pc.cyan(`${stepCounter++}.`)} ${packageManager} install\n`;
|
|
7192
|
-
if (database === "sqlite" && dbSetup === "turso") output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:local\n${pc.dim(" (optional - starts local Turso/libsql replica; requires Turso CLI)")}\n`;
|
|
6154
|
+
if (!depsInstalled) output += `${pc$1.cyan(`${stepCounter++}.`)} ${packageManager} install\n`;
|
|
6155
|
+
if (database === "sqlite" && dbSetup === "turso") output += `${pc$1.cyan(`${stepCounter++}.`)} ${runCmd} db:local\n${pc$1.dim(" (optional - starts local Turso/libsql replica; requires Turso CLI)")}\n`;
|
|
7193
6156
|
if (isConvex) {
|
|
7194
|
-
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev:setup\n${pc.dim(" (this will guide you through Convex project setup)")}\n`;
|
|
7195
|
-
output += `${pc.cyan(`${stepCounter++}.`)} Copy environment variables from\n${pc.white(" packages/backend/.env.local")} to ${pc.white("apps/*/.env")}\n`;
|
|
7196
|
-
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n\n`;
|
|
7197
|
-
} else if (isBackendSelf) output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
6157
|
+
output += `${pc$1.cyan(`${stepCounter++}.`)} ${runCmd} dev:setup\n${pc$1.dim(" (this will guide you through Convex project setup)")}\n`;
|
|
6158
|
+
output += `${pc$1.cyan(`${stepCounter++}.`)} Copy environment variables from\n${pc$1.white(" packages/backend/.env.local")} to ${pc$1.white("apps/*/.env")}\n`;
|
|
6159
|
+
output += `${pc$1.cyan(`${stepCounter++}.`)} ${runCmd} dev\n\n`;
|
|
6160
|
+
} else if (isBackendSelf) output += `${pc$1.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
7198
6161
|
else {
|
|
7199
|
-
if (runtime !== "workers") output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
6162
|
+
if (runtime !== "workers") output += `${pc$1.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
7200
6163
|
if (runtime === "workers") {
|
|
7201
|
-
if (dbSetup === "d1") output += `${pc.yellow("IMPORTANT:")} Complete D1 database setup first\n (see Database commands below)\n`;
|
|
7202
|
-
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
6164
|
+
if (dbSetup === "d1") output += `${pc$1.yellow("IMPORTANT:")} Complete D1 database setup first\n (see Database commands below)\n`;
|
|
6165
|
+
output += `${pc$1.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
7203
6166
|
}
|
|
7204
6167
|
}
|
|
7205
6168
|
const hasStandaloneBackend = backend !== "none";
|
|
7206
|
-
if (hasWeb || hasStandaloneBackend
|
|
7207
|
-
output += `${pc.bold("Your project will be available at:")}\n`;
|
|
7208
|
-
if (hasWeb) output += `${pc.cyan("•")} Frontend: http://localhost:${webPort}\n`;
|
|
7209
|
-
else if (!hasNative
|
|
6169
|
+
if (hasWeb || hasStandaloneBackend) {
|
|
6170
|
+
output += `${pc$1.bold("Your project will be available at:")}\n`;
|
|
6171
|
+
if (hasWeb) output += `${pc$1.cyan("•")} Frontend: http://localhost:${webPort}\n`;
|
|
6172
|
+
else if (!hasNative) output += `${pc$1.yellow("NOTE:")} You are creating a backend-only app\n (no frontend selected)\n`;
|
|
7210
6173
|
if (!isConvex && !isBackendSelf && hasStandaloneBackend) {
|
|
7211
|
-
output += `${pc.cyan("•")} Backend API: http://localhost:3000\n`;
|
|
7212
|
-
if (api === "orpc") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/api-reference\n`;
|
|
6174
|
+
output += `${pc$1.cyan("•")} Backend API: http://localhost:3000\n`;
|
|
6175
|
+
if (api === "orpc") output += `${pc$1.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/api-reference\n`;
|
|
6176
|
+
if (api === "orval") {
|
|
6177
|
+
output += `${pc$1.cyan("•")} OpenAPI contract: apps/api/openapi.yaml\n`;
|
|
6178
|
+
output += `${pc$1.cyan("•")} Regenerate API code: bun run api:generate\n`;
|
|
6179
|
+
}
|
|
7213
6180
|
}
|
|
7214
6181
|
if (isBackendSelf && api === "orpc") {
|
|
7215
6182
|
const rpcPath = frontend?.includes("next") || frontend?.includes("tanstack-start") ? "/api/rpc" : "/rpc";
|
|
7216
|
-
output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:${webPort}${rpcPath}/api-reference\n`;
|
|
6183
|
+
output += `${pc$1.cyan("•")} OpenAPI (Scalar UI): http://localhost:${webPort}${rpcPath}/api-reference\n`;
|
|
7217
6184
|
}
|
|
7218
|
-
if (addons?.includes("starlight")) output += `${pc.cyan("•")} Docs: http://localhost:4321\n`;
|
|
7219
|
-
if (addons?.includes("fumadocs")) output += `${pc.cyan("•")} Fumadocs: http://localhost:4000\n`;
|
|
7220
6185
|
}
|
|
7221
6186
|
if (nativeInstructions) output += `\n${nativeInstructions.trim()}\n`;
|
|
7222
6187
|
if (databaseInstructions) output += `\n${databaseInstructions.trim()}\n`;
|
|
@@ -7227,18 +6192,18 @@ async function displayPostInstallInstructions(config) {
|
|
|
7227
6192
|
if (vitePlusNativeHooksInstructions) output += `\n${vitePlusNativeHooksInstructions.trim()}\n`;
|
|
7228
6193
|
if (lintingInstructions) output += `\n${lintingInstructions.trim()}\n`;
|
|
7229
6194
|
if (pwaInstructions) output += `\n${pwaInstructions.trim()}\n`;
|
|
7230
|
-
if (starlightInstructions) output += `\n${starlightInstructions.trim()}\n`;
|
|
7231
6195
|
if (playwrightInstructions) output += `\n${playwrightInstructions.trim()}\n`;
|
|
7232
6196
|
if (clerkInstructions) output += `\n${clerkInstructions.trim()}\n`;
|
|
7233
6197
|
if (betterAuthConvexInstructions) output += `\n${betterAuthConvexInstructions.trim()}\n`;
|
|
7234
6198
|
if (getMonitorInstructions) output += `\n${getMonitorInstructions.trim()}\n`;
|
|
7235
6199
|
if (resendInstructions) output += `\n${resendInstructions.trim()}\n`;
|
|
7236
6200
|
if (notifiqueInstructions) output += `\n${notifiqueInstructions.trim()}\n`;
|
|
6201
|
+
if (araraInstructions) output += `\n${araraInstructions.trim()}\n`;
|
|
7237
6202
|
if (alchemyDeployInstructions) output += `\n${alchemyDeployInstructions.trim()}\n`;
|
|
7238
6203
|
if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`;
|
|
7239
6204
|
if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
|
|
7240
|
-
output += `\n${pc.bold("Like kubojs?")} Please consider giving us a star\n on GitHub:\n`;
|
|
7241
|
-
output += pc.cyan("https://github.com/albuquerquesz/kubo");
|
|
6205
|
+
output += `\n${pc$1.bold("Like kubojs?")} Please consider giving us a star\n on GitHub:\n`;
|
|
6206
|
+
output += pc$1.cyan("https://github.com/albuquerquesz/kubo");
|
|
7242
6207
|
cliConsola.box(output);
|
|
7243
6208
|
}
|
|
7244
6209
|
function getNativeInstructions(isConvex, isBackendSelf, frontend, runCmd) {
|
|
@@ -7247,26 +6212,27 @@ function getNativeInstructions(isConvex, isBackendSelf, frontend, runCmd) {
|
|
|
7247
6212
|
const exampleUrl = isConvex ? "https://example.convex.cloud" : isBackendSelf ? `http://<YOUR_LOCAL_IP>:${selfBackendPort}` : "http://<YOUR_LOCAL_IP>:3000";
|
|
7248
6213
|
const envFileName = ".env";
|
|
7249
6214
|
const ipNote = isConvex ? "your Convex deployment URL (find after running 'dev:setup')" : "your local IP address";
|
|
7250
|
-
let instructions = `${pc.yellow("NOTE:")} For Expo connectivity issues, update\n apps/native/${envFileName} with ${ipNote}:\n ${`${envVar}=${exampleUrl}`}\n`;
|
|
7251
|
-
if (isConvex) instructions += `\n${pc.yellow("IMPORTANT:")} When using local development with Convex and native apps,\n ensure you use your local IP address instead of localhost or 127.0.0.1\n for proper connectivity.\n`;
|
|
7252
|
-
if (frontend.includes("native-unistyles")) instructions += `\n${pc.yellow("NOTE:")} Unistyles requires a development build.\n cd apps/native and run ${runCmd} android or ${runCmd} ios\n`;
|
|
6215
|
+
let instructions = `${pc$1.yellow("NOTE:")} For Expo connectivity issues, update\n apps/native/${envFileName} with ${ipNote}:\n ${`${envVar}=${exampleUrl}`}\n`;
|
|
6216
|
+
if (isConvex) instructions += `\n${pc$1.yellow("IMPORTANT:")} When using local development with Convex and native apps,\n ensure you use your local IP address instead of localhost or 127.0.0.1\n for proper connectivity.\n`;
|
|
6217
|
+
if (frontend.includes("native-unistyles")) instructions += `\n${pc$1.yellow("NOTE:")} Unistyles requires a development build.\n cd apps/native and run ${runCmd} android or ${runCmd} ios\n`;
|
|
7253
6218
|
return instructions;
|
|
7254
6219
|
}
|
|
7255
6220
|
function getHuskyInstructions(runCmd) {
|
|
7256
|
-
return `${pc.bold("Git hooks with Husky:")}\n${pc.cyan("•")} Initialize hooks: ${`${runCmd} prepare`}\n`;
|
|
6221
|
+
return `${pc$1.bold("Git hooks with Husky:")}\n${pc$1.cyan("•")} Initialize hooks: ${`${runCmd} prepare`}\n`;
|
|
7257
6222
|
}
|
|
7258
|
-
function getPlaywrightInstructions(runCmd) {
|
|
7259
|
-
|
|
6223
|
+
function getPlaywrightInstructions(runCmd, packageManager) {
|
|
6224
|
+
const browserInstall = packageManager === "bun" ? "bunx playwright install" : packageManager === "pnpm" ? "pnpm exec playwright install" : "npx playwright install";
|
|
6225
|
+
return `${pc$1.bold("End-to-end tests with Playwright:")}\n${pc$1.cyan("•")} Install browsers (if needed): ${browserInstall}\n${pc$1.cyan("•")} Run e2e tests: ${`${runCmd} test:e2e`}\n`;
|
|
7260
6226
|
}
|
|
7261
6227
|
function getLintingInstructions(runCmd) {
|
|
7262
|
-
return `${pc.bold("Linting and formatting:")}\n${pc.cyan("•")} Run checks: ${`${runCmd} check`}\n`;
|
|
6228
|
+
return `${pc$1.bold("Linting and formatting:")}\n${pc$1.cyan("•")} Run checks: ${`${runCmd} check`}\n`;
|
|
7263
6229
|
}
|
|
7264
6230
|
function getLefthookInstructions(packageManager) {
|
|
7265
6231
|
const cmd = packageManager === "npm" ? "npx" : packageManager;
|
|
7266
|
-
return `${pc.bold("Git hooks with Lefthook:")}\n${pc.cyan("•")} Install hooks: ${cmd} lefthook install\n`;
|
|
6232
|
+
return `${pc$1.bold("Git hooks with Lefthook:")}\n${pc$1.cyan("•")} Install hooks: ${cmd} lefthook install\n`;
|
|
7267
6233
|
}
|
|
7268
6234
|
function getVitePlusNativeHooksInstructions(runCmd) {
|
|
7269
|
-
return `${pc.bold("Vite+ native Git hooks:")}\n${pc.cyan("•")} Optional hook setup: ${`${runCmd} hooks:setup`}\n${pc.dim(" (runs vp config; hooks install into .vite-hooks and use vp staged)")}\n`;
|
|
6235
|
+
return `${pc$1.bold("Vite+ native Git hooks:")}\n${pc$1.cyan("•")} Optional hook setup: ${`${runCmd} hooks:setup`}\n${pc$1.dim(" (runs vp config; hooks install into .vite-hooks and use vp staged)")}\n`;
|
|
7270
6236
|
}
|
|
7271
6237
|
async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup, webDeploy, serverDeploy, backend) {
|
|
7272
6238
|
const instructions = [];
|
|
@@ -7279,62 +6245,63 @@ async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup,
|
|
|
7279
6245
|
}
|
|
7280
6246
|
}
|
|
7281
6247
|
if (isD1Alchemy) {
|
|
7282
|
-
if (orm === "drizzle") instructions.push(`${pc.cyan("•")} Generate migrations: ${`${runCmd} db:generate`}`);
|
|
6248
|
+
if (orm === "drizzle") instructions.push(`${pc$1.cyan("•")} Generate migrations: ${`${runCmd} db:generate`}`);
|
|
7283
6249
|
else if (orm === "prisma") {
|
|
7284
|
-
instructions.push(`${pc.cyan("•")} Generate Prisma client: ${`${runCmd} db:generate`}`);
|
|
7285
|
-
instructions.push(`${pc.cyan("•")} Apply migrations: ${`${runCmd} db:migrate`}`);
|
|
6250
|
+
instructions.push(`${pc$1.cyan("•")} Generate Prisma client: ${`${runCmd} db:generate`}`);
|
|
6251
|
+
instructions.push(`${pc$1.cyan("•")} Apply migrations: ${`${runCmd} db:migrate`}`);
|
|
7286
6252
|
}
|
|
7287
6253
|
}
|
|
7288
6254
|
if (dbSetup === "planetscale") {
|
|
7289
|
-
if (database === "mysql" && orm === "drizzle") instructions.push(`${pc.yellow("NOTE:")} Enable foreign key constraints in PlanetScale database settings`);
|
|
7290
|
-
if (database === "mysql" && orm === "prisma") instructions.push(`${pc.yellow("NOTE:")} How to handle Prisma migrations with PlanetScale:\n https://github.com/prisma/prisma/issues/7292`);
|
|
6255
|
+
if (database === "mysql" && orm === "drizzle") instructions.push(`${pc$1.yellow("NOTE:")} Enable foreign key constraints in PlanetScale database settings`);
|
|
6256
|
+
if (database === "mysql" && orm === "prisma") instructions.push(`${pc$1.yellow("NOTE:")} How to handle Prisma migrations with PlanetScale:\n https://github.com/prisma/prisma/issues/7292`);
|
|
7291
6257
|
}
|
|
7292
|
-
if (dbSetup === "turso" && orm === "prisma") instructions.push(`${pc.yellow("NOTE:")} Follow Turso's Prisma guide for migrations via the Turso CLI:\n https://docs.turso.tech/sdk/ts/orm/prisma`);
|
|
6258
|
+
if (dbSetup === "turso" && orm === "prisma") instructions.push(`${pc$1.yellow("NOTE:")} Follow Turso's Prisma guide for migrations via the Turso CLI:\n https://docs.turso.tech/sdk/ts/orm/prisma`);
|
|
7293
6259
|
if (orm === "prisma") {
|
|
7294
|
-
if (database === "mongodb" && dbSetup === "docker") instructions.push(`${pc.yellow("WARNING:")} Prisma + MongoDB + Docker combination\n may not work.`);
|
|
7295
|
-
if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
6260
|
+
if (database === "mongodb" && dbSetup === "docker") instructions.push(`${pc$1.yellow("WARNING:")} Prisma + MongoDB + Docker combination\n may not work.`);
|
|
6261
|
+
if (dbSetup === "docker") instructions.push(`${pc$1.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
7296
6262
|
if (!isD1Alchemy) {
|
|
7297
|
-
instructions.push(`${pc.cyan("•")} Generate Prisma Client: ${`${runCmd} db:generate`}`);
|
|
7298
|
-
instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
|
|
6263
|
+
instructions.push(`${pc$1.cyan("•")} Generate Prisma Client: ${`${runCmd} db:generate`}`);
|
|
6264
|
+
instructions.push(`${pc$1.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
|
|
7299
6265
|
}
|
|
7300
|
-
if (!isD1Alchemy) instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
|
|
6266
|
+
if (!isD1Alchemy) instructions.push(`${pc$1.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
|
|
7301
6267
|
} else if (orm === "drizzle") {
|
|
7302
|
-
if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
7303
|
-
if (!isD1Alchemy) instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
|
|
7304
|
-
if (!isD1Alchemy) instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
|
|
6268
|
+
if (dbSetup === "docker") instructions.push(`${pc$1.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
6269
|
+
if (!isD1Alchemy) instructions.push(`${pc$1.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
|
|
6270
|
+
if (!isD1Alchemy) instructions.push(`${pc$1.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
|
|
7305
6271
|
} else if (orm === "mongoose") {
|
|
7306
|
-
if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
7307
|
-
} else if (orm === "none") instructions.push(`${pc.yellow("NOTE:")} Manual database schema setup\n required.`);
|
|
7308
|
-
return instructions.length ? `${pc.bold("Database commands:")}\n${instructions.join("\n")}` : "";
|
|
6272
|
+
if (dbSetup === "docker") instructions.push(`${pc$1.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
6273
|
+
} else if (orm === "none") instructions.push(`${pc$1.yellow("NOTE:")} Manual database schema setup\n required.`);
|
|
6274
|
+
return instructions.length ? `${pc$1.bold("Database commands:")}\n${instructions.join("\n")}` : "";
|
|
7309
6275
|
}
|
|
7310
6276
|
function getTauriInstructions(runCmd, frontend) {
|
|
7311
6277
|
const staticBuildNote = getDesktopStaticBuildNote(frontend);
|
|
7312
|
-
return `\n${pc.bold("Desktop app with Tauri:")}\n${pc.cyan("•")} Start desktop app: ${`cd apps/web && ${runCmd} desktop:dev`}\n${pc.cyan("•")} Build desktop app: ${`cd apps/web && ${runCmd} desktop:build`}\n${pc.yellow("NOTE:")} Tauri requires Rust and platform-specific dependencies.\n See: https://v2.tauri.app/start/prerequisites/${staticBuildNote ? `\n${staticBuildNote}` : ""}`;
|
|
6278
|
+
return `\n${pc$1.bold("Desktop app with Tauri:")}\n${pc$1.cyan("•")} Start desktop app: ${`cd apps/web && ${runCmd} desktop:dev`}\n${pc$1.cyan("•")} Build desktop app: ${`cd apps/web && ${runCmd} desktop:build`}\n${pc$1.yellow("NOTE:")} Tauri requires Rust and platform-specific dependencies.\n See: https://v2.tauri.app/start/prerequisites/${staticBuildNote ? `\n${staticBuildNote}` : ""}`;
|
|
7313
6279
|
}
|
|
7314
6280
|
function getElectrobunInstructions(runCmd, frontend) {
|
|
7315
6281
|
const staticBuildNote = getDesktopStaticBuildNote(frontend);
|
|
7316
|
-
return `\n${pc.bold("Desktop app with Electrobun:")}\n${pc.cyan("•")} Start desktop app with HMR: ${`${runCmd} dev:desktop`}\n${pc.cyan("•")} Build stable desktop app (DMG/App): ${`${runCmd} build:desktop`}\n${pc.cyan("•")} Build canary desktop app: ${`${runCmd} build:desktop:canary`}\n${pc.yellow("NOTE:")} Electrobun wraps your web frontend in a desktop shell.\n See: https://framework.blackboard.sh/electrobun/${staticBuildNote ? `\n${staticBuildNote}` : ""}`;
|
|
6282
|
+
return `\n${pc$1.bold("Desktop app with Electrobun:")}\n${pc$1.cyan("•")} Start desktop app with HMR: ${`${runCmd} dev:desktop`}\n${pc$1.cyan("•")} Build stable desktop app (DMG/App): ${`${runCmd} build:desktop`}\n${pc$1.cyan("•")} Build canary desktop app: ${`${runCmd} build:desktop:canary`}\n${pc$1.yellow("NOTE:")} Electrobun wraps your web frontend in a desktop shell.\n See: https://framework.blackboard.sh/electrobun/${staticBuildNote ? `\n${staticBuildNote}` : ""}`;
|
|
7317
6283
|
}
|
|
7318
6284
|
function getPwaInstructions() {
|
|
7319
|
-
return `\n${pc.bold("PWA with React Router v7:")}\n${pc.yellow("NOTE:")} There is a known compatibility issue between VitePWA\n and React Router v7. See:\n https://github.com/vite-pwa/vite-plugin-pwa/issues/809`;
|
|
7320
|
-
}
|
|
7321
|
-
function getStarlightInstructions(runCmd) {
|
|
7322
|
-
return `\n${pc.bold("Documentation with Starlight:")}\n${pc.cyan("•")} Start docs site: ${`cd apps/docs && ${runCmd} dev`}\n${pc.cyan("•")} Build docs site: ${`cd apps/docs && ${runCmd} build`}`;
|
|
6285
|
+
return `\n${pc$1.bold("PWA with React Router v7:")}\n${pc$1.yellow("NOTE:")} There is a known compatibility issue between VitePWA\n and React Router v7. See:\n https://github.com/vite-pwa/vite-plugin-pwa/issues/809`;
|
|
7323
6286
|
}
|
|
7324
6287
|
function getNoOrmWarning() {
|
|
7325
|
-
return `\n${pc.yellow("WARNING:")} Database selected without an ORM. Features requiring\n database access (e.g., examples, auth) need manual setup.`;
|
|
6288
|
+
return `\n${pc$1.yellow("WARNING:")} Database selected without an ORM. Features requiring\n database access (e.g., examples, auth) need manual setup.`;
|
|
7326
6289
|
}
|
|
7327
6290
|
function getGetMonitorInstructions() {
|
|
7328
|
-
return `${pc.bold("GetMonitor observability:")}\n${pc.cyan("•")} Keys are optional on first run — the app starts without them; capture stays idle until configured\n${pc.cyan("•")} When ready, set GETMONITOR_API_KEY (public gm_xxx) in the web and/or server .env\n${pc.cyan("•")} Ingestion host is fixed (http://ingest.getmonitor.io) — no apiHost env var\n${pc.cyan("•")} Optional: set GETMONITOR_AUTH_TOKEN for Next/Nuxt source-map upload on production builds\n${pc.cyan("•")} Browser/Node capture is automatic; React trees include GetMonitorErrorBoundary\n${pc.cyan("•")} Opt out next time with --disable-observability\n${pc.dim(" https://github.com/get-monitor/getmonitor-js")}`;
|
|
6291
|
+
return `${pc$1.bold("GetMonitor observability:")}\n${pc$1.cyan("•")} Keys are optional on first run — the app starts without them; capture stays idle until configured\n${pc$1.cyan("•")} When ready, set GETMONITOR_API_KEY (public gm_xxx) in the web and/or server .env\n${pc$1.cyan("•")} Ingestion host is fixed (http://ingest.getmonitor.io) — no apiHost env var\n${pc$1.cyan("•")} Optional: set GETMONITOR_AUTH_TOKEN for Next/Nuxt source-map upload on production builds\n${pc$1.cyan("•")} Browser/Node capture is automatic; React trees include GetMonitorErrorBoundary\n${pc$1.cyan("•")} Opt out next time with --disable-observability\n${pc$1.dim(" https://github.com/get-monitor/getmonitor-js")}`;
|
|
7329
6292
|
}
|
|
7330
6293
|
function getResendInstructions() {
|
|
7331
|
-
return `${pc.bold("Resend communication:")}\n${pc.cyan("•")} Keys are optional on first run — sendEmail throws only when called without RESEND_API_KEY\n${pc.cyan("•")} Set RESEND_API_KEY (and optional RESEND_FROM_EMAIL) in the server .env\n${pc.cyan("•")} Import sendEmail from packages/email (e.g. @project/email)\n${pc.cyan("•")} Replace onboarding@resend.dev with a verified domain sender for production\n${pc.dim(" https://resend.com/docs/send-with-nodejs")}`;
|
|
6294
|
+
return `${pc$1.bold("Resend communication:")}\n${pc$1.cyan("•")} Keys are optional on first run — sendEmail throws only when called without RESEND_API_KEY\n${pc$1.cyan("•")} Set RESEND_API_KEY (and optional RESEND_FROM_EMAIL) in the server .env\n${pc$1.cyan("•")} Import sendEmail from packages/email (e.g. @project/email)\n${pc$1.cyan("•")} Replace onboarding@resend.dev with a verified domain sender for production\n${pc$1.dim(" https://resend.com/docs/send-with-nodejs")}`;
|
|
7332
6295
|
}
|
|
7333
6296
|
function getNotifiqueInstructions() {
|
|
7334
|
-
return `${pc.bold("Notifique communication:")}\n${pc.cyan("•")} Keys are optional on first run — helpers throw only when called without NOTIFIQUE_API_KEY\n${pc.cyan("•")} Set NOTIFIQUE_API_KEY (sk_live_… / sk_test_…) in the server .env\n${pc.cyan("•")} Import sendSms / sendWhatsAppText / sendEmail from packages/notifique\n${pc.cyan("•")} Auth is Bearer only — do not send x-workspace-id\n${pc.dim(" https://docs.notifique.dev/skill.md")}`;
|
|
6297
|
+
return `${pc$1.bold("Notifique communication:")}\n${pc$1.cyan("•")} Keys are optional on first run — helpers throw only when called without NOTIFIQUE_API_KEY\n${pc$1.cyan("•")} Set NOTIFIQUE_API_KEY (sk_live_… / sk_test_…) in the server .env\n${pc$1.cyan("•")} Import sendSms / sendWhatsAppText / sendEmail from packages/notifique\n${pc$1.cyan("•")} Auth is Bearer only — do not send x-workspace-id\n${pc$1.dim(" https://docs.notifique.dev/skill.md")}`;
|
|
6298
|
+
}
|
|
6299
|
+
function getAraraInstructions(backend) {
|
|
6300
|
+
const envPath = backend === "convex" ? "packages/backend/.env.local" : "the server .env";
|
|
6301
|
+
return `${pc$1.bold("AraraHQ communication:")}\n${pc$1.cyan("•")} Set ARARA_API_KEY in ${envPath}\n${pc$1.cyan("•")} Use @project/arara only from server code; the SDK does not run on Edge/Workers\n${pc$1.cyan("•")} Convex projects use the generated Node Action (api.arara.execute)\n${pc$1.dim(" https://docs.ararahq.com/sdks/node")}`;
|
|
7335
6302
|
}
|
|
7336
6303
|
function getBunWebNativeWarning() {
|
|
7337
|
-
return `\n${pc.yellow("WARNING:")} 'bun' might cause issues with web + native apps in a monorepo.\n Use 'pnpm' if problems arise.`;
|
|
6304
|
+
return `\n${pc$1.yellow("WARNING:")} 'bun' might cause issues with web + native apps in a monorepo.\n Use 'pnpm' if problems arise.`;
|
|
7338
6305
|
}
|
|
7339
6306
|
function getClerkQuickstartUrl(frontend) {
|
|
7340
6307
|
if (frontend.includes("next")) return "https://clerk.com/docs/nextjs/getting-started/quickstart";
|
|
@@ -7390,28 +6357,28 @@ function getClerkInstructionLines(frontend, backend, api) {
|
|
|
7390
6357
|
}
|
|
7391
6358
|
function getClerkInstructions(frontend, backend, api) {
|
|
7392
6359
|
return [
|
|
7393
|
-
`${pc.bold("Clerk Authentication Setup:")}`,
|
|
7394
|
-
`${pc.cyan("•")} Follow the guide: ${pc.underline(getClerkQuickstartUrl(frontend))}`,
|
|
7395
|
-
...getClerkInstructionLines(frontend, backend, api).map((line) => `${pc.cyan("•")} ${line}`)
|
|
6360
|
+
`${pc$1.bold("Clerk Authentication Setup:")}`,
|
|
6361
|
+
`${pc$1.cyan("•")} Follow the guide: ${pc$1.underline(getClerkQuickstartUrl(frontend))}`,
|
|
6362
|
+
...getClerkInstructionLines(frontend, backend, api).map((line) => `${pc$1.cyan("•")} ${line}`)
|
|
7396
6363
|
].join("\n");
|
|
7397
6364
|
}
|
|
7398
6365
|
function getBetterAuthConvexInstructions(hasWeb, webPort, packageManager, runCmd) {
|
|
7399
6366
|
const cmd = packageManager === "npm" ? "npx" : packageManager;
|
|
7400
|
-
return `${pc.bold("Better Auth + Convex Setup:")}\n${pc.cyan("•")} Configure the Convex deployment before setting env vars:\n${pc.white(` ${runCmd} dev:setup`)}\n${pc.cyan("•")} Set environment variables from ${pc.white("packages/backend")}:\n${pc.white(" cd packages/backend")}\n${pc.white(` ${cmd} convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)`)}\n` + (hasWeb ? `${pc.white(` ${cmd} convex env set SITE_URL http://localhost:${webPort}`)}\n` : "");
|
|
6367
|
+
return `${pc$1.bold("Better Auth + Convex Setup:")}\n${pc$1.cyan("•")} Configure the Convex deployment before setting env vars:\n${pc$1.white(` ${runCmd} dev:setup`)}\n${pc$1.cyan("•")} Set environment variables from ${pc$1.white("packages/backend")}:\n${pc$1.white(" cd packages/backend")}\n${pc$1.white(` ${cmd} convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)`)}\n` + (hasWeb ? `${pc$1.white(` ${cmd} convex env set SITE_URL http://localhost:${webPort}`)}\n` : "");
|
|
7401
6368
|
}
|
|
7402
6369
|
function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend) {
|
|
7403
6370
|
const instructions = [];
|
|
7404
6371
|
const isBackendSelf = backend === "self";
|
|
7405
6372
|
if (webDeploy === "cloudflare" && serverDeploy !== "cloudflare" && !isBackendSelf) {
|
|
7406
6373
|
const cfDeploy = serverDeploy === "vercel" ? "deploy:web" : "deploy";
|
|
7407
|
-
instructions.push(`${pc.bold("Deploy web with Cloudflare (Alchemy):")}\n${pc.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`${runCmd} ${cfDeploy}`}\n${pc.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
|
|
6374
|
+
instructions.push(`${pc$1.bold("Deploy web with Cloudflare (Alchemy):")}\n${pc$1.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc$1.cyan("•")} Deploy: ${`${runCmd} ${cfDeploy}`}\n${pc$1.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
|
|
7408
6375
|
} else if (serverDeploy === "cloudflare" && webDeploy !== "cloudflare" && !isBackendSelf) {
|
|
7409
6376
|
const cfDeploy = webDeploy === "vercel" ? "deploy:server" : "deploy";
|
|
7410
|
-
instructions.push(`${pc.bold("Deploy server with Cloudflare (Alchemy):")}\n${pc.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`${runCmd} ${cfDeploy}`}\n${pc.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
|
|
7411
|
-
} else if (webDeploy === "cloudflare" && (serverDeploy === "cloudflare" || isBackendSelf)) instructions.push(`${pc.bold("Deploy with Cloudflare (Alchemy):")}\n${pc.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
|
|
6377
|
+
instructions.push(`${pc$1.bold("Deploy server with Cloudflare (Alchemy):")}\n${pc$1.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc$1.cyan("•")} Deploy: ${`${runCmd} ${cfDeploy}`}\n${pc$1.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
|
|
6378
|
+
} else if (webDeploy === "cloudflare" && (serverDeploy === "cloudflare" || isBackendSelf)) instructions.push(`${pc$1.bold("Deploy with Cloudflare (Alchemy):")}\n${pc$1.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc$1.cyan("•")} Deploy: ${`${runCmd} deploy`}\n${pc$1.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
|
|
7412
6379
|
if (webDeploy === "docker" || serverDeploy === "docker") {
|
|
7413
6380
|
const dockerTargets = webDeploy === "docker" && serverDeploy === "docker" ? "web + server" : webDeploy === "docker" ? "web" : "server";
|
|
7414
|
-
instructions.push(`${pc.bold(`Deploy ${dockerTargets} with Docker Compose:`)}\n${pc.cyan("•")} Start: ${`${runCmd} docker:up`}\n${pc.cyan("•")} Logs: ${`${runCmd} docker:logs`}\n${pc.cyan("•")} Stop: ${`${runCmd} docker:down`}\n${pc.cyan("•")} Config: docker-compose.yml`);
|
|
6381
|
+
instructions.push(`${pc$1.bold(`Deploy ${dockerTargets} with Docker Compose:`)}\n${pc$1.cyan("•")} Start: ${`${runCmd} docker:up`}\n${pc$1.cyan("•")} Logs: ${`${runCmd} docker:logs`}\n${pc$1.cyan("•")} Stop: ${`${runCmd} docker:down`}\n${pc$1.cyan("•")} Config: docker-compose.yml`);
|
|
7415
6382
|
}
|
|
7416
6383
|
if (webDeploy === "vercel" || serverDeploy === "vercel") {
|
|
7417
6384
|
const mixedWithCloudflare = webDeploy !== "none" && serverDeploy !== "none" && webDeploy !== serverDeploy;
|
|
@@ -7419,14 +6386,14 @@ function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend)
|
|
|
7419
6386
|
const vercelEnvScript = "env:production";
|
|
7420
6387
|
const vercelDeployScript = mixedWithCloudflare ? webDeploy === "vercel" ? "deploy:web:prod" : "deploy:server:prod" : "deploy:prod";
|
|
7421
6388
|
const vercelTargets = webDeploy === "vercel" && (serverDeploy === "vercel" || isBackendSelf) ? "web + server" : webDeploy === "vercel" ? "web" : "server";
|
|
7422
|
-
instructions.push(`${pc.bold(`Deploy ${vercelTargets} with Vercel Services:`)}\n${pc.cyan("•")} Link project: ${`${runCmd} ${vercelSetupScript}`}\n${pc.cyan("•")} Sync env (before first deploy): ${`${runCmd} ${vercelEnvScript}`}\n${pc.cyan("•")} Deploy: ${`${runCmd} ${vercelDeployScript}`}\n${pc.cyan("•")} Guide: https://www.kubojs.dev/docs/guides/vercel`);
|
|
6389
|
+
instructions.push(`${pc$1.bold(`Deploy ${vercelTargets} with Vercel Services:`)}\n${pc$1.cyan("•")} Link project: ${`${runCmd} ${vercelSetupScript}`}\n${pc$1.cyan("•")} Sync env (before first deploy): ${`${runCmd} ${vercelEnvScript}`}\n${pc$1.cyan("•")} Deploy: ${`${runCmd} ${vercelDeployScript}`}\n${pc$1.cyan("•")} Guide: https://www.kubojs.dev/docs/guides/vercel`);
|
|
7423
6390
|
}
|
|
7424
6391
|
if (webDeploy === "guaracloud" || serverDeploy === "guaracloud") {
|
|
7425
6392
|
const deployScript = webDeploy !== "none" && serverDeploy !== "none" && webDeploy !== serverDeploy || webDeploy === "guaracloud" && serverDeploy === "guaracloud" ? `deploy:${webDeploy === "guaracloud" ? "web" : "server"}` : "deploy";
|
|
7426
6393
|
const linkScript = `${deployScript}:link`;
|
|
7427
6394
|
const logsScript = `${deployScript}:logs`;
|
|
7428
6395
|
const guaraTargets = webDeploy === "guaracloud" && (serverDeploy === "guaracloud" || isBackendSelf) ? "web + server" : webDeploy === "guaracloud" ? "web" : "server";
|
|
7429
|
-
instructions.push(`${pc.bold(`Deploy ${guaraTargets} with Guara Cloud:`)}\n${pc.cyan("•")} Login: ${`${runCmd} deploy:login`}\n${pc.cyan("•")} Link app directory: ${`${runCmd} ${linkScript}`}\n${pc.cyan("•")} Deploy: ${`${runCmd} ${deployScript}`}\n${pc.cyan("•")} Logs: ${`${runCmd} ${logsScript}`}\n${pc.cyan("•")} Docs: https://guaracloud.com/docs/getting-started/introduction/`);
|
|
6396
|
+
instructions.push(`${pc$1.bold(`Deploy ${guaraTargets} with Guara Cloud:`)}\n${pc$1.cyan("•")} Login: ${`${runCmd} deploy:login`}\n${pc$1.cyan("•")} Link app directory: ${`${runCmd} ${linkScript}`}\n${pc$1.cyan("•")} Deploy: ${`${runCmd} ${deployScript}`}\n${pc$1.cyan("•")} Logs: ${`${runCmd} ${logsScript}`}\n${pc$1.cyan("•")} Docs: https://guaracloud.com/docs/getting-started/introduction/`);
|
|
7430
6397
|
}
|
|
7431
6398
|
return instructions.length ? `\n${instructions.join("\n")}` : "";
|
|
7432
6399
|
}
|
|
@@ -7492,10 +6459,13 @@ async function createProject(options, cliInput = {}) {
|
|
|
7492
6459
|
}));
|
|
7493
6460
|
yield* Result.await(formatProject(projectDir, { addons: options.addons }));
|
|
7494
6461
|
if (!isSilent()) log.success("Project template successfully scaffolded!");
|
|
7495
|
-
if (options.install)
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
6462
|
+
if (options.install) {
|
|
6463
|
+
yield* Result.await(installDependencies({
|
|
6464
|
+
projectDir,
|
|
6465
|
+
packageManager: options.packageManager
|
|
6466
|
+
}));
|
|
6467
|
+
if (options.testing.includes("playwright")) await installPlaywrightBrowsers(projectDir, options.packageManager);
|
|
6468
|
+
}
|
|
7499
6469
|
yield* Result.await(initializeGit(projectDir, options.git));
|
|
7500
6470
|
if (!isSilent()) await displayPostInstallInstructions({
|
|
7501
6471
|
...options,
|
|
@@ -7568,7 +6538,7 @@ function createEmptyResult(timeScaffolded, elapsedTimeMs, error) {
|
|
|
7568
6538
|
examples: [],
|
|
7569
6539
|
testing: [],
|
|
7570
6540
|
auth: "none",
|
|
7571
|
-
payments:
|
|
6541
|
+
payments: [],
|
|
7572
6542
|
observability: [],
|
|
7573
6543
|
communication: "none",
|
|
7574
6544
|
git: false,
|
|
@@ -7608,7 +6578,7 @@ async function createProjectHandler(input, options = {}) {
|
|
|
7608
6578
|
async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
7609
6579
|
return Result.gen(async function* () {
|
|
7610
6580
|
if (!isSilent() && input.renderTitle !== false) renderTitle();
|
|
7611
|
-
if (!isSilent()) intro(
|
|
6581
|
+
if (!isSilent()) intro(cliColors.bright("Creating a new kubojs project"));
|
|
7612
6582
|
if (!isSilent() && input.yolo) cliConsola.fatal("YOLO mode enabled - skipping checks. Things may break!");
|
|
7613
6583
|
let currentPathInput;
|
|
7614
6584
|
if (isSilent()) currentPathInput = yield* Result.await(resolveProjectNameForSilent(input));
|
|
@@ -7670,7 +6640,7 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
7670
6640
|
const templateName = input.template.toUpperCase();
|
|
7671
6641
|
const templateDescription = getTemplateDescription(input.template);
|
|
7672
6642
|
if (!isSilent()) {
|
|
7673
|
-
log.message(pc.bold(
|
|
6643
|
+
log.message(pc.bold(cliColors.bright(`Using template: ${pc.white(templateName)}`)));
|
|
7674
6644
|
log.message(pc.dim(` ${templateDescription}`));
|
|
7675
6645
|
}
|
|
7676
6646
|
const userOverrides = {};
|
|
@@ -7753,7 +6723,7 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
7753
6723
|
log.success(pc.green("Dry run validation passed. No files were written."));
|
|
7754
6724
|
log.message(pc.dim(`Target directory: ${finalResolvedPath}`));
|
|
7755
6725
|
log.message(pc.dim(`Run without --dry-run to create the project.`));
|
|
7756
|
-
outro(
|
|
6726
|
+
outro(cliColors.signal("Dry run complete."));
|
|
7757
6727
|
}
|
|
7758
6728
|
return Result.ok({
|
|
7759
6729
|
success: true,
|
|
@@ -7769,14 +6739,14 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
7769
6739
|
manualDb: cliInput.manualDb ?? input.manualDb,
|
|
7770
6740
|
dbSetupOptions: effectiveDbSetupOptions
|
|
7771
6741
|
}));
|
|
7772
|
-
if (!isSilent()) log.success(
|
|
6742
|
+
if (!isSilent()) log.success(cliColors.bright(`You can reproduce this setup with the following command:\n${reproducibleCommand}`));
|
|
7773
6743
|
await trackProjectCreation(config, input.disableAnalytics);
|
|
7774
6744
|
const historyResult = await addToHistory(config, reproducibleCommand);
|
|
7775
6745
|
if (historyResult.isErr() && !isSilent()) log.warn(pc.yellow(historyResult.error.message));
|
|
7776
6746
|
const elapsedTimeMs = Date.now() - startTime;
|
|
7777
6747
|
if (!isSilent()) {
|
|
7778
6748
|
const elapsedTimeInSeconds = (elapsedTimeMs / 1e3).toFixed(2);
|
|
7779
|
-
outro(
|
|
6749
|
+
outro(cliColors.signal(`Project created successfully in ${pc.bold(elapsedTimeInSeconds)} seconds!`));
|
|
7780
6750
|
}
|
|
7781
6751
|
return Result.ok({
|
|
7782
6752
|
success: true,
|
|
@@ -8085,6 +7055,8 @@ async function builder() {
|
|
|
8085
7055
|
* ```
|
|
8086
7056
|
*/
|
|
8087
7057
|
async function createVirtual(options) {
|
|
7058
|
+
const rawPayments = options.payments;
|
|
7059
|
+
const rawObservability = options.observability;
|
|
8088
7060
|
const config = {
|
|
8089
7061
|
projectName: options.projectName || "my-project",
|
|
8090
7062
|
projectDir: "/virtual",
|
|
@@ -8100,8 +7072,8 @@ async function createVirtual(options) {
|
|
|
8100
7072
|
examples: options.examples || [],
|
|
8101
7073
|
testing: options.testing || [],
|
|
8102
7074
|
auth: options.auth || "none",
|
|
8103
|
-
payments:
|
|
8104
|
-
observability: options.disableObservability ? [] :
|
|
7075
|
+
payments: rawPayments === "none" ? [] : Array.isArray(rawPayments) ? rawPayments : rawPayments ? [rawPayments] : [],
|
|
7076
|
+
observability: options.disableObservability ? [] : rawObservability === "none" ? [] : Array.isArray(rawObservability) ? rawObservability : rawObservability ? [rawObservability] : [],
|
|
8105
7077
|
communication: options.communication || "none",
|
|
8106
7078
|
git: options.git ?? false,
|
|
8107
7079
|
packageManager: options.packageManager || "bun",
|