create-kubojs 0.2.0 → 0.2.2
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-CAMyqVYx.mjs} +462 -1461
- package/package.json +3 -3
|
@@ -11,18 +11,61 @@ 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 TRUECOLOR_COLORTERM = new Set(["24bit", "truecolor"]);
|
|
34
|
+
function parseForceColor(forceColor) {
|
|
35
|
+
if (forceColor === void 0 || forceColor === "") return void 0;
|
|
36
|
+
if (forceColor === "true") return 1;
|
|
37
|
+
const parsed = Number.parseInt(forceColor, 10);
|
|
38
|
+
return Number.isNaN(parsed) ? 1 : parsed;
|
|
39
|
+
}
|
|
40
|
+
function supportsAnsiColor(env = process.env, terminal = process.stdout) {
|
|
41
|
+
if (env.NO_COLOR !== void 0) return false;
|
|
42
|
+
const forceColor = parseForceColor(env.FORCE_COLOR);
|
|
43
|
+
if (forceColor === 0) return false;
|
|
44
|
+
if (forceColor !== void 0) return true;
|
|
45
|
+
return terminal.isTTY === true;
|
|
46
|
+
}
|
|
47
|
+
function supportsTrueColor(env = process.env, terminal = process.stdout) {
|
|
48
|
+
if (!supportsAnsiColor(env, terminal)) return false;
|
|
49
|
+
const forceColor = parseForceColor(env.FORCE_COLOR);
|
|
50
|
+
if (forceColor !== void 0) return forceColor >= 3;
|
|
51
|
+
const colorTerm = env.COLORTERM?.toLowerCase();
|
|
52
|
+
if (colorTerm !== void 0 && TRUECOLOR_COLORTERM.has(colorTerm)) return true;
|
|
53
|
+
return env.TERM?.toLowerCase().includes("direct") === true;
|
|
54
|
+
}
|
|
55
|
+
function createColor(hex, fallback) {
|
|
56
|
+
if (!supportsTrueColor()) return fallback;
|
|
57
|
+
const red = Number.parseInt(hex.slice(1, 3), 16);
|
|
58
|
+
const green = Number.parseInt(hex.slice(3, 5), 16);
|
|
59
|
+
const blue = Number.parseInt(hex.slice(5, 7), 16);
|
|
60
|
+
return (text) => `\u001B[38;2;${red};${green};${blue}m${text}\u001B[39m`;
|
|
61
|
+
}
|
|
62
|
+
const cliColors = {
|
|
63
|
+
signal: createColor(KUBO_COLORS.signal, pc.yellow),
|
|
64
|
+
bright: createColor(KUBO_COLORS.bright, pc.yellow),
|
|
65
|
+
orange: createColor(KUBO_COLORS.orange, pc.yellow),
|
|
66
|
+
cream: createColor(KUBO_COLORS.cream, pc.white)
|
|
67
|
+
};
|
|
68
|
+
//#endregion
|
|
26
69
|
//#region src/utils/get-package-manager.ts
|
|
27
70
|
const getUserPkgManager = () => {
|
|
28
71
|
const userAgent = process.env.npm_config_user_agent;
|
|
@@ -42,7 +85,7 @@ const DEFAULT_CONFIG_BASE = {
|
|
|
42
85
|
database: "sqlite",
|
|
43
86
|
orm: "drizzle",
|
|
44
87
|
auth: "better-auth",
|
|
45
|
-
payments:
|
|
88
|
+
payments: [],
|
|
46
89
|
observability: ["getmonitor"],
|
|
47
90
|
communication: "none",
|
|
48
91
|
addons: ["turborepo"],
|
|
@@ -63,6 +106,8 @@ function getDefaultConfig() {
|
|
|
63
106
|
projectDir: path.resolve(process.cwd(), DEFAULT_CONFIG_BASE.projectName),
|
|
64
107
|
packageManager: getUserPkgManager(),
|
|
65
108
|
frontend: [...DEFAULT_CONFIG_BASE.frontend],
|
|
109
|
+
payments: [...DEFAULT_CONFIG_BASE.payments],
|
|
110
|
+
observability: [...DEFAULT_CONFIG_BASE.observability],
|
|
66
111
|
addons: [...DEFAULT_CONFIG_BASE.addons],
|
|
67
112
|
examples: [...DEFAULT_CONFIG_BASE.examples],
|
|
68
113
|
testing: [...DEFAULT_CONFIG_BASE.testing]
|
|
@@ -82,17 +127,12 @@ const ADDON_COMPATIBILITY = {
|
|
|
82
127
|
husky: [],
|
|
83
128
|
lefthook: [],
|
|
84
129
|
turborepo: [],
|
|
85
|
-
nx: [],
|
|
86
130
|
"vite-plus": [],
|
|
87
|
-
starlight: [],
|
|
88
|
-
ultracite: [],
|
|
89
131
|
mcp: [],
|
|
90
132
|
oxlint: [],
|
|
91
|
-
fumadocs: [],
|
|
92
133
|
opentui: [],
|
|
93
|
-
wxt: [],
|
|
94
134
|
skills: [],
|
|
95
|
-
|
|
135
|
+
"s3-storage": [],
|
|
96
136
|
none: []
|
|
97
137
|
};
|
|
98
138
|
//#endregion
|
|
@@ -429,24 +469,28 @@ const TITLE_TEXT = `
|
|
|
429
469
|
██║ ██╗╚██████╔╝██████╔╝╚██████╔╝
|
|
430
470
|
╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝
|
|
431
471
|
`;
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
472
|
+
const KUBO_TITLE_COLORS = [
|
|
473
|
+
"#D6A72B",
|
|
474
|
+
"#F5D76E",
|
|
475
|
+
"#E0B43E",
|
|
476
|
+
"#E08A2E",
|
|
477
|
+
"#E0B43E",
|
|
478
|
+
"#E8A43A",
|
|
479
|
+
"#E08A2E",
|
|
480
|
+
"#E08A2E",
|
|
481
|
+
"#C49314",
|
|
482
|
+
"#E8C978",
|
|
483
|
+
"#E5D3A5"
|
|
484
|
+
];
|
|
445
485
|
const renderTitle = () => {
|
|
446
486
|
const terminalWidth = process.stdout.columns || 80;
|
|
447
487
|
const titleLines = TITLE_TEXT.split("\n");
|
|
448
|
-
|
|
449
|
-
|
|
488
|
+
const title = terminalWidth < Math.max(...titleLines.map((line) => line.length)) ? "Kubo" : TITLE_TEXT;
|
|
489
|
+
if (supportsTrueColor()) {
|
|
490
|
+
console.log(gradient([...KUBO_TITLE_COLORS]).multiline(title));
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
console.log(pc.yellow(title));
|
|
450
494
|
};
|
|
451
495
|
//#endregion
|
|
452
496
|
//#region src/commands/history.ts
|
|
@@ -493,10 +537,10 @@ async function historyHandler(input) {
|
|
|
493
537
|
return;
|
|
494
538
|
}
|
|
495
539
|
renderTitle();
|
|
496
|
-
intro(
|
|
540
|
+
intro(cliColors.signal(`Project History (${entries.length} entries)`));
|
|
497
541
|
for (const [index, entry] of entries.entries()) {
|
|
498
542
|
const num = pc.dim(`${index + 1}.`);
|
|
499
|
-
const name =
|
|
543
|
+
const name = cliColors.bright(pc.bold(entry.projectName));
|
|
500
544
|
const stack = pc.dim(formatStackSummary(entry));
|
|
501
545
|
log.message(`${num} ${name}`);
|
|
502
546
|
log.message(` ${pc.dim("Created:")} ${formatDate(entry.createdAt)}`);
|
|
@@ -529,7 +573,7 @@ async function openExternalUrl(url, successMessage) {
|
|
|
529
573
|
if ((await Result.tryPromise({
|
|
530
574
|
try: () => openUrl(url),
|
|
531
575
|
catch: () => null
|
|
532
|
-
})).isOk()) log.success(
|
|
576
|
+
})).isOk()) log.success(cliColors.signal(successMessage));
|
|
533
577
|
else log.message(`Please visit ${url}`);
|
|
534
578
|
}
|
|
535
579
|
async function openDocsCommand() {
|
|
@@ -557,17 +601,9 @@ const WEB_FRAMEWORKS = [
|
|
|
557
601
|
];
|
|
558
602
|
//#endregion
|
|
559
603
|
//#region src/utils/compatibility-rules.ts
|
|
560
|
-
const TASK_RUNNER_ADDONS$1 = [
|
|
561
|
-
"turborepo",
|
|
562
|
-
"nx",
|
|
563
|
-
"vite-plus"
|
|
564
|
-
];
|
|
604
|
+
const TASK_RUNNER_ADDONS$1 = ["turborepo", "vite-plus"];
|
|
565
605
|
/** Mutually exclusive code-quality linters (one slot). */
|
|
566
|
-
const LINTER_ADDONS = [
|
|
567
|
-
"biome",
|
|
568
|
-
"oxlint",
|
|
569
|
-
"ultracite"
|
|
570
|
-
];
|
|
606
|
+
const LINTER_ADDONS = ["biome", "oxlint"];
|
|
571
607
|
const STATIC_DESKTOP_ADDONS = ["tauri", "electrobun"];
|
|
572
608
|
function isLinterAddon(addon) {
|
|
573
609
|
return LINTER_ADDONS.includes(addon);
|
|
@@ -639,23 +675,6 @@ const FULLSTACK_FRONTENDS$1 = [
|
|
|
639
675
|
"svelte",
|
|
640
676
|
"astro"
|
|
641
677
|
];
|
|
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
678
|
function validateSelfBackendCompatibility(providedFlags, options, config) {
|
|
660
679
|
const backend = config.backend || options.backend;
|
|
661
680
|
const frontends = config.frontend || options.frontend || [];
|
|
@@ -701,7 +720,7 @@ function isFrontendAllowedWithBackend(frontend, backend, auth) {
|
|
|
701
720
|
function supportsConvexBetterAuth(frontends = []) {
|
|
702
721
|
return frontends.some((frontend) => CONVEX_BETTER_AUTH_SUPPORTED_FRONTENDS.includes(frontend));
|
|
703
722
|
}
|
|
704
|
-
function allowedApisForFrontends(frontends = []) {
|
|
723
|
+
function allowedApisForFrontends(frontends = [], backend) {
|
|
705
724
|
const includesNuxt = frontends.includes("nuxt");
|
|
706
725
|
const includesSvelte = frontends.includes("svelte");
|
|
707
726
|
const includesSolid = frontends.includes("solid");
|
|
@@ -711,9 +730,17 @@ function allowedApisForFrontends(frontends = []) {
|
|
|
711
730
|
"orpc",
|
|
712
731
|
"none"
|
|
713
732
|
];
|
|
714
|
-
if (
|
|
733
|
+
if (backend === "hono") base.splice(2, 0, "orval");
|
|
734
|
+
if (includesNuxt || includesSvelte || includesSolid || includesAstro) return backend === "hono" ? [
|
|
735
|
+
"orpc",
|
|
736
|
+
"orval",
|
|
737
|
+
"none"
|
|
738
|
+
] : ["orpc", "none"];
|
|
715
739
|
return base;
|
|
716
740
|
}
|
|
741
|
+
function isApiCompatibleWithBackend(api, backend) {
|
|
742
|
+
return api !== "orval" || backend === "hono";
|
|
743
|
+
}
|
|
717
744
|
function isExampleTodoAllowed(backend, database, api) {
|
|
718
745
|
if (backend === "convex") return true;
|
|
719
746
|
if (database === "none" || api === "none") return false;
|
|
@@ -773,10 +800,6 @@ function validateDockerWebDeployDesktopAddons(webDeploy, addons, frontend, backe
|
|
|
773
800
|
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
801
|
}
|
|
775
802
|
function validateAddonCompatibility(addon, frontend, auth, backend, runtime) {
|
|
776
|
-
if (addon === "evlog" && !supportsEvlogAddon(frontend, backend, runtime)) return {
|
|
777
|
-
isCompatible: false,
|
|
778
|
-
reason: evlogCompatibilityMessage
|
|
779
|
-
};
|
|
780
803
|
if (STATIC_DESKTOP_ADDONS.includes(addon) && auth === "clerk" && frontend.includes("react-router")) return {
|
|
781
804
|
isCompatible: false,
|
|
782
805
|
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 +812,13 @@ function validateAddonCompatibility(addon, frontend, auth, backend, runtime) {
|
|
|
789
812
|
isCompatible: false,
|
|
790
813
|
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
814
|
};
|
|
815
|
+
if (!(addon in ADDON_COMPATIBILITY)) return {
|
|
816
|
+
isCompatible: false,
|
|
817
|
+
reason: `${addon} is not a supported addon`
|
|
818
|
+
};
|
|
792
819
|
const compatibleFrontends = ADDON_COMPATIBILITY[addon];
|
|
793
820
|
if (compatibleFrontends.length > 0) {
|
|
794
|
-
if (!frontend.some((f) => compatibleFrontends.
|
|
821
|
+
if (!frontend.some((f) => compatibleFrontends.some((compatibleFrontend) => compatibleFrontend === f))) return {
|
|
795
822
|
isCompatible: false,
|
|
796
823
|
reason: `${addon} addon requires one of these frontends: ${compatibleFrontends.join(", ")}`
|
|
797
824
|
};
|
|
@@ -808,8 +835,8 @@ function getCompatibleAddons(allAddons, frontend, existingAddons = [], auth, bac
|
|
|
808
835
|
});
|
|
809
836
|
}
|
|
810
837
|
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'
|
|
838
|
+
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.");
|
|
839
|
+
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
840
|
for (const addon of addons) {
|
|
814
841
|
if (addon === "none") continue;
|
|
815
842
|
const { isCompatible, reason } = validateAddonCompatibility(addon, frontends, auth, backend, runtime);
|
|
@@ -822,21 +849,29 @@ function validateAddonsAgainstConfig(addons = [], config) {
|
|
|
822
849
|
}
|
|
823
850
|
function validateCommunicationCompatibility(communication, backend) {
|
|
824
851
|
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'.`);
|
|
852
|
+
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'.`);
|
|
853
|
+
return Result.ok(void 0);
|
|
854
|
+
}
|
|
855
|
+
function validateAraraRuntimeCompatibility(communication, backend, runtime, serverDeploy) {
|
|
856
|
+
if (communication !== "arara" || backend === "convex") return Result.ok(void 0);
|
|
857
|
+
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
858
|
return Result.ok(void 0);
|
|
827
859
|
}
|
|
828
860
|
function validatePaymentsCompatibility(payments, auth, backend, frontends = [], orm, database) {
|
|
829
|
-
if (!payments || payments ===
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
if (
|
|
861
|
+
if (!payments || payments.length === 0) return Result.ok(void 0);
|
|
862
|
+
for (const provider of payments) {
|
|
863
|
+
const issue = getPaymentCompatibilityIssue({
|
|
864
|
+
provider,
|
|
865
|
+
backend,
|
|
866
|
+
frontends,
|
|
867
|
+
database,
|
|
868
|
+
orm
|
|
869
|
+
});
|
|
870
|
+
const label = provider === "stripe" ? "Stripe" : "AbacatePay";
|
|
871
|
+
if (issue === "convex-unsupported") return validationErr$1(`${label} payments is not compatible with '--backend convex'. Please use a server backend or backend 'self'.`);
|
|
872
|
+
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.`);
|
|
873
|
+
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.`);
|
|
874
|
+
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
875
|
}
|
|
841
876
|
return Result.ok(void 0);
|
|
842
877
|
}
|
|
@@ -871,12 +906,12 @@ function isGoBack(value) {
|
|
|
871
906
|
* These prompts return GO_BACK_SYMBOL when 'b' is pressed (instead of canceling)
|
|
872
907
|
*/
|
|
873
908
|
const unicode = process.platform !== "win32";
|
|
874
|
-
const S_STEP_ACTIVE = unicode ? "◆" : "*";
|
|
909
|
+
const S_STEP_ACTIVE$1 = unicode ? "◆" : "*";
|
|
875
910
|
const S_STEP_CANCEL = unicode ? "■" : "x";
|
|
876
911
|
const S_STEP_ERROR = unicode ? "▲" : "x";
|
|
877
912
|
const S_STEP_SUBMIT = unicode ? "◇" : "o";
|
|
878
|
-
const S_BAR = unicode ? "│" : "|";
|
|
879
|
-
const S_BAR_END = unicode ? "└" : "—";
|
|
913
|
+
const S_BAR$1 = unicode ? "│" : "|";
|
|
914
|
+
const S_BAR_END$1 = unicode ? "└" : "—";
|
|
880
915
|
const S_RADIO_ACTIVE = unicode ? "●" : ">";
|
|
881
916
|
const S_RADIO_INACTIVE = unicode ? "○" : " ";
|
|
882
917
|
const S_CHECKBOX_ACTIVE = unicode ? "◻" : "[•]";
|
|
@@ -885,10 +920,10 @@ const S_CHECKBOX_INACTIVE = unicode ? "◻" : "[ ]";
|
|
|
885
920
|
function symbol(state) {
|
|
886
921
|
switch (state) {
|
|
887
922
|
case "initial":
|
|
888
|
-
case "active": return
|
|
923
|
+
case "active": return cliColors.orange(S_STEP_ACTIVE$1);
|
|
889
924
|
case "cancel": return pc.red(S_STEP_CANCEL);
|
|
890
925
|
case "error": return pc.yellow(S_STEP_ERROR);
|
|
891
|
-
case "submit": return
|
|
926
|
+
case "submit": return cliColors.signal(S_STEP_SUBMIT);
|
|
892
927
|
}
|
|
893
928
|
}
|
|
894
929
|
const KEYBOARD_HINT = pc.dim(`${pc.gray("↑/↓")} navigate • ${pc.gray("enter")} confirm • ${pc.gray("b")} back • ${pc.gray("ctrl+c")} cancel`);
|
|
@@ -905,6 +940,12 @@ function getMultiHint() {
|
|
|
905
940
|
function normalizeValidationMessage(validationMessage) {
|
|
906
941
|
return validationMessage instanceof Error ? validationMessage.message : validationMessage;
|
|
907
942
|
}
|
|
943
|
+
function formatKeycap(key) {
|
|
944
|
+
return pc.bold(`<${key}>`);
|
|
945
|
+
}
|
|
946
|
+
function getRequiredMultiselectMessage() {
|
|
947
|
+
return `Please select at least one option.\n${pc.dim(`Press ${formatKeycap("space")} to select, ${formatKeycap("enter")} to submit`)}`;
|
|
948
|
+
}
|
|
908
949
|
async function runWithNavigation(prompt) {
|
|
909
950
|
let goBack = false;
|
|
910
951
|
prompt.on("key", (char) => {
|
|
@@ -923,7 +964,7 @@ async function navigableSelect(opts) {
|
|
|
923
964
|
switch (state) {
|
|
924
965
|
case "disabled": return `${pc.gray(S_RADIO_INACTIVE)} ${pc.gray(label)}${option.hint ? ` ${pc.dim(`(${option.hint ?? "disabled"})`)}` : ""}`;
|
|
925
966
|
case "selected": return `${pc.dim(label)}`;
|
|
926
|
-
case "active": return `${
|
|
967
|
+
case "active": return `${cliColors.bright(S_RADIO_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
927
968
|
case "cancelled": return `${pc.strikethrough(pc.dim(label))}`;
|
|
928
969
|
default: return `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(label)}`;
|
|
929
970
|
}
|
|
@@ -932,14 +973,14 @@ async function navigableSelect(opts) {
|
|
|
932
973
|
options: opts.options,
|
|
933
974
|
initialValue: opts.initialValue,
|
|
934
975
|
render() {
|
|
935
|
-
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
976
|
+
const title = `${pc.gray(S_BAR$1)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
936
977
|
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)}`;
|
|
978
|
+
case "submit": return `${title}${pc.gray(S_BAR$1)} ${opt(this.options[this.cursor], "selected")}`;
|
|
979
|
+
case "cancel": return `${title}${pc.gray(S_BAR$1)} ${opt(this.options[this.cursor], "cancelled")}\n${pc.gray(S_BAR$1)}`;
|
|
939
980
|
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}${
|
|
981
|
+
const optionsText = this.options.map((option, i) => opt(option, option.disabled ? "disabled" : i === this.cursor ? "active" : "inactive")).join(`\n${cliColors.signal(S_BAR$1)} `);
|
|
982
|
+
const hint = `\n${pc.gray(S_BAR$1)} ${getHint()}`;
|
|
983
|
+
return `${title}${cliColors.signal(S_BAR$1)} ${optionsText}\n${cliColors.signal(S_BAR_END$1)}${hint}\n`;
|
|
943
984
|
}
|
|
944
985
|
}
|
|
945
986
|
}
|
|
@@ -950,10 +991,10 @@ async function navigableMultiselect(opts) {
|
|
|
950
991
|
const opt = (option, state) => {
|
|
951
992
|
const label = option.label ?? String(option.value);
|
|
952
993
|
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 `${
|
|
994
|
+
if (state === "active") return `${cliColors.orange(S_CHECKBOX_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
995
|
+
if (state === "selected") return `${cliColors.bright(S_CHECKBOX_SELECTED)} ${pc.dim(label)}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
955
996
|
if (state === "cancelled") return `${pc.strikethrough(pc.dim(label))}`;
|
|
956
|
-
if (state === "active-selected") return `${
|
|
997
|
+
if (state === "active-selected") return `${cliColors.bright(S_CHECKBOX_SELECTED)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
957
998
|
if (state === "submitted") return `${pc.dim(label)}`;
|
|
958
999
|
return `${pc.dim(S_CHECKBOX_INACTIVE)} ${pc.dim(label)}`;
|
|
959
1000
|
};
|
|
@@ -962,11 +1003,11 @@ async function navigableMultiselect(opts) {
|
|
|
962
1003
|
initialValues: opts.initialValues,
|
|
963
1004
|
required,
|
|
964
1005
|
validate(selected) {
|
|
965
|
-
if (required && (selected === void 0 || selected.length === 0)) return
|
|
1006
|
+
if (required && (selected === void 0 || selected.length === 0)) return getRequiredMultiselectMessage();
|
|
966
1007
|
return normalizeValidationMessage(opts.validate?.(selected));
|
|
967
1008
|
},
|
|
968
1009
|
render() {
|
|
969
|
-
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1010
|
+
const title = `${pc.gray(S_BAR$1)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
970
1011
|
const value = this.value ?? [];
|
|
971
1012
|
const styleOption = (option, active) => {
|
|
972
1013
|
if (option.disabled) return opt(option, "disabled");
|
|
@@ -978,21 +1019,21 @@ async function navigableMultiselect(opts) {
|
|
|
978
1019
|
switch (this.state) {
|
|
979
1020
|
case "submit": {
|
|
980
1021
|
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}`;
|
|
1022
|
+
return `${title}${pc.gray(S_BAR$1)} ${submitText}`;
|
|
982
1023
|
}
|
|
983
1024
|
case "cancel": {
|
|
984
1025
|
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)}`;
|
|
1026
|
+
return `${title}${pc.gray(S_BAR$1)} ${label}\n${pc.gray(S_BAR$1)}`;
|
|
986
1027
|
}
|
|
987
1028
|
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`;
|
|
1029
|
+
const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc.yellow(S_BAR_END$1)} ${pc.yellow(ln)}` : ` ${ln}`).join("\n");
|
|
1030
|
+
const optionsText = this.options.map((option, i) => styleOption(option, i === this.cursor)).join(`\n${pc.yellow(S_BAR$1)} `);
|
|
1031
|
+
return `${title}${pc.yellow(S_BAR$1)} ${optionsText}\n${footer}\n`;
|
|
991
1032
|
}
|
|
992
1033
|
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}${
|
|
1034
|
+
const optionsText = this.options.map((option, i) => styleOption(option, i === this.cursor)).join(`\n${cliColors.signal(S_BAR$1)} `);
|
|
1035
|
+
const hint = `\n${pc.gray(S_BAR$1)} ${getMultiHint()}`;
|
|
1036
|
+
return `${title}${cliColors.signal(S_BAR$1)} ${optionsText}\n${cliColors.signal(S_BAR_END$1)}${hint}\n`;
|
|
996
1037
|
}
|
|
997
1038
|
}
|
|
998
1039
|
}
|
|
@@ -1006,14 +1047,14 @@ async function navigableConfirm(opts) {
|
|
|
1006
1047
|
inactive,
|
|
1007
1048
|
initialValue: opts.initialValue ?? true,
|
|
1008
1049
|
render() {
|
|
1009
|
-
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1050
|
+
const title = `${pc.gray(S_BAR$1)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1010
1051
|
const value = this.value ? active : inactive;
|
|
1011
1052
|
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)}`;
|
|
1053
|
+
case "submit": return `${title}${pc.gray(S_BAR$1)} ${pc.dim(value)}`;
|
|
1054
|
+
case "cancel": return `${title}${pc.gray(S_BAR$1)} ${pc.strikethrough(pc.dim(value))}\n${pc.gray(S_BAR$1)}`;
|
|
1014
1055
|
default: {
|
|
1015
|
-
const hint = `\n${pc.gray(S_BAR)} ${getHint()}`;
|
|
1016
|
-
return `${title}${
|
|
1056
|
+
const hint = `\n${pc.gray(S_BAR$1)} ${getHint()}`;
|
|
1057
|
+
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
1058
|
}
|
|
1018
1059
|
}
|
|
1019
1060
|
}
|
|
@@ -1026,16 +1067,16 @@ async function navigableGroupMultiselect(opts) {
|
|
|
1026
1067
|
const isItem = typeof option.group === "string";
|
|
1027
1068
|
const next = isItem && (options[options.indexOf(option) + 1] ?? { group: true });
|
|
1028
1069
|
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}${
|
|
1070
|
+
const prefix = isItem ? `${isLast ? S_BAR_END$1 : S_BAR$1} ` : "";
|
|
1071
|
+
if (state === "active") return `${pc.dim(prefix)}${cliColors.orange(S_CHECKBOX_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
1072
|
+
if (state === "group-active") return `${prefix}${cliColors.orange(S_CHECKBOX_ACTIVE)} ${pc.dim(label)}`;
|
|
1073
|
+
if (state === "group-active-selected") return `${prefix}${cliColors.bright(S_CHECKBOX_SELECTED)} ${pc.dim(label)}`;
|
|
1033
1074
|
if (state === "selected") {
|
|
1034
|
-
const selectedCheckbox = isItem ?
|
|
1075
|
+
const selectedCheckbox = isItem ? cliColors.bright(S_CHECKBOX_SELECTED) : "";
|
|
1035
1076
|
return `${pc.dim(prefix)}${selectedCheckbox} ${pc.dim(label)}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
1036
1077
|
}
|
|
1037
1078
|
if (state === "cancelled") return `${pc.strikethrough(pc.dim(label))}`;
|
|
1038
|
-
if (state === "active-selected") return `${pc.dim(prefix)}${
|
|
1079
|
+
if (state === "active-selected") return `${pc.dim(prefix)}${cliColors.bright(S_CHECKBOX_SELECTED)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : ""}`;
|
|
1039
1080
|
if (state === "submitted") return `${pc.dim(label)}`;
|
|
1040
1081
|
const unselectedCheckbox = isItem ? pc.dim(S_CHECKBOX_INACTIVE) : "";
|
|
1041
1082
|
return `${pc.dim(prefix)}${unselectedCheckbox} ${pc.dim(label)}`;
|
|
@@ -1046,24 +1087,24 @@ async function navigableGroupMultiselect(opts) {
|
|
|
1046
1087
|
required,
|
|
1047
1088
|
selectableGroups: true,
|
|
1048
1089
|
validate(selected) {
|
|
1049
|
-
if (required && (selected === void 0 || selected.length === 0)) return
|
|
1090
|
+
if (required && (selected === void 0 || selected.length === 0)) return getRequiredMultiselectMessage();
|
|
1050
1091
|
return normalizeValidationMessage(opts.validate?.(selected));
|
|
1051
1092
|
},
|
|
1052
1093
|
render() {
|
|
1053
|
-
const title = `${pc.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1094
|
+
const title = `${pc.gray(S_BAR$1)}\n${symbol(this.state)} ${opts.message}\n`;
|
|
1054
1095
|
const value = this.value ?? [];
|
|
1055
1096
|
switch (this.state) {
|
|
1056
1097
|
case "submit": {
|
|
1057
1098
|
const selectedOptions = this.options.filter(({ value: optionValue }) => value.includes(optionValue)).map((option) => opt(option, "submitted"));
|
|
1058
1099
|
const optionsText = selectedOptions.length === 0 ? "" : ` ${selectedOptions.join(pc.dim(", "))}`;
|
|
1059
|
-
return `${title}${pc.gray(S_BAR)}${optionsText}`;
|
|
1100
|
+
return `${title}${pc.gray(S_BAR$1)}${optionsText}`;
|
|
1060
1101
|
}
|
|
1061
1102
|
case "cancel": {
|
|
1062
1103
|
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)}` : ""}`;
|
|
1104
|
+
return `${title}${pc.gray(S_BAR$1)} ${label.trim() ? `${label}\n${pc.gray(S_BAR$1)}` : ""}`;
|
|
1064
1105
|
}
|
|
1065
1106
|
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");
|
|
1107
|
+
const footer = this.error.split("\n").map((ln, i) => i === 0 ? `${pc.yellow(S_BAR_END$1)} ${pc.yellow(ln)}` : ` ${ln}`).join("\n");
|
|
1067
1108
|
const optionsText = this.options.map((option, i, options) => {
|
|
1068
1109
|
const selected = value.includes(option.value) || option.group === true && this.isGroupSelected(`${option.value}`);
|
|
1069
1110
|
const active = i === this.cursor;
|
|
@@ -1071,8 +1112,8 @@ async function navigableGroupMultiselect(opts) {
|
|
|
1071
1112
|
if (active && selected) return opt(option, "active-selected", options);
|
|
1072
1113
|
if (selected) return opt(option, "selected", options);
|
|
1073
1114
|
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`;
|
|
1115
|
+
}).join(`\n${pc.yellow(S_BAR$1)} `);
|
|
1116
|
+
return `${title}${pc.yellow(S_BAR$1)} ${optionsText}\n${footer}\n`;
|
|
1076
1117
|
}
|
|
1077
1118
|
default: {
|
|
1078
1119
|
const optionsText = this.options.map((option, i, options) => {
|
|
@@ -1085,10 +1126,10 @@ async function navigableGroupMultiselect(opts) {
|
|
|
1085
1126
|
else if (selected) optionText = opt(option, "selected", options);
|
|
1086
1127
|
else optionText = opt(option, active ? "active" : "inactive", options);
|
|
1087
1128
|
return `${i !== 0 && !optionText.startsWith("\n") ? " " : ""}${optionText}`;
|
|
1088
|
-
}).join(`\n${
|
|
1129
|
+
}).join(`\n${cliColors.signal(S_BAR$1)}`);
|
|
1089
1130
|
const optionsPrefix = optionsText.startsWith("\n") ? "" : " ";
|
|
1090
|
-
const hint = `\n${pc.gray(S_BAR)} ${getMultiHint()}`;
|
|
1091
|
-
return `${title}${
|
|
1131
|
+
const hint = `\n${pc.gray(S_BAR$1)} ${getMultiHint()}`;
|
|
1132
|
+
return `${title}${cliColors.signal(S_BAR$1)}${optionsPrefix}${optionsText}\n${cliColors.signal(S_BAR_END$1)}${hint}\n`;
|
|
1092
1133
|
}
|
|
1093
1134
|
}
|
|
1094
1135
|
}
|
|
@@ -1108,10 +1149,6 @@ function getAddonDisplay(addon) {
|
|
|
1108
1149
|
label = "Turborepo";
|
|
1109
1150
|
hint = "High-performance build system";
|
|
1110
1151
|
break;
|
|
1111
|
-
case "nx":
|
|
1112
|
-
label = "Nx";
|
|
1113
|
-
hint = "Smart monorepo orchestration and task graph";
|
|
1114
|
-
break;
|
|
1115
1152
|
case "vite-plus":
|
|
1116
1153
|
label = "Vite+";
|
|
1117
1154
|
hint = "Unified Vite toolchain and workspace task runner";
|
|
@@ -1136,10 +1173,6 @@ function getAddonDisplay(addon) {
|
|
|
1136
1173
|
label = "Oxlint";
|
|
1137
1174
|
hint = "Oxlint + Oxfmt (linting & formatting)";
|
|
1138
1175
|
break;
|
|
1139
|
-
case "ultracite":
|
|
1140
|
-
label = "Ultracite";
|
|
1141
|
-
hint = "Zero-config preset for Biome or Oxlint with AI integration";
|
|
1142
|
-
break;
|
|
1143
1176
|
case "lefthook":
|
|
1144
1177
|
label = "Lefthook";
|
|
1145
1178
|
hint = "Fast and powerful Git hooks manager";
|
|
@@ -1148,22 +1181,10 @@ function getAddonDisplay(addon) {
|
|
|
1148
1181
|
label = "Husky";
|
|
1149
1182
|
hint = "Modern native Git hooks made easy";
|
|
1150
1183
|
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
1184
|
case "opentui":
|
|
1160
1185
|
label = "OpenTUI";
|
|
1161
1186
|
hint = "Build terminal user interfaces";
|
|
1162
1187
|
break;
|
|
1163
|
-
case "wxt":
|
|
1164
|
-
label = "WXT";
|
|
1165
|
-
hint = "Build browser extensions";
|
|
1166
|
-
break;
|
|
1167
1188
|
case "skills":
|
|
1168
1189
|
label = "Skills";
|
|
1169
1190
|
hint = "AI coding agent skills for your stack";
|
|
@@ -1172,9 +1193,9 @@ function getAddonDisplay(addon) {
|
|
|
1172
1193
|
label = "MCP";
|
|
1173
1194
|
hint = "Install MCP servers, including kubojs, via add-mcp";
|
|
1174
1195
|
break;
|
|
1175
|
-
case "
|
|
1176
|
-
label = "
|
|
1177
|
-
hint = "
|
|
1196
|
+
case "s3-storage":
|
|
1197
|
+
label = "S3-compatible Storage";
|
|
1198
|
+
hint = "Add object storage for AWS S3, Cloudflare R2, MinIO, or Backblaze";
|
|
1178
1199
|
break;
|
|
1179
1200
|
default:
|
|
1180
1201
|
label = addon;
|
|
@@ -1186,27 +1207,20 @@ function getAddonDisplay(addon) {
|
|
|
1186
1207
|
};
|
|
1187
1208
|
}
|
|
1188
1209
|
const ADDON_GROUPS = {
|
|
1189
|
-
"Monorepo & Tasks": [
|
|
1190
|
-
"turborepo",
|
|
1191
|
-
"nx",
|
|
1192
|
-
"vite-plus"
|
|
1193
|
-
],
|
|
1210
|
+
"Monorepo & Tasks": ["turborepo", "vite-plus"],
|
|
1194
1211
|
"Code Quality": [
|
|
1195
1212
|
"biome",
|
|
1196
1213
|
"oxlint",
|
|
1197
|
-
"ultracite",
|
|
1198
1214
|
"husky",
|
|
1199
1215
|
"lefthook"
|
|
1200
1216
|
],
|
|
1201
|
-
Documentation: ["starlight", "fumadocs"],
|
|
1202
1217
|
"Platform Extensions": [
|
|
1203
1218
|
"pwa",
|
|
1204
1219
|
"tauri",
|
|
1205
1220
|
"electrobun",
|
|
1206
1221
|
"opentui",
|
|
1207
|
-
"
|
|
1222
|
+
"s3-storage"
|
|
1208
1223
|
],
|
|
1209
|
-
Observability: ["evlog"],
|
|
1210
1224
|
"AI & Agent Tools": ["skills", "mcp"]
|
|
1211
1225
|
};
|
|
1212
1226
|
function createGroupedOptions() {
|
|
@@ -1231,16 +1245,8 @@ function sortAndPruneGroupedOptions(groupedOptions) {
|
|
|
1231
1245
|
});
|
|
1232
1246
|
}
|
|
1233
1247
|
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.";
|
|
1248
|
+
if ((selected?.filter((addon) => ["turborepo", "vite-plus"].includes(addon)) ?? []).length > 1) return "Choose Turborepo or Vite+ as your task runner, not both.";
|
|
1249
|
+
if ((selected?.filter((addon) => ["biome", "oxlint"].includes(addon)) ?? []).length > 1) return "Choose Biome or Oxlint as your code-quality tool, not both.";
|
|
1244
1250
|
}
|
|
1245
1251
|
async function getAddonsChoice(addons, frontends, auth, backend, runtime, previousValue) {
|
|
1246
1252
|
if (addons !== void 0) return addons;
|
|
@@ -1265,7 +1271,7 @@ async function getAddonsChoice(addons, frontends, auth, backend, runtime, previo
|
|
|
1265
1271
|
required: false,
|
|
1266
1272
|
validate: validateAddonSelection
|
|
1267
1273
|
});
|
|
1268
|
-
if (isCancel$
|
|
1274
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
1269
1275
|
return response;
|
|
1270
1276
|
}
|
|
1271
1277
|
async function getAddonsToAdd(config) {
|
|
@@ -1288,7 +1294,7 @@ async function getAddonsToAdd(config) {
|
|
|
1288
1294
|
required: false,
|
|
1289
1295
|
validate: validateAddonSelection
|
|
1290
1296
|
});
|
|
1291
|
-
if (isCancel$
|
|
1297
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
1292
1298
|
return response;
|
|
1293
1299
|
}
|
|
1294
1300
|
//#endregion
|
|
@@ -1364,515 +1370,6 @@ const addPackageDependency = async (opts) => {
|
|
|
1364
1370
|
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
1365
1371
|
};
|
|
1366
1372
|
//#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
1373
|
//#region src/prompts/navigable-group.ts
|
|
1877
1374
|
/**
|
|
1878
1375
|
* Navigable group - a group of prompts that allows going back
|
|
@@ -1915,7 +1412,7 @@ async function navigableGroup(prompts, opts) {
|
|
|
1915
1412
|
goingBack = false;
|
|
1916
1413
|
continue;
|
|
1917
1414
|
}
|
|
1918
|
-
if (isCancel$
|
|
1415
|
+
if (isCancel$2(result)) {
|
|
1919
1416
|
if (typeof opts?.onCancel === "function") {
|
|
1920
1417
|
results[name] = "canceled";
|
|
1921
1418
|
opts.onCancel({ results });
|
|
@@ -2036,206 +1533,6 @@ function getPackageRunnerPrefix(packageManager) {
|
|
|
2036
1533
|
}
|
|
2037
1534
|
}
|
|
2038
1535
|
//#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
1536
|
//#region src/helpers/addons/mcp-setup.ts
|
|
2240
1537
|
const MCP_AGENTS = [
|
|
2241
1538
|
{
|
|
@@ -2310,7 +1607,7 @@ const MCP_AGENTS = [
|
|
|
2310
1607
|
}
|
|
2311
1608
|
];
|
|
2312
1609
|
const DEFAULT_SCOPE$1 = "project";
|
|
2313
|
-
const DEFAULT_AGENTS$
|
|
1610
|
+
const DEFAULT_AGENTS$1 = [
|
|
2314
1611
|
"cursor",
|
|
2315
1612
|
"claude-code",
|
|
2316
1613
|
"vscode"
|
|
@@ -2434,7 +1731,6 @@ function getAllMcpServers(config) {
|
|
|
2434
1731
|
function getRecommendedMcpServers(config, scope) {
|
|
2435
1732
|
const serversByKey = new Map(getAllMcpServers(config).map((server) => [server.key, server]));
|
|
2436
1733
|
const recommendedServerKeys = ["kubojs", "context7"];
|
|
2437
|
-
if (scope === "project" && config.addons.includes("nx")) recommendedServerKeys.push("nx");
|
|
2438
1734
|
if (config.runtime === "workers" || config.webDeploy === "cloudflare" || config.serverDeploy === "cloudflare") recommendedServerKeys.push("cloudflare-docs");
|
|
2439
1735
|
if (config.backend === "convex") recommendedServerKeys.push("convex");
|
|
2440
1736
|
if (hasReactBasedFrontend$1(config.frontend)) recommendedServerKeys.push("shadcn");
|
|
@@ -2473,7 +1769,7 @@ async function setupMcp(config) {
|
|
|
2473
1769
|
selectedServerKeys = configuredServerKeys?.filter((k) => availableServerKeys.has(k)) ?? serverOptions;
|
|
2474
1770
|
if (selectedServerKeys.length === 0) return Result.ok(void 0);
|
|
2475
1771
|
const agentOptions = filterAgentsForScope(scope);
|
|
2476
|
-
const defaultAgents = uniqueValues$1(DEFAULT_AGENTS$
|
|
1772
|
+
const defaultAgents = uniqueValues$1(DEFAULT_AGENTS$1.filter((a) => agentOptions.some((o) => o.value === a)));
|
|
2477
1773
|
selectedAgents = configuredAgents?.filter((a) => agentOptions.some((o) => o.value === a)) ?? defaultAgents;
|
|
2478
1774
|
if (selectedAgents.length === 0) return Result.ok(void 0);
|
|
2479
1775
|
} else {
|
|
@@ -2516,7 +1812,7 @@ async function setupMcp(config) {
|
|
|
2516
1812
|
if (currentServers !== void 0 && currentServers.length === 0) return [];
|
|
2517
1813
|
const agentOpts = filterAgentsForScope(currentScope);
|
|
2518
1814
|
if (agentOpts.length === 0) return [];
|
|
2519
|
-
const defaults = uniqueValues$1(DEFAULT_AGENTS$
|
|
1815
|
+
const defaults = uniqueValues$1(DEFAULT_AGENTS$1.filter((a) => agentOpts.some((o) => o.value === a)));
|
|
2520
1816
|
if (configuredAgents !== void 0) return configuredAgents.filter((a) => agentOpts.some((o) => o.value === a));
|
|
2521
1817
|
return navigableMultiselect({
|
|
2522
1818
|
message: "Select agents to install MCP servers to",
|
|
@@ -2762,7 +2058,7 @@ const AVAILABLE_AGENTS = [
|
|
|
2762
2058
|
}
|
|
2763
2059
|
];
|
|
2764
2060
|
const DEFAULT_SCOPE = "project";
|
|
2765
|
-
const DEFAULT_AGENTS
|
|
2061
|
+
const DEFAULT_AGENTS = [
|
|
2766
2062
|
"cursor",
|
|
2767
2063
|
"claude-code",
|
|
2768
2064
|
"github-copilot"
|
|
@@ -2797,8 +2093,6 @@ function getRecommendedSourceKeys(config) {
|
|
|
2797
2093
|
if (backend === "elysia") sources.push("elysiajs/skills");
|
|
2798
2094
|
if (backend === "convex") sources.push("waynesutton/convexskills");
|
|
2799
2095
|
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
2096
|
return sources;
|
|
2803
2097
|
}
|
|
2804
2098
|
const CURATED_SKILLS_BY_SOURCE = {
|
|
@@ -2910,7 +2204,7 @@ async function setupSkills(config) {
|
|
|
2910
2204
|
scope = configuredScope ?? DEFAULT_SCOPE;
|
|
2911
2205
|
selectedSkills = configuredSelections !== void 0 ? configuredSelections.flatMap((selection) => selection.skills.map((skill) => `${selection.source}::${skill}`)) : allSkillValues;
|
|
2912
2206
|
if (selectedSkills.length === 0) return Result.ok(void 0);
|
|
2913
|
-
selectedAgents = configuredAgents ? [...configuredAgents] : [...DEFAULT_AGENTS
|
|
2207
|
+
selectedAgents = configuredAgents ? [...configuredAgents] : [...DEFAULT_AGENTS];
|
|
2914
2208
|
if (selectedAgents.length === 0) return Result.ok(void 0);
|
|
2915
2209
|
} else {
|
|
2916
2210
|
const results = await navigableGroup({
|
|
@@ -2947,7 +2241,7 @@ async function setupSkills(config) {
|
|
|
2947
2241
|
message: "Select agents to install skills to",
|
|
2948
2242
|
options: AVAILABLE_AGENTS,
|
|
2949
2243
|
required: false,
|
|
2950
|
-
initialValues: [...DEFAULT_AGENTS
|
|
2244
|
+
initialValues: [...DEFAULT_AGENTS]
|
|
2951
2245
|
});
|
|
2952
2246
|
}
|
|
2953
2247
|
});
|
|
@@ -2996,44 +2290,6 @@ async function setupSkills(config) {
|
|
|
2996
2290
|
return Result.ok(void 0);
|
|
2997
2291
|
}
|
|
2998
2292
|
//#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
2293
|
//#region src/helpers/addons/tauri-setup.ts
|
|
3038
2294
|
function getWebFrontend(frontend) {
|
|
3039
2295
|
return frontend.find((value) => types_exports.desktopWebFrontends.includes(value));
|
|
@@ -3114,7 +2370,7 @@ async function setupTauri(config) {
|
|
|
3114
2370
|
}
|
|
3115
2371
|
//#endregion
|
|
3116
2372
|
//#region src/helpers/addons/tui-setup.ts
|
|
3117
|
-
const TEMPLATES
|
|
2373
|
+
const TEMPLATES = {
|
|
3118
2374
|
core: {
|
|
3119
2375
|
label: "Core",
|
|
3120
2376
|
hint: "Basic OpenTUI template"
|
|
@@ -3128,7 +2384,7 @@ const TEMPLATES$1 = {
|
|
|
3128
2384
|
hint: "SolidJS-based OpenTUI template"
|
|
3129
2385
|
}
|
|
3130
2386
|
};
|
|
3131
|
-
const DEFAULT_TEMPLATE
|
|
2387
|
+
const DEFAULT_TEMPLATE = "core";
|
|
3132
2388
|
const TUI_LOCKFILES = [
|
|
3133
2389
|
"bun.lock",
|
|
3134
2390
|
"package-lock.json",
|
|
@@ -3138,7 +2394,7 @@ const TUI_LOCKFILES = [
|
|
|
3138
2394
|
function resolveTuiTemplate(config) {
|
|
3139
2395
|
const configuredTemplate = config.addonOptions?.opentui?.template;
|
|
3140
2396
|
if (configuredTemplate) return configuredTemplate;
|
|
3141
|
-
if (isSilent()) return DEFAULT_TEMPLATE
|
|
2397
|
+
if (isSilent()) return DEFAULT_TEMPLATE;
|
|
3142
2398
|
}
|
|
3143
2399
|
async function setupTui(config) {
|
|
3144
2400
|
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
@@ -3149,14 +2405,14 @@ async function setupTui(config) {
|
|
|
3149
2405
|
setIsFirstPrompt(true);
|
|
3150
2406
|
const selectedTemplate = await navigableSelect({
|
|
3151
2407
|
message: "Choose a template",
|
|
3152
|
-
options: Object.entries(TEMPLATES
|
|
2408
|
+
options: Object.entries(TEMPLATES).map(([key, templateOption]) => ({
|
|
3153
2409
|
value: key,
|
|
3154
2410
|
label: templateOption.label,
|
|
3155
2411
|
hint: templateOption.hint
|
|
3156
2412
|
})),
|
|
3157
|
-
initialValue: DEFAULT_TEMPLATE
|
|
2413
|
+
initialValue: DEFAULT_TEMPLATE
|
|
3158
2414
|
});
|
|
3159
|
-
if (isCancel$
|
|
2415
|
+
if (isCancel$2(selectedTemplate)) return userCancelled("Operation cancelled");
|
|
3160
2416
|
template = selectedTemplate;
|
|
3161
2417
|
}
|
|
3162
2418
|
const args = getPackageExecutionArgs(packageManager, `create-tui@latest --template ${template} --no-git --no-install tui`);
|
|
@@ -3234,324 +2490,6 @@ async function postProcessTuiWorkspace(tuiDir) {
|
|
|
3234
2490
|
return Result.ok(void 0);
|
|
3235
2491
|
}
|
|
3236
2492
|
//#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
2493
|
//#region src/helpers/addons/addons-setup.ts
|
|
3556
2494
|
async function runSetup(setupFn) {
|
|
3557
2495
|
const result = await setupFn();
|
|
@@ -3572,41 +2510,27 @@ async function runAddonStep(addon, step) {
|
|
|
3572
2510
|
if (result.isErr()) cliConsola.error(pc.red(result.error.message));
|
|
3573
2511
|
}
|
|
3574
2512
|
async function setupAddons(config) {
|
|
3575
|
-
const { addons,
|
|
2513
|
+
const { addons, frontend, projectDir } = config;
|
|
3576
2514
|
const hasWebFrontend = frontend.some((value) => types_exports.desktopWebFrontends.includes(value));
|
|
3577
2515
|
if (addons.includes("tauri") && hasWebFrontend) await runSetup(() => setupTauri(config));
|
|
3578
|
-
const
|
|
3579
|
-
const
|
|
3580
|
-
const hasBiome = !hasUltracite && !hasOxlint && addons.includes("biome");
|
|
2516
|
+
const hasOxlint = addons.includes("oxlint");
|
|
2517
|
+
const hasBiome = !hasOxlint && addons.includes("biome");
|
|
3581
2518
|
const hasHusky = addons.includes("husky");
|
|
3582
2519
|
const hasLefthook = addons.includes("lefthook");
|
|
3583
2520
|
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
|
-
}
|
|
2521
|
+
if (hasBiome) await runAddonStep("biome", () => setupBiome(projectDir));
|
|
2522
|
+
else if (hasOxlint) await runSetup(() => setupOxlint(projectDir, config.packageManager));
|
|
2523
|
+
if (hasHusky || hasLefthook) {
|
|
2524
|
+
let linter;
|
|
2525
|
+
if (hasOxlint) linter = "oxlint";
|
|
2526
|
+
else if (hasBiome) linter = "biome";
|
|
2527
|
+
else if (hasVitePlus) linter = "vite-plus";
|
|
2528
|
+
if (hasHusky) await runAddonStep("husky", () => setupHusky(projectDir, linter));
|
|
2529
|
+
if (hasLefthook) await runAddonStep("lefthook", () => setupLefthook(projectDir));
|
|
3600
2530
|
}
|
|
3601
|
-
if (addons.includes("starlight")) await runSetup(() => setupStarlight(config));
|
|
3602
|
-
if (addons.includes("fumadocs")) await runSetup(() => setupFumadocs(config));
|
|
3603
2531
|
if (addons.includes("opentui")) await runSetup(() => setupTui(config));
|
|
3604
|
-
if (addons.includes("wxt")) await runSetup(() => setupWxt(config));
|
|
3605
2532
|
if (addons.includes("skills")) await runSetup(() => setupSkills(config));
|
|
3606
2533
|
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
2534
|
}
|
|
3611
2535
|
async function setupBiome(projectDir) {
|
|
3612
2536
|
await addPackageDependency({
|
|
@@ -3679,6 +2603,20 @@ async function setupTesting(projectDir, testing) {
|
|
|
3679
2603
|
await updateScripts(projectDir, { "test:e2e": "playwright test" });
|
|
3680
2604
|
}
|
|
3681
2605
|
}
|
|
2606
|
+
/** Best-effort browser install after deps; never fails project creation. */
|
|
2607
|
+
async function installPlaywrightBrowsers(projectDir, packageManager) {
|
|
2608
|
+
if (shouldSkipExternalCommands()) return;
|
|
2609
|
+
const s = createSpinner();
|
|
2610
|
+
s.start("Installing Playwright browsers...");
|
|
2611
|
+
const args = getPackageExecutionArgs(packageManager, "playwright install");
|
|
2612
|
+
try {
|
|
2613
|
+
await $({ cwd: projectDir })`${args}`;
|
|
2614
|
+
s.stop(pc.green("Playwright browsers installed"));
|
|
2615
|
+
} catch (e) {
|
|
2616
|
+
s.stop(pc.yellow("Playwright browser install skipped (run playwright install later)"));
|
|
2617
|
+
cliLog.info(pc.dim(`Playwright browsers were not installed automatically: ${e instanceof Error ? e.message : String(e)}`));
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
3682
2620
|
//#endregion
|
|
3683
2621
|
//#region src/utils/config-processing.ts
|
|
3684
2622
|
function processArrayOption(options) {
|
|
@@ -3700,7 +2638,7 @@ function processFlags(options, projectName) {
|
|
|
3700
2638
|
if (options.database) config.database = options.database;
|
|
3701
2639
|
if (options.orm) config.orm = options.orm;
|
|
3702
2640
|
if (options.auth !== void 0) config.auth = options.auth;
|
|
3703
|
-
if (options.payments !== void 0) config.payments = options.payments;
|
|
2641
|
+
if (options.payments !== void 0) config.payments = normalizePayments(options.payments);
|
|
3704
2642
|
if (options.observability !== void 0) config.observability = normalizeObservability(options.observability);
|
|
3705
2643
|
if (options.disableObservability) config.observability = [];
|
|
3706
2644
|
if (options.communication !== void 0) config.communication = options.communication;
|
|
@@ -3761,6 +2699,7 @@ async function detectProjectConfig(projectDir) {
|
|
|
3761
2699
|
frontend: kubojsConfig.frontend,
|
|
3762
2700
|
addons: kubojsConfig.addons,
|
|
3763
2701
|
examples: kubojsConfig.examples,
|
|
2702
|
+
testing: kubojsConfig.testing,
|
|
3764
2703
|
auth: kubojsConfig.auth,
|
|
3765
2704
|
payments: kubojsConfig.payments,
|
|
3766
2705
|
observability: normalizeObservability(kubojsConfig.observability),
|
|
@@ -3808,9 +2747,8 @@ const ADD_PACKAGE_JSON_PATHS = [
|
|
|
3808
2747
|
"apps/web/package.json",
|
|
3809
2748
|
"apps/native/package.json",
|
|
3810
2749
|
"apps/desktop/package.json",
|
|
3811
|
-
"apps/fumadocs/package.json",
|
|
3812
2750
|
"apps/docs/package.json",
|
|
3813
|
-
"
|
|
2751
|
+
"apps/api/package.json",
|
|
3814
2752
|
"packages/db/package.json",
|
|
3815
2753
|
"packages/auth/package.json",
|
|
3816
2754
|
"packages/backend/package.json",
|
|
@@ -3820,17 +2758,21 @@ const ADD_PACKAGE_JSON_PATHS = [
|
|
|
3820
2758
|
"packages/ui/package.json"
|
|
3821
2759
|
];
|
|
3822
2760
|
const ADD_TEXT_FILE_PATHS = ["apps/web/vite.config.ts", "lefthook.yml"];
|
|
2761
|
+
const ADD_ENV_FILE_PATHS = [
|
|
2762
|
+
".env",
|
|
2763
|
+
"apps/web/.env",
|
|
2764
|
+
"apps/server/.env",
|
|
2765
|
+
"apps/native/.env",
|
|
2766
|
+
"packages/backend/.env.local",
|
|
2767
|
+
"packages/infra/.env"
|
|
2768
|
+
];
|
|
3823
2769
|
const HOOK_ADDONS = ["husky", "lefthook"];
|
|
3824
2770
|
const HOOK_LINTER_ADDONS = [
|
|
3825
2771
|
"biome",
|
|
3826
2772
|
"oxlint",
|
|
3827
2773
|
"vite-plus"
|
|
3828
2774
|
];
|
|
3829
|
-
const TASK_RUNNER_ADDONS = [
|
|
3830
|
-
"turborepo",
|
|
3831
|
-
"nx",
|
|
3832
|
-
"vite-plus"
|
|
3833
|
-
];
|
|
2775
|
+
const TASK_RUNNER_ADDONS = ["turborepo", "vite-plus"];
|
|
3834
2776
|
function mergeAddonOptions(existingAddonOptions, nextAddonOptions) {
|
|
3835
2777
|
if (!existingAddonOptions && !nextAddonOptions) return;
|
|
3836
2778
|
const mergedAddonOptions = { ...existingAddonOptions };
|
|
@@ -3868,11 +2810,8 @@ async function cleanupRemovedLinters(projectDir, removedAddons) {
|
|
|
3868
2810
|
const packageJson = await fs.readJson(packageJsonPath);
|
|
3869
2811
|
packageJson.devDependencies = packageJson.devDependencies ?? {};
|
|
3870
2812
|
for (const removed of removedLinters) {
|
|
3871
|
-
if (removed === "biome"
|
|
3872
|
-
|
|
3873
|
-
delete packageJson.devDependencies.ultracite;
|
|
3874
|
-
}
|
|
3875
|
-
if (removed === "oxlint" || removed === "ultracite") {
|
|
2813
|
+
if (removed === "biome") delete packageJson.devDependencies["@biomejs/biome"];
|
|
2814
|
+
if (removed === "oxlint") {
|
|
3876
2815
|
delete packageJson.devDependencies.oxlint;
|
|
3877
2816
|
delete packageJson.devDependencies.oxfmt;
|
|
3878
2817
|
}
|
|
@@ -3880,7 +2819,6 @@ async function cleanupRemovedLinters(projectDir, removedAddons) {
|
|
|
3880
2819
|
if (typeof packageJson.scripts?.check === "string" && removedLinters.some((linter) => {
|
|
3881
2820
|
if (linter === "biome") return packageJson.scripts.check.includes("biome");
|
|
3882
2821
|
if (linter === "oxlint") return packageJson.scripts.check.includes("oxlint");
|
|
3883
|
-
if (linter === "ultracite") return packageJson.scripts.check.includes("ultracite");
|
|
3884
2822
|
return false;
|
|
3885
2823
|
})) delete packageJson.scripts.check;
|
|
3886
2824
|
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
@@ -3936,7 +2874,7 @@ async function addHandlerInternal(input) {
|
|
|
3936
2874
|
}));
|
|
3937
2875
|
if (!isSilent()) {
|
|
3938
2876
|
renderTitle();
|
|
3939
|
-
intro(
|
|
2877
|
+
intro(cliColors.signal("Add addons to your kubojs project"));
|
|
3940
2878
|
}
|
|
3941
2879
|
const existingConfig = await detectProjectConfig(projectDir);
|
|
3942
2880
|
if (!existingConfig) return Result.err(new CLIError({ message: `No kubojs project found in ${projectDir}. Make sure kubojs.jsonrc exists.` }));
|
|
@@ -3969,7 +2907,7 @@ async function addHandlerInternal(input) {
|
|
|
3969
2907
|
if (selectedAddons.length === 0) {
|
|
3970
2908
|
if (!isSilent()) {
|
|
3971
2909
|
log.info(pc.dim("No addons selected."));
|
|
3972
|
-
outro(
|
|
2910
|
+
outro(cliColors.signal("Nothing to add."));
|
|
3973
2911
|
}
|
|
3974
2912
|
return Result.ok({
|
|
3975
2913
|
success: true,
|
|
@@ -3980,10 +2918,11 @@ async function addHandlerInternal(input) {
|
|
|
3980
2918
|
addonsToAdd = selectedAddons;
|
|
3981
2919
|
}
|
|
3982
2920
|
const { updatedAddons, removedAddons } = mergeAddonsExclusive(existingConfig.addons, addonsToAdd);
|
|
2921
|
+
const updatedTesting = existingConfig.testing ?? [];
|
|
3983
2922
|
const addonsValidationResult = validateAddonsAgainstConfig(updatedAddons, existingConfig);
|
|
3984
2923
|
if (addonsValidationResult.isErr()) return Result.err(new CLIError({ message: addonsValidationResult.error.message }));
|
|
3985
2924
|
if (!isSilent()) {
|
|
3986
|
-
log.info(
|
|
2925
|
+
log.info(cliColors.bright(`Adding addons: ${addonsToAdd.join(", ")}`));
|
|
3987
2926
|
if (removedAddons.length > 0) log.info(pc.dim(`Replacing exclusive addons: ${removedAddons.join(", ")}`));
|
|
3988
2927
|
}
|
|
3989
2928
|
const mergedAddonOptions = mergeAddonOptions(existingConfig.addonOptions, input.addonOptions);
|
|
@@ -4003,6 +2942,7 @@ async function addHandlerInternal(input) {
|
|
|
4003
2942
|
payments: existingConfig.payments,
|
|
4004
2943
|
observability: existingConfig.observability,
|
|
4005
2944
|
communication: existingConfig.communication ?? "none",
|
|
2945
|
+
testing: updatedTesting,
|
|
4006
2946
|
git: false,
|
|
4007
2947
|
packageManager: input.packageManager || existingConfig.packageManager,
|
|
4008
2948
|
install: input.install ?? false,
|
|
@@ -4031,10 +2971,17 @@ async function addHandlerInternal(input) {
|
|
|
4031
2971
|
vfs.writeFile(filePath, content);
|
|
4032
2972
|
}
|
|
4033
2973
|
}
|
|
2974
|
+
for (const filePath of ADD_ENV_FILE_PATHS) {
|
|
2975
|
+
const fullPath = path.join(projectDir, filePath);
|
|
2976
|
+
if (await fs.pathExists(fullPath)) {
|
|
2977
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
2978
|
+
vfs.writeFile(filePath, content);
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
4034
2981
|
await processAddonTemplates(vfs, EMBEDDED_TEMPLATES, config);
|
|
4035
2982
|
processAddonsDeps(vfs, config);
|
|
2983
|
+
processEnvVariables(vfs, updatedConfig);
|
|
4036
2984
|
if (addonsToAdd.includes("turborepo")) processTurboConfig(vfs, updatedConfig);
|
|
4037
|
-
if (addonsToAdd.includes("nx")) processNxConfig(vfs, updatedConfig);
|
|
4038
2985
|
if (addonsToAdd.includes("vite-plus")) processVitePlusConfig(vfs, updatedConfig);
|
|
4039
2986
|
if (updatedAddons.some((addon) => TASK_RUNNER_ADDONS.includes(addon))) processPackageConfigs(vfs, updatedConfig);
|
|
4040
2987
|
if (updatedAddons.includes("vite-plus")) updateViteConfigImportsForVitePlus(vfs);
|
|
@@ -4049,7 +2996,7 @@ async function addHandlerInternal(input) {
|
|
|
4049
2996
|
if (!isSilent()) {
|
|
4050
2997
|
log.success(pc.green("Dry run validation passed. No addon files were written."));
|
|
4051
2998
|
log.info(pc.dim(`Planned addon files: ${vfs.getFileCount()}`));
|
|
4052
|
-
outro(
|
|
2999
|
+
outro(cliColors.signal("Dry run complete."));
|
|
4053
3000
|
}
|
|
4054
3001
|
return Result.ok({
|
|
4055
3002
|
success: true,
|
|
@@ -4087,7 +3034,8 @@ async function addHandlerInternal(input) {
|
|
|
4087
3034
|
if (testingSetupResult.isErr()) return Result.err(testingSetupResult.error);
|
|
4088
3035
|
await updateKubojsConfig(projectDir, {
|
|
4089
3036
|
addons: updatedAddons,
|
|
4090
|
-
addonOptions: updatedConfig.addonOptions
|
|
3037
|
+
addonOptions: updatedConfig.addonOptions,
|
|
3038
|
+
testing: updatedTesting
|
|
4091
3039
|
});
|
|
4092
3040
|
if (input.install) {
|
|
4093
3041
|
if (!isSilent()) log.info(pc.dim("Installing dependencies..."));
|
|
@@ -4099,7 +3047,7 @@ async function addHandlerInternal(input) {
|
|
|
4099
3047
|
if (!isSilent()) {
|
|
4100
3048
|
log.success(pc.green(`Successfully added: ${addonsToAdd.join(", ")}`));
|
|
4101
3049
|
if (!input.install) log.info(pc.yellow(`Run '${config.packageManager === "npm" ? "npm install" : `${config.packageManager} install`}' to install new dependencies.`));
|
|
4102
|
-
outro(
|
|
3050
|
+
outro(cliColors.signal("Addons added successfully!"));
|
|
4103
3051
|
}
|
|
4104
3052
|
return Result.ok({
|
|
4105
3053
|
success: true,
|
|
@@ -4112,7 +3060,7 @@ async function addHandlerInternal(input) {
|
|
|
4112
3060
|
//#region src/prompts/api.ts
|
|
4113
3061
|
async function getApiChoice(Api, frontend, backend, previousValue) {
|
|
4114
3062
|
if (backend === "convex" || backend === "none") return "none";
|
|
4115
|
-
const allowed = allowedApisForFrontends(frontend ?? []);
|
|
3063
|
+
const allowed = allowedApisForFrontends(frontend ?? [], backend);
|
|
4116
3064
|
if (Api) {
|
|
4117
3065
|
const compat = validateApiFrontendCompatibility(Api, frontend ?? []);
|
|
4118
3066
|
if (compat.isErr()) throw compat.error;
|
|
@@ -4126,6 +3074,10 @@ async function getApiChoice(Api, frontend, backend, previousValue) {
|
|
|
4126
3074
|
value: "orpc",
|
|
4127
3075
|
label: "oRPC",
|
|
4128
3076
|
hint: "End-to-end type-safe APIs that adhere to OpenAPI standards"
|
|
3077
|
+
} : a === "orval" ? {
|
|
3078
|
+
value: "orval",
|
|
3079
|
+
label: "Orval",
|
|
3080
|
+
hint: "OpenAPI REST APIs with generated Hono handlers and Fetch clients"
|
|
4129
3081
|
} : {
|
|
4130
3082
|
value: "none",
|
|
4131
3083
|
label: "None",
|
|
@@ -4136,7 +3088,7 @@ async function getApiChoice(Api, frontend, backend, previousValue) {
|
|
|
4136
3088
|
options: apiOptions,
|
|
4137
3089
|
initialValue: preferValidInitial(apiOptions, previousValue, apiOptions[0].value)
|
|
4138
3090
|
});
|
|
4139
|
-
if (isCancel$
|
|
3091
|
+
if (isCancel$2(apiType)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4140
3092
|
return apiType;
|
|
4141
3093
|
}
|
|
4142
3094
|
//#endregion
|
|
@@ -4188,7 +3140,7 @@ async function getAuthChoice(auth, backend, frontend = [], previousValue) {
|
|
|
4188
3140
|
options,
|
|
4189
3141
|
initialValue: preferValidInitial(options, previousValue, options.some((option) => option.value === DEFAULT_CONFIG.auth) ? DEFAULT_CONFIG.auth : "none")
|
|
4190
3142
|
});
|
|
4191
|
-
if (isCancel$
|
|
3143
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4192
3144
|
return response;
|
|
4193
3145
|
}
|
|
4194
3146
|
//#endregion
|
|
@@ -4242,7 +3194,7 @@ async function getBackendFrameworkChoice(backendFramework, frontends, previousVa
|
|
|
4242
3194
|
options: backendOptions,
|
|
4243
3195
|
initialValue: preferValidInitial(backendOptions, previousValue, hasFullstackFrontend ? "self" : DEFAULT_CONFIG.backend)
|
|
4244
3196
|
});
|
|
4245
|
-
if (isCancel$
|
|
3197
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4246
3198
|
return response;
|
|
4247
3199
|
}
|
|
4248
3200
|
//#endregion
|
|
@@ -4265,6 +3217,11 @@ async function getCommunicationChoice(communication, backend, previousValue) {
|
|
|
4265
3217
|
value: "notifique",
|
|
4266
3218
|
label: "Notifique",
|
|
4267
3219
|
hint: "Omnichannel BR messaging API"
|
|
3220
|
+
},
|
|
3221
|
+
{
|
|
3222
|
+
value: "arara",
|
|
3223
|
+
label: "AraraHQ",
|
|
3224
|
+
hint: "WhatsApp messaging via the official Node SDK"
|
|
4268
3225
|
}
|
|
4269
3226
|
];
|
|
4270
3227
|
const response = await navigableSelect({
|
|
@@ -4272,7 +3229,7 @@ async function getCommunicationChoice(communication, backend, previousValue) {
|
|
|
4272
3229
|
options,
|
|
4273
3230
|
initialValue: preferValidInitial(options, previousValue, DEFAULT_CONFIG.communication)
|
|
4274
3231
|
});
|
|
4275
|
-
if (isCancel$
|
|
3232
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4276
3233
|
return response;
|
|
4277
3234
|
}
|
|
4278
3235
|
//#endregion
|
|
@@ -4312,7 +3269,7 @@ async function getDatabaseChoice(database, backend, runtime, previousValue) {
|
|
|
4312
3269
|
options: databaseOptions,
|
|
4313
3270
|
initialValue: preferValidInitial(databaseOptions, previousValue, DEFAULT_CONFIG.database)
|
|
4314
3271
|
});
|
|
4315
|
-
if (isCancel$
|
|
3272
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4316
3273
|
return response;
|
|
4317
3274
|
}
|
|
4318
3275
|
//#endregion
|
|
@@ -4411,7 +3368,7 @@ async function getDBSetupChoice(databaseType, dbSetup, _orm, backend, runtime, p
|
|
|
4411
3368
|
options,
|
|
4412
3369
|
initialValue: preferValidInitial(options, previousValue, "none")
|
|
4413
3370
|
});
|
|
4414
|
-
if (isCancel$
|
|
3371
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4415
3372
|
return response;
|
|
4416
3373
|
}
|
|
4417
3374
|
//#endregion
|
|
@@ -4438,7 +3395,7 @@ async function getExamplesChoice(examples, database, frontends, backend, api, pr
|
|
|
4438
3395
|
required: false,
|
|
4439
3396
|
initialValues: (previousValue ?? DEFAULT_CONFIG.examples)?.filter((ex) => options.some((o) => o.value === ex))
|
|
4440
3397
|
});
|
|
4441
|
-
if (isCancel$
|
|
3398
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4442
3399
|
return response;
|
|
4443
3400
|
}
|
|
4444
3401
|
//#endregion
|
|
@@ -4474,7 +3431,7 @@ async function getFrontendChoice(frontendOptions, backend, auth, previousValue)
|
|
|
4474
3431
|
initialValues: previousValue ? [...previousWeb ? ["web"] : [], ...previousNative ? ["native"] : []] : ["web"]
|
|
4475
3432
|
});
|
|
4476
3433
|
if (isGoBack(frontendTypes)) return GO_BACK_SYMBOL;
|
|
4477
|
-
if (isCancel$
|
|
3434
|
+
if (isCancel$2(frontendTypes)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4478
3435
|
setIsFirstPrompt(false);
|
|
4479
3436
|
const result = [];
|
|
4480
3437
|
let shouldRestart = false;
|
|
@@ -4527,7 +3484,7 @@ async function getFrontendChoice(frontendOptions, backend, auth, previousValue)
|
|
|
4527
3484
|
initialValue: preferValidInitial(webOptions, previousWeb, DEFAULT_CONFIG.frontend[0])
|
|
4528
3485
|
});
|
|
4529
3486
|
if (isGoBack(webFramework)) shouldRestart = true;
|
|
4530
|
-
else if (isCancel$
|
|
3487
|
+
else if (isCancel$2(webFramework)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4531
3488
|
else result.push(webFramework);
|
|
4532
3489
|
}
|
|
4533
3490
|
if (shouldRestart) {
|
|
@@ -4561,7 +3518,7 @@ async function getFrontendChoice(frontendOptions, backend, auth, previousValue)
|
|
|
4561
3518
|
setIsFirstPrompt(wasFirstPrompt);
|
|
4562
3519
|
continue;
|
|
4563
3520
|
}
|
|
4564
|
-
else if (isCancel$
|
|
3521
|
+
else if (isCancel$2(nativeFramework)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4565
3522
|
else result.push(nativeFramework);
|
|
4566
3523
|
}
|
|
4567
3524
|
if (shouldRestart) {
|
|
@@ -4579,7 +3536,7 @@ async function getGitChoice(git, previousValue) {
|
|
|
4579
3536
|
message: "Initialize git repository?",
|
|
4580
3537
|
initialValue: previousValue ?? DEFAULT_CONFIG.git
|
|
4581
3538
|
});
|
|
4582
|
-
if (isCancel$
|
|
3539
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4583
3540
|
return response;
|
|
4584
3541
|
}
|
|
4585
3542
|
//#endregion
|
|
@@ -4590,7 +3547,7 @@ async function getinstallChoice(install, previousValue) {
|
|
|
4590
3547
|
message: "Install dependencies?",
|
|
4591
3548
|
initialValue: previousValue ?? DEFAULT_CONFIG.install
|
|
4592
3549
|
});
|
|
4593
|
-
if (isCancel$
|
|
3550
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4594
3551
|
return response;
|
|
4595
3552
|
}
|
|
4596
3553
|
//#endregion
|
|
@@ -4612,7 +3569,7 @@ async function getObservabilityChoice(observability, previousValue) {
|
|
|
4612
3569
|
required: false,
|
|
4613
3570
|
initialValues: previousValue ?? DEFAULT_CONFIG.observability
|
|
4614
3571
|
});
|
|
4615
|
-
if (isCancel$
|
|
3572
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4616
3573
|
return response;
|
|
4617
3574
|
}
|
|
4618
3575
|
//#endregion
|
|
@@ -4730,7 +3687,7 @@ function validateBackendNoneConstraints(config, providedFlags) {
|
|
|
4730
3687
|
if (has("orm") && config.orm !== "none") return validationErr("Backend 'none' requires '--orm none'. Please remove the --orm flag or set it to 'none'.");
|
|
4731
3688
|
if (has("api") && config.api !== "none") return validationErr("Backend 'none' requires '--api none'. Please remove the --api flag or set it to 'none'.");
|
|
4732
3689
|
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
|
|
3690
|
+
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
3691
|
if (has("communication") && config.communication !== "none") return validationErr("Backend 'none' requires '--communication none'. Please remove the --communication flag or set it to 'none'.");
|
|
4735
3692
|
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
3693
|
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 +3736,8 @@ function validateFrontendConstraints(config, providedFlags) {
|
|
|
4779
3736
|
return Result.ok(void 0);
|
|
4780
3737
|
}
|
|
4781
3738
|
function validateApiConstraints(config, options) {
|
|
3739
|
+
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.");
|
|
3740
|
+
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
3741
|
if (config.api === "none") {
|
|
4783
3742
|
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
3743
|
}
|
|
@@ -4811,6 +3770,7 @@ function validateFullConfig(config, providedFlags, options) {
|
|
|
4811
3770
|
yield* validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
|
|
4812
3771
|
yield* validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend ?? [], config.orm, config.database);
|
|
4813
3772
|
yield* validateCommunicationCompatibility(config.communication, config.backend);
|
|
3773
|
+
yield* validateAraraRuntimeCompatibility(config.communication, config.backend, config.runtime, config.serverDeploy);
|
|
4814
3774
|
return Result.ok(void 0);
|
|
4815
3775
|
});
|
|
4816
3776
|
}
|
|
@@ -4822,6 +3782,7 @@ function validateConfigForProgrammaticUse(config) {
|
|
|
4822
3782
|
yield* validateTestingAgainstFrontends(config.testing ?? [], config.frontend ?? []);
|
|
4823
3783
|
yield* validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend, config.orm, config.database);
|
|
4824
3784
|
yield* validateCommunicationCompatibility(config.communication, config.backend);
|
|
3785
|
+
yield* validateAraraRuntimeCompatibility(config.communication, config.backend, config.runtime, config.serverDeploy);
|
|
4825
3786
|
if (config.addons && config.addons.length > 0) yield* validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth, config.backend, config.runtime);
|
|
4826
3787
|
yield* validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
|
|
4827
3788
|
return Result.ok(void 0);
|
|
@@ -4860,7 +3821,7 @@ async function getORMChoice(orm, hasDatabase, database, backend, runtime, previo
|
|
|
4860
3821
|
options,
|
|
4861
3822
|
initialValue: preferValidInitial(options, previousValue, database === "mongodb" ? "prisma" : runtime === "workers" ? "drizzle" : DEFAULT_CONFIG.orm)
|
|
4862
3823
|
});
|
|
4863
|
-
if (isCancel$
|
|
3824
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4864
3825
|
return response;
|
|
4865
3826
|
}
|
|
4866
3827
|
//#endregion
|
|
@@ -4889,47 +3850,48 @@ async function getPackageManagerChoice(packageManager, previousValue) {
|
|
|
4889
3850
|
],
|
|
4890
3851
|
initialValue: previousValue ?? detectedPackageManager
|
|
4891
3852
|
});
|
|
4892
|
-
if (isCancel$
|
|
3853
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4893
3854
|
return response;
|
|
4894
3855
|
}
|
|
4895
3856
|
//#endregion
|
|
4896
3857
|
//#region src/prompts/payments.ts
|
|
4897
3858
|
async function getPaymentsChoice(payments, auth, backend, frontends = [], previousValue) {
|
|
4898
3859
|
if (payments !== void 0) return payments;
|
|
4899
|
-
if (backend === "none") return
|
|
3860
|
+
if (backend === "none") return [];
|
|
4900
3861
|
const options = [{
|
|
4901
|
-
value: "none",
|
|
4902
|
-
label: "None",
|
|
4903
|
-
hint: "No payments integration"
|
|
4904
|
-
}, {
|
|
4905
3862
|
value: "abacatepay",
|
|
4906
3863
|
label: "AbacatePay",
|
|
4907
3864
|
hint: "Hosted checkout with webhook reconciliation"
|
|
3865
|
+
}, {
|
|
3866
|
+
value: "stripe",
|
|
3867
|
+
label: "Stripe",
|
|
3868
|
+
hint: "Embedded Checkout with a fulfillment webhook"
|
|
4908
3869
|
}];
|
|
4909
|
-
const
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
"
|
|
4916
|
-
"
|
|
4917
|
-
"
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
3870
|
+
const getDisabledReason = (provider) => {
|
|
3871
|
+
const issue = getPaymentCompatibilityIssue({
|
|
3872
|
+
provider,
|
|
3873
|
+
backend,
|
|
3874
|
+
frontends
|
|
3875
|
+
});
|
|
3876
|
+
if (issue === "requires-web-frontend") return "Requires a web frontend";
|
|
3877
|
+
if (issue === "convex-unsupported") return "Not supported with Convex";
|
|
3878
|
+
if (issue === "native-only-unsupported") return "Not supported with native-only frontends";
|
|
3879
|
+
if (issue === "sql-database-required") return "Requires a SQL database with Prisma or Drizzle";
|
|
3880
|
+
};
|
|
3881
|
+
const response = await navigableMultiselect({
|
|
3882
|
+
message: "Select payments providers",
|
|
3883
|
+
options: options.map((option) => {
|
|
3884
|
+
const disabledReason = getDisabledReason(option.value);
|
|
3885
|
+
return {
|
|
3886
|
+
...option,
|
|
3887
|
+
hint: disabledReason ?? option.hint,
|
|
3888
|
+
disabled: disabledReason !== void 0
|
|
3889
|
+
};
|
|
3890
|
+
}),
|
|
3891
|
+
required: false,
|
|
3892
|
+
initialValues: previousValue ?? DEFAULT_CONFIG.payments
|
|
4931
3893
|
});
|
|
4932
|
-
if (isCancel$
|
|
3894
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4933
3895
|
return response;
|
|
4934
3896
|
}
|
|
4935
3897
|
//#endregion
|
|
@@ -4956,7 +3918,7 @@ async function getRuntimeChoice(runtime, backend, previousValue) {
|
|
|
4956
3918
|
options: runtimeOptions,
|
|
4957
3919
|
initialValue: preferValidInitial(runtimeOptions, previousValue, DEFAULT_CONFIG.runtime)
|
|
4958
3920
|
});
|
|
4959
|
-
if (isCancel$
|
|
3921
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4960
3922
|
return response;
|
|
4961
3923
|
}
|
|
4962
3924
|
//#endregion
|
|
@@ -5014,7 +3976,7 @@ async function getServerDeploymentChoice(deployment, runtime, backend, _webDeplo
|
|
|
5014
3976
|
options,
|
|
5015
3977
|
initialValue: preferValidInitial(options, previousValue, DEFAULT_CONFIG.serverDeploy)
|
|
5016
3978
|
});
|
|
5017
|
-
if (isCancel$
|
|
3979
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
5018
3980
|
return response;
|
|
5019
3981
|
}
|
|
5020
3982
|
//#endregion
|
|
@@ -5037,7 +3999,7 @@ async function getTestingChoice(testing, frontends, previousValue) {
|
|
|
5037
3999
|
required: false,
|
|
5038
4000
|
initialValues: (previousValue ?? DEFAULT_CONFIG.testing)?.filter((t) => options.some((o) => o.value === t))
|
|
5039
4001
|
});
|
|
5040
|
-
if (isCancel$
|
|
4002
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
5041
4003
|
return response;
|
|
5042
4004
|
}
|
|
5043
4005
|
//#endregion
|
|
@@ -5090,7 +4052,7 @@ async function getDeploymentChoice(deployment, _runtime, backend, frontend = [],
|
|
|
5090
4052
|
options,
|
|
5091
4053
|
initialValue: preferValidInitial(options, previousValue, DEFAULT_CONFIG.webDeploy)
|
|
5092
4054
|
});
|
|
5093
|
-
if (isCancel$
|
|
4055
|
+
if (isCancel$2(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
5094
4056
|
return response;
|
|
5095
4057
|
}
|
|
5096
4058
|
//#endregion
|
|
@@ -5174,6 +4136,32 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
5174
4136
|
}
|
|
5175
4137
|
//#endregion
|
|
5176
4138
|
//#region src/prompts/project-name.ts
|
|
4139
|
+
const S_BAR = "│";
|
|
4140
|
+
const S_BAR_END = "└";
|
|
4141
|
+
const S_STEP_ACTIVE = "◆";
|
|
4142
|
+
function kuboText(options) {
|
|
4143
|
+
return new TextPrompt({
|
|
4144
|
+
validate: options.validate,
|
|
4145
|
+
placeholder: options.placeholder,
|
|
4146
|
+
defaultValue: options.defaultValue,
|
|
4147
|
+
initialValue: options.initialValue,
|
|
4148
|
+
render() {
|
|
4149
|
+
const title = `${cliColors.signal(S_BAR)}\n${cliColors.bright(S_STEP_ACTIVE)} ${options.message}\n`;
|
|
4150
|
+
const placeholder = options.placeholder ? pc.dim(options.placeholder) : pc.dim("_");
|
|
4151
|
+
const input = this.userInput ? this.userInputWithCursor : placeholder;
|
|
4152
|
+
const value = this.value ?? "";
|
|
4153
|
+
switch (this.state) {
|
|
4154
|
+
case "error": {
|
|
4155
|
+
const error = this.error ? ` ${pc.yellow(this.error)}` : "";
|
|
4156
|
+
return `${title.trim()}\n${cliColors.signal(S_BAR)} ${input}\n${pc.yellow(S_BAR_END)}${error}\n`;
|
|
4157
|
+
}
|
|
4158
|
+
case "submit": return `${title}${pc.gray(S_BAR)}${value ? ` ${pc.dim(value)}` : ""}`;
|
|
4159
|
+
case "cancel": return `${title}${pc.gray(S_BAR)}${value ? ` ${pc.strikethrough(pc.dim(value))}` : ""}${value.trim() ? `\n${pc.gray(S_BAR)}` : ""}`;
|
|
4160
|
+
default: return `${title}${cliColors.signal(S_BAR)} ${input}\n${cliColors.signal(S_BAR_END)}\n`;
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
}).prompt();
|
|
4164
|
+
}
|
|
5177
4165
|
function isPathWithinCwd$1(targetPath) {
|
|
5178
4166
|
const resolved = path.resolve(targetPath);
|
|
5179
4167
|
const rel = path.relative(process.cwd(), resolved);
|
|
@@ -5201,7 +4189,7 @@ async function getProjectName(initialName) {
|
|
|
5201
4189
|
counter++;
|
|
5202
4190
|
}
|
|
5203
4191
|
while (!isValid) {
|
|
5204
|
-
const response = await
|
|
4192
|
+
const response = await kuboText({
|
|
5205
4193
|
message: "Enter your project name or path (relative to current directory)",
|
|
5206
4194
|
placeholder: defaultName,
|
|
5207
4195
|
initialValue: initialName,
|
|
@@ -5215,7 +4203,7 @@ async function getProjectName(initialName) {
|
|
|
5215
4203
|
}
|
|
5216
4204
|
}
|
|
5217
4205
|
});
|
|
5218
|
-
if (isCancel(response)) throw new UserCancelledError({ message: "Operation cancelled." });
|
|
4206
|
+
if (isCancel$1(response)) throw new UserCancelledError({ message: "Operation cancelled." });
|
|
5219
4207
|
projectPath = response || defaultName;
|
|
5220
4208
|
isValid = true;
|
|
5221
4209
|
}
|
|
@@ -5256,48 +4244,48 @@ async function trackProjectCreation(config, disableAnalytics = false) {
|
|
|
5256
4244
|
//#region src/utils/display-config.ts
|
|
5257
4245
|
function displayConfig(config) {
|
|
5258
4246
|
const configDisplay = [];
|
|
5259
|
-
if (config.projectName) configDisplay.push(`${
|
|
4247
|
+
if (config.projectName) configDisplay.push(`${cliColors.signal("Project Name:")} ${config.projectName}`);
|
|
5260
4248
|
if (config.frontend !== void 0) {
|
|
5261
4249
|
const frontend = Array.isArray(config.frontend) ? config.frontend : [config.frontend];
|
|
5262
4250
|
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(`${
|
|
4251
|
+
configDisplay.push(`${cliColors.signal("Frontend:")} ${frontendText}`);
|
|
4252
|
+
}
|
|
4253
|
+
if (config.backend !== void 0) configDisplay.push(`${cliColors.signal("Backend:")} ${String(config.backend)}`);
|
|
4254
|
+
if (config.runtime !== void 0) configDisplay.push(`${cliColors.signal("Runtime:")} ${String(config.runtime)}`);
|
|
4255
|
+
if (config.api !== void 0) configDisplay.push(`${cliColors.signal("API:")} ${String(config.api)}`);
|
|
4256
|
+
if (config.database !== void 0) configDisplay.push(`${cliColors.signal("Database:")} ${String(config.database)}`);
|
|
4257
|
+
if (config.orm !== void 0) configDisplay.push(`${cliColors.signal("ORM:")} ${String(config.orm)}`);
|
|
4258
|
+
if (config.auth !== void 0) configDisplay.push(`${cliColors.signal("Auth:")} ${String(config.auth)}`);
|
|
4259
|
+
if (config.payments !== void 0) configDisplay.push(`${cliColors.signal("Payments:")} ${config.payments.join(", ") || "none"}`);
|
|
4260
|
+
if (config.communication !== void 0) configDisplay.push(`${cliColors.signal("Communication:")} ${String(config.communication)}`);
|
|
4261
|
+
if (config.observability !== void 0) configDisplay.push(`${cliColors.signal("Observability:")} ${config.observability.join(", ") || "none"}`);
|
|
5274
4262
|
if (config.addons !== void 0) {
|
|
5275
4263
|
const addons = Array.isArray(config.addons) ? config.addons : [config.addons];
|
|
5276
4264
|
const addonsText = addons.length > 0 && addons[0] !== void 0 ? addons.join(", ") : "none";
|
|
5277
|
-
configDisplay.push(`${
|
|
4265
|
+
configDisplay.push(`${cliColors.signal("Addons:")} ${addonsText}`);
|
|
5278
4266
|
}
|
|
5279
4267
|
if (config.examples !== void 0) {
|
|
5280
4268
|
const examples = Array.isArray(config.examples) ? config.examples : [config.examples];
|
|
5281
4269
|
const examplesText = examples.length > 0 && examples[0] !== void 0 ? examples.join(", ") : "none";
|
|
5282
|
-
configDisplay.push(`${
|
|
4270
|
+
configDisplay.push(`${cliColors.signal("Examples:")} ${examplesText}`);
|
|
5283
4271
|
}
|
|
5284
4272
|
if (config.testing !== void 0) {
|
|
5285
4273
|
const testing = Array.isArray(config.testing) ? config.testing : [config.testing];
|
|
5286
4274
|
const testingText = testing.length > 0 && testing[0] !== void 0 ? testing.join(", ") : "none";
|
|
5287
|
-
configDisplay.push(`${
|
|
4275
|
+
configDisplay.push(`${cliColors.signal("Testing:")} ${testingText}`);
|
|
5288
4276
|
}
|
|
5289
4277
|
if (config.git !== void 0) {
|
|
5290
4278
|
const gitText = typeof config.git === "boolean" ? config.git ? "Yes" : "No" : String(config.git);
|
|
5291
|
-
configDisplay.push(`${
|
|
4279
|
+
configDisplay.push(`${cliColors.signal("Git Init:")} ${gitText}`);
|
|
5292
4280
|
}
|
|
5293
|
-
if (config.packageManager !== void 0) configDisplay.push(`${
|
|
4281
|
+
if (config.packageManager !== void 0) configDisplay.push(`${cliColors.signal("Package Manager:")} ${String(config.packageManager)}`);
|
|
5294
4282
|
if (config.install !== void 0) {
|
|
5295
4283
|
const installText = typeof config.install === "boolean" ? config.install ? "Yes" : "No" : String(config.install);
|
|
5296
|
-
configDisplay.push(`${
|
|
4284
|
+
configDisplay.push(`${cliColors.signal("Install Dependencies:")} ${installText}`);
|
|
5297
4285
|
}
|
|
5298
|
-
if (config.dbSetup !== void 0) configDisplay.push(`${
|
|
5299
|
-
if (config.webDeploy !== void 0) configDisplay.push(`${
|
|
5300
|
-
if (config.serverDeploy !== void 0) configDisplay.push(`${
|
|
4286
|
+
if (config.dbSetup !== void 0) configDisplay.push(`${cliColors.signal("Database Setup:")} ${String(config.dbSetup)}`);
|
|
4287
|
+
if (config.webDeploy !== void 0) configDisplay.push(`${cliColors.signal("Web Deployment:")} ${String(config.webDeploy)}`);
|
|
4288
|
+
if (config.serverDeploy !== void 0) configDisplay.push(`${cliColors.signal("Server Deployment:")} ${String(config.serverDeploy)}`);
|
|
5301
4289
|
if (configDisplay.length === 0) return pc.yellow("No configuration selected.");
|
|
5302
4290
|
return configDisplay.join("\n");
|
|
5303
4291
|
}
|
|
@@ -5427,7 +4415,7 @@ const TEMPLATE_PRESETS = {
|
|
|
5427
4415
|
frontend: ["react-router"],
|
|
5428
4416
|
api: "orpc",
|
|
5429
4417
|
auth: "better-auth",
|
|
5430
|
-
payments:
|
|
4418
|
+
payments: [],
|
|
5431
4419
|
observability: ["getmonitor"],
|
|
5432
4420
|
communication: "none",
|
|
5433
4421
|
addons: ["turborepo"],
|
|
@@ -5444,7 +4432,7 @@ const TEMPLATE_PRESETS = {
|
|
|
5444
4432
|
frontend: ["tanstack-router"],
|
|
5445
4433
|
api: "trpc",
|
|
5446
4434
|
auth: "better-auth",
|
|
5447
|
-
payments:
|
|
4435
|
+
payments: [],
|
|
5448
4436
|
observability: ["getmonitor"],
|
|
5449
4437
|
communication: "none",
|
|
5450
4438
|
addons: ["turborepo"],
|
|
@@ -5461,7 +4449,7 @@ const TEMPLATE_PRESETS = {
|
|
|
5461
4449
|
frontend: ["next"],
|
|
5462
4450
|
api: "trpc",
|
|
5463
4451
|
auth: "better-auth",
|
|
5464
|
-
payments:
|
|
4452
|
+
payments: [],
|
|
5465
4453
|
observability: ["getmonitor"],
|
|
5466
4454
|
communication: "none",
|
|
5467
4455
|
addons: ["biome", "turborepo"],
|
|
@@ -5478,7 +4466,7 @@ const TEMPLATE_PRESETS = {
|
|
|
5478
4466
|
frontend: ["native-uniwind"],
|
|
5479
4467
|
api: "none",
|
|
5480
4468
|
auth: "none",
|
|
5481
|
-
payments:
|
|
4469
|
+
payments: [],
|
|
5482
4470
|
observability: [],
|
|
5483
4471
|
communication: "none",
|
|
5484
4472
|
addons: ["none"],
|
|
@@ -5614,7 +4602,7 @@ function shouldSkipFile(fileName) {
|
|
|
5614
4602
|
}
|
|
5615
4603
|
function shouldUseBiomeFormatter(addons) {
|
|
5616
4604
|
if (!addons) return false;
|
|
5617
|
-
return addons.includes("biome")
|
|
4605
|
+
return addons.includes("biome");
|
|
5618
4606
|
}
|
|
5619
4607
|
async function formatCode(filePath, content) {
|
|
5620
4608
|
const result = await Result.tryPromise({
|
|
@@ -5670,7 +4658,7 @@ async function formatProjectWithBiome(projectDir) {
|
|
|
5670
4658
|
}
|
|
5671
4659
|
/**
|
|
5672
4660
|
* Post-scaffold format.
|
|
5673
|
-
* - biome
|
|
4661
|
+
* - biome → Biome `check --write` (not oxfmt)
|
|
5674
4662
|
* - oxlint / none → oxfmt
|
|
5675
4663
|
* Always skips kubojs.jsonrc, lockfiles, and binary extensions (oxfmt path).
|
|
5676
4664
|
*/
|
|
@@ -6118,7 +5106,7 @@ async function writeEnvFile$2(projectDir, backend, config) {
|
|
|
6118
5106
|
}
|
|
6119
5107
|
async function setupWithNeonDb(projectDir, packageManager, backend) {
|
|
6120
5108
|
const s = createSpinner();
|
|
6121
|
-
s.start("Creating Neon database using
|
|
5109
|
+
s.start("Creating Neon database using neon-new...");
|
|
6122
5110
|
const targetApp = backend === "self" ? "apps/web" : "apps/server";
|
|
6123
5111
|
const targetDir = path.join(projectDir, targetApp);
|
|
6124
5112
|
const ensureDirResult = await Result.tryPromise({
|
|
@@ -6133,17 +5121,17 @@ async function setupWithNeonDb(projectDir, packageManager, backend) {
|
|
|
6133
5121
|
s.stop(pc.red("Failed to create directory"));
|
|
6134
5122
|
return ensureDirResult;
|
|
6135
5123
|
}
|
|
6136
|
-
const packageArgs = getPackageExecutionArgs(packageManager, `
|
|
5124
|
+
const packageArgs = getPackageExecutionArgs(packageManager, `neon-new@latest --yes --ref "sbA3tIe"`);
|
|
6137
5125
|
return Result.tryPromise({
|
|
6138
5126
|
try: async () => {
|
|
6139
5127
|
await $({ cwd: targetDir })`${packageArgs}`;
|
|
6140
5128
|
s.stop(pc.green("Neon database created successfully!"));
|
|
6141
5129
|
},
|
|
6142
5130
|
catch: (e) => {
|
|
6143
|
-
s.stop(pc.red("Failed to create database with
|
|
5131
|
+
s.stop(pc.red("Failed to create database with neon-new"));
|
|
6144
5132
|
return new DatabaseSetupError({
|
|
6145
5133
|
provider: "neon",
|
|
6146
|
-
message: `Failed to create database with
|
|
5134
|
+
message: `Failed to create database with neon-new: ${e instanceof Error ? e.message : String(e)}`,
|
|
6147
5135
|
cause: e
|
|
6148
5136
|
});
|
|
6149
5137
|
}
|
|
@@ -6209,7 +5197,7 @@ async function setupNeonPostgres(config, cliInput) {
|
|
|
6209
5197
|
const promptedSetupMethod = await select({
|
|
6210
5198
|
message: "Choose your Neon setup method:",
|
|
6211
5199
|
options: [{
|
|
6212
|
-
label: "Quick setup with
|
|
5200
|
+
label: "Quick setup with neon-new",
|
|
6213
5201
|
value: "neondb",
|
|
6214
5202
|
hint: "fastest, no auth required"
|
|
6215
5203
|
}, {
|
|
@@ -7141,6 +6129,10 @@ async function getDockerStatus(database) {
|
|
|
7141
6129
|
}
|
|
7142
6130
|
//#endregion
|
|
7143
6131
|
//#region src/helpers/core/post-installation.ts
|
|
6132
|
+
const pc$1 = {
|
|
6133
|
+
...pc,
|
|
6134
|
+
cyan: cliColors.signal
|
|
6135
|
+
};
|
|
7144
6136
|
function getDesktopStaticBuildNote(frontend) {
|
|
7145
6137
|
const staticBuildFrontends = new Map([
|
|
7146
6138
|
["tanstack-start", "TanStack Start"],
|
|
@@ -7151,7 +6143,7 @@ function getDesktopStaticBuildNote(frontend) {
|
|
|
7151
6143
|
]);
|
|
7152
6144
|
const staticBuildFrontend = frontend.find((value) => staticBuildFrontends.has(value));
|
|
7153
6145
|
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.`;
|
|
6146
|
+
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
6147
|
}
|
|
7156
6148
|
async function displayPostInstallInstructions(config) {
|
|
7157
6149
|
const { api, database, relativePath, packageManager, depsInstalled, orm, addons, testing, runtime, frontend, backend, dbSetup, webDeploy, serverDeploy, observability, communication } = config;
|
|
@@ -7173,50 +6165,52 @@ async function displayPostInstallInstructions(config) {
|
|
|
7173
6165
|
const lintingInstructions = hasGitHooksOrLinting ? getLintingInstructions(runCmd) : "";
|
|
7174
6166
|
const nativeInstructions = (frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles")) && backend !== "none" ? getNativeInstructions(isConvex, isBackendSelf, frontend || [], runCmd) : "";
|
|
7175
6167
|
const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
|
|
7176
|
-
const
|
|
7177
|
-
const playwrightInstructions = testing?.includes("playwright") ? getPlaywrightInstructions(runCmd) : "";
|
|
6168
|
+
const playwrightInstructions = testing?.includes("playwright") ? getPlaywrightInstructions(runCmd, packageManager) : "";
|
|
7178
6169
|
const clerkInstructions = config.auth === "clerk" ? getClerkInstructions(frontend || [], backend, api) : "";
|
|
7179
6170
|
const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend);
|
|
7180
6171
|
const getMonitorInstructions = observability.includes("getmonitor") ? getGetMonitorInstructions() : "";
|
|
7181
6172
|
const resendInstructions = communication === "resend" ? getResendInstructions() : "";
|
|
7182
6173
|
const notifiqueInstructions = communication === "notifique" ? getNotifiqueInstructions() : "";
|
|
6174
|
+
const araraInstructions = communication === "arara" ? getAraraInstructions(backend) : "";
|
|
7183
6175
|
const hasWeb = frontend?.some((f) => types_exports.desktopWebFrontends.includes(f));
|
|
7184
6176
|
const hasNative = frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles");
|
|
7185
6177
|
const webPort = (0, types_exports.getWebPort)(frontend ?? []);
|
|
7186
6178
|
const betterAuthConvexInstructions = isConvex && config.auth === "better-auth" ? getBetterAuthConvexInstructions(hasWeb ?? false, webPort, packageManager, runCmd) : "";
|
|
7187
6179
|
const bunWebNativeWarning = packageManager === "bun" && hasNative && hasWeb ? getBunWebNativeWarning() : "";
|
|
7188
6180
|
const noOrmWarning = !isConvex && database !== "none" && orm === "none" ? getNoOrmWarning() : "";
|
|
7189
|
-
let output = `${pc.bold("Next steps")}\n${pc.cyan("1.")} ${cdCmd}\n`;
|
|
6181
|
+
let output = `${pc$1.bold("Next steps")}\n${pc$1.cyan("1.")} ${cdCmd}\n`;
|
|
7190
6182
|
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`;
|
|
6183
|
+
if (!depsInstalled) output += `${pc$1.cyan(`${stepCounter++}.`)} ${packageManager} install\n`;
|
|
6184
|
+
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
6185
|
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`;
|
|
6186
|
+
output += `${pc$1.cyan(`${stepCounter++}.`)} ${runCmd} dev:setup\n${pc$1.dim(" (this will guide you through Convex project setup)")}\n`;
|
|
6187
|
+
output += `${pc$1.cyan(`${stepCounter++}.`)} Copy environment variables from\n${pc$1.white(" packages/backend/.env.local")} to ${pc$1.white("apps/*/.env")}\n`;
|
|
6188
|
+
output += `${pc$1.cyan(`${stepCounter++}.`)} ${runCmd} dev\n\n`;
|
|
6189
|
+
} else if (isBackendSelf) output += `${pc$1.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
7198
6190
|
else {
|
|
7199
|
-
if (runtime !== "workers") output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
6191
|
+
if (runtime !== "workers") output += `${pc$1.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
7200
6192
|
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`;
|
|
6193
|
+
if (dbSetup === "d1") output += `${pc$1.yellow("IMPORTANT:")} Complete D1 database setup first\n (see Database commands below)\n`;
|
|
6194
|
+
output += `${pc$1.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
7203
6195
|
}
|
|
7204
6196
|
}
|
|
7205
6197
|
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
|
|
6198
|
+
if (hasWeb || hasStandaloneBackend) {
|
|
6199
|
+
output += `${pc$1.bold("Your project will be available at:")}\n`;
|
|
6200
|
+
if (hasWeb) output += `${pc$1.cyan("•")} Frontend: http://localhost:${webPort}\n`;
|
|
6201
|
+
else if (!hasNative) output += `${pc$1.yellow("NOTE:")} You are creating a backend-only app\n (no frontend selected)\n`;
|
|
7210
6202
|
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`;
|
|
6203
|
+
output += `${pc$1.cyan("•")} Backend API: http://localhost:3000\n`;
|
|
6204
|
+
if (api === "orpc") output += `${pc$1.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/api-reference\n`;
|
|
6205
|
+
if (api === "orval") {
|
|
6206
|
+
output += `${pc$1.cyan("•")} OpenAPI contract: apps/api/openapi.yaml\n`;
|
|
6207
|
+
output += `${pc$1.cyan("•")} Regenerate API code: bun run api:generate\n`;
|
|
6208
|
+
}
|
|
7213
6209
|
}
|
|
7214
6210
|
if (isBackendSelf && api === "orpc") {
|
|
7215
6211
|
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`;
|
|
6212
|
+
output += `${pc$1.cyan("•")} OpenAPI (Scalar UI): http://localhost:${webPort}${rpcPath}/api-reference\n`;
|
|
7217
6213
|
}
|
|
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
6214
|
}
|
|
7221
6215
|
if (nativeInstructions) output += `\n${nativeInstructions.trim()}\n`;
|
|
7222
6216
|
if (databaseInstructions) output += `\n${databaseInstructions.trim()}\n`;
|
|
@@ -7227,18 +6221,18 @@ async function displayPostInstallInstructions(config) {
|
|
|
7227
6221
|
if (vitePlusNativeHooksInstructions) output += `\n${vitePlusNativeHooksInstructions.trim()}\n`;
|
|
7228
6222
|
if (lintingInstructions) output += `\n${lintingInstructions.trim()}\n`;
|
|
7229
6223
|
if (pwaInstructions) output += `\n${pwaInstructions.trim()}\n`;
|
|
7230
|
-
if (starlightInstructions) output += `\n${starlightInstructions.trim()}\n`;
|
|
7231
6224
|
if (playwrightInstructions) output += `\n${playwrightInstructions.trim()}\n`;
|
|
7232
6225
|
if (clerkInstructions) output += `\n${clerkInstructions.trim()}\n`;
|
|
7233
6226
|
if (betterAuthConvexInstructions) output += `\n${betterAuthConvexInstructions.trim()}\n`;
|
|
7234
6227
|
if (getMonitorInstructions) output += `\n${getMonitorInstructions.trim()}\n`;
|
|
7235
6228
|
if (resendInstructions) output += `\n${resendInstructions.trim()}\n`;
|
|
7236
6229
|
if (notifiqueInstructions) output += `\n${notifiqueInstructions.trim()}\n`;
|
|
6230
|
+
if (araraInstructions) output += `\n${araraInstructions.trim()}\n`;
|
|
7237
6231
|
if (alchemyDeployInstructions) output += `\n${alchemyDeployInstructions.trim()}\n`;
|
|
7238
6232
|
if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`;
|
|
7239
6233
|
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");
|
|
6234
|
+
output += `\n${pc$1.bold("Like kubojs?")} Please consider giving us a star\n on GitHub:\n`;
|
|
6235
|
+
output += pc$1.cyan("https://github.com/albuquerquesz/kubo");
|
|
7242
6236
|
cliConsola.box(output);
|
|
7243
6237
|
}
|
|
7244
6238
|
function getNativeInstructions(isConvex, isBackendSelf, frontend, runCmd) {
|
|
@@ -7247,26 +6241,27 @@ function getNativeInstructions(isConvex, isBackendSelf, frontend, runCmd) {
|
|
|
7247
6241
|
const exampleUrl = isConvex ? "https://example.convex.cloud" : isBackendSelf ? `http://<YOUR_LOCAL_IP>:${selfBackendPort}` : "http://<YOUR_LOCAL_IP>:3000";
|
|
7248
6242
|
const envFileName = ".env";
|
|
7249
6243
|
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`;
|
|
6244
|
+
let instructions = `${pc$1.yellow("NOTE:")} For Expo connectivity issues, update\n apps/native/${envFileName} with ${ipNote}:\n ${`${envVar}=${exampleUrl}`}\n`;
|
|
6245
|
+
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`;
|
|
6246
|
+
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
6247
|
return instructions;
|
|
7254
6248
|
}
|
|
7255
6249
|
function getHuskyInstructions(runCmd) {
|
|
7256
|
-
return `${pc.bold("Git hooks with Husky:")}\n${pc.cyan("•")} Initialize hooks: ${`${runCmd} prepare`}\n`;
|
|
6250
|
+
return `${pc$1.bold("Git hooks with Husky:")}\n${pc$1.cyan("•")} Initialize hooks: ${`${runCmd} prepare`}\n`;
|
|
7257
6251
|
}
|
|
7258
|
-
function getPlaywrightInstructions(runCmd) {
|
|
7259
|
-
|
|
6252
|
+
function getPlaywrightInstructions(runCmd, packageManager) {
|
|
6253
|
+
const browserInstall = packageManager === "bun" ? "bunx playwright install" : packageManager === "pnpm" ? "pnpm exec playwright install" : "npx playwright install";
|
|
6254
|
+
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
6255
|
}
|
|
7261
6256
|
function getLintingInstructions(runCmd) {
|
|
7262
|
-
return `${pc.bold("Linting and formatting:")}\n${pc.cyan("•")} Run checks: ${`${runCmd} check`}\n`;
|
|
6257
|
+
return `${pc$1.bold("Linting and formatting:")}\n${pc$1.cyan("•")} Run checks: ${`${runCmd} check`}\n`;
|
|
7263
6258
|
}
|
|
7264
6259
|
function getLefthookInstructions(packageManager) {
|
|
7265
6260
|
const cmd = packageManager === "npm" ? "npx" : packageManager;
|
|
7266
|
-
return `${pc.bold("Git hooks with Lefthook:")}\n${pc.cyan("•")} Install hooks: ${cmd} lefthook install\n`;
|
|
6261
|
+
return `${pc$1.bold("Git hooks with Lefthook:")}\n${pc$1.cyan("•")} Install hooks: ${cmd} lefthook install\n`;
|
|
7267
6262
|
}
|
|
7268
6263
|
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`;
|
|
6264
|
+
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
6265
|
}
|
|
7271
6266
|
async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup, webDeploy, serverDeploy, backend) {
|
|
7272
6267
|
const instructions = [];
|
|
@@ -7279,62 +6274,63 @@ async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup,
|
|
|
7279
6274
|
}
|
|
7280
6275
|
}
|
|
7281
6276
|
if (isD1Alchemy) {
|
|
7282
|
-
if (orm === "drizzle") instructions.push(`${pc.cyan("•")} Generate migrations: ${`${runCmd} db:generate`}`);
|
|
6277
|
+
if (orm === "drizzle") instructions.push(`${pc$1.cyan("•")} Generate migrations: ${`${runCmd} db:generate`}`);
|
|
7283
6278
|
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`}`);
|
|
6279
|
+
instructions.push(`${pc$1.cyan("•")} Generate Prisma client: ${`${runCmd} db:generate`}`);
|
|
6280
|
+
instructions.push(`${pc$1.cyan("•")} Apply migrations: ${`${runCmd} db:migrate`}`);
|
|
7286
6281
|
}
|
|
7287
6282
|
}
|
|
7288
6283
|
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`);
|
|
6284
|
+
if (database === "mysql" && orm === "drizzle") instructions.push(`${pc$1.yellow("NOTE:")} Enable foreign key constraints in PlanetScale database settings`);
|
|
6285
|
+
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
6286
|
}
|
|
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`);
|
|
6287
|
+
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
6288
|
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`}`);
|
|
6289
|
+
if (database === "mongodb" && dbSetup === "docker") instructions.push(`${pc$1.yellow("WARNING:")} Prisma + MongoDB + Docker combination\n may not work.`);
|
|
6290
|
+
if (dbSetup === "docker") instructions.push(`${pc$1.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
7296
6291
|
if (!isD1Alchemy) {
|
|
7297
|
-
instructions.push(`${pc.cyan("•")} Generate Prisma Client: ${`${runCmd} db:generate`}`);
|
|
7298
|
-
instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
|
|
6292
|
+
instructions.push(`${pc$1.cyan("•")} Generate Prisma Client: ${`${runCmd} db:generate`}`);
|
|
6293
|
+
instructions.push(`${pc$1.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
|
|
7299
6294
|
}
|
|
7300
|
-
if (!isD1Alchemy) instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
|
|
6295
|
+
if (!isD1Alchemy) instructions.push(`${pc$1.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
|
|
7301
6296
|
} 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`}`);
|
|
6297
|
+
if (dbSetup === "docker") instructions.push(`${pc$1.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
6298
|
+
if (!isD1Alchemy) instructions.push(`${pc$1.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
|
|
6299
|
+
if (!isD1Alchemy) instructions.push(`${pc$1.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
|
|
7305
6300
|
} 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")}` : "";
|
|
6301
|
+
if (dbSetup === "docker") instructions.push(`${pc$1.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
6302
|
+
} else if (orm === "none") instructions.push(`${pc$1.yellow("NOTE:")} Manual database schema setup\n required.`);
|
|
6303
|
+
return instructions.length ? `${pc$1.bold("Database commands:")}\n${instructions.join("\n")}` : "";
|
|
7309
6304
|
}
|
|
7310
6305
|
function getTauriInstructions(runCmd, frontend) {
|
|
7311
6306
|
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}` : ""}`;
|
|
6307
|
+
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
6308
|
}
|
|
7314
6309
|
function getElectrobunInstructions(runCmd, frontend) {
|
|
7315
6310
|
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}` : ""}`;
|
|
6311
|
+
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
6312
|
}
|
|
7318
6313
|
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`}`;
|
|
6314
|
+
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
6315
|
}
|
|
7324
6316
|
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.`;
|
|
6317
|
+
return `\n${pc$1.yellow("WARNING:")} Database selected without an ORM. Features requiring\n database access (e.g., examples, auth) need manual setup.`;
|
|
7326
6318
|
}
|
|
7327
6319
|
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")}`;
|
|
6320
|
+
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
6321
|
}
|
|
7330
6322
|
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")}`;
|
|
6323
|
+
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
6324
|
}
|
|
7333
6325
|
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")}`;
|
|
6326
|
+
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")}`;
|
|
6327
|
+
}
|
|
6328
|
+
function getAraraInstructions(backend) {
|
|
6329
|
+
const envPath = backend === "convex" ? "packages/backend/.env.local" : "the server .env";
|
|
6330
|
+
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
6331
|
}
|
|
7336
6332
|
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.`;
|
|
6333
|
+
return `\n${pc$1.yellow("WARNING:")} 'bun' might cause issues with web + native apps in a monorepo.\n Use 'pnpm' if problems arise.`;
|
|
7338
6334
|
}
|
|
7339
6335
|
function getClerkQuickstartUrl(frontend) {
|
|
7340
6336
|
if (frontend.includes("next")) return "https://clerk.com/docs/nextjs/getting-started/quickstart";
|
|
@@ -7390,28 +6386,28 @@ function getClerkInstructionLines(frontend, backend, api) {
|
|
|
7390
6386
|
}
|
|
7391
6387
|
function getClerkInstructions(frontend, backend, api) {
|
|
7392
6388
|
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}`)
|
|
6389
|
+
`${pc$1.bold("Clerk Authentication Setup:")}`,
|
|
6390
|
+
`${pc$1.cyan("•")} Follow the guide: ${pc$1.underline(getClerkQuickstartUrl(frontend))}`,
|
|
6391
|
+
...getClerkInstructionLines(frontend, backend, api).map((line) => `${pc$1.cyan("•")} ${line}`)
|
|
7396
6392
|
].join("\n");
|
|
7397
6393
|
}
|
|
7398
6394
|
function getBetterAuthConvexInstructions(hasWeb, webPort, packageManager, runCmd) {
|
|
7399
6395
|
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` : "");
|
|
6396
|
+
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
6397
|
}
|
|
7402
6398
|
function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend) {
|
|
7403
6399
|
const instructions = [];
|
|
7404
6400
|
const isBackendSelf = backend === "self";
|
|
7405
6401
|
if (webDeploy === "cloudflare" && serverDeploy !== "cloudflare" && !isBackendSelf) {
|
|
7406
6402
|
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`}`);
|
|
6403
|
+
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
6404
|
} else if (serverDeploy === "cloudflare" && webDeploy !== "cloudflare" && !isBackendSelf) {
|
|
7409
6405
|
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`}`);
|
|
6406
|
+
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`}`);
|
|
6407
|
+
} 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
6408
|
if (webDeploy === "docker" || serverDeploy === "docker") {
|
|
7413
6409
|
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`);
|
|
6410
|
+
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
6411
|
}
|
|
7416
6412
|
if (webDeploy === "vercel" || serverDeploy === "vercel") {
|
|
7417
6413
|
const mixedWithCloudflare = webDeploy !== "none" && serverDeploy !== "none" && webDeploy !== serverDeploy;
|
|
@@ -7419,14 +6415,14 @@ function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend)
|
|
|
7419
6415
|
const vercelEnvScript = "env:production";
|
|
7420
6416
|
const vercelDeployScript = mixedWithCloudflare ? webDeploy === "vercel" ? "deploy:web:prod" : "deploy:server:prod" : "deploy:prod";
|
|
7421
6417
|
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`);
|
|
6418
|
+
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
6419
|
}
|
|
7424
6420
|
if (webDeploy === "guaracloud" || serverDeploy === "guaracloud") {
|
|
7425
6421
|
const deployScript = webDeploy !== "none" && serverDeploy !== "none" && webDeploy !== serverDeploy || webDeploy === "guaracloud" && serverDeploy === "guaracloud" ? `deploy:${webDeploy === "guaracloud" ? "web" : "server"}` : "deploy";
|
|
7426
6422
|
const linkScript = `${deployScript}:link`;
|
|
7427
6423
|
const logsScript = `${deployScript}:logs`;
|
|
7428
6424
|
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/`);
|
|
6425
|
+
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
6426
|
}
|
|
7431
6427
|
return instructions.length ? `\n${instructions.join("\n")}` : "";
|
|
7432
6428
|
}
|
|
@@ -7492,10 +6488,13 @@ async function createProject(options, cliInput = {}) {
|
|
|
7492
6488
|
}));
|
|
7493
6489
|
yield* Result.await(formatProject(projectDir, { addons: options.addons }));
|
|
7494
6490
|
if (!isSilent()) log.success("Project template successfully scaffolded!");
|
|
7495
|
-
if (options.install)
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
6491
|
+
if (options.install) {
|
|
6492
|
+
yield* Result.await(installDependencies({
|
|
6493
|
+
projectDir,
|
|
6494
|
+
packageManager: options.packageManager
|
|
6495
|
+
}));
|
|
6496
|
+
if (options.testing.includes("playwright")) await installPlaywrightBrowsers(projectDir, options.packageManager);
|
|
6497
|
+
}
|
|
7499
6498
|
yield* Result.await(initializeGit(projectDir, options.git));
|
|
7500
6499
|
if (!isSilent()) await displayPostInstallInstructions({
|
|
7501
6500
|
...options,
|
|
@@ -7568,7 +6567,7 @@ function createEmptyResult(timeScaffolded, elapsedTimeMs, error) {
|
|
|
7568
6567
|
examples: [],
|
|
7569
6568
|
testing: [],
|
|
7570
6569
|
auth: "none",
|
|
7571
|
-
payments:
|
|
6570
|
+
payments: [],
|
|
7572
6571
|
observability: [],
|
|
7573
6572
|
communication: "none",
|
|
7574
6573
|
git: false,
|
|
@@ -7608,7 +6607,7 @@ async function createProjectHandler(input, options = {}) {
|
|
|
7608
6607
|
async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
7609
6608
|
return Result.gen(async function* () {
|
|
7610
6609
|
if (!isSilent() && input.renderTitle !== false) renderTitle();
|
|
7611
|
-
if (!isSilent()) intro(
|
|
6610
|
+
if (!isSilent()) intro(cliColors.bright("Creating a new kubojs project"));
|
|
7612
6611
|
if (!isSilent() && input.yolo) cliConsola.fatal("YOLO mode enabled - skipping checks. Things may break!");
|
|
7613
6612
|
let currentPathInput;
|
|
7614
6613
|
if (isSilent()) currentPathInput = yield* Result.await(resolveProjectNameForSilent(input));
|
|
@@ -7670,7 +6669,7 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
7670
6669
|
const templateName = input.template.toUpperCase();
|
|
7671
6670
|
const templateDescription = getTemplateDescription(input.template);
|
|
7672
6671
|
if (!isSilent()) {
|
|
7673
|
-
log.message(pc.bold(
|
|
6672
|
+
log.message(pc.bold(cliColors.bright(`Using template: ${pc.white(templateName)}`)));
|
|
7674
6673
|
log.message(pc.dim(` ${templateDescription}`));
|
|
7675
6674
|
}
|
|
7676
6675
|
const userOverrides = {};
|
|
@@ -7753,7 +6752,7 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
7753
6752
|
log.success(pc.green("Dry run validation passed. No files were written."));
|
|
7754
6753
|
log.message(pc.dim(`Target directory: ${finalResolvedPath}`));
|
|
7755
6754
|
log.message(pc.dim(`Run without --dry-run to create the project.`));
|
|
7756
|
-
outro(
|
|
6755
|
+
outro(cliColors.signal("Dry run complete."));
|
|
7757
6756
|
}
|
|
7758
6757
|
return Result.ok({
|
|
7759
6758
|
success: true,
|
|
@@ -7769,14 +6768,14 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
7769
6768
|
manualDb: cliInput.manualDb ?? input.manualDb,
|
|
7770
6769
|
dbSetupOptions: effectiveDbSetupOptions
|
|
7771
6770
|
}));
|
|
7772
|
-
if (!isSilent()) log.success(
|
|
6771
|
+
if (!isSilent()) log.success(cliColors.bright(`You can reproduce this setup with the following command:\n${reproducibleCommand}`));
|
|
7773
6772
|
await trackProjectCreation(config, input.disableAnalytics);
|
|
7774
6773
|
const historyResult = await addToHistory(config, reproducibleCommand);
|
|
7775
6774
|
if (historyResult.isErr() && !isSilent()) log.warn(pc.yellow(historyResult.error.message));
|
|
7776
6775
|
const elapsedTimeMs = Date.now() - startTime;
|
|
7777
6776
|
if (!isSilent()) {
|
|
7778
6777
|
const elapsedTimeInSeconds = (elapsedTimeMs / 1e3).toFixed(2);
|
|
7779
|
-
outro(
|
|
6778
|
+
outro(cliColors.signal(`Project created successfully in ${pc.bold(elapsedTimeInSeconds)} seconds!`));
|
|
7780
6779
|
}
|
|
7781
6780
|
return Result.ok({
|
|
7782
6781
|
success: true,
|
|
@@ -8085,6 +7084,8 @@ async function builder() {
|
|
|
8085
7084
|
* ```
|
|
8086
7085
|
*/
|
|
8087
7086
|
async function createVirtual(options) {
|
|
7087
|
+
const rawPayments = options.payments;
|
|
7088
|
+
const rawObservability = options.observability;
|
|
8088
7089
|
const config = {
|
|
8089
7090
|
projectName: options.projectName || "my-project",
|
|
8090
7091
|
projectDir: "/virtual",
|
|
@@ -8100,8 +7101,8 @@ async function createVirtual(options) {
|
|
|
8100
7101
|
examples: options.examples || [],
|
|
8101
7102
|
testing: options.testing || [],
|
|
8102
7103
|
auth: options.auth || "none",
|
|
8103
|
-
payments:
|
|
8104
|
-
observability: options.disableObservability ? [] :
|
|
7104
|
+
payments: rawPayments === "none" ? [] : Array.isArray(rawPayments) ? rawPayments : rawPayments ? [rawPayments] : [],
|
|
7105
|
+
observability: options.disableObservability ? [] : rawObservability === "none" ? [] : Array.isArray(rawObservability) ? rawObservability : rawObservability ? [rawObservability] : [],
|
|
8105
7106
|
communication: options.communication || "none",
|
|
8106
7107
|
git: options.git ?? false,
|
|
8107
7108
|
packageManager: options.packageManager || "bun",
|