betterstart-cli 0.0.93 → 0.0.94

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.js CHANGED
@@ -123,7 +123,10 @@ function createInitCommand(runtime) {
123
123
  ).option("--database-provider <provider>", "Database provider: vercel, railway, or manual").option(
124
124
  "--storage-provider <provider>",
125
125
  "Storage provider: vercel-blob, railway-bucket, r2, or local"
126
- ).option("--deploy-provider <provider>", "Deploy provider: vercel, railway, or none").option("--database-plan <id>", "Neon plan id for a Vercel-provisioned database").option("--railway-workspace <id-or-name>", "Railway workspace for a new project").option("--railway-project <id-or-name>", "Existing Railway project to intentionally reuse").option("--railway-environment <id-or-name>", "Railway environment (default: production)").option("--railway-service <id-or-name>", "Railway app service (default: web)").option(
126
+ ).option("--deploy-provider <provider>", "Deploy provider: vercel, railway, or none").option(
127
+ "--use-existing-resources",
128
+ "Require database/storage credentials from existing project env files; never provision them"
129
+ ).option("--database-plan <id>", "Neon plan id for a Vercel-provisioned database").option("--railway-workspace <id-or-name>", "Railway workspace for a new project").option("--railway-project <id-or-name>", "Existing Railway project to intentionally reuse").option("--railway-environment <id-or-name>", "Railway environment (default: production)").option("--railway-service <id-or-name>", "Railway app service (default: web)").option(
127
130
  "--railway-bucket-region <region>",
128
131
  "Railway bucket region: sjc, iad, ams, or sin (default: iad)"
129
132
  ).option("--force", "Overwrite all existing Admin files (nuclear option)").addHelpText(
@@ -1778,6 +1781,21 @@ function spawnAsync(cmd, args, cwd) {
1778
1781
  child.on("error", reject);
1779
1782
  });
1780
1783
  }
1784
+ function isUnexpectedPnpmStoreError(error) {
1785
+ return error instanceof Error && error.message.includes("ERR_PNPM_UNEXPECTED_STORE");
1786
+ }
1787
+ async function runDependencyInstall(pm, args, cwd, recovery) {
1788
+ try {
1789
+ await spawnAsync(pm, args, cwd);
1790
+ } catch (error) {
1791
+ if (pm !== "pnpm" || recovery.attempted || !isUnexpectedPnpmStoreError(error)) {
1792
+ throw error;
1793
+ }
1794
+ recovery.attempted = true;
1795
+ await spawnAsync("pnpm", ["install", "--force"], cwd);
1796
+ await spawnAsync(pm, args, cwd);
1797
+ }
1798
+ }
1781
1799
  function unique(values) {
1782
1800
  return Array.from(new Set(values));
1783
1801
  }
