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 +268 -136
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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(
|
|
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
|
|
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
|
|
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(
|
|
3748
|
+
return requiredKeys.every((key) => Boolean(readProjectEnvVar(cwd, key)));
|
|
3698
3749
|
}
|
|
3699
3750
|
function readNonEmptyEnvVar(cwd, key) {
|
|
3700
|
-
const value =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
27501
|
-
|
|
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
|
-
|
|
27522
|
-
|
|
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
|
-
|
|
27533
|
-
|
|
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
|
-
|
|
27542
|
-
|
|
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
|
-
|
|
27574
|
-
|
|
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
|
-
|
|
27591
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
27731
|
-
|
|
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
|
-
|
|
27737
|
-
|
|
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
|
|
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
|
-
|
|
27765
|
-
|
|
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
|
-
|
|
27788
|
-
|
|
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) => !
|
|
27948
|
+
const missingR2Keys = R2_ENV_KEYS.filter((key) => !readProjectEnvVar(cwd, key)?.trim());
|
|
27834
27949
|
if (options.yes && missingR2Keys.length > 0) {
|
|
27835
|
-
|
|
27836
|
-
|
|
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
|
-
|
|
27850
|
-
|
|
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" && !
|
|
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
|
-
|
|
27869
|
-
|
|
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
|
-
|
|
28112
|
-
|
|
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
|
-
|
|
28127
|
-
|
|
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
|
-
|
|
28153
|
-
|
|
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
|
-
|
|
28263
|
-
|
|
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
|
-
|
|
28305
|
-
|
|
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
|
-
|
|
28328
|
-
|
|
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
|
-
|
|
28336
|
-
|
|
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
|
|
28358
|
-
|
|
28359
|
-
|
|
28360
|
-
|
|
28361
|
-
|
|
28362
|
-
|
|
28363
|
-
|
|
28364
|
-
|
|
28365
|
-
|
|
28366
|
-
|
|
28367
|
-
|
|
28368
|
-
|
|
28369
|
-
|
|
28370
|
-
|
|
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
|
|
28386
|
-
if (!
|
|
28387
|
-
const
|
|
28388
|
-
|
|
28389
|
-
|
|
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
|
|
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(
|
|
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
|
);
|