create-better-t-stack 3.42.2 → 3.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +4 -3
- package/dist/index.d.mts +1090 -295
- package/dist/index.mjs +1 -1
- package/dist/{src-C0_lYEa0.mjs → src-BSgSoI0r.mjs} +653 -758
- package/dist/virtual.d.mts +1 -1
- package/package.json +17 -16
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { n as __reExport, t as __exportAll } from "./rolldown-runtime-yhw22V8Z.mjs";
|
|
3
3
|
import { getAllJsonSchemas } from "@better-t-stack/types/json-schema";
|
|
4
|
-
import {
|
|
4
|
+
import { os } from "@orpc/server";
|
|
5
5
|
import { Result, Result as Result$1, TaggedError } from "better-result";
|
|
6
6
|
import { createCli } from "trpc-cli";
|
|
7
|
-
import z from "zod";
|
|
7
|
+
import z, { z as z$1 } from "zod";
|
|
8
8
|
import { box, cancel, confirm, intro, isCancel, limitOptions, log, outro, select, spinner, text } from "@clack/prompts";
|
|
9
9
|
import pc from "picocolors";
|
|
10
10
|
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 {
|
|
15
|
-
import {
|
|
14
|
+
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, processNxConfig, processPackageConfigs, processPwaPlugins, processTemplateString, processTurboConfig, processVitePlusConfig } from "@better-t-stack/template-generator";
|
|
15
|
+
import { BetterTStackConfigSchema, CONVEX_AI_INCOMPATIBLE_FRONTENDS, CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS, CONVEX_BETTER_AUTH_SUPPORTED_FRONTENDS, FULLSTACK_FRONTENDS, OBSERVABILITY_ADDONS, OBSERVABILITY_ADDONS as OBSERVABILITY_ADDONS$1, SERVER_BACKENDS, TASK_RUNNER_ADDONS, TASK_RUNNER_ADDONS as TASK_RUNNER_ADDONS$1, TRPC_INCOMPATIBLE_FRONTENDS, allowedApisForFrontends, allowedApisForFrontends as allowedApisForFrontends$1, getBackendDisabledOptions, getDesktopDeployConflict, hasCloudflareNextPostgresConflict, isExampleAIAllowed, isExampleAIAllowed as isExampleAIAllowed$1, isExampleTodoAllowed, isExampleTodoAllowed as isExampleTodoAllowed$1, isFrontendAllowedWithBackend, isFrontendAllowedWithBackend as isFrontendAllowedWithBackend$1, supportsClerkBackend, supportsClerkFrontend, supportsConvexBetterAuth, supportsDatabaseSetup, supportsDatabaseSetupRuntime, supportsOrmDatabase, supportsPaymentsAuth, supportsPrismaWebDeploy, supportsPrismaWebDeploy as supportsPrismaWebDeploy$1, supportsRuntimeBackend, supportsRuntimeDatabase, supportsServerDeployRuntime, usesAlchemyManagedDatabase, validateAddonCompatibility, validateAddonCompatibility as validateAddonCompatibility$1, webFrontends } from "@better-t-stack/types";
|
|
16
|
+
import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1, wrapTextWithPrefix } from "@clack/core";
|
|
16
17
|
import { consola, createConsola } from "consola";
|
|
17
18
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
18
19
|
import gradient from "gradient-string";
|
|
19
20
|
import { $, execa } from "execa";
|
|
20
21
|
import { writeTree } from "@better-t-stack/template-generator/fs-writer";
|
|
21
|
-
import
|
|
22
|
+
import * as fs$1 from "node:fs/promises";
|
|
22
23
|
import { applyEdits, modify, parse } from "jsonc-parser";
|
|
23
|
-
import os from "node:os";
|
|
24
|
+
import os$1 from "node:os";
|
|
24
25
|
import { clean, coerce, satisfies } from "semver";
|
|
25
26
|
import { format } from "oxfmt";
|
|
26
27
|
//#region src/utils/display-config.ts
|
|
@@ -76,6 +77,7 @@ const VALUE_LABELS = {
|
|
|
76
77
|
skills: "Agent skills",
|
|
77
78
|
mcp: "MCP servers",
|
|
78
79
|
evlog: "evlog",
|
|
80
|
+
axiom: "Axiom",
|
|
79
81
|
todo: "Todo app",
|
|
80
82
|
ai: "AI chat",
|
|
81
83
|
turso: "Turso",
|
|
@@ -205,32 +207,6 @@ function getDefaultConfig() {
|
|
|
205
207
|
};
|
|
206
208
|
}
|
|
207
209
|
const DEFAULT_CONFIG = getDefaultConfig();
|
|
208
|
-
const ADDON_COMPATIBILITY = {
|
|
209
|
-
pwa: [
|
|
210
|
-
"tanstack-router",
|
|
211
|
-
"react-router",
|
|
212
|
-
"solid",
|
|
213
|
-
"next"
|
|
214
|
-
],
|
|
215
|
-
tauri: desktopWebFrontends$2,
|
|
216
|
-
electrobun: desktopWebFrontends$2,
|
|
217
|
-
biome: [],
|
|
218
|
-
husky: [],
|
|
219
|
-
lefthook: [],
|
|
220
|
-
turborepo: [],
|
|
221
|
-
nx: [],
|
|
222
|
-
"vite-plus": [],
|
|
223
|
-
starlight: [],
|
|
224
|
-
ultracite: [],
|
|
225
|
-
mcp: [],
|
|
226
|
-
oxlint: [],
|
|
227
|
-
fumadocs: [],
|
|
228
|
-
opentui: [],
|
|
229
|
-
wxt: [],
|
|
230
|
-
skills: [],
|
|
231
|
-
evlog: [],
|
|
232
|
-
none: []
|
|
233
|
-
};
|
|
234
210
|
//#endregion
|
|
235
211
|
//#region src/utils/context.ts
|
|
236
212
|
const cliStorage = new AsyncLocalStorage();
|
|
@@ -396,42 +372,52 @@ const noopSpinner = {
|
|
|
396
372
|
const FRAME_MS = 80;
|
|
397
373
|
const HIDE_CURSOR = "\x1B[?25l";
|
|
398
374
|
const SHOW_CURSOR = "\x1B[?25h";
|
|
399
|
-
const
|
|
400
|
-
|
|
375
|
+
const ERASE_DOWN = "\r\x1B[J";
|
|
376
|
+
const cursorUp = (rows) => `\x1b[${rows}A`;
|
|
377
|
+
let cursorOutput;
|
|
401
378
|
let restoreCursorOnExit = false;
|
|
402
|
-
function hideCursor() {
|
|
403
|
-
if (
|
|
404
|
-
|
|
405
|
-
|
|
379
|
+
function hideCursor(out) {
|
|
380
|
+
if (cursorOutput) return;
|
|
381
|
+
cursorOutput = out;
|
|
382
|
+
out.write(HIDE_CURSOR);
|
|
406
383
|
if (!restoreCursorOnExit) {
|
|
407
384
|
restoreCursorOnExit = true;
|
|
408
385
|
process.once("exit", () => {
|
|
409
|
-
|
|
386
|
+
cursorOutput?.write(SHOW_CURSOR);
|
|
410
387
|
});
|
|
411
388
|
}
|
|
412
389
|
}
|
|
413
|
-
function showCursor() {
|
|
414
|
-
if (
|
|
415
|
-
|
|
416
|
-
|
|
390
|
+
function showCursor(out) {
|
|
391
|
+
if (cursorOutput !== out) return;
|
|
392
|
+
cursorOutput = void 0;
|
|
393
|
+
out.write(SHOW_CURSOR);
|
|
417
394
|
}
|
|
418
395
|
/**
|
|
419
396
|
* The clack spinner puts stdin in raw mode and exits the process on Ctrl-C from its own
|
|
420
397
|
* keypress handler. This one never touches stdin, so Ctrl-C stays a SIGINT and the
|
|
421
398
|
* interrupt scope decides what happens.
|
|
422
399
|
*/
|
|
423
|
-
function createTerminalSpinner() {
|
|
424
|
-
const
|
|
425
|
-
const animate = out.isTTY && !process.env.CI;
|
|
400
|
+
function createTerminalSpinner(out) {
|
|
401
|
+
const animate = Boolean(out.isTTY) && !process.env.CI;
|
|
426
402
|
let text = "";
|
|
427
403
|
let active = false;
|
|
428
404
|
let interruptedBefore = false;
|
|
429
405
|
let frame = 0;
|
|
430
406
|
let dots = 0;
|
|
407
|
+
let renderedRows = 0;
|
|
431
408
|
let timer;
|
|
409
|
+
const clearFrame = () => {
|
|
410
|
+
if (renderedRows > 1) out.write(cursorUp(renderedRows - 1));
|
|
411
|
+
out.write(ERASE_DOWN);
|
|
412
|
+
renderedRows = 0;
|
|
413
|
+
};
|
|
432
414
|
const render = () => {
|
|
433
415
|
const suffix = ".".repeat(Math.floor(dots)).slice(0, 3);
|
|
434
|
-
|
|
416
|
+
const line = `${pc.magenta(SPINNER_FRAMES[frame])} ${text}${suffix}`;
|
|
417
|
+
clearFrame();
|
|
418
|
+
const wrapped = wrapTextWithPrefix(out, line, "");
|
|
419
|
+
out.write(wrapped.replaceAll("\n", "\r\n"));
|
|
420
|
+
renderedRows = wrapped.split("\n").length;
|
|
435
421
|
frame = (frame + 1) % SPINNER_FRAMES.length;
|
|
436
422
|
dots = dots < 4 ? dots + .125 : 0;
|
|
437
423
|
};
|
|
@@ -446,7 +432,7 @@ function createTerminalSpinner() {
|
|
|
446
432
|
setText(message);
|
|
447
433
|
out.write(`${pc.gray(S_BAR)}\n`);
|
|
448
434
|
if (animate) {
|
|
449
|
-
hideCursor();
|
|
435
|
+
hideCursor(out);
|
|
450
436
|
render();
|
|
451
437
|
timer = setInterval(render, FRAME_MS);
|
|
452
438
|
} else out.write(`${pc.magenta(SPINNER_FRAMES[0])} ${text}\n`);
|
|
@@ -457,16 +443,16 @@ function createTerminalSpinner() {
|
|
|
457
443
|
active = false;
|
|
458
444
|
if (timer) clearInterval(timer);
|
|
459
445
|
if (animate) {
|
|
460
|
-
|
|
461
|
-
showCursor();
|
|
446
|
+
clearFrame();
|
|
447
|
+
showCursor(out);
|
|
462
448
|
}
|
|
463
449
|
const cancelled = wasInterrupted() && !interruptedBefore;
|
|
464
450
|
out.write(cancelled ? `${pc.yellow(S_STEP_CANCEL)} ${text} (cancelled)\n` : `${pc.green(S_STEP_SUBMIT)} ${message || text}\n`);
|
|
465
451
|
}
|
|
466
452
|
};
|
|
467
453
|
}
|
|
468
|
-
function createSpinner() {
|
|
469
|
-
return isSilent() ? noopSpinner : createTerminalSpinner();
|
|
454
|
+
function createSpinner(output) {
|
|
455
|
+
return isSilent() ? noopSpinner : createTerminalSpinner(output ?? process.stdout);
|
|
470
456
|
}
|
|
471
457
|
const baseConsola = createConsola({
|
|
472
458
|
...consola.options,
|
|
@@ -1059,40 +1045,9 @@ import * as import__better_t_stack_types from "@better-t-stack/types";
|
|
|
1059
1045
|
__reExport(types_exports, import__better_t_stack_types);
|
|
1060
1046
|
//#endregion
|
|
1061
1047
|
//#region src/utils/compatibility.ts
|
|
1062
|
-
const WEB_FRAMEWORKS =
|
|
1063
|
-
"tanstack-router",
|
|
1064
|
-
"react-router",
|
|
1065
|
-
"tanstack-start",
|
|
1066
|
-
"next",
|
|
1067
|
-
"nuxt",
|
|
1068
|
-
"svelte",
|
|
1069
|
-
"solid",
|
|
1070
|
-
"astro"
|
|
1071
|
-
];
|
|
1048
|
+
const WEB_FRAMEWORKS = webFrontends;
|
|
1072
1049
|
//#endregion
|
|
1073
1050
|
//#region src/utils/compatibility-rules.ts
|
|
1074
|
-
const TASK_RUNNER_ADDONS = [
|
|
1075
|
-
"turborepo",
|
|
1076
|
-
"nx",
|
|
1077
|
-
"vite-plus"
|
|
1078
|
-
];
|
|
1079
|
-
const STATIC_DESKTOP_ADDONS = ["tauri", "electrobun"];
|
|
1080
|
-
const TAURI_STATIC_EXPORT_FRONTENDS = ["next", "tanstack-start"];
|
|
1081
|
-
const CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS = [
|
|
1082
|
-
"nuxt",
|
|
1083
|
-
"svelte",
|
|
1084
|
-
"solid",
|
|
1085
|
-
"astro"
|
|
1086
|
-
];
|
|
1087
|
-
const CONVEX_BETTER_AUTH_SUPPORTED_FRONTENDS = [
|
|
1088
|
-
"tanstack-router",
|
|
1089
|
-
"react-router",
|
|
1090
|
-
"tanstack-start",
|
|
1091
|
-
"next",
|
|
1092
|
-
"native-bare",
|
|
1093
|
-
"native-uniwind",
|
|
1094
|
-
"native-unistyles"
|
|
1095
|
-
];
|
|
1096
1051
|
function validationErr$1(message) {
|
|
1097
1052
|
return Result.err(new ValidationError({ message }));
|
|
1098
1053
|
}
|
|
@@ -1111,111 +1066,29 @@ function ensureSingleWebAndNative(frontends) {
|
|
|
1111
1066
|
if (native.length > 1) return validationErr$1("Cannot select multiple native frameworks. Choose only one of: native-bare, native-uniwind, native-unistyles");
|
|
1112
1067
|
return Result.ok(void 0);
|
|
1113
1068
|
}
|
|
1114
|
-
|
|
1115
|
-
"next",
|
|
1116
|
-
"tanstack-start",
|
|
1117
|
-
"nuxt",
|
|
1118
|
-
"svelte",
|
|
1119
|
-
"solid",
|
|
1120
|
-
"astro"
|
|
1121
|
-
];
|
|
1122
|
-
const EVLOG_SERVER_BACKENDS = [
|
|
1123
|
-
"hono",
|
|
1124
|
-
"express",
|
|
1125
|
-
"fastify",
|
|
1126
|
-
"elysia"
|
|
1127
|
-
];
|
|
1128
|
-
const EVLOG_FULLSTACK_FRONTENDS = [
|
|
1129
|
-
"next",
|
|
1130
|
-
"tanstack-start",
|
|
1131
|
-
"nuxt",
|
|
1132
|
-
"svelte",
|
|
1133
|
-
"astro"
|
|
1134
|
-
];
|
|
1135
|
-
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.";
|
|
1136
|
-
function supportsEvlogAddon(frontend = [], backend, _runtime) {
|
|
1137
|
-
if (!backend) return true;
|
|
1138
|
-
if (EVLOG_SERVER_BACKENDS.includes(backend)) return true;
|
|
1139
|
-
if (backend === "self") {
|
|
1140
|
-
if (frontend.length === 0) return true;
|
|
1141
|
-
return frontend.some((f) => EVLOG_FULLSTACK_FRONTENDS.includes(f));
|
|
1142
|
-
}
|
|
1143
|
-
return false;
|
|
1144
|
-
}
|
|
1145
|
-
function validateSelfBackendCompatibility(providedFlags, options, config) {
|
|
1069
|
+
function validateSelfBackendCompatibility(_providedFlags, options, config) {
|
|
1146
1070
|
const backend = config.backend || options.backend;
|
|
1147
1071
|
const frontends = config.frontend || options.frontend || [];
|
|
1148
1072
|
if (backend === "self") {
|
|
1149
1073
|
const { web, native } = splitFrontends(frontends);
|
|
1150
|
-
if (!(web.length === 1 && FULLSTACK_FRONTENDS
|
|
1074
|
+
if (!(web.length === 1 && FULLSTACK_FRONTENDS.some((frontend) => frontend === web[0]))) return validationErr$1("Backend 'self' (fullstack) currently only supports Next.js, TanStack Start, Nuxt, SvelteKit, Solid, and Astro frontends. Please use --frontend next, --frontend tanstack-start, --frontend nuxt, --frontend svelte, --frontend solid, or --frontend astro.");
|
|
1151
1075
|
if (native.length > 1) return validationErr$1("Cannot select multiple native frameworks. Choose only one of: native-bare, native-uniwind, native-unistyles");
|
|
1152
1076
|
}
|
|
1153
|
-
const hasFullstackFrontend = frontends.some((f) => FULLSTACK_FRONTENDS$1.includes(f));
|
|
1154
|
-
if (providedFlags.has("backend") && !hasFullstackFrontend && backend === "self") return validationErr$1("Backend 'self' (fullstack) currently only supports Next.js, TanStack Start, Nuxt, SvelteKit, Solid, and Astro frontends. Please use --frontend next, --frontend tanstack-start, --frontend nuxt, --frontend svelte, --frontend solid, --frontend astro, or choose a different backend.");
|
|
1155
1077
|
return Result.ok(void 0);
|
|
1156
1078
|
}
|
|
1157
1079
|
function validateWorkersCompatibility(providedFlags, options, config) {
|
|
1158
|
-
if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && config.backend
|
|
1159
|
-
if (providedFlags.has("backend") && config.backend && config.backend
|
|
1160
|
-
if (providedFlags.has("runtime") && options.runtime
|
|
1161
|
-
if (providedFlags.has("database") && config.
|
|
1080
|
+
if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && !supportsRuntimeBackend("workers", config.backend)) return validationErr$1(`Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend (--backend hono). Current backend: ${config.backend}. Please use '--backend hono' or choose a different runtime.`);
|
|
1081
|
+
if (providedFlags.has("backend") && config.backend && !supportsRuntimeBackend("workers", config.backend) && config.runtime === "workers") return validationErr$1(`Backend '${config.backend}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Hono backend. Please use '--backend hono' or choose a different runtime.`);
|
|
1082
|
+
if (providedFlags.has("runtime") && !supportsRuntimeDatabase(options.runtime, config.database)) return validationErr$1("Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.");
|
|
1083
|
+
if (providedFlags.has("database") && !supportsRuntimeDatabase(config.runtime, config.database)) return validationErr$1("MongoDB database is not compatible with Cloudflare Workers runtime. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.");
|
|
1162
1084
|
return Result.ok(void 0);
|
|
1163
1085
|
}
|
|
1164
1086
|
function validateApiFrontendCompatibility(api, frontends = []) {
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
const includesAstro = frontends.includes("astro");
|
|
1169
|
-
if ((includesNuxt || includesSvelte || includesSolid || includesAstro) && api === "trpc") return validationErr$1(`tRPC API is not supported with '${includesNuxt ? "nuxt" : includesSvelte ? "svelte" : includesSolid ? "solid" : "astro"}' frontend. Please use --api orpc or --api none or remove '${includesNuxt ? "nuxt" : includesSvelte ? "svelte" : includesSolid ? "solid" : "astro"}' from --frontend.`);
|
|
1170
|
-
return Result.ok(void 0);
|
|
1171
|
-
}
|
|
1172
|
-
function isFrontendAllowedWithBackend(frontend, backend, auth) {
|
|
1173
|
-
if (backend === "convex") {
|
|
1174
|
-
if (auth === "better-auth" && CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS.includes(frontend)) return false;
|
|
1175
|
-
if (frontend === "solid" || frontend === "astro") return false;
|
|
1087
|
+
if (api === "trpc" && !allowedApisForFrontends(frontends).includes(api)) {
|
|
1088
|
+
const frontend = frontends.find((f) => TRPC_INCOMPATIBLE_FRONTENDS.some((value) => value === f));
|
|
1089
|
+
return validationErr$1(`tRPC API is not supported with '${frontend}' frontend. Please use --api orpc or --api none or remove '${frontend}' from --frontend.`);
|
|
1176
1090
|
}
|
|
1177
|
-
|
|
1178
|
-
if ([
|
|
1179
|
-
"nuxt",
|
|
1180
|
-
"svelte",
|
|
1181
|
-
"solid",
|
|
1182
|
-
"astro"
|
|
1183
|
-
].includes(frontend)) return false;
|
|
1184
|
-
}
|
|
1185
|
-
return true;
|
|
1186
|
-
}
|
|
1187
|
-
function supportsConvexBetterAuth(frontends = []) {
|
|
1188
|
-
return frontends.some((frontend) => CONVEX_BETTER_AUTH_SUPPORTED_FRONTENDS.includes(frontend));
|
|
1189
|
-
}
|
|
1190
|
-
function allowedApisForFrontends(frontends = []) {
|
|
1191
|
-
const includesNuxt = frontends.includes("nuxt");
|
|
1192
|
-
const includesSvelte = frontends.includes("svelte");
|
|
1193
|
-
const includesSolid = frontends.includes("solid");
|
|
1194
|
-
const includesAstro = frontends.includes("astro");
|
|
1195
|
-
const base = [
|
|
1196
|
-
"trpc",
|
|
1197
|
-
"orpc",
|
|
1198
|
-
"none"
|
|
1199
|
-
];
|
|
1200
|
-
if (includesNuxt || includesSvelte || includesSolid || includesAstro) return ["orpc", "none"];
|
|
1201
|
-
return base;
|
|
1202
|
-
}
|
|
1203
|
-
function isExampleTodoAllowed(backend, database, api) {
|
|
1204
|
-
if (backend === "convex") return true;
|
|
1205
|
-
if (database === "none" || api === "none") return false;
|
|
1206
|
-
return true;
|
|
1207
|
-
}
|
|
1208
|
-
function isExampleAIAllowed(backend, frontends = []) {
|
|
1209
|
-
if (backend === "none") return false;
|
|
1210
|
-
const includesSolid = frontends.includes("solid");
|
|
1211
|
-
const includesAstro = frontends.includes("astro");
|
|
1212
|
-
if (includesSolid || includesAstro) return false;
|
|
1213
|
-
if (backend === "convex") {
|
|
1214
|
-
const includesNuxt = frontends.includes("nuxt");
|
|
1215
|
-
const includesSvelte = frontends.includes("svelte");
|
|
1216
|
-
if (includesNuxt || includesSvelte) return false;
|
|
1217
|
-
}
|
|
1218
|
-
return true;
|
|
1091
|
+
return Result.ok(void 0);
|
|
1219
1092
|
}
|
|
1220
1093
|
function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
|
|
1221
1094
|
if (webDeploy && webDeploy !== "none" && !hasWebFrontendFlag) return validationErr$1("'--web-deploy' requires a web frontend. Please select a web frontend or set '--web-deploy none'.");
|
|
@@ -1227,97 +1100,48 @@ function validateServerDeployRequiresBackend(serverDeploy, backend) {
|
|
|
1227
1100
|
}
|
|
1228
1101
|
function validateDockerServerDeploy(serverDeploy, backend, runtime) {
|
|
1229
1102
|
if (serverDeploy !== "docker") return Result.ok(void 0);
|
|
1230
|
-
if (backend
|
|
1231
|
-
if (runtime
|
|
1103
|
+
if (backend && backend !== "none" && !SERVER_BACKENDS.includes(backend)) return validationErr$1("'--server-deploy docker' requires a separate server backend (hono, express, fastify, elysia). For a fullstack 'self' backend, use '--web-deploy docker' instead.");
|
|
1104
|
+
if (runtime && !supportsServerDeployRuntime(serverDeploy, runtime)) return validationErr$1("'--server-deploy docker' is not compatible with '--runtime workers'. Use '--runtime bun' or '--runtime node', or choose '--server-deploy cloudflare'.");
|
|
1232
1105
|
return Result.ok(void 0);
|
|
1233
1106
|
}
|
|
1234
1107
|
function validateVercelServerDeploy(serverDeploy, backend, runtime) {
|
|
1235
1108
|
if (serverDeploy !== "vercel") return Result.ok(void 0);
|
|
1236
|
-
if (backend
|
|
1237
|
-
if (runtime
|
|
1109
|
+
if (backend && backend !== "none" && !SERVER_BACKENDS.includes(backend)) return validationErr$1("'--server-deploy vercel' requires a separate server backend (hono, express, fastify, elysia). For a fullstack 'self' backend, use '--web-deploy vercel' instead.");
|
|
1110
|
+
if (runtime && !supportsServerDeployRuntime(serverDeploy, runtime)) return validationErr$1("'--server-deploy vercel' is not compatible with '--runtime workers'. Use '--runtime bun' or '--runtime node', or choose '--server-deploy cloudflare'.");
|
|
1238
1111
|
return Result.ok(void 0);
|
|
1239
1112
|
}
|
|
1240
1113
|
function validatePrismaServerDeploy(serverDeploy, backend, runtime) {
|
|
1241
1114
|
if (serverDeploy !== "prisma") return Result.ok(void 0);
|
|
1242
|
-
if (backend
|
|
1243
|
-
if (
|
|
1115
|
+
if (backend && backend !== "none" && !SERVER_BACKENDS.includes(backend)) return validationErr$1("'--server-deploy prisma' requires a separate server backend (hono, express, fastify, elysia). For a fullstack 'self' backend, use '--web-deploy prisma' instead.");
|
|
1116
|
+
if (!supportsServerDeployRuntime(serverDeploy, runtime)) return validationErr$1("'--server-deploy prisma' requires '--runtime bun' or '--runtime node'. Use '--server-deploy cloudflare' for Workers.");
|
|
1244
1117
|
return Result.ok(void 0);
|
|
1245
1118
|
}
|
|
1246
|
-
const PRISMA_COMPUTE_WEB_FRONTENDS = [
|
|
1247
|
-
"next",
|
|
1248
|
-
"nuxt",
|
|
1249
|
-
"astro",
|
|
1250
|
-
"react-router",
|
|
1251
|
-
"tanstack-start",
|
|
1252
|
-
"svelte",
|
|
1253
|
-
"solid"
|
|
1254
|
-
];
|
|
1255
|
-
function supportsPrismaWebDeploy(frontend) {
|
|
1256
|
-
return frontend.some((value) => PRISMA_COMPUTE_WEB_FRONTENDS.includes(value));
|
|
1257
|
-
}
|
|
1258
1119
|
function validatePrismaWebDeploy(webDeploy, frontend) {
|
|
1259
1120
|
if (webDeploy !== "prisma" || !frontend) return Result.ok(void 0);
|
|
1260
1121
|
if (!supportsPrismaWebDeploy(frontend)) return validationErr$1("'--web-deploy prisma' requires a supported server frontend. Choose Next.js, Nuxt, Astro, React Router, TanStack Start, SvelteKit, or Solid. TanStack Router is a static SPA, while Prisma Compute requires an executable server artifact.");
|
|
1261
1122
|
return Result.ok(void 0);
|
|
1262
1123
|
}
|
|
1263
1124
|
function validateCloudflareWebDeployKnownIssues(config) {
|
|
1264
|
-
if (config.
|
|
1265
|
-
if (config.database === "postgres" && config.orm === "prisma" && config.dbSetup !== "neon" && config.dbSetup !== "prisma-postgres") return validationErr$1("This Prisma PostgreSQL setup with Next.js on Cloudflare is temporarily unavailable because OpenNext does not preserve pg-cloudflare's workerd files. Use Neon or Prisma Postgres, choose another Cloudflare frontend, or choose Prisma, Docker, or Vercel deployment.");
|
|
1125
|
+
if (hasCloudflareNextPostgresConflict(config)) return validationErr$1("This Prisma PostgreSQL setup with Next.js on Cloudflare is temporarily unavailable because OpenNext does not preserve pg-cloudflare's workerd files. Use Neon or Prisma Postgres, choose another Cloudflare frontend, or choose Prisma, Docker, or Vercel deployment.");
|
|
1266
1126
|
return Result.ok(void 0);
|
|
1267
1127
|
}
|
|
1268
|
-
const DESKTOP_STATIC_EXPORT_FRONTENDS = [
|
|
1269
|
-
"next",
|
|
1270
|
-
"svelte",
|
|
1271
|
-
"astro",
|
|
1272
|
-
"react-router"
|
|
1273
|
-
];
|
|
1274
1128
|
function validateDockerWebDeployDesktopAddons(webDeploy, addons, frontend, backend, auth) {
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
const affected = frontend.find((f) => DESKTOP_STATIC_EXPORT_FRONTENDS.includes(f));
|
|
1279
|
-
if (!affected) return Result.ok(void 0);
|
|
1280
|
-
if (affected === "next" && !desktopAddons.includes("tauri") && backend === "convex" && auth === "better-auth") return Result.ok(void 0);
|
|
1129
|
+
const conflict = getDesktopDeployConflict(webDeploy, addons, frontend, backend, auth);
|
|
1130
|
+
if (webDeploy !== "docker" || !conflict) return Result.ok(void 0);
|
|
1131
|
+
const { selectedDesktopAddons: desktopAddons, affectedFrontend: affected } = conflict;
|
|
1281
1132
|
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 the static-serving tanstack-router frontend.`);
|
|
1282
1133
|
}
|
|
1283
1134
|
function validatePrismaWebDeployDesktopAddons(webDeploy, addons, frontend) {
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
const affected = frontend.find((value) => DESKTOP_STATIC_EXPORT_FRONTENDS.includes(value));
|
|
1288
|
-
if (!affected) return Result.ok(void 0);
|
|
1135
|
+
const conflict = getDesktopDeployConflict(webDeploy, addons, frontend);
|
|
1136
|
+
if (webDeploy !== "prisma" || !conflict) return Result.ok(void 0);
|
|
1137
|
+
const { selectedDesktopAddons: desktopAddons, affectedFrontend: affected } = conflict;
|
|
1289
1138
|
return validationErr$1(`'--web-deploy prisma' is not compatible with the ${desktopAddons.join(", ")} addon on '${affected}' because desktop addons replace its executable server output with a static export, while Prisma Compute requires an executable server artifact. Remove the addon or choose a server deployment that supports this desktop build.`);
|
|
1290
1139
|
}
|
|
1291
|
-
function validateAddonCompatibility(addon, frontend, auth, backend, runtime) {
|
|
1292
|
-
if (addon === "evlog" && !supportsEvlogAddon(frontend, backend, runtime)) return {
|
|
1293
|
-
isCompatible: false,
|
|
1294
|
-
reason: evlogCompatibilityMessage
|
|
1295
|
-
};
|
|
1296
|
-
if (STATIC_DESKTOP_ADDONS.includes(addon) && auth === "clerk" && frontend.includes("react-router")) return {
|
|
1297
|
-
isCompatible: false,
|
|
1298
|
-
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.`
|
|
1299
|
-
};
|
|
1300
|
-
if (backend === "self" && STATIC_DESKTOP_ADDONS.includes(addon)) return {
|
|
1301
|
-
isCompatible: false,
|
|
1302
|
-
reason: `${addon} addon requires a separate backend or no backend because backend 'self' emits server routes that cannot be bundled as static desktop assets.`
|
|
1303
|
-
};
|
|
1304
|
-
if (addon === "tauri" && backend === "convex" && auth === "better-auth" && frontend.some((f) => TAURI_STATIC_EXPORT_FRONTENDS.includes(f))) return {
|
|
1305
|
-
isCompatible: false,
|
|
1306
|
-
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."
|
|
1307
|
-
};
|
|
1308
|
-
const compatibleFrontends = ADDON_COMPATIBILITY[addon];
|
|
1309
|
-
if (compatibleFrontends.length > 0) {
|
|
1310
|
-
if (!frontend.some((f) => compatibleFrontends.includes(f))) return {
|
|
1311
|
-
isCompatible: false,
|
|
1312
|
-
reason: `${addon} addon requires one of these frontends: ${compatibleFrontends.join(", ")}`
|
|
1313
|
-
};
|
|
1314
|
-
}
|
|
1315
|
-
return { isCompatible: true };
|
|
1316
|
-
}
|
|
1317
1140
|
function getCompatibleAddons(allAddons, frontend, existingAddons = [], auth, backend, runtime) {
|
|
1318
1141
|
return allAddons.filter((addon) => {
|
|
1319
1142
|
if (existingAddons.includes(addon)) return false;
|
|
1320
1143
|
if (addon === "none") return false;
|
|
1144
|
+
if (OBSERVABILITY_ADDONS.includes(addon) && existingAddons.some((existing) => OBSERVABILITY_ADDONS.includes(existing))) return false;
|
|
1321
1145
|
if (TASK_RUNNER_ADDONS.includes(addon) && existingAddons.some((existingAddon) => TASK_RUNNER_ADDONS.includes(existingAddon))) return false;
|
|
1322
1146
|
const { isCompatible } = validateAddonCompatibility(addon, frontend, auth, backend, runtime);
|
|
1323
1147
|
return isCompatible;
|
|
@@ -1327,8 +1151,8 @@ function validateAddonsAgainstFrontends(addons = [], frontends = [], auth, backe
|
|
|
1327
1151
|
if (addons.filter((addon) => TASK_RUNNER_ADDONS.includes(addon)).length > 1) return validationErr$1("Cannot combine 'turborepo', 'nx', and 'vite-plus' addons. Choose one task runner.");
|
|
1328
1152
|
for (const addon of addons) {
|
|
1329
1153
|
if (addon === "none") continue;
|
|
1330
|
-
const
|
|
1331
|
-
if (!isCompatible) return validationErr$1(`Incompatible addon/frontend combination: ${reason}`);
|
|
1154
|
+
const compatibility = validateAddonCompatibility(addon, frontends, auth, backend, runtime);
|
|
1155
|
+
if (!compatibility.isCompatible) return validationErr$1(`Incompatible addon/frontend combination: ${compatibility.reason}`);
|
|
1332
1156
|
}
|
|
1333
1157
|
return Result.ok(void 0);
|
|
1334
1158
|
}
|
|
@@ -1344,25 +1168,22 @@ function validateAddonsAgainstConfig(addons = [], config) {
|
|
|
1344
1168
|
function validatePaymentsCompatibility(payments, auth, _backend, _frontends = []) {
|
|
1345
1169
|
if (!payments || payments === "none") return Result.ok(void 0);
|
|
1346
1170
|
if (payments === "polar") {
|
|
1347
|
-
if (!
|
|
1171
|
+
if (!supportsPaymentsAuth(payments, auth)) return validationErr$1("Polar payments requires Better Auth. Please use '--auth better-auth' or choose a different payments provider.");
|
|
1348
1172
|
}
|
|
1349
1173
|
return Result.ok(void 0);
|
|
1350
1174
|
}
|
|
1351
1175
|
function validateExamplesCompatibility(examples, backend, database, frontend, api) {
|
|
1352
1176
|
const examplesArr = examples ?? [];
|
|
1353
1177
|
if (examplesArr.length === 0 || examplesArr.includes("none")) return Result.ok(void 0);
|
|
1354
|
-
if (examplesArr.includes("todo") && backend
|
|
1178
|
+
if (examplesArr.includes("todo") && !isExampleTodoAllowed(backend, database, api)) {
|
|
1355
1179
|
if (database === "none") return validationErr$1("The 'todo' example requires a database. Cannot use --examples todo when database is 'none'.");
|
|
1356
1180
|
if (api === "none") return validationErr$1("The 'todo' example requires an API layer (tRPC or oRPC). Cannot use --examples todo when api is 'none'.");
|
|
1357
1181
|
}
|
|
1358
|
-
if (examplesArr.includes("ai") && (frontend
|
|
1359
|
-
if (examplesArr.includes("ai") && (frontend
|
|
1182
|
+
if (examplesArr.includes("ai") && !isExampleAIAllowed(backend, frontend) && frontend?.includes("solid")) return validationErr$1("The 'ai' example is not compatible with the Solid frontend.");
|
|
1183
|
+
if (examplesArr.includes("ai") && !isExampleAIAllowed(backend, frontend) && frontend?.includes("astro")) return validationErr$1("The 'ai' example is not compatible with the Astro frontend.");
|
|
1360
1184
|
if (examplesArr.includes("ai") && backend === "none") return validationErr$1("The 'ai' example requires a backend.");
|
|
1361
1185
|
if (examplesArr.includes("ai") && backend === "convex") {
|
|
1362
|
-
|
|
1363
|
-
const includesNuxt = frontendArr.includes("nuxt");
|
|
1364
|
-
const includesSvelte = frontendArr.includes("svelte");
|
|
1365
|
-
if (includesNuxt || includesSvelte) return validationErr$1("The 'ai' example with Convex backend only supports React-based frontends (Next.js, TanStack Router, TanStack Start, React Router). Svelte and Nuxt are not supported with Convex AI.");
|
|
1186
|
+
if ((frontend ?? []).some((f) => CONVEX_AI_INCOMPATIBLE_FRONTENDS.some((value) => value === f))) return validationErr$1("The 'ai' example with Convex backend only supports React-based frontends (Next.js, TanStack Router, TanStack Start, React Router). Svelte and Nuxt are not supported with Convex AI.");
|
|
1366
1187
|
}
|
|
1367
1188
|
return Result.ok(void 0);
|
|
1368
1189
|
}
|
|
@@ -1755,6 +1576,10 @@ function getAddonDisplay(addon) {
|
|
|
1755
1576
|
label = "evlog";
|
|
1756
1577
|
hint = "Request logging with Better Auth context and AI SDK telemetry";
|
|
1757
1578
|
break;
|
|
1579
|
+
case "axiom":
|
|
1580
|
+
label = "Axiom";
|
|
1581
|
+
hint = "Managed production observability through evlog and Alchemy";
|
|
1582
|
+
break;
|
|
1758
1583
|
default:
|
|
1759
1584
|
label = addon;
|
|
1760
1585
|
hint = `Add ${addon}`;
|
|
@@ -1785,7 +1610,7 @@ const ADDON_GROUPS = {
|
|
|
1785
1610
|
"opentui",
|
|
1786
1611
|
"wxt"
|
|
1787
1612
|
],
|
|
1788
|
-
Observability: ["evlog"],
|
|
1613
|
+
Observability: ["evlog", "axiom"],
|
|
1789
1614
|
"AI & Agent Tools": ["skills", "mcp"]
|
|
1790
1615
|
};
|
|
1791
1616
|
function createGroupedOptions() {
|
|
@@ -1810,7 +1635,8 @@ function sortAndPruneGroupedOptions(groupedOptions) {
|
|
|
1810
1635
|
});
|
|
1811
1636
|
}
|
|
1812
1637
|
function validateAddonSelection(selected) {
|
|
1813
|
-
if ((selected?.filter((addon) => TASK_RUNNER_ADDONS.includes(addon)) ?? []).length > 1) return "Choose Turborepo, Nx, or Vite+ as your task runner, not more than one.";
|
|
1638
|
+
if ((selected?.filter((addon) => TASK_RUNNER_ADDONS$1.includes(addon)) ?? []).length > 1) return "Choose Turborepo, Nx, or Vite+ as your task runner, not more than one.";
|
|
1639
|
+
if ((selected?.filter((addon) => OBSERVABILITY_ADDONS$1.includes(addon)) ?? []).length > 1) return "Choose evlog or Axiom, not both. Axiom already includes evlog.";
|
|
1814
1640
|
}
|
|
1815
1641
|
async function getAddonsChoice(addons, frontends, auth, backend, runtime, previousValue) {
|
|
1816
1642
|
if (addons !== void 0) return addons;
|
|
@@ -1818,7 +1644,7 @@ async function getAddonsChoice(addons, frontends, auth, backend, runtime, previo
|
|
|
1818
1644
|
const groupedOptions = createGroupedOptions();
|
|
1819
1645
|
const frontendsArray = frontends || [];
|
|
1820
1646
|
for (const addon of allAddons) {
|
|
1821
|
-
const { isCompatible } = validateAddonCompatibility(addon, frontendsArray, auth, backend, runtime);
|
|
1647
|
+
const { isCompatible } = validateAddonCompatibility$1(addon, frontendsArray, auth, backend, runtime);
|
|
1822
1648
|
if (!isCompatible) continue;
|
|
1823
1649
|
const { label, hint } = getAddonDisplay(addon);
|
|
1824
1650
|
addOptionToGroup(groupedOptions, {
|
|
@@ -1833,7 +1659,7 @@ async function getAddonsChoice(addons, frontends, auth, backend, runtime, previo
|
|
|
1833
1659
|
options: groupedOptions,
|
|
1834
1660
|
initialValues: (previousValue ?? DEFAULT_CONFIG.addons).filter((addonValue) => Object.values(groupedOptions).some((options) => options.some((opt) => opt.value === addonValue))),
|
|
1835
1661
|
required: false,
|
|
1836
|
-
exclusive: [TASK_RUNNER_ADDONS],
|
|
1662
|
+
exclusive: [TASK_RUNNER_ADDONS$1, OBSERVABILITY_ADDONS$1],
|
|
1837
1663
|
validate: validateAddonSelection
|
|
1838
1664
|
});
|
|
1839
1665
|
if (isCancel$1(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
@@ -1842,7 +1668,7 @@ async function getAddonsChoice(addons, frontends, auth, backend, runtime, previo
|
|
|
1842
1668
|
async function getAddonsToAdd(config) {
|
|
1843
1669
|
const groupedOptions = createGroupedOptions();
|
|
1844
1670
|
const frontendArray = config.frontend || [];
|
|
1845
|
-
const compatibleAddons = getCompatibleAddons(types_exports.AddonsSchema.options.filter((addon) => addon !== "none"), frontendArray, config.addons, config.auth, config.backend, config.runtime);
|
|
1671
|
+
const compatibleAddons = getCompatibleAddons(types_exports.AddonsSchema.options.filter((addon) => addon !== "none" && addon !== "axiom"), frontendArray, config.addons, config.auth, config.backend, config.runtime);
|
|
1846
1672
|
for (const addon of compatibleAddons) {
|
|
1847
1673
|
const { label, hint } = getAddonDisplay(addon);
|
|
1848
1674
|
addOptionToGroup(groupedOptions, {
|
|
@@ -1857,41 +1683,45 @@ async function getAddonsToAdd(config) {
|
|
|
1857
1683
|
message: "Select addons to add",
|
|
1858
1684
|
options: groupedOptions,
|
|
1859
1685
|
required: false,
|
|
1860
|
-
exclusive: [TASK_RUNNER_ADDONS],
|
|
1686
|
+
exclusive: [TASK_RUNNER_ADDONS$1, OBSERVABILITY_ADDONS$1],
|
|
1861
1687
|
validate: validateAddonSelection
|
|
1862
1688
|
});
|
|
1863
|
-
if (
|
|
1689
|
+
if (!Array.isArray(response)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
1864
1690
|
return response;
|
|
1865
1691
|
}
|
|
1866
1692
|
//#endregion
|
|
1867
1693
|
//#region src/utils/bts-config.ts
|
|
1868
1694
|
const BTS_CONFIG_FILE = "bts.jsonc";
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1695
|
+
function parseBtsConfig(content) {
|
|
1696
|
+
const errors = [];
|
|
1697
|
+
const value = parse(content, errors, { allowTrailingComma: true });
|
|
1698
|
+
if (errors.length > 0) throw new Error("Invalid JSONC in bts.jsonc");
|
|
1699
|
+
return BetterTStackConfigSchema.parse(value);
|
|
1700
|
+
}
|
|
1872
1701
|
async function readBtsConfig(projectDir) {
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
return null;
|
|
1879
|
-
}
|
|
1702
|
+
const result = await Result.tryPromise({
|
|
1703
|
+
try: async () => parseBtsConfig(await fs$1.readFile(path.join(projectDir, BTS_CONFIG_FILE), "utf8")),
|
|
1704
|
+
catch: () => null
|
|
1705
|
+
});
|
|
1706
|
+
return result.isOk() ? result.value : null;
|
|
1880
1707
|
}
|
|
1881
|
-
/**
|
|
1882
|
-
* Updates specific fields in the BTS configuration file.
|
|
1883
|
-
*/
|
|
1884
1708
|
async function updateBtsConfig(projectDir, updates) {
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1709
|
+
return Result.tryPromise({
|
|
1710
|
+
try: async () => {
|
|
1711
|
+
const configPath = path.join(projectDir, BTS_CONFIG_FILE);
|
|
1712
|
+
let content = await fs$1.readFile(configPath, "utf8");
|
|
1713
|
+
BetterTStackConfigSchema.parse({
|
|
1714
|
+
...parseBtsConfig(content),
|
|
1715
|
+
...updates
|
|
1716
|
+
});
|
|
1717
|
+
for (const [key, value] of Object.entries(updates)) content = applyEdits(content, modify(content, [key], value, { formattingOptions: { tabSize: 2 } }));
|
|
1718
|
+
await fs$1.writeFile(configPath, content, "utf8");
|
|
1719
|
+
},
|
|
1720
|
+
catch: (cause) => new CLIError({
|
|
1721
|
+
message: `Failed to update bts.jsonc: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
1722
|
+
cause
|
|
1723
|
+
})
|
|
1724
|
+
});
|
|
1895
1725
|
}
|
|
1896
1726
|
//#endregion
|
|
1897
1727
|
//#region src/utils/telemetry.ts
|
|
@@ -2019,12 +1849,12 @@ function shouldSkipExternalCommands() {
|
|
|
2019
1849
|
//#endregion
|
|
2020
1850
|
//#region src/utils/requirements.ts
|
|
2021
1851
|
const PACKAGE_MANAGER_VERSION_RANGES = {
|
|
2022
|
-
bun: ">=1.
|
|
1852
|
+
bun: ">=1.3.3",
|
|
2023
1853
|
npm: ">=11.16.0",
|
|
2024
1854
|
pnpm: ">=10.26.0"
|
|
2025
1855
|
};
|
|
2026
1856
|
const PACKAGE_MANAGER_REASONS = {
|
|
2027
|
-
bun: "
|
|
1857
|
+
bun: "Varlock requires Bun 1.3.3 or newer",
|
|
2028
1858
|
npm: "generated npm workspaces use the allowScripts install-script policy",
|
|
2029
1859
|
pnpm: "generated pnpm workspaces use catalogs and the allowBuilds policy"
|
|
2030
1860
|
};
|
|
@@ -2047,6 +1877,7 @@ function addNodeRequirement(requirements, range, reason) {
|
|
|
2047
1877
|
}
|
|
2048
1878
|
function getNodeToolingRequirements(config) {
|
|
2049
1879
|
const requirements = [];
|
|
1880
|
+
addNodeRequirement(requirements, ">=22.3.0", "Varlock configuration loading");
|
|
2050
1881
|
for (const frontend of config.frontend) switch (frontend) {
|
|
2051
1882
|
case "astro":
|
|
2052
1883
|
addNodeRequirement(requirements, ">=22.12.0", "Astro 7");
|
|
@@ -2070,9 +1901,7 @@ function getNodeToolingRequirements(config) {
|
|
|
2070
1901
|
break;
|
|
2071
1902
|
case "native-bare":
|
|
2072
1903
|
case "native-uniwind":
|
|
2073
|
-
case "native-unistyles":
|
|
2074
|
-
addNodeRequirement(requirements, "^22.13.0 || ^24.3.0 || >=26.0.0", "React Native 0.86");
|
|
2075
|
-
break;
|
|
1904
|
+
case "native-unistyles": addNodeRequirement(requirements, "^22.13.0 || ^24.3.0 || >=26.0.0", "React Native 0.86");
|
|
2076
1905
|
}
|
|
2077
1906
|
if (![
|
|
2078
1907
|
"none",
|
|
@@ -2101,7 +1930,7 @@ function getBaselineRequirements(packageManager, hostRuntime) {
|
|
|
2101
1930
|
range: PACKAGE_MANAGER_VERSION_RANGES[packageManager],
|
|
2102
1931
|
reason: PACKAGE_MANAGER_REASONS[packageManager]
|
|
2103
1932
|
});
|
|
2104
|
-
if (hostRuntime === "node") addNodeRequirement(requirements, ">=22.
|
|
1933
|
+
if (hostRuntime === "node") addNodeRequirement(requirements, ">=22.12.0", "create-better-t-stack");
|
|
2105
1934
|
return requirements;
|
|
2106
1935
|
}
|
|
2107
1936
|
function getLocalVersionRequirements(config, hostRuntime) {
|
|
@@ -2151,7 +1980,7 @@ async function readToolVersion(tool) {
|
|
|
2151
1980
|
const result = await Result.tryPromise({
|
|
2152
1981
|
try: async () => {
|
|
2153
1982
|
const { stdout } = await execa(tool, ["--version"], {
|
|
2154
|
-
cwd: os.tmpdir(),
|
|
1983
|
+
cwd: os$1.tmpdir(),
|
|
2155
1984
|
stderr: "pipe"
|
|
2156
1985
|
});
|
|
2157
1986
|
return normalizeVersion(stdout);
|
|
@@ -2260,11 +2089,14 @@ function isEvlogBackend(backend) {
|
|
|
2260
2089
|
function getEvlogWebFrontend(frontends) {
|
|
2261
2090
|
return frontends.find((frontend) => evlogWebFrontends.includes(frontend));
|
|
2262
2091
|
}
|
|
2092
|
+
function usesAxiom(config) {
|
|
2093
|
+
return config.addons.includes("axiom");
|
|
2094
|
+
}
|
|
2263
2095
|
function shouldWireEvlogServerFsDrain(config) {
|
|
2264
|
-
return isEvlogBackend(config.backend) && config.runtime !== "workers" && config.serverDeploy !== "cloudflare";
|
|
2096
|
+
return !usesAxiom(config) && isEvlogBackend(config.backend) && config.runtime !== "workers" && config.serverDeploy !== "cloudflare";
|
|
2265
2097
|
}
|
|
2266
2098
|
function shouldWireEvlogWebFsDrain(config) {
|
|
2267
|
-
return getEvlogWebFrontend(config.frontend) !== void 0 && config.webDeploy !== "cloudflare";
|
|
2099
|
+
return !usesAxiom(config) && getEvlogWebFrontend(config.frontend) !== void 0 && config.webDeploy !== "cloudflare";
|
|
2268
2100
|
}
|
|
2269
2101
|
function supportsEvlogLocalLogs(config) {
|
|
2270
2102
|
return shouldWireEvlogServerFsDrain(config) || shouldWireEvlogWebFsDrain(config);
|
|
@@ -2272,13 +2104,15 @@ function supportsEvlogLocalLogs(config) {
|
|
|
2272
2104
|
function shouldIdentifyWebAuth(config) {
|
|
2273
2105
|
return config.auth === "better-auth" && config.backend === "self";
|
|
2274
2106
|
}
|
|
2275
|
-
function getEvlogServerMiddlewareMarker(backend, fsDrain) {
|
|
2276
|
-
const options = fsDrain ? `{ drain: ${NODE_DEV_FS_DRAIN_EXPRESSION} }` : "";
|
|
2107
|
+
function getEvlogServerMiddlewareMarker(backend, fsDrain, axiom = false) {
|
|
2108
|
+
const options = axiom ? "{ drain: createAxiomDrain() }" : fsDrain ? `{ drain: ${NODE_DEV_FS_DRAIN_EXPRESSION} }` : "";
|
|
2277
2109
|
if (backend === "hono" || backend === "express") return `app.use(evlog(${options}));`;
|
|
2278
2110
|
if (backend === "fastify") return `fastify.register(evlog${options ? `, ${options}` : ""});`;
|
|
2279
2111
|
return `.use(evlog(${options}))`;
|
|
2280
2112
|
}
|
|
2281
2113
|
function findEvlogServerMiddlewareMarker(content, backend) {
|
|
2114
|
+
const axiomMarker = getEvlogServerMiddlewareMarker(backend, false, true);
|
|
2115
|
+
if (content.includes(axiomMarker)) return axiomMarker;
|
|
2282
2116
|
const fsDrainMarker = getEvlogServerMiddlewareMarker(backend, true);
|
|
2283
2117
|
return content.includes(fsDrainMarker) ? fsDrainMarker : getEvlogServerMiddlewareMarker(backend, false);
|
|
2284
2118
|
}
|
|
@@ -2322,11 +2156,11 @@ async function updateFileIfExists(filePath, update) {
|
|
|
2322
2156
|
function usesCreateAuthFactory(config) {
|
|
2323
2157
|
return config.runtime === "workers" || config.serverDeploy === "cloudflare" || config.backend === "self" && config.webDeploy === "cloudflare";
|
|
2324
2158
|
}
|
|
2325
|
-
function getAuthImportLine(config) {
|
|
2326
|
-
return usesCreateAuthFactory(config) ? `import { createAuth } from "
|
|
2159
|
+
function getAuthImportLine(config, source) {
|
|
2160
|
+
return usesCreateAuthFactory(config) ? `import { createAuth } from "${source}";` : `import { auth } from "${source}";`;
|
|
2327
2161
|
}
|
|
2328
2162
|
function getAuthExpression(config) {
|
|
2329
|
-
return usesCreateAuthFactory(config) ? "createAuth()" : "auth";
|
|
2163
|
+
return usesCreateAuthFactory(config) ? "(await createAuth())" : "auth";
|
|
2330
2164
|
}
|
|
2331
2165
|
function addAiSdkEvlogTelemetry(content, loggerExpression) {
|
|
2332
2166
|
let nextContent = addNamedImport(content, "evlog/ai", ["createAILogger", "createEvlogIntegration"]);
|
|
@@ -2364,15 +2198,16 @@ function addEvlogBetterAuthServerSetup(content, backend, authExpression) {
|
|
|
2364
2198
|
const evlogMarker = findEvlogServerMiddlewareMarker(nextContent, backend);
|
|
2365
2199
|
return insertAfterOnce(nextContent, evlogMarker, `\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");
|
|
2366
2200
|
}
|
|
2367
|
-
function addEvlogServerSetup(content, backend, serviceName, fsDrain) {
|
|
2201
|
+
function addEvlogServerSetup(content, backend, serviceName, fsDrain, axiom = false) {
|
|
2368
2202
|
const initSnippet = `initLogger({\n\tenv: { service: "${serviceName}" },\n});\n\n`;
|
|
2369
|
-
const evlogMarker = getEvlogServerMiddlewareMarker(backend, fsDrain);
|
|
2203
|
+
const evlogMarker = getEvlogServerMiddlewareMarker(backend, fsDrain, axiom);
|
|
2370
2204
|
const legacyEvlogMarker = getEvlogServerMiddlewareMarker(backend, false);
|
|
2371
2205
|
if (backend === "hono") {
|
|
2372
2206
|
let nextContent = prependMissingImports(content, [
|
|
2373
2207
|
"import { initLogger } from \"evlog\";",
|
|
2374
2208
|
"import { evlog, type EvlogVariables } from \"evlog/hono\";",
|
|
2375
|
-
...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : []
|
|
2209
|
+
...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : [],
|
|
2210
|
+
...axiom ? ["import { createAxiomDrain } from \"evlog/axiom\";"] : []
|
|
2376
2211
|
]);
|
|
2377
2212
|
nextContent = insertBeforeOnce(nextContent, "const app = new Hono", initSnippet, "initLogger({");
|
|
2378
2213
|
nextContent = nextContent.replace("const app = new Hono();", "const app = new Hono<EvlogVariables>();");
|
|
@@ -2384,7 +2219,8 @@ function addEvlogServerSetup(content, backend, serviceName, fsDrain) {
|
|
|
2384
2219
|
let nextContent = prependMissingImports(content, [
|
|
2385
2220
|
"import { initLogger } from \"evlog\";",
|
|
2386
2221
|
"import { evlog } from \"evlog/express\";",
|
|
2387
|
-
...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : []
|
|
2222
|
+
...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : [],
|
|
2223
|
+
...axiom ? ["import { createAxiomDrain } from \"evlog/axiom\";"] : []
|
|
2388
2224
|
]);
|
|
2389
2225
|
nextContent = insertBeforeOnce(nextContent, "const app = express();", initSnippet, "initLogger({");
|
|
2390
2226
|
if (fsDrain) nextContent = nextContent.replace(legacyEvlogMarker, evlogMarker);
|
|
@@ -2394,7 +2230,8 @@ function addEvlogServerSetup(content, backend, serviceName, fsDrain) {
|
|
|
2394
2230
|
let nextContent = prependMissingImports(content, [
|
|
2395
2231
|
"import { initLogger } from \"evlog\";",
|
|
2396
2232
|
"import { evlog } from \"evlog/fastify\";",
|
|
2397
|
-
...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : []
|
|
2233
|
+
...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : [],
|
|
2234
|
+
...axiom ? ["import { createAxiomDrain } from \"evlog/axiom\";"] : []
|
|
2398
2235
|
]);
|
|
2399
2236
|
nextContent = insertBeforeOnce(nextContent, "const fastify = Fastify", initSnippet, "initLogger({");
|
|
2400
2237
|
if (fsDrain) nextContent = nextContent.replace(legacyEvlogMarker, evlogMarker);
|
|
@@ -2403,7 +2240,8 @@ function addEvlogServerSetup(content, backend, serviceName, fsDrain) {
|
|
|
2403
2240
|
let nextContent = prependMissingImports(content, [
|
|
2404
2241
|
"import { initLogger } from \"evlog\";",
|
|
2405
2242
|
"import { evlog } from \"evlog/elysia\";",
|
|
2406
|
-
...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : []
|
|
2243
|
+
...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : [],
|
|
2244
|
+
...axiom ? ["import { createAxiomDrain } from \"evlog/axiom\";"] : []
|
|
2407
2245
|
]);
|
|
2408
2246
|
const elysiaMarker = nextContent.includes("const app = new Elysia") ? "const app = new Elysia" : "new Elysia";
|
|
2409
2247
|
nextContent = insertBeforeOnce(nextContent, elysiaMarker, initSnippet, "initLogger({");
|
|
@@ -2425,14 +2263,19 @@ function addSvelteViteEvlogSetup(content, serviceName) {
|
|
|
2425
2263
|
if (nextContent.includes("evlog({")) return nextContent;
|
|
2426
2264
|
return nextContent.replace("sveltekit(),", `sveltekit(),\n evlog({ service: "${serviceName}" }),`);
|
|
2427
2265
|
}
|
|
2428
|
-
function getSvelteEvlogHooksCall(fsDrain) {
|
|
2266
|
+
function getSvelteEvlogHooksCall(fsDrain, axiom = false) {
|
|
2267
|
+
if (axiom) return "createEvlogHooks({ drain: createAxiomDrain() })";
|
|
2429
2268
|
return fsDrain ? `createEvlogHooks({ drain: ${SVELTE_DEV_FS_DRAIN_EXPRESSION} })` : "createEvlogHooks()";
|
|
2430
2269
|
}
|
|
2431
|
-
function addSvelteHooksEvlogSetup(content, fsDrain) {
|
|
2432
|
-
let nextContent = prependMissingImports(content, [
|
|
2270
|
+
function addSvelteHooksEvlogSetup(content, fsDrain, axiom) {
|
|
2271
|
+
let nextContent = prependMissingImports(content, [
|
|
2272
|
+
"import { createEvlogHooks } from \"evlog/sveltekit\";",
|
|
2273
|
+
...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : [],
|
|
2274
|
+
...axiom ? ["import { createAxiomDrain } from \"evlog/axiom\";"] : []
|
|
2275
|
+
]);
|
|
2433
2276
|
if (fsDrain) nextContent = addNamedImport(nextContent, "$app/environment", ["dev"]);
|
|
2434
|
-
const hooksCall = getSvelteEvlogHooksCall(fsDrain);
|
|
2435
|
-
if (fsDrain) nextContent = nextContent.replaceAll("createEvlogHooks()", hooksCall);
|
|
2277
|
+
const hooksCall = getSvelteEvlogHooksCall(fsDrain, axiom);
|
|
2278
|
+
if (fsDrain || axiom) nextContent = nextContent.replaceAll("createEvlogHooks()", hooksCall);
|
|
2436
2279
|
if (!nextContent.includes("export const handle") && !nextContent.includes("const authHandle")) {
|
|
2437
2280
|
if (!nextContent.includes("createEvlogHooks(")) nextContent = `${nextContent.trimEnd()}\n\nexport const { handle, handleError } = ${hooksCall};\n`;
|
|
2438
2281
|
return nextContent;
|
|
@@ -2457,33 +2300,36 @@ function addTanstackStartRootEvlogSetup(content) {
|
|
|
2457
2300
|
if (/server:\s*{/.test(nextContent)) return nextContent.replace(/server:\s*{\n/, `server: {\n middleware: [${middlewareEntry}],\n`);
|
|
2458
2301
|
return nextContent.replace("head: () => ({", `server: {\n middleware: [${middlewareEntry}],\n },\n\n head: () => ({`);
|
|
2459
2302
|
}
|
|
2460
|
-
function getInitLoggerSnippet(serviceName, fsDrain, indent) {
|
|
2461
|
-
return `initLogger({\n${indent}env: { service: "${serviceName}" },${fsDrain ? `\n${indent}drain: ${ASTRO_DEV_FS_DRAIN_EXPRESSION},` : ""}\n});\n\n`;
|
|
2303
|
+
function getInitLoggerSnippet(serviceName, fsDrain, indent, axiom = false) {
|
|
2304
|
+
return `initLogger({\n${indent}env: { service: "${serviceName}" },${axiom ? `\n${indent}drain: createAxiomDrain(),` : fsDrain ? `\n${indent}drain: ${ASTRO_DEV_FS_DRAIN_EXPRESSION},` : ""}\n});\n\n`;
|
|
2462
2305
|
}
|
|
2463
|
-
function addAstroMiddlewareEvlogSetup(content, serviceName, fsDrain) {
|
|
2464
|
-
let nextContent = prependMissingImports(content, [
|
|
2465
|
-
|
|
2306
|
+
function addAstroMiddlewareEvlogSetup(content, serviceName, fsDrain, axiom, cloudflare) {
|
|
2307
|
+
let nextContent = prependMissingImports(content, [
|
|
2308
|
+
"import { createRequestLogger, initLogger } from \"evlog\";",
|
|
2309
|
+
...fsDrain ? ["import { createFsDrain } from \"evlog/fs\";"] : [],
|
|
2310
|
+
...axiom ? ["import { createAxiomDrain } from \"evlog/axiom\";"] : []
|
|
2311
|
+
]);
|
|
2312
|
+
const initSnippet = getInitLoggerSnippet(serviceName, fsDrain, " ", axiom);
|
|
2466
2313
|
nextContent = insertBeforeOnce(nextContent, "export const onRequest", initSnippet, "initLogger({");
|
|
2467
2314
|
if (fsDrain && !nextContent.includes("drain:")) nextContent = nextContent.replace(/initLogger\(\{\n(\s+env: \{ service: "[^"]+" \},)/, `initLogger({\n$1\n drain: ${ASTRO_DEV_FS_DRAIN_EXPRESSION},`);
|
|
2468
2315
|
if (nextContent.includes("createRequestLogger({")) return nextContent;
|
|
2469
2316
|
const contextMarker = "export const onRequest = defineMiddleware(async (context, next) => {";
|
|
2470
2317
|
if (nextContent.includes(contextMarker)) {
|
|
2471
|
-
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({");
|
|
2472
|
-
return nextContent.replace("return next();", "const response = await next();\n log.emit();\n return response;");
|
|
2318
|
+
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${cloudflare ? " waitUntil: context.locals.cfContext.waitUntil.bind(context.locals.cfContext),\n" : ""} });\n\n context.locals.log = log;\n`, "const log = createRequestLogger({");
|
|
2319
|
+
return nextContent.replace("return next();", "const response = await next();\n log.set({ status: response.status });\n log.emit();\n return response;");
|
|
2473
2320
|
}
|
|
2474
2321
|
const localsMarker = "export const onRequest = defineMiddleware(async ({ request, locals }, next) => {";
|
|
2475
2322
|
if (nextContent.includes(localsMarker)) {
|
|
2476
|
-
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({");
|
|
2477
|
-
return nextContent.replace("return next();", "const response = await next();\n log.emit();\n return response;");
|
|
2323
|
+
nextContent = insertAfterOnce(nextContent, localsMarker, `\n const url = new URL(request.url);\n const log = createRequestLogger({\n method: request.method,\n path: url.pathname,\n${cloudflare ? " waitUntil: locals.cfContext.waitUntil.bind(locals.cfContext),\n" : ""} });\n\n locals.log = log;\n`, "const log = createRequestLogger({");
|
|
2324
|
+
return nextContent.replace("return next();", "const response = await next();\n log.set({ status: response.status });\n log.emit();\n return response;");
|
|
2478
2325
|
}
|
|
2479
2326
|
return nextContent;
|
|
2480
2327
|
}
|
|
2481
2328
|
function addAstroLocalsType(content) {
|
|
2482
2329
|
let nextContent = prependMissingImports(content, ["import type { RequestLogger } from \"evlog\";"]);
|
|
2483
2330
|
if (nextContent.includes("log: RequestLogger")) return nextContent;
|
|
2484
|
-
if (nextContent.includes("interface Locals {")) return nextContent.replace("interface Locals {", "interface Locals {\n
|
|
2485
|
-
|
|
2486
|
-
return `${nextContent.trimEnd()}\n\ndeclare namespace App {\n interface Locals {\n log: RequestLogger;\n }\n}\n`;
|
|
2331
|
+
if (nextContent.includes("declare global {") && nextContent.includes("interface Locals {")) return nextContent.replace("interface Locals {", "interface Locals {\n log: RequestLogger;");
|
|
2332
|
+
return `${nextContent.trimEnd()}\n\ndeclare global {\n namespace App {\n interface Locals {\n log: RequestLogger;\n }\n }\n}\n`;
|
|
2487
2333
|
}
|
|
2488
2334
|
function addNextRouteWrappers(content) {
|
|
2489
2335
|
let nextContent = prependMissingImports(content, ["import { withEvlog } from \"@/lib/evlog\";"]);
|
|
@@ -2534,11 +2380,11 @@ function addNextBetterAuthToRoute(content) {
|
|
|
2534
2380
|
function addSvelteBetterAuthEvlogSetup(content, config) {
|
|
2535
2381
|
if (!content.includes("authHandle") || content.includes("evlogAuthHandle")) return content;
|
|
2536
2382
|
let nextContent = addNamedImport(content, "evlog/better-auth", ["createAuthMiddleware", "type BetterAuthInstance"]);
|
|
2537
|
-
if (!nextContent.includes(
|
|
2538
|
-
if (usesCreateAuthFactory(config) && config.webDeploy === "cloudflare" && !nextContent.includes(
|
|
2383
|
+
if (!nextContent.includes("from \"./services\"")) nextContent = prependMissingImports(nextContent, [getAuthImportLine(config, "./services")]);
|
|
2384
|
+
if (usesCreateAuthFactory(config) && config.webDeploy === "cloudflare" && !nextContent.includes("from \"./env.server\"")) nextContent = prependMissingImports(nextContent, ["import { env as localEnv } from \"./env.server\";"]);
|
|
2539
2385
|
const authExpression = getAuthExpression(config);
|
|
2540
2386
|
const authOptions = "{ exclude: [\"/api/auth/**\"], maskEmail: true }";
|
|
2541
|
-
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`;
|
|
2387
|
+
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((await 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`;
|
|
2542
2388
|
const evlogHandleDeclaration = nextContent.match(/const \{ handle: evlogHandle, handleError \} = createEvlogHooks\([\s\S]*?\);\n\n/)?.[0];
|
|
2543
2389
|
if (evlogHandleDeclaration) nextContent = insertAfterOnce(nextContent, evlogHandleDeclaration, authHandleSnippet, "evlogAuthHandle");
|
|
2544
2390
|
return nextContent.replace("sequence(evlogHandle as Handle, authHandle)", "sequence(evlogHandle as Handle, evlogAuthHandle, authHandle)").replace("sequence(evlogHandle, authHandle)", "sequence(evlogHandle as Handle, evlogAuthHandle, authHandle)");
|
|
@@ -2546,7 +2392,7 @@ function addSvelteBetterAuthEvlogSetup(content, config) {
|
|
|
2546
2392
|
function addAstroBetterAuthEvlogSetup(content, config) {
|
|
2547
2393
|
if (content.includes("createAuthMiddleware(")) return content;
|
|
2548
2394
|
let nextContent = addNamedImport(content, "evlog/better-auth", ["createAuthMiddleware", "type BetterAuthInstance"]);
|
|
2549
|
-
if (!nextContent.includes(
|
|
2395
|
+
if (!nextContent.includes("from \"./services\"")) nextContent = prependMissingImports(nextContent, [getAuthImportLine(config, "./services")]);
|
|
2550
2396
|
const authExpression = getAuthExpression(config);
|
|
2551
2397
|
const authOptions = "{ exclude: [\"/api/auth/**\"], maskEmail: true }";
|
|
2552
2398
|
const usesFactory = usesCreateAuthFactory(config);
|
|
@@ -2559,21 +2405,28 @@ function addAstroBetterAuthEvlogSetup(content, config) {
|
|
|
2559
2405
|
}
|
|
2560
2406
|
return nextContent;
|
|
2561
2407
|
}
|
|
2562
|
-
function getNextEvlogFile(serviceName, fsDrain) {
|
|
2408
|
+
function getNextEvlogFile(serviceName, fsDrain, axiom) {
|
|
2563
2409
|
return `import { createEvlog } from "evlog/next";
|
|
2564
2410
|
import { createInstrumentation } from "evlog/next/instrumentation/create";
|
|
2565
2411
|
${fsDrain ? "import { createFsDrain } from \"evlog/fs\";\n" : ""}
|
|
2412
|
+
${axiom ? "import { createAxiomDrain } from \"evlog/axiom\";\n" : ""}
|
|
2566
2413
|
|
|
2567
2414
|
export const { withEvlog, useLogger, log, createError } = createEvlog({
|
|
2568
2415
|
service: "${serviceName}",
|
|
2569
|
-
${fsDrain ? ` drain: ${NODE_DEV_FS_DRAIN_EXPRESSION},\n` : ""}});
|
|
2416
|
+
${axiom ? " drain: createAxiomDrain(),\n" : fsDrain ? ` drain: ${NODE_DEV_FS_DRAIN_EXPRESSION},\n` : ""}});
|
|
2570
2417
|
|
|
2571
2418
|
export const { register, onRequestError } = createInstrumentation({
|
|
2572
2419
|
service: "${serviceName}",
|
|
2573
2420
|
});
|
|
2574
2421
|
`;
|
|
2575
2422
|
}
|
|
2576
|
-
function getNitroEvlogDrainFile() {
|
|
2423
|
+
function getNitroEvlogDrainFile(axiom = false, nitroV3 = false) {
|
|
2424
|
+
if (axiom) return `${nitroV3 ? "import { definePlugin as defineNitroPlugin } from \"nitro\";\n" : ""}import { createAxiomDrain } from "evlog/axiom";
|
|
2425
|
+
|
|
2426
|
+
export default defineNitroPlugin((nitroApp) => {
|
|
2427
|
+
nitroApp.hooks.hook("evlog:drain", createAxiomDrain());
|
|
2428
|
+
});
|
|
2429
|
+
`;
|
|
2577
2430
|
return `import { createFsDrain } from "evlog/fs";
|
|
2578
2431
|
|
|
2579
2432
|
export default defineNitroPlugin((nitroApp) => {
|
|
@@ -2599,7 +2452,7 @@ export const config = {
|
|
|
2599
2452
|
`;
|
|
2600
2453
|
}
|
|
2601
2454
|
function getNextEvlogAuthFile(config) {
|
|
2602
|
-
if (usesCreateAuthFactory(config)) return `${getAuthImportLine(config)}
|
|
2455
|
+
if (usesCreateAuthFactory(config)) return `${getAuthImportLine(config, "../services")}
|
|
2603
2456
|
import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
|
|
2604
2457
|
import { useLogger } from "@/lib/evlog";
|
|
2605
2458
|
|
|
@@ -2611,7 +2464,7 @@ export async function identifyEvlogUser(request: Request) {
|
|
|
2611
2464
|
await identifyUser(useLogger(), request.headers, new URL(request.url).pathname);
|
|
2612
2465
|
}
|
|
2613
2466
|
`;
|
|
2614
|
-
return `${getAuthImportLine(config)}
|
|
2467
|
+
return `${getAuthImportLine(config, "../services")}
|
|
2615
2468
|
import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
|
|
2616
2469
|
import { useLogger } from "@/lib/evlog";
|
|
2617
2470
|
|
|
@@ -2626,7 +2479,7 @@ export async function identifyEvlogUser(request: Request) {
|
|
|
2626
2479
|
`;
|
|
2627
2480
|
}
|
|
2628
2481
|
function getNitroEvlogAuthPluginFile(config) {
|
|
2629
|
-
if (usesCreateAuthFactory(config)) return `${getAuthImportLine(config)}
|
|
2482
|
+
if (usesCreateAuthFactory(config)) return `${getAuthImportLine(config, "../../src/services")}
|
|
2630
2483
|
import { createAuthIdentifier, type BetterAuthInstance } from "evlog/better-auth";
|
|
2631
2484
|
|
|
2632
2485
|
export default defineNitroPlugin((nitroApp) => {
|
|
@@ -2639,7 +2492,7 @@ export default defineNitroPlugin((nitroApp) => {
|
|
|
2639
2492
|
});
|
|
2640
2493
|
});
|
|
2641
2494
|
`;
|
|
2642
|
-
return `${getAuthImportLine(config)}
|
|
2495
|
+
return `${getAuthImportLine(config, "../../src/services")}
|
|
2643
2496
|
import { createAuthIdentifier, type BetterAuthInstance } from "evlog/better-auth";
|
|
2644
2497
|
|
|
2645
2498
|
export default defineNitroPlugin((nitroApp) => {
|
|
@@ -2656,9 +2509,9 @@ export default defineNitroPlugin((nitroApp) => {
|
|
|
2656
2509
|
function getNuxtEvlogAuthMiddlewareFile(config) {
|
|
2657
2510
|
if (usesCreateAuthFactory(config)) {
|
|
2658
2511
|
const usesCloudflareRequestEnv = config.backend === "self" && config.webDeploy === "cloudflare";
|
|
2659
|
-
const authExpression = usesCloudflareRequestEnv ? "createAuth((event.context.cloudflare as { env: CloudflareEnv }).env)" : getAuthExpression(config);
|
|
2660
|
-
return `${getAuthImportLine(config)}
|
|
2661
|
-
${usesCloudflareRequestEnv ? `import type { CloudflareEnv } from "
|
|
2512
|
+
const authExpression = usesCloudflareRequestEnv ? "(await createAuth((event.context.cloudflare as { env: CloudflareEnv }).env))" : getAuthExpression(config);
|
|
2513
|
+
return `${getAuthImportLine(config, "../../src/services")}
|
|
2514
|
+
${usesCloudflareRequestEnv ? `import type { CloudflareEnv } from "../../src/env.server";\n` : ""}
|
|
2662
2515
|
import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
|
|
2663
2516
|
|
|
2664
2517
|
export default defineEventHandler(async (event) => {
|
|
@@ -2671,7 +2524,7 @@ export default defineEventHandler(async (event) => {
|
|
|
2671
2524
|
});
|
|
2672
2525
|
`;
|
|
2673
2526
|
}
|
|
2674
|
-
return `${getAuthImportLine(config)}
|
|
2527
|
+
return `${getAuthImportLine(config, "../../src/services")}
|
|
2675
2528
|
import { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";
|
|
2676
2529
|
|
|
2677
2530
|
const identify = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {
|
|
@@ -2690,6 +2543,7 @@ function getTanstackNitroConfigFile(serviceName) {
|
|
|
2690
2543
|
import evlog from "evlog/nitro/v3";
|
|
2691
2544
|
|
|
2692
2545
|
export default defineConfig({
|
|
2546
|
+
serverDir: "./server",
|
|
2693
2547
|
experimental: {
|
|
2694
2548
|
asyncContext: true,
|
|
2695
2549
|
},
|
|
@@ -2701,24 +2555,26 @@ export default defineConfig({
|
|
|
2701
2555
|
});
|
|
2702
2556
|
`;
|
|
2703
2557
|
}
|
|
2704
|
-
function getAstroMiddlewareFile(serviceName, fsDrain) {
|
|
2558
|
+
function getAstroMiddlewareFile(serviceName, fsDrain, axiom, cloudflare) {
|
|
2705
2559
|
return `import { defineMiddleware } from "astro:middleware";
|
|
2706
2560
|
import { createRequestLogger, initLogger } from "evlog";
|
|
2707
2561
|
${fsDrain ? "import { createFsDrain } from \"evlog/fs\";\n" : ""}
|
|
2562
|
+
${axiom ? "import { createAxiomDrain } from \"evlog/axiom\";\n" : ""}
|
|
2708
2563
|
|
|
2709
|
-
${getInitLoggerSnippet(serviceName, fsDrain, " ").trimEnd()}
|
|
2564
|
+
${getInitLoggerSnippet(serviceName, fsDrain, " ", axiom).trimEnd()}
|
|
2710
2565
|
|
|
2711
2566
|
export const onRequest = defineMiddleware(async ({ request, locals }, next) => {
|
|
2712
2567
|
const url = new URL(request.url);
|
|
2713
2568
|
const log = createRequestLogger({
|
|
2714
2569
|
method: request.method,
|
|
2715
2570
|
path: url.pathname,
|
|
2716
|
-
});
|
|
2571
|
+
${cloudflare ? " waitUntil: locals.cfContext.waitUntil.bind(locals.cfContext),\n" : ""} });
|
|
2717
2572
|
|
|
2718
2573
|
locals.log = log;
|
|
2719
2574
|
|
|
2720
2575
|
try {
|
|
2721
2576
|
const response = await next();
|
|
2577
|
+
log.set({ status: response.status });
|
|
2722
2578
|
log.emit();
|
|
2723
2579
|
return response;
|
|
2724
2580
|
} catch (error) {
|
|
@@ -2734,9 +2590,11 @@ function getAstroEnvFile() {
|
|
|
2734
2590
|
|
|
2735
2591
|
import type { RequestLogger } from "evlog";
|
|
2736
2592
|
|
|
2737
|
-
declare
|
|
2738
|
-
|
|
2739
|
-
|
|
2593
|
+
declare global {
|
|
2594
|
+
namespace App {
|
|
2595
|
+
interface Locals {
|
|
2596
|
+
log: RequestLogger;
|
|
2597
|
+
}
|
|
2740
2598
|
}
|
|
2741
2599
|
}
|
|
2742
2600
|
`;
|
|
@@ -2744,8 +2602,9 @@ declare namespace App {
|
|
|
2744
2602
|
async function setupNextEvlog(config, serviceName) {
|
|
2745
2603
|
const webDir = path.join(config.projectDir, "apps/web");
|
|
2746
2604
|
const fsDrain = shouldWireEvlogWebFsDrain(config);
|
|
2605
|
+
const axiom = usesAxiom(config);
|
|
2747
2606
|
const evlogPath = path.join(webDir, "src/lib/evlog.ts");
|
|
2748
|
-
if (!await fs.pathExists(evlogPath)) await writeFileIfChanged(evlogPath, getNextEvlogFile(serviceName, fsDrain));
|
|
2607
|
+
if (!await fs.pathExists(evlogPath)) await writeFileIfChanged(evlogPath, getNextEvlogFile(serviceName, fsDrain, axiom));
|
|
2749
2608
|
const identifyWebAuth = shouldIdentifyWebAuth(config);
|
|
2750
2609
|
if (identifyWebAuth) {
|
|
2751
2610
|
const evlogAuthPath = path.join(webDir, "src/lib/evlog-auth.ts");
|
|
@@ -2773,9 +2632,9 @@ async function setupNuxtEvlog(config, serviceName) {
|
|
|
2773
2632
|
const webDir = path.join(config.projectDir, "apps/web");
|
|
2774
2633
|
const fsDrain = shouldWireEvlogWebFsDrain(config);
|
|
2775
2634
|
await updateFileIfExists(path.join(webDir, "nuxt.config.ts"), (content) => addNuxtEvlogSetup(content, serviceName));
|
|
2776
|
-
if (fsDrain) {
|
|
2635
|
+
if (fsDrain || usesAxiom(config)) {
|
|
2777
2636
|
const drainPath = path.join(webDir, "server/plugins/evlog-drain.ts");
|
|
2778
|
-
if (!await fs.pathExists(drainPath)) await writeFileIfChanged(drainPath, getNitroEvlogDrainFile());
|
|
2637
|
+
if (!await fs.pathExists(drainPath)) await writeFileIfChanged(drainPath, getNitroEvlogDrainFile(usesAxiom(config), config.frontend.includes("tanstack-start")));
|
|
2779
2638
|
}
|
|
2780
2639
|
if (shouldIdentifyWebAuth(config)) {
|
|
2781
2640
|
const oldAuthPluginPath = path.join(webDir, "server/plugins/evlog-auth.ts");
|
|
@@ -2790,28 +2649,53 @@ async function setupNuxtEvlog(config, serviceName) {
|
|
|
2790
2649
|
async function setupSvelteEvlog(config, serviceName) {
|
|
2791
2650
|
const webDir = path.join(config.projectDir, "apps/web");
|
|
2792
2651
|
const fsDrain = shouldWireEvlogWebFsDrain(config);
|
|
2652
|
+
const axiom = usesAxiom(config);
|
|
2793
2653
|
await updateFileIfExists(path.join(webDir, "vite.config.ts"), (content) => addSvelteViteEvlogSetup(content, serviceName));
|
|
2794
2654
|
const hooksPath = path.join(webDir, "src/hooks.server.ts");
|
|
2795
|
-
if (await fs.pathExists(hooksPath)) await updateFileIfExists(hooksPath, (content) => addSvelteHooksEvlogSetup(content, fsDrain));
|
|
2655
|
+
if (await fs.pathExists(hooksPath)) await updateFileIfExists(hooksPath, (content) => addSvelteHooksEvlogSetup(content, fsDrain, axiom));
|
|
2796
2656
|
else await writeFileIfChanged(hooksPath, `import { createEvlogHooks } from "evlog/sveltekit";
|
|
2797
2657
|
${fsDrain ? "import { createFsDrain } from \"evlog/fs\";\n" : ""}
|
|
2658
|
+
${axiom ? "import { createAxiomDrain } from \"evlog/axiom\";\n" : ""}
|
|
2798
2659
|
${fsDrain ? "import { dev } from \"$app/environment\";\n" : ""}
|
|
2799
2660
|
|
|
2800
|
-
export const { handle, handleError } = ${getSvelteEvlogHooksCall(fsDrain)};
|
|
2661
|
+
export const { handle, handleError } = ${getSvelteEvlogHooksCall(fsDrain, axiom)};
|
|
2801
2662
|
`);
|
|
2802
2663
|
await updateFileIfExists(path.join(webDir, "src/app.d.ts"), addSvelteLocalsType);
|
|
2803
2664
|
if (shouldIdentifyWebAuth(config)) await updateFileIfExists(path.join(webDir, "src/hooks.server.ts"), (content) => addSvelteBetterAuthEvlogSetup(content, config));
|
|
2804
2665
|
if (config.examples.includes("ai")) await updateFileIfExists(path.join(webDir, "src/routes/api/ai/+server.ts"), addSvelteAiEvlogSetup);
|
|
2805
2666
|
}
|
|
2667
|
+
function getTanstackWorkersEvlogFile(config, serviceName) {
|
|
2668
|
+
const identify = shouldIdentifyWebAuth(config);
|
|
2669
|
+
return `import handler from "@tanstack/react-start/server-entry";
|
|
2670
|
+
import { initWorkersLogger, withEvlog } from "evlog/workers";
|
|
2671
|
+
import { createAxiomDrain } from "evlog/axiom";
|
|
2672
|
+
${identify ? `${getAuthImportLine(config, "./services")}\nimport { createAuthMiddleware, type BetterAuthInstance } from "evlog/better-auth";\n` : ""}
|
|
2673
|
+
initWorkersLogger({ env: { service: "${serviceName}" } });
|
|
2674
|
+
|
|
2675
|
+
export default withEvlog(async (request${identify ? ", _env, _ctx, log" : ""}) => {
|
|
2676
|
+
${identify ? ` const identifyUser = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {
|
|
2677
|
+
exclude: ["/api/auth/**"],
|
|
2678
|
+
maskEmail: true,
|
|
2679
|
+
});
|
|
2680
|
+
await identifyUser(log, request.headers, new URL(request.url).pathname);
|
|
2681
|
+
` : ""} return handler.fetch(request);
|
|
2682
|
+
}, { drain: createAxiomDrain() });
|
|
2683
|
+
`;
|
|
2684
|
+
}
|
|
2806
2685
|
async function setupTanstackStartEvlog(config, serviceName) {
|
|
2807
2686
|
const webDir = path.join(config.projectDir, "apps/web");
|
|
2808
2687
|
const fsDrain = shouldWireEvlogWebFsDrain(config);
|
|
2688
|
+
if (usesAxiom(config) && config.webDeploy === "cloudflare") {
|
|
2689
|
+
await writeFileIfChanged(path.join(webDir, "src/server.ts"), getTanstackWorkersEvlogFile(config, serviceName));
|
|
2690
|
+
if (config.examples.includes("ai")) await updateFileIfExists(path.join(webDir, "src/routes/api/ai/$.ts"), (content) => addAiSdkEvlogTelemetry(addNamedImport(content, "evlog", ["useLogger"]), "useLogger()"));
|
|
2691
|
+
return;
|
|
2692
|
+
}
|
|
2809
2693
|
const nitroConfigPath = path.join(webDir, "nitro.config.ts");
|
|
2810
2694
|
if (!await fs.pathExists(nitroConfigPath)) await writeFileIfChanged(nitroConfigPath, getTanstackNitroConfigFile(serviceName));
|
|
2811
2695
|
await updateFileIfExists(path.join(webDir, "src/routes/__root.tsx"), addTanstackStartRootEvlogSetup);
|
|
2812
|
-
if (fsDrain) {
|
|
2696
|
+
if (fsDrain || usesAxiom(config)) {
|
|
2813
2697
|
const drainPath = path.join(webDir, "server/plugins/evlog-drain.ts");
|
|
2814
|
-
if (!await fs.pathExists(drainPath)) await writeFileIfChanged(drainPath, getNitroEvlogDrainFile());
|
|
2698
|
+
if (!await fs.pathExists(drainPath)) await writeFileIfChanged(drainPath, getNitroEvlogDrainFile(usesAxiom(config), config.frontend.includes("tanstack-start")));
|
|
2815
2699
|
}
|
|
2816
2700
|
if (shouldIdentifyWebAuth(config)) {
|
|
2817
2701
|
const authPluginPath = path.join(webDir, "server/plugins/evlog-auth.ts");
|
|
@@ -2822,12 +2706,17 @@ async function setupTanstackStartEvlog(config, serviceName) {
|
|
|
2822
2706
|
async function setupAstroEvlog(config, serviceName) {
|
|
2823
2707
|
const webDir = path.join(config.projectDir, "apps/web");
|
|
2824
2708
|
const fsDrain = shouldWireEvlogWebFsDrain(config);
|
|
2709
|
+
const axiom = usesAxiom(config);
|
|
2825
2710
|
const middlewarePath = path.join(webDir, "src/middleware.ts");
|
|
2826
|
-
if (!await fs.pathExists(middlewarePath)) await writeFileIfChanged(middlewarePath, getAstroMiddlewareFile(serviceName, fsDrain));
|
|
2827
|
-
else await updateFileIfExists(middlewarePath, (content) => addAstroMiddlewareEvlogSetup(content, serviceName, fsDrain));
|
|
2828
|
-
const envPath = path.join(webDir, "src/
|
|
2711
|
+
if (!await fs.pathExists(middlewarePath)) await writeFileIfChanged(middlewarePath, getAstroMiddlewareFile(serviceName, fsDrain, axiom, config.webDeploy === "cloudflare"));
|
|
2712
|
+
else await updateFileIfExists(middlewarePath, (content) => addAstroMiddlewareEvlogSetup(content, serviceName, fsDrain, axiom, config.webDeploy === "cloudflare"));
|
|
2713
|
+
const envPath = path.join(webDir, "src/locals.d.ts");
|
|
2829
2714
|
if (!await fs.pathExists(envPath)) await writeFileIfChanged(envPath, getAstroEnvFile());
|
|
2830
2715
|
else await updateFileIfExists(envPath, addAstroLocalsType);
|
|
2716
|
+
if (config.webDeploy === "cloudflare") await updateFileIfExists(envPath, (content) => {
|
|
2717
|
+
if (content.includes("cfContext:")) return content;
|
|
2718
|
+
return `/// <reference types="@cloudflare/workers-types" />\n${content.replace("interface Locals {", "interface Locals {\n cfContext: ExecutionContext;")}`;
|
|
2719
|
+
});
|
|
2831
2720
|
if (shouldIdentifyWebAuth(config)) await updateFileIfExists(middlewarePath, (content) => addAstroBetterAuthEvlogSetup(content, config));
|
|
2832
2721
|
}
|
|
2833
2722
|
async function setupEvlogWeb(config) {
|
|
@@ -2847,7 +2736,7 @@ async function setupEvlog(config) {
|
|
|
2847
2736
|
const serverIndexPath = path.join(config.projectDir, "apps/server/src/index.ts");
|
|
2848
2737
|
if (await fs.pathExists(serverIndexPath)) {
|
|
2849
2738
|
const content = await fs.readFile(serverIndexPath, "utf-8");
|
|
2850
|
-
let nextContent = addEvlogServerSetup(content, config.backend, `${config.projectName}-server`, shouldWireEvlogServerFsDrain(config));
|
|
2739
|
+
let nextContent = addEvlogServerSetup(content, config.backend, `${config.projectName}-server`, shouldWireEvlogServerFsDrain(config), usesAxiom(config));
|
|
2851
2740
|
if (config.auth === "better-auth") nextContent = addEvlogBetterAuthServerSetup(nextContent, config.backend, getAuthExpression(config));
|
|
2852
2741
|
if (config.examples.includes("ai")) nextContent = addBackendAiEvlogSetup(nextContent, config.backend);
|
|
2853
2742
|
if (nextContent !== content) await fs.writeFile(serverIndexPath, nextContent);
|
|
@@ -4081,7 +3970,7 @@ function getRecommendedSourceKeys(config) {
|
|
|
4081
3970
|
if (backend === "convex") sources.push("waynesutton/convexskills");
|
|
4082
3971
|
if (addons.includes("opentui")) sources.push("msmps/opentui-skill");
|
|
4083
3972
|
if (addons.includes("ultracite")) sources.push("haydenbleasel/ultracite");
|
|
4084
|
-
if (addons.includes("evlog")) sources.push("https://www.evlog.dev");
|
|
3973
|
+
if (addons.includes("evlog") || addons.includes("axiom")) sources.push("https://www.evlog.dev");
|
|
4085
3974
|
return sources;
|
|
4086
3975
|
}
|
|
4087
3976
|
const CURATED_SKILLS_BY_SOURCE = {
|
|
@@ -4597,7 +4486,7 @@ const HOOKS = {
|
|
|
4597
4486
|
claude: { label: "Claude Code" },
|
|
4598
4487
|
copilot: { label: "GitHub Copilot" }
|
|
4599
4488
|
};
|
|
4600
|
-
const ULTRACITE_VERSION = "7.
|
|
4489
|
+
const ULTRACITE_VERSION = "7.11.0";
|
|
4601
4490
|
const DEFAULT_LINTER = "biome";
|
|
4602
4491
|
const DEFAULT_EDITORS = ["vscode"];
|
|
4603
4492
|
const DEFAULT_AGENTS = ["universal"];
|
|
@@ -4657,73 +4546,75 @@ async function setupUltracite(config, gitHooks) {
|
|
|
4657
4546
|
let editors = configuredOptions?.editors;
|
|
4658
4547
|
let agents = configuredOptions?.agents;
|
|
4659
4548
|
let hooks = configuredOptions?.hooks;
|
|
4660
|
-
if (!linter || !editors || !agents || !hooks)
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4549
|
+
if (!linter || !editors || !agents || !hooks) {
|
|
4550
|
+
if (isSilent()) {
|
|
4551
|
+
linter = linter ?? DEFAULT_LINTER;
|
|
4552
|
+
editors = editors ?? [...DEFAULT_EDITORS];
|
|
4553
|
+
agents = agents ?? [...DEFAULT_AGENTS];
|
|
4554
|
+
hooks = hooks ?? [...DEFAULT_HOOKS];
|
|
4555
|
+
} else {
|
|
4556
|
+
const results = await navigableGroup({
|
|
4557
|
+
linter: async () => {
|
|
4558
|
+
if (linter !== void 0) return linter;
|
|
4559
|
+
return navigableSelect({
|
|
4560
|
+
message: "Which linter do you want to use?",
|
|
4561
|
+
options: Object.entries(LINTERS).map(([key, linterOption]) => ({
|
|
4562
|
+
value: key,
|
|
4563
|
+
label: linterOption.label
|
|
4564
|
+
})),
|
|
4565
|
+
initialValue: linter ?? DEFAULT_LINTER
|
|
4566
|
+
});
|
|
4567
|
+
},
|
|
4568
|
+
editors: async () => {
|
|
4569
|
+
if (editors !== void 0) return editors;
|
|
4570
|
+
return navigableMultiselect({
|
|
4571
|
+
message: "Which editors do you want to configure (recommended)?",
|
|
4572
|
+
required: false,
|
|
4573
|
+
options: [
|
|
4574
|
+
{
|
|
4575
|
+
value: "vscode",
|
|
4576
|
+
label: "VSCode / Cursor / Windsurf"
|
|
4577
|
+
},
|
|
4578
|
+
{
|
|
4579
|
+
value: "zed",
|
|
4580
|
+
label: "Zed"
|
|
4581
|
+
},
|
|
4582
|
+
{
|
|
4583
|
+
value: "universal",
|
|
4584
|
+
label: "Universal (.vscode/settings.json)"
|
|
4585
|
+
}
|
|
4586
|
+
]
|
|
4587
|
+
});
|
|
4588
|
+
},
|
|
4589
|
+
agents: async () => {
|
|
4590
|
+
if (agents !== void 0) return agents;
|
|
4591
|
+
return navigableMultiselect({
|
|
4592
|
+
message: "Which agent files do you want to add (optional)?",
|
|
4593
|
+
required: false,
|
|
4594
|
+
options: Object.entries(AGENTS).map(([key, agent]) => ({
|
|
4595
|
+
value: key,
|
|
4596
|
+
label: agent.label
|
|
4597
|
+
}))
|
|
4598
|
+
});
|
|
4599
|
+
},
|
|
4600
|
+
hooks: async () => {
|
|
4601
|
+
if (hooks !== void 0) return hooks;
|
|
4602
|
+
return navigableMultiselect({
|
|
4603
|
+
message: "Which agent hooks do you want to enable (optional)?",
|
|
4604
|
+
required: false,
|
|
4605
|
+
options: Object.entries(HOOKS).map(([key, hook]) => ({
|
|
4606
|
+
value: key,
|
|
4607
|
+
label: hook.label
|
|
4608
|
+
}))
|
|
4609
|
+
});
|
|
4610
|
+
}
|
|
4611
|
+
});
|
|
4612
|
+
if (results.linter === void 0 || results.editors === void 0 || results.agents === void 0 || results.hooks === void 0) return userCancelled("Operation cancelled");
|
|
4613
|
+
linter = results.linter;
|
|
4614
|
+
editors = results.editors;
|
|
4615
|
+
agents = results.agents;
|
|
4616
|
+
hooks = results.hooks;
|
|
4617
|
+
}
|
|
4727
4618
|
}
|
|
4728
4619
|
const frameworks = getFrameworksFromFrontend(frontend);
|
|
4729
4620
|
const args = buildUltraciteInitArgs({
|
|
@@ -4796,20 +4687,22 @@ async function setupWxt(config) {
|
|
|
4796
4687
|
cliLog.info("Setting up WXT...");
|
|
4797
4688
|
const configuredOptions = config.addonOptions?.wxt;
|
|
4798
4689
|
let template = configuredOptions?.template;
|
|
4799
|
-
if (!template)
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4690
|
+
if (!template) {
|
|
4691
|
+
if (isSilent()) template = DEFAULT_TEMPLATE;
|
|
4692
|
+
else {
|
|
4693
|
+
setIsFirstPrompt(true);
|
|
4694
|
+
const selectedTemplate = await navigableSelect({
|
|
4695
|
+
message: "Choose a template",
|
|
4696
|
+
options: Object.entries(TEMPLATES).map(([key, templateOption]) => ({
|
|
4697
|
+
value: key,
|
|
4698
|
+
label: templateOption.label,
|
|
4699
|
+
hint: templateOption.hint
|
|
4700
|
+
})),
|
|
4701
|
+
initialValue: DEFAULT_TEMPLATE
|
|
4702
|
+
});
|
|
4703
|
+
if (isCancel$1(selectedTemplate)) return userCancelled("Operation cancelled");
|
|
4704
|
+
template = selectedTemplate;
|
|
4705
|
+
}
|
|
4813
4706
|
}
|
|
4814
4707
|
const devPort = configuredOptions?.devPort ?? DEFAULT_DEV_PORT;
|
|
4815
4708
|
const args = getPackageExecutionArgs(packageManager, `wxt@latest init extension --template ${template} --pm ${packageManager}`);
|
|
@@ -4909,7 +4802,7 @@ async function setupAddons(config) {
|
|
|
4909
4802
|
if (addons.includes("wxt")) await runSetup(() => setupWxt(config));
|
|
4910
4803
|
if (addons.includes("skills")) await runSetup(() => setupSkills(config));
|
|
4911
4804
|
if (addons.includes("mcp")) await runSetup(() => setupMcp(config));
|
|
4912
|
-
if (addons.includes("evlog")) await runSetup(() => setupEvlog(config));
|
|
4805
|
+
if (addons.includes("evlog") || addons.includes("axiom")) await runSetup(() => setupEvlog(config));
|
|
4913
4806
|
}
|
|
4914
4807
|
async function setupBiome(projectDir) {
|
|
4915
4808
|
await addPackageDependency({
|
|
@@ -4953,30 +4846,39 @@ async function setupLefthook(projectDir) {
|
|
|
4953
4846
|
}
|
|
4954
4847
|
//#endregion
|
|
4955
4848
|
//#region src/helpers/core/detect-project-config.ts
|
|
4849
|
+
const packageIdentitySchema = z.object({ name: z.string().optional() });
|
|
4956
4850
|
async function detectProjectConfig(projectDir) {
|
|
4851
|
+
projectDir = path.resolve(projectDir);
|
|
4957
4852
|
const result = await Result.tryPromise({
|
|
4958
4853
|
try: async () => {
|
|
4959
4854
|
const btsConfig = await readBtsConfig(projectDir);
|
|
4960
|
-
if (btsConfig)
|
|
4961
|
-
projectDir,
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4855
|
+
if (btsConfig) {
|
|
4856
|
+
const configPackagePath = path.join(projectDir, "packages/config/package.json");
|
|
4857
|
+
const configPackage = await fs.pathExists(configPackagePath) ? packageIdentitySchema.parse(await fs.readJson(configPackagePath)) : void 0;
|
|
4858
|
+
const rootPackagePath = path.join(projectDir, "package.json");
|
|
4859
|
+
const rootPackage = await fs.pathExists(rootPackagePath) ? packageIdentitySchema.parse(await fs.readJson(rootPackagePath)) : void 0;
|
|
4860
|
+
const scope = configPackage?.name ? /^@([^/]+)\/config$/.exec(configPackage.name)?.[1] : void 0;
|
|
4861
|
+
return {
|
|
4862
|
+
projectDir,
|
|
4863
|
+
projectName: scope || rootPackage?.name || path.basename(projectDir),
|
|
4864
|
+
addonOptions: btsConfig.addonOptions,
|
|
4865
|
+
dbSetupOptions: btsConfig.dbSetupOptions,
|
|
4866
|
+
database: btsConfig.database,
|
|
4867
|
+
orm: btsConfig.orm,
|
|
4868
|
+
backend: btsConfig.backend,
|
|
4869
|
+
runtime: btsConfig.runtime,
|
|
4870
|
+
frontend: btsConfig.frontend,
|
|
4871
|
+
addons: btsConfig.addons,
|
|
4872
|
+
examples: btsConfig.examples,
|
|
4873
|
+
auth: btsConfig.auth,
|
|
4874
|
+
payments: btsConfig.payments,
|
|
4875
|
+
packageManager: btsConfig.packageManager,
|
|
4876
|
+
dbSetup: btsConfig.dbSetup,
|
|
4877
|
+
api: btsConfig.api,
|
|
4878
|
+
webDeploy: btsConfig.webDeploy,
|
|
4879
|
+
serverDeploy: btsConfig.serverDeploy
|
|
4880
|
+
};
|
|
4881
|
+
}
|
|
4980
4882
|
return null;
|
|
4981
4883
|
},
|
|
4982
4884
|
catch: () => null
|
|
@@ -4987,7 +4889,7 @@ async function detectProjectConfig(projectDir) {
|
|
|
4987
4889
|
//#region src/helpers/core/install-dependencies.ts
|
|
4988
4890
|
const FORCE_KILL_AFTER_MS = 2e3;
|
|
4989
4891
|
async function installDependencies({ projectDir, packageManager }) {
|
|
4990
|
-
if (shouldSkipExternalCommands()) return Result.ok("
|
|
4892
|
+
if (shouldSkipExternalCommands()) return Result.ok("skipped");
|
|
4991
4893
|
startInterruptibleStep();
|
|
4992
4894
|
const s = createSpinner();
|
|
4993
4895
|
s.start(`Running ${packageManager} install...`);
|
|
@@ -5036,7 +4938,14 @@ const ADD_PACKAGE_JSON_PATHS = [
|
|
|
5036
4938
|
"packages/infra/package.json",
|
|
5037
4939
|
"packages/ui/package.json"
|
|
5038
4940
|
];
|
|
5039
|
-
const ADD_TEXT_FILE_PATHS = [
|
|
4941
|
+
const ADD_TEXT_FILE_PATHS = [
|
|
4942
|
+
"apps/web/vite.config.ts",
|
|
4943
|
+
"apps/web/next.config.ts",
|
|
4944
|
+
"apps/web/src/Document.tsx",
|
|
4945
|
+
"apps/web/src/root.tsx",
|
|
4946
|
+
"apps/web/src/app/layout.tsx",
|
|
4947
|
+
"lefthook.yml"
|
|
4948
|
+
];
|
|
5040
4949
|
const HOOK_ADDONS = ["husky", "lefthook"];
|
|
5041
4950
|
const HOOK_LINTER_ADDONS = [
|
|
5042
4951
|
"biome",
|
|
@@ -5197,12 +5106,13 @@ async function reportAddOutcome(input, result) {
|
|
|
5197
5106
|
});
|
|
5198
5107
|
}
|
|
5199
5108
|
async function addHandlerInternal(input) {
|
|
5200
|
-
const
|
|
5201
|
-
const hardeningResult = validateAgentSafePathInput(
|
|
5109
|
+
const projectDirInput = input.projectDir || process.cwd();
|
|
5110
|
+
const hardeningResult = validateAgentSafePathInput(projectDirInput, "projectDir");
|
|
5202
5111
|
if (hardeningResult.isErr()) return Result.err(new CLIError({
|
|
5203
5112
|
message: hardeningResult.error.message,
|
|
5204
5113
|
cause: hardeningResult.error
|
|
5205
5114
|
}));
|
|
5115
|
+
const projectDir = path.resolve(projectDirInput);
|
|
5206
5116
|
if (!isSilent()) {
|
|
5207
5117
|
renderTitle();
|
|
5208
5118
|
intro(pc.magenta("Add to your project"));
|
|
@@ -5249,6 +5159,7 @@ async function addHandlerInternal(input) {
|
|
|
5249
5159
|
}
|
|
5250
5160
|
addonsToAdd = selectedAddons;
|
|
5251
5161
|
}
|
|
5162
|
+
if (addonsToAdd.includes("axiom")) return Result.err(new CLIError({ message: "Axiom must be selected during project creation because it configures Alchemy infrastructure and deployment bindings." }));
|
|
5252
5163
|
const updatedAddons = [...existingConfig.addons, ...addonsToAdd];
|
|
5253
5164
|
const addonsValidationResult = validateAddonsAgainstConfig(updatedAddons, existingConfig);
|
|
5254
5165
|
if (addonsValidationResult.isErr()) return Result.err(new CLIError({ message: addonsValidationResult.error.message }));
|
|
@@ -5311,10 +5222,21 @@ async function addHandlerInternal(input) {
|
|
|
5311
5222
|
}
|
|
5312
5223
|
await processAddonTemplates(vfs, EMBEDDED_TEMPLATES, config);
|
|
5313
5224
|
processAddonsDeps(vfs, config);
|
|
5225
|
+
processPwaPlugins(vfs, config);
|
|
5314
5226
|
if (addonsToAdd.includes("turborepo")) processTurboConfig(vfs, updatedConfig);
|
|
5315
5227
|
if (addonsToAdd.includes("nx")) processNxConfig(vfs, updatedConfig);
|
|
5316
5228
|
if (addonsToAdd.includes("vite-plus")) processVitePlusConfig(vfs, updatedConfig);
|
|
5317
|
-
if (updatedAddons.some((addon) => TASK_RUNNER_ADDONS.includes(addon))
|
|
5229
|
+
if (updatedAddons.some((addon) => TASK_RUNNER_ADDONS$1.includes(addon)) || addonsToAdd.includes("electrobun")) {
|
|
5230
|
+
const existingNames = new Map(ADD_PACKAGE_JSON_PATHS.map((filePath) => [filePath, vfs.readJson(filePath)?.name]));
|
|
5231
|
+
processPackageConfigs(vfs, updatedConfig);
|
|
5232
|
+
for (const [filePath, name] of existingNames) if (name) {
|
|
5233
|
+
const pkg = vfs.readJson(filePath);
|
|
5234
|
+
if (pkg) vfs.writeJson(filePath, {
|
|
5235
|
+
...pkg,
|
|
5236
|
+
name
|
|
5237
|
+
});
|
|
5238
|
+
}
|
|
5239
|
+
}
|
|
5318
5240
|
if (updatedAddons.includes("vite-plus")) updateViteConfigImportsForVitePlus(vfs);
|
|
5319
5241
|
if (shouldRefreshLefthook(addonsToAdd, updatedAddons)) refreshLefthookTemplate(vfs, updatedConfig);
|
|
5320
5242
|
}
|
|
@@ -5380,10 +5302,13 @@ async function addHandlerInternal(input) {
|
|
|
5380
5302
|
}
|
|
5381
5303
|
});
|
|
5382
5304
|
if (setupResult.isErr()) return Result.err(setupResult.error);
|
|
5383
|
-
if (addonsToAdd.length > 0)
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5305
|
+
if (addonsToAdd.length > 0) {
|
|
5306
|
+
const configResult = await updateBtsConfig(projectDir, {
|
|
5307
|
+
addons: updatedAddons,
|
|
5308
|
+
addonOptions: updatedConfig.addonOptions
|
|
5309
|
+
});
|
|
5310
|
+
if (configResult.isErr()) return Result.err(configResult.error);
|
|
5311
|
+
}
|
|
5387
5312
|
if (input.install) await installDependencies({
|
|
5388
5313
|
projectDir,
|
|
5389
5314
|
packageManager: config.packageManager
|
|
@@ -5457,7 +5382,7 @@ function resolveProjectDbSetupOptions(config, cliOptions = {}) {
|
|
|
5457
5382
|
//#region src/prompts/api.ts
|
|
5458
5383
|
async function getApiChoice(Api, frontend, backend, previousValue) {
|
|
5459
5384
|
if (backend === "convex" || backend === "none") return "none";
|
|
5460
|
-
const allowed = allowedApisForFrontends(frontend ?? []);
|
|
5385
|
+
const allowed = allowedApisForFrontends$1(frontend ?? []);
|
|
5461
5386
|
if (Api) {
|
|
5462
5387
|
const compat = validateApiFrontendCompatibility(Api, frontend ?? []);
|
|
5463
5388
|
if (compat.isErr()) throw compat.error;
|
|
@@ -5488,20 +5413,11 @@ async function getApiChoice(Api, frontend, backend, previousValue) {
|
|
|
5488
5413
|
//#region src/prompts/auth.ts
|
|
5489
5414
|
function getAvailableAuthProviders(backend, frontend = []) {
|
|
5490
5415
|
if (backend === "none") return ["none"];
|
|
5491
|
-
const hasClerkCompatibleFrontends = frontend.some((f) => [
|
|
5492
|
-
"react-router",
|
|
5493
|
-
"tanstack-router",
|
|
5494
|
-
"tanstack-start",
|
|
5495
|
-
"next",
|
|
5496
|
-
"native-bare",
|
|
5497
|
-
"native-uniwind",
|
|
5498
|
-
"native-unistyles"
|
|
5499
|
-
].includes(f));
|
|
5500
5416
|
const options = [];
|
|
5501
5417
|
if (backend === "convex") {
|
|
5502
|
-
if (supportsConvexBetterAuth(frontend)) options.push("better-auth");
|
|
5418
|
+
if (supportsConvexBetterAuth(frontend) && frontend.every((f) => isFrontendAllowedWithBackend(f, backend, "better-auth"))) options.push("better-auth");
|
|
5503
5419
|
} else options.push("better-auth");
|
|
5504
|
-
if (
|
|
5420
|
+
if (supportsClerkFrontend(frontend) && supportsClerkBackend(backend, frontend)) options.push("clerk");
|
|
5505
5421
|
if (options.length === 0) return ["none"];
|
|
5506
5422
|
return [...options, "none"];
|
|
5507
5423
|
}
|
|
@@ -5538,7 +5454,7 @@ async function getAuthChoice(auth, backend, frontend = [], previousValue) {
|
|
|
5538
5454
|
}
|
|
5539
5455
|
//#endregion
|
|
5540
5456
|
//#region src/prompts/backend.ts
|
|
5541
|
-
const FULLSTACK_FRONTENDS = [
|
|
5457
|
+
const FULLSTACK_FRONTENDS$1 = [
|
|
5542
5458
|
"next",
|
|
5543
5459
|
"tanstack-start",
|
|
5544
5460
|
"nuxt",
|
|
@@ -5549,7 +5465,7 @@ const FULLSTACK_FRONTENDS = [
|
|
|
5549
5465
|
async function getBackendFrameworkChoice(backendFramework, frontends, previousValue) {
|
|
5550
5466
|
if (backendFramework !== void 0) return backendFramework;
|
|
5551
5467
|
const hasIncompatibleFrontend = frontends?.some((f) => f === "solid" || f === "astro");
|
|
5552
|
-
const hasFullstackFrontend = frontends?.some((f) => FULLSTACK_FRONTENDS.includes(f));
|
|
5468
|
+
const hasFullstackFrontend = frontends?.some((f) => FULLSTACK_FRONTENDS$1.includes(f));
|
|
5553
5469
|
const backendOptions = [];
|
|
5554
5470
|
if (hasFullstackFrontend) backendOptions.push({
|
|
5555
5471
|
value: "self",
|
|
@@ -5618,7 +5534,7 @@ async function getDatabaseChoice(database, backend, runtime, previousValue) {
|
|
|
5618
5534
|
hint: "popular open-source relational database system"
|
|
5619
5535
|
}
|
|
5620
5536
|
];
|
|
5621
|
-
if (runtime
|
|
5537
|
+
if (supportsRuntimeDatabase(runtime, "mongodb")) databaseOptions.push({
|
|
5622
5538
|
value: "mongodb",
|
|
5623
5539
|
label: "MongoDB",
|
|
5624
5540
|
hint: "open-source NoSQL database that stores data in JSON-like documents called BSON"
|
|
@@ -5637,25 +5553,17 @@ async function getDBSetupChoice(databaseType, dbSetup, _orm, backend, runtime, p
|
|
|
5637
5553
|
if (backend === "convex") return "none";
|
|
5638
5554
|
if (dbSetup !== void 0) return dbSetup;
|
|
5639
5555
|
if (databaseType === "none") return "none";
|
|
5640
|
-
|
|
5641
|
-
if (databaseType === "sqlite") options = [
|
|
5556
|
+
const options = [
|
|
5642
5557
|
{
|
|
5643
5558
|
value: "turso",
|
|
5644
5559
|
label: "Turso",
|
|
5645
5560
|
hint: "SQLite for Production. Powered by libSQL"
|
|
5646
5561
|
},
|
|
5647
|
-
|
|
5562
|
+
{
|
|
5648
5563
|
value: "d1",
|
|
5649
5564
|
label: "Cloudflare D1",
|
|
5650
5565
|
hint: "Cloudflare's managed, serverless database with SQLite's SQL semantics"
|
|
5651
|
-
}
|
|
5652
|
-
{
|
|
5653
|
-
value: "none",
|
|
5654
|
-
label: "None",
|
|
5655
|
-
hint: "Manual setup"
|
|
5656
|
-
}
|
|
5657
|
-
];
|
|
5658
|
-
else if (databaseType === "postgres") options = [
|
|
5566
|
+
},
|
|
5659
5567
|
{
|
|
5660
5568
|
value: "neon",
|
|
5661
5569
|
label: "Neon Postgres",
|
|
@@ -5676,35 +5584,6 @@ async function getDBSetupChoice(databaseType, dbSetup, _orm, backend, runtime, p
|
|
|
5676
5584
|
label: "Prisma Postgres",
|
|
5677
5585
|
hint: "Instant Postgres for Global Applications"
|
|
5678
5586
|
},
|
|
5679
|
-
{
|
|
5680
|
-
value: "docker",
|
|
5681
|
-
label: "Docker",
|
|
5682
|
-
hint: "Run locally with docker compose"
|
|
5683
|
-
},
|
|
5684
|
-
{
|
|
5685
|
-
value: "none",
|
|
5686
|
-
label: "None",
|
|
5687
|
-
hint: "Manual setup"
|
|
5688
|
-
}
|
|
5689
|
-
];
|
|
5690
|
-
else if (databaseType === "mysql") options = [
|
|
5691
|
-
{
|
|
5692
|
-
value: "planetscale",
|
|
5693
|
-
label: "PlanetScale",
|
|
5694
|
-
hint: "MySQL on Vitess (NVMe, HA)"
|
|
5695
|
-
},
|
|
5696
|
-
{
|
|
5697
|
-
value: "docker",
|
|
5698
|
-
label: "Docker",
|
|
5699
|
-
hint: "Run locally with docker compose"
|
|
5700
|
-
},
|
|
5701
|
-
{
|
|
5702
|
-
value: "none",
|
|
5703
|
-
label: "None",
|
|
5704
|
-
hint: "Manual setup"
|
|
5705
|
-
}
|
|
5706
|
-
];
|
|
5707
|
-
else if (databaseType === "mongodb") options = [
|
|
5708
5587
|
{
|
|
5709
5588
|
value: "mongodb-atlas",
|
|
5710
5589
|
label: "MongoDB Atlas",
|
|
@@ -5720,8 +5599,7 @@ async function getDBSetupChoice(databaseType, dbSetup, _orm, backend, runtime, p
|
|
|
5720
5599
|
label: "None",
|
|
5721
5600
|
hint: "Manual setup"
|
|
5722
5601
|
}
|
|
5723
|
-
];
|
|
5724
|
-
else return "none";
|
|
5602
|
+
].filter(({ value }) => supportsDatabaseSetup(value, databaseType) && supportsDatabaseSetupRuntime(value, runtime, backend));
|
|
5725
5603
|
const response = await navigableSelect({
|
|
5726
5604
|
message: `Choose a ${databaseType} setup`,
|
|
5727
5605
|
options,
|
|
@@ -5744,8 +5622,8 @@ async function getDbProvisioningChoice(mode, dbSetup, backend, webDeploy, server
|
|
|
5744
5622
|
serverDeploy
|
|
5745
5623
|
})) return mode === "alchemy" ? void 0 : mode;
|
|
5746
5624
|
if (mode !== void 0) return mode;
|
|
5625
|
+
if (dbSetup !== "neon" && dbSetup !== "planetscale" && dbSetup !== "prisma-postgres") return;
|
|
5747
5626
|
const provider = providerLabels[dbSetup];
|
|
5748
|
-
if (!provider) return void 0;
|
|
5749
5627
|
const options = [
|
|
5750
5628
|
{
|
|
5751
5629
|
value: "alchemy",
|
|
@@ -5778,12 +5656,12 @@ async function getExamplesChoice(examples, database, frontends, backend, api, pr
|
|
|
5778
5656
|
if (backend === "none") return [];
|
|
5779
5657
|
let response = [];
|
|
5780
5658
|
const options = [];
|
|
5781
|
-
if (isExampleTodoAllowed(backend, database, api)) options.push({
|
|
5659
|
+
if (isExampleTodoAllowed$1(backend, database, api)) options.push({
|
|
5782
5660
|
value: "todo",
|
|
5783
5661
|
label: "Todo App",
|
|
5784
5662
|
hint: "A simple CRUD example app"
|
|
5785
5663
|
});
|
|
5786
|
-
if (isExampleAIAllowed(backend, frontends ?? [])) options.push({
|
|
5664
|
+
if (isExampleAIAllowed$1(backend, frontends ?? [])) options.push({
|
|
5787
5665
|
value: "ai",
|
|
5788
5666
|
label: "AI Chat",
|
|
5789
5667
|
hint: "A simple AI chat interface using AI SDK"
|
|
@@ -5877,7 +5755,7 @@ async function getFrontendChoice(frontendOptions, backend, auth, previousValue)
|
|
|
5877
5755
|
label: "TanStack Start",
|
|
5878
5756
|
hint: "SSR, Server Functions, API Routes and more with TanStack Router"
|
|
5879
5757
|
}
|
|
5880
|
-
].filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth));
|
|
5758
|
+
].filter((option) => isFrontendAllowedWithBackend$1(option.value, backend, auth));
|
|
5881
5759
|
const webFramework = await navigableSelect({
|
|
5882
5760
|
message: "Choose a web framework",
|
|
5883
5761
|
options: webOptions,
|
|
@@ -5913,12 +5791,13 @@ async function getFrontendChoice(frontendOptions, backend, auth, previousValue)
|
|
|
5913
5791
|
],
|
|
5914
5792
|
initialValue: previousNative ?? "native-bare"
|
|
5915
5793
|
});
|
|
5916
|
-
if (isGoBack(nativeFramework))
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5794
|
+
if (isGoBack(nativeFramework)) {
|
|
5795
|
+
if (frontendTypes.includes("web")) shouldRestart = true;
|
|
5796
|
+
else {
|
|
5797
|
+
setIsFirstPrompt(wasFirstPrompt);
|
|
5798
|
+
continue;
|
|
5799
|
+
}
|
|
5800
|
+
} else if (isCancel$1(nativeFramework)) throw new UserCancelledError({ message: "Operation cancelled" });
|
|
5922
5801
|
else result.push(nativeFramework);
|
|
5923
5802
|
}
|
|
5924
5803
|
if (shouldRestart) {
|
|
@@ -5974,12 +5853,11 @@ function canResolveSelfCloudflareD1Target(config) {
|
|
|
5974
5853
|
* prompt).
|
|
5975
5854
|
*/
|
|
5976
5855
|
function validateOrmDatabaseCompat(orm, database) {
|
|
5977
|
-
if (orm
|
|
5856
|
+
if (!orm || !database || supportsOrmDatabase(orm, database)) return Result.ok(void 0);
|
|
5857
|
+
if (orm === "mongoose") return validationErr("Mongoose ORM requires MongoDB database. Please use '--database mongodb' or choose a different ORM.");
|
|
5978
5858
|
if (orm === "drizzle" && database === "mongodb") return validationErr("Drizzle ORM does not support MongoDB. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
|
|
5979
|
-
if (
|
|
5980
|
-
|
|
5981
|
-
if (orm && orm !== "none" && database === "none") return validationErr("ORM selection requires a database. Please choose a database or set '--orm none'.");
|
|
5982
|
-
return Result.ok(void 0);
|
|
5859
|
+
if (orm === "none") return validationErr("Database selection requires an ORM. Please choose '--orm drizzle', '--orm prisma', or '--orm mongoose'.");
|
|
5860
|
+
return validationErr("ORM selection requires a database. Please choose a database or set '--orm none'.");
|
|
5983
5861
|
}
|
|
5984
5862
|
function validateDatabaseOrmAuth(cfg, flags) {
|
|
5985
5863
|
const has = (k) => flags ? flags.has(k) : true;
|
|
@@ -5990,40 +5868,19 @@ function validateDatabaseSetup(config, providedFlags) {
|
|
|
5990
5868
|
const { dbSetup, database, runtime } = config;
|
|
5991
5869
|
if (providedFlags.has("dbSetup") && providedFlags.has("database") && dbSetup && dbSetup !== "none" && database === "none") return validationErr("Database setup requires a database. Please choose a database or set '--db-setup none'.");
|
|
5992
5870
|
const setupValidations = {
|
|
5993
|
-
turso: {
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
},
|
|
5997
|
-
neon: {
|
|
5998
|
-
database: "postgres",
|
|
5999
|
-
errorMessage: "Neon setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
|
|
6000
|
-
},
|
|
6001
|
-
"prisma-postgres": {
|
|
6002
|
-
database: "postgres",
|
|
6003
|
-
errorMessage: "Prisma PostgreSQL setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
|
|
6004
|
-
},
|
|
5871
|
+
turso: { errorMessage: "Turso setup requires SQLite database. Please use '--database sqlite' or choose a different setup." },
|
|
5872
|
+
neon: { errorMessage: "Neon setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup." },
|
|
5873
|
+
"prisma-postgres": { errorMessage: "Prisma PostgreSQL setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup." },
|
|
6005
5874
|
planetscale: { errorMessage: "PlanetScale setup requires PostgreSQL or MySQL database. Please use '--database postgres' or '--database mysql' or choose a different setup." },
|
|
6006
|
-
"mongodb-atlas": {
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
},
|
|
6010
|
-
supabase: {
|
|
6011
|
-
database: "postgres",
|
|
6012
|
-
errorMessage: "Supabase setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
|
|
6013
|
-
},
|
|
6014
|
-
d1: {
|
|
6015
|
-
database: "sqlite",
|
|
6016
|
-
errorMessage: "Cloudflare D1 setup requires SQLite database."
|
|
6017
|
-
},
|
|
5875
|
+
"mongodb-atlas": { errorMessage: "MongoDB Atlas setup requires MongoDB database. Please use '--database mongodb' or choose a different setup." },
|
|
5876
|
+
supabase: { errorMessage: "Supabase setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup." },
|
|
5877
|
+
d1: { errorMessage: "Cloudflare D1 setup requires SQLite database." },
|
|
6018
5878
|
docker: { errorMessage: "Docker setup is not compatible with SQLite database or Cloudflare Workers runtime." },
|
|
6019
5879
|
none: { errorMessage: "" }
|
|
6020
5880
|
};
|
|
6021
5881
|
if (dbSetup && dbSetup !== "none") {
|
|
6022
5882
|
const validation = setupValidations[dbSetup];
|
|
6023
|
-
if (dbSetup
|
|
6024
|
-
if (database !== "postgres" && database !== "mysql") return validationErr(validation.errorMessage);
|
|
6025
|
-
} else if ("database" in validation && validation.database && database !== validation.database) return validationErr(validation.errorMessage);
|
|
6026
|
-
if ("runtime" in validation && validation.runtime && runtime !== validation.runtime) return validationErr(validation.errorMessage);
|
|
5883
|
+
if (dbSetup !== "docker" && !supportsDatabaseSetup(dbSetup, database)) return validationErr(validation.errorMessage);
|
|
6027
5884
|
if (dbSetup === "d1") {
|
|
6028
5885
|
const isWorkersTarget = hasResolvedWorkersD1Target(config);
|
|
6029
5886
|
const isSelfCloudflareTarget = hasResolvedSelfCloudflareD1Target(config);
|
|
@@ -6032,8 +5889,8 @@ function validateDatabaseSetup(config, providedFlags) {
|
|
|
6032
5889
|
if (!isWorkersTarget && !isSelfCloudflareTarget && !canResolveWorkersTarget && !canResolveSelfCloudflareTarget) return validationErr("Cloudflare D1 setup requires SQLite database and either Cloudflare Workers runtime with server deployment or backend 'self' with Cloudflare web deployment.");
|
|
6033
5890
|
}
|
|
6034
5891
|
if (dbSetup === "docker") {
|
|
6035
|
-
if (database
|
|
6036
|
-
if (runtime
|
|
5892
|
+
if (database && !supportsDatabaseSetup(dbSetup, database)) return validationErr("Docker setup is not compatible with SQLite database. SQLite is file-based and doesn't require Docker. Please use '--database postgres', '--database mysql', '--database mongodb', or choose a different setup.");
|
|
5893
|
+
if (!supportsDatabaseSetupRuntime(dbSetup, runtime, config.backend)) return validationErr("Docker setup is not compatible with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.");
|
|
6037
5894
|
}
|
|
6038
5895
|
}
|
|
6039
5896
|
return Result.ok(void 0);
|
|
@@ -6056,12 +5913,8 @@ function validateConvexConstraints(config, providedFlags) {
|
|
|
6056
5913
|
const { backend } = config;
|
|
6057
5914
|
if (backend !== "convex") return Result.ok(void 0);
|
|
6058
5915
|
const has = (k) => providedFlags.has(k);
|
|
6059
|
-
|
|
6060
|
-
if (
|
|
6061
|
-
if (has("orm") && config.orm !== "none") return validationErr("Convex backend requires '--orm none'. Please remove the --orm flag or set it to 'none'.");
|
|
6062
|
-
if (has("api") && config.api !== "none") return validationErr("Convex backend requires '--api none'. Please remove the --api flag or set it to 'none'.");
|
|
6063
|
-
if (has("dbSetup") && config.dbSetup !== "none") return validationErr("Convex backend requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.");
|
|
6064
|
-
if (has("serverDeploy") && config.serverDeploy !== "none") return validationErr("Convex backend requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.");
|
|
5916
|
+
const disabled = validateBackendDisabledOptions(config, providedFlags);
|
|
5917
|
+
if (disabled.isErr()) return disabled;
|
|
6065
5918
|
if (has("auth") && config.auth === "better-auth") {
|
|
6066
5919
|
const incompatibleFrontends = config.frontend?.filter((f) => CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS.includes(f)) ?? [];
|
|
6067
5920
|
const hasSupportedFrontend = supportsConvexBetterAuth(config.frontend);
|
|
@@ -6070,43 +5923,36 @@ function validateConvexConstraints(config, providedFlags) {
|
|
|
6070
5923
|
}
|
|
6071
5924
|
return Result.ok(void 0);
|
|
6072
5925
|
}
|
|
6073
|
-
function
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
if (has("api") && config.api !== "none") return validationErr("Backend 'none' requires '--api none'. Please remove the --api flag or set it to 'none'.");
|
|
6081
|
-
if (has("auth") && config.auth !== "none") return validationErr("Backend 'none' requires '--auth none'. Please remove the --auth flag or set it to 'none'.");
|
|
6082
|
-
if (has("payments") && config.payments !== "none") return validationErr("Backend 'none' requires '--payments none'. Please remove the --payments flag or set it to 'none'.");
|
|
6083
|
-
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'.");
|
|
6084
|
-
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'.");
|
|
5926
|
+
function validateBackendDisabledOptions(config, providedFlags) {
|
|
5927
|
+
if (!config.backend) return Result.ok(void 0);
|
|
5928
|
+
for (const key of getBackendDisabledOptions(config.backend)) {
|
|
5929
|
+
if (!providedFlags.has(key) || config[key] === "none") continue;
|
|
5930
|
+
const flag = key.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
5931
|
+
return validationErr(`${config.backend === "convex" ? "Convex backend" : `Backend '${config.backend}'${config.backend === "self" ? " (fullstack)" : ""}`} requires '--${flag} none'. Please remove the --${flag} flag or set it to 'none'.`);
|
|
5932
|
+
}
|
|
6085
5933
|
return Result.ok(void 0);
|
|
6086
5934
|
}
|
|
5935
|
+
function validateBackendNoneConstraints(config, providedFlags) {
|
|
5936
|
+
return config.backend === "none" ? validateBackendDisabledOptions(config, providedFlags) : Result.ok(void 0);
|
|
5937
|
+
}
|
|
6087
5938
|
function validateSelfBackendConstraints(config, providedFlags) {
|
|
6088
|
-
|
|
6089
|
-
if (backend !== "self") return Result.ok(void 0);
|
|
6090
|
-
const has = (k) => providedFlags.has(k);
|
|
6091
|
-
if (has("runtime") && config.runtime !== "none") return validationErr("Backend 'self' (fullstack) requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.");
|
|
6092
|
-
return Result.ok(void 0);
|
|
5939
|
+
return config.backend === "self" ? validateBackendDisabledOptions(config, providedFlags) : Result.ok(void 0);
|
|
6093
5940
|
}
|
|
6094
5941
|
function validateBackendConstraints(config, providedFlags, options) {
|
|
6095
5942
|
const { backend } = config;
|
|
6096
5943
|
if (config.auth === "clerk" && config.frontend) {
|
|
6097
|
-
const incompatibleFrontends = config.frontend.filter((f) =>
|
|
6098
|
-
"nuxt",
|
|
6099
|
-
"svelte",
|
|
6100
|
-
"solid",
|
|
6101
|
-
"astro"
|
|
6102
|
-
].includes(f));
|
|
5944
|
+
const incompatibleFrontends = config.frontend.filter((f) => !isFrontendAllowedWithBackend(f, void 0, "clerk"));
|
|
6103
5945
|
if (incompatibleFrontends.length > 0) return validationErr(`Clerk authentication is not compatible with the following frontends: ${incompatibleFrontends.join(", ")}. Please choose a different frontend or auth provider.`);
|
|
6104
5946
|
}
|
|
6105
|
-
if (
|
|
5947
|
+
if (config.auth === "clerk") {
|
|
5948
|
+
if (!supportsClerkBackend(backend, config.frontend)) return validationErr("Clerk requires Convex, a separate server, or backend self with Next.js or TanStack Start.");
|
|
5949
|
+
if (config.frontend && !supportsClerkFrontend(config.frontend)) return validationErr("Clerk requires a React web or native frontend.");
|
|
5950
|
+
}
|
|
5951
|
+
if (providedFlags.has("backend") && backend && !supportsRuntimeBackend("none", backend)) {
|
|
6106
5952
|
if (providedFlags.has("runtime") && options.runtime === "none") return validationErr("'--runtime none' is only supported with '--backend convex', '--backend none', or '--backend self'. Please choose 'bun', 'node', or remove the --runtime flag.");
|
|
6107
5953
|
}
|
|
6108
5954
|
if (backend === "convex" && providedFlags.has("frontend") && options.frontend) {
|
|
6109
|
-
const incompatibleFrontends = options.frontend.filter((
|
|
5955
|
+
const incompatibleFrontends = options.frontend.filter((frontend) => !isFrontendAllowedWithBackend(frontend, "convex"));
|
|
6110
5956
|
if (incompatibleFrontends.length > 0) return validationErr(`The following frontends are not compatible with '--backend convex': ${incompatibleFrontends.join(", ")}. Please choose a different frontend or backend.`);
|
|
6111
5957
|
}
|
|
6112
5958
|
return Result.ok(void 0);
|
|
@@ -6154,7 +6000,7 @@ function validateFullConfig(config, providedFlags, options) {
|
|
|
6154
6000
|
yield* validateSelfBackendCompatibility(providedFlags, options, config);
|
|
6155
6001
|
yield* validateWorkersCompatibility(providedFlags, options, config);
|
|
6156
6002
|
if (config.runtime === "workers" && config.serverDeploy === "none") yield* validationErr("Cloudflare Workers runtime requires a server deployment. Please choose 'cloudflare' for --server-deploy.");
|
|
6157
|
-
if (providedFlags.has("serverDeploy") && config.serverDeploy === "cloudflare" && config.runtime
|
|
6003
|
+
if (providedFlags.has("serverDeploy") && config.serverDeploy === "cloudflare" && !supportsServerDeployRuntime(config.serverDeploy, config.runtime)) yield* validationErr(`Server deployment '${config.serverDeploy}' requires '--runtime workers'. Please use '--runtime workers' or choose a different server deployment.`);
|
|
6158
6004
|
if (config.addons && config.addons.length > 0) {
|
|
6159
6005
|
yield* validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth, config.backend, config.runtime);
|
|
6160
6006
|
config.addons = [...new Set(config.addons)];
|
|
@@ -6202,7 +6048,11 @@ async function getORMChoice(orm, hasDatabase, database, backend, runtime, previo
|
|
|
6202
6048
|
if (compat.isErr()) throw compat.error;
|
|
6203
6049
|
return orm;
|
|
6204
6050
|
}
|
|
6205
|
-
const options =
|
|
6051
|
+
const options = [
|
|
6052
|
+
ormOptions.drizzle,
|
|
6053
|
+
ormOptions.prisma,
|
|
6054
|
+
ormOptions.mongoose
|
|
6055
|
+
].filter((option) => supportsOrmDatabase(option.value, database ?? "sqlite"));
|
|
6206
6056
|
const response = await navigableSelect({
|
|
6207
6057
|
message: "Choose an ORM",
|
|
6208
6058
|
options,
|
|
@@ -6266,7 +6116,7 @@ async function getPaymentsChoice(payments, auth, backend, _frontends, previousVa
|
|
|
6266
6116
|
//#endregion
|
|
6267
6117
|
//#region src/prompts/runtime.ts
|
|
6268
6118
|
async function getRuntimeChoice(runtime, backend, previousValue) {
|
|
6269
|
-
if (backend
|
|
6119
|
+
if (backend && supportsRuntimeBackend("none", backend)) return "none";
|
|
6270
6120
|
if (runtime !== void 0) return runtime;
|
|
6271
6121
|
const runtimeOptions = [{
|
|
6272
6122
|
value: "bun",
|
|
@@ -6277,7 +6127,7 @@ async function getRuntimeChoice(runtime, backend, previousValue) {
|
|
|
6277
6127
|
label: "Node.js",
|
|
6278
6128
|
hint: "Traditional Node.js runtime"
|
|
6279
6129
|
}];
|
|
6280
|
-
if (backend
|
|
6130
|
+
if (backend && supportsRuntimeBackend("workers", backend)) runtimeOptions.push({
|
|
6281
6131
|
value: "workers",
|
|
6282
6132
|
label: "Cloudflare Workers",
|
|
6283
6133
|
hint: "Edge runtime on Cloudflare's global network"
|
|
@@ -6380,7 +6230,7 @@ async function getDeploymentChoice(deployment, _runtime, backend, frontend = [],
|
|
|
6380
6230
|
if (deployment !== void 0) return deployment;
|
|
6381
6231
|
if (!hasWebFrontend(frontend)) return "none";
|
|
6382
6232
|
if (backend === "self" && dbSetup === "d1") return "cloudflare";
|
|
6383
|
-
const supportsPrismaCompute = supportsPrismaWebDeploy(frontend);
|
|
6233
|
+
const supportsPrismaCompute = supportsPrismaWebDeploy$1(frontend);
|
|
6384
6234
|
const options = [
|
|
6385
6235
|
...validateCloudflareWebDeployKnownIssues({
|
|
6386
6236
|
webDeploy: "cloudflare",
|
|
@@ -6581,7 +6431,7 @@ async function getProjectName(initialName) {
|
|
|
6581
6431
|
}
|
|
6582
6432
|
});
|
|
6583
6433
|
if (isCancel(response)) throw new UserCancelledError({ message: "Operation cancelled." });
|
|
6584
|
-
projectPath = response || defaultName;
|
|
6434
|
+
projectPath = types_exports.ProjectNameSchema.parse(response || defaultName);
|
|
6585
6435
|
isValid = true;
|
|
6586
6436
|
}
|
|
6587
6437
|
return projectPath;
|
|
@@ -7400,6 +7250,21 @@ const formatOptions = {
|
|
|
7400
7250
|
experimentalSortPackageJson: true,
|
|
7401
7251
|
experimentalSortImports: { order: "asc" }
|
|
7402
7252
|
};
|
|
7253
|
+
const ignoredDirectories = /* @__PURE__ */ new Set([
|
|
7254
|
+
"node_modules",
|
|
7255
|
+
".git",
|
|
7256
|
+
".next",
|
|
7257
|
+
".nuxt",
|
|
7258
|
+
".output",
|
|
7259
|
+
".svelte-kit",
|
|
7260
|
+
".astro",
|
|
7261
|
+
".turbo",
|
|
7262
|
+
".vercel",
|
|
7263
|
+
".wrangler",
|
|
7264
|
+
"dist",
|
|
7265
|
+
"build",
|
|
7266
|
+
"coverage"
|
|
7267
|
+
]);
|
|
7403
7268
|
async function formatCode(filePath, content) {
|
|
7404
7269
|
const result = await Result.tryPromise({
|
|
7405
7270
|
try: async () => {
|
|
@@ -7420,8 +7285,10 @@ async function formatProject(projectDir, signal) {
|
|
|
7420
7285
|
await Promise.all(entries.map(async (entry) => {
|
|
7421
7286
|
if (signal?.aborted) return;
|
|
7422
7287
|
const fullPath = path.join(dir, entry.name);
|
|
7423
|
-
if (entry.isDirectory())
|
|
7424
|
-
|
|
7288
|
+
if (entry.isDirectory()) {
|
|
7289
|
+
if (ignoredDirectories.has(entry.name)) return;
|
|
7290
|
+
await formatDirectory(fullPath);
|
|
7291
|
+
} else if (entry.isFile()) await Result.tryPromise({
|
|
7425
7292
|
try: async () => {
|
|
7426
7293
|
const content = await fs.readFile(fullPath, "utf-8");
|
|
7427
7294
|
const formatted = await formatCode(fullPath, content);
|
|
@@ -7483,9 +7350,10 @@ async function setupCloudflareD1(config) {
|
|
|
7483
7350
|
condition: true
|
|
7484
7351
|
}]);
|
|
7485
7352
|
await fs.ensureFile(path.join(projectDir, "packages/db/local.db"));
|
|
7353
|
+
const serverDir = path.join(projectDir, backend === "self" ? "apps/web" : "apps/server");
|
|
7486
7354
|
await addPackageDependency({
|
|
7487
7355
|
dependencies: ["@prisma/adapter-d1"],
|
|
7488
|
-
projectDir:
|
|
7356
|
+
projectDir: serverDir
|
|
7489
7357
|
});
|
|
7490
7358
|
},
|
|
7491
7359
|
catch: (e) => new DatabaseSetupError({
|
|
@@ -7651,7 +7519,7 @@ async function setupMongoDBAtlas(config, cliInput) {
|
|
|
7651
7519
|
initialValue: "auto"
|
|
7652
7520
|
});
|
|
7653
7521
|
if (isCancel(promptedMode)) return userCancelled("Operation cancelled");
|
|
7654
|
-
mode = promptedMode;
|
|
7522
|
+
mode = types_exports.DbSetupModeSchema.parse(promptedMode);
|
|
7655
7523
|
}
|
|
7656
7524
|
if (mode === "manual") {
|
|
7657
7525
|
cliLog.info("MongoDB Atlas manual setup selected");
|
|
@@ -7845,23 +7713,25 @@ async function setupNeonPostgres(config, cliInput) {
|
|
|
7845
7713
|
return Result.ok(void 0);
|
|
7846
7714
|
}
|
|
7847
7715
|
let selectedMode = setupMode;
|
|
7848
|
-
if (!selectedMode)
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7716
|
+
if (!selectedMode) {
|
|
7717
|
+
if (isSilent()) selectedMode = "manual";
|
|
7718
|
+
else {
|
|
7719
|
+
const promptedMode = await select({
|
|
7720
|
+
message: "Neon setup: choose mode",
|
|
7721
|
+
options: [{
|
|
7722
|
+
label: "Automatic",
|
|
7723
|
+
value: "auto",
|
|
7724
|
+
hint: "Automated setup with provider CLI, sets .env"
|
|
7725
|
+
}, {
|
|
7726
|
+
label: "Manual",
|
|
7727
|
+
value: "manual",
|
|
7728
|
+
hint: "Manual setup, add env vars yourself"
|
|
7729
|
+
}],
|
|
7730
|
+
initialValue: "auto"
|
|
7731
|
+
});
|
|
7732
|
+
if (isCancel(promptedMode)) return userCancelled("Operation cancelled");
|
|
7733
|
+
selectedMode = types_exports.DbSetupModeSchema.parse(promptedMode);
|
|
7734
|
+
}
|
|
7865
7735
|
}
|
|
7866
7736
|
if (selectedMode === "manual") {
|
|
7867
7737
|
const envResult = await writeEnvFile$2(projectDir, backend);
|
|
@@ -7870,23 +7740,25 @@ async function setupNeonPostgres(config, cliInput) {
|
|
|
7870
7740
|
return Result.ok(void 0);
|
|
7871
7741
|
}
|
|
7872
7742
|
let setupMethod = cliInput?.dbSetupOptions?.neon?.method ?? config.dbSetupOptions?.neon?.method;
|
|
7873
|
-
if (!setupMethod)
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
7743
|
+
if (!setupMethod) {
|
|
7744
|
+
if (isSilent()) setupMethod = "neon-new";
|
|
7745
|
+
else {
|
|
7746
|
+
const promptedSetupMethod = await select({
|
|
7747
|
+
message: "Choose your Neon setup method:",
|
|
7748
|
+
options: [{
|
|
7749
|
+
label: "Quick setup with neon-new",
|
|
7750
|
+
value: "neon-new",
|
|
7751
|
+
hint: "fastest, no auth required"
|
|
7752
|
+
}, {
|
|
7753
|
+
label: "Custom setup with Neon CLI",
|
|
7754
|
+
value: "neon",
|
|
7755
|
+
hint: "More control - choose project name and region"
|
|
7756
|
+
}],
|
|
7757
|
+
initialValue: "neon-new"
|
|
7758
|
+
});
|
|
7759
|
+
if (isCancel(promptedSetupMethod)) return userCancelled("Operation cancelled");
|
|
7760
|
+
setupMethod = types_exports.NeonSetupMethodSchema.parse(promptedSetupMethod);
|
|
7761
|
+
}
|
|
7890
7762
|
}
|
|
7891
7763
|
if (setupMethod === "neon-new" || setupMethod === "neondb") {
|
|
7892
7764
|
const neonDbResult = await setupWithNeonDb(projectDir, packageManager, backend);
|
|
@@ -7902,26 +7774,30 @@ async function setupNeonPostgres(config, cliInput) {
|
|
|
7902
7774
|
}
|
|
7903
7775
|
const suggestedProjectName = path.basename(projectDir);
|
|
7904
7776
|
let projectName = cliInput?.dbSetupOptions?.neon?.projectName ?? config.dbSetupOptions?.neon?.projectName;
|
|
7905
|
-
if (!projectName)
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
|
|
7777
|
+
if (!projectName) {
|
|
7778
|
+
if (isSilent()) projectName = suggestedProjectName;
|
|
7779
|
+
else {
|
|
7780
|
+
const promptedProjectName = await text({
|
|
7781
|
+
message: "Enter a name for your Neon project:",
|
|
7782
|
+
defaultValue: suggestedProjectName,
|
|
7783
|
+
initialValue: suggestedProjectName
|
|
7784
|
+
});
|
|
7785
|
+
if (isCancel(promptedProjectName)) return userCancelled("Operation cancelled");
|
|
7786
|
+
projectName = promptedProjectName;
|
|
7787
|
+
}
|
|
7914
7788
|
}
|
|
7915
7789
|
let regionId = cliInput?.dbSetupOptions?.neon?.regionId ?? config.dbSetupOptions?.neon?.regionId;
|
|
7916
|
-
if (!regionId)
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7790
|
+
if (!regionId) {
|
|
7791
|
+
if (isSilent()) regionId = NEON_REGIONS[0].value;
|
|
7792
|
+
else {
|
|
7793
|
+
const promptedRegionId = await select({
|
|
7794
|
+
message: "Select a region for your Neon project:",
|
|
7795
|
+
options: NEON_REGIONS,
|
|
7796
|
+
initialValue: NEON_REGIONS[0].value
|
|
7797
|
+
});
|
|
7798
|
+
if (isCancel(promptedRegionId)) return userCancelled("Operation cancelled");
|
|
7799
|
+
regionId = z$1.string().parse(promptedRegionId);
|
|
7800
|
+
}
|
|
7925
7801
|
}
|
|
7926
7802
|
const neonConfigResult = await createNeonProject(projectName, regionId, packageManager);
|
|
7927
7803
|
if (neonConfigResult.isErr()) {
|
|
@@ -8044,15 +7920,17 @@ const CREATE_DB_USER_AGENT = "aman/better-t-stack";
|
|
|
8044
7920
|
async function setupWithCreateDb(serverDir, packageManager, regionId) {
|
|
8045
7921
|
cliLog.info("Starting Prisma Postgres setup with create-db.");
|
|
8046
7922
|
let selectedRegion = regionId;
|
|
8047
|
-
if (!selectedRegion)
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
7923
|
+
if (!selectedRegion) {
|
|
7924
|
+
if (isSilent()) selectedRegion = "ap-southeast-1";
|
|
7925
|
+
else {
|
|
7926
|
+
const promptedRegion = await select({
|
|
7927
|
+
message: "Select your preferred region:",
|
|
7928
|
+
options: AVAILABLE_REGIONS,
|
|
7929
|
+
initialValue: "ap-southeast-1"
|
|
7930
|
+
});
|
|
7931
|
+
if (isCancel(promptedRegion)) return userCancelled("Operation cancelled");
|
|
7932
|
+
selectedRegion = z$1.string().parse(promptedRegion);
|
|
7933
|
+
}
|
|
8056
7934
|
}
|
|
8057
7935
|
const createDbArgs = [
|
|
8058
7936
|
...getPackageRunnerPrefix(packageManager),
|
|
@@ -8155,23 +8033,25 @@ async function setupPrismaPostgres(config, cliInput) {
|
|
|
8155
8033
|
return Result.ok(void 0);
|
|
8156
8034
|
}
|
|
8157
8035
|
let selectedSetupMode = setupMode;
|
|
8158
|
-
if (!selectedSetupMode)
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8036
|
+
if (!selectedSetupMode) {
|
|
8037
|
+
if (isSilent()) selectedSetupMode = "manual";
|
|
8038
|
+
else {
|
|
8039
|
+
const promptedSetupMode = await select({
|
|
8040
|
+
message: "Prisma Postgres setup: choose mode",
|
|
8041
|
+
options: [{
|
|
8042
|
+
label: "Automatic (create-db)",
|
|
8043
|
+
value: "auto",
|
|
8044
|
+
hint: "Provision a database via Prisma's create-db CLI"
|
|
8045
|
+
}, {
|
|
8046
|
+
label: "Manual",
|
|
8047
|
+
value: "manual",
|
|
8048
|
+
hint: "Add your own DATABASE_URL later"
|
|
8049
|
+
}],
|
|
8050
|
+
initialValue: "auto"
|
|
8051
|
+
});
|
|
8052
|
+
if (isCancel(promptedSetupMode)) return userCancelled("Operation cancelled");
|
|
8053
|
+
selectedSetupMode = types_exports.DbSetupModeSchema.parse(promptedSetupMode);
|
|
8054
|
+
}
|
|
8175
8055
|
}
|
|
8176
8056
|
if (selectedSetupMode === "manual") {
|
|
8177
8057
|
const envResult = await writeEnvFile$1(projectDir, backend);
|
|
@@ -8316,23 +8196,25 @@ async function setupSupabase(config, cliInput) {
|
|
|
8316
8196
|
return writeSupabaseEnvFile(projectDir, backend, "");
|
|
8317
8197
|
}
|
|
8318
8198
|
let mode = setupMode;
|
|
8319
|
-
if (!mode)
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8199
|
+
if (!mode) {
|
|
8200
|
+
if (isSilent()) mode = "manual";
|
|
8201
|
+
else {
|
|
8202
|
+
const promptedMode = await select({
|
|
8203
|
+
message: "Supabase setup: choose mode",
|
|
8204
|
+
options: [{
|
|
8205
|
+
label: "Automatic",
|
|
8206
|
+
value: "auto",
|
|
8207
|
+
hint: "Automated setup with provider CLI, sets .env"
|
|
8208
|
+
}, {
|
|
8209
|
+
label: "Manual",
|
|
8210
|
+
value: "manual",
|
|
8211
|
+
hint: "Manual setup, add env vars yourself"
|
|
8212
|
+
}],
|
|
8213
|
+
initialValue: "auto"
|
|
8214
|
+
});
|
|
8215
|
+
if (isCancel(promptedMode)) return userCancelled("Operation cancelled");
|
|
8216
|
+
mode = types_exports.DbSetupModeSchema.parse(promptedMode);
|
|
8217
|
+
}
|
|
8336
8218
|
}
|
|
8337
8219
|
if (mode === "manual") {
|
|
8338
8220
|
displayManualSupabaseInstructions(targetApp);
|
|
@@ -8458,12 +8340,13 @@ async function selectTursoGroup() {
|
|
|
8458
8340
|
cliLog.info(`Using the only available group: ${pc.blue(groups[0].name)}`);
|
|
8459
8341
|
return Result.ok(groups[0].name);
|
|
8460
8342
|
}
|
|
8343
|
+
const groupOptions = groups.map((group) => ({
|
|
8344
|
+
value: group.name,
|
|
8345
|
+
label: `${group.name} (${group.locations})`
|
|
8346
|
+
}));
|
|
8461
8347
|
const selectedGroup = await select({
|
|
8462
8348
|
message: "Select a Turso database group:",
|
|
8463
|
-
options:
|
|
8464
|
-
value: group.name,
|
|
8465
|
-
label: `${group.name} (${group.locations})`
|
|
8466
|
-
}))
|
|
8349
|
+
options: groupOptions
|
|
8467
8350
|
});
|
|
8468
8351
|
if (isCancel(selectedGroup)) return userCancelled("Operation cancelled");
|
|
8469
8352
|
return Result.ok(selectedGroup);
|
|
@@ -8561,23 +8444,25 @@ async function setupTurso(config, cliInput) {
|
|
|
8561
8444
|
return Result.ok(void 0);
|
|
8562
8445
|
}
|
|
8563
8446
|
let mode = setupMode;
|
|
8564
|
-
if (!mode)
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8447
|
+
if (!mode) {
|
|
8448
|
+
if (isSilent()) mode = "manual";
|
|
8449
|
+
else {
|
|
8450
|
+
const promptedMode = await select({
|
|
8451
|
+
message: "Turso setup: choose mode",
|
|
8452
|
+
options: [{
|
|
8453
|
+
label: "Automatic",
|
|
8454
|
+
value: "auto",
|
|
8455
|
+
hint: "Automated setup with provider CLI, sets .env"
|
|
8456
|
+
}, {
|
|
8457
|
+
label: "Manual",
|
|
8458
|
+
value: "manual",
|
|
8459
|
+
hint: "Manual setup, add env vars yourself"
|
|
8460
|
+
}],
|
|
8461
|
+
initialValue: "auto"
|
|
8462
|
+
});
|
|
8463
|
+
if (isCancel(promptedMode)) return userCancelled("Operation cancelled");
|
|
8464
|
+
mode = types_exports.DbSetupModeSchema.parse(promptedMode);
|
|
8465
|
+
}
|
|
8581
8466
|
}
|
|
8582
8467
|
if (mode === "manual") {
|
|
8583
8468
|
const envResult = await writeEnvFile(projectDir, backend);
|
|
@@ -8586,7 +8471,7 @@ async function setupTurso(config, cliInput) {
|
|
|
8586
8471
|
return Result.ok(void 0);
|
|
8587
8472
|
}
|
|
8588
8473
|
setupSpinner.start("Checking Turso CLI availability...");
|
|
8589
|
-
const platform = os.platform();
|
|
8474
|
+
const platform = os$1.platform();
|
|
8590
8475
|
const isMac = platform === "darwin";
|
|
8591
8476
|
if (platform === "win32") {
|
|
8592
8477
|
setupSpinner.stop(pc.yellow("Turso setup not supported on Windows"));
|
|
@@ -8599,14 +8484,16 @@ async function setupTurso(config, cliInput) {
|
|
|
8599
8484
|
setupSpinner.stop("Turso CLI availability checked");
|
|
8600
8485
|
if (!await isTursoInstalled()) {
|
|
8601
8486
|
let shouldInstall = cliInput?.dbSetupOptions?.turso?.installCli;
|
|
8602
|
-
if (shouldInstall === void 0)
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8487
|
+
if (shouldInstall === void 0) {
|
|
8488
|
+
if (isSilent()) shouldInstall = false;
|
|
8489
|
+
else {
|
|
8490
|
+
const promptedInstall = await confirm({
|
|
8491
|
+
message: "Would you like to install Turso CLI?",
|
|
8492
|
+
initialValue: true
|
|
8493
|
+
});
|
|
8494
|
+
if (isCancel(promptedInstall)) return userCancelled("Operation cancelled");
|
|
8495
|
+
shouldInstall = z$1.boolean().parse(promptedInstall);
|
|
8496
|
+
}
|
|
8610
8497
|
}
|
|
8611
8498
|
if (!shouldInstall) {
|
|
8612
8499
|
const envResult = await writeEnvFile(projectDir, backend);
|
|
@@ -8641,11 +8528,13 @@ async function setupTurso(config, cliInput) {
|
|
|
8641
8528
|
}
|
|
8642
8529
|
}
|
|
8643
8530
|
let selectedGroup = cliInput?.dbSetupOptions?.turso?.groupName ?? config.dbSetupOptions?.turso?.groupName ?? null;
|
|
8644
|
-
if (!selectedGroup)
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8531
|
+
if (!selectedGroup) {
|
|
8532
|
+
if (isSilent()) selectedGroup = (await getTursoGroups())[0]?.name ?? null;
|
|
8533
|
+
else {
|
|
8534
|
+
const groupResult = await selectTursoGroup();
|
|
8535
|
+
if (groupResult.isErr()) return Result.err(groupResult.error);
|
|
8536
|
+
selectedGroup = groupResult.value;
|
|
8537
|
+
}
|
|
8649
8538
|
}
|
|
8650
8539
|
let suggestedName = cliInput?.dbSetupOptions?.turso?.databaseName ?? config.dbSetupOptions?.turso?.databaseName ?? path.basename(projectDir);
|
|
8651
8540
|
if (isSilent()) {
|
|
@@ -8810,7 +8699,7 @@ function getDockerInstallInstructions(platform, database) {
|
|
|
8810
8699
|
return `${pc.yellow("IMPORTANT:")} Docker required for ${databaseName}. Install for ${platformName}:\n${pc.blue(installUrl)}`;
|
|
8811
8700
|
}
|
|
8812
8701
|
async function getDockerStatus(database) {
|
|
8813
|
-
const platform = os.platform();
|
|
8702
|
+
const platform = os$1.platform();
|
|
8814
8703
|
if (!await isDockerInstalled()) return {
|
|
8815
8704
|
installed: false,
|
|
8816
8705
|
running: false,
|
|
@@ -8862,7 +8751,7 @@ async function displayPostInstallInstructions(config) {
|
|
|
8862
8751
|
const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
|
|
8863
8752
|
const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : "";
|
|
8864
8753
|
const clerkInstructions = config.auth === "clerk" ? getClerkInstructions(frontend || [], backend, api) : "";
|
|
8865
|
-
const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend, config.auth, frontend || []);
|
|
8754
|
+
const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend, config.auth, frontend || [], addons || []);
|
|
8866
8755
|
const hasWeb = frontend?.some((f) => types_exports.webFrontends.includes(f));
|
|
8867
8756
|
const hasNative = frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles");
|
|
8868
8757
|
const hasReactRouter = frontend?.includes("react-router");
|
|
@@ -8879,6 +8768,7 @@ async function displayPostInstallInstructions(config) {
|
|
|
8879
8768
|
if (database === "sqlite" && dbSetup !== "d1") output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:local\n${pc.dim(" (optional - starts local SQLite database)")}\n`;
|
|
8880
8769
|
const hasAlchemyD1 = dbSetup === "d1" && (serverDeploy === "cloudflare" || isBackendSelf && webDeploy === "cloudflare");
|
|
8881
8770
|
const hasWranglerLocalD1 = (0, types_exports.getLocalD1Owner)(config) === "wrangler";
|
|
8771
|
+
if (orm === "prisma" && !hasAlchemyD1) output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:generate\n`;
|
|
8882
8772
|
if (hasAlchemyD1 && orm !== "none") {
|
|
8883
8773
|
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:generate\n`;
|
|
8884
8774
|
if (orm === "prisma") output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:migrate\n`;
|
|
@@ -9077,7 +8967,7 @@ function getElectrobunInstructions(runCmd, frontend) {
|
|
|
9077
8967
|
return `\n${pc.bold("Desktop app with Electrobun:")}\n${pc.cyan("•")} Start desktop app with HMR (runs the whole stack): ${`${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 The first desktop command downloads Hutch and the Electrobun toolchain.\n Packaged builds bake the API URL from apps/web/.env, so point it at a reachable server first.\n See: https://framework.blackboard.sh/electrobun/${staticBuildNote ? `\n${staticBuildNote}` : ""}`;
|
|
9078
8968
|
}
|
|
9079
8969
|
function getPwaInstructions() {
|
|
9080
|
-
return `\n${pc.bold("PWA with React Router
|
|
8970
|
+
return `\n${pc.bold("PWA with React Router:")}\n${pc.yellow("NOTE:")} Verify PWA behavior with a production build on HTTPS or localhost.\n Offline navigation shows a precached fallback page.\n Server-rendered pages require a connection.`;
|
|
9081
8971
|
}
|
|
9082
8972
|
function getStarlightInstructions(runCmd) {
|
|
9083
8973
|
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`}`;
|
|
@@ -9159,21 +9049,26 @@ function getPolarInstructions(backend, packageManager) {
|
|
|
9159
9049
|
const envPath = backend === "self" ? "apps/web/.env" : "apps/server/.env";
|
|
9160
9050
|
return `${pc.bold("Polar Payments Setup:")}\n${pc.cyan("•")} Get access token & product ID from ${pc.underline("https://sandbox.polar.sh/")}\n${pc.cyan("•")} Set POLAR_ACCESS_TOKEN in ${envPath}`;
|
|
9161
9051
|
}
|
|
9162
|
-
function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend, auth, frontend) {
|
|
9052
|
+
function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend, auth, frontend, addons) {
|
|
9163
9053
|
const instructions = [];
|
|
9164
9054
|
const isBackendSelf = backend === "self";
|
|
9165
9055
|
const hasAlchemyWeb = (0, types_exports.isAlchemyDeployTarget)(webDeploy);
|
|
9166
9056
|
const hasAlchemyServer = (0, types_exports.isAlchemyDeployTarget)(serverDeploy);
|
|
9057
|
+
const hasAxiom = addons.includes("axiom");
|
|
9167
9058
|
const alchemyExec = runCmd === "npm run" ? "npx" : runCmd === "pnpm run" ? "pnpm exec" : "bunx";
|
|
9168
|
-
if (hasAlchemyWeb || hasAlchemyServer) {
|
|
9169
|
-
const targetParts = [
|
|
9170
|
-
|
|
9059
|
+
if (hasAlchemyWeb || hasAlchemyServer || hasAxiom) {
|
|
9060
|
+
const targetParts = [
|
|
9061
|
+
...hasAlchemyWeb ? [`web on ${webDeploy === "cloudflare" ? "Cloudflare" : "Prisma"}`] : [],
|
|
9062
|
+
...hasAlchemyServer && !isBackendSelf ? [`server on ${serverDeploy === "cloudflare" ? "Cloudflare" : "Prisma"}`] : [],
|
|
9063
|
+
...hasAxiom ? ["Axiom observability"] : []
|
|
9064
|
+
];
|
|
9065
|
+
const deployScript = !(hasAlchemyWeb || hasAlchemyServer) && (webDeploy === "vercel" || serverDeploy === "vercel" || webDeploy === "docker" || serverDeploy === "docker") ? "deploy:infra" : webDeploy === "vercel" ? "deploy:server" : serverDeploy === "vercel" ? "deploy:web" : "deploy";
|
|
9171
9066
|
const originSteps = [];
|
|
9172
9067
|
const hasWeb = frontend.some((value) => types_exports.webFrontends.includes(value));
|
|
9173
9068
|
if (!isBackendSelf && hasAlchemyServer && hasWeb) originSteps.push(`${pc.cyan("•")} Required after the first deploy: set CORS_ORIGIN in apps/server/.env to the deployed web origin, then deploy again`);
|
|
9174
9069
|
if (auth === "better-auth" && isBackendSelf && webDeploy === "prisma") originSteps.push(`${pc.cyan("•")} After the first deploy, set BETTER_AUTH_URL in apps/web/.env to the returned web URL, then deploy again`);
|
|
9175
9070
|
else if (auth === "better-auth" && !isBackendSelf && serverDeploy === "prisma") originSteps.push(`${pc.cyan("•")} After the first deploy, set BETTER_AUTH_URL in apps/server/.env to the returned server URL, then deploy again`);
|
|
9176
|
-
instructions.push(`${pc.bold(`Deploy with Alchemy (${targetParts.join(" + ")}):`)}\n${pc.cyan("•")} Configure provider
|
|
9071
|
+
instructions.push(`${pc.bold(`Deploy with Alchemy (${targetParts.join(" + ")}):`)}\n${pc.cyan("•")} Configure provider accounts: ${`cd packages/infra && ${alchemyExec} alchemy profile edit`}\n${hasAxiom && (webDeploy === "vercel" || serverDeploy === "vercel") ? `${pc.cyan("•")} For Axiom, deploy from packages/infra with alchemy deploy --stage preview or --stage production. Link Vercel first: ${`${runCmd} deploy:setup`}\n` : ""}${pc.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`${runCmd} ${deployScript}`}\n${originSteps.join("\n")}${originSteps.length > 0 ? "\n" : ""}${pc.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
|
|
9177
9072
|
}
|
|
9178
9073
|
if (webDeploy === "docker" || serverDeploy === "docker") {
|
|
9179
9074
|
const dockerTargets = webDeploy === "docker" && serverDeploy === "docker" ? "web + server" : webDeploy === "docker" ? "web" : "server";
|
|
@@ -9693,7 +9588,7 @@ const CreateVirtualInputSchema = types_exports.ProjectConfigSchema.omit({
|
|
|
9693
9588
|
projectDir: true,
|
|
9694
9589
|
relativePath: true
|
|
9695
9590
|
}).partial().strict();
|
|
9696
|
-
const
|
|
9591
|
+
const command = os.$meta({});
|
|
9697
9592
|
function getCliSchemaJson() {
|
|
9698
9593
|
return createCli({
|
|
9699
9594
|
router,
|
|
@@ -9710,8 +9605,8 @@ function getSchemaResult(name) {
|
|
|
9710
9605
|
if (name === "cli") return getCliSchemaJson();
|
|
9711
9606
|
return schemas[name];
|
|
9712
9607
|
}
|
|
9713
|
-
const router =
|
|
9714
|
-
create:
|
|
9608
|
+
const router = os.router({
|
|
9609
|
+
create: command.meta({
|
|
9715
9610
|
description: "Create a new Better-T-Stack project",
|
|
9716
9611
|
default: true,
|
|
9717
9612
|
negateBooleans: true
|
|
@@ -9743,7 +9638,7 @@ const router = t.router({
|
|
|
9743
9638
|
disableAnalytics: z.boolean().optional().default(false).describe("Disable analytics"),
|
|
9744
9639
|
manualDb: z.boolean().optional().default(false).describe("Skip automatic/manual database setup prompt and use manual setup"),
|
|
9745
9640
|
dbSetupOptions: types_exports.DbSetupOptionsSchema.optional().describe("Structured database setup options")
|
|
9746
|
-
})])).
|
|
9641
|
+
})])).handler(async ({ input }) => {
|
|
9747
9642
|
const [projectName, options] = input;
|
|
9748
9643
|
const result = await createProjectHandler({
|
|
9749
9644
|
projectName,
|
|
@@ -9751,10 +9646,10 @@ const router = t.router({
|
|
|
9751
9646
|
});
|
|
9752
9647
|
if (options.verbose) return result;
|
|
9753
9648
|
}),
|
|
9754
|
-
createJson:
|
|
9649
|
+
createJson: command.meta({
|
|
9755
9650
|
description: "Create a project from a raw JSON payload (agent-friendly)",
|
|
9756
9651
|
jsonInput: "always"
|
|
9757
|
-
}).input(types_exports.CreateInputSchema).
|
|
9652
|
+
}).input(types_exports.CreateInputSchema).handler(async ({ input }) => {
|
|
9758
9653
|
const result = await createProjectHandler(input, {
|
|
9759
9654
|
silent: true,
|
|
9760
9655
|
mode: "json"
|
|
@@ -9763,11 +9658,11 @@ const router = t.router({
|
|
|
9763
9658
|
if (!result.success) throw new CLIError({ message: result.error || "Unknown error occurred" });
|
|
9764
9659
|
return result;
|
|
9765
9660
|
}),
|
|
9766
|
-
schema:
|
|
9767
|
-
sponsors:
|
|
9768
|
-
docs:
|
|
9769
|
-
builder:
|
|
9770
|
-
add:
|
|
9661
|
+
schema: command.meta({ description: "Show runtime CLI and input schemas as JSON" }).input(z.object({ name: SchemaNameSchema.describe("Schema name to inspect") })).handler(({ input }) => getSchemaResult(input.name)),
|
|
9662
|
+
sponsors: command.meta({ description: "Show Better-T-Stack sponsors" }).handler(() => showSponsorsCommand()),
|
|
9663
|
+
docs: command.meta({ description: "Open Better-T-Stack documentation" }).handler(() => openDocsCommand()),
|
|
9664
|
+
builder: command.meta({ description: "Open the web-based stack builder" }).handler(() => openBuilderCommand()),
|
|
9665
|
+
add: command.meta({ description: "Add addons or a workspace package to an existing Better-T-Stack project" }).input(z.object({
|
|
9771
9666
|
addons: z.array(types_exports.AddonsSchema).optional().describe("Addons to add"),
|
|
9772
9667
|
package: types_exports.WorkspacePackageNameSchema.optional(),
|
|
9773
9668
|
install: z.boolean().optional().default(false).describe("Install dependencies after adding"),
|
|
@@ -9775,13 +9670,13 @@ const router = t.router({
|
|
|
9775
9670
|
projectDir: z.string().optional().describe("Project directory (defaults to current)"),
|
|
9776
9671
|
dryRun: z.boolean().optional().default(false).describe("Preview addon changes without writing files"),
|
|
9777
9672
|
disableAnalytics: z.boolean().optional().default(false).describe("Disable analytics")
|
|
9778
|
-
})).
|
|
9673
|
+
})).handler(async ({ input }) => {
|
|
9779
9674
|
await addHandler(input);
|
|
9780
9675
|
}),
|
|
9781
|
-
addJson:
|
|
9676
|
+
addJson: command.meta({
|
|
9782
9677
|
description: "Add addons or a workspace package from a raw JSON payload (agent-friendly)",
|
|
9783
9678
|
jsonInput: "always"
|
|
9784
|
-
}).input(types_exports.AddInputSchema).
|
|
9679
|
+
}).input(types_exports.AddInputSchema).handler(async ({ input }) => {
|
|
9785
9680
|
const result = await addHandler(input, {
|
|
9786
9681
|
silent: true,
|
|
9787
9682
|
mode: "json"
|
|
@@ -9790,11 +9685,11 @@ const router = t.router({
|
|
|
9790
9685
|
if (!result.success) throw new CLIError({ message: result.error || "Unknown error occurred" });
|
|
9791
9686
|
return result;
|
|
9792
9687
|
}),
|
|
9793
|
-
history:
|
|
9688
|
+
history: command.meta({ description: "Show project creation history" }).input(z.object({
|
|
9794
9689
|
limit: z.number().optional().default(10).describe("Number of entries to show"),
|
|
9795
9690
|
clear: z.boolean().optional().default(false).describe("Clear all history"),
|
|
9796
9691
|
json: z.boolean().optional().default(false).describe("Output as JSON")
|
|
9797
|
-
})).
|
|
9692
|
+
})).handler(async ({ input }) => {
|
|
9798
9693
|
await historyHandler(input);
|
|
9799
9694
|
})
|
|
9800
9695
|
});
|