create-kubojs 0.5.1 → 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-DdCJmpnU.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-DdCJmpnU.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";
@@ -2477,6 +2477,15 @@ async function setupSkills(config) {
2477
2477
  return Result.ok(void 0);
2478
2478
  }
2479
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
2480
2489
  //#region src/helpers/addons/tauri-setup.ts
2481
2490
  function getWebFrontend(frontend) {
2482
2491
  return frontend.find((value) => types_exports.desktopWebFrontends.includes(value));
@@ -2527,7 +2536,7 @@ async function setupTauri(config) {
2527
2536
  if (shouldSkipExternalCommands()) return Result.ok(void 0);
2528
2537
  const { packageManager, frontend, projectDir } = config;
2529
2538
  const s = createSpinner();
2530
- const clientPackageDir = path.join(projectDir, "apps/web");
2539
+ const clientPackageDir = path.join(projectDir, appDir(config, "web"));
2531
2540
  if (!await fs.pathExists(clientPackageDir)) return Result.ok(void 0);
2532
2541
  s.start("Setting up Tauri desktop app support...");
2533
2542
  const [command, ...args] = buildTauriInitArgs({
@@ -4393,6 +4402,90 @@ function displayConfig(config) {
4393
4402
  return configDisplay.join("\n");
4394
4403
  }
4395
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
4396
4489
  //#region src/utils/project-directory.ts
4397
4490
  async function handleDirectoryConflict(currentPathInput) {
4398
4491
  while (true) {
@@ -4806,7 +4899,7 @@ async function generateRouteTreeIfNeeded(projectDir, config) {
4806
4899
  if (!needsRouteTree(config.frontend)) return Result.ok(void 0);
4807
4900
  const target = resolveRouteTreeTarget(config.frontend);
4808
4901
  if (!target) return Result.ok(void 0);
4809
- const webAppDir = path.join(projectDir, "apps/web");
4902
+ const webAppDir = path.join(projectDir, appDir(config, "web"));
4810
4903
  const routesDir = path.join(webAppDir, "src/routes");
4811
4904
  if (!await fs.pathExists(routesDir)) return Result.ok(void 0);
4812
4905
  return Result.tryPromise({
@@ -4869,7 +4962,7 @@ async function setupCloudflareD1(config) {
4869
4962
  if (!(orm === "prisma" && (serverDeploy === "cloudflare" || backend === "self" && webDeploy === "cloudflare"))) return Result.ok(void 0);
4870
4963
  return Result.tryPromise({
4871
4964
  try: async () => {
4872
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
4965
+ const targetApp = getDbEnvTargetApp(config);
4873
4966
  await addEnvVariablesToFile(path.join(projectDir, targetApp, ".env"), [{
4874
4967
  key: "DATABASE_URL",
4875
4968
  value: `file:${path.join(projectDir, targetApp, "local.db")}`,
@@ -4877,7 +4970,7 @@ async function setupCloudflareD1(config) {
4877
4970
  }]);
4878
4971
  await addPackageDependency({
4879
4972
  dependencies: ["@prisma/adapter-d1"],
4880
- projectDir: path.join(projectDir, backend === "self" ? "apps/web" : "apps/server")
4973
+ projectDir: path.join(projectDir, getDbEnvTargetApp(config))
4881
4974
  });
4882
4975
  },
4883
4976
  catch: (e) => new DatabaseSetupError({
@@ -4890,11 +4983,11 @@ async function setupCloudflareD1(config) {
4890
4983
  //#endregion
4891
4984
  //#region src/helpers/database-providers/docker-compose-setup.ts
4892
4985
  async function setupDockerCompose(config) {
4893
- const { database, projectDir, projectName, backend } = config;
4986
+ const { database, projectDir, projectName } = config;
4894
4987
  if (database === "none" || database === "sqlite") return Result.ok(void 0);
4895
4988
  const result = await Result.tryPromise({
4896
4989
  try: async () => {
4897
- await writeEnvFile$4(projectDir, database, projectName, backend);
4990
+ await writeEnvFile$4(projectDir, database, projectName, config);
4898
4991
  },
4899
4992
  catch: (e) => new DatabaseSetupError({
4900
4993
  provider: "docker-compose",
@@ -4904,8 +4997,8 @@ async function setupDockerCompose(config) {
4904
4997
  });
4905
4998
  return result.isErr() ? result : Result.ok(void 0);
4906
4999
  }
4907
- async function writeEnvFile$4(projectDir, database, projectName, backend) {
4908
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
5000
+ async function writeEnvFile$4(projectDir, database, projectName, project) {
5001
+ const targetApp = getDbEnvTargetApp(project);
4909
5002
  await addEnvVariablesToFile(path.join(projectDir, targetApp, ".env"), [{
4910
5003
  key: "DATABASE_URL",
4911
5004
  value: getDatabaseUrl(database, projectName),
@@ -5006,10 +5099,10 @@ async function initMongoDBAtlas(serverDir) {
5006
5099
  }
5007
5100
  return Result.ok({ connectionString });
5008
5101
  }
5009
- async function writeEnvFile$3(projectDir, backend, config) {
5102
+ async function writeEnvFile$3(projectDir, project, config) {
5010
5103
  return Result.tryPromise({
5011
5104
  try: async () => {
5012
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
5105
+ const targetApp = getDbEnvTargetApp(project);
5013
5106
  await addEnvVariablesToFile(path.join(projectDir, targetApp, ".env"), [{
5014
5107
  key: "DATABASE_URL",
5015
5108
  value: config?.connectionString ?? "mongodb://localhost:27017/mydb",
@@ -5041,12 +5134,12 @@ ${pc.green("MongoDB Atlas Manual Setup Instructions:")}
5041
5134
  `);
5042
5135
  }
5043
5136
  async function setupMongoDBAtlas(config, cliInput) {
5044
- const { projectDir, backend } = config;
5137
+ const { projectDir } = config;
5045
5138
  const setupMode = resolveDbSetupMode("mongodb-atlas", {
5046
5139
  manualDb: cliInput?.manualDb,
5047
5140
  dbSetupOptions: cliInput?.dbSetupOptions ?? config.dbSetupOptions
5048
5141
  });
5049
- const serverDir = path.join(projectDir, "packages/db");
5142
+ const serverDir = path.join(projectDir, pkgDir(config, "db"));
5050
5143
  const ensureDirResult = await Result.tryPromise({
5051
5144
  try: () => fs.ensureDir(serverDir),
5052
5145
  catch: (e) => new DatabaseSetupError({
@@ -5058,7 +5151,7 @@ async function setupMongoDBAtlas(config, cliInput) {
5058
5151
  if (ensureDirResult.isErr()) return ensureDirResult;
5059
5152
  if (setupMode === "manual") {
5060
5153
  cliLog.info("MongoDB Atlas manual setup selected");
5061
- const envResult = await writeEnvFile$3(projectDir, backend);
5154
+ const envResult = await writeEnvFile$3(projectDir, config);
5062
5155
  if (envResult.isErr()) return envResult;
5063
5156
  displayManualSetupInstructions$3();
5064
5157
  return Result.ok(void 0);
@@ -5067,7 +5160,7 @@ async function setupMongoDBAtlas(config, cliInput) {
5067
5160
  if (!mode) {
5068
5161
  if (isSilent()) {
5069
5162
  cliLog.warn(pc.yellow("MongoDB Atlas automatic setup requires interactive input. Falling back to manual setup."));
5070
- const envResult = await writeEnvFile$3(projectDir, backend);
5163
+ const envResult = await writeEnvFile$3(projectDir, config);
5071
5164
  if (envResult.isErr()) return envResult;
5072
5165
  displayManualSetupInstructions$3();
5073
5166
  return Result.ok(void 0);
@@ -5090,21 +5183,21 @@ async function setupMongoDBAtlas(config, cliInput) {
5090
5183
  }
5091
5184
  if (mode === "manual") {
5092
5185
  cliLog.info("MongoDB Atlas manual setup selected");
5093
- const envResult = await writeEnvFile$3(projectDir, backend);
5186
+ const envResult = await writeEnvFile$3(projectDir, config);
5094
5187
  if (envResult.isErr()) return envResult;
5095
5188
  displayManualSetupInstructions$3();
5096
5189
  return Result.ok(void 0);
5097
5190
  }
5098
5191
  const mongoConfigResult = await initMongoDBAtlas(serverDir);
5099
5192
  if (mongoConfigResult.isOk()) {
5100
- const envResult = await writeEnvFile$3(projectDir, backend, mongoConfigResult.value);
5193
+ const envResult = await writeEnvFile$3(projectDir, config, mongoConfigResult.value);
5101
5194
  if (envResult.isErr()) return envResult;
5102
5195
  cliLog.success(pc.green("MongoDB Atlas setup complete! Connection saved to .env file."));
5103
5196
  return Result.ok(void 0);
5104
5197
  }
5105
5198
  if (UserCancelledError.is(mongoConfigResult.error)) return Result.err(mongoConfigResult.error);
5106
5199
  cliLog.warn(pc.yellow("Falling back to local MongoDB configuration"));
5107
- const envResult = await writeEnvFile$3(projectDir, backend);
5200
+ const envResult = await writeEnvFile$3(projectDir, config);
5108
5201
  if (envResult.isErr()) return envResult;
5109
5202
  displayManualSetupInstructions$3();
5110
5203
  return Result.ok(void 0);
@@ -5191,10 +5284,10 @@ async function createNeonProject(projectName, regionId, packageManager) {
5191
5284
  }
5192
5285
  return databaseSetupError("neon", "Failed to extract connection information from Neon response");
5193
5286
  }
5194
- async function writeEnvFile$2(projectDir, backend, config) {
5287
+ async function writeEnvFile$2(projectDir, project, config) {
5195
5288
  return Result.tryPromise({
5196
5289
  try: async () => {
5197
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
5290
+ const targetApp = getDbEnvTargetApp(project);
5198
5291
  await addEnvVariablesToFile(path.join(projectDir, targetApp, ".env"), [{
5199
5292
  key: "DATABASE_URL",
5200
5293
  value: config?.connectionString ?? "postgresql://postgres:postgres@localhost:5432/mydb?schema=public",
@@ -5208,10 +5301,10 @@ async function writeEnvFile$2(projectDir, backend, config) {
5208
5301
  })
5209
5302
  });
5210
5303
  }
5211
- async function setupWithNeonDb(projectDir, packageManager, backend) {
5304
+ async function setupWithNeonDb(projectDir, packageManager, project) {
5212
5305
  const s = createSpinner();
5213
5306
  s.start("Creating Neon database using neon-new...");
5214
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
5307
+ const targetApp = getDbEnvTargetApp(project);
5215
5308
  const targetDir = path.join(projectDir, targetApp);
5216
5309
  const ensureDirResult = await Result.tryPromise({
5217
5310
  try: () => fs.ensureDir(targetDir),
@@ -5252,20 +5345,20 @@ function displayManualSetupInstructions$2(target) {
5252
5345
  DATABASE_URL="your_connection_string"`);
5253
5346
  }
5254
5347
  async function setupNeonPostgres(config, cliInput) {
5255
- const { packageManager, projectDir, backend } = config;
5348
+ const { packageManager, projectDir } = config;
5256
5349
  const setupMode = resolveDbSetupMode("neon", {
5257
5350
  manualDb: cliInput?.manualDb,
5258
5351
  dbSetupOptions: cliInput?.dbSetupOptions ?? config.dbSetupOptions
5259
5352
  });
5260
- const target = backend === "self" ? "apps/web" : "apps/server";
5353
+ const target = getDbEnvTargetApp(config);
5261
5354
  if (shouldSkipExternalCommands()) {
5262
- const envResult = await writeEnvFile$2(projectDir, backend);
5355
+ const envResult = await writeEnvFile$2(projectDir, config);
5263
5356
  if (envResult.isErr()) return envResult;
5264
5357
  displayManualSetupInstructions$2(target);
5265
5358
  return Result.ok(void 0);
5266
5359
  }
5267
5360
  if (setupMode === "manual") {
5268
- const envResult = await writeEnvFile$2(projectDir, backend);
5361
+ const envResult = await writeEnvFile$2(projectDir, config);
5269
5362
  if (envResult.isErr()) return envResult;
5270
5363
  displayManualSetupInstructions$2(target);
5271
5364
  return Result.ok(void 0);
@@ -5290,7 +5383,7 @@ async function setupNeonPostgres(config, cliInput) {
5290
5383
  selectedMode = promptedMode;
5291
5384
  }
5292
5385
  if (selectedMode === "manual") {
5293
- const envResult = await writeEnvFile$2(projectDir, backend);
5386
+ const envResult = await writeEnvFile$2(projectDir, config);
5294
5387
  if (envResult.isErr()) return envResult;
5295
5388
  displayManualSetupInstructions$2(target);
5296
5389
  return Result.ok(void 0);
@@ -5315,10 +5408,10 @@ async function setupNeonPostgres(config, cliInput) {
5315
5408
  setupMethod = promptedSetupMethod;
5316
5409
  }
5317
5410
  if (setupMethod === "neondb") {
5318
- const neonDbResult = await setupWithNeonDb(projectDir, packageManager, backend);
5411
+ const neonDbResult = await setupWithNeonDb(projectDir, packageManager, config);
5319
5412
  if (neonDbResult.isErr()) {
5320
5413
  cliLog.error(pc.red(neonDbResult.error.message));
5321
- const envResult = await writeEnvFile$2(projectDir, backend);
5414
+ const envResult = await writeEnvFile$2(projectDir, config);
5322
5415
  if (envResult.isErr()) return envResult;
5323
5416
  displayManualSetupInstructions$2(target);
5324
5417
  return Result.ok(void 0);
@@ -5352,14 +5445,14 @@ async function setupNeonPostgres(config, cliInput) {
5352
5445
  const neonConfigResult = await createNeonProject(projectName, regionId, packageManager);
5353
5446
  if (neonConfigResult.isErr()) {
5354
5447
  cliLog.error(pc.red(neonConfigResult.error.message));
5355
- const envResult = await writeEnvFile$2(projectDir, backend);
5448
+ const envResult = await writeEnvFile$2(projectDir, config);
5356
5449
  if (envResult.isErr()) return envResult;
5357
5450
  displayManualSetupInstructions$2(target);
5358
5451
  return Result.ok(void 0);
5359
5452
  }
5360
5453
  const finalSpinner = createSpinner();
5361
5454
  finalSpinner.start("Configuring database connection");
5362
- const envResult = await writeEnvFile$2(projectDir, backend, neonConfigResult.value);
5455
+ const envResult = await writeEnvFile$2(projectDir, config, neonConfigResult.value);
5363
5456
  if (envResult.isErr()) {
5364
5457
  finalSpinner.stop(pc.red("Failed to configure database connection"));
5365
5458
  return envResult;
@@ -5371,11 +5464,11 @@ async function setupNeonPostgres(config, cliInput) {
5371
5464
  //#endregion
5372
5465
  //#region src/helpers/database-providers/planetscale-setup.ts
5373
5466
  async function setupPlanetScale(config) {
5374
- const { projectDir, database, orm, backend } = config;
5467
+ const { projectDir, database, orm } = config;
5375
5468
  if (!["mysql", "postgres"].includes(database)) return Result.ok(void 0);
5376
5469
  return Result.tryPromise({
5377
5470
  try: async () => {
5378
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
5471
+ const targetApp = getDbEnvTargetApp(config);
5379
5472
  const envPath = path.join(projectDir, targetApp, ".env");
5380
5473
  if (database === "mysql" && orm === "drizzle") {
5381
5474
  const variables = [
@@ -5523,10 +5616,10 @@ async function setupWithCreateDb(serverDir, packageManager, regionId) {
5523
5616
  claimUrl: createDbResponse.claimUrl
5524
5617
  });
5525
5618
  }
5526
- async function writeEnvFile$1(projectDir, backend, config) {
5619
+ async function writeEnvFile$1(projectDir, project, config) {
5527
5620
  return Result.tryPromise({
5528
5621
  try: async () => {
5529
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
5622
+ const targetApp = getDbEnvTargetApp(project);
5530
5623
  const envPath = path.join(projectDir, targetApp, ".env");
5531
5624
  const variables = [{
5532
5625
  key: "DATABASE_URL",
@@ -5558,13 +5651,13 @@ function displayManualSetupInstructions$1(target) {
5558
5651
  DATABASE_URL="your_database_url"`);
5559
5652
  }
5560
5653
  async function setupPrismaPostgres(config, cliInput) {
5561
- const { packageManager, projectDir, backend } = config;
5654
+ const { packageManager, projectDir } = config;
5562
5655
  const setupMode = resolveDbSetupMode("prisma-postgres", {
5563
5656
  manualDb: cliInput?.manualDb,
5564
5657
  dbSetupOptions: cliInput?.dbSetupOptions ?? config.dbSetupOptions
5565
5658
  });
5566
- const dbDir = path.join(projectDir, "packages/db");
5567
- const target = backend === "self" ? "apps/web" : "apps/server";
5659
+ const dbDir = path.join(projectDir, pkgDir(config, "db"));
5660
+ const target = getDbEnvTargetApp(config);
5568
5661
  const ensureDirResult = await Result.tryPromise({
5569
5662
  try: () => fs.ensureDir(dbDir),
5570
5663
  catch: (e) => new DatabaseSetupError({
@@ -5575,7 +5668,7 @@ async function setupPrismaPostgres(config, cliInput) {
5575
5668
  });
5576
5669
  if (ensureDirResult.isErr()) return ensureDirResult;
5577
5670
  if (setupMode === "manual") {
5578
- const envResult = await writeEnvFile$1(projectDir, backend);
5671
+ const envResult = await writeEnvFile$1(projectDir, config);
5579
5672
  if (envResult.isErr()) return envResult;
5580
5673
  displayManualSetupInstructions$1(target);
5581
5674
  return Result.ok(void 0);
@@ -5600,7 +5693,7 @@ async function setupPrismaPostgres(config, cliInput) {
5600
5693
  selectedSetupMode = promptedSetupMode;
5601
5694
  }
5602
5695
  if (selectedSetupMode === "manual") {
5603
- const envResult = await writeEnvFile$1(projectDir, backend);
5696
+ const envResult = await writeEnvFile$1(projectDir, config);
5604
5697
  if (envResult.isErr()) return envResult;
5605
5698
  displayManualSetupInstructions$1(target);
5606
5699
  return Result.ok(void 0);
@@ -5609,13 +5702,13 @@ async function setupPrismaPostgres(config, cliInput) {
5609
5702
  if (prismaConfigResult.isErr()) {
5610
5703
  if (UserCancelledError.is(prismaConfigResult.error)) return Result.err(prismaConfigResult.error);
5611
5704
  cliLog.error(pc.red(prismaConfigResult.error.message));
5612
- const envResult = await writeEnvFile$1(projectDir, backend);
5705
+ const envResult = await writeEnvFile$1(projectDir, config);
5613
5706
  if (envResult.isErr()) return envResult;
5614
5707
  displayManualSetupInstructions$1(target);
5615
5708
  cliLog.info("Setup completed with manual configuration required.");
5616
5709
  return Result.ok(void 0);
5617
5710
  }
5618
- const envResult = await writeEnvFile$1(projectDir, backend, prismaConfigResult.value);
5711
+ const envResult = await writeEnvFile$1(projectDir, config, prismaConfigResult.value);
5619
5712
  if (envResult.isErr()) return envResult;
5620
5713
  cliLog.success(pc.green("Prisma Postgres database configured successfully!"));
5621
5714
  if (prismaConfigResult.value.claimUrl) cliLog.info(pc.blue(`Claim URL saved to .env: ${prismaConfigResult.value.claimUrl}`));
@@ -5623,10 +5716,10 @@ async function setupPrismaPostgres(config, cliInput) {
5623
5716
  }
5624
5717
  //#endregion
5625
5718
  //#region src/helpers/database-providers/supabase-setup.ts
5626
- async function writeSupabaseEnvFile(projectDir, backend, databaseUrl) {
5719
+ async function writeSupabaseEnvFile(projectDir, project, databaseUrl) {
5627
5720
  return Result.tryPromise({
5628
5721
  try: async () => {
5629
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
5722
+ const targetApp = getDbEnvTargetApp(project);
5630
5723
  const envPath = path.join(projectDir, targetApp, ".env");
5631
5724
  const dbUrlToUse = databaseUrl || "postgresql://postgres:postgres@127.0.0.1:54322/postgres";
5632
5725
  await addEnvVariablesToFile(envPath, [{
@@ -5710,13 +5803,13 @@ ${pc.dim(output)}` : ""}
5710
5803
  ${pc.gray("DATABASE_URL=\"your_supabase_db_url\"")}`);
5711
5804
  }
5712
5805
  async function setupSupabase(config, cliInput) {
5713
- const { projectDir, packageManager, backend } = config;
5714
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
5806
+ const { projectDir, packageManager } = config;
5807
+ const targetApp = getDbEnvTargetApp(config);
5715
5808
  const setupMode = resolveDbSetupMode("supabase", {
5716
5809
  manualDb: cliInput?.manualDb,
5717
5810
  dbSetupOptions: cliInput?.dbSetupOptions ?? config.dbSetupOptions
5718
5811
  });
5719
- const serverDir = path.join(projectDir, "packages", "db");
5812
+ const serverDir = path.join(projectDir, pkgDir(config, "db"));
5720
5813
  const ensureDirResult = await Result.tryPromise({
5721
5814
  try: () => fs.ensureDir(serverDir),
5722
5815
  catch: (e) => new DatabaseSetupError({
@@ -5728,7 +5821,7 @@ async function setupSupabase(config, cliInput) {
5728
5821
  if (ensureDirResult.isErr()) return ensureDirResult;
5729
5822
  if (setupMode === "manual") {
5730
5823
  displayManualSupabaseInstructions(targetApp);
5731
- return writeSupabaseEnvFile(projectDir, backend, "");
5824
+ return writeSupabaseEnvFile(projectDir, config, "");
5732
5825
  }
5733
5826
  let mode = setupMode;
5734
5827
  if (!mode) if (isSilent()) mode = "manual";
@@ -5751,24 +5844,24 @@ async function setupSupabase(config, cliInput) {
5751
5844
  }
5752
5845
  if (mode === "manual") {
5753
5846
  displayManualSupabaseInstructions(targetApp);
5754
- return writeSupabaseEnvFile(projectDir, backend, "");
5847
+ return writeSupabaseEnvFile(projectDir, config, "");
5755
5848
  }
5756
5849
  const initResult = await initializeSupabase(serverDir, packageManager);
5757
5850
  if (initResult.isErr()) {
5758
5851
  cliLog.error(pc.red(initResult.error.message));
5759
5852
  displayManualSupabaseInstructions(targetApp);
5760
- return writeSupabaseEnvFile(projectDir, backend, "");
5853
+ return writeSupabaseEnvFile(projectDir, config, "");
5761
5854
  }
5762
5855
  const startResult = await startSupabase(serverDir, packageManager);
5763
5856
  if (startResult.isErr()) {
5764
5857
  cliLog.error(pc.red(startResult.error.message));
5765
5858
  displayManualSupabaseInstructions(targetApp);
5766
- return writeSupabaseEnvFile(projectDir, backend, "");
5859
+ return writeSupabaseEnvFile(projectDir, config, "");
5767
5860
  }
5768
5861
  const supabaseOutput = startResult.value;
5769
5862
  const dbUrl = extractDbUrl(supabaseOutput);
5770
5863
  if (dbUrl) {
5771
- const envResult = await writeSupabaseEnvFile(projectDir, backend, dbUrl);
5864
+ const envResult = await writeSupabaseEnvFile(projectDir, config, dbUrl);
5772
5865
  if (envResult.isOk()) cliLog.success(pc.green("Supabase local development setup ready!"));
5773
5866
  else {
5774
5867
  cliLog.error(pc.red("Supabase setup completed, but failed to update .env automatically."));
@@ -5929,10 +6022,10 @@ async function createTursoDatabase(dbName, groupName) {
5929
6022
  }
5930
6023
  });
5931
6024
  }
5932
- async function writeEnvFile(projectDir, backend, config) {
6025
+ async function writeEnvFile(projectDir, project, config) {
5933
6026
  return Result.tryPromise({
5934
6027
  try: async () => {
5935
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
6028
+ const targetApp = getDbEnvTargetApp(project);
5936
6029
  await addEnvVariablesToFile(path.join(projectDir, targetApp, ".env"), [{
5937
6030
  key: "DATABASE_URL",
5938
6031
  value: config?.dbUrl ?? "",
@@ -5962,15 +6055,15 @@ DATABASE_URL=your_database_url
5962
6055
  DATABASE_AUTH_TOKEN=your_auth_token`);
5963
6056
  }
5964
6057
  async function setupTurso(config, cliInput) {
5965
- const { projectDir, backend } = config;
5966
- const targetApp = backend === "self" ? "apps/web" : "apps/server";
6058
+ const { projectDir } = config;
6059
+ const targetApp = getDbEnvTargetApp(config);
5967
6060
  const setupMode = resolveDbSetupMode("turso", {
5968
6061
  manualDb: cliInput?.manualDb,
5969
6062
  dbSetupOptions: cliInput?.dbSetupOptions ?? config.dbSetupOptions
5970
6063
  });
5971
6064
  const setupSpinner = createSpinner();
5972
6065
  if (setupMode === "manual") {
5973
- const envResult = await writeEnvFile(projectDir, backend);
6066
+ const envResult = await writeEnvFile(projectDir, config);
5974
6067
  if (envResult.isErr()) return envResult;
5975
6068
  displayManualSetupInstructions(targetApp);
5976
6069
  return Result.ok(void 0);
@@ -5995,7 +6088,7 @@ async function setupTurso(config, cliInput) {
5995
6088
  mode = promptedMode;
5996
6089
  }
5997
6090
  if (mode === "manual") {
5998
- const envResult = await writeEnvFile(projectDir, backend);
6091
+ const envResult = await writeEnvFile(projectDir, config);
5999
6092
  if (envResult.isErr()) return envResult;
6000
6093
  displayManualSetupInstructions(targetApp);
6001
6094
  return Result.ok(void 0);
@@ -6006,7 +6099,7 @@ async function setupTurso(config, cliInput) {
6006
6099
  if (platform === "win32") {
6007
6100
  setupSpinner.stop(pc.yellow("Turso setup not supported on Windows"));
6008
6101
  cliLog.warn(pc.yellow("Automatic Turso setup is not supported on Windows."));
6009
- const envResult = await writeEnvFile(projectDir, backend);
6102
+ const envResult = await writeEnvFile(projectDir, config);
6010
6103
  if (envResult.isErr()) return envResult;
6011
6104
  displayManualSetupInstructions(targetApp);
6012
6105
  return Result.ok(void 0);
@@ -6024,7 +6117,7 @@ async function setupTurso(config, cliInput) {
6024
6117
  shouldInstall = promptedInstall;
6025
6118
  }
6026
6119
  if (!shouldInstall) {
6027
- const envResult = await writeEnvFile(projectDir, backend);
6120
+ const envResult = await writeEnvFile(projectDir, config);
6028
6121
  if (envResult.isErr()) return envResult;
6029
6122
  displayManualSetupInstructions(targetApp);
6030
6123
  return Result.ok(void 0);
@@ -6032,7 +6125,7 @@ async function setupTurso(config, cliInput) {
6032
6125
  const installResult = await installTursoCLI(isMac);
6033
6126
  if (installResult.isErr()) {
6034
6127
  cliLog.error(pc.red(installResult.error.message));
6035
- const envResult = await writeEnvFile(projectDir, backend);
6128
+ const envResult = await writeEnvFile(projectDir, config);
6036
6129
  if (envResult.isErr()) return envResult;
6037
6130
  displayManualSetupInstructions(targetApp);
6038
6131
  return Result.ok(void 0);
@@ -6041,7 +6134,7 @@ async function setupTurso(config, cliInput) {
6041
6134
  if (!await isTursoLoggedIn()) {
6042
6135
  if (isSilent()) {
6043
6136
  cliLog.warn(pc.yellow("Turso CLI is not logged in. Falling back to manual setup."));
6044
- const envResult = await writeEnvFile(projectDir, backend);
6137
+ const envResult = await writeEnvFile(projectDir, config);
6045
6138
  if (envResult.isErr()) return envResult;
6046
6139
  displayManualSetupInstructions(targetApp);
6047
6140
  return Result.ok(void 0);
@@ -6049,7 +6142,7 @@ async function setupTurso(config, cliInput) {
6049
6142
  const loginResult = await loginToTurso();
6050
6143
  if (loginResult.isErr()) {
6051
6144
  cliLog.error(pc.red(loginResult.error.message));
6052
- const envResult = await writeEnvFile(projectDir, backend);
6145
+ const envResult = await writeEnvFile(projectDir, config);
6053
6146
  if (envResult.isErr()) return envResult;
6054
6147
  displayManualSetupInstructions(targetApp);
6055
6148
  return Result.ok(void 0);
@@ -6067,13 +6160,13 @@ async function setupTurso(config, cliInput) {
6067
6160
  const createResult = await createTursoDatabase(suggestedName, selectedGroup);
6068
6161
  if (createResult.isErr()) {
6069
6162
  cliLog.error(pc.red(createResult.error.message));
6070
- const envResult = await writeEnvFile(projectDir, backend);
6163
+ const envResult = await writeEnvFile(projectDir, config);
6071
6164
  if (envResult.isErr()) return envResult;
6072
6165
  displayManualSetupInstructions(targetApp);
6073
6166
  cliLog.success("Setup completed with manual configuration required.");
6074
6167
  return Result.ok(void 0);
6075
6168
  }
6076
- const envResult = await writeEnvFile(projectDir, backend, createResult.value);
6169
+ const envResult = await writeEnvFile(projectDir, config, createResult.value);
6077
6170
  if (envResult.isErr()) return envResult;
6078
6171
  cliLog.success("Turso database setup completed successfully!");
6079
6172
  return Result.ok(void 0);
@@ -6095,13 +6188,13 @@ async function setupTurso(config, cliInput) {
6095
6188
  continue;
6096
6189
  }
6097
6190
  cliLog.error(pc.red(createResult.error.message));
6098
- const envResult = await writeEnvFile(projectDir, backend);
6191
+ const envResult = await writeEnvFile(projectDir, config);
6099
6192
  if (envResult.isErr()) return envResult;
6100
6193
  displayManualSetupInstructions(targetApp);
6101
6194
  cliLog.success("Setup completed with manual configuration required.");
6102
6195
  return Result.ok(void 0);
6103
6196
  }
6104
- const envResult = await writeEnvFile(projectDir, backend, createResult.value);
6197
+ const envResult = await writeEnvFile(projectDir, config, createResult.value);
6105
6198
  if (envResult.isErr()) return envResult;
6106
6199
  cliLog.success("Turso database setup completed successfully!");
6107
6200
  return Result.ok(void 0);
@@ -6118,12 +6211,12 @@ async function setupDatabase(config, cliInput) {
6118
6211
  const { database, dbSetup, backend, projectDir } = config;
6119
6212
  if (backend === "convex" || database === "none") {
6120
6213
  if (backend !== "convex") {
6121
- const serverDbDir = path.join(projectDir, "apps/server/src/db");
6214
+ const serverDbDir = path.join(projectDir, appDir(config, "server"), "src/db");
6122
6215
  if (await fs.pathExists(serverDbDir)) await fs.remove(serverDbDir);
6123
6216
  }
6124
6217
  return;
6125
6218
  }
6126
- const dbPackageDir = path.join(projectDir, "packages/db");
6219
+ const dbPackageDir = path.join(projectDir, pkgDir(config, "db"));
6127
6220
  if (!await fs.pathExists(dbPackageDir)) return;
6128
6221
  async function runSetup(setupFn) {
6129
6222
  const result = await setupFn();
@@ -6338,7 +6431,7 @@ async function displayPostInstallInstructions(config) {
6338
6431
  if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
6339
6432
  output += `\n${pc$1.bold("Like kubojs?")} Please consider giving us a star\n on GitHub:\n`;
6340
6433
  output += pc$1.cyan("https://github.com/albuquerquesz/kubo");
6341
- cliConsola.box(output);
6434
+ cliConsola.box(relocateLayoutPaths(output, config));
6342
6435
  }
6343
6436
  function getNativeInstructions(isConvex, isBackendSelf, frontend, runCmd) {
6344
6437
  const envVar = isConvex ? "EXPO_PUBLIC_CONVEX_URL" : "EXPO_PUBLIC_SERVER_URL";
@@ -6731,6 +6824,13 @@ async function createProjectHandler(input, options = {}) {
6731
6824
  process.exit(1);
6732
6825
  });
6733
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
+ }
6734
6834
  /** Stack-selecting flags; their presence means the user chose the stack via flags, not prompts. */
6735
6835
  const STACK_FLAG_KEYS = [
6736
6836
  "database",
@@ -6768,6 +6868,7 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
6768
6868
  if (!isSilent() && input.renderTitle !== false) renderTitle();
6769
6869
  if (!isSilent()) intro(cliColors.bright("Creating a new kubojs project"));
6770
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)));
6771
6872
  let currentPathInput;
6772
6873
  if (isSilent()) currentPathInput = yield* Result.await(resolveProjectNameForSilent(input));
6773
6874
  else if (input.yes && input.projectName) currentPathInput = input.projectName;
@@ -6897,6 +6998,10 @@ async function createProjectHandlerInternal(input, startTime, timeScaffolded) {
6897
6998
  ...config,
6898
6999
  dbSetupOptions: effectiveDbSetupOptions
6899
7000
  };
7001
+ if (layout) config = {
7002
+ ...config,
7003
+ layout
7004
+ };
6900
7005
  if (!input.yolo) {
6901
7006
  const addonsValidationResult = validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth, config.backend, config.runtime);
6902
7007
  if (addonsValidationResult.isErr()) return Result.err(new CLIError({ message: addonsValidationResult.error.message }));
@@ -7106,12 +7211,17 @@ const router = t.router({
7106
7211
  renderTitle: z.boolean().optional(),
7107
7212
  disableAnalytics: z.boolean().optional().default(false).describe("Disable analytics"),
7108
7213
  manualDb: z.boolean().optional().default(false).describe("Skip automatic/manual database setup prompt and use manual setup"),
7109
- 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)")
7110
7217
  })])).mutation(async ({ input }) => {
7111
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;
7112
7221
  const result = await createProjectHandler({
7113
7222
  projectName,
7114
- ...options
7223
+ ...options,
7224
+ layout: layoutResult.value
7115
7225
  });
7116
7226
  if (options.verbose || options.dryRun) return result;
7117
7227
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-kubojs",
3
- "version": "0.5.1",
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.1",
77
- "@kubojs/types": "^0.5.1",
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",