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 +1 -1
- package/dist/index.d.mts +20 -0
- package/dist/index.mjs +1 -1
- package/dist/{src-QAuuh4xm.mjs → src-CYAa_B_y.mjs} +330 -73
- package/package.json +3 -3
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-
|
|
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-
|
|
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, "
|
|
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
|
-
|
|
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, "
|
|
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 =
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
4761
|
-
const targetApp =
|
|
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,
|
|
5102
|
+
async function writeEnvFile$3(projectDir, project, config) {
|
|
4863
5103
|
return Result.tryPromise({
|
|
4864
5104
|
try: async () => {
|
|
4865
|
-
const targetApp =
|
|
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
|
|
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, "
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
5287
|
+
async function writeEnvFile$2(projectDir, project, config) {
|
|
5048
5288
|
return Result.tryPromise({
|
|
5049
5289
|
try: async () => {
|
|
5050
|
-
const targetApp =
|
|
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,
|
|
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 =
|
|
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
|
|
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 =
|
|
5353
|
+
const target = getDbEnvTargetApp(config);
|
|
5114
5354
|
if (shouldSkipExternalCommands()) {
|
|
5115
|
-
const envResult = await writeEnvFile$2(projectDir,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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 =
|
|
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,
|
|
5619
|
+
async function writeEnvFile$1(projectDir, project, config) {
|
|
5380
5620
|
return Result.tryPromise({
|
|
5381
5621
|
try: async () => {
|
|
5382
|
-
const targetApp =
|
|
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
|
|
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, "
|
|
5420
|
-
const target =
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
5719
|
+
async function writeSupabaseEnvFile(projectDir, project, databaseUrl) {
|
|
5480
5720
|
return Result.tryPromise({
|
|
5481
5721
|
try: async () => {
|
|
5482
|
-
const targetApp =
|
|
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
|
|
5567
|
-
const targetApp =
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
6025
|
+
async function writeEnvFile(projectDir, project, config) {
|
|
5786
6026
|
return Result.tryPromise({
|
|
5787
6027
|
try: async () => {
|
|
5788
|
-
const targetApp =
|
|
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
|
|
5819
|
-
const targetApp =
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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, "
|
|
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, "
|
|
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.
|
|
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.
|
|
77
|
-
"@kubojs/types": "^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",
|