create-kubojs 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/cli.mjs +26 -21
- package/dist/index.d.mts +20 -15
- package/dist/index.mjs +2 -2
- package/dist/{src-BAYXN3Za.mjs → src-CNqGe5RA.mjs} +588 -360
- package/package.json +5 -4
|
@@ -22,6 +22,7 @@ import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt,
|
|
|
22
22
|
import { applyEdits, modify, parse } from "jsonc-parser";
|
|
23
23
|
import os from "node:os";
|
|
24
24
|
import { format } from "oxfmt";
|
|
25
|
+
import { Generator, configSchema } from "@tanstack/router-generator";
|
|
25
26
|
//#region src/utils/get-package-manager.ts
|
|
26
27
|
const getUserPkgManager = () => {
|
|
27
28
|
const userAgent = process.env.npm_config_user_agent;
|
|
@@ -42,9 +43,11 @@ const DEFAULT_CONFIG_BASE = {
|
|
|
42
43
|
orm: "drizzle",
|
|
43
44
|
auth: "better-auth",
|
|
44
45
|
payments: "none",
|
|
45
|
-
observability: "
|
|
46
|
+
observability: ["getmonitor"],
|
|
47
|
+
communication: "none",
|
|
46
48
|
addons: ["turborepo"],
|
|
47
49
|
examples: [],
|
|
50
|
+
testing: [],
|
|
48
51
|
git: true,
|
|
49
52
|
install: true,
|
|
50
53
|
dbSetup: "none",
|
|
@@ -61,7 +64,8 @@ function getDefaultConfig() {
|
|
|
61
64
|
packageManager: getUserPkgManager(),
|
|
62
65
|
frontend: [...DEFAULT_CONFIG_BASE.frontend],
|
|
63
66
|
addons: [...DEFAULT_CONFIG_BASE.addons],
|
|
64
|
-
examples: [...DEFAULT_CONFIG_BASE.examples]
|
|
67
|
+
examples: [...DEFAULT_CONFIG_BASE.examples],
|
|
68
|
+
testing: [...DEFAULT_CONFIG_BASE.testing]
|
|
65
69
|
};
|
|
66
70
|
}
|
|
67
71
|
const DEFAULT_CONFIG = getDefaultConfig();
|
|
@@ -383,9 +387,11 @@ async function addToHistory(config, reproducibleCommand) {
|
|
|
383
387
|
auth: config.auth,
|
|
384
388
|
payments: config.payments,
|
|
385
389
|
observability: config.observability,
|
|
390
|
+
communication: config.communication,
|
|
386
391
|
api: config.api,
|
|
387
392
|
addons: config.addons,
|
|
388
393
|
examples: config.examples,
|
|
394
|
+
testing: config.testing,
|
|
389
395
|
dbSetup: config.dbSetup,
|
|
390
396
|
packageManager: config.packageManager
|
|
391
397
|
},
|
|
@@ -516,169 +522,6 @@ async function openUrl(url) {
|
|
|
516
522
|
await $({ stdio: "ignore" })`xdg-open ${url}`;
|
|
517
523
|
}
|
|
518
524
|
//#endregion
|
|
519
|
-
//#region src/utils/sponsors.ts
|
|
520
|
-
const SPONSORS_JSON_URL = "https://sponsors.kubojs.dev/sponsors.json";
|
|
521
|
-
const GITHUB_SPONSOR_URL = "https://github.com/sponsors/AmanVarshney01";
|
|
522
|
-
const nullableString = z.string().nullish().transform((value) => value ?? void 0);
|
|
523
|
-
const nullableNumber = z.number().nullish().transform((value) => value ?? void 0);
|
|
524
|
-
const sponsorSchema = z.object({
|
|
525
|
-
name: nullableString,
|
|
526
|
-
githubId: z.string(),
|
|
527
|
-
avatarUrl: z.string(),
|
|
528
|
-
websiteUrl: nullableString,
|
|
529
|
-
githubUrl: z.string(),
|
|
530
|
-
tierName: nullableString,
|
|
531
|
-
sinceWhen: z.string(),
|
|
532
|
-
transactionCount: z.number(),
|
|
533
|
-
totalProcessedAmount: nullableNumber,
|
|
534
|
-
formattedAmount: nullableString
|
|
535
|
-
});
|
|
536
|
-
const sponsorSummarySchema = z.object({
|
|
537
|
-
total_sponsors: z.number(),
|
|
538
|
-
total_lifetime_amount: z.number(),
|
|
539
|
-
total_current_monthly: z.number(),
|
|
540
|
-
special_sponsors: z.number(),
|
|
541
|
-
current_sponsors: z.number(),
|
|
542
|
-
past_sponsors: z.number(),
|
|
543
|
-
backers: z.number(),
|
|
544
|
-
top_sponsor: z.object({
|
|
545
|
-
name: z.string(),
|
|
546
|
-
amount: z.number()
|
|
547
|
-
}).nullish().transform((value) => value ?? void 0)
|
|
548
|
-
});
|
|
549
|
-
const sponsorEntrySchema = z.object({
|
|
550
|
-
generated_at: z.string(),
|
|
551
|
-
summary: sponsorSummarySchema,
|
|
552
|
-
specialSponsors: z.array(sponsorSchema),
|
|
553
|
-
sponsors: z.array(sponsorSchema),
|
|
554
|
-
pastSponsors: z.array(sponsorSchema),
|
|
555
|
-
backers: z.array(sponsorSchema)
|
|
556
|
-
});
|
|
557
|
-
async function fetchSponsors(url = SPONSORS_JSON_URL) {
|
|
558
|
-
return fetchSponsorsData({
|
|
559
|
-
url,
|
|
560
|
-
withSpinner: true
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
async function fetchSponsorsQuietly({ url = SPONSORS_JSON_URL, timeoutMs = 1500 } = {}) {
|
|
564
|
-
return fetchSponsorsData({
|
|
565
|
-
url,
|
|
566
|
-
withSpinner: false,
|
|
567
|
-
timeoutMs
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
function displaySponsors(sponsors) {
|
|
571
|
-
const { total_sponsors } = sponsors.summary;
|
|
572
|
-
if (total_sponsors === 0) {
|
|
573
|
-
log.info("No sponsors found. You can be the first one! ✨");
|
|
574
|
-
outro(pc.cyan(`Visit ${GITHUB_SPONSOR_URL} to become a sponsor.`));
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
displaySponsorsBox(sponsors);
|
|
578
|
-
if (total_sponsors - sponsors.specialSponsors.length > 0) log.message(pc.blue(`+${total_sponsors - sponsors.specialSponsors.length} more amazing sponsors.\n`));
|
|
579
|
-
outro(pc.magenta(`Visit ${GITHUB_SPONSOR_URL} to become a sponsor.`));
|
|
580
|
-
}
|
|
581
|
-
function displaySponsorsBox(sponsors) {
|
|
582
|
-
if (sponsors.specialSponsors.length === 0) return;
|
|
583
|
-
let output = `${pc.bold(pc.cyan("-> Special Sponsors"))}\n\n`;
|
|
584
|
-
sponsors.specialSponsors.forEach((sponsor, idx) => {
|
|
585
|
-
const displayName = sponsor.name ?? sponsor.githubId;
|
|
586
|
-
const tier = sponsor.tierName ? ` ${pc.yellow(`(${sponsor.tierName})`)}` : "";
|
|
587
|
-
output += `${pc.green(`• ${displayName}`)}${tier}\n`;
|
|
588
|
-
output += ` ${pc.dim("GitHub:")} https://github.com/${sponsor.githubId}\n`;
|
|
589
|
-
const website = sponsor.websiteUrl ?? sponsor.githubUrl;
|
|
590
|
-
if (website) output += ` ${pc.dim("Website:")} ${website}\n`;
|
|
591
|
-
if (idx < sponsors.specialSponsors.length - 1) output += "\n";
|
|
592
|
-
});
|
|
593
|
-
cliConsola.box(output);
|
|
594
|
-
}
|
|
595
|
-
function formatPostInstallSpecialSponsorsSection(sponsors) {
|
|
596
|
-
if (sponsors.specialSponsors.length === 0) return "";
|
|
597
|
-
const wrappedSponsorLines = wrapSponsorTokens(sponsors.specialSponsors.map((sponsor) => {
|
|
598
|
-
return `• ${sponsor.name ?? sponsor.githubId}`;
|
|
599
|
-
}), getPostInstallSponsorLineWidth());
|
|
600
|
-
let output = `${pc.bold("Special sponsors")}\n`;
|
|
601
|
-
wrappedSponsorLines.forEach((line) => {
|
|
602
|
-
output += `${line}\n`;
|
|
603
|
-
});
|
|
604
|
-
return output.trimEnd();
|
|
605
|
-
}
|
|
606
|
-
function getPostInstallSponsorLineWidth() {
|
|
607
|
-
const terminalWidth = process.stdout.columns;
|
|
608
|
-
if (!terminalWidth || terminalWidth <= 0) return 72;
|
|
609
|
-
const availableWidth = Math.max(8, terminalWidth - 24);
|
|
610
|
-
return Math.min(72, availableWidth);
|
|
611
|
-
}
|
|
612
|
-
function wrapSponsorTokens(tokens, maxLineWidth) {
|
|
613
|
-
const lines = [];
|
|
614
|
-
const separator = " ";
|
|
615
|
-
let currentLine = "";
|
|
616
|
-
tokens.forEach((token) => {
|
|
617
|
-
const candidateLine = currentLine ? `${currentLine}${separator}${token}` : token;
|
|
618
|
-
if (candidateLine.length <= maxLineWidth) {
|
|
619
|
-
currentLine = candidateLine;
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
622
|
-
if (currentLine) {
|
|
623
|
-
lines.push(currentLine);
|
|
624
|
-
currentLine = token;
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
lines.push(token);
|
|
628
|
-
});
|
|
629
|
-
if (currentLine) lines.push(currentLine);
|
|
630
|
-
return lines;
|
|
631
|
-
}
|
|
632
|
-
async function fetchSponsorsData({ url = SPONSORS_JSON_URL, withSpinner = false, timeoutMs }) {
|
|
633
|
-
const s = withSpinner ? spinner() : null;
|
|
634
|
-
if (s) s.start("Fetching sponsors…");
|
|
635
|
-
const controller = timeoutMs ? new AbortController() : null;
|
|
636
|
-
const timeout = timeoutMs ? setTimeout(() => {
|
|
637
|
-
controller?.abort();
|
|
638
|
-
}, timeoutMs) : null;
|
|
639
|
-
try {
|
|
640
|
-
const response = await fetch(url, controller ? { signal: controller.signal } : void 0);
|
|
641
|
-
if (!response.ok) {
|
|
642
|
-
const message = `Failed to fetch sponsors: ${response.statusText || String(response.status)}`;
|
|
643
|
-
if (s) s.stop(pc.red(message));
|
|
644
|
-
return Result.err(new CLIError({ message }));
|
|
645
|
-
}
|
|
646
|
-
const rawSponsors = await response.json();
|
|
647
|
-
const parseResult = sponsorEntrySchema.safeParse(rawSponsors);
|
|
648
|
-
if (!parseResult.success) {
|
|
649
|
-
const message = `Failed to fetch sponsors: invalid response format at "${parseResult.error.issues[0]?.path?.join(".") || "unknown"}"`;
|
|
650
|
-
if (s) s.stop(pc.red(message));
|
|
651
|
-
return Result.err(new CLIError({
|
|
652
|
-
message,
|
|
653
|
-
cause: parseResult.error
|
|
654
|
-
}));
|
|
655
|
-
}
|
|
656
|
-
if (s) s.stop("Sponsors fetched successfully!");
|
|
657
|
-
return Result.ok(parseResult.data);
|
|
658
|
-
} catch (error) {
|
|
659
|
-
const normalizedError = normalizeSponsorFetchError(error);
|
|
660
|
-
if (s) s.stop(pc.red(normalizedError.message));
|
|
661
|
-
return Result.err(normalizedError);
|
|
662
|
-
} finally {
|
|
663
|
-
if (timeout) clearTimeout(timeout);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
function normalizeSponsorFetchError(error) {
|
|
667
|
-
if (error instanceof Error && error.name === "AbortError") return new CLIError({
|
|
668
|
-
message: "Failed to fetch sponsors: request timed out",
|
|
669
|
-
cause: error
|
|
670
|
-
});
|
|
671
|
-
if (CLIError.is(error)) return error;
|
|
672
|
-
if (error instanceof Error) return new CLIError({
|
|
673
|
-
message: error.message.startsWith("Failed to fetch sponsors:") ? error.message : `Failed to fetch sponsors: ${error.message}`,
|
|
674
|
-
cause: error
|
|
675
|
-
});
|
|
676
|
-
return new CLIError({
|
|
677
|
-
message: `Failed to fetch sponsors: ${String(error)}`,
|
|
678
|
-
cause: error
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
//#endregion
|
|
682
525
|
//#region src/commands/meta.ts
|
|
683
526
|
const DOCS_URL = "https://kubojs.dev/docs";
|
|
684
527
|
const BUILDER_URL = "https://kubojs.dev/new";
|
|
@@ -689,17 +532,6 @@ async function openExternalUrl(url, successMessage) {
|
|
|
689
532
|
})).isOk()) log.success(pc.blue(successMessage));
|
|
690
533
|
else log.message(`Please visit ${url}`);
|
|
691
534
|
}
|
|
692
|
-
async function showSponsorsCommand() {
|
|
693
|
-
renderTitle();
|
|
694
|
-
intro(pc.magenta("I dont know Sponsors"));
|
|
695
|
-
const sponsorsResult = await fetchSponsors();
|
|
696
|
-
if (sponsorsResult.isErr()) {
|
|
697
|
-
displayError(sponsorsResult.error);
|
|
698
|
-
process.exit(1);
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
displaySponsors(sponsorsResult.value);
|
|
702
|
-
}
|
|
703
535
|
async function openDocsCommand() {
|
|
704
536
|
await openExternalUrl(DOCS_URL, "Opened docs in your default browser.");
|
|
705
537
|
}
|
|
@@ -730,7 +562,35 @@ const TASK_RUNNER_ADDONS$1 = [
|
|
|
730
562
|
"nx",
|
|
731
563
|
"vite-plus"
|
|
732
564
|
];
|
|
565
|
+
/** Mutually exclusive code-quality linters (one slot). */
|
|
566
|
+
const LINTER_ADDONS = [
|
|
567
|
+
"biome",
|
|
568
|
+
"oxlint",
|
|
569
|
+
"ultracite"
|
|
570
|
+
];
|
|
733
571
|
const STATIC_DESKTOP_ADDONS = ["tauri", "electrobun"];
|
|
572
|
+
function isLinterAddon(addon) {
|
|
573
|
+
return LINTER_ADDONS.includes(addon);
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* When adding a code-quality linter, drop sibling linters so only one remains.
|
|
577
|
+
* Last-selected linter wins. Task runners stay validation-only (reject on dual).
|
|
578
|
+
*/
|
|
579
|
+
function mergeAddonsExclusive(existingAddons, addonsToAdd) {
|
|
580
|
+
const removed = /* @__PURE__ */ new Set();
|
|
581
|
+
let result = [...existingAddons];
|
|
582
|
+
for (const addon of addonsToAdd) {
|
|
583
|
+
if (LINTER_ADDONS.includes(addon)) {
|
|
584
|
+
for (const current of result) if (LINTER_ADDONS.includes(current) && current !== addon) removed.add(current);
|
|
585
|
+
result = result.filter((current) => !LINTER_ADDONS.includes(current) || current === addon);
|
|
586
|
+
}
|
|
587
|
+
if (!result.includes(addon) && addon !== "none") result.push(addon);
|
|
588
|
+
}
|
|
589
|
+
return {
|
|
590
|
+
updatedAddons: result,
|
|
591
|
+
removedAddons: [...removed]
|
|
592
|
+
};
|
|
593
|
+
}
|
|
734
594
|
const TAURI_STATIC_EXPORT_FRONTENDS = ["next", "tanstack-start"];
|
|
735
595
|
const CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS = [
|
|
736
596
|
"nuxt",
|
|
@@ -753,6 +613,13 @@ function validationErr$1(message) {
|
|
|
753
613
|
function isWebFrontend(value) {
|
|
754
614
|
return WEB_FRAMEWORKS.includes(value);
|
|
755
615
|
}
|
|
616
|
+
function isPlaywrightAllowed(frontends = []) {
|
|
617
|
+
return frontends.some((frontend) => isWebFrontend(frontend));
|
|
618
|
+
}
|
|
619
|
+
function validateTestingAgainstFrontends(testing = [], frontends = []) {
|
|
620
|
+
if (testing.includes("playwright") && !isPlaywrightAllowed(frontends)) return validationErr$1("playwright testing requires a web frontend");
|
|
621
|
+
return Result.ok(void 0);
|
|
622
|
+
}
|
|
756
623
|
function splitFrontends(values = []) {
|
|
757
624
|
return {
|
|
758
625
|
web: values.filter((f) => isWebFrontend(f)),
|
|
@@ -942,6 +809,7 @@ function getCompatibleAddons(allAddons, frontend, existingAddons = [], auth, bac
|
|
|
942
809
|
}
|
|
943
810
|
function validateAddonsAgainstFrontends(addons = [], frontends = [], auth, backend, runtime) {
|
|
944
811
|
if (addons.filter((addon) => TASK_RUNNER_ADDONS$1.includes(addon)).length > 1) return validationErr$1("Cannot combine 'turborepo', 'nx', and 'vite-plus' addons. Choose one task runner.");
|
|
812
|
+
if ([...new Set(addons.filter((addon) => LINTER_ADDONS.includes(addon)))].length > 1) return validationErr$1("Cannot combine 'biome', 'oxlint', and 'ultracite' addons. Choose one code-quality linter.");
|
|
945
813
|
for (const addon of addons) {
|
|
946
814
|
if (addon === "none") continue;
|
|
947
815
|
const { isCompatible, reason } = validateAddonCompatibility(addon, frontends, auth, backend, runtime);
|
|
@@ -952,6 +820,11 @@ function validateAddonsAgainstFrontends(addons = [], frontends = [], auth, backe
|
|
|
952
820
|
function validateAddonsAgainstConfig(addons = [], config) {
|
|
953
821
|
return validateAddonsAgainstFrontends(addons, config.frontend ?? [], config.auth, config.backend, config.runtime);
|
|
954
822
|
}
|
|
823
|
+
function validateCommunicationCompatibility(communication, backend) {
|
|
824
|
+
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'.`);
|
|
826
|
+
return Result.ok(void 0);
|
|
827
|
+
}
|
|
955
828
|
function validatePaymentsCompatibility(payments, auth, backend, frontends = [], orm, database) {
|
|
956
829
|
if (!payments || payments === "none") return Result.ok(void 0);
|
|
957
830
|
if (payments === "abacatepay") {
|
|
@@ -1297,7 +1170,7 @@ function getAddonDisplay(addon) {
|
|
|
1297
1170
|
break;
|
|
1298
1171
|
case "mcp":
|
|
1299
1172
|
label = "MCP";
|
|
1300
|
-
hint = "Install MCP servers, including
|
|
1173
|
+
hint = "Install MCP servers, including kubojs, via add-mcp";
|
|
1301
1174
|
break;
|
|
1302
1175
|
case "evlog":
|
|
1303
1176
|
label = "evlog";
|
|
@@ -1363,6 +1236,11 @@ function validateAddonSelection(selected) {
|
|
|
1363
1236
|
"nx",
|
|
1364
1237
|
"vite-plus"
|
|
1365
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.";
|
|
1366
1244
|
}
|
|
1367
1245
|
async function getAddonsChoice(addons, frontends, auth, backend, runtime, previousValue) {
|
|
1368
1246
|
if (addons !== void 0) return addons;
|
|
@@ -1414,14 +1292,34 @@ async function getAddonsToAdd(config) {
|
|
|
1414
1292
|
return response;
|
|
1415
1293
|
}
|
|
1416
1294
|
//#endregion
|
|
1417
|
-
//#region src/utils/
|
|
1418
|
-
|
|
1295
|
+
//#region src/utils/input-hardening.ts
|
|
1296
|
+
function hasControlCharacters(value) {
|
|
1297
|
+
for (const char of value) {
|
|
1298
|
+
const charCode = char.charCodeAt(0);
|
|
1299
|
+
if (charCode < 32 || charCode === 127) return true;
|
|
1300
|
+
}
|
|
1301
|
+
return false;
|
|
1302
|
+
}
|
|
1303
|
+
function hardeningError(field, value, message) {
|
|
1304
|
+
return Result.err(new ValidationError({
|
|
1305
|
+
field,
|
|
1306
|
+
value,
|
|
1307
|
+
message
|
|
1308
|
+
}));
|
|
1309
|
+
}
|
|
1310
|
+
function validateAgentSafePathInput(value, field) {
|
|
1311
|
+
if (hasControlCharacters(value)) return hardeningError(field, value, `Invalid ${field}: control characters are not allowed.`);
|
|
1312
|
+
return Result.ok(void 0);
|
|
1313
|
+
}
|
|
1314
|
+
//#endregion
|
|
1315
|
+
//#region src/utils/kubojs-config.ts
|
|
1316
|
+
const KUBOJS_CONFIG_FILE = "kubojs.jsonrc";
|
|
1419
1317
|
/**
|
|
1420
|
-
* Reads the
|
|
1318
|
+
* Reads the kubojs configuration file from the project directory.
|
|
1421
1319
|
*/
|
|
1422
|
-
async function
|
|
1320
|
+
async function readKubojsConfig(projectDir) {
|
|
1423
1321
|
try {
|
|
1424
|
-
const configPath = path.join(projectDir,
|
|
1322
|
+
const configPath = path.join(projectDir, KUBOJS_CONFIG_FILE);
|
|
1425
1323
|
if (!await fs.pathExists(configPath)) return null;
|
|
1426
1324
|
return parse(await fs.readFile(configPath, "utf-8"));
|
|
1427
1325
|
} catch {
|
|
@@ -1429,11 +1327,11 @@ async function readBtsConfig(projectDir) {
|
|
|
1429
1327
|
}
|
|
1430
1328
|
}
|
|
1431
1329
|
/**
|
|
1432
|
-
* Updates specific fields in the
|
|
1330
|
+
* Updates specific fields in the kubojs configuration file.
|
|
1433
1331
|
*/
|
|
1434
|
-
async function
|
|
1332
|
+
async function updateKubojsConfig(projectDir, updates) {
|
|
1435
1333
|
try {
|
|
1436
|
-
const configPath = path.join(projectDir,
|
|
1334
|
+
const configPath = path.join(projectDir, KUBOJS_CONFIG_FILE);
|
|
1437
1335
|
if (!await fs.pathExists(configPath)) return;
|
|
1438
1336
|
let content = await fs.readFile(configPath, "utf-8");
|
|
1439
1337
|
for (const [key, value] of Object.entries(updates)) {
|
|
@@ -1444,26 +1342,6 @@ async function updateBtsConfig(projectDir, updates) {
|
|
|
1444
1342
|
} catch {}
|
|
1445
1343
|
}
|
|
1446
1344
|
//#endregion
|
|
1447
|
-
//#region src/utils/input-hardening.ts
|
|
1448
|
-
function hasControlCharacters(value) {
|
|
1449
|
-
for (const char of value) {
|
|
1450
|
-
const charCode = char.charCodeAt(0);
|
|
1451
|
-
if (charCode < 32 || charCode === 127) return true;
|
|
1452
|
-
}
|
|
1453
|
-
return false;
|
|
1454
|
-
}
|
|
1455
|
-
function hardeningError(field, value, message) {
|
|
1456
|
-
return Result.err(new ValidationError({
|
|
1457
|
-
field,
|
|
1458
|
-
value,
|
|
1459
|
-
message
|
|
1460
|
-
}));
|
|
1461
|
-
}
|
|
1462
|
-
function validateAgentSafePathInput(value, field) {
|
|
1463
|
-
if (hasControlCharacters(value)) return hardeningError(field, value, `Invalid ${field}: control characters are not allowed.`);
|
|
1464
|
-
return Result.ok(void 0);
|
|
1465
|
-
}
|
|
1466
|
-
//#endregion
|
|
1467
1345
|
//#region src/utils/add-package-deps.ts
|
|
1468
1346
|
const addPackageDependency = async (opts) => {
|
|
1469
1347
|
const { dependencies = [], devDependencies = [], customDependencies = {}, customDevDependencies = {}, projectDir } = opts;
|
|
@@ -2318,7 +2196,7 @@ async function setupFumadocs(config) {
|
|
|
2318
2196
|
"--no-git"
|
|
2319
2197
|
];
|
|
2320
2198
|
if (isNextTemplate) options.push("--src");
|
|
2321
|
-
const persistedConfig = await
|
|
2199
|
+
const persistedConfig = await readKubojsConfig(projectDir);
|
|
2322
2200
|
const linter = getFumadocsLinter(getFumadocsAddonContext(config.addons, persistedConfig?.addons));
|
|
2323
2201
|
if (linter) options.push(`--linter ${linter}`);
|
|
2324
2202
|
if (search) options.push(`--search ${search}`);
|
|
@@ -2450,7 +2328,7 @@ function getAllMcpServers(config) {
|
|
|
2450
2328
|
return [
|
|
2451
2329
|
{
|
|
2452
2330
|
key: "kubojs",
|
|
2453
|
-
label: "
|
|
2331
|
+
label: "kubojs",
|
|
2454
2332
|
name: "kubojs",
|
|
2455
2333
|
target: getPackageExecutionCommand(config.packageManager, "kubojs@latest mcp")
|
|
2456
2334
|
},
|
|
@@ -3001,11 +2879,11 @@ function uniqueValues(values) {
|
|
|
3001
2879
|
async function setupSkills(config) {
|
|
3002
2880
|
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
3003
2881
|
const { packageManager, projectDir } = config;
|
|
3004
|
-
const
|
|
3005
|
-
const fullConfig =
|
|
2882
|
+
const kubojsConfig = await readKubojsConfig(projectDir);
|
|
2883
|
+
const fullConfig = kubojsConfig ? {
|
|
3006
2884
|
...config,
|
|
3007
|
-
addons:
|
|
3008
|
-
addonOptions:
|
|
2885
|
+
addons: kubojsConfig.addons ?? config.addons,
|
|
2886
|
+
addonOptions: kubojsConfig.addonOptions ?? config.addonOptions
|
|
3009
2887
|
} : config;
|
|
3010
2888
|
const recommendedSourceKeys = getRecommendedSourceKeys(fullConfig);
|
|
3011
2889
|
const skillsOptions = fullConfig.addonOptions?.skills;
|
|
@@ -3694,14 +3572,14 @@ async function runAddonStep(addon, step) {
|
|
|
3694
3572
|
if (result.isErr()) cliConsola.error(pc.red(result.error.message));
|
|
3695
3573
|
}
|
|
3696
3574
|
async function setupAddons(config) {
|
|
3697
|
-
const { addons, frontend, projectDir } = config;
|
|
3575
|
+
const { addons, testing, frontend, projectDir } = config;
|
|
3698
3576
|
const hasWebFrontend = frontend.some((value) => types_exports.desktopWebFrontends.includes(value));
|
|
3699
3577
|
if (addons.includes("tauri") && hasWebFrontend) await runSetup(() => setupTauri(config));
|
|
3700
3578
|
const hasUltracite = addons.includes("ultracite");
|
|
3701
|
-
const
|
|
3579
|
+
const hasOxlint = !hasUltracite && addons.includes("oxlint");
|
|
3580
|
+
const hasBiome = !hasUltracite && !hasOxlint && addons.includes("biome");
|
|
3702
3581
|
const hasHusky = addons.includes("husky");
|
|
3703
3582
|
const hasLefthook = addons.includes("lefthook");
|
|
3704
|
-
const hasOxlint = addons.includes("oxlint");
|
|
3705
3583
|
const hasVitePlus = addons.includes("vite-plus");
|
|
3706
3584
|
if (hasUltracite) {
|
|
3707
3585
|
const gitHooks = [];
|
|
@@ -3710,7 +3588,7 @@ async function setupAddons(config) {
|
|
|
3710
3588
|
await runSetup(() => setupUltracite(config, gitHooks));
|
|
3711
3589
|
} else {
|
|
3712
3590
|
if (hasBiome) await runAddonStep("biome", () => setupBiome(projectDir));
|
|
3713
|
-
if (hasOxlint) await runSetup(() => setupOxlint(projectDir, config.packageManager));
|
|
3591
|
+
else if (hasOxlint) await runSetup(() => setupOxlint(projectDir, config.packageManager));
|
|
3714
3592
|
if (hasHusky || hasLefthook) {
|
|
3715
3593
|
let linter;
|
|
3716
3594
|
if (hasOxlint) linter = "oxlint";
|
|
@@ -3727,6 +3605,8 @@ async function setupAddons(config) {
|
|
|
3727
3605
|
if (addons.includes("skills")) await runSetup(() => setupSkills(config));
|
|
3728
3606
|
if (addons.includes("mcp")) await runSetup(() => setupMcp(config));
|
|
3729
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));
|
|
3730
3610
|
}
|
|
3731
3611
|
async function setupBiome(projectDir) {
|
|
3732
3612
|
await addPackageDependency({
|
|
@@ -3769,31 +3649,127 @@ async function setupLefthook(projectDir) {
|
|
|
3769
3649
|
});
|
|
3770
3650
|
}
|
|
3771
3651
|
//#endregion
|
|
3652
|
+
//#region src/helpers/testing/testing-setup.ts
|
|
3653
|
+
async function updateScripts(projectDir, scripts) {
|
|
3654
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
3655
|
+
if (!await fs.pathExists(packageJsonPath)) return;
|
|
3656
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
3657
|
+
packageJson.scripts = {
|
|
3658
|
+
...packageJson.scripts,
|
|
3659
|
+
...scripts
|
|
3660
|
+
};
|
|
3661
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
3662
|
+
}
|
|
3663
|
+
async function setupTesting(projectDir, testing) {
|
|
3664
|
+
if (testing.includes("vitest")) {
|
|
3665
|
+
await addPackageDependency({
|
|
3666
|
+
devDependencies: ["vitest"],
|
|
3667
|
+
projectDir
|
|
3668
|
+
});
|
|
3669
|
+
await updateScripts(projectDir, {
|
|
3670
|
+
test: "vitest run",
|
|
3671
|
+
"test:watch": "vitest"
|
|
3672
|
+
});
|
|
3673
|
+
}
|
|
3674
|
+
if (testing.includes("playwright")) {
|
|
3675
|
+
await addPackageDependency({
|
|
3676
|
+
devDependencies: ["@playwright/test"],
|
|
3677
|
+
projectDir
|
|
3678
|
+
});
|
|
3679
|
+
await updateScripts(projectDir, { "test:e2e": "playwright test" });
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
//#endregion
|
|
3683
|
+
//#region src/utils/config-processing.ts
|
|
3684
|
+
function processArrayOption(options) {
|
|
3685
|
+
if (!options || options.length === 0) return [];
|
|
3686
|
+
if (options.includes("none")) return [];
|
|
3687
|
+
return options.filter((item) => item !== "none");
|
|
3688
|
+
}
|
|
3689
|
+
function deriveProjectName(projectName, projectDirectory) {
|
|
3690
|
+
if (projectName) return projectName;
|
|
3691
|
+
if (projectDirectory) return path.basename(path.resolve(process.cwd(), projectDirectory));
|
|
3692
|
+
return "";
|
|
3693
|
+
}
|
|
3694
|
+
function processFlags(options, projectName) {
|
|
3695
|
+
const config = {};
|
|
3696
|
+
if (options.api) config.api = options.api;
|
|
3697
|
+
if (options.addonOptions) config.addonOptions = options.addonOptions;
|
|
3698
|
+
if (options.dbSetupOptions) config.dbSetupOptions = options.dbSetupOptions;
|
|
3699
|
+
if (options.backend) config.backend = options.backend;
|
|
3700
|
+
if (options.database) config.database = options.database;
|
|
3701
|
+
if (options.orm) config.orm = options.orm;
|
|
3702
|
+
if (options.auth !== void 0) config.auth = options.auth;
|
|
3703
|
+
if (options.payments !== void 0) config.payments = options.payments;
|
|
3704
|
+
if (options.observability !== void 0) config.observability = normalizeObservability(options.observability);
|
|
3705
|
+
if (options.disableObservability) config.observability = [];
|
|
3706
|
+
if (options.communication !== void 0) config.communication = options.communication;
|
|
3707
|
+
if (options.git !== void 0) config.git = options.git;
|
|
3708
|
+
if (options.install !== void 0) config.install = options.install;
|
|
3709
|
+
if (options.runtime) config.runtime = options.runtime;
|
|
3710
|
+
if (options.dbSetup) config.dbSetup = options.dbSetup;
|
|
3711
|
+
if (options.packageManager) config.packageManager = options.packageManager;
|
|
3712
|
+
if (options.webDeploy) config.webDeploy = options.webDeploy;
|
|
3713
|
+
if (options.serverDeploy) config.serverDeploy = options.serverDeploy;
|
|
3714
|
+
const derivedName = deriveProjectName(projectName, options.projectDirectory);
|
|
3715
|
+
if (derivedName) config.projectName = projectName || derivedName;
|
|
3716
|
+
if (options.frontend && options.frontend.length > 0) config.frontend = processArrayOption(options.frontend);
|
|
3717
|
+
if (options.addons && options.addons.length > 0) config.addons = processArrayOption(options.addons);
|
|
3718
|
+
if (options.examples && options.examples.length > 0) config.examples = processArrayOption(options.examples);
|
|
3719
|
+
if (options.testing && options.testing.length > 0) config.testing = processArrayOption(options.testing);
|
|
3720
|
+
return config;
|
|
3721
|
+
}
|
|
3722
|
+
function normalizeObservability(value) {
|
|
3723
|
+
if (value === "none" || value === void 0) return [];
|
|
3724
|
+
return [...new Set((Array.isArray(value) ? value : [value]).filter((item) => item === "getmonitor" || item === "himetrica"))];
|
|
3725
|
+
}
|
|
3726
|
+
function getProvidedFlags(options) {
|
|
3727
|
+
return new Set(Object.keys(options).filter((key) => options[key] !== void 0));
|
|
3728
|
+
}
|
|
3729
|
+
function validateNoneExclusivity(options, optionName) {
|
|
3730
|
+
if (!options || options.length === 0) return Result.ok(void 0);
|
|
3731
|
+
if (options.includes("none") && options.length > 1) return Result.err(new ValidationError({ message: `Cannot combine 'none' with other ${optionName}.` }));
|
|
3732
|
+
return Result.ok(void 0);
|
|
3733
|
+
}
|
|
3734
|
+
function validateArrayOptions(options) {
|
|
3735
|
+
if (options.disableObservability && options.observability !== void 0) return Result.err(new ValidationError({ message: "Cannot combine '--disable-observability' with '--observability'." }));
|
|
3736
|
+
const frontendResult = validateNoneExclusivity(options.frontend, "frontend options");
|
|
3737
|
+
if (frontendResult.isErr()) return frontendResult;
|
|
3738
|
+
const addonsResult = validateNoneExclusivity(options.addons, "addons");
|
|
3739
|
+
if (addonsResult.isErr()) return addonsResult;
|
|
3740
|
+
const examplesResult = validateNoneExclusivity(options.examples, "examples");
|
|
3741
|
+
if (examplesResult.isErr()) return examplesResult;
|
|
3742
|
+
const testingResult = validateNoneExclusivity(options.testing, "testing");
|
|
3743
|
+
if (testingResult.isErr()) return testingResult;
|
|
3744
|
+
return Result.ok(void 0);
|
|
3745
|
+
}
|
|
3746
|
+
//#endregion
|
|
3772
3747
|
//#region src/helpers/core/detect-project-config.ts
|
|
3773
3748
|
async function detectProjectConfig(projectDir) {
|
|
3774
3749
|
const result = await Result.tryPromise({
|
|
3775
3750
|
try: async () => {
|
|
3776
|
-
const
|
|
3777
|
-
if (
|
|
3751
|
+
const kubojsConfig = await readKubojsConfig(projectDir);
|
|
3752
|
+
if (kubojsConfig) return {
|
|
3778
3753
|
projectDir,
|
|
3779
3754
|
projectName: path.basename(projectDir),
|
|
3780
|
-
addonOptions:
|
|
3781
|
-
dbSetupOptions:
|
|
3782
|
-
database:
|
|
3783
|
-
orm:
|
|
3784
|
-
backend:
|
|
3785
|
-
runtime:
|
|
3786
|
-
frontend:
|
|
3787
|
-
addons:
|
|
3788
|
-
examples:
|
|
3789
|
-
auth:
|
|
3790
|
-
payments:
|
|
3791
|
-
observability:
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3755
|
+
addonOptions: kubojsConfig.addonOptions,
|
|
3756
|
+
dbSetupOptions: kubojsConfig.dbSetupOptions,
|
|
3757
|
+
database: kubojsConfig.database,
|
|
3758
|
+
orm: kubojsConfig.orm,
|
|
3759
|
+
backend: kubojsConfig.backend,
|
|
3760
|
+
runtime: kubojsConfig.runtime,
|
|
3761
|
+
frontend: kubojsConfig.frontend,
|
|
3762
|
+
addons: kubojsConfig.addons,
|
|
3763
|
+
examples: kubojsConfig.examples,
|
|
3764
|
+
auth: kubojsConfig.auth,
|
|
3765
|
+
payments: kubojsConfig.payments,
|
|
3766
|
+
observability: normalizeObservability(kubojsConfig.observability),
|
|
3767
|
+
communication: kubojsConfig.communication ?? "none",
|
|
3768
|
+
packageManager: kubojsConfig.packageManager,
|
|
3769
|
+
dbSetup: kubojsConfig.dbSetup,
|
|
3770
|
+
api: kubojsConfig.api,
|
|
3771
|
+
webDeploy: kubojsConfig.webDeploy,
|
|
3772
|
+
serverDeploy: kubojsConfig.serverDeploy
|
|
3797
3773
|
};
|
|
3798
3774
|
return null;
|
|
3799
3775
|
},
|
|
@@ -3883,6 +3859,41 @@ function refreshLefthookTemplate(vfs, config) {
|
|
|
3883
3859
|
if (!template) return;
|
|
3884
3860
|
vfs.writeFile("lefthook.yml", processTemplateString(template, config));
|
|
3885
3861
|
}
|
|
3862
|
+
/** Remove files/deps left by a replaced exclusive linter. */
|
|
3863
|
+
async function cleanupRemovedLinters(projectDir, removedAddons) {
|
|
3864
|
+
const removedLinters = removedAddons.filter(isLinterAddon);
|
|
3865
|
+
if (removedLinters.length === 0) return;
|
|
3866
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
3867
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
3868
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
3869
|
+
packageJson.devDependencies = packageJson.devDependencies ?? {};
|
|
3870
|
+
for (const removed of removedLinters) {
|
|
3871
|
+
if (removed === "biome" || removed === "ultracite") {
|
|
3872
|
+
delete packageJson.devDependencies["@biomejs/biome"];
|
|
3873
|
+
delete packageJson.devDependencies.ultracite;
|
|
3874
|
+
}
|
|
3875
|
+
if (removed === "oxlint" || removed === "ultracite") {
|
|
3876
|
+
delete packageJson.devDependencies.oxlint;
|
|
3877
|
+
delete packageJson.devDependencies.oxfmt;
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
if (typeof packageJson.scripts?.check === "string" && removedLinters.some((linter) => {
|
|
3881
|
+
if (linter === "biome") return packageJson.scripts.check.includes("biome");
|
|
3882
|
+
if (linter === "oxlint") return packageJson.scripts.check.includes("oxlint");
|
|
3883
|
+
if (linter === "ultracite") return packageJson.scripts.check.includes("ultracite");
|
|
3884
|
+
return false;
|
|
3885
|
+
})) delete packageJson.scripts.check;
|
|
3886
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
3887
|
+
}
|
|
3888
|
+
for (const removed of removedLinters) {
|
|
3889
|
+
if (removed === "biome") await fs.remove(path.join(projectDir, "biome.json"));
|
|
3890
|
+
if (removed === "oxlint") {
|
|
3891
|
+
await fs.remove(path.join(projectDir, ".oxlintrc.json"));
|
|
3892
|
+
await fs.remove(path.join(projectDir, ".oxfmtrc.json"));
|
|
3893
|
+
await fs.remove(path.join(projectDir, ".oxfmtrc.jsonc"));
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3886
3897
|
function updateViteConfigImportsForVitePlus(vfs) {
|
|
3887
3898
|
for (const viteConfigPath of ["apps/web/vite.config.ts"]) {
|
|
3888
3899
|
const content = vfs.readFile(viteConfigPath);
|
|
@@ -3925,10 +3936,10 @@ async function addHandlerInternal(input) {
|
|
|
3925
3936
|
}));
|
|
3926
3937
|
if (!isSilent()) {
|
|
3927
3938
|
renderTitle();
|
|
3928
|
-
intro(pc.magenta("Add addons to your
|
|
3939
|
+
intro(pc.magenta("Add addons to your kubojs project"));
|
|
3929
3940
|
}
|
|
3930
3941
|
const existingConfig = await detectProjectConfig(projectDir);
|
|
3931
|
-
if (!existingConfig) return Result.err(new CLIError({ message: `No
|
|
3942
|
+
if (!existingConfig) return Result.err(new CLIError({ message: `No kubojs project found in ${projectDir}. Make sure kubojs.jsonrc exists.` }));
|
|
3932
3943
|
if (!isSilent()) log.info(pc.dim(`Detected project: ${existingConfig.projectName}`));
|
|
3933
3944
|
let addonsToAdd;
|
|
3934
3945
|
if (input.addons && input.addons.length > 0) {
|
|
@@ -3968,10 +3979,13 @@ async function addHandlerInternal(input) {
|
|
|
3968
3979
|
}
|
|
3969
3980
|
addonsToAdd = selectedAddons;
|
|
3970
3981
|
}
|
|
3971
|
-
const updatedAddons =
|
|
3982
|
+
const { updatedAddons, removedAddons } = mergeAddonsExclusive(existingConfig.addons, addonsToAdd);
|
|
3972
3983
|
const addonsValidationResult = validateAddonsAgainstConfig(updatedAddons, existingConfig);
|
|
3973
3984
|
if (addonsValidationResult.isErr()) return Result.err(new CLIError({ message: addonsValidationResult.error.message }));
|
|
3974
|
-
if (!isSilent())
|
|
3985
|
+
if (!isSilent()) {
|
|
3986
|
+
log.info(pc.cyan(`Adding addons: ${addonsToAdd.join(", ")}`));
|
|
3987
|
+
if (removedAddons.length > 0) log.info(pc.dim(`Replacing exclusive addons: ${removedAddons.join(", ")}`));
|
|
3988
|
+
}
|
|
3975
3989
|
const mergedAddonOptions = mergeAddonOptions(existingConfig.addonOptions, input.addonOptions);
|
|
3976
3990
|
const config = {
|
|
3977
3991
|
projectName: existingConfig.projectName,
|
|
@@ -3988,6 +4002,7 @@ async function addHandlerInternal(input) {
|
|
|
3988
4002
|
auth: existingConfig.auth,
|
|
3989
4003
|
payments: existingConfig.payments,
|
|
3990
4004
|
observability: existingConfig.observability,
|
|
4005
|
+
communication: existingConfig.communication ?? "none",
|
|
3991
4006
|
git: false,
|
|
3992
4007
|
packageManager: input.packageManager || existingConfig.packageManager,
|
|
3993
4008
|
install: input.install ?? false,
|
|
@@ -4047,6 +4062,7 @@ async function addHandlerInternal(input) {
|
|
|
4047
4062
|
const writeResult = await writeTree(tree, projectDir);
|
|
4048
4063
|
if (writeResult.isErr()) return Result.err(new CLIError({ message: `Failed to write addon files: ${writeResult.error.message}` }));
|
|
4049
4064
|
if (vfs.getFileCount() > 0 && !isSilent()) log.info(pc.dim(`Wrote ${vfs.getFileCount()} addon files`));
|
|
4065
|
+
await cleanupRemovedLinters(projectDir, removedAddons);
|
|
4050
4066
|
const setupResult = await Result.tryPromise({
|
|
4051
4067
|
try: () => setupAddons({
|
|
4052
4068
|
...config,
|
|
@@ -4061,7 +4077,15 @@ async function addHandlerInternal(input) {
|
|
|
4061
4077
|
}
|
|
4062
4078
|
});
|
|
4063
4079
|
if (setupResult.isErr()) return Result.err(setupResult.error);
|
|
4064
|
-
await
|
|
4080
|
+
const testingSetupResult = await Result.tryPromise({
|
|
4081
|
+
try: () => setupTesting(projectDir, updatedTesting),
|
|
4082
|
+
catch: (e) => new CLIError({
|
|
4083
|
+
message: e instanceof Error ? e.message : String(e),
|
|
4084
|
+
cause: e
|
|
4085
|
+
})
|
|
4086
|
+
});
|
|
4087
|
+
if (testingSetupResult.isErr()) return Result.err(testingSetupResult.error);
|
|
4088
|
+
await updateKubojsConfig(projectDir, {
|
|
4065
4089
|
addons: updatedAddons,
|
|
4066
4090
|
addonOptions: updatedConfig.addonOptions
|
|
4067
4091
|
});
|
|
@@ -4222,6 +4246,36 @@ async function getBackendFrameworkChoice(backendFramework, frontends, previousVa
|
|
|
4222
4246
|
return response;
|
|
4223
4247
|
}
|
|
4224
4248
|
//#endregion
|
|
4249
|
+
//#region src/prompts/communication.ts
|
|
4250
|
+
async function getCommunicationChoice(communication, backend, previousValue) {
|
|
4251
|
+
if (communication !== void 0) return communication;
|
|
4252
|
+
if (backend === "none") return "none";
|
|
4253
|
+
const options = [
|
|
4254
|
+
{
|
|
4255
|
+
value: "none",
|
|
4256
|
+
label: "None",
|
|
4257
|
+
hint: "No email/communication provider"
|
|
4258
|
+
},
|
|
4259
|
+
{
|
|
4260
|
+
value: "resend",
|
|
4261
|
+
label: "Resend",
|
|
4262
|
+
hint: "Transactional email for developers"
|
|
4263
|
+
},
|
|
4264
|
+
{
|
|
4265
|
+
value: "notifique",
|
|
4266
|
+
label: "Notifique",
|
|
4267
|
+
hint: "Omnichannel BR messaging API"
|
|
4268
|
+
}
|
|
4269
|
+
];
|
|
4270
|
+
const response = await navigableSelect({
|
|
4271
|
+
message: "Select communication provider",
|
|
4272
|
+
options,
|
|
4273
|
+
initialValue: preferValidInitial(options, previousValue, DEFAULT_CONFIG.communication)
|
|
4274
|
+
});
|
|
4275
|
+
if (isCancel$1(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4276
|
+
return response;
|
|
4277
|
+
}
|
|
4278
|
+
//#endregion
|
|
4225
4279
|
//#region src/prompts/database.ts
|
|
4226
4280
|
async function getDatabaseChoice(database, backend, runtime, previousValue) {
|
|
4227
4281
|
if (backend === "convex" || backend === "none") return "none";
|
|
@@ -4544,18 +4598,19 @@ async function getinstallChoice(install, previousValue) {
|
|
|
4544
4598
|
const options = [{
|
|
4545
4599
|
value: "getmonitor",
|
|
4546
4600
|
label: "GetMonitor",
|
|
4547
|
-
hint: "
|
|
4601
|
+
hint: "Recommended — JS/TS error tracking for browser and server"
|
|
4548
4602
|
}, {
|
|
4549
|
-
value: "
|
|
4550
|
-
label: "
|
|
4551
|
-
hint: "
|
|
4603
|
+
value: "himetrica",
|
|
4604
|
+
label: "Himetrica",
|
|
4605
|
+
hint: "Product analytics, errors, and Web Vitals for web apps"
|
|
4552
4606
|
}];
|
|
4553
4607
|
async function getObservabilityChoice(observability, previousValue) {
|
|
4554
4608
|
if (observability !== void 0) return observability;
|
|
4555
|
-
const response = await
|
|
4556
|
-
message: "Select observability
|
|
4609
|
+
const response = await navigableMultiselect({
|
|
4610
|
+
message: "Select observability providers",
|
|
4557
4611
|
options,
|
|
4558
|
-
|
|
4612
|
+
required: false,
|
|
4613
|
+
initialValues: previousValue ?? DEFAULT_CONFIG.observability
|
|
4559
4614
|
});
|
|
4560
4615
|
if (isCancel$1(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
4561
4616
|
return response;
|
|
@@ -4676,6 +4731,7 @@ function validateBackendNoneConstraints(config, providedFlags) {
|
|
|
4676
4731
|
if (has("api") && config.api !== "none") return validationErr("Backend 'none' requires '--api none'. Please remove the --api flag or set it to 'none'.");
|
|
4677
4732
|
if (has("auth") && config.auth !== "none") return validationErr("Backend 'none' requires '--auth none'. Please remove the --auth flag or set it to 'none'.");
|
|
4678
4733
|
if (has("payments") && config.payments !== "none") return validationErr("Backend 'none' requires '--payments none'. Please remove the --payments flag or set it to 'none'.");
|
|
4734
|
+
if (has("communication") && config.communication !== "none") return validationErr("Backend 'none' requires '--communication none'. Please remove the --communication flag or set it to 'none'.");
|
|
4679
4735
|
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'.");
|
|
4680
4736
|
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'.");
|
|
4681
4737
|
return Result.ok(void 0);
|
|
@@ -4737,6 +4793,7 @@ function validateFullConfig(config, providedFlags, options) {
|
|
|
4737
4793
|
yield* validateSelfBackendConstraints(config, providedFlags);
|
|
4738
4794
|
yield* validateBackendConstraints(config, providedFlags, options);
|
|
4739
4795
|
yield* validateFrontendConstraints(config, providedFlags);
|
|
4796
|
+
yield* validateTestingAgainstFrontends(config.testing ?? [], config.frontend ?? []);
|
|
4740
4797
|
yield* validateApiConstraints(config, options);
|
|
4741
4798
|
yield* validateServerDeployRequiresBackend(config.serverDeploy, config.backend);
|
|
4742
4799
|
yield* validateDockerServerDeploy(config.serverDeploy, config.backend, config.runtime);
|
|
@@ -4753,6 +4810,7 @@ function validateFullConfig(config, providedFlags, options) {
|
|
|
4753
4810
|
}
|
|
4754
4811
|
yield* validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
|
|
4755
4812
|
yield* validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend ?? [], config.orm, config.database);
|
|
4813
|
+
yield* validateCommunicationCompatibility(config.communication, config.backend);
|
|
4756
4814
|
return Result.ok(void 0);
|
|
4757
4815
|
});
|
|
4758
4816
|
}
|
|
@@ -4761,7 +4819,9 @@ function validateConfigForProgrammaticUse(config) {
|
|
|
4761
4819
|
yield* validateDatabaseOrmAuth(config);
|
|
4762
4820
|
if (config.frontend && config.frontend.length > 0) yield* ensureSingleWebAndNative(config.frontend);
|
|
4763
4821
|
yield* validateApiFrontendCompatibility(config.api, config.frontend);
|
|
4822
|
+
yield* validateTestingAgainstFrontends(config.testing ?? [], config.frontend ?? []);
|
|
4764
4823
|
yield* validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend, config.orm, config.database);
|
|
4824
|
+
yield* validateCommunicationCompatibility(config.communication, config.backend);
|
|
4765
4825
|
if (config.addons && config.addons.length > 0) yield* validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth, config.backend, config.runtime);
|
|
4766
4826
|
yield* validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? [], config.api);
|
|
4767
4827
|
return Result.ok(void 0);
|
|
@@ -4958,6 +5018,29 @@ async function getServerDeploymentChoice(deployment, runtime, backend, _webDeplo
|
|
|
4958
5018
|
return response;
|
|
4959
5019
|
}
|
|
4960
5020
|
//#endregion
|
|
5021
|
+
//#region src/prompts/testing.ts
|
|
5022
|
+
async function getTestingChoice(testing, frontends, previousValue) {
|
|
5023
|
+
if (testing !== void 0) return testing;
|
|
5024
|
+
const options = [{
|
|
5025
|
+
value: "vitest",
|
|
5026
|
+
label: "Vitest",
|
|
5027
|
+
hint: "Fast unit test runner"
|
|
5028
|
+
}];
|
|
5029
|
+
if (isPlaywrightAllowed(frontends ?? [])) options.push({
|
|
5030
|
+
value: "playwright",
|
|
5031
|
+
label: "Playwright",
|
|
5032
|
+
hint: "End-to-end browser testing"
|
|
5033
|
+
});
|
|
5034
|
+
const response = await navigableMultiselect({
|
|
5035
|
+
message: "Select testing tools",
|
|
5036
|
+
options,
|
|
5037
|
+
required: false,
|
|
5038
|
+
initialValues: (previousValue ?? DEFAULT_CONFIG.testing)?.filter((t) => options.some((o) => o.value === t))
|
|
5039
|
+
});
|
|
5040
|
+
if (isCancel$1(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
5041
|
+
return response;
|
|
5042
|
+
}
|
|
5043
|
+
//#endregion
|
|
4961
5044
|
//#region src/prompts/web-deploy.ts
|
|
4962
5045
|
function hasWebFrontend(frontends) {
|
|
4963
5046
|
return frontends.some((f) => WEB_FRAMEWORKS.includes(f));
|
|
@@ -5027,8 +5110,10 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
5027
5110
|
auth: flags.auth ?? DEFAULT_CONFIG.auth,
|
|
5028
5111
|
payments: flags.payments ?? DEFAULT_CONFIG.payments,
|
|
5029
5112
|
observability: flags.observability ?? DEFAULT_CONFIG.observability,
|
|
5113
|
+
communication: flags.communication ?? DEFAULT_CONFIG.communication,
|
|
5030
5114
|
addons: flags.addons ?? [...DEFAULT_CONFIG.addons],
|
|
5031
5115
|
examples: flags.examples ?? [...DEFAULT_CONFIG.examples],
|
|
5116
|
+
testing: flags.testing ?? [...DEFAULT_CONFIG.testing],
|
|
5032
5117
|
git: flags.git ?? DEFAULT_CONFIG.git,
|
|
5033
5118
|
packageManager: flags.packageManager ?? DEFAULT_CONFIG.packageManager,
|
|
5034
5119
|
install: flags.install ?? DEFAULT_CONFIG.install,
|
|
@@ -5047,6 +5132,8 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
5047
5132
|
auth: ({ results, previousAnswer }) => getAuthChoice(flags.auth, results.backend, results.frontend, previousAnswer),
|
|
5048
5133
|
payments: ({ results, previousAnswer }) => getPaymentsChoice(flags.payments, results.auth, results.backend, results.frontend, previousAnswer),
|
|
5049
5134
|
observability: ({ previousAnswer }) => getObservabilityChoice(flags.observability, previousAnswer),
|
|
5135
|
+
communication: ({ results, previousAnswer }) => getCommunicationChoice(flags.communication, results.backend, previousAnswer),
|
|
5136
|
+
testing: ({ results, previousAnswer }) => getTestingChoice(flags.testing, results.frontend, previousAnswer),
|
|
5050
5137
|
addons: ({ results, previousAnswer }) => getAddonsChoice(flags.addons, results.frontend, results.auth, results.backend, results.runtime, previousAnswer),
|
|
5051
5138
|
examples: ({ results, previousAnswer }) => getExamplesChoice(flags.examples, results.database, results.frontend, results.backend, results.api, previousAnswer),
|
|
5052
5139
|
dbSetup: ({ results, previousAnswer }) => getDBSetupChoice(results.database ?? "none", flags.dbSetup, results.orm, results.backend, results.runtime, previousAnswer),
|
|
@@ -5072,8 +5159,10 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
5072
5159
|
auth: result.auth,
|
|
5073
5160
|
payments: result.payments,
|
|
5074
5161
|
observability: result.observability,
|
|
5162
|
+
communication: result.communication,
|
|
5075
5163
|
addons: result.addons,
|
|
5076
5164
|
examples: result.examples,
|
|
5165
|
+
testing: result.testing,
|
|
5077
5166
|
git: result.git,
|
|
5078
5167
|
packageManager: result.packageManager,
|
|
5079
5168
|
install: result.install,
|
|
@@ -5180,7 +5269,8 @@ function displayConfig(config) {
|
|
|
5180
5269
|
if (config.orm !== void 0) configDisplay.push(`${pc.blue("ORM:")} ${String(config.orm)}`);
|
|
5181
5270
|
if (config.auth !== void 0) configDisplay.push(`${pc.blue("Auth:")} ${String(config.auth)}`);
|
|
5182
5271
|
if (config.payments !== void 0) configDisplay.push(`${pc.blue("Payments:")} ${String(config.payments)}`);
|
|
5183
|
-
if (config.
|
|
5272
|
+
if (config.communication !== void 0) configDisplay.push(`${pc.blue("Communication:")} ${String(config.communication)}`);
|
|
5273
|
+
if (config.observability !== void 0) configDisplay.push(`${pc.blue("Observability:")} ${config.observability.join(", ") || "none"}`);
|
|
5184
5274
|
if (config.addons !== void 0) {
|
|
5185
5275
|
const addons = Array.isArray(config.addons) ? config.addons : [config.addons];
|
|
5186
5276
|
const addonsText = addons.length > 0 && addons[0] !== void 0 ? addons.join(", ") : "none";
|
|
@@ -5191,6 +5281,11 @@ function displayConfig(config) {
|
|
|
5191
5281
|
const examplesText = examples.length > 0 && examples[0] !== void 0 ? examples.join(", ") : "none";
|
|
5192
5282
|
configDisplay.push(`${pc.blue("Examples:")} ${examplesText}`);
|
|
5193
5283
|
}
|
|
5284
|
+
if (config.testing !== void 0) {
|
|
5285
|
+
const testing = Array.isArray(config.testing) ? config.testing : [config.testing];
|
|
5286
|
+
const testingText = testing.length > 0 && testing[0] !== void 0 ? testing.join(", ") : "none";
|
|
5287
|
+
configDisplay.push(`${pc.blue("Testing:")} ${testingText}`);
|
|
5288
|
+
}
|
|
5194
5289
|
if (config.git !== void 0) {
|
|
5195
5290
|
const gitText = typeof config.git === "boolean" ? config.git ? "Yes" : "No" : String(config.git);
|
|
5196
5291
|
configDisplay.push(`${pc.blue("Git Init:")} ${gitText}`);
|
|
@@ -5333,7 +5428,8 @@ const TEMPLATE_PRESETS = {
|
|
|
5333
5428
|
api: "orpc",
|
|
5334
5429
|
auth: "better-auth",
|
|
5335
5430
|
payments: "none",
|
|
5336
|
-
observability: "
|
|
5431
|
+
observability: ["getmonitor"],
|
|
5432
|
+
communication: "none",
|
|
5337
5433
|
addons: ["turborepo"],
|
|
5338
5434
|
examples: ["todo"],
|
|
5339
5435
|
dbSetup: "mongodb-atlas",
|
|
@@ -5349,7 +5445,8 @@ const TEMPLATE_PRESETS = {
|
|
|
5349
5445
|
api: "trpc",
|
|
5350
5446
|
auth: "better-auth",
|
|
5351
5447
|
payments: "none",
|
|
5352
|
-
observability: "
|
|
5448
|
+
observability: ["getmonitor"],
|
|
5449
|
+
communication: "none",
|
|
5353
5450
|
addons: ["turborepo"],
|
|
5354
5451
|
examples: ["todo"],
|
|
5355
5452
|
dbSetup: "none",
|
|
@@ -5365,7 +5462,8 @@ const TEMPLATE_PRESETS = {
|
|
|
5365
5462
|
api: "trpc",
|
|
5366
5463
|
auth: "better-auth",
|
|
5367
5464
|
payments: "none",
|
|
5368
|
-
observability: "
|
|
5465
|
+
observability: ["getmonitor"],
|
|
5466
|
+
communication: "none",
|
|
5369
5467
|
addons: ["biome", "turborepo"],
|
|
5370
5468
|
examples: ["none"],
|
|
5371
5469
|
dbSetup: "none",
|
|
@@ -5381,7 +5479,8 @@ const TEMPLATE_PRESETS = {
|
|
|
5381
5479
|
api: "none",
|
|
5382
5480
|
auth: "none",
|
|
5383
5481
|
payments: "none",
|
|
5384
|
-
observability:
|
|
5482
|
+
observability: [],
|
|
5483
|
+
communication: "none",
|
|
5385
5484
|
addons: ["none"],
|
|
5386
5485
|
examples: ["none"],
|
|
5387
5486
|
dbSetup: "none",
|
|
@@ -5406,60 +5505,6 @@ function getTemplateDescription(template) {
|
|
|
5406
5505
|
}[template] || "";
|
|
5407
5506
|
}
|
|
5408
5507
|
//#endregion
|
|
5409
|
-
//#region src/utils/config-processing.ts
|
|
5410
|
-
function processArrayOption(options) {
|
|
5411
|
-
if (!options || options.length === 0) return [];
|
|
5412
|
-
if (options.includes("none")) return [];
|
|
5413
|
-
return options.filter((item) => item !== "none");
|
|
5414
|
-
}
|
|
5415
|
-
function deriveProjectName(projectName, projectDirectory) {
|
|
5416
|
-
if (projectName) return projectName;
|
|
5417
|
-
if (projectDirectory) return path.basename(path.resolve(process.cwd(), projectDirectory));
|
|
5418
|
-
return "";
|
|
5419
|
-
}
|
|
5420
|
-
function processFlags(options, projectName) {
|
|
5421
|
-
const config = {};
|
|
5422
|
-
if (options.api) config.api = options.api;
|
|
5423
|
-
if (options.addonOptions) config.addonOptions = options.addonOptions;
|
|
5424
|
-
if (options.dbSetupOptions) config.dbSetupOptions = options.dbSetupOptions;
|
|
5425
|
-
if (options.backend) config.backend = options.backend;
|
|
5426
|
-
if (options.database) config.database = options.database;
|
|
5427
|
-
if (options.orm) config.orm = options.orm;
|
|
5428
|
-
if (options.auth !== void 0) config.auth = options.auth;
|
|
5429
|
-
if (options.payments !== void 0) config.payments = options.payments;
|
|
5430
|
-
if (options.observability !== void 0) config.observability = options.observability;
|
|
5431
|
-
if (options.git !== void 0) config.git = options.git;
|
|
5432
|
-
if (options.install !== void 0) config.install = options.install;
|
|
5433
|
-
if (options.runtime) config.runtime = options.runtime;
|
|
5434
|
-
if (options.dbSetup) config.dbSetup = options.dbSetup;
|
|
5435
|
-
if (options.packageManager) config.packageManager = options.packageManager;
|
|
5436
|
-
if (options.webDeploy) config.webDeploy = options.webDeploy;
|
|
5437
|
-
if (options.serverDeploy) config.serverDeploy = options.serverDeploy;
|
|
5438
|
-
const derivedName = deriveProjectName(projectName, options.projectDirectory);
|
|
5439
|
-
if (derivedName) config.projectName = projectName || derivedName;
|
|
5440
|
-
if (options.frontend && options.frontend.length > 0) config.frontend = processArrayOption(options.frontend);
|
|
5441
|
-
if (options.addons && options.addons.length > 0) config.addons = processArrayOption(options.addons);
|
|
5442
|
-
if (options.examples && options.examples.length > 0) config.examples = processArrayOption(options.examples);
|
|
5443
|
-
return config;
|
|
5444
|
-
}
|
|
5445
|
-
function getProvidedFlags(options) {
|
|
5446
|
-
return new Set(Object.keys(options).filter((key) => options[key] !== void 0));
|
|
5447
|
-
}
|
|
5448
|
-
function validateNoneExclusivity(options, optionName) {
|
|
5449
|
-
if (!options || options.length === 0) return Result.ok(void 0);
|
|
5450
|
-
if (options.includes("none") && options.length > 1) return Result.err(new ValidationError({ message: `Cannot combine 'none' with other ${optionName}.` }));
|
|
5451
|
-
return Result.ok(void 0);
|
|
5452
|
-
}
|
|
5453
|
-
function validateArrayOptions(options) {
|
|
5454
|
-
const frontendResult = validateNoneExclusivity(options.frontend, "frontend options");
|
|
5455
|
-
if (frontendResult.isErr()) return frontendResult;
|
|
5456
|
-
const addonsResult = validateNoneExclusivity(options.addons, "addons");
|
|
5457
|
-
if (addonsResult.isErr()) return addonsResult;
|
|
5458
|
-
const examplesResult = validateNoneExclusivity(options.examples, "examples");
|
|
5459
|
-
if (examplesResult.isErr()) return examplesResult;
|
|
5460
|
-
return Result.ok(void 0);
|
|
5461
|
-
}
|
|
5462
|
-
//#endregion
|
|
5463
5508
|
//#region src/validation.ts
|
|
5464
5509
|
const CORE_STACK_FLAGS = new Set([
|
|
5465
5510
|
"database",
|
|
@@ -5469,10 +5514,13 @@ const CORE_STACK_FLAGS = new Set([
|
|
|
5469
5514
|
"frontend",
|
|
5470
5515
|
"addons",
|
|
5471
5516
|
"examples",
|
|
5517
|
+
"testing",
|
|
5472
5518
|
"auth",
|
|
5473
5519
|
"dbSetup",
|
|
5474
5520
|
"payments",
|
|
5475
5521
|
"observability",
|
|
5522
|
+
"disableObservability",
|
|
5523
|
+
"communication",
|
|
5476
5524
|
"api",
|
|
5477
5525
|
"webDeploy",
|
|
5478
5526
|
"serverDeploy"
|
|
@@ -5525,6 +5573,49 @@ const formatOptions = {
|
|
|
5525
5573
|
experimentalSortPackageJson: true,
|
|
5526
5574
|
experimentalSortImports: { order: "asc" }
|
|
5527
5575
|
};
|
|
5576
|
+
/** Files that must not be rewritten by post-scaffold formatters. */
|
|
5577
|
+
const SKIP_FILE_NAMES = new Set([
|
|
5578
|
+
"kubojs.jsonrc",
|
|
5579
|
+
"package-lock.json",
|
|
5580
|
+
"pnpm-lock.yaml",
|
|
5581
|
+
"yarn.lock",
|
|
5582
|
+
"bun.lock",
|
|
5583
|
+
"bun.lockb",
|
|
5584
|
+
"Cargo.lock",
|
|
5585
|
+
"composer.lock"
|
|
5586
|
+
]);
|
|
5587
|
+
const SKIP_EXTENSIONS = new Set([
|
|
5588
|
+
".png",
|
|
5589
|
+
".jpg",
|
|
5590
|
+
".jpeg",
|
|
5591
|
+
".gif",
|
|
5592
|
+
".webp",
|
|
5593
|
+
".ico",
|
|
5594
|
+
".svg",
|
|
5595
|
+
".woff",
|
|
5596
|
+
".woff2",
|
|
5597
|
+
".ttf",
|
|
5598
|
+
".eot",
|
|
5599
|
+
".mp4",
|
|
5600
|
+
".mp3",
|
|
5601
|
+
".wasm",
|
|
5602
|
+
".zip",
|
|
5603
|
+
".gz",
|
|
5604
|
+
".tgz",
|
|
5605
|
+
".br",
|
|
5606
|
+
".pdf",
|
|
5607
|
+
".db",
|
|
5608
|
+
".sqlite"
|
|
5609
|
+
]);
|
|
5610
|
+
function shouldSkipFile(fileName) {
|
|
5611
|
+
if (SKIP_FILE_NAMES.has(fileName)) return true;
|
|
5612
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
5613
|
+
return SKIP_EXTENSIONS.has(ext);
|
|
5614
|
+
}
|
|
5615
|
+
function shouldUseBiomeFormatter(addons) {
|
|
5616
|
+
if (!addons) return false;
|
|
5617
|
+
return addons.includes("biome") || addons.includes("ultracite");
|
|
5618
|
+
}
|
|
5528
5619
|
async function formatCode(filePath, content) {
|
|
5529
5620
|
const result = await Result.tryPromise({
|
|
5530
5621
|
try: async () => {
|
|
@@ -5536,25 +5627,61 @@ async function formatCode(filePath, content) {
|
|
|
5536
5627
|
});
|
|
5537
5628
|
return result.isOk() ? result.value : null;
|
|
5538
5629
|
}
|
|
5539
|
-
async function
|
|
5630
|
+
async function formatProjectWithOxfmt(projectDir) {
|
|
5631
|
+
async function formatDirectory(dir) {
|
|
5632
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
5633
|
+
await Promise.all(entries.map(async (entry) => {
|
|
5634
|
+
const fullPath = path.join(dir, entry.name);
|
|
5635
|
+
if (entry.isDirectory()) {
|
|
5636
|
+
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist" || entry.name === ".turbo") return;
|
|
5637
|
+
await formatDirectory(fullPath);
|
|
5638
|
+
} else if (entry.isFile()) {
|
|
5639
|
+
if (shouldSkipFile(entry.name)) return;
|
|
5640
|
+
await Result.tryPromise({
|
|
5641
|
+
try: async () => {
|
|
5642
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
5643
|
+
const formatted = await formatCode(fullPath, content);
|
|
5644
|
+
if (formatted && formatted !== content) await fs.writeFile(fullPath, formatted, "utf-8");
|
|
5645
|
+
},
|
|
5646
|
+
catch: () => void 0
|
|
5647
|
+
});
|
|
5648
|
+
}
|
|
5649
|
+
}));
|
|
5650
|
+
}
|
|
5651
|
+
await formatDirectory(projectDir);
|
|
5652
|
+
}
|
|
5653
|
+
/**
|
|
5654
|
+
* Format with Biome so scaffold matches `biome check` (source of truth).
|
|
5655
|
+
* Uses bunx so it works before local install. Best-effort: failures are ignored
|
|
5656
|
+
* so offline CI/tests still scaffold.
|
|
5657
|
+
*/
|
|
5658
|
+
async function formatProjectWithBiome(projectDir) {
|
|
5659
|
+
if (shouldSkipExternalCommands()) return;
|
|
5660
|
+
await Result.tryPromise({
|
|
5661
|
+
try: async () => {
|
|
5662
|
+
await $({
|
|
5663
|
+
cwd: projectDir,
|
|
5664
|
+
reject: false,
|
|
5665
|
+
stdio: "pipe"
|
|
5666
|
+
})`bunx --bun @biomejs/biome@2.5.8 check --write .`;
|
|
5667
|
+
},
|
|
5668
|
+
catch: () => void 0
|
|
5669
|
+
});
|
|
5670
|
+
}
|
|
5671
|
+
/**
|
|
5672
|
+
* Post-scaffold format.
|
|
5673
|
+
* - biome / ultracite → Biome `check --write` (not oxfmt)
|
|
5674
|
+
* - oxlint / none → oxfmt
|
|
5675
|
+
* Always skips kubojs.jsonrc, lockfiles, and binary extensions (oxfmt path).
|
|
5676
|
+
*/
|
|
5677
|
+
async function formatProject(projectDir, options = {}) {
|
|
5540
5678
|
return Result.tryPromise({
|
|
5541
5679
|
try: async () => {
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
const fullPath = path.join(dir, entry.name);
|
|
5546
|
-
if (entry.isDirectory()) await formatDirectory(fullPath);
|
|
5547
|
-
else if (entry.isFile()) await Result.tryPromise({
|
|
5548
|
-
try: async () => {
|
|
5549
|
-
const content = await fs.readFile(fullPath, "utf-8");
|
|
5550
|
-
const formatted = await formatCode(fullPath, content);
|
|
5551
|
-
if (formatted && formatted !== content) await fs.writeFile(fullPath, formatted, "utf-8");
|
|
5552
|
-
},
|
|
5553
|
-
catch: () => void 0
|
|
5554
|
-
});
|
|
5555
|
-
}));
|
|
5680
|
+
if (shouldUseBiomeFormatter(options.addons)) {
|
|
5681
|
+
await formatProjectWithBiome(projectDir);
|
|
5682
|
+
return;
|
|
5556
5683
|
}
|
|
5557
|
-
await
|
|
5684
|
+
await formatProjectWithOxfmt(projectDir);
|
|
5558
5685
|
},
|
|
5559
5686
|
catch: (e) => new ProjectCreationError({
|
|
5560
5687
|
phase: "formatting",
|
|
@@ -5564,6 +5691,57 @@ async function formatProject(projectDir) {
|
|
|
5564
5691
|
});
|
|
5565
5692
|
}
|
|
5566
5693
|
//#endregion
|
|
5694
|
+
//#region src/utils/generate-route-tree.ts
|
|
5695
|
+
const TANSTACK_ROUTE_TREE_FRONTENDS = [
|
|
5696
|
+
"tanstack-router",
|
|
5697
|
+
"tanstack-start",
|
|
5698
|
+
"solid"
|
|
5699
|
+
];
|
|
5700
|
+
function resolveRouteTreeTarget(frontend) {
|
|
5701
|
+
if (frontend.includes("solid")) return "solid";
|
|
5702
|
+
if (frontend.includes("tanstack-router") || frontend.includes("tanstack-start")) return "react";
|
|
5703
|
+
return null;
|
|
5704
|
+
}
|
|
5705
|
+
function needsRouteTree(frontend) {
|
|
5706
|
+
return frontend.some((value) => TANSTACK_ROUTE_TREE_FRONTENDS.includes(value));
|
|
5707
|
+
}
|
|
5708
|
+
/**
|
|
5709
|
+
* Generate apps/web/src/routeTree.gen.ts from the scaffolded file routes.
|
|
5710
|
+
* TanStack recommends committing this file (runtime source, not a build artifact).
|
|
5711
|
+
* Running at create-time makes `check-types` pass without a prior vite build.
|
|
5712
|
+
*/
|
|
5713
|
+
async function generateRouteTreeIfNeeded(projectDir, config) {
|
|
5714
|
+
if (!needsRouteTree(config.frontend)) return Result.ok(void 0);
|
|
5715
|
+
const target = resolveRouteTreeTarget(config.frontend);
|
|
5716
|
+
if (!target) return Result.ok(void 0);
|
|
5717
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
5718
|
+
const routesDir = path.join(webAppDir, "src/routes");
|
|
5719
|
+
if (!await fs.pathExists(routesDir)) return Result.ok(void 0);
|
|
5720
|
+
return Result.tryPromise({
|
|
5721
|
+
try: async () => {
|
|
5722
|
+
await new Generator({
|
|
5723
|
+
config: configSchema.parse({
|
|
5724
|
+
target,
|
|
5725
|
+
routesDirectory: path.join(webAppDir, "src/routes"),
|
|
5726
|
+
generatedRouteTree: path.join(webAppDir, "src/routeTree.gen.ts"),
|
|
5727
|
+
tmpDir: path.join(webAppDir, ".tanstack/tmp"),
|
|
5728
|
+
autoCodeSplitting: true,
|
|
5729
|
+
disableLogging: true,
|
|
5730
|
+
enableRouteTreeFormatting: true,
|
|
5731
|
+
quoteStyle: "single",
|
|
5732
|
+
semicolons: false
|
|
5733
|
+
}),
|
|
5734
|
+
root: webAppDir
|
|
5735
|
+
}).run();
|
|
5736
|
+
},
|
|
5737
|
+
catch: (e) => new ProjectCreationError({
|
|
5738
|
+
phase: "route-tree-generation",
|
|
5739
|
+
message: `Failed to generate TanStack route tree: ${e instanceof Error ? e.message : String(e)}`,
|
|
5740
|
+
cause: e
|
|
5741
|
+
})
|
|
5742
|
+
});
|
|
5743
|
+
}
|
|
5744
|
+
//#endregion
|
|
5567
5745
|
//#region src/utils/env-utils.ts
|
|
5568
5746
|
async function addEnvVariablesToFile(envPath, variables) {
|
|
5569
5747
|
let content = "";
|
|
@@ -5974,7 +6152,7 @@ async function setupWithNeonDb(projectDir, packageManager, backend) {
|
|
|
5974
6152
|
function displayManualSetupInstructions$2(target) {
|
|
5975
6153
|
cliLog.info(`Manual Neon PostgreSQL Setup Instructions:
|
|
5976
6154
|
|
|
5977
|
-
1. Get Neon with
|
|
6155
|
+
1. Get Neon with kubojs referral: https://get.neon.com/sbA3tIe
|
|
5978
6156
|
2. Create a new project from the dashboard
|
|
5979
6157
|
3. Get your connection string
|
|
5980
6158
|
4. Add the database URL to the .env file in ${target}/.env
|
|
@@ -6053,7 +6231,7 @@ async function setupNeonPostgres(config, cliInput) {
|
|
|
6053
6231
|
displayManualSetupInstructions$2(target);
|
|
6054
6232
|
return Result.ok(void 0);
|
|
6055
6233
|
}
|
|
6056
|
-
cliLog.info(`Get Neon with
|
|
6234
|
+
cliLog.info(`Get Neon with kubojs referral: ${pc.cyan("https://get.neon.com/sbA3tIe")}`);
|
|
6057
6235
|
return Result.ok(void 0);
|
|
6058
6236
|
}
|
|
6059
6237
|
const suggestedProjectName = path.basename(projectDir);
|
|
@@ -6095,7 +6273,7 @@ async function setupNeonPostgres(config, cliInput) {
|
|
|
6095
6273
|
return envResult;
|
|
6096
6274
|
}
|
|
6097
6275
|
finalSpinner.stop("Neon database configured!");
|
|
6098
|
-
cliLog.info(`Get Neon with
|
|
6276
|
+
cliLog.info(`Get Neon with kubojs referral: ${pc.cyan("https://get.neon.com/sbA3tIe")}`);
|
|
6099
6277
|
return Result.ok(void 0);
|
|
6100
6278
|
}
|
|
6101
6279
|
//#endregion
|
|
@@ -6196,7 +6374,7 @@ const AVAILABLE_REGIONS = [
|
|
|
6196
6374
|
label: "US West (N. California)"
|
|
6197
6375
|
}
|
|
6198
6376
|
];
|
|
6199
|
-
const CREATE_DB_USER_AGENT = "
|
|
6377
|
+
const CREATE_DB_USER_AGENT = "kubojs/kubojs";
|
|
6200
6378
|
async function setupWithCreateDb(serverDir, packageManager, regionId) {
|
|
6201
6379
|
cliLog.info("Starting Prisma Postgres setup with create-db.");
|
|
6202
6380
|
let selectedRegion = regionId;
|
|
@@ -6976,7 +7154,7 @@ function getDesktopStaticBuildNote(frontend) {
|
|
|
6976
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.`;
|
|
6977
7155
|
}
|
|
6978
7156
|
async function displayPostInstallInstructions(config) {
|
|
6979
|
-
const { api, database, relativePath, packageManager, depsInstalled, orm, addons, runtime, frontend, backend, dbSetup, webDeploy, serverDeploy, observability } = config;
|
|
7157
|
+
const { api, database, relativePath, packageManager, depsInstalled, orm, addons, testing, runtime, frontend, backend, dbSetup, webDeploy, serverDeploy, observability, communication } = config;
|
|
6980
7158
|
const isConvex = backend === "convex";
|
|
6981
7159
|
const isBackendSelf = backend === "self";
|
|
6982
7160
|
const runCmd = packageManager === "npm" ? "npm run" : packageManager === "pnpm" ? "pnpm run" : "bun run";
|
|
@@ -6996,22 +7174,22 @@ async function displayPostInstallInstructions(config) {
|
|
|
6996
7174
|
const nativeInstructions = (frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles")) && backend !== "none" ? getNativeInstructions(isConvex, isBackendSelf, frontend || [], runCmd) : "";
|
|
6997
7175
|
const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
|
|
6998
7176
|
const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : "";
|
|
7177
|
+
const playwrightInstructions = testing?.includes("playwright") ? getPlaywrightInstructions(runCmd) : "";
|
|
6999
7178
|
const clerkInstructions = config.auth === "clerk" ? getClerkInstructions(frontend || [], backend, api) : "";
|
|
7000
7179
|
const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend);
|
|
7001
|
-
const getMonitorInstructions = observability
|
|
7180
|
+
const getMonitorInstructions = observability.includes("getmonitor") ? getGetMonitorInstructions() : "";
|
|
7181
|
+
const resendInstructions = communication === "resend" ? getResendInstructions() : "";
|
|
7182
|
+
const notifiqueInstructions = communication === "notifique" ? getNotifiqueInstructions() : "";
|
|
7002
7183
|
const hasWeb = frontend?.some((f) => types_exports.desktopWebFrontends.includes(f));
|
|
7003
7184
|
const hasNative = frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles");
|
|
7004
|
-
const
|
|
7005
|
-
const hasSvelte = frontend?.includes("svelte");
|
|
7006
|
-
const hasAstro = frontend?.includes("astro");
|
|
7007
|
-
const webPort = hasReactRouter || hasSvelte ? "5173" : hasAstro ? "4321" : "3001";
|
|
7185
|
+
const webPort = (0, types_exports.getWebPort)(frontend ?? []);
|
|
7008
7186
|
const betterAuthConvexInstructions = isConvex && config.auth === "better-auth" ? getBetterAuthConvexInstructions(hasWeb ?? false, webPort, packageManager, runCmd) : "";
|
|
7009
7187
|
const bunWebNativeWarning = packageManager === "bun" && hasNative && hasWeb ? getBunWebNativeWarning() : "";
|
|
7010
7188
|
const noOrmWarning = !isConvex && database !== "none" && orm === "none" ? getNoOrmWarning() : "";
|
|
7011
7189
|
let output = `${pc.bold("Next steps")}\n${pc.cyan("1.")} ${cdCmd}\n`;
|
|
7012
7190
|
let stepCounter = 2;
|
|
7013
7191
|
if (!depsInstalled) output += `${pc.cyan(`${stepCounter++}.`)} ${packageManager} install\n`;
|
|
7014
|
-
if (database === "sqlite" && dbSetup
|
|
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`;
|
|
7015
7193
|
if (isConvex) {
|
|
7016
7194
|
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev:setup\n${pc.dim(" (this will guide you through Convex project setup)")}\n`;
|
|
7017
7195
|
output += `${pc.cyan(`${stepCounter++}.`)} Copy environment variables from\n${pc.white(" packages/backend/.env.local")} to ${pc.white("apps/*/.env")}\n`;
|
|
@@ -7050,16 +7228,16 @@ async function displayPostInstallInstructions(config) {
|
|
|
7050
7228
|
if (lintingInstructions) output += `\n${lintingInstructions.trim()}\n`;
|
|
7051
7229
|
if (pwaInstructions) output += `\n${pwaInstructions.trim()}\n`;
|
|
7052
7230
|
if (starlightInstructions) output += `\n${starlightInstructions.trim()}\n`;
|
|
7231
|
+
if (playwrightInstructions) output += `\n${playwrightInstructions.trim()}\n`;
|
|
7053
7232
|
if (clerkInstructions) output += `\n${clerkInstructions.trim()}\n`;
|
|
7054
7233
|
if (betterAuthConvexInstructions) output += `\n${betterAuthConvexInstructions.trim()}\n`;
|
|
7055
7234
|
if (getMonitorInstructions) output += `\n${getMonitorInstructions.trim()}\n`;
|
|
7235
|
+
if (resendInstructions) output += `\n${resendInstructions.trim()}\n`;
|
|
7236
|
+
if (notifiqueInstructions) output += `\n${notifiqueInstructions.trim()}\n`;
|
|
7056
7237
|
if (alchemyDeployInstructions) output += `\n${alchemyDeployInstructions.trim()}\n`;
|
|
7057
7238
|
if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`;
|
|
7058
7239
|
if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
|
|
7059
|
-
|
|
7060
|
-
const specialSponsorsSection = sponsorsResult.isOk() ? formatPostInstallSpecialSponsorsSection(sponsorsResult.value) : "";
|
|
7061
|
-
if (specialSponsorsSection) output += `\n${specialSponsorsSection.trim()}\n`;
|
|
7062
|
-
output += `\n${pc.bold("Like I dont know?")} Please consider giving us a star\n on GitHub:\n`;
|
|
7240
|
+
output += `\n${pc.bold("Like kubojs?")} Please consider giving us a star\n on GitHub:\n`;
|
|
7063
7241
|
output += pc.cyan("https://github.com/albuquerquesz/kubo");
|
|
7064
7242
|
cliConsola.box(output);
|
|
7065
7243
|
}
|
|
@@ -7077,6 +7255,9 @@ function getNativeInstructions(isConvex, isBackendSelf, frontend, runCmd) {
|
|
|
7077
7255
|
function getHuskyInstructions(runCmd) {
|
|
7078
7256
|
return `${pc.bold("Git hooks with Husky:")}\n${pc.cyan("•")} Initialize hooks: ${`${runCmd} prepare`}\n`;
|
|
7079
7257
|
}
|
|
7258
|
+
function getPlaywrightInstructions(runCmd) {
|
|
7259
|
+
return `${pc.bold("End-to-end tests with Playwright:")}\n${pc.cyan("•")} Install browsers: npx playwright install\n${pc.cyan("•")} Run e2e tests: ${`${runCmd} test:e2e`}\n`;
|
|
7260
|
+
}
|
|
7080
7261
|
function getLintingInstructions(runCmd) {
|
|
7081
7262
|
return `${pc.bold("Linting and formatting:")}\n${pc.cyan("•")} Run checks: ${`${runCmd} check`}\n`;
|
|
7082
7263
|
}
|
|
@@ -7144,7 +7325,13 @@ function getNoOrmWarning() {
|
|
|
7144
7325
|
return `\n${pc.yellow("WARNING:")} Database selected without an ORM. Features requiring\n database access (e.g., examples, auth) need manual setup.`;
|
|
7145
7326
|
}
|
|
7146
7327
|
function getGetMonitorInstructions() {
|
|
7147
|
-
return `${pc.bold("GetMonitor observability:")}\n${pc.cyan("•")}
|
|
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")}`;
|
|
7329
|
+
}
|
|
7330
|
+
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")}`;
|
|
7332
|
+
}
|
|
7333
|
+
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")}`;
|
|
7148
7335
|
}
|
|
7149
7336
|
function getBunWebNativeWarning() {
|
|
7150
7337
|
return `\n${pc.yellow("WARNING:")} 'bun' might cause issues with web + native apps in a monorepo.\n Use 'pnpm' if problems arise.`;
|
|
@@ -7275,6 +7462,7 @@ async function createProject(options, cliInput = {}) {
|
|
|
7275
7462
|
message: e.message,
|
|
7276
7463
|
cause: e
|
|
7277
7464
|
}))));
|
|
7465
|
+
yield* Result.await(generateRouteTreeIfNeeded(projectDir, options));
|
|
7278
7466
|
yield* Result.await(setPackageManagerVersion(projectDir, options.packageManager));
|
|
7279
7467
|
if (!isConvex && options.database !== "none") yield* Result.await(Result.tryPromise({
|
|
7280
7468
|
try: () => setupDatabase(options, cliInput),
|
|
@@ -7284,7 +7472,9 @@ async function createProject(options, cliInput = {}) {
|
|
|
7284
7472
|
cause: e
|
|
7285
7473
|
})
|
|
7286
7474
|
}));
|
|
7287
|
-
|
|
7475
|
+
const hasAddons = options.addons.some((addon) => addon !== "none");
|
|
7476
|
+
const hasTesting = options.testing.some((tool) => tool !== "none");
|
|
7477
|
+
if (hasAddons) yield* Result.await(Result.tryPromise({
|
|
7288
7478
|
try: () => setupAddons(options),
|
|
7289
7479
|
catch: (e) => new ProjectCreationError({
|
|
7290
7480
|
phase: "addons-setup",
|
|
@@ -7292,7 +7482,15 @@ async function createProject(options, cliInput = {}) {
|
|
|
7292
7482
|
cause: e
|
|
7293
7483
|
})
|
|
7294
7484
|
}));
|
|
7295
|
-
yield* Result.await(
|
|
7485
|
+
if (hasTesting) yield* Result.await(Result.tryPromise({
|
|
7486
|
+
try: () => setupTesting(projectDir, options.testing),
|
|
7487
|
+
catch: (e) => new ProjectCreationError({
|
|
7488
|
+
phase: "testing-setup",
|
|
7489
|
+
message: String(e),
|
|
7490
|
+
cause: e
|
|
7491
|
+
})
|
|
7492
|
+
}));
|
|
7493
|
+
yield* Result.await(formatProject(projectDir, { addons: options.addons }));
|
|
7296
7494
|
if (!isSilent()) log.success("Project template successfully scaffolded!");
|
|
7297
7495
|
if (options.install) yield* Result.await(installDependencies({
|
|
7298
7496
|
projectDir,
|
|
@@ -7306,21 +7504,40 @@ async function createProject(options, cliInput = {}) {
|
|
|
7306
7504
|
return Result.ok(projectDir);
|
|
7307
7505
|
});
|
|
7308
7506
|
}
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7507
|
+
/**
|
|
7508
|
+
* Corepack/Turbo require `name@x.y.z`. Placeholders like `@latest` are invalid.
|
|
7509
|
+
*/
|
|
7510
|
+
function isValidPackageManagerField(value) {
|
|
7511
|
+
return /^(bun|npm|pnpm|yarn)@\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(value);
|
|
7512
|
+
}
|
|
7513
|
+
function normalizePackageManagerVersion(version) {
|
|
7514
|
+
const cleaned = version.replace(/^v/i, "").trim();
|
|
7515
|
+
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(cleaned)) return null;
|
|
7516
|
+
return cleaned;
|
|
7517
|
+
}
|
|
7518
|
+
async function resolveInstalledPackageManagerVersion(packageManager) {
|
|
7519
|
+
if (packageManager === "bun" && typeof process.versions.bun === "string" && process.versions.bun.length > 0) return normalizePackageManagerVersion(process.versions.bun);
|
|
7312
7520
|
const versionResult = await Result.tryPromise({
|
|
7313
7521
|
try: async () => {
|
|
7314
7522
|
const { stdout } = await $({ cwd: os.tmpdir() })`${packageManager} -v`;
|
|
7315
|
-
return stdout
|
|
7523
|
+
return normalizePackageManagerVersion(stdout);
|
|
7316
7524
|
},
|
|
7317
7525
|
catch: () => null
|
|
7318
7526
|
});
|
|
7527
|
+
return versionResult.isOk() ? versionResult.value : null;
|
|
7528
|
+
}
|
|
7529
|
+
async function setPackageManagerVersion(projectDir, packageManager) {
|
|
7530
|
+
const pkgJsonPath = path.join(projectDir, "package.json");
|
|
7531
|
+
if (!await fs.pathExists(pkgJsonPath)) return Result.ok(void 0);
|
|
7532
|
+
const version = await resolveInstalledPackageManagerVersion(packageManager);
|
|
7319
7533
|
return Result.tryPromise({
|
|
7320
7534
|
try: async () => {
|
|
7321
7535
|
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
7322
|
-
if (
|
|
7323
|
-
|
|
7536
|
+
if (version) {
|
|
7537
|
+
const packageManagerField = `${packageManager}@${version}`;
|
|
7538
|
+
if (isValidPackageManagerField(packageManagerField)) pkgJson.packageManager = packageManagerField;
|
|
7539
|
+
else delete pkgJson.packageManager;
|
|
7540
|
+
} else delete pkgJson.packageManager;
|
|
7324
7541
|
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
7325
7542
|
},
|
|
7326
7543
|
catch: (e) => new ProjectCreationError({
|
|
@@ -7349,9 +7566,11 @@ function createEmptyResult(timeScaffolded, elapsedTimeMs, error) {
|
|
|
7349
7566
|
frontend: [],
|
|
7350
7567
|
addons: [],
|
|
7351
7568
|
examples: [],
|
|
7569
|
+
testing: [],
|
|
7352
7570
|
auth: "none",
|
|
7353
7571
|
payments: "none",
|
|
7354
|
-
observability:
|
|
7572
|
+
observability: [],
|
|
7573
|
+
communication: "none",
|
|
7355
7574
|
git: false,
|
|
7356
7575
|
packageManager: "npm",
|
|
7357
7576
|
install: false,
|
|
@@ -7389,7 +7608,7 @@ async function createProjectHandler(input, options = {}) {
|
|
|
7389
7608
|
async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
7390
7609
|
return Result.gen(async function* () {
|
|
7391
7610
|
if (!isSilent() && input.renderTitle !== false) renderTitle();
|
|
7392
|
-
if (!isSilent()) intro(pc.magenta("Creating a new
|
|
7611
|
+
if (!isSilent()) intro(pc.magenta("Creating a new kubojs project"));
|
|
7393
7612
|
if (!isSilent() && input.yolo) cliConsola.fatal("YOLO mode enabled - skipping checks. Things may break!");
|
|
7394
7613
|
let currentPathInput;
|
|
7395
7614
|
if (isSilent()) currentPathInput = yield* Result.await(resolveProjectNameForSilent(input));
|
|
@@ -7523,6 +7742,8 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
|
|
|
7523
7742
|
if (!input.yolo) {
|
|
7524
7743
|
const addonsValidationResult = validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth, config.backend, config.runtime);
|
|
7525
7744
|
if (addonsValidationResult.isErr()) return Result.err(new CLIError({ message: addonsValidationResult.error.message }));
|
|
7745
|
+
const testingValidationResult = validateTestingAgainstFrontends(config.testing, config.frontend);
|
|
7746
|
+
if (testingValidationResult.isErr()) return Result.err(new CLIError({ message: testingValidationResult.error.message }));
|
|
7526
7747
|
}
|
|
7527
7748
|
const reproducibleCommand = generateReproducibleCommand(config);
|
|
7528
7749
|
if (input.dryRun) {
|
|
@@ -7655,12 +7876,14 @@ const SchemaNameSchema = z.enum([
|
|
|
7655
7876
|
"frontend",
|
|
7656
7877
|
"addons",
|
|
7657
7878
|
"examples",
|
|
7879
|
+
"testing",
|
|
7658
7880
|
"packageManager",
|
|
7659
7881
|
"databaseSetup",
|
|
7660
7882
|
"api",
|
|
7661
7883
|
"auth",
|
|
7662
7884
|
"payments",
|
|
7663
7885
|
"observability",
|
|
7886
|
+
"communication",
|
|
7664
7887
|
"webDeploy",
|
|
7665
7888
|
"serverDeploy",
|
|
7666
7889
|
"directoryConflict",
|
|
@@ -7670,8 +7893,8 @@ const SchemaNameSchema = z.enum([
|
|
|
7670
7893
|
"createInput",
|
|
7671
7894
|
"addInput",
|
|
7672
7895
|
"projectConfig",
|
|
7673
|
-
"
|
|
7674
|
-
"
|
|
7896
|
+
"kubojsConfig",
|
|
7897
|
+
"kubojsConfigFile",
|
|
7675
7898
|
"initResult"
|
|
7676
7899
|
]).default("all");
|
|
7677
7900
|
const t = initTRPC.meta().create();
|
|
@@ -7693,7 +7916,7 @@ function getSchemaResult(name) {
|
|
|
7693
7916
|
}
|
|
7694
7917
|
const router = t.router({
|
|
7695
7918
|
create: t.procedure.meta({
|
|
7696
|
-
description: "Create a new
|
|
7919
|
+
description: "Create a new kubojs project",
|
|
7697
7920
|
default: true,
|
|
7698
7921
|
negateBooleans: true
|
|
7699
7922
|
}).input(z.tuple([types_exports.ProjectNameSchema.optional(), z.object({
|
|
@@ -7707,9 +7930,12 @@ const router = t.router({
|
|
|
7707
7930
|
auth: types_exports.AuthSchema.optional(),
|
|
7708
7931
|
payments: types_exports.PaymentsSchema.optional(),
|
|
7709
7932
|
observability: types_exports.ObservabilitySchema.optional(),
|
|
7933
|
+
disableObservability: z.boolean().optional().describe("Disable all observability providers"),
|
|
7934
|
+
communication: types_exports.CommunicationSchema.optional(),
|
|
7710
7935
|
frontend: z.array(types_exports.FrontendSchema).optional(),
|
|
7711
7936
|
addons: z.array(types_exports.AddonsSchema).optional(),
|
|
7712
7937
|
examples: z.array(types_exports.ExamplesSchema).optional(),
|
|
7938
|
+
testing: z.array(types_exports.TestingSchema).optional(),
|
|
7713
7939
|
git: z.boolean().optional(),
|
|
7714
7940
|
packageManager: types_exports.PackageManagerSchema.optional(),
|
|
7715
7941
|
install: z.boolean().optional(),
|
|
@@ -7742,11 +7968,11 @@ const router = t.router({
|
|
|
7742
7968
|
return result;
|
|
7743
7969
|
}),
|
|
7744
7970
|
schema: t.procedure.meta({ description: "Show runtime CLI and input schemas as JSON" }).input(z.object({ name: SchemaNameSchema.describe("Schema name to inspect") })).query(({ input }) => getSchemaResult(input.name)),
|
|
7745
|
-
|
|
7746
|
-
docs: t.procedure.meta({ description: "Open I dont know documentation" }).mutation(() => openDocsCommand()),
|
|
7971
|
+
docs: t.procedure.meta({ description: "Open kubojs documentation" }).mutation(() => openDocsCommand()),
|
|
7747
7972
|
builder: t.procedure.meta({ description: "Open the web-based stack builder" }).mutation(() => openBuilderCommand()),
|
|
7748
|
-
add: t.procedure.meta({ description: "Add addons to an existing
|
|
7973
|
+
add: t.procedure.meta({ description: "Add addons to an existing kubojs project" }).input(z.object({
|
|
7749
7974
|
addons: z.array(types_exports.AddonsSchema).optional().describe("Addons to add"),
|
|
7975
|
+
testing: z.array(types_exports.TestingSchema).optional().describe("Testing tools to add"),
|
|
7750
7976
|
install: z.boolean().optional().default(false).describe("Install dependencies after adding"),
|
|
7751
7977
|
packageManager: types_exports.PackageManagerSchema.optional().describe("Package manager to use"),
|
|
7752
7978
|
projectDir: z.string().optional().describe("Project directory (defaults to current)")
|
|
@@ -7778,7 +8004,7 @@ function createBtsCli() {
|
|
|
7778
8004
|
});
|
|
7779
8005
|
}
|
|
7780
8006
|
/**
|
|
7781
|
-
* Programmatic API to create a new
|
|
8007
|
+
* Programmatic API to create a new kubojs project.
|
|
7782
8008
|
* Returns a Result type - no console output, no interactive prompts.
|
|
7783
8009
|
*
|
|
7784
8010
|
* @example
|
|
@@ -7829,9 +8055,6 @@ async function create(projectName, options) {
|
|
|
7829
8055
|
}
|
|
7830
8056
|
});
|
|
7831
8057
|
}
|
|
7832
|
-
async function sponsors() {
|
|
7833
|
-
return showSponsorsCommand();
|
|
7834
|
-
}
|
|
7835
8058
|
async function docs() {
|
|
7836
8059
|
return openDocsCommand();
|
|
7837
8060
|
}
|
|
@@ -7875,9 +8098,11 @@ async function createVirtual(options) {
|
|
|
7875
8098
|
frontend: options.frontend || ["tanstack-router"],
|
|
7876
8099
|
addons: options.addons || [],
|
|
7877
8100
|
examples: options.examples || [],
|
|
8101
|
+
testing: options.testing || [],
|
|
7878
8102
|
auth: options.auth || "none",
|
|
7879
8103
|
payments: options.payments || "none",
|
|
7880
|
-
observability: options.observability ||
|
|
8104
|
+
observability: options.disableObservability ? [] : options.observability || [],
|
|
8105
|
+
communication: options.communication || "none",
|
|
7881
8106
|
git: options.git ?? false,
|
|
7882
8107
|
packageManager: options.packageManager || "bun",
|
|
7883
8108
|
install: false,
|
|
@@ -7894,10 +8119,13 @@ async function createVirtual(options) {
|
|
|
7894
8119
|
"frontend",
|
|
7895
8120
|
"addons",
|
|
7896
8121
|
"examples",
|
|
8122
|
+
"testing",
|
|
7897
8123
|
"auth",
|
|
7898
8124
|
"dbSetup",
|
|
7899
8125
|
"payments",
|
|
7900
8126
|
"observability",
|
|
8127
|
+
"disableObservability",
|
|
8128
|
+
"communication",
|
|
7901
8129
|
"api",
|
|
7902
8130
|
"webDeploy",
|
|
7903
8131
|
"serverDeploy"
|
|
@@ -7913,7 +8141,7 @@ async function createVirtual(options) {
|
|
|
7913
8141
|
});
|
|
7914
8142
|
}
|
|
7915
8143
|
/**
|
|
7916
|
-
* Programmatic API to add addons to an existing
|
|
8144
|
+
* Programmatic API to add addons to an existing kubojs project.
|
|
7917
8145
|
*
|
|
7918
8146
|
* @example
|
|
7919
8147
|
* ```typescript
|
|
@@ -7933,4 +8161,4 @@ async function add(options = {}) {
|
|
|
7933
8161
|
return addHandler(options, { silent: true });
|
|
7934
8162
|
}
|
|
7935
8163
|
//#endregion
|
|
7936
|
-
export {
|
|
8164
|
+
export { UserCancelledError as C, ProjectCreationError as S, getLatestCLIVersion as _, TEMPLATE_COUNT as a, DatabaseSetupError as b, builder as c, createVirtual as d, docs as f, types_exports as g, router as h, SchemaNameSchema as i, create as l, getSchemaResult as m, GeneratorError$1 as n, VirtualFileSystem$1 as o, generate$1 as p, Result$1 as r, add as s, EMBEDDED_TEMPLATES$1 as t, createBtsCli as u, CLIError as v, ValidationError as w, DirectoryConflictError as x, CompatibilityError as y };
|