betterstart-cli 0.0.73 → 0.0.75
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 +115 -110
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -115,7 +115,7 @@ function createInitCommand(runtime) {
|
|
|
115
115
|
).option("--database-provider <provider>", "Database provider: vercel, railway, or manual").option(
|
|
116
116
|
"--storage-provider <provider>",
|
|
117
117
|
"Storage provider: vercel-blob, railway-bucket, r2, or local"
|
|
118
|
-
).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>", "Railway project to reuse").option("--railway-environment <id-or-name>", "Railway environment (default: production)").option("--railway-service <id-or-name>", "Railway app service (default: web)").option(
|
|
118
|
+
).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(
|
|
119
119
|
"--railway-bucket-region <region>",
|
|
120
120
|
"Railway bucket region: sjc, iad, ams, or sin (default: iad)"
|
|
121
121
|
).option("--force", "Overwrite all existing Admin files (nuclear option)").action(
|
|
@@ -20138,6 +20138,10 @@ function spinner2(options) {
|
|
|
20138
20138
|
};
|
|
20139
20139
|
return {
|
|
20140
20140
|
start: (message = "") => {
|
|
20141
|
+
if (started) {
|
|
20142
|
+
inner.message(fitSpinnerMessage(message));
|
|
20143
|
+
return;
|
|
20144
|
+
}
|
|
20141
20145
|
setStarted(true);
|
|
20142
20146
|
inner.start(fitSpinnerMessage(message));
|
|
20143
20147
|
},
|
|
@@ -22971,6 +22975,16 @@ function parseProjectSummary(value) {
|
|
|
22971
22975
|
}
|
|
22972
22976
|
return { id: project2.id, name: project2.name, workspace };
|
|
22973
22977
|
}
|
|
22978
|
+
var MAX_PROJECT_NAME_ATTEMPTS = 10;
|
|
22979
|
+
function versionedRailwayProjectName(base, attempt) {
|
|
22980
|
+
return attempt <= 1 ? base : `${base}-v${attempt}`;
|
|
22981
|
+
}
|
|
22982
|
+
function railwayProjectNameIsTaken(projects, candidate) {
|
|
22983
|
+
return projects.some((project2) => project2.name.toLowerCase() === candidate.toLowerCase());
|
|
22984
|
+
}
|
|
22985
|
+
function isRailwayProjectNameConflict(detail) {
|
|
22986
|
+
return /already exists|name.*taken|duplicate.*name/i.test(detail);
|
|
22987
|
+
}
|
|
22974
22988
|
async function readRailwayProjectContext(runner, cwd, environment, env) {
|
|
22975
22989
|
const result = await runRailway(runner, ["status", "--environment", environment, "--json"], {
|
|
22976
22990
|
cwd,
|
|
@@ -23039,18 +23053,26 @@ ${result.stderr}`) ?? `Could not link Railway project ${project2}.`
|
|
|
23039
23053
|
);
|
|
23040
23054
|
}
|
|
23041
23055
|
}
|
|
23042
|
-
async function createRailwayProject(runner, cwd, projectName, options) {
|
|
23043
|
-
const
|
|
23044
|
-
|
|
23045
|
-
|
|
23046
|
-
|
|
23047
|
-
|
|
23048
|
-
|
|
23049
|
-
|
|
23050
|
-
|
|
23051
|
-
);
|
|
23056
|
+
async function createRailwayProject(runner, cwd, projectName, existingProjects, options) {
|
|
23057
|
+
const knownProjects = [...existingProjects];
|
|
23058
|
+
for (let attempt = 1; attempt <= MAX_PROJECT_NAME_ATTEMPTS; attempt++) {
|
|
23059
|
+
const candidate = versionedRailwayProjectName(projectName, attempt);
|
|
23060
|
+
if (railwayProjectNameIsTaken(knownProjects, candidate)) continue;
|
|
23061
|
+
const args = ["init", "--name", candidate, "--json"];
|
|
23062
|
+
if (options.workspace) args.push("--workspace", options.workspace);
|
|
23063
|
+
const result = await runRailway(runner, args, { cwd, mode: "capture", env: options.env });
|
|
23064
|
+
const project2 = result.success ? parseProjectSummary(parseRailwayJson(result.stdout)) : void 0;
|
|
23065
|
+
if (project2) return project2;
|
|
23066
|
+
const detail = railwayOutputTail(`${result.stdout}
|
|
23067
|
+
${result.stderr}`);
|
|
23068
|
+
if (!detail || !isRailwayProjectNameConflict(detail)) {
|
|
23069
|
+
throw new Error(detail ?? `Could not create Railway project ${candidate}.`);
|
|
23070
|
+
}
|
|
23071
|
+
knownProjects.push({ id: candidate, name: candidate });
|
|
23052
23072
|
}
|
|
23053
|
-
|
|
23073
|
+
throw new Error(
|
|
23074
|
+
`Could not create a unique Railway project name after ${MAX_PROJECT_NAME_ATTEMPTS} attempts.`
|
|
23075
|
+
);
|
|
23054
23076
|
}
|
|
23055
23077
|
async function ensureRailwayProject(runner, options) {
|
|
23056
23078
|
const environment = options.environment?.trim() || "production";
|
|
@@ -23063,77 +23085,47 @@ async function ensureRailwayProject(runner, options) {
|
|
|
23063
23085
|
env: options.env
|
|
23064
23086
|
});
|
|
23065
23087
|
} else {
|
|
23066
|
-
|
|
23067
|
-
if (
|
|
23088
|
+
let createWorkspace = options.workspace;
|
|
23089
|
+
if (!createWorkspace && options.workspaces?.length === 1) {
|
|
23090
|
+
createWorkspace = options.workspaces[0]?.id;
|
|
23091
|
+
} else if (!createWorkspace && options.interactive && options.workspaces?.length) {
|
|
23068
23092
|
projectSpinner.clear();
|
|
23069
|
-
|
|
23070
|
-
|
|
23071
|
-
|
|
23072
|
-
|
|
23073
|
-
|
|
23074
|
-
|
|
23075
|
-
const createValue = "__betterstart_create_railway_project__";
|
|
23076
|
-
const choice = await p15.select({
|
|
23077
|
-
message: "Choose a Railway project",
|
|
23078
|
-
options: [
|
|
23079
|
-
{
|
|
23080
|
-
value: createValue,
|
|
23081
|
-
label: `Create ${options.projectName}`,
|
|
23082
|
-
hint: "new project"
|
|
23083
|
-
},
|
|
23084
|
-
...projects.map((project2) => ({
|
|
23085
|
-
value: project2.id,
|
|
23086
|
-
label: project2.name,
|
|
23087
|
-
hint: project2.workspace?.name
|
|
23088
|
-
}))
|
|
23089
|
-
],
|
|
23090
|
-
initialValue: createValue
|
|
23093
|
+
const workspace = await p15.select({
|
|
23094
|
+
message: "Choose a Railway workspace",
|
|
23095
|
+
options: options.workspaces.map((candidate) => ({
|
|
23096
|
+
value: candidate.id,
|
|
23097
|
+
label: candidate.name
|
|
23098
|
+
}))
|
|
23091
23099
|
});
|
|
23092
|
-
if (p15.isCancel(
|
|
23100
|
+
if (p15.isCancel(workspace)) {
|
|
23093
23101
|
p15.cancel("Setup cancelled.");
|
|
23094
23102
|
process.exit(0);
|
|
23095
23103
|
}
|
|
23096
|
-
|
|
23097
|
-
|
|
23098
|
-
|
|
23099
|
-
|
|
23100
|
-
);
|
|
23101
|
-
|
|
23102
|
-
|
|
23103
|
-
|
|
23104
|
-
|
|
23105
|
-
|
|
23106
|
-
|
|
23107
|
-
let createWorkspace = options.workspace;
|
|
23108
|
-
if (!createWorkspace && options.workspaces?.length === 1) {
|
|
23109
|
-
createWorkspace = options.workspaces[0]?.id;
|
|
23110
|
-
} else if (!createWorkspace && options.interactive && options.workspaces?.length) {
|
|
23111
|
-
projectSpinner.clear();
|
|
23112
|
-
const workspace = await p15.select({
|
|
23113
|
-
message: "Choose a Railway workspace",
|
|
23114
|
-
options: options.workspaces.map((candidate) => ({
|
|
23115
|
-
value: candidate.id,
|
|
23116
|
-
label: candidate.name
|
|
23117
|
-
}))
|
|
23118
|
-
});
|
|
23119
|
-
if (p15.isCancel(workspace)) {
|
|
23120
|
-
p15.cancel("Setup cancelled.");
|
|
23121
|
-
process.exit(0);
|
|
23122
|
-
}
|
|
23123
|
-
createWorkspace = workspace;
|
|
23124
|
-
projectSpinner.start(`Creating Railway project ${pc2.cyan(options.projectName)}`);
|
|
23125
|
-
}
|
|
23126
|
-
const created = await createRailwayProject(runner, options.cwd, options.projectName, {
|
|
23104
|
+
createWorkspace = workspace;
|
|
23105
|
+
projectSpinner.start("Checking Railway project names");
|
|
23106
|
+
}
|
|
23107
|
+
const projects = await listRailwayProjects(runner, options.cwd, options.env);
|
|
23108
|
+
projectSpinner.message(`Creating Railway project ${pc2.cyan(options.projectName)}`);
|
|
23109
|
+
const created = await createRailwayProject(
|
|
23110
|
+
runner,
|
|
23111
|
+
options.cwd,
|
|
23112
|
+
options.projectName,
|
|
23113
|
+
projects,
|
|
23114
|
+
{
|
|
23127
23115
|
workspace: createWorkspace,
|
|
23128
23116
|
env: options.env
|
|
23129
|
-
});
|
|
23130
|
-
if (environment.toLowerCase() !== "production") {
|
|
23131
|
-
await linkRailwayProject(runner, options.cwd, created.id, environment, {
|
|
23132
|
-
workspace: created.workspace?.id ?? options.workspace,
|
|
23133
|
-
env: options.env
|
|
23134
|
-
});
|
|
23135
23117
|
}
|
|
23118
|
+
);
|
|
23119
|
+
await linkRailwayProject(runner, options.cwd, created.id, environment, {
|
|
23120
|
+
workspace: created.workspace?.id ?? createWorkspace,
|
|
23121
|
+
env: options.env
|
|
23122
|
+
});
|
|
23123
|
+
const context2 = await readRailwayProjectContext(runner, options.cwd, environment, options.env);
|
|
23124
|
+
if (!context2 || context2.id !== created.id) {
|
|
23125
|
+
throw new Error("Railway did not link the newly created project.");
|
|
23136
23126
|
}
|
|
23127
|
+
projectSpinner.clear();
|
|
23128
|
+
return context2;
|
|
23137
23129
|
}
|
|
23138
23130
|
const context = await readRailwayProjectContext(runner, options.cwd, environment, options.env);
|
|
23139
23131
|
projectSpinner.clear();
|
|
@@ -23255,41 +23247,48 @@ async function waitForPostgresVariables(session, cwd, service) {
|
|
|
23255
23247
|
return {};
|
|
23256
23248
|
}
|
|
23257
23249
|
async function ensureRailwayPostgres(session, cwd) {
|
|
23258
|
-
const
|
|
23259
|
-
|
|
23260
|
-
|
|
23261
|
-
const
|
|
23262
|
-
|
|
23263
|
-
|
|
23250
|
+
const provisionSpinner = spinner2();
|
|
23251
|
+
provisionSpinner.start("Creating your Railway Postgres database");
|
|
23252
|
+
try {
|
|
23253
|
+
const services = await listRailwayServices(session, cwd);
|
|
23254
|
+
const candidates = services.filter((service2) => /^postgres(?:[-_ ].*)?$/i.test(service2.name));
|
|
23255
|
+
for (const candidate of candidates) {
|
|
23256
|
+
const variables2 = await readRailwayServiceVariables(session, cwd, candidate.name);
|
|
23257
|
+
if (variables2.DATABASE_PUBLIC_URL) {
|
|
23258
|
+
return { service: candidate, databaseUrl: variables2.DATABASE_PUBLIC_URL };
|
|
23259
|
+
}
|
|
23264
23260
|
}
|
|
23265
|
-
|
|
23266
|
-
|
|
23267
|
-
|
|
23268
|
-
|
|
23269
|
-
|
|
23270
|
-
|
|
23271
|
-
|
|
23272
|
-
|
|
23273
|
-
|
|
23274
|
-
|
|
23275
|
-
|
|
23276
|
-
|
|
23277
|
-
|
|
23278
|
-
|
|
23279
|
-
|
|
23280
|
-
|
|
23281
|
-
railwayOutputTail(`${result.stdout}
|
|
23261
|
+
if (candidates.length > 0) {
|
|
23262
|
+
throw new Error(
|
|
23263
|
+
`Railway Postgres service ${candidates.map((candidate) => candidate.name).join(", ")} exists, but DATABASE_PUBLIC_URL is not available. Rerun init after the database is ready.`
|
|
23264
|
+
);
|
|
23265
|
+
}
|
|
23266
|
+
const result = await runRailway(session.runner, ["add", "--database", "postgres", "--json"], {
|
|
23267
|
+
cwd,
|
|
23268
|
+
mode: "capture",
|
|
23269
|
+
env: session.env
|
|
23270
|
+
});
|
|
23271
|
+
const payload = result.success ? parseRailwayJson(result.stdout) : void 0;
|
|
23272
|
+
const serviceName = payload && isNonEmptyString(payload.serviceName) ? payload.serviceName : null;
|
|
23273
|
+
const serviceId = payload && isNonEmptyString(payload.serviceId) ? payload.serviceId : null;
|
|
23274
|
+
if (!result.success || !serviceName) {
|
|
23275
|
+
throw new Error(
|
|
23276
|
+
railwayOutputTail(`${result.stdout}
|
|
23282
23277
|
${result.stderr}`) ?? "Could not provision Railway Postgres."
|
|
23283
|
-
|
|
23284
|
-
|
|
23285
|
-
|
|
23286
|
-
|
|
23287
|
-
|
|
23288
|
-
|
|
23289
|
-
|
|
23290
|
-
|
|
23278
|
+
);
|
|
23279
|
+
}
|
|
23280
|
+
const service = { id: serviceId ?? serviceName, name: serviceName };
|
|
23281
|
+
provisionSpinner.message("Waiting for the database connection string");
|
|
23282
|
+
const variables = await waitForPostgresVariables(session, cwd, service);
|
|
23283
|
+
if (!variables.DATABASE_PUBLIC_URL) {
|
|
23284
|
+
throw new Error(
|
|
23285
|
+
`Railway created ${service.name}, but DATABASE_PUBLIC_URL was not available. Rerun init after the database is ready.`
|
|
23286
|
+
);
|
|
23287
|
+
}
|
|
23288
|
+
return { service, databaseUrl: variables.DATABASE_PUBLIC_URL };
|
|
23289
|
+
} finally {
|
|
23290
|
+
provisionSpinner.clear();
|
|
23291
23291
|
}
|
|
23292
|
-
return { service, databaseUrl: variables.DATABASE_PUBLIC_URL };
|
|
23293
23292
|
}
|
|
23294
23293
|
function parseBucket(value) {
|
|
23295
23294
|
if (!value || typeof value !== "object") return void 0;
|
|
@@ -23320,6 +23319,15 @@ function parseBucketCredentials(value) {
|
|
|
23320
23319
|
async function ensureRailwayBucket(session, cwd, options) {
|
|
23321
23320
|
const name = options.name ?? "media";
|
|
23322
23321
|
const environmentArgs = ["--environment", session.project.environmentName];
|
|
23322
|
+
const provisionSpinner = spinner2();
|
|
23323
|
+
provisionSpinner.start("Creating your Railway bucket");
|
|
23324
|
+
try {
|
|
23325
|
+
return await provisionRailwayBucketResource(session, cwd, name, options.region, environmentArgs);
|
|
23326
|
+
} finally {
|
|
23327
|
+
provisionSpinner.clear();
|
|
23328
|
+
}
|
|
23329
|
+
}
|
|
23330
|
+
async function provisionRailwayBucketResource(session, cwd, name, region, environmentArgs) {
|
|
23323
23331
|
const list = await runRailway(session.runner, ["bucket", "list", ...environmentArgs, "--json"], {
|
|
23324
23332
|
cwd,
|
|
23325
23333
|
mode: "capture",
|
|
@@ -23344,7 +23352,7 @@ ${list.stderr}`) ?? "Could not list Railway buckets."
|
|
|
23344
23352
|
if (!bucket) {
|
|
23345
23353
|
const create = await runRailway(
|
|
23346
23354
|
session.runner,
|
|
23347
|
-
["bucket", "create", name, "--region",
|
|
23355
|
+
["bucket", "create", name, "--region", region, ...environmentArgs, "--json"],
|
|
23348
23356
|
{ cwd, mode: "capture", env: session.env }
|
|
23349
23357
|
);
|
|
23350
23358
|
const createPayload = create.success ? parseRailwayJson(create.stdout) : void 0;
|
|
@@ -27301,9 +27309,6 @@ async function runInitCommand(name, options) {
|
|
|
27301
27309
|
databaseUrl = await promptConnectionString();
|
|
27302
27310
|
}
|
|
27303
27311
|
} else {
|
|
27304
|
-
p22.log.warn(
|
|
27305
|
-
"Railway Postgres is container-backed. Plan and test your own backups, upgrades, and high-availability strategy before production use."
|
|
27306
|
-
);
|
|
27307
27312
|
try {
|
|
27308
27313
|
const session = await getRailwaySession();
|
|
27309
27314
|
const postgres = await ensureRailwayPostgres(session, cwd);
|