create-kubojs 0.5.0 → 0.6.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 CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { _ as types_exports, g as setTelemetrySource, i as SchemaNameSchema, l as create, m as getSchemaResult, s as add, u as createBtsCli, v as getLatestCLIVersion } from "./src-QAuuh4xm.mjs";
2
+ import { _ as types_exports, g as setTelemetrySource, i as SchemaNameSchema, l as create, m as getSchemaResult, s as add, u as createBtsCli, v as getLatestCLIVersion } from "./src-CYAa_B_y.mjs";
3
3
  import z from "zod";
4
4
  import { format } from "node:util";
5
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
package/dist/index.d.mts CHANGED
@@ -216,6 +216,8 @@ declare const router: _$_trpc_server0.TRPCBuiltRouter<{
216
216
  installCli?: boolean | undefined;
217
217
  } | undefined;
218
218
  } | undefined;
219
+ layout?: string | undefined;
220
+ layoutCode?: string | undefined;
219
221
  }];
220
222
  output: CreateProjectResult | undefined;
221
223
  meta: TrpcCliMeta;
@@ -286,6 +288,24 @@ declare const router: _$_trpc_server0.TRPCBuiltRouter<{
286
288
  renderTitle?: boolean | undefined;
287
289
  disableAnalytics?: boolean | undefined;
288
290
  manualDb?: boolean | undefined;
291
+ layout?: {
292
+ version?: 1 | undefined;
293
+ preset?: "server-separated" | "standard" | "libs" | undefined;
294
+ dirs?: {
295
+ apps?: string | undefined;
296
+ packages?: string | undefined;
297
+ } | undefined;
298
+ apps?: {
299
+ web?: string | undefined;
300
+ server?: string | undefined;
301
+ native?: string | undefined;
302
+ desktop?: string | undefined;
303
+ docs?: string | undefined;
304
+ api?: string | undefined;
305
+ } | undefined;
306
+ packages?: Record<string, string> | undefined;
307
+ } | undefined;
308
+ layoutCode?: string | undefined;
289
309
  };
290
310
  output: CreateProjectResult;
291
311
  meta: TrpcCliMeta;
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { C as ProjectCreationError, S as DirectoryConflictError, T as ValidationError, a as TEMPLATE_COUNT, b as CompatibilityError, c as builder, d as createVirtual, f as docs, h as router, i as SchemaNameSchema, l as create, m as getSchemaResult, n as GeneratorError, o as VirtualFileSystem, p as generate, r as Result, s as add, t as EMBEDDED_TEMPLATES, u as createBtsCli, w as UserCancelledError, x as DatabaseSetupError, y as CLIError } from "./src-QAuuh4xm.mjs";
2
+ import { C as ProjectCreationError, S as DirectoryConflictError, T as ValidationError, a as TEMPLATE_COUNT, b as CompatibilityError, c as builder, d as createVirtual, f as docs, h as router, i as SchemaNameSchema, l as create, m as getSchemaResult, n as GeneratorError, o as VirtualFileSystem, p as generate, r as Result, s as add, t as EMBEDDED_TEMPLATES, u as createBtsCli, w as UserCancelledError, x as DatabaseSetupError, y as CLIError } from "./src-CYAa_B_y.mjs";
3
3
  export { CLIError, CompatibilityError, DatabaseSetupError, DirectoryConflictError, EMBEDDED_TEMPLATES, GeneratorError, ProjectCreationError, Result, SchemaNameSchema, TEMPLATE_COUNT, UserCancelledError, ValidationError, VirtualFileSystem, add, builder, create, createBtsCli, createVirtual, docs, generate, getSchemaResult, router };
@@ -12,7 +12,7 @@ import envPaths from "env-paths";
12
12
  import fs from "fs-extra";
13
13
  import { fileURLToPath } from "node:url";
14
14
  import { ADDON_COMPATIBILITY, COMMUNICATION_PRODUCT_NAMES, CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS, CONVEX_BETTER_AUTH_SUPPORTED_FRONTENDS, FRONTEND_VALUES, KUBO_CLI_TITLE, KUBO_TITLE_COLORS, KuboConfigFileSchema, KubojsConfigFileSchema, WEB_FRONTENDS_REQUIRING_ORPC, backendAllowsAiExample, backendAllowsApi, backendAllowsOnlyNoneApi, evaluate, getCommunicationCompatibilityIssue, getPaymentCompatibilityIssue, isCommunicationProvider, isWebFrontend, normalizeCommunication, normalizeCommunication as normalizeCommunication$2, normalizeLegacyKuboConfig, normalizeObservability, normalizePayments } from "@kubojs/types";
15
- import { EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, GeneratorError, GeneratorError as GeneratorError$1, KUBO_CONFIG_FILE, TEMPLATE_COUNT, VirtualFileSystem, VirtualFileSystem as VirtualFileSystem$1, applyAddonCatalog, dependencyVersionMap, generate, generate as generate$1, generateReproducibleCommand, getWebAppPath, isBinaryFile, processEnvVariables, processPackageConfigs, processTemplateString, resolveLayout, serializeKuboConfigFile } from "@kubojs/template-generator";
15
+ import { EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, GeneratorError, GeneratorError as GeneratorError$1, KUBO_CONFIG_FILE, TEMPLATE_COUNT, VirtualFileSystem, VirtualFileSystem as VirtualFileSystem$1, appDir, applyAddonCatalog, createLayoutResolver, dependencyVersionMap, generate, generate as generate$1, generateReproducibleCommand, getWebAppPath, isBinaryFile, pkgDir, processEnvVariables, processPackageConfigs, processTemplateString, relocateLayoutPaths, resolveLayout, serializeKuboConfigFile } from "@kubojs/template-generator";
16
16
  import { consola, createConsola } from "consola";
17
17
  import { AsyncLocalStorage } from "node:async_hooks";
18
18
  import gradient from "gradient-string";
@@ -21,6 +21,7 @@ import { writeSelected, writeTree } from "@kubojs/template-generator/fs-writer";
21
21
  import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, TextPrompt, isCancel as isCancel$1, isCancel as isCancel$2 } from "@clack/core";
22
22
  import { createJiti } from "jiti";
23
23
  import { parse } from "jsonc-parser";
24
+ import { randomUUID } from "node:crypto";
24
25
  import os from "node:os";
25
26
  import { format } from "oxfmt";
26
27
  import { Generator, configSchema } from "@tanstack/router-generator";
@@ -2476,6 +2477,15 @@ async function setupSkills(config) {
2476
2477
  return Result.ok(void 0);
2477
2478
  }
2478
2479
  //#endregion
2480
+ //#region src/utils/layout-paths.ts
2481
+ /**
2482
+ * App folder that holds the database `.env` (the web app for fullstack `self` backends,
2483
+ * otherwise the server app), resolved against the project layout.
2484
+ */
2485
+ function getDbEnvTargetApp(config) {
2486
+ return appDir(config, config.backend === "self" ? "web" : "server");
2487
+ }
2488
+ //#endregion
2479
2489
  //#region src/helpers/addons/tauri-setup.ts
2480
2490
  function getWebFrontend(frontend) {
2481
2491
  return frontend.find((value) => types_exports.desktopWebFrontends.includes(value));
@@ -2526,7 +2536,7 @@ async function setupTauri(config) {
2526
2536
  if (shouldSkipExternalCommands()) return Result.ok(void 0);
2527
2537
  const { packageManager, frontend, projectDir } = config;
2528
2538
  const s = createSpinner();
2529
- const clientPackageDir = path.join(projectDir, "apps/web");
2539
+ const clientPackageDir = path.join(projectDir, appDir(config, "web"));
2530
2540
  if (!await fs.pathExists(clientPackageDir)) return Result.ok(void 0);
2531
2541
  s.start("Setting up Tauri desktop app support...");
2532
2542
  const [command, ...args] = buildTauriInitArgs({
@@ -4182,7 +4192,58 @@ async function getProjectName(initialName) {
4182
4192
  return projectPath;
4183
4193
  }
4184
4194
  //#endregion
4195
+ //#region src/utils/telemetry.ts
4196
+ const TRUTHY = new Set([
4197
+ "1",
4198
+ "true",
4199
+ "yes"
4200
+ ]);
4201
+ function isTruthy(value) {
4202
+ return value !== void 0 && TRUTHY.has(value.toLowerCase());
4203
+ }
4204
+ /**
4205
+ * Returns true if telemetry/analytics should be enabled, false otherwise.
4206
+ *
4207
+ * Opt-out, checked in order:
4208
+ * - `KUBO_TELEMETRY_DISABLED=1` disables.
4209
+ * - `DO_NOT_TRACK=1` disables (https://consoledonottrack.com).
4210
+ * - Legacy `BTS_TELEMETRY_DISABLED=1` disables; legacy `BTS_TELEMETRY=0` disables.
4211
+ * - Otherwise enabled.
4212
+ */
4213
+ function isTelemetryEnabled(env = process.env) {
4214
+ if (isTruthy(env.KUBO_TELEMETRY_DISABLED)) return false;
4215
+ if (isTruthy(env.DO_NOT_TRACK)) return false;
4216
+ if (env.BTS_TELEMETRY_DISABLED !== void 0) return env.BTS_TELEMETRY_DISABLED !== "1";
4217
+ if (env.BTS_TELEMETRY !== void 0) return env.BTS_TELEMETRY === "1";
4218
+ return true;
4219
+ }
4220
+ /** Coding agents we can recognize from the environment they set for child processes. */
4221
+ const AGENT_ENV_MARKERS = [
4222
+ ["claude-code", "CLAUDECODE"],
4223
+ ["cursor", "CURSOR_AGENT"],
4224
+ ["cursor", "CURSOR_TRACE_ID"],
4225
+ ["codex", "CODEX_SANDBOX"],
4226
+ ["codex", "CODEX_MANAGED_BY_NPM"],
4227
+ ["gemini-cli", "GEMINI_CLI"],
4228
+ ["opencode", "OPENCODE"],
4229
+ ["amp", "AMP_CURRENT_THREAD_ID"],
4230
+ ["cline", "CLINE_ACTIVE"],
4231
+ ["replit", "REPL_ID"]
4232
+ ];
4233
+ function detectAgent(env = process.env) {
4234
+ const match = AGENT_ENV_MARKERS.find(([, envVar]) => Boolean(env[envVar]));
4235
+ if (match) return match[0];
4236
+ return env.AI_AGENT ? "other" : void 0;
4237
+ }
4238
+ function isCI(env = process.env) {
4239
+ return isTruthy(env.CI) || Boolean(env.GITHUB_ACTIONS || env.GITLAB_CI || env.BUILDKITE || env.CIRCLECI);
4240
+ }
4241
+ //#endregion
4185
4242
  //#region src/utils/analytics.ts
4243
+ const CONVEX_INGEST_URL = "https://moonlit-seal-677.convex.site/api/analytics/ingest";
4244
+ const SEND_TIMEOUT_MS = 2e3;
4245
+ const TELEMETRY_FILE = "telemetry.json";
4246
+ const TELEMETRY_DOCS_URL = "https://kubojs.dev/docs/analytics";
4186
4247
  let telemetrySource = "cli";
4187
4248
  /** Set once per process by entry points that are not the interactive terminal (e.g. MCP). */
4188
4249
  function setTelemetrySource(source) {
@@ -4192,7 +4253,102 @@ function setTelemetrySource(source) {
4192
4253
  function markProgrammaticTelemetrySource() {
4193
4254
  if (telemetrySource === "cli") telemetrySource = "api";
4194
4255
  }
4195
- async function trackCreateEvent(event, disableAnalytics = false) {}
4256
+ function getTelemetryFilePath() {
4257
+ return path.join(envPaths("kubojs", { suffix: "" }).config, TELEMETRY_FILE);
4258
+ }
4259
+ function isTelemetryState(value) {
4260
+ return typeof value === "object" && value !== null && "installId" in value && typeof value.installId === "string" && "noticeShown" in value && typeof value.noticeShown === "boolean";
4261
+ }
4262
+ /**
4263
+ * Reads the anonymous install id (a random UUID, not derived from the machine or user),
4264
+ * creating it on first use. Returns undefined if the config dir is not writable.
4265
+ */
4266
+ async function loadTelemetryState() {
4267
+ const filePath = getTelemetryFilePath();
4268
+ const existing = await Result.tryPromise({
4269
+ try: async () => fs.readJson(filePath),
4270
+ catch: () => void 0
4271
+ });
4272
+ if (existing.isOk() && isTelemetryState(existing.value)) return existing.value;
4273
+ const state = {
4274
+ installId: randomUUID(),
4275
+ noticeShown: false
4276
+ };
4277
+ return (await Result.tryPromise({
4278
+ try: async () => {
4279
+ await fs.ensureDir(path.dirname(filePath));
4280
+ await fs.writeJson(filePath, state, { spaces: 2 });
4281
+ },
4282
+ catch: () => void 0
4283
+ })).isOk() ? state : void 0;
4284
+ }
4285
+ async function markNoticeShown(state) {
4286
+ await Result.tryPromise({
4287
+ try: () => fs.writeJson(getTelemetryFilePath(), {
4288
+ ...state,
4289
+ noticeShown: true
4290
+ }, { spaces: 2 }),
4291
+ catch: () => void 0
4292
+ });
4293
+ }
4294
+ function showTelemetryNotice() {
4295
+ log.info(pc.dim(`kubojs collects anonymous usage data (stack choices, CLI version, OS) to guide development.
4296
+ No project names, paths or personal data. Opt out with KUBO_TELEMETRY_DISABLED=1.
4297
+ Details: ${TELEMETRY_DOCS_URL}`));
4298
+ }
4299
+ async function sendConvexEvent(payload) {
4300
+ await Result.tryPromise({
4301
+ try: () => fetch(CONVEX_INGEST_URL, {
4302
+ method: "POST",
4303
+ headers: { "Content-Type": "application/json" },
4304
+ body: JSON.stringify(payload),
4305
+ signal: AbortSignal.timeout(SEND_TIMEOUT_MS)
4306
+ }),
4307
+ catch: () => void 0
4308
+ });
4309
+ }
4310
+ function getStackPayload(config) {
4311
+ return {
4312
+ database: config.database,
4313
+ orm: config.orm,
4314
+ backend: config.backend,
4315
+ runtime: config.runtime,
4316
+ frontend: config.frontend,
4317
+ addons: config.addons,
4318
+ examples: config.examples,
4319
+ auth: config.auth,
4320
+ payments: config.payments,
4321
+ git: config.git,
4322
+ packageManager: config.packageManager,
4323
+ install: config.install,
4324
+ dbSetup: config.dbSetup,
4325
+ api: config.api,
4326
+ webDeploy: config.webDeploy,
4327
+ serverDeploy: config.serverDeploy
4328
+ };
4329
+ }
4330
+ async function trackCreateEvent(event, disableAnalytics = false) {
4331
+ if (!isTelemetryEnabled() || disableAnalytics) return;
4332
+ const state = await loadTelemetryState();
4333
+ if (state && !state.noticeShown && !isSilent()) {
4334
+ showTelemetryNotice();
4335
+ await markNoticeShown(state);
4336
+ }
4337
+ await sendConvexEvent({
4338
+ ...event.config ? getStackPayload(event.config) : {},
4339
+ install_id: state?.installId,
4340
+ source: telemetrySource,
4341
+ mode: event.mode,
4342
+ agent: detectAgent(),
4343
+ ci: isCI(),
4344
+ outcome: event.outcome,
4345
+ error_category: event.errorCategory,
4346
+ duration_ms: event.durationMs,
4347
+ cli_version: getLatestCLIVersion(),
4348
+ node_version: process.version,
4349
+ platform: process.platform
4350
+ });
4351
+ }
4196
4352
  //#endregion
4197
4353
  //#region src/utils/display-config.ts
4198
4354
  function displayConfig(config) {
@@ -4246,6 +4402,90 @@ function displayConfig(config) {
4246
4402
  return configDisplay.join("\n");
4247
4403
  }
4248
4404
  //#endregion
4405
+ //#region src/utils/layout-input.ts
4406
+ function formatZodIssues(error) {
4407
+ return error.issues.map((issue) => `${issue.path.length > 0 ? issue.path.join(".") : "layout"}: ${issue.message}`).join("; ");
4408
+ }
4409
+ function isLayoutPreset(value) {
4410
+ return types_exports.LAYOUT_PRESET_IDS.some((preset) => preset === value);
4411
+ }
4412
+ /**
4413
+ * Parses the `--layout` flag: a preset name (`standard`, `libs`) or a JSON layout object,
4414
+ * e.g. `--layout '{"preset":"libs","apps":{"web":"web"}}'`.
4415
+ */
4416
+ function parseLayoutFlag(value) {
4417
+ const trimmed = value.trim();
4418
+ if (isLayoutPreset(trimmed)) return Result.ok({
4419
+ version: 1,
4420
+ preset: trimmed
4421
+ });
4422
+ if (!trimmed.startsWith("{")) return Result.err(new CLIError({ message: `Invalid --layout "${value}". Use a preset (${types_exports.LAYOUT_PRESET_IDS.join(", ")}) or a JSON layout object.` }));
4423
+ const json = Result.try({
4424
+ try: () => JSON.parse(trimmed),
4425
+ catch: (cause) => new CLIError({
4426
+ message: "Invalid --layout JSON",
4427
+ cause
4428
+ })
4429
+ });
4430
+ if (json.isErr()) return Result.err(json.error);
4431
+ const parsed = types_exports.LayoutConfigSchema.safeParse(json.value);
4432
+ if (!parsed.success) return Result.err(new CLIError({ message: `Invalid --layout: ${formatZodIssues(parsed.error)}` }));
4433
+ return Result.ok(parsed.data);
4434
+ }
4435
+ /**
4436
+ * Validates a layout before anything is written: schema shape plus folder conflicts
4437
+ * (collisions, case-only collisions, nested folders).
4438
+ */
4439
+ function validateCreateLayout(layout) {
4440
+ if (layout === void 0) return Result.ok(void 0);
4441
+ const parsed = types_exports.LayoutConfigSchema.safeParse(layout);
4442
+ if (!parsed.success) return Result.err(new CLIError({ message: `Invalid layout: ${formatZodIssues(parsed.error)}` }));
4443
+ const resolver = createLayoutResolver(parsed.data);
4444
+ if (resolver.isErr()) return Result.err(new CLIError({
4445
+ message: `Invalid layout: ${resolver.error.message}`,
4446
+ cause: resolver.error
4447
+ }));
4448
+ return Result.ok(parsed.data);
4449
+ }
4450
+ const DEFAULT_KUBO_API_URL = "https://kubojs.dev";
4451
+ const REDEEM_TIMEOUT_MS = 1e4;
4452
+ function getRedeemUrl() {
4453
+ return `${process.env.KUBO_API_URL?.replace(/\/$/, "") || DEFAULT_KUBO_API_URL}/api/layout/redeem`;
4454
+ }
4455
+ /**
4456
+ * Exchanges a single-use code from kubojs.dev for the layout saved in the account. The code
4457
+ * is consumed on success; it is never logged or sent to telemetry.
4458
+ */
4459
+ async function redeemLayoutCode(code) {
4460
+ const response = await Result.tryPromise({
4461
+ try: () => fetch(getRedeemUrl(), {
4462
+ method: "POST",
4463
+ headers: { "Content-Type": "application/json" },
4464
+ body: JSON.stringify({ code }),
4465
+ signal: AbortSignal.timeout(REDEEM_TIMEOUT_MS)
4466
+ }),
4467
+ catch: (cause) => new CLIError({
4468
+ message: "Could not reach kubojs.dev to redeem the layout code. Check your connection.",
4469
+ cause
4470
+ })
4471
+ });
4472
+ if (response.isErr()) return Result.err(response.error);
4473
+ if (response.value.status === 400 || response.value.status === 404) return Result.err(new CLIError({ message: "This layout code is invalid, expired or was already used. Generate a new one in the Layout tab at kubojs.dev/new." }));
4474
+ if (!response.value.ok) return Result.err(new CLIError({ message: `Layout code redemption failed (HTTP ${response.value.status}).` }));
4475
+ const body = await Result.tryPromise({
4476
+ try: async () => response.value.json(),
4477
+ catch: (cause) => new CLIError({
4478
+ message: "Invalid response while redeeming the layout code",
4479
+ cause
4480
+ })
4481
+ });
4482
+ if (body.isErr()) return Result.err(body.error);
4483
+ const layout = typeof body.value === "object" && body.value !== null && "layout" in body.value ? body.value.layout : void 0;
4484
+ const parsed = types_exports.LayoutConfigSchema.safeParse(layout);
4485
+ if (!parsed.success) return Result.err(new CLIError({ message: "The redeemed layout is not valid." }));
4486
+ return Result.ok(parsed.data);
4487
+ }
4488
+ //#endregion
4249
4489
  //#region src/utils/project-directory.ts
4250
4490
  async function handleDirectoryConflict(currentPathInput) {
4251
4491
  while (true) {
@@ -4659,7 +4899,7 @@ async function generateRouteTreeIfNeeded(projectDir, config) {
4659
4899
  if (!needsRouteTree(config.frontend)) return Result.ok(void 0);
4660
4900
  const target = resolveRouteTreeTarget(config.frontend);
4661
4901
  if (!target) return Result.ok(void 0);
4662
- const webAppDir = path.join(projectDir, "apps/web");
4902
+ const webAppDir = path.join(projectDir, appDir(config, "web"));
4663
4903
  const routesDir = path.join(webAppDir, "src/routes");
4664
4904
  if (!await fs.pathExists(routesDir)) return Result.ok(void 0);
4665
4905
  return Result.tryPromise({
@@ -4722,7 +4962,7 @@ async function setupCloudflareD1(config) {
4722
4962
  if (!(orm === "prisma" && (serverDeploy === "cloudflare" || backend === "self" && webDeploy === "cloudflare"))) return Result.ok(void 0);
4723
4963
  return Result.tryPromise({
4724
4964
  try: async () => {
4725
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
4965
+ const targetApp = getDbEnvTargetApp(config);
4726
4966
  await addEnvVariablesToFile(path.join(projectDir, targetApp, ".env"), [{
4727
4967
  key: "DATABASE_URL",
4728
4968
  value: `file:${path.join(projectDir, targetApp, "local.db")}`,
@@ -4730,7 +4970,7 @@ async function setupCloudflareD1(config) {
4730
4970
  }]);
4731
4971
  await addPackageDependency({
4732
4972
  dependencies: ["@prisma/adapter-d1"],
4733
- projectDir: path.join(projectDir, backend === "self" ? "apps/web" : "apps/server")
4973
+ projectDir: path.join(projectDir, getDbEnvTargetApp(config))
4734
4974
  });
4735
4975
  },
4736
4976
  catch: (e) => new DatabaseSetupError({
@@ -4743,11 +4983,11 @@ async function setupCloudflareD1(config) {
4743
4983
  //#endregion
4744
4984
  //#region src/helpers/database-providers/docker-compose-setup.ts
4745
4985
  async function setupDockerCompose(config) {
4746
- const { database, projectDir, projectName, backend } = config;
4986
+ const { database, projectDir, projectName } = config;
4747
4987
  if (database === "none" || database === "sqlite") return Result.ok(void 0);
4748
4988
  const result = await Result.tryPromise({
4749
4989
  try: async () => {
4750
- await writeEnvFile$4(projectDir, database, projectName, backend);
4990
+ await writeEnvFile$4(projectDir, database, projectName, config);
4751
4991
  },
4752
4992
  catch: (e) => new DatabaseSetupError({
4753
4993
  provider: "docker-compose",
@@ -4757,8 +4997,8 @@ async function setupDockerCompose(config) {
4757
4997
  });
4758
4998
  return result.isErr() ? result : Result.ok(void 0);
4759
4999
  }
4760
- async function writeEnvFile$4(projectDir, database, projectName, backend) {
4761
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
5000
+ async function writeEnvFile$4(projectDir, database, projectName, project) {
5001
+ const targetApp = getDbEnvTargetApp(project);
4762
5002
  await addEnvVariablesToFile(path.join(projectDir, targetApp, ".env"), [{
4763
5003
  key: "DATABASE_URL",
4764
5004
  value: getDatabaseUrl(database, projectName),
@@ -4859,10 +5099,10 @@ async function initMongoDBAtlas(serverDir) {
4859
5099
  }
4860
5100
  return Result.ok({ connectionString });
4861
5101
  }
4862
- async function writeEnvFile$3(projectDir, backend, config) {
5102
+ async function writeEnvFile$3(projectDir, project, config) {
4863
5103
  return Result.tryPromise({
4864
5104
  try: async () => {
4865
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
5105
+ const targetApp = getDbEnvTargetApp(project);
4866
5106
  await addEnvVariablesToFile(path.join(projectDir, targetApp, ".env"), [{
4867
5107
  key: "DATABASE_URL",
4868
5108
  value: config?.connectionString ?? "mongodb://localhost:27017/mydb",
@@ -4894,12 +5134,12 @@ ${pc.green("MongoDB Atlas Manual Setup Instructions:")}
4894
5134
  `);
4895
5135
  }
4896
5136
  async function setupMongoDBAtlas(config, cliInput) {
4897
- const { projectDir, backend } = config;
5137
+ const { projectDir } = config;
4898
5138
  const setupMode = resolveDbSetupMode("mongodb-atlas", {
4899
5139
  manualDb: cliInput?.manualDb,
4900
5140
  dbSetupOptions: cliInput?.dbSetupOptions ?? config.dbSetupOptions
4901
5141
  });
4902
- const serverDir = path.join(projectDir, "packages/db");
5142
+ const serverDir = path.join(projectDir, pkgDir(config, "db"));
4903
5143
  const ensureDirResult = await Result.tryPromise({
4904
5144
  try: () => fs.ensureDir(serverDir),
4905
5145
  catch: (e) => new DatabaseSetupError({
@@ -4911,7 +5151,7 @@ async function setupMongoDBAtlas(config, cliInput) {
4911
5151
  if (ensureDirResult.isErr()) return ensureDirResult;
4912
5152
  if (setupMode === "manual") {
4913
5153
  cliLog.info("MongoDB Atlas manual setup selected");
4914
- const envResult = await writeEnvFile$3(projectDir, backend);
5154
+ const envResult = await writeEnvFile$3(projectDir, config);
4915
5155
  if (envResult.isErr()) return envResult;
4916
5156
  displayManualSetupInstructions$3();
4917
5157
  return Result.ok(void 0);
@@ -4920,7 +5160,7 @@ async function setupMongoDBAtlas(config, cliInput) {
4920
5160
  if (!mode) {
4921
5161
  if (isSilent()) {
4922
5162
  cliLog.warn(pc.yellow("MongoDB Atlas automatic setup requires interactive input. Falling back to manual setup."));
4923
- const envResult = await writeEnvFile$3(projectDir, backend);
5163
+ const envResult = await writeEnvFile$3(projectDir, config);
4924
5164
  if (envResult.isErr()) return envResult;
4925
5165
  displayManualSetupInstructions$3();
4926
5166
  return Result.ok(void 0);
@@ -4943,21 +5183,21 @@ async function setupMongoDBAtlas(config, cliInput) {
4943
5183
  }
4944
5184
  if (mode === "manual") {
4945
5185
  cliLog.info("MongoDB Atlas manual setup selected");
4946
- const envResult = await writeEnvFile$3(projectDir, backend);
5186
+ const envResult = await writeEnvFile$3(projectDir, config);
4947
5187
  if (envResult.isErr()) return envResult;
4948
5188
  displayManualSetupInstructions$3();
4949
5189
  return Result.ok(void 0);
4950
5190
  }
4951
5191
  const mongoConfigResult = await initMongoDBAtlas(serverDir);
4952
5192
  if (mongoConfigResult.isOk()) {
4953
- const envResult = await writeEnvFile$3(projectDir, backend, mongoConfigResult.value);
5193
+ const envResult = await writeEnvFile$3(projectDir, config, mongoConfigResult.value);
4954
5194
  if (envResult.isErr()) return envResult;
4955
5195
  cliLog.success(pc.green("MongoDB Atlas setup complete! Connection saved to .env file."));
4956
5196
  return Result.ok(void 0);
4957
5197
  }
4958
5198
  if (UserCancelledError.is(mongoConfigResult.error)) return Result.err(mongoConfigResult.error);
4959
5199
  cliLog.warn(pc.yellow("Falling back to local MongoDB configuration"));
4960
- const envResult = await writeEnvFile$3(projectDir, backend);
5200
+ const envResult = await writeEnvFile$3(projectDir, config);
4961
5201
  if (envResult.isErr()) return envResult;
4962
5202
  displayManualSetupInstructions$3();
4963
5203
  return Result.ok(void 0);
@@ -5044,10 +5284,10 @@ async function createNeonProject(projectName, regionId, packageManager) {
5044
5284
  }
5045
5285
  return databaseSetupError("neon", "Failed to extract connection information from Neon response");
5046
5286
  }
5047
- async function writeEnvFile$2(projectDir, backend, config) {
5287
+ async function writeEnvFile$2(projectDir, project, config) {
5048
5288
  return Result.tryPromise({
5049
5289
  try: async () => {
5050
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
5290
+ const targetApp = getDbEnvTargetApp(project);
5051
5291
  await addEnvVariablesToFile(path.join(projectDir, targetApp, ".env"), [{
5052
5292
  key: "DATABASE_URL",
5053
5293
  value: config?.connectionString ?? "postgresql://postgres:postgres@localhost:5432/mydb?schema=public",
@@ -5061,10 +5301,10 @@ async function writeEnvFile$2(projectDir, backend, config) {
5061
5301
  })
5062
5302
  });
5063
5303
  }
5064
- async function setupWithNeonDb(projectDir, packageManager, backend) {
5304
+ async function setupWithNeonDb(projectDir, packageManager, project) {
5065
5305
  const s = createSpinner();
5066
5306
  s.start("Creating Neon database using neon-new...");
5067
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
5307
+ const targetApp = getDbEnvTargetApp(project);
5068
5308
  const targetDir = path.join(projectDir, targetApp);
5069
5309
  const ensureDirResult = await Result.tryPromise({
5070
5310
  try: () => fs.ensureDir(targetDir),
@@ -5105,20 +5345,20 @@ function displayManualSetupInstructions$2(target) {
5105
5345
  DATABASE_URL="your_connection_string"`);
5106
5346
  }
5107
5347
  async function setupNeonPostgres(config, cliInput) {
5108
- const { packageManager, projectDir, backend } = config;
5348
+ const { packageManager, projectDir } = config;
5109
5349
  const setupMode = resolveDbSetupMode("neon", {
5110
5350
  manualDb: cliInput?.manualDb,
5111
5351
  dbSetupOptions: cliInput?.dbSetupOptions ?? config.dbSetupOptions
5112
5352
  });
5113
- const target = backend === "self" ? "apps/web" : "apps/server";
5353
+ const target = getDbEnvTargetApp(config);
5114
5354
  if (shouldSkipExternalCommands()) {
5115
- const envResult = await writeEnvFile$2(projectDir, backend);
5355
+ const envResult = await writeEnvFile$2(projectDir, config);
5116
5356
  if (envResult.isErr()) return envResult;
5117
5357
  displayManualSetupInstructions$2(target);
5118
5358
  return Result.ok(void 0);
5119
5359
  }
5120
5360
  if (setupMode === "manual") {
5121
- const envResult = await writeEnvFile$2(projectDir, backend);
5361
+ const envResult = await writeEnvFile$2(projectDir, config);
5122
5362
  if (envResult.isErr()) return envResult;
5123
5363
  displayManualSetupInstructions$2(target);
5124
5364
  return Result.ok(void 0);
@@ -5143,7 +5383,7 @@ async function setupNeonPostgres(config, cliInput) {
5143
5383
  selectedMode = promptedMode;
5144
5384
  }
5145
5385
  if (selectedMode === "manual") {
5146
- const envResult = await writeEnvFile$2(projectDir, backend);
5386
+ const envResult = await writeEnvFile$2(projectDir, config);
5147
5387
  if (envResult.isErr()) return envResult;
5148
5388
  displayManualSetupInstructions$2(target);
5149
5389
  return Result.ok(void 0);
@@ -5168,10 +5408,10 @@ async function setupNeonPostgres(config, cliInput) {
5168
5408
  setupMethod = promptedSetupMethod;
5169
5409
  }
5170
5410
  if (setupMethod === "neondb") {
5171
- const neonDbResult = await setupWithNeonDb(projectDir, packageManager, backend);
5411
+ const neonDbResult = await setupWithNeonDb(projectDir, packageManager, config);
5172
5412
  if (neonDbResult.isErr()) {
5173
5413
  cliLog.error(pc.red(neonDbResult.error.message));
5174
- const envResult = await writeEnvFile$2(projectDir, backend);
5414
+ const envResult = await writeEnvFile$2(projectDir, config);
5175
5415
  if (envResult.isErr()) return envResult;
5176
5416
  displayManualSetupInstructions$2(target);
5177
5417
  return Result.ok(void 0);
@@ -5205,14 +5445,14 @@ async function setupNeonPostgres(config, cliInput) {
5205
5445
  const neonConfigResult = await createNeonProject(projectName, regionId, packageManager);
5206
5446
  if (neonConfigResult.isErr()) {
5207
5447
  cliLog.error(pc.red(neonConfigResult.error.message));
5208
- const envResult = await writeEnvFile$2(projectDir, backend);
5448
+ const envResult = await writeEnvFile$2(projectDir, config);
5209
5449
  if (envResult.isErr()) return envResult;
5210
5450
  displayManualSetupInstructions$2(target);
5211
5451
  return Result.ok(void 0);
5212
5452
  }
5213
5453
  const finalSpinner = createSpinner();
5214
5454
  finalSpinner.start("Configuring database connection");
5215
- const envResult = await writeEnvFile$2(projectDir, backend, neonConfigResult.value);
5455
+ const envResult = await writeEnvFile$2(projectDir, config, neonConfigResult.value);
5216
5456
  if (envResult.isErr()) {
5217
5457
  finalSpinner.stop(pc.red("Failed to configure database connection"));
5218
5458
  return envResult;
@@ -5224,11 +5464,11 @@ async function setupNeonPostgres(config, cliInput) {
5224
5464
  //#endregion
5225
5465
  //#region src/helpers/database-providers/planetscale-setup.ts
5226
5466
  async function setupPlanetScale(config) {
5227
- const { projectDir, database, orm, backend } = config;
5467
+ const { projectDir, database, orm } = config;
5228
5468
  if (!["mysql", "postgres"].includes(database)) return Result.ok(void 0);
5229
5469
  return Result.tryPromise({
5230
5470
  try: async () => {
5231
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
5471
+ const targetApp = getDbEnvTargetApp(config);
5232
5472
  const envPath = path.join(projectDir, targetApp, ".env");
5233
5473
  if (database === "mysql" && orm === "drizzle") {
5234
5474
  const variables = [
@@ -5376,10 +5616,10 @@ async function setupWithCreateDb(serverDir, packageManager, regionId) {
5376
5616
  claimUrl: createDbResponse.claimUrl
5377
5617
  });
5378
5618
  }
5379
- async function writeEnvFile$1(projectDir, backend, config) {
5619
+ async function writeEnvFile$1(projectDir, project, config) {
5380
5620
  return Result.tryPromise({
5381
5621
  try: async () => {
5382
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
5622
+ const targetApp = getDbEnvTargetApp(project);
5383
5623
  const envPath = path.join(projectDir, targetApp, ".env");
5384
5624
  const variables = [{
5385
5625
  key: "DATABASE_URL",
@@ -5411,13 +5651,13 @@ function displayManualSetupInstructions$1(target) {
5411
5651
  DATABASE_URL="your_database_url"`);
5412
5652
  }
5413
5653
  async function setupPrismaPostgres(config, cliInput) {
5414
- const { packageManager, projectDir, backend } = config;
5654
+ const { packageManager, projectDir } = config;
5415
5655
  const setupMode = resolveDbSetupMode("prisma-postgres", {
5416
5656
  manualDb: cliInput?.manualDb,
5417
5657
  dbSetupOptions: cliInput?.dbSetupOptions ?? config.dbSetupOptions
5418
5658
  });
5419
- const dbDir = path.join(projectDir, "packages/db");
5420
- const target = backend === "self" ? "apps/web" : "apps/server";
5659
+ const dbDir = path.join(projectDir, pkgDir(config, "db"));
5660
+ const target = getDbEnvTargetApp(config);
5421
5661
  const ensureDirResult = await Result.tryPromise({
5422
5662
  try: () => fs.ensureDir(dbDir),
5423
5663
  catch: (e) => new DatabaseSetupError({
@@ -5428,7 +5668,7 @@ async function setupPrismaPostgres(config, cliInput) {
5428
5668
  });
5429
5669
  if (ensureDirResult.isErr()) return ensureDirResult;
5430
5670
  if (setupMode === "manual") {
5431
- const envResult = await writeEnvFile$1(projectDir, backend);
5671
+ const envResult = await writeEnvFile$1(projectDir, config);
5432
5672
  if (envResult.isErr()) return envResult;
5433
5673
  displayManualSetupInstructions$1(target);
5434
5674
  return Result.ok(void 0);
@@ -5453,7 +5693,7 @@ async function setupPrismaPostgres(config, cliInput) {
5453
5693
  selectedSetupMode = promptedSetupMode;
5454
5694
  }
5455
5695
  if (selectedSetupMode === "manual") {
5456
- const envResult = await writeEnvFile$1(projectDir, backend);
5696
+ const envResult = await writeEnvFile$1(projectDir, config);
5457
5697
  if (envResult.isErr()) return envResult;
5458
5698
  displayManualSetupInstructions$1(target);
5459
5699
  return Result.ok(void 0);
@@ -5462,13 +5702,13 @@ async function setupPrismaPostgres(config, cliInput) {
5462
5702
  if (prismaConfigResult.isErr()) {
5463
5703
  if (UserCancelledError.is(prismaConfigResult.error)) return Result.err(prismaConfigResult.error);
5464
5704
  cliLog.error(pc.red(prismaConfigResult.error.message));
5465
- const envResult = await writeEnvFile$1(projectDir, backend);
5705
+ const envResult = await writeEnvFile$1(projectDir, config);
5466
5706
  if (envResult.isErr()) return envResult;
5467
5707
  displayManualSetupInstructions$1(target);
5468
5708
  cliLog.info("Setup completed with manual configuration required.");
5469
5709
  return Result.ok(void 0);
5470
5710
  }
5471
- const envResult = await writeEnvFile$1(projectDir, backend, prismaConfigResult.value);
5711
+ const envResult = await writeEnvFile$1(projectDir, config, prismaConfigResult.value);
5472
5712
  if (envResult.isErr()) return envResult;
5473
5713
  cliLog.success(pc.green("Prisma Postgres database configured successfully!"));
5474
5714
  if (prismaConfigResult.value.claimUrl) cliLog.info(pc.blue(`Claim URL saved to .env: ${prismaConfigResult.value.claimUrl}`));
@@ -5476,10 +5716,10 @@ async function setupPrismaPostgres(config, cliInput) {
5476
5716
  }
5477
5717
  //#endregion
5478
5718
  //#region src/helpers/database-providers/supabase-setup.ts
5479
- async function writeSupabaseEnvFile(projectDir, backend, databaseUrl) {
5719
+ async function writeSupabaseEnvFile(projectDir, project, databaseUrl) {
5480
5720
  return Result.tryPromise({
5481
5721
  try: async () => {
5482
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
5722
+ const targetApp = getDbEnvTargetApp(project);
5483
5723
  const envPath = path.join(projectDir, targetApp, ".env");
5484
5724
  const dbUrlToUse = databaseUrl || "postgresql://postgres:postgres@127.0.0.1:54322/postgres";
5485
5725
  await addEnvVariablesToFile(envPath, [{
@@ -5563,13 +5803,13 @@ ${pc.dim(output)}` : ""}
5563
5803
  ${pc.gray("DATABASE_URL=\"your_supabase_db_url\"")}`);
5564
5804
  }
5565
5805
  async function setupSupabase(config, cliInput) {
5566
- const { projectDir, packageManager, backend } = config;
5567
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
5806
+ const { projectDir, packageManager } = config;
5807
+ const targetApp = getDbEnvTargetApp(config);
5568
5808
  const setupMode = resolveDbSetupMode("supabase", {
5569
5809
  manualDb: cliInput?.manualDb,
5570
5810
  dbSetupOptions: cliInput?.dbSetupOptions ?? config.dbSetupOptions
5571
5811
  });
5572
- const serverDir = path.join(projectDir, "packages", "db");
5812
+ const serverDir = path.join(projectDir, pkgDir(config, "db"));
5573
5813
  const ensureDirResult = await Result.tryPromise({
5574
5814
  try: () => fs.ensureDir(serverDir),
5575
5815
  catch: (e) => new DatabaseSetupError({
@@ -5581,7 +5821,7 @@ async function setupSupabase(config, cliInput) {
5581
5821
  if (ensureDirResult.isErr()) return ensureDirResult;
5582
5822
  if (setupMode === "manual") {
5583
5823
  displayManualSupabaseInstructions(targetApp);
5584
- return writeSupabaseEnvFile(projectDir, backend, "");
5824
+ return writeSupabaseEnvFile(projectDir, config, "");
5585
5825
  }
5586
5826
  let mode = setupMode;
5587
5827
  if (!mode) if (isSilent()) mode = "manual";
@@ -5604,24 +5844,24 @@ async function setupSupabase(config, cliInput) {
5604
5844
  }
5605
5845
  if (mode === "manual") {
5606
5846
  displayManualSupabaseInstructions(targetApp);
5607
- return writeSupabaseEnvFile(projectDir, backend, "");
5847
+ return writeSupabaseEnvFile(projectDir, config, "");
5608
5848
  }
5609
5849
  const initResult = await initializeSupabase(serverDir, packageManager);
5610
5850
  if (initResult.isErr()) {
5611
5851
  cliLog.error(pc.red(initResult.error.message));
5612
5852
  displayManualSupabaseInstructions(targetApp);
5613
- return writeSupabaseEnvFile(projectDir, backend, "");
5853
+ return writeSupabaseEnvFile(projectDir, config, "");
5614
5854
  }
5615
5855
  const startResult = await startSupabase(serverDir, packageManager);
5616
5856
  if (startResult.isErr()) {
5617
5857
  cliLog.error(pc.red(startResult.error.message));
5618
5858
  displayManualSupabaseInstructions(targetApp);
5619
- return writeSupabaseEnvFile(projectDir, backend, "");
5859
+ return writeSupabaseEnvFile(projectDir, config, "");
5620
5860
  }
5621
5861
  const supabaseOutput = startResult.value;
5622
5862
  const dbUrl = extractDbUrl(supabaseOutput);
5623
5863
  if (dbUrl) {
5624
- const envResult = await writeSupabaseEnvFile(projectDir, backend, dbUrl);
5864
+ const envResult = await writeSupabaseEnvFile(projectDir, config, dbUrl);
5625
5865
  if (envResult.isOk()) cliLog.success(pc.green("Supabase local development setup ready!"));
5626
5866
  else {
5627
5867
  cliLog.error(pc.red("Supabase setup completed, but failed to update .env automatically."));
@@ -5782,10 +6022,10 @@ async function createTursoDatabase(dbName, groupName) {
5782
6022
  }
5783
6023
  });
5784
6024
  }
5785
- async function writeEnvFile(projectDir, backend, config) {
6025
+ async function writeEnvFile(projectDir, project, config) {
5786
6026
  return Result.tryPromise({
5787
6027
  try: async () => {
5788
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
6028
+ const targetApp = getDbEnvTargetApp(project);
5789
6029
  await addEnvVariablesToFile(path.join(projectDir, targetApp, ".env"), [{
5790
6030
  key: "DATABASE_URL",
5791
6031
  value: config?.dbUrl ?? "",
@@ -5815,15 +6055,15 @@ DATABASE_URL=your_database_url
5815
6055
  DATABASE_AUTH_TOKEN=your_auth_token`);
5816
6056
  }
5817
6057
  async function setupTurso(config, cliInput) {
5818
- const { projectDir, backend } = config;
5819
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
6058
+ const { projectDir } = config;
6059
+ const targetApp = getDbEnvTargetApp(config);
5820
6060
  const setupMode = resolveDbSetupMode("turso", {
5821
6061
  manualDb: cliInput?.manualDb,
5822
6062
  dbSetupOptions: cliInput?.dbSetupOptions ?? config.dbSetupOptions
5823
6063
  });
5824
6064
  const setupSpinner = createSpinner();
5825
6065
  if (setupMode === "manual") {
5826
- const envResult = await writeEnvFile(projectDir, backend);
6066
+ const envResult = await writeEnvFile(projectDir, config);
5827
6067
  if (envResult.isErr()) return envResult;
5828
6068
  displayManualSetupInstructions(targetApp);
5829
6069
  return Result.ok(void 0);
@@ -5848,7 +6088,7 @@ async function setupTurso(config, cliInput) {
5848
6088
  mode = promptedMode;
5849
6089
  }
5850
6090
  if (mode === "manual") {
5851
- const envResult = await writeEnvFile(projectDir, backend);
6091
+ const envResult = await writeEnvFile(projectDir, config);
5852
6092
  if (envResult.isErr()) return envResult;
5853
6093
  displayManualSetupInstructions(targetApp);
5854
6094
  return Result.ok(void 0);
@@ -5859,7 +6099,7 @@ async function setupTurso(config, cliInput) {
5859
6099
  if (platform === "win32") {
5860
6100
  setupSpinner.stop(pc.yellow("Turso setup not supported on Windows"));
5861
6101
  cliLog.warn(pc.yellow("Automatic Turso setup is not supported on Windows."));
5862
- const envResult = await writeEnvFile(projectDir, backend);
6102
+ const envResult = await writeEnvFile(projectDir, config);
5863
6103
  if (envResult.isErr()) return envResult;
5864
6104
  displayManualSetupInstructions(targetApp);
5865
6105
  return Result.ok(void 0);
@@ -5877,7 +6117,7 @@ async function setupTurso(config, cliInput) {
5877
6117
  shouldInstall = promptedInstall;
5878
6118
  }
5879
6119
  if (!shouldInstall) {
5880
- const envResult = await writeEnvFile(projectDir, backend);
6120
+ const envResult = await writeEnvFile(projectDir, config);
5881
6121
  if (envResult.isErr()) return envResult;
5882
6122
  displayManualSetupInstructions(targetApp);
5883
6123
  return Result.ok(void 0);
@@ -5885,7 +6125,7 @@ async function setupTurso(config, cliInput) {
5885
6125
  const installResult = await installTursoCLI(isMac);
5886
6126
  if (installResult.isErr()) {
5887
6127
  cliLog.error(pc.red(installResult.error.message));
5888
- const envResult = await writeEnvFile(projectDir, backend);
6128
+ const envResult = await writeEnvFile(projectDir, config);
5889
6129
  if (envResult.isErr()) return envResult;
5890
6130
  displayManualSetupInstructions(targetApp);
5891
6131
  return Result.ok(void 0);
@@ -5894,7 +6134,7 @@ async function setupTurso(config, cliInput) {
5894
6134
  if (!await isTursoLoggedIn()) {
5895
6135
  if (isSilent()) {
5896
6136
  cliLog.warn(pc.yellow("Turso CLI is not logged in. Falling back to manual setup."));
5897
- const envResult = await writeEnvFile(projectDir, backend);
6137
+ const envResult = await writeEnvFile(projectDir, config);
5898
6138
  if (envResult.isErr()) return envResult;
5899
6139
  displayManualSetupInstructions(targetApp);
5900
6140
  return Result.ok(void 0);
@@ -5902,7 +6142,7 @@ async function setupTurso(config, cliInput) {
5902
6142
  const loginResult = await loginToTurso();
5903
6143
  if (loginResult.isErr()) {
5904
6144
  cliLog.error(pc.red(loginResult.error.message));
5905
- const envResult = await writeEnvFile(projectDir, backend);
6145
+ const envResult = await writeEnvFile(projectDir, config);
5906
6146
  if (envResult.isErr()) return envResult;
5907
6147
  displayManualSetupInstructions(targetApp);
5908
6148
  return Result.ok(void 0);
@@ -5920,13 +6160,13 @@ async function setupTurso(config, cliInput) {
5920
6160
  const createResult = await createTursoDatabase(suggestedName, selectedGroup);
5921
6161
  if (createResult.isErr()) {
5922
6162
  cliLog.error(pc.red(createResult.error.message));
5923
- const envResult = await writeEnvFile(projectDir, backend);
6163
+ const envResult = await writeEnvFile(projectDir, config);
5924
6164
  if (envResult.isErr()) return envResult;
5925
6165
  displayManualSetupInstructions(targetApp);
5926
6166
  cliLog.success("Setup completed with manual configuration required.");
5927
6167
  return Result.ok(void 0);
5928
6168
  }
5929
- const envResult = await writeEnvFile(projectDir, backend, createResult.value);
6169
+ const envResult = await writeEnvFile(projectDir, config, createResult.value);
5930
6170
  if (envResult.isErr()) return envResult;
5931
6171
  cliLog.success("Turso database setup completed successfully!");
5932
6172
  return Result.ok(void 0);
@@ -5948,13 +6188,13 @@ async function setupTurso(config, cliInput) {
5948
6188
  continue;
5949
6189
  }
5950
6190
  cliLog.error(pc.red(createResult.error.message));
5951
- const envResult = await writeEnvFile(projectDir, backend);
6191
+ const envResult = await writeEnvFile(projectDir, config);
5952
6192
  if (envResult.isErr()) return envResult;
5953
6193
  displayManualSetupInstructions(targetApp);
5954
6194
  cliLog.success("Setup completed with manual configuration required.");
5955
6195
  return Result.ok(void 0);
5956
6196
  }
5957
- const envResult = await writeEnvFile(projectDir, backend, createResult.value);
6197
+ const envResult = await writeEnvFile(projectDir, config, createResult.value);
5958
6198
  if (envResult.isErr()) return envResult;
5959
6199
  cliLog.success("Turso database setup completed successfully!");
5960
6200
  return Result.ok(void 0);
@@ -5971,12 +6211,12 @@ async function setupDatabase(config, cliInput) {
5971
6211
  const { database, dbSetup, backend, projectDir } = config;
5972
6212
  if (backend === "convex" || database === "none") {
5973
6213
  if (backend !== "convex") {
5974
- const serverDbDir = path.join(projectDir, "apps/server/src/db");
6214
+ const serverDbDir = path.join(projectDir, appDir(config, "server"), "src/db");
5975
6215
  if (await fs.pathExists(serverDbDir)) await fs.remove(serverDbDir);
5976
6216
  }
5977
6217
  return;
5978
6218
  }
5979
- const dbPackageDir = path.join(projectDir, "packages/db");
6219
+ const dbPackageDir = path.join(projectDir, pkgDir(config, "db"));
5980
6220
  if (!await fs.pathExists(dbPackageDir)) return;
5981
6221
  async function runSetup(setupFn) {
5982
6222
  const result = await setupFn();
@@ -6191,7 +6431,7 @@ async function displayPostInstallInstructions(config) {
6191
6431
  if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
6192
6432
  output += `\n${pc$1.bold("Like kubojs?")} Please consider giving us a star\n on GitHub:\n`;
6193
6433
  output += pc$1.cyan("https://github.com/albuquerquesz/kubo");
6194
- cliConsola.box(output);
6434
+ cliConsola.box(relocateLayoutPaths(output, config));
6195
6435
  }
6196
6436
  function getNativeInstructions(isConvex, isBackendSelf, frontend, runCmd) {
6197
6437
  const envVar = isConvex ? "EXPO_PUBLIC_CONVEX_URL" : "EXPO_PUBLIC_SERVER_URL";
@@ -6584,6 +6824,13 @@ async function createProjectHandler(input, options = {}) {
6584
6824
  process.exit(1);
6585
6825
  });
6586
6826
  }
6827
+ /** `--layout` and `--layout-code` are exclusive; a code is redeemed (and consumed) here. */
6828
+ async function resolveInputLayout(input) {
6829
+ if (input.layoutCode === void 0) return Result.ok(input.layout);
6830
+ if (input.layout !== void 0) return Result.err(new CLIError({ message: "Use either --layout or --layout-code, not both." }));
6831
+ if (input.dryRun) return Result.err(new CLIError({ message: "--layout-code is single-use and cannot be combined with --dry-run." }));
6832
+ return redeemLayoutCode(input.layoutCode);
6833
+ }
6587
6834
  /** Stack-selecting flags; their presence means the user chose the stack via flags, not prompts. */
6588
6835
  const STACK_FLAG_KEYS = [
6589
6836
  "database",
@@ -6621,6 +6868,7 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
6621
6868
  if (!isSilent() && input.renderTitle !== false) renderTitle();
6622
6869
  if (!isSilent()) intro(cliColors.bright("Creating a new kubojs project"));
6623
6870
  if (!isSilent() && input.yolo) cliConsola.fatal("YOLO mode enabled - skipping checks. Things may break!");
6871
+ const layout = yield* validateCreateLayout(yield* Result.await(resolveInputLayout(input)));
6624
6872
  let currentPathInput;
6625
6873
  if (isSilent()) currentPathInput = yield* Result.await(resolveProjectNameForSilent(input));
6626
6874
  else if (input.yes && input.projectName) currentPathInput = input.projectName;
@@ -6750,6 +6998,10 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
6750
6998
  ...config,
6751
6999
  dbSetupOptions: effectiveDbSetupOptions
6752
7000
  };
7001
+ if (layout) config = {
7002
+ ...config,
7003
+ layout
7004
+ };
6753
7005
  if (!input.yolo) {
6754
7006
  const addonsValidationResult = validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth, config.backend, config.runtime);
6755
7007
  if (addonsValidationResult.isErr()) return Result.err(new CLIError({ message: addonsValidationResult.error.message }));
@@ -6959,12 +7211,17 @@ const router = t.router({
6959
7211
  renderTitle: z.boolean().optional(),
6960
7212
  disableAnalytics: z.boolean().optional().default(false).describe("Disable analytics"),
6961
7213
  manualDb: z.boolean().optional().default(false).describe("Skip automatic/manual database setup prompt and use manual setup"),
6962
- dbSetupOptions: types_exports.DbSetupOptionsSchema.optional().describe("Structured database setup options")
7214
+ dbSetupOptions: types_exports.DbSetupOptionsSchema.optional().describe("Structured database setup options"),
7215
+ layout: z.string().optional().describe("Folder layout: a preset (standard, libs) or JSON, e.g. '{\"preset\":\"libs\",\"apps\":{\"web\":\"web\"}}'"),
7216
+ layoutCode: z.string().optional().describe("Single-use code from the Layout tab at kubojs.dev/new (applies your saved layout)")
6963
7217
  })])).mutation(async ({ input }) => {
6964
7218
  const [projectName, options] = input;
7219
+ const layoutResult = options.layout === void 0 ? Result.ok(void 0) : parseLayoutFlag(options.layout);
7220
+ if (layoutResult.isErr()) throw layoutResult.error;
6965
7221
  const result = await createProjectHandler({
6966
7222
  projectName,
6967
- ...options
7223
+ ...options,
7224
+ layout: layoutResult.value
6968
7225
  });
6969
7226
  if (options.verbose || options.dryRun) return result;
6970
7227
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-kubojs",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "keywords": [
6
6
  "better-auth",
@@ -73,8 +73,8 @@
73
73
  "dependencies": {
74
74
  "@clack/core": "^1.4.1",
75
75
  "@clack/prompts": "^1.5.1",
76
- "@kubojs/template-generator": "^0.5.0",
77
- "@kubojs/types": "^0.5.0",
76
+ "@kubojs/template-generator": "^0.6.0",
77
+ "@kubojs/types": "^0.6.0",
78
78
  "@modelcontextprotocol/sdk": "1.29.0",
79
79
  "@tanstack/router-generator": "1.167.28",
80
80
  "@trpc/server": "^11.17.0",