betterstart-cli 0.0.72 → 0.0.74

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
@@ -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(
@@ -22971,6 +22971,16 @@ function parseProjectSummary(value) {
22971
22971
  }
22972
22972
  return { id: project2.id, name: project2.name, workspace };
22973
22973
  }
22974
+ var MAX_PROJECT_NAME_ATTEMPTS = 10;
22975
+ function versionedRailwayProjectName(base, attempt) {
22976
+ return attempt <= 1 ? base : `${base}-v${attempt}`;
22977
+ }
22978
+ function railwayProjectNameIsTaken(projects, candidate) {
22979
+ return projects.some((project2) => project2.name.toLowerCase() === candidate.toLowerCase());
22980
+ }
22981
+ function isRailwayProjectNameConflict(detail) {
22982
+ return /already exists|name.*taken|duplicate.*name/i.test(detail);
22983
+ }
22974
22984
  async function readRailwayProjectContext(runner, cwd, environment, env) {
22975
22985
  const result = await runRailway(runner, ["status", "--environment", environment, "--json"], {
22976
22986
  cwd,
@@ -23039,18 +23049,26 @@ ${result.stderr}`) ?? `Could not link Railway project ${project2}.`
23039
23049
  );
23040
23050
  }
23041
23051
  }
23042
- async function createRailwayProject(runner, cwd, projectName, options) {
23043
- const args = ["init", "--name", projectName, "--json"];
23044
- if (options.workspace) args.push("--workspace", options.workspace);
23045
- const result = await runRailway(runner, args, { cwd, mode: "capture", env: options.env });
23046
- const project2 = result.success ? parseProjectSummary(parseRailwayJson(result.stdout)) : void 0;
23047
- if (!project2) {
23048
- throw new Error(
23049
- railwayOutputTail(`${result.stdout}
23050
- ${result.stderr}`) ?? `Could not create Railway project ${projectName}.`
23051
- );
23052
+ async function createRailwayProject(runner, cwd, projectName, existingProjects, options) {
23053
+ const knownProjects = [...existingProjects];
23054
+ for (let attempt = 1; attempt <= MAX_PROJECT_NAME_ATTEMPTS; attempt++) {
23055
+ const candidate = versionedRailwayProjectName(projectName, attempt);
23056
+ if (railwayProjectNameIsTaken(knownProjects, candidate)) continue;
23057
+ const args = ["init", "--name", candidate, "--json"];
23058
+ if (options.workspace) args.push("--workspace", options.workspace);
23059
+ const result = await runRailway(runner, args, { cwd, mode: "capture", env: options.env });
23060
+ const project2 = result.success ? parseProjectSummary(parseRailwayJson(result.stdout)) : void 0;
23061
+ if (project2) return project2;
23062
+ const detail = railwayOutputTail(`${result.stdout}
23063
+ ${result.stderr}`);
23064
+ if (!detail || !isRailwayProjectNameConflict(detail)) {
23065
+ throw new Error(detail ?? `Could not create Railway project ${candidate}.`);
23066
+ }
23067
+ knownProjects.push({ id: candidate, name: candidate });
23052
23068
  }
23053
- return project2;
23069
+ throw new Error(
23070
+ `Could not create a unique Railway project name after ${MAX_PROJECT_NAME_ATTEMPTS} attempts.`
23071
+ );
23054
23072
  }
23055
23073
  async function ensureRailwayProject(runner, options) {
23056
23074
  const environment = options.environment?.trim() || "production";
@@ -23063,77 +23081,47 @@ async function ensureRailwayProject(runner, options) {
23063
23081
  env: options.env
23064
23082
  });
23065
23083
  } else {
23066
- const linked = await readRailwayProjectContext(runner, options.cwd, environment, options.env);
23067
- if (linked) {
23084
+ let createWorkspace = options.workspace;
23085
+ if (!createWorkspace && options.workspaces?.length === 1) {
23086
+ createWorkspace = options.workspaces[0]?.id;
23087
+ } else if (!createWorkspace && options.interactive && options.workspaces?.length) {
23068
23088
  projectSpinner.clear();
23069
- return linked;
23070
- }
23071
- const projects = options.interactive ? await listRailwayProjects(runner, options.cwd, options.env) : [];
23072
- projectSpinner.clear();
23073
- let selectedProject;
23074
- if (options.interactive && projects.length > 0) {
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
23089
+ const workspace = await p15.select({
23090
+ message: "Choose a Railway workspace",
23091
+ options: options.workspaces.map((candidate) => ({
23092
+ value: candidate.id,
23093
+ label: candidate.name
23094
+ }))
23091
23095
  });
23092
- if (p15.isCancel(choice)) {
23096
+ if (p15.isCancel(workspace)) {
23093
23097
  p15.cancel("Setup cancelled.");
23094
23098
  process.exit(0);
23095
23099
  }
23096
- selectedProject = projects.find((project2) => project2.id === choice);
23097
- }
23098
- projectSpinner.start(
23099
- selectedProject ? `Linking Railway project ${pc2.cyan(selectedProject.name)}` : `Creating Railway project ${pc2.cyan(options.projectName)}`
23100
- );
23101
- if (selectedProject) {
23102
- await linkRailwayProject(runner, options.cwd, selectedProject.id, environment, {
23103
- workspace: selectedProject.workspace?.id ?? options.workspace,
23104
- env: options.env
23105
- });
23106
- } else {
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, {
23100
+ createWorkspace = workspace;
23101
+ projectSpinner.start("Checking Railway project names");
23102
+ }
23103
+ const projects = await listRailwayProjects(runner, options.cwd, options.env);
23104
+ projectSpinner.start(`Creating Railway project ${pc2.cyan(options.projectName)}`);
23105
+ const created = await createRailwayProject(
23106
+ runner,
23107
+ options.cwd,
23108
+ options.projectName,
23109
+ projects,
23110
+ {
23127
23111
  workspace: createWorkspace,
23128
23112
  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
23113
  }
23114
+ );
23115
+ await linkRailwayProject(runner, options.cwd, created.id, environment, {
23116
+ workspace: created.workspace?.id ?? createWorkspace,
23117
+ env: options.env
23118
+ });
23119
+ const context2 = await readRailwayProjectContext(runner, options.cwd, environment, options.env);
23120
+ if (!context2 || context2.id !== created.id) {
23121
+ throw new Error("Railway did not link the newly created project.");
23136
23122
  }
23123
+ projectSpinner.clear();
23124
+ return context2;
23137
23125
  }
23138
23126
  const context = await readRailwayProjectContext(runner, options.cwd, environment, options.env);
23139
23127
  projectSpinner.clear();
@@ -23587,13 +23575,22 @@ async function runRailwayDeployFlow(options) {
23587
23575
  service,
23588
23576
  options.session.env
23589
23577
  );
23578
+ const url = await ensureRailwayDomain(options.session, options.cwd, service);
23579
+ if (!url) {
23580
+ throw new Error("Railway could not create a public domain required for Better Auth.");
23581
+ }
23582
+ const providerOverrides = {
23583
+ ...options.providerOverrides,
23584
+ BETTERSTART_AUTH_URL: url,
23585
+ NEXT_PUBLIC_BETTERSTART_AUTH_URL: url
23586
+ };
23590
23587
  const envSpinner = spinner2();
23591
23588
  envSpinner.start("Syncing environment variables to Railway");
23592
23589
  const sync = await syncRailwayServiceEnv(
23593
23590
  options.session,
23594
23591
  options.cwd,
23595
23592
  service,
23596
- options.providerOverrides ?? {}
23593
+ providerOverrides
23597
23594
  );
23598
23595
  envSpinner.clear();
23599
23596
  for (const key of sync.failed) {
@@ -23662,7 +23659,6 @@ async function runRailwayDeployFlow(options) {
23662
23659
  ${deploy.stderr}`) ?? deploy.errorMessage
23663
23660
  };
23664
23661
  }
23665
- const url = await ensureRailwayDomain(options.session, options.cwd, service);
23666
23662
  deploySpinner.stop(url ? `Deployed ${pc3.cyan(url)}` : "Deployed to Railway");
23667
23663
  return { ok: true, url, service, syncedEnvKeys: sync.synced };
23668
23664
  } catch (error) {