@@ -1824,11 +1842,22 @@ async function installDependenciesAsync({
1824
1842
  ensurePnpmAllowedBuilds(cwd, installPlan.pnpmAllowedBuilds ?? []);
1825
1843
  ensurePnpmPackageExtensions(cwd, installPlan.dependencies);
1826
1844
  }
1845
+ const pnpmStoreRecovery = { attempted: false };
1827
1846
  if (installPlan.dependencies.length > 0) {
1828
- await spawnAsync(pm, buildAddArgs(pm, installPlan.dependencies, false), cwd);
1847
+ await runDependencyInstall(
1848
+ pm,
1849
+ buildAddArgs(pm, installPlan.dependencies, false),
1850
+ cwd,
1851
+ pnpmStoreRecovery
1852
+ );
1829
1853
  }
1830
1854
  if (installPlan.devDependencies.length > 0) {
1831
- await spawnAsync(pm, buildAddArgs(pm, installPlan.devDependencies, true), cwd);
1855
+ await runDependencyInstall(
1856
+ pm,
1857
+ buildAddArgs(pm, installPlan.devDependencies, true),
1858
+ cwd,
1859
+ pnpmStoreRecovery
1860
+ );
1832
1861
  }
1833
1862
  return {
1834
1863
  dependencies: installPlan.dependencies,
@@ -3289,6 +3318,13 @@ function walkSlot(field, fieldPath, errors) {
3289
3318
  // core-engine/utils/env.ts
3290
3319
  import fs10 from "fs";
3291
3320
  import path12 from "path";
3321
+ import { parseEnv } from "util";
3322
+ var DEVELOPMENT_ENV_FILES = [
3323
+ ".env.development.local",
3324
+ ".env.local",
3325
+ ".env.development",
3326
+ ".env"
3327
+ ];
3292
3328
  function readEnvVar(cwd, key) {
3293
3329
  const envPath = path12.join(cwd, ".env.local");
3294
3330
  if (!fs10.existsSync(envPath)) {
@@ -3308,6 +3344,21 @@ function readEnvVar(cwd, key) {
3308
3344
  }
3309
3345
  return void 0;
3310
3346
  }
3347
+ function resolveProjectEnvVar(cwd, key) {
3348
+ for (const envFile of DEVELOPMENT_ENV_FILES) {
3349
+ const envPath = path12.join(cwd, envFile);
3350
+ if (!fs10.existsSync(envPath)) continue;
3351
+ try {
3352
+ const value = parseEnv(fs10.readFileSync(envPath, "utf-8"))[key]?.trim();
3353
+ if (value) return { source: envFile, value };
3354
+ } catch {
3355
+ }
3356
+ }
3357
+ return void 0;
3358
+ }
3359
+ function readProjectEnvVar(cwd, key) {
3360
+ return resolveProjectEnvVar(cwd, key)?.value;
3361
+ }
3311
3362
  function parseEnvValue(rawValue) {
3312
3363
  const value = rawValue.trim();
3313
3364
  const quote2 = value[0];
@@ -3694,10 +3745,10 @@ function hasRequiredIntegrationEnv(definition, cwd) {
3694
3745
  const requiredKeys = definition.envSections.flatMap(
3695
3746
  (section) => section.vars.filter((entry) => entry.value === "").map((entry) => entry.key)
3696
3747
  );
3697
- return requiredKeys.every((key) => Boolean(readEnvVar(cwd, key)));
3748
+ return requiredKeys.every((key) => Boolean(readProjectEnvVar(cwd, key)));
3698
3749
  }
3699
3750
  function readNonEmptyEnvVar(cwd, key) {
3700
- const value = readEnvVar(cwd, key)?.trim();
3751
+ const value = readProjectEnvVar(cwd, key)?.trim();
3701
3752
  return value ? value : void 0;
3702
3753
  }
3703
3754
  async function resolveTextEnvValue(options) {
@@ -4319,7 +4370,8 @@ async function installIntegrations({
4319
4370
  pm,
4320
4371
  integrationIds,
4321
4372
  interactive,
4322
- includeBiome
4373
+ includeBiome,
4374
+ dependenciesInstalled = false
4323
4375
  }) {
4324
4376
  const orderedIntegrationIds = resolveIntegrationInstallOrder(integrationIds);
4325
4377
  const installedIntegrationIds = normalizeInstalledIntegrations(config);
@@ -4397,7 +4449,7 @@ async function installIntegrations({
4397
4449
  (dependency) => !currentPlan.devDependencies.includes(dependency)
4398
4450
  )
4399
4451
  };
4400
- if (dependencyDiff.dependencies.length > 0 || dependencyDiff.devDependencies.length > 0) {
4452
+ if (!dependenciesInstalled && (dependencyDiff.dependencies.length > 0 || dependencyDiff.devDependencies.length > 0)) {
4401
4453
  const result = await installDependenciesAsync({
4402
4454
  cwd,
4403
4455
  pm,
@@ -19444,7 +19496,8 @@ async function installPresets({
19444
19496
  config,
19445
19497
  pm,
19446
19498
  presetIds,
19447
- includeBiome
19499
+ includeBiome,
19500
+ dependenciesInstalled = false
19448
19501
  }) {
19449
19502
  const orderedPresetIds = resolvePresetInstallOrder(presetIds);
19450
19503
  const installedPresetIds = normalizeInstalledPresets(config);
@@ -19479,7 +19532,7 @@ async function installPresets({
19479
19532
  (dependency) => !currentPlan.devDependencies.includes(dependency)
19480
19533
  )
19481
19534
  };
19482
- if (dependencyDiff.dependencies.length > 0 || dependencyDiff.devDependencies.length > 0) {
19535
+ if (!dependenciesInstalled && (dependencyDiff.dependencies.length > 0 || dependencyDiff.devDependencies.length > 0)) {
19483
19536
  const result = await installDependenciesAsync({
19484
19537
  cwd,
19485
19538
  pm,
@@ -22834,7 +22887,7 @@ async function promptPresets(cwd, options = {}) {
22834
22887
  mergeIntegrationConfig(await collectIntegrationConfig(cwd, ["railway-bucket"]));
22835
22888
  }
22836
22889
  } else if (storage === "vercel-blob") {
22837
- const existingToken = readEnvVar(cwd, "BLOB_READ_WRITE_TOKEN")?.trim();
22890
+ const existingToken = readProjectEnvVar(cwd, "BLOB_READ_WRITE_TOKEN")?.trim();
22838
22891
  const flow = !existingToken && options.provisionVercelBlob ? await options.provisionVercelBlob() : void 0;
22839
22892
  if (flow?.ok && flow.token) {
22840
22893
  persistBlobReadWriteToken(cwd, flow.token);
@@ -22947,6 +23000,14 @@ function validateInitProviderFlags(options) {
22947
23000
  if (options.databasePlan && options.databaseProvider && options.databaseProvider !== "vercel") {
22948
23001
  throw new Error("--database-plan can only be used with --database-provider vercel.");
22949
23002
  }
23003
+ if (options.useExistingResources && !options.yes) {
23004
+ throw new Error("--use-existing-resources requires --yes.");
23005
+ }
23006
+ if (options.useExistingResources && options.databaseProvider && options.databaseProvider !== "manual") {
23007
+ throw new Error(
23008
+ "--use-existing-resources requires --database-provider manual for an existing DATABASE_URL."
23009
+ );
23010
+ }
22950
23011
  }
22951
23012
  function resolveFlagStorageProvider(explicitProvider, integrations) {
22952
23013
  const inferred = Array.from(
@@ -27493,20 +27554,62 @@ function removeExistingAdminPaths(cwd, namespaces) {
27493
27554
  }
27494
27555
  return removed;
27495
27556
  }
27557
+ function writeInitJson(context, payload) {
27558
+ context.restoreStdout?.();
27559
+ context.restoreStdout = void 0;
27560
+ context.written = true;
27561
+ console.log(JSON.stringify(payload, null, 2));
27562
+ }
27496
27563
  async function runInitCommand(name, options) {
27497
- let restoreStdout;
27564
+ const jsonContext = {
27565
+ restoreStdout: options.json ? redirectStdoutToStderr() : void 0,
27566
+ written: false
27567
+ };
27568
+ try {
27569
+ await runInitCommandInternal(name, options, jsonContext);
27570
+ } catch (error) {
27571
+ if (options.json && !jsonContext.written) {
27572
+ const message = error instanceof Error ? error.message : String(error);
27573
+ writeInitJson(jsonContext, {
27574
+ success: false,
27575
+ phase: "initialization",
27576
+ error: {
27577
+ code: "UNEXPECTED_INIT_ERROR",
27578
+ message: redactSecrets(message)
27579
+ }
27580
+ });
27581
+ }
27582
+ throw error;
27583
+ } finally {
27584
+ jsonContext.restoreStdout?.();
27585
+ }
27586
+ }
27587
+ async function runInitCommandInternal(name, options, jsonContext) {
27588
+ const exitInit = (phase, message, code = "INIT_FAILED") => {
27589
+ if (options.json) {
27590
+ writeInitJson(jsonContext, {
27591
+ success: false,
27592
+ phase,
27593
+ error: {
27594
+ code,
27595
+ message: redactSecrets(message)
27596
+ }
27597
+ });
27598
+ }
27599
+ process.exit(1);
27600
+ };
27498
27601
  if (options.json) {
27499
27602
  if (!options.yes) {
27500
- p26.log.error("--json requires --yes.");
27501
- process.exit(1);
27603
+ const message = "--json requires --yes.";
27604
+ p26.log.error(message);
27605
+ exitInit("validation", message, "JSON_REQUIRES_YES");
27502
27606
  }
27503
- restoreStdout = redirectStdoutToStderr();
27504
27607
  }
27505
27608
  installPromptCheckmarks();
27506
27609
  const disposeCancelGuard = installSetupCancelGuard();
27507
27610
  renderInitBanner();
27508
- let selectedPresets;
27509
- let selectedIntegrations;
27611
+ let selectedPresets = [];
27612
+ let selectedIntegrations = [];
27510
27613
  let flagStorageProvider;
27511
27614
  try {
27512
27615
  validateInitProviderFlags(options);
@@ -27518,8 +27621,9 @@ async function runInitCommand(name, options) {
27518
27621
  }
27519
27622
  } catch (error) {
27520
27623
  disposeCancelGuard();
27521
- p26.log.error(error instanceof Error ? error.message : String(error));
27522
- process.exit(1);
27624
+ const message = error instanceof Error ? error.message : String(error);
27625
+ p26.log.error(message);
27626
+ exitInit("validation", message, "INVALID_OPTIONS");
27523
27627
  }
27524
27628
  let cwd = process.cwd();
27525
27629
  let projectName = path52.basename(cwd);
@@ -27529,8 +27633,9 @@ async function runInitCommand(name, options) {
27529
27633
  try {
27530
27634
  namespace = validateAdminNamespace(options.namespace);
27531
27635
  } catch (error) {
27532
- p26.log.error(error instanceof Error ? error.message : String(error));
27533
- process.exit(1);
27636
+ const message = error instanceof Error ? error.message : String(error);
27637
+ p26.log.error(message);
27638
+ exitInit("validation", message, "INVALID_NAMESPACE");
27534
27639
  }
27535
27640
  }
27536
27641
  let projectPrompt;
@@ -27538,8 +27643,9 @@ async function runInitCommand(name, options) {
27538
27643
  try {
27539
27644
  projectPrompt = options.yes ? resolveNonInteractiveProject(name) : await promptProject(name);
27540
27645
  } catch (error) {
27541
- p26.log.error(error instanceof Error ? error.message : String(error));
27542
- process.exit(1);
27646
+ const message = error instanceof Error ? error.message : String(error);
27647
+ p26.log.error(message);
27648
+ exitInit("project", message, "PROJECT_SETUP_FAILED");
27543
27649
  }
27544
27650
  }
27545
27651
  if (!options.yes && !options.namespace) {
@@ -27570,8 +27676,9 @@ async function runInitCommand(name, options) {
27570
27676
  if (project2.isExisting) {
27571
27677
  srcDir = project2.hasSrcDir;
27572
27678
  if (!project2.hasTypeScript) {
27573
- p26.log.error("TypeScript is required. Please add a tsconfig.json first.");
27574
- process.exit(1);
27679
+ const message = "TypeScript is required. Please add a tsconfig.json first.";
27680
+ p26.log.error(message);
27681
+ exitInit("project", message, "TYPESCRIPT_REQUIRED");
27575
27682
  }
27576
27683
  if (forceMode) {
27577
27684
  const nuked = removeExistingAdminPaths(cwd, await resolveForceInitNamespaces(cwd, namespace));
@@ -27587,10 +27694,9 @@ async function runInitCommand(name, options) {
27587
27694
  );
27588
27695
  p26.note(conflictLines.join("\n"), pc10.yellow("Conflicts"));
27589
27696
  if (options.yes) {
27590
- p26.log.error(
27591
- "Can't continue with --yes while admin files conflict. Re-run with --force to remove them first."
27592
- );
27593
- process.exit(1);
27697
+ const message = "Can't continue with --yes while admin files conflict. Re-run with --force to remove them first.";
27698
+ p26.log.error(message);
27699
+ exitInit("project", message, "ADMIN_FILES_CONFLICT");
27594
27700
  }
27595
27701
  const proceed = await p26.confirm({
27596
27702
  message: [
@@ -27665,7 +27771,7 @@ async function runInitCommand(name, options) {
27665
27771
  ${pc10.cyan(`npx create-next-app@latest ${freshProject.projectName} --typescript --tailwind --app`)}
27666
27772
  Then run ${pc10.cyan("betterstart init")} inside it.`
27667
27773
  );
27668
- process.exit(1);
27774
+ exitInit("project", createNextAppResult.error, "CREATE_NEXT_APP_FAILED");
27669
27775
  }
27670
27776
  cwd = path52.resolve(cwd, freshProject.projectName);
27671
27777
  const hasPackageJson = fs41.existsSync(path52.join(cwd, "package.json"));
@@ -27683,7 +27789,11 @@ async function runInitCommand(name, options) {
27683
27789
  ${pc10.cyan(manualCmd)}
27684
27790
  Then run ${pc10.cyan("betterstart init")} inside it.`
27685
27791
  );
27686
- process.exit(1);
27792
+ exitInit(
27793
+ "project",
27794
+ "create-next-app completed but the project was not created.",
27795
+ "CREATE_NEXT_APP_INCOMPLETE"
27796
+ );
27687
27797
  }
27688
27798
  createNextAppSpinner.clear();
27689
27799
  project2 = detectProject(cwd, namespace);
@@ -27695,7 +27805,8 @@ async function runInitCommand(name, options) {
27695
27805
  let railwayDatabaseServiceName;
27696
27806
  let railwayBucketResourceName;
27697
27807
  let railwayBucketUrlStyle;
27698
- const existingDbUrl = readExistingDbUrl(cwd);
27808
+ const existingDatabase = readExistingDbUrl(cwd);
27809
+ const existingDbUrl = existingDatabase?.value;
27699
27810
  const getRailwaySession = () => {
27700
27811
  railwaySessionPromise ??= createRailwaySession({
27701
27812
  cwd,
@@ -27727,24 +27838,28 @@ async function runInitCommand(name, options) {
27727
27838
  try {
27728
27839
  validateResolvedDatabaseProvider(databaseProvider, options);
27729
27840
  } catch (error) {
27730
- p26.log.error(error instanceof Error ? error.message : String(error));
27731
- process.exit(1);
27841
+ const message = error instanceof Error ? error.message : String(error);
27842
+ p26.log.error(message);
27843
+ exitInit("providers", message, "INVALID_DATABASE_PROVIDER");
27732
27844
  }
27733
27845
  if (databaseProvider === "manual") {
27734
27846
  const candidate = options.databaseUrl ?? existingDbUrl ?? promptedManualUrl;
27735
27847
  if (candidate && !isValidDbUrl(candidate)) {
27736
- p26.log.error(
27737
- `Invalid database URL. Must start with ${pc10.cyan("postgres://")} or ${pc10.cyan("postgresql://")}`
27738
- );
27739
- process.exit(1);
27848
+ const message = "Invalid database URL. Must start with postgres:// or postgresql://";
27849
+ p26.log.error(message);
27850
+ exitInit("providers", message, "INVALID_DATABASE_URL");
27740
27851
  }
27741
27852
  if (candidate) {
27742
27853
  databaseUrl = candidate;
27743
27854
  if (existingDbUrl === candidate && !options.databaseUrl) {
27744
27855
  p26.log.info(
27745
- `Using the existing DATABASE_URL from .env.local ${pc10.dim(`(${maskDbUrl(candidate)})`)}`
27856
+ `Using the existing DATABASE_URL from ${existingDatabase?.source ?? "project env"} ${pc10.dim(`(${maskDbUrl(candidate)})`)}`
27746
27857
  );
27747
27858
  }
27859
+ } else if (options.useExistingResources) {
27860
+ const message = "DATABASE_URL is missing from the project environment files.";
27861
+ p26.log.error(message);
27862
+ exitInit("providers", message, "MISSING_DATABASE_URL");
27748
27863
  } else if (!options.yes) {
27749
27864
  databaseUrl = await promptConnectionString();
27750
27865
  }
@@ -27761,10 +27876,9 @@ async function runInitCommand(name, options) {
27761
27876
  persistDatabaseUrl(cwd, databaseUrl);
27762
27877
  dismissVercelSignedInNote = flow.dismissSignedInNote;
27763
27878
  } else if (options.yes) {
27764
- p26.log.error(
27765
- flow.ok ? "Created a Neon database, but DATABASE_URL could not be retrieved from Vercel." : "Vercel database provisioning did not complete."
27766
- );
27767
- process.exit(1);
27879
+ const message = flow.ok ? "Created a Neon database, but DATABASE_URL could not be retrieved from Vercel." : "Vercel database provisioning did not complete.";
27880
+ p26.log.error(message);
27881
+ exitInit("providers", message, "DATABASE_PROVISIONING_FAILED");
27768
27882
  } else if (flow.ok) {
27769
27883
  openBrowserVercelNeonResource(flow.dashboardUrl ?? flow.resourceUrl);
27770
27884
  databaseUrl = await promptConnectionString();
@@ -27784,8 +27898,9 @@ async function runInitCommand(name, options) {
27784
27898
  } catch (error) {
27785
27899
  const message = error instanceof Error ? error.message : String(error);
27786
27900
  if (options.yes) {
27787
- p26.log.error(`Railway database provisioning failed: ${message}`);
27788
- process.exit(1);
27901
+ const failureMessage = `Railway database provisioning failed: ${message}`;
27902
+ p26.log.error(failureMessage);
27903
+ exitInit("providers", failureMessage, "DATABASE_PROVISIONING_FAILED");
27789
27904
  }
27790
27905
  p26.log.warn(`Railway database provisioning failed: ${message}`);
27791
27906
  p26.log.info("Falling back to a manual database connection string.");
@@ -27830,27 +27945,39 @@ async function runInitCommand(name, options) {
27830
27945
  storage
27831
27946
  };
27832
27947
  if (storage === "r2") {
27833
- const missingR2Keys = R2_ENV_KEYS.filter((key) => !readEnvVar(cwd, key)?.trim());
27948
+ const missingR2Keys = R2_ENV_KEYS.filter((key) => !readProjectEnvVar(cwd, key)?.trim());
27834
27949
  if (options.yes && missingR2Keys.length > 0) {
27835
- p26.log.error(
27836
- `Cloudflare R2 is missing required environment variables: ${missingR2Keys.join(", ")}.`
27837
- );
27838
- process.exit(1);
27950
+ const message = `Cloudflare R2 is missing required environment variables: ${missingR2Keys.join(", ")}.`;
27951
+ p26.log.error(message);
27952
+ exitInit("providers", message, "MISSING_STORAGE_CREDENTIALS");
27839
27953
  }
27840
27954
  mergeCollectedIntegrationConfig(await collectIntegrationConfig(cwd, ["r2"]));
27841
27955
  } else if (storage === "railway-bucket") {
27842
27956
  if (hasRailwayBucketConfig(cwd)) {
27843
27957
  mergeCollectedIntegrationConfig(await collectIntegrationConfig(cwd, ["railway-bucket"]));
27958
+ } else if (options.useExistingResources) {
27959
+ const missingKeys = RAILWAY_BUCKET_ENV_KEYS.filter(
27960
+ (key) => !readProjectEnvVar(cwd, key)?.trim()
27961
+ );
27962
+ const message = `Railway Bucket is missing required environment variables: ${missingKeys.join(", ")}.`;
27963
+ p26.log.error(message);
27964
+ exitInit("providers", message, "MISSING_STORAGE_CREDENTIALS");
27844
27965
  } else {
27845
27966
  const flow = await provisionRailwayBucket();
27846
27967
  if (flow.ok && flow.config) {
27847
27968
  mergeCollectedIntegrationConfig(flow.config);
27848
27969
  } else if (options.yes) {
27849
- p26.log.error("Railway bucket provisioning did not complete.");
27850
- process.exit(1);
27970
+ const message = "Railway bucket provisioning did not complete.";
27971
+ p26.log.error(message);
27972
+ exitInit("providers", message, "STORAGE_PROVISIONING_FAILED");
27851
27973
  }
27852
27974
  }
27853
- } else if (storage === "vercel-blob" && !readEnvVar(cwd, "BLOB_READ_WRITE_TOKEN")?.trim()) {
27975
+ } else if (storage === "vercel-blob" && !readProjectEnvVar(cwd, "BLOB_READ_WRITE_TOKEN")?.trim()) {
27976
+ if (options.useExistingResources) {
27977
+ const message = "BLOB_READ_WRITE_TOKEN is missing from the project environment files.";
27978
+ p26.log.error(message);
27979
+ exitInit("providers", message, "MISSING_STORAGE_CREDENTIALS");
27980
+ }
27854
27981
  const flow = await runVercelBlobFlow({
27855
27982
  cwd,
27856
27983
  projectName,
@@ -27865,8 +27992,9 @@ async function runInitCommand(name, options) {
27865
27992
  });
27866
27993
  collectedIntegrationConfig.overwriteKeys.add("BLOB_READ_WRITE_TOKEN");
27867
27994
  } else if (options.yes) {
27868
- p26.log.error("Vercel Blob provisioning did not complete.");
27869
- process.exit(1);
27995
+ const message = "Vercel Blob provisioning did not complete.";
27996
+ p26.log.error(message);
27997
+ exitInit("providers", message, "STORAGE_PROVISIONING_FAILED");
27870
27998
  } else {
27871
27999
  p26.log.warn(
27872
28000
  "Continuing without a Blob token \u2014 add BLOB_READ_WRITE_TOKEN to .env.local from the store on vercel.com/dashboard/stores."
@@ -27903,6 +28031,45 @@ async function runInitCommand(name, options) {
27903
28031
  config.paths = config.frameworkConfig.next.paths;
27904
28032
  config.database.migrationsDir = deriveMigrationsDir(namespace);
27905
28033
  const s = spinner2();
28034
+ const coreDependencyPlan = getDependencyPlan(
28035
+ presetSelection.presets,
28036
+ presetSelection.integrations,
28037
+ project2.linter.type === "none"
28038
+ );
28039
+ const cliDependencyPlan = getCliDependencySyncPlan(cwd);
28040
+ s.start("Installing dependencies, This may take a moment");
28041
+ const depsResult = await installDependenciesAsync({
28042
+ cwd,
28043
+ pm,
28044
+ ...isFreshProject && pm === "pnpm" ? { pnpmAllowedBuilds: [...FRESH_PNPM_ALLOWED_BUILDS] } : {},
28045
+ dependencies: Array.from(
28046
+ /* @__PURE__ */ new Set([...coreDependencyPlan.dependencies, ...cliDependencyPlan?.dependencies ?? []])
28047
+ ),
28048
+ devDependencies: Array.from(
28049
+ /* @__PURE__ */ new Set([
28050
+ ...coreDependencyPlan.devDependencies,
28051
+ ...cliDependencyPlan?.devDependencies ?? []
28052
+ ])
28053
+ )
28054
+ });
28055
+ if (depsResult.success) {
28056
+ s.stop("");
28057
+ } else {
28058
+ const message = redactSecrets(depsResult.error ?? "Unknown dependency installation error");
28059
+ s.stop("Failed to install dependencies");
28060
+ p26.log.warn(message);
28061
+ p26.log.info(
28062
+ `You can install them manually:
28063
+ ${pc10.cyan(`${pm} add ${depsResult.dependencies.join(" ")}`)}
28064
+ ${pc10.cyan(`${pm} add -D ${depsResult.devDeps.join(" ")}`)}`
28065
+ );
28066
+ disposeCancelGuard();
28067
+ exitInit(
28068
+ "dependencies",
28069
+ message,
28070
+ message.includes("ERR_PNPM_UNEXPECTED_STORE") ? "PNPM_UNEXPECTED_STORE" : "DEPENDENCY_INSTALL_FAILED"
28071
+ );
28072
+ }
27906
28073
  s.start("Directory structure");
27907
28074
  scaffoldBase({
27908
28075
  cwd,
@@ -27970,36 +28137,6 @@ async function runInitCommand(name, options) {
27970
28137
  }
27971
28138
  }
27972
28139
  }
27973
- const coreDependencyPlan = getDependencyPlan([], [], project2.linter.type === "none");
27974
- const cliDependencyPlan = getCliDependencySyncPlan(cwd);
27975
- s.start("Installing dependencies, This may take a moment");
27976
- const depsResult = await installDependenciesAsync({
27977
- cwd,
27978
- pm,
27979
- ...isFreshProject && pm === "pnpm" ? { pnpmAllowedBuilds: [...FRESH_PNPM_ALLOWED_BUILDS] } : {},
27980
- dependencies: Array.from(
27981
- /* @__PURE__ */ new Set([...coreDependencyPlan.dependencies, ...cliDependencyPlan?.dependencies ?? []])
27982
- ),
27983
- devDependencies: Array.from(
27984
- /* @__PURE__ */ new Set([
27985
- ...coreDependencyPlan.devDependencies,
27986
- ...cliDependencyPlan?.devDependencies ?? []
27987
- ])
27988
- )
27989
- });
27990
- if (depsResult.success) {
27991
- s.stop("");
27992
- } else {
27993
- s.stop("Failed to install dependencies");
27994
- p26.log.warn(depsResult.error ?? "Unknown error");
27995
- p26.log.info(
27996
- `You can install them manually:
27997
- ${pc10.cyan(`${pm} add ${depsResult.dependencies.join(" ")}`)}
27998
- ${pc10.cyan(`${pm} add -D ${depsResult.devDeps.join(" ")}`)}`
27999
- );
28000
- disposeCancelGuard();
28001
- process.exit(1);
28002
- }
28003
28140
  process.stdout.write("\x1B[2A\x1B[J");
28004
28141
  s.start("Generating core schemas");
28005
28142
  const coreSchemasResult = scaffoldCoreSchemas({ cwd, config });
@@ -28013,7 +28150,8 @@ async function runInitCommand(name, options) {
28013
28150
  pm,
28014
28151
  presetIds: presetSelection.presets,
28015
28152
  interactive: !options.yes,
28016
- includeBiome: project2.linter.type === "none"
28153
+ includeBiome: project2.linter.type === "none",
28154
+ dependenciesInstalled: true
28017
28155
  });
28018
28156
  })() : Promise.resolve({ installed: [], skipped: [], warnings: [], config });
28019
28157
  const resolvedPresetInstallResult = await presetInstallResult;
@@ -28032,7 +28170,8 @@ async function runInitCommand(name, options) {
28032
28170
  // .env.local, so the install runs without prompting (rule: no
28033
28171
  // mid-scaffold input).
28034
28172
  interactive: false,
28035
- includeBiome: project2.linter.type === "none"
28173
+ includeBiome: project2.linter.type === "none",
28174
+ dependenciesInstalled: true
28036
28175
  });
28037
28176
  })() : Promise.resolve({
28038
28177
  installed: [],
@@ -28108,8 +28247,9 @@ async function runInitCommand(name, options) {
28108
28247
  clearDbSpinner();
28109
28248
  if (!verification.success) {
28110
28249
  p26.log.warn(verification.error);
28111
- p26.log.error("Database was not reachable. Aborting setup.");
28112
- process.exit(1);
28250
+ const message = "Database was not reachable. Aborting setup.";
28251
+ p26.log.error(message);
28252
+ exitInit("database", verification.error, "DATABASE_UNREACHABLE");
28113
28253
  }
28114
28254
  } else {
28115
28255
  clearDbSpinner();
@@ -28123,8 +28263,9 @@ async function runInitCommand(name, options) {
28123
28263
  const pushError = pushResult.error ?? "Unknown error";
28124
28264
  p26.log.warn(pushError);
28125
28265
  if (isDatabaseReachabilityError(pushError)) {
28126
- p26.log.error("Database was not reachable. Aborting setup.");
28127
- process.exit(1);
28266
+ const message = "Database was not reachable. Aborting setup.";
28267
+ p26.log.error(message);
28268
+ exitInit("database", pushError, "DATABASE_UNREACHABLE");
28128
28269
  }
28129
28270
  p26.log.info(`You can run it manually: ${pc10.cyan(drizzlePushCommand(pm))}`);
28130
28271
  }
@@ -28149,8 +28290,9 @@ async function runInitCommand(name, options) {
28149
28290
  if (adminCheck.error) {
28150
28291
  p26.log.warn(`Could not verify existing admin account ${pc10.dim(`(${adminCheck.error})`)}`);
28151
28292
  if (isDatabaseReachabilityError(adminCheck.error)) {
28152
- p26.log.error("Database was not reachable. Aborting setup.");
28153
- process.exit(1);
28293
+ const message = "Database was not reachable. Aborting setup.";
28294
+ p26.log.error(message);
28295
+ exitInit("database", adminCheck.error, "DATABASE_UNREACHABLE");
28154
28296
  }
28155
28297
  } else if (adminCheck.existingAdmin) {
28156
28298
  const existingAdminLabel = formatAdminIdentity(adminCheck.existingAdmin);
@@ -28259,8 +28401,9 @@ Run manually: ${pc10.cyan(betterstartExecCommand(pm, "seed"))}`,
28259
28401
  pc10.red("Seed failed")
28260
28402
  );
28261
28403
  if (isDatabaseReachabilityError(seedResult.error)) {
28262
- p26.log.error("Database was not reachable. Aborting setup.");
28263
- process.exit(1);
28404
+ const message = "Database was not reachable. Aborting setup.";
28405
+ p26.log.error(message);
28406
+ exitInit("database", seedResult.error, "DATABASE_UNREACHABLE");
28264
28407
  }
28265
28408
  }
28266
28409
  }
@@ -28301,8 +28444,9 @@ Run manually: ${pc10.cyan(betterstartExecCommand(pm, "seed"))}`,
28301
28444
  deployedUrl = deployFlow.url;
28302
28445
  if (!deployFlow.ok) {
28303
28446
  if (options.yes) {
28304
- p26.log.error("Vercel deploy did not complete.");
28305
- process.exit(1);
28447
+ const message = "Vercel deploy did not complete.";
28448
+ p26.log.error(message);
28449
+ exitInit("deployment", message, "DEPLOYMENT_FAILED");
28306
28450
  }
28307
28451
  p26.log.warn("Vercel deploy did not complete; continuing.");
28308
28452
  }
@@ -28324,16 +28468,18 @@ Run manually: ${pc10.cyan(betterstartExecCommand(pm, "seed"))}`,
28324
28468
  if (!deployFlow.ok) {
28325
28469
  if (deployFlow.detail) p26.log.message(pc10.dim(redactSecrets(deployFlow.detail)));
28326
28470
  if (options.yes) {
28327
- p26.log.error("Railway deploy did not complete.");
28328
- process.exit(1);
28471
+ const message = "Railway deploy did not complete.";
28472
+ p26.log.error(message);
28473
+ exitInit("deployment", message, "DEPLOYMENT_FAILED");
28329
28474
  }
28330
28475
  p26.log.warn("Railway deploy did not complete; continuing.");
28331
28476
  }
28332
28477
  } catch (error) {
28333
28478
  const message = error instanceof Error ? error.message : String(error);
28334
28479
  if (options.yes) {
28335
- p26.log.error(`Railway deploy failed: ${message}`);
28336
- process.exit(1);
28480
+ const failureMessage = `Railway deploy failed: ${message}`;
28481
+ p26.log.error(failureMessage);
28482
+ exitInit("deployment", failureMessage, "DEPLOYMENT_FAILED");
28337
28483
  }
28338
28484
  p26.log.warn(`Railway deploy failed: ${message}`);
28339
28485
  }
@@ -28354,26 +28500,20 @@ Run manually: ${pc10.cyan(betterstartExecCommand(pm, "seed"))}`,
28354
28500
  }
28355
28501
  }
28356
28502
  disposeCancelGuard();
28357
- if (options.json && restoreStdout) {
28358
- restoreStdout();
28359
- console.log(
28360
- JSON.stringify(
28361
- {
28362
- namespace,
28363
- configPath: CONFIG_FILE_NAME,
28364
- databaseProvider,
28365
- storageProvider: presetSelection.storage,
28366
- deployProvider,
28367
- presets: presetSelection.presets,
28368
- integrations: presetSelection.integrations,
28369
- adminUrl: adminLoginUrl,
28370
- adminEmail: seedEmail ?? null,
28371
- deployedUrl: deployedUrl ?? null
28372
- },
28373
- null,
28374
- 2
28375
- )
28376
- );
28503
+ if (options.json) {
28504
+ writeInitJson(jsonContext, {
28505
+ success: true,
28506
+ namespace,
28507
+ configPath: CONFIG_FILE_NAME,
28508
+ databaseProvider,
28509
+ storageProvider: presetSelection.storage,
28510
+ deployProvider,
28511
+ presets: presetSelection.presets,
28512
+ integrations: presetSelection.integrations,
28513
+ adminUrl: adminLoginUrl,
28514
+ adminEmail: seedEmail ?? null,
28515
+ deployedUrl: deployedUrl ?? null
28516
+ });
28377
28517
  return;
28378
28518
  }
28379
28519
  p26.outro(`Admin ready at ${adminNamespace.routePath}`);
@@ -28382,21 +28522,13 @@ function isValidDbUrl(url) {
28382
28522
  return url.startsWith("postgres://") || url.startsWith("postgresql://");
28383
28523
  }
28384
28524
  function readExistingDbUrl(cwd) {
28385
- const envPath = path52.join(cwd, ".env.local");
28386
- if (!fs41.existsSync(envPath)) return void 0;
28387
- const content = fs41.readFileSync(envPath, "utf-8");
28388
- for (const line of content.split("\n")) {
28389
- const trimmed = line.trim();
28390
- if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
28391
- const [key, ...rest] = trimmed.split("=");
28392
- if (key?.trim() === "DATABASE_URL") {
28393
- const val = rest.join("=").replace(/^['"]|['"]$/g, "").trim();
28394
- if (val.length > 0 && !val.startsWith("your_") && val !== "postgresql://..." && isValidDbUrl(val)) {
28395
- return val;
28396
- }
28397
- }
28525
+ const resolved = resolveProjectEnvVar(cwd, "DATABASE_URL");
28526
+ if (!resolved) return void 0;
28527
+ const value = resolved.value.trim();
28528
+ if (value.startsWith("your_") || value === "postgresql://..." || !isValidDbUrl(value)) {
28529
+ return void 0;
28398
28530
  }
28399
- return void 0;
28531
+ return { ...resolved, value };
28400
28532
  }
28401
28533
  function maskDbUrl(url) {
28402
28534
  try {
@@ -28438,7 +28570,7 @@ var R2_ENV_KEYS = [
28438
28570
  "BETTERSTART_R2_PUBLIC_URL"
28439
28571
  ];
28440
28572
  function hasRailwayBucketConfig(cwd) {
28441
- return RAILWAY_BUCKET_ENV_KEYS.every((key) => Boolean(readEnvVar(cwd, key)?.trim()));
28573
+ return RAILWAY_BUCKET_ENV_KEYS.every((key) => Boolean(readProjectEnvVar(cwd, key)?.trim()));
28442
28574
  }
28443
28575
  function railwayBucketIntegrationConfig(credentials) {
28444
28576
  return {
@@ -28558,7 +28690,7 @@ main().catch((error) => {
28558
28690
  cwd,
28559
28691
  timeoutMs: 15e3,
28560
28692
  env: {
28561
- DATABASE_URL: databaseUrl
28693
+ DATABASE_URL: databaseUrl.value
28562
28694
  }
28563
28695
  }
28564
28696
  );