betterstart-cli 0.0.29 → 0.0.30

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
@@ -21852,6 +21852,16 @@ function openBrowserVercelNeon() {
21852
21852
  `Opening Vercel... Create a Neon Postgres database, then copy the ${pc.cyan("DATABASE_URL")} from the dashboard.`
21853
21853
  );
21854
21854
  }
21855
+ function openBrowserVercelNeonResource(url) {
21856
+ if (!url) {
21857
+ openBrowserVercelNeon();
21858
+ return;
21859
+ }
21860
+ openBrowser(url);
21861
+ p10.log.info(
21862
+ `Opening Vercel... Copy the Neon ${pc.cyan("DATABASE_URL")} from the database dashboard, then paste it below.`
21863
+ );
21864
+ }
21855
21865
  async function promptConnectionString() {
21856
21866
  const input = await p10.text({
21857
21867
  message: "Paste your PostgreSQL connection string",
@@ -24655,25 +24665,17 @@ import pc5 from "picocolors";
24655
24665
  var PROVISION_TIMEOUT_MS2 = 18e4;
24656
24666
  var INTERACTIVE_PROVISION_TIMEOUT_MS2 = 6e5;
24657
24667
  async function provisionNeonInteractive(runner, cwd, options) {
24658
- const addArgs = ["integration", "add", "neon", "--no-env-pull"];
24659
- if (options.plan) addArgs.push("--plan", options.plan);
24660
24668
  const quietSpinner = spinner2();
24661
24669
  quietSpinner.start("Creating your Neon database");
24662
- const quiet = await runVercel(runner, addArgs, {
24670
+ const quiet = await runVercel(runner, neonAddArgs(options), {
24663
24671
  cwd,
24664
24672
  mode: "capture",
24665
24673
  timeoutMs: PROVISION_TIMEOUT_MS2,
24666
24674
  env: options.env
24667
24675
  });
24668
24676
  if (quiet.success) {
24669
- quietSpinner.message("Retrieving the database connection string");
24670
- const databaseUrl2 = await pullDatabaseUrl(runner, cwd, options.env);
24671
- if (!databaseUrl2) {
24672
- quietSpinner.stop("No DATABASE_URL came back from Vercel");
24673
- return { failure: "env-pull-empty" };
24674
- }
24675
24677
  quietSpinner.clear();
24676
- return { databaseUrl: databaseUrl2 };
24678
+ return readNeonProvisionInfo(quiet.stdout);
24677
24679
  }
24678
24680
  quietSpinner.clear();
24679
24681
  p15.log.info(
@@ -24681,7 +24683,7 @@ async function provisionNeonInteractive(runner, cwd, options) {
24681
24683
  "(the Free plan is recommended)."
24682
24684
  )}`
24683
24685
  );
24684
- const add = await runVercel(runner, addArgs, {
24686
+ const add = await runVercel(runner, connectedNeonAddArgs(options), {
24685
24687
  cwd,
24686
24688
  mode: "inherit",
24687
24689
  timeoutMs: INTERACTIVE_PROVISION_TIMEOUT_MS2,
@@ -24691,21 +24693,19 @@ async function provisionNeonInteractive(runner, cwd, options) {
24691
24693
  return { failure: add.timedOut ? "timeout" : "provision-failed" };
24692
24694
  }
24693
24695
  const pullSpinner = spinner2();
24694
- pullSpinner.start("Retrieving the database connection string");
24695
- const databaseUrl = await pullDatabaseUrl(runner, cwd, options.env);
24696
+ pullSpinner.start("Retrieving DATABASE_URL from Vercel");
24697
+ const databaseUrl = await pullVercelEnvValue(runner, cwd, readDbUrlFromDotenv, options.env);
24696
24698
  if (!databaseUrl) {
24697
- pullSpinner.stop("No DATABASE_URL came back from Vercel");
24698
- return { failure: "env-pull-empty" };
24699
+ pullSpinner.stop(`${pc5.yellow("\u25B2")} No DATABASE_URL came back from Vercel`);
24700
+ return {};
24699
24701
  }
24700
24702
  pullSpinner.clear();
24701
24703
  return { databaseUrl };
24702
24704
  }
24703
24705
  async function provisionNeon(runner, cwd, options) {
24704
- const addArgs = ["integration", "add", "neon", "--no-env-pull", "--format", "json"];
24705
- if (options.plan) addArgs.push("--plan", options.plan);
24706
24706
  const provisionSpinner = spinner2();
24707
24707
  provisionSpinner.start("Creating your Neon database");
24708
- const add = await runVercel(runner, addArgs, {
24708
+ const add = await runVercel(runner, neonAddArgs(options), {
24709
24709
  cwd,
24710
24710
  mode: "capture",
24711
24711
  timeoutMs: PROVISION_TIMEOUT_MS2,
@@ -24720,33 +24720,49 @@ ${add.stderr}`.trim();
24720
24720
  if (/claim/i.test(combined)) return { failure: "claim-required", detail };
24721
24721
  return { failure: add.timedOut ? "timeout" : "provision-failed", detail };
24722
24722
  }
24723
- const meta = parseVercelJson(add.stdout);
24724
- const resourceName = meta?.resourceName ?? meta?.name;
24725
- provisionSpinner.message("Retrieving the database connection string");
24726
- const databaseUrl = await pullDatabaseUrl(runner, cwd, options.env);
24727
- if (!databaseUrl) {
24728
- provisionSpinner.stop("No DATABASE_URL came back from Vercel");
24729
- return { resourceName, failure: "env-pull-empty" };
24730
- }
24731
24723
  provisionSpinner.clear();
24732
- return { databaseUrl, resourceName };
24724
+ return readNeonProvisionInfo(add.stdout);
24733
24725
  }
24734
- function pullDatabaseUrl(runner, cwd, env) {
24735
- return pullVercelEnvValue(runner, cwd, readDbUrlFromDotenv, env);
24726
+ async function connectNeonResourceToProject(runner, cwd, resourceName, env) {
24727
+ const connect = await runVercel(
24728
+ runner,
24729
+ ["integration", "resource", "connect", resourceName, "--yes", "--format", "json"],
24730
+ { cwd, mode: "capture", timeoutMs: PROVISION_TIMEOUT_MS2, env }
24731
+ );
24732
+ if (connect.success) return { ok: true };
24733
+ const detail = `${connect.stdout}
24734
+ ${connect.stderr}`.trim();
24735
+ if (/already connected/i.test(detail)) {
24736
+ return { ok: true, alreadyConnected: true, detail: detail || void 0 };
24737
+ }
24738
+ return { ok: false, detail: detail || void 0 };
24736
24739
  }
24737
24740
  function readDbUrlFromDotenv(filePath) {
24738
24741
  const vars = parseDotenvFile(filePath);
24739
24742
  const isPg = (v) => !!v && (v.startsWith("postgres://") || v.startsWith("postgresql://"));
24740
24743
  if (isPg(vars.get("DATABASE_URL"))) return vars.get("DATABASE_URL");
24741
24744
  if (isPg(vars.get("POSTGRES_URL"))) return vars.get("POSTGRES_URL");
24742
- for (const [key, value] of vars) {
24743
- if (/DATABASE_URL$/.test(key) && isPg(value)) return value;
24744
- }
24745
- for (const value of vars.values()) {
24746
- if (isPg(value)) return value;
24747
- }
24748
24745
  return void 0;
24749
24746
  }
24747
+ function readNeonProvisionInfo(stdout) {
24748
+ const meta = parseVercelJson(stdout);
24749
+ if (!meta) return {};
24750
+ return {
24751
+ resourceName: meta.resource?.name,
24752
+ dashboardUrl: meta.dashboardUrl,
24753
+ resourceUrl: meta.ssoUrl?.resource
24754
+ };
24755
+ }
24756
+ function neonAddArgs(options) {
24757
+ const addArgs = ["integration", "add", "neon", "--no-connect", "--format", "json"];
24758
+ if (options.plan) addArgs.push("--plan", options.plan);
24759
+ return addArgs;
24760
+ }
24761
+ function connectedNeonAddArgs(options) {
24762
+ const addArgs = ["integration", "add", "neon"];
24763
+ if (options.plan) addArgs.push("--plan", options.plan);
24764
+ return addArgs;
24765
+ }
24750
24766
 
24751
24767
  // adapters/next/init/vercel/flow.ts
24752
24768
  async function runVercelNeonFlow(options) {
@@ -24762,23 +24778,29 @@ async function runVercelNeonFlow(options) {
24762
24778
  p16.log.warn(authFailureMessage(auth.reason));
24763
24779
  return { ok: false };
24764
24780
  }
24765
- const projectSpinner = spinner2();
24766
- projectSpinner.start(`Creating a Vercel project ${pc6.cyan(options.projectName)}`);
24767
- const project2 = await createVercelProject(runner, options.cwd, options.projectName, env);
24768
- projectSpinner.stop(
24769
- project2.linked ? `Linked Vercel project ${pc6.cyan(project2.name)}` : `Using Vercel project ${pc6.cyan(project2.name)}`
24770
- );
24781
+ await ensureLinkedProject(runner, options.cwd, options.projectName, env);
24771
24782
  const neon = await provisionNeonForMode(runner, options);
24772
- if (neon.failure || !neon.databaseUrl) {
24783
+ if (neon.failure) {
24773
24784
  p16.log.warn(neonFailureMessage(neon.failure));
24774
24785
  if (neon.detail) p16.log.message(pc6.dim(neon.detail));
24775
24786
  return { ok: false };
24776
24787
  }
24788
+ let databaseUrl = neon.databaseUrl;
24789
+ if (neon.resourceName) {
24790
+ const pulledDatabaseUrl = await connectNeonAndPullDatabaseUrl(
24791
+ runner,
24792
+ options.cwd,
24793
+ neon.resourceName,
24794
+ env
24795
+ );
24796
+ databaseUrl = pulledDatabaseUrl ?? databaseUrl;
24797
+ }
24777
24798
  return {
24778
24799
  ok: true,
24779
- databaseUrl: neon.databaseUrl,
24780
- vercelProjectId: project2.projectId,
24781
- neonResourceName: neon.resourceName
24800
+ databaseUrl,
24801
+ neonResourceName: neon.resourceName,
24802
+ dashboardUrl: neon.dashboardUrl,
24803
+ resourceUrl: neon.resourceUrl
24782
24804
  };
24783
24805
  } catch (error) {
24784
24806
  p16.log.warn(
@@ -24802,16 +24824,7 @@ async function runVercelBlobFlow(options) {
24802
24824
  p16.log.warn(authFailureMessage(auth.reason));
24803
24825
  return { ok: false };
24804
24826
  }
24805
- let projectId = readLinkedProjectId(options.cwd);
24806
- if (!projectId) {
24807
- const projectSpinner = spinner2();
24808
- projectSpinner.start(`Creating a Vercel project ${pc6.cyan(options.projectName)}`);
24809
- const project2 = await createVercelProject(runner, options.cwd, options.projectName, env);
24810
- projectSpinner.stop(
24811
- project2.linked ? `Linked Vercel project ${pc6.cyan(project2.name)}` : `Using Vercel project ${pc6.cyan(project2.name)}`
24812
- );
24813
- projectId = project2.projectId;
24814
- }
24827
+ await ensureLinkedProject(runner, options.cwd, options.projectName, env);
24815
24828
  const blob = await provisionBlobForMode(runner, options);
24816
24829
  if (blob.failure || !blob.token) {
24817
24830
  p16.log.warn(blobFailureMessage(blob.failure));
@@ -24821,7 +24834,6 @@ async function runVercelBlobFlow(options) {
24821
24834
  return {
24822
24835
  ok: true,
24823
24836
  token: blob.token,
24824
- vercelProjectId: projectId,
24825
24837
  blobStoreName: blob.storeName
24826
24838
  };
24827
24839
  } catch (error) {
@@ -24847,13 +24859,24 @@ async function runVercelDeployFlow(options) {
24847
24859
  printManualDeployHint();
24848
24860
  return { ok: false };
24849
24861
  }
24850
- if (!readLinkedProjectId(options.cwd)) {
24851
- const projectSpinner = spinner2();
24852
- projectSpinner.start(`Creating a Vercel project ${pc6.cyan(options.projectName)}`);
24853
- const project2 = await createVercelProject(runner, options.cwd, options.projectName, env);
24854
- projectSpinner.stop(
24855
- project2.linked ? `Linked Vercel project ${pc6.cyan(project2.name)}` : `Using Vercel project ${pc6.cyan(project2.name)}`
24862
+ await ensureLinkedProject(runner, options.cwd, options.projectName, env);
24863
+ if (options.neonResourceName) {
24864
+ const neonSpinner = spinner2();
24865
+ neonSpinner.start("Connecting Neon database to Vercel project");
24866
+ const connect = await connectNeonResourceToProject(
24867
+ runner,
24868
+ options.cwd,
24869
+ options.neonResourceName,
24870
+ env
24856
24871
  );
24872
+ if (connect.ok) {
24873
+ neonSpinner.stop(
24874
+ connect.alreadyConnected ? "Neon database already connected to Vercel project" : "Connected Neon database to Vercel project"
24875
+ );
24876
+ } else {
24877
+ neonSpinner.stop(`${pc6.yellow("\u25B2")} Could not connect Neon database to Vercel project`);
24878
+ if (connect.detail) p16.log.message(pc6.dim(connect.detail));
24879
+ }
24857
24880
  }
24858
24881
  const envSpinner = spinner2();
24859
24882
  envSpinner.start("Syncing environment variables to Vercel");
@@ -24908,6 +24931,33 @@ async function runVercelDeployFlow(options) {
24908
24931
  function printManualDeployHint() {
24909
24932
  p16.log.info(`You can deploy manually: ${pc6.cyan("vercel deploy --prod")}`);
24910
24933
  }
24934
+ async function ensureLinkedProject(runner, cwd, projectName, env) {
24935
+ if (readLinkedProjectId(cwd)) return;
24936
+ const projectSpinner = spinner2();
24937
+ projectSpinner.start(`Creating a Vercel project ${pc6.cyan(projectName)}`);
24938
+ const project2 = await createVercelProject(runner, cwd, projectName, env);
24939
+ projectSpinner.stop(
24940
+ project2.linked ? `Linked Vercel project ${pc6.cyan(project2.name)}` : `Using Vercel project ${pc6.cyan(project2.name)}`
24941
+ );
24942
+ }
24943
+ async function connectNeonAndPullDatabaseUrl(runner, cwd, resourceName, env) {
24944
+ const neonSpinner = spinner2();
24945
+ neonSpinner.start("Connecting Neon database to Vercel project");
24946
+ const connect = await connectNeonResourceToProject(runner, cwd, resourceName, env);
24947
+ if (!connect.ok) {
24948
+ neonSpinner.stop(`${pc6.yellow("\u25B2")} Could not connect Neon database to Vercel project`);
24949
+ if (connect.detail) p16.log.message(pc6.dim(connect.detail));
24950
+ return void 0;
24951
+ }
24952
+ neonSpinner.message("Retrieving DATABASE_URL from Vercel");
24953
+ const databaseUrl = await pullVercelEnvValue(runner, cwd, readDbUrlFromDotenv, env);
24954
+ if (!databaseUrl) {
24955
+ neonSpinner.stop(`${pc6.yellow("\u25B2")} No DATABASE_URL came back from Vercel`);
24956
+ return void 0;
24957
+ }
24958
+ neonSpinner.clear();
24959
+ return databaseUrl;
24960
+ }
24911
24961
  function provisionBlobForMode(runner, options) {
24912
24962
  const opts = { name: blobStoreName(options.projectName), env: options.env };
24913
24963
  return options.interactive ? provisionBlobStoreInteractive(runner, options.cwd, opts) : provisionBlobStore(runner, options.cwd, opts);
@@ -24954,8 +25004,6 @@ function neonFailureMessage(reason) {
24954
25004
  return "Provisioning needs the Neon marketplace terms accepted \u2014 falling back to manual entry.";
24955
25005
  case "claim-required":
24956
25006
  return "The Neon resource needs to be claimed \u2014 falling back to manual entry.";
24957
- case "env-pull-empty":
24958
- return "Provisioned Neon, but no DATABASE_URL came back \u2014 falling back to manual entry.";
24959
25007
  case "timeout":
24960
25008
  return "Neon provisioning timed out \u2014 falling back to manual entry.";
24961
25009
  default:
@@ -25533,6 +25581,7 @@ async function runInitCommand(name, options) {
25533
25581
  isFreshProject = true;
25534
25582
  }
25535
25583
  let databaseUrl;
25584
+ let neonResourceName;
25536
25585
  const existingDbUrl = readExistingDbUrl(cwd);
25537
25586
  if (options.yes) {
25538
25587
  if (options.databaseUrl) {
@@ -25554,9 +25603,23 @@ async function runInitCommand(name, options) {
25554
25603
  env: process.env
25555
25604
  });
25556
25605
  if (flow.ok && flow.databaseUrl) {
25606
+ neonResourceName = flow.neonResourceName;
25557
25607
  databaseUrl = flow.databaseUrl;
25558
25608
  persistDatabaseUrl(cwd, databaseUrl);
25559
25609
  p17.log.success(`Saved DATABASE_URL to ${pc7.cyan(".env.local")}`);
25610
+ } else if (flow.ok) {
25611
+ neonResourceName = flow.neonResourceName;
25612
+ p17.log.warn("Created a Neon database, but DATABASE_URL could not be retrieved from Vercel.");
25613
+ const resourceUrl = flow.resourceUrl ?? flow.dashboardUrl;
25614
+ if (resourceUrl) {
25615
+ p17.log.info(
25616
+ `Open ${pc7.cyan(resourceUrl)} to copy DATABASE_URL, then rerun ${pc7.cyan("betterstart init --database-url <url> --yes")}.`
25617
+ );
25618
+ } else {
25619
+ p17.log.info(
25620
+ `Rerun ${pc7.cyan("betterstart init --database-url <url> --yes")} with a Postgres connection string.`
25621
+ );
25622
+ }
25560
25623
  } else {
25561
25624
  p17.log.warn("Vercel provisioning did not complete; continuing without a database URL.");
25562
25625
  }
@@ -25576,9 +25639,16 @@ async function runInitCommand(name, options) {
25576
25639
  env: process.env
25577
25640
  });
25578
25641
  if (flow.ok && flow.databaseUrl) {
25642
+ neonResourceName = flow.neonResourceName;
25579
25643
  databaseUrl = flow.databaseUrl;
25580
25644
  persistDatabaseUrl(cwd, databaseUrl);
25581
25645
  p17.log.success(`Saved DATABASE_URL to ${pc7.cyan(".env.local")}`);
25646
+ } else if (flow.ok) {
25647
+ neonResourceName = flow.neonResourceName;
25648
+ openBrowserVercelNeonResource(flow.resourceUrl ?? flow.dashboardUrl);
25649
+ databaseUrl = await promptConnectionString();
25650
+ persistDatabaseUrl(cwd, databaseUrl);
25651
+ p17.log.success(`Saved DATABASE_URL to ${pc7.cyan(".env.local")}`);
25582
25652
  } else {
25583
25653
  p17.log.info("Falling back to a manual database connection string.");
25584
25654
  openBrowserVercelNeon();
@@ -26102,10 +26172,15 @@ Run manually: ${pc7.cyan(betterstartExecCommand(pm, "seed"))}`,
26102
26172
  initialValue: linkedVercelProject
26103
26173
  });
26104
26174
  if (!p17.isCancel(deployNow) && deployNow) {
26105
- await runVercelDeployFlow({ cwd, projectName, env: process.env });
26175
+ await runVercelDeployFlow({ cwd, projectName, neonResourceName, env: process.env });
26106
26176
  }
26107
26177
  } else if (linkedVercelProject && process.env.VERCEL_TOKEN) {
26108
- const deployFlow = await runVercelDeployFlow({ cwd, projectName, env: process.env });
26178
+ const deployFlow = await runVercelDeployFlow({
26179
+ cwd,
26180
+ projectName,
26181
+ neonResourceName,
26182
+ env: process.env
26183
+ });
26109
26184
  if (!deployFlow.ok) {
26110
26185
  p17.log.warn("Vercel deploy did not complete; continuing.");
26111
26186
  }