betterstart-cli 0.0.25 → 0.0.27

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
@@ -3068,20 +3068,26 @@ async function resolvePasswordEnvValue(options) {
3068
3068
  options.overwriteEnvKeys.add(options.key);
3069
3069
  return result.trim();
3070
3070
  }
3071
- async function collectResendConfig(cwd) {
3071
+ async function collectResendConfig(cwd, presetApiKey) {
3072
3072
  const overwriteEnvKeys = /* @__PURE__ */ new Set();
3073
- const apiKey = await resolvePasswordEnvValue({
3074
- cwd,
3075
- key: "BETTERSTART_RESEND_API_KEY",
3076
- message: "Resend API key",
3077
- cancelMessage: "Resend configuration cancelled.",
3078
- overwriteEnvKeys,
3079
- validate(value) {
3080
- if (!value?.trim()) {
3081
- return "Enter a Resend API key.";
3073
+ let apiKey;
3074
+ if (presetApiKey?.trim()) {
3075
+ apiKey = presetApiKey.trim();
3076
+ overwriteEnvKeys.add("BETTERSTART_RESEND_API_KEY");
3077
+ } else {
3078
+ apiKey = await resolvePasswordEnvValue({
3079
+ cwd,
3080
+ key: "BETTERSTART_RESEND_API_KEY",
3081
+ message: "Resend API key",
3082
+ cancelMessage: "Resend configuration cancelled.",
3083
+ overwriteEnvKeys,
3084
+ validate(value) {
3085
+ if (!value?.trim()) {
3086
+ return "Enter a Resend API key.";
3087
+ }
3082
3088
  }
3083
- }
3084
- });
3089
+ });
3090
+ }
3085
3091
  const fromAddress = await resolveTextEnvValue({
3086
3092
  cwd,
3087
3093
  key: "BETTERSTART_EMAIL_FROM",
@@ -3197,12 +3203,12 @@ async function promptR2Config(cwd) {
3197
3203
  updatedEnvKeys: envResult.updated
3198
3204
  };
3199
3205
  }
3200
- async function collectIntegrationConfig(cwd, integrationIds) {
3206
+ async function collectIntegrationConfig(cwd, integrationIds, options) {
3201
3207
  const sections = [];
3202
3208
  const overwriteKeys = /* @__PURE__ */ new Set();
3203
3209
  for (const integrationId of resolveIntegrationInstallOrder(integrationIds)) {
3204
3210
  const definition = getIntegrationDefinition(integrationId);
3205
- const collected = definition.configure === "resend" ? await collectResendConfig(cwd) : definition.configure === "r2" ? await collectR2Config(cwd) : void 0;
3211
+ const collected = definition.configure === "resend" ? await collectResendConfig(cwd, options?.resendApiKey) : definition.configure === "r2" ? await collectR2Config(cwd) : void 0;
3206
3212
  if (!collected) {
3207
3213
  continue;
3208
3214
  }
@@ -5283,14 +5289,14 @@ function buildStepsConstant(steps) {
5283
5289
  ${entries.join(",\n")}
5284
5290
  ]`;
5285
5291
  }
5286
- function buildComponentSource(p19) {
5292
+ function buildComponentSource(p21) {
5287
5293
  const providerOpen = ` <NuqsAdapter>
5288
5294
  <React.Suspense fallback={null}>
5289
- <${p19.pascal}FormInner />
5295
+ <${p21.pascal}FormInner />
5290
5296
  </React.Suspense>
5291
5297
  </NuqsAdapter>`;
5292
5298
  const exportWrapper = `
5293
- export function ${p19.pascal}Form() {
5299
+ export function ${p21.pascal}Form() {
5294
5300
  return (
5295
5301
  ${providerOpen}
5296
5302
  )
@@ -5299,22 +5305,22 @@ ${providerOpen}
5299
5305
  return `'use client'
5300
5306
 
5301
5307
  import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
5302
- import { ChevronLeft, ChevronRight${p19.hasListFields ? ", Trash2" : ""} } from 'lucide-react'
5308
+ import { ChevronLeft, ChevronRight${p21.hasListFields ? ", Trash2" : ""} } from 'lucide-react'
5303
5309
  import { createParser, useQueryState } from 'nuqs'
5304
5310
  import { NuqsAdapter } from 'nuqs/adapters/next/app'
5305
5311
  import * as React from 'react'
5306
- ${p19.rhfImport}
5312
+ ${p21.rhfImport}
5307
5313
  import { z } from 'zod/v3'
5308
- import { create${p19.pascal}Submission } from '@admin/actions/${p19.actionImportPath}'
5314
+ import { create${p21.pascal}Submission } from '@admin/actions/${p21.actionImportPath}'
5309
5315
 
5310
5316
  const formSchema = z.object({
5311
- ${p19.zodFields}
5317
+ ${p21.zodFields}
5312
5318
  })
5313
5319
 
5314
5320
  type FormValues = z.infer<typeof formSchema>
5315
5321
  ${buildFieldErrorHelper()}
5316
5322
 
5317
- ${p19.stepsConst}
5323
+ ${p21.stepsConst}
5318
5324
 
5319
5325
  const stepParser = createParser({
5320
5326
  parse(value) {
@@ -5332,7 +5338,7 @@ const stepParser = createParser({
5332
5338
  }
5333
5339
  }).withDefault(0)
5334
5340
 
5335
- function ${p19.pascal}FormInner() {
5341
+ function ${p21.pascal}FormInner() {
5336
5342
  const [currentStep, setCurrentStep] = useQueryState('step', stepParser)
5337
5343
  const [submitted, setSubmitted] = React.useState(false)
5338
5344
  const [submitting, startSubmitTransition] = React.useTransition()
@@ -5340,11 +5346,11 @@ function ${p19.pascal}FormInner() {
5340
5346
  const form = useForm<FormValues>({
5341
5347
  resolver: standardSchemaResolver(formSchema),
5342
5348
  defaultValues: {
5343
- ${p19.defaults}
5349
+ ${p21.defaults}
5344
5350
  },
5345
5351
  })
5346
5352
 
5347
- ${p19.fieldArraySetup}${p19.watchSetup}
5353
+ ${p21.fieldArraySetup}${p21.watchSetup}
5348
5354
  async function handleNext() {
5349
5355
  const stepFields = STEPS[currentStep].fields as (keyof FormValues)[]
5350
5356
  const isValid = await form.trigger(stepFields, { shouldFocus: true })
@@ -5360,9 +5366,9 @@ ${p19.fieldArraySetup}${p19.watchSetup}
5360
5366
  function onSubmit(values: FormValues) {
5361
5367
  startSubmitTransition(async () => {
5362
5368
  try {
5363
- const result = await create${p19.pascal}Submission(values)
5369
+ const result = await create${p21.pascal}Submission(values)
5364
5370
  if (result.success) {
5365
- ${p19.successHandler}
5371
+ ${p21.successHandler}
5366
5372
  } else {
5367
5373
  form.setError('root', { message: result.error || 'Something went wrong' })
5368
5374
  }
@@ -5376,7 +5382,7 @@ ${p19.fieldArraySetup}${p19.watchSetup}
5376
5382
  return (
5377
5383
  <div className="rounded-sm border p-6 text-center">
5378
5384
  <h3 className="text-lg font-semibold">Thank you!</h3>
5379
- <p className="mt-2 text-muted-foreground">${p19.successMessage}</p>
5385
+ <p className="mt-2 text-muted-foreground">${p21.successMessage}</p>
5380
5386
  </div>
5381
5387
  )
5382
5388
  }
@@ -5393,7 +5399,7 @@ ${p19.fieldArraySetup}${p19.watchSetup}
5393
5399
 
5394
5400
  {/* Step content */}
5395
5401
  <div key={currentStep} className="animate-in fade-in duration-300 space-y-6">
5396
- ${p19.stepContentBlocks}
5402
+ ${p21.stepContentBlocks}
5397
5403
  </div>
5398
5404
 
5399
5405
  {form.formState.errors.root && (
@@ -5427,7 +5433,7 @@ ${p19.stepContentBlocks}
5427
5433
  onClick={() => form.handleSubmit(onSubmit)()}
5428
5434
  className="inline-flex h-9 items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow-xs transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50"
5429
5435
  >
5430
- {submitting ? 'Submitting...' : ${p19.submitText}}
5436
+ {submitting ? 'Submitting...' : ${p21.submitText}}
5431
5437
  </button>
5432
5438
  )}
5433
5439
  </div>
@@ -21743,38 +21749,44 @@ ${validationErrors.map((error) => ` - ${error}`).join("\n")}`
21743
21749
  import { execFileSync as execFileSync5, spawn as spawn4 } from "child_process";
21744
21750
  import fs37 from "fs";
21745
21751
  import path48 from "path";
21746
- import * as p14 from "@clack/prompts";
21752
+ import * as p16 from "@clack/prompts";
21747
21753
 
21748
21754
  // adapters/next/init/prompts/database.ts
21749
21755
  import { execFileSync as execFileSync4 } from "child_process";
21756
+ import { styleText } from "util";
21750
21757
  import * as p9 from "@clack/prompts";
21751
21758
  import pc from "picocolors";
21752
21759
  var VERCEL_NEON_URL = "https://vercel.com/dashboard/integrations/checkout/neon";
21753
- async function promptDatabase(options) {
21760
+ async function promptServices(options) {
21754
21761
  if (!options.allowVercel) {
21755
21762
  const url2 = await promptConnectionString();
21756
21763
  return { provider: "manual", url: url2 };
21757
21764
  }
21758
21765
  const choice = await p9.select({
21759
- message: "How would you like to connect your PostgreSQL database?",
21766
+ message: `How would you like to connect your services?
21767
+ ${styleText(
21768
+ "dim",
21769
+ "Database, Email, and File Storage"
21770
+ )}`,
21760
21771
  options: [
21761
21772
  {
21762
- value: "vercel-neon",
21763
- label: "Vercel (Neon)",
21764
- hint: "create & connect a free Postgres database via the Vercel CLI"
21773
+ value: "vercel",
21774
+ label: "Vercel",
21775
+ hint: "provision Neon Postgres + Resend email via the Vercel CLI"
21765
21776
  },
21766
21777
  {
21767
21778
  value: "manual",
21768
- label: "Enter connection string manually"
21779
+ label: "Manual",
21780
+ hint: "enter connection details yourself"
21769
21781
  }
21770
21782
  ],
21771
- initialValue: "vercel-neon"
21783
+ initialValue: "vercel"
21772
21784
  });
21773
21785
  if (p9.isCancel(choice)) {
21774
21786
  p9.cancel("Setup cancelled.");
21775
21787
  process.exit(0);
21776
21788
  }
21777
- if (choice === "vercel-neon") {
21789
+ if (choice === "vercel") {
21778
21790
  return { provider: "vercel-cli" };
21779
21791
  }
21780
21792
  const url = await promptConnectionString();
@@ -21822,9 +21834,9 @@ function openBrowser(url) {
21822
21834
  }
21823
21835
 
21824
21836
  // adapters/next/init/prompts/plugins.ts
21825
- import { styleText } from "util";
21837
+ import { styleText as styleText2 } from "util";
21826
21838
  import * as p10 from "@clack/prompts";
21827
- async function promptPlugins(cwd) {
21839
+ async function promptPlugins(cwd, options) {
21828
21840
  const sections = [];
21829
21841
  const overwriteKeys = /* @__PURE__ */ new Set();
21830
21842
  const mergeIntegrationConfig = (collected) => {
@@ -21835,7 +21847,7 @@ async function promptPlugins(cwd) {
21835
21847
  };
21836
21848
  const selectedPlugins = await p10.multiselect({
21837
21849
  message: `Select presets
21838
- ${styleText("dim", "Press [Spacebar] to select/unselect")}`,
21850
+ ${styleText2("dim", "Press [Spacebar] to select/unselect")}`,
21839
21851
  options: [
21840
21852
  {
21841
21853
  value: "blog",
@@ -21873,37 +21885,47 @@ ${styleText("dim", "Press [Spacebar] to select/unselect")}`,
21873
21885
  if (storage === "r2") {
21874
21886
  mergeIntegrationConfig(await collectIntegrationConfig(cwd, ["r2"]));
21875
21887
  }
21876
- const selectedEmailProvider = await p10.select({
21877
- message: `Which email provider do you want to set up?
21878
- ${styleText(
21879
- "dim",
21880
- "Auth + Forms email delivery"
21881
- )}`,
21882
- options: [
21883
- {
21884
- value: "resend",
21885
- label: "Resend"
21886
- },
21887
- {
21888
- value: "skip",
21889
- label: "Skip"
21890
- },
21891
- {
21892
- value: "cloudflare",
21893
- label: `Cloudflare ${styleText("dim", "(coming soon)")}`,
21894
- disabled: true
21895
- }
21896
- ],
21897
- initialValue: "resend"
21898
- });
21899
- if (p10.isCancel(selectedEmailProvider)) {
21900
- p10.cancel("Setup cancelled.");
21901
- process.exit(0);
21902
- }
21903
21888
  const integrations = [];
21904
- if (selectedEmailProvider === "resend") {
21889
+ if (options?.vercelResendApiKey) {
21890
+ p10.log.info(`Email provider: Resend ${styleText2("dim", "(connected via Vercel)")}`);
21905
21891
  integrations.push("resend");
21906
- mergeIntegrationConfig(await collectIntegrationConfig(cwd, ["resend"]));
21892
+ mergeIntegrationConfig(
21893
+ await collectIntegrationConfig(cwd, ["resend"], {
21894
+ resendApiKey: options.vercelResendApiKey
21895
+ })
21896
+ );
21897
+ } else {
21898
+ const selectedEmailProvider = await p10.select({
21899
+ message: `Which email provider do you want to set up?
21900
+ ${styleText2(
21901
+ "dim",
21902
+ "Auth + Forms email delivery"
21903
+ )}`,
21904
+ options: [
21905
+ {
21906
+ value: "resend",
21907
+ label: "Resend"
21908
+ },
21909
+ {
21910
+ value: "skip",
21911
+ label: "Skip"
21912
+ },
21913
+ {
21914
+ value: "cloudflare",
21915
+ label: `Cloudflare ${styleText2("dim", "(coming soon)")}`,
21916
+ disabled: true
21917
+ }
21918
+ ],
21919
+ initialValue: "resend"
21920
+ });
21921
+ if (p10.isCancel(selectedEmailProvider)) {
21922
+ p10.cancel("Setup cancelled.");
21923
+ process.exit(0);
21924
+ }
21925
+ if (selectedEmailProvider === "resend") {
21926
+ integrations.push("resend");
21927
+ mergeIntegrationConfig(await collectIntegrationConfig(cwd, ["resend"]));
21928
+ }
21907
21929
  }
21908
21930
  if (storage === "r2") {
21909
21931
  integrations.push("r2");
@@ -23827,7 +23849,7 @@ function scaffoldTsconfig(cwd, config) {
23827
23849
  }
23828
23850
 
23829
23851
  // adapters/next/init/vercel/flow.ts
23830
- import * as p13 from "@clack/prompts";
23852
+ import * as p15 from "@clack/prompts";
23831
23853
  import pc4 from "picocolors";
23832
23854
 
23833
23855
  // adapters/next/init/vercel/auth.ts
@@ -23956,11 +23978,20 @@ async function checkVercelAuth(runner, cwd, env) {
23956
23978
  }
23957
23979
  async function ensureVercelAuth(runner, cwd, options) {
23958
23980
  const env = options.env ?? process.env;
23981
+ const spinner10 = p12.spinner();
23982
+ spinner10.start(
23983
+ runner.source === "npx" ? "Checking Vercel sign-in (first run downloads the Vercel CLI)" : "Checking Vercel sign-in"
23984
+ );
23959
23985
  const existing = await checkVercelAuth(runner, cwd, env);
23960
- if (existing.authed) return existing;
23986
+ if (existing.authed) {
23987
+ spinner10.stop(signedInMessage(existing.username));
23988
+ return existing;
23989
+ }
23961
23990
  if (env.VERCEL_TOKEN) {
23991
+ spinner10.stop("Could not verify VERCEL_TOKEN with Vercel");
23962
23992
  return { authed: false, reason: "login-failed" };
23963
23993
  }
23994
+ spinner10.stop("Not signed in to Vercel yet");
23964
23995
  p12.log.info(
23965
23996
  `Sign in to Vercel to continue \u2014 a browser link and code will appear below. ${pc3.dim(
23966
23997
  "(or press Ctrl-C to enter a connection string manually)"
@@ -23975,17 +24006,63 @@ async function ensureVercelAuth(runner, cwd, options) {
23975
24006
  if (!login.success) {
23976
24007
  return { authed: false, reason: login.timedOut ? "timeout" : "login-cancelled" };
23977
24008
  }
24009
+ const verifySpinner = p12.spinner();
24010
+ verifySpinner.start("Verifying Vercel sign-in");
23978
24011
  const after = await checkVercelAuth(runner, cwd, env);
23979
- return after.authed ? after : { authed: false, reason: "login-failed" };
24012
+ if (after.authed) {
24013
+ verifySpinner.stop(signedInMessage(after.username));
24014
+ return after;
24015
+ }
24016
+ verifySpinner.stop("Could not verify your Vercel sign-in");
24017
+ return { authed: false, reason: "login-failed" };
24018
+ }
24019
+ function signedInMessage(username) {
24020
+ return username ? `Signed in to Vercel as ${pc3.cyan(username)}` : "Signed in to Vercel";
23980
24021
  }
23981
24022
 
23982
24023
  // adapters/next/init/vercel/neon.ts
24024
+ import * as p13 from "@clack/prompts";
24025
+
24026
+ // adapters/next/init/vercel/env-pull.ts
23983
24027
  import fs34 from "fs";
23984
24028
  import os from "os";
23985
24029
  import path45 from "path";
24030
+ var ENV_PULL_TIMEOUT_MS = 6e4;
24031
+ async function pullVercelEnvValue(runner, cwd, read, env) {
24032
+ const tmpDir = fs34.mkdtempSync(path45.join(os.tmpdir(), "betterstart-vercel-"));
24033
+ const tmpEnv = path45.join(tmpDir, ".env.pull");
24034
+ try {
24035
+ const pull = await runVercel(
24036
+ runner,
24037
+ ["env", "pull", tmpEnv, "--environment", "development", "--yes"],
24038
+ { cwd, mode: "capture", timeoutMs: ENV_PULL_TIMEOUT_MS, env }
24039
+ );
24040
+ if (!pull.success) return void 0;
24041
+ return read(tmpEnv);
24042
+ } finally {
24043
+ try {
24044
+ fs34.rmSync(tmpDir, { recursive: true, force: true });
24045
+ } catch {
24046
+ }
24047
+ }
24048
+ }
24049
+ function parseDotenvFile(filePath) {
24050
+ const vars = /* @__PURE__ */ new Map();
24051
+ if (!fs34.existsSync(filePath)) return vars;
24052
+ for (const line of fs34.readFileSync(filePath, "utf-8").split("\n")) {
24053
+ const trimmed = line.trim();
24054
+ if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
24055
+ const eq = trimmed.indexOf("=");
24056
+ const key = trimmed.slice(0, eq).trim();
24057
+ const value = trimmed.slice(eq + 1).replace(/^['"]|['"]$/g, "").trim();
24058
+ if (key) vars.set(key, value);
24059
+ }
24060
+ return vars;
24061
+ }
24062
+
24063
+ // adapters/next/init/vercel/neon.ts
23986
24064
  var PROVISION_TIMEOUT_MS = 18e4;
23987
24065
  var INTERACTIVE_PROVISION_TIMEOUT_MS = 6e5;
23988
- var ENV_PULL_TIMEOUT_MS = 6e4;
23989
24066
  async function provisionNeonInteractive(runner, cwd, options) {
23990
24067
  const addArgs = ["integration", "add", "neon", "--no-env-pull"];
23991
24068
  if (options.plan) addArgs.push("--plan", options.plan);
@@ -23998,13 +24075,21 @@ async function provisionNeonInteractive(runner, cwd, options) {
23998
24075
  if (!add.success) {
23999
24076
  return { failure: add.timedOut ? "timeout" : "provision-failed" };
24000
24077
  }
24078
+ const pullSpinner = p13.spinner();
24079
+ pullSpinner.start("Retrieving the database connection string");
24001
24080
  const databaseUrl = await pullDatabaseUrl(runner, cwd, options.env);
24002
- if (!databaseUrl) return { failure: "env-pull-empty" };
24081
+ if (!databaseUrl) {
24082
+ pullSpinner.stop("No DATABASE_URL came back from Vercel");
24083
+ return { failure: "env-pull-empty" };
24084
+ }
24085
+ pullSpinner.stop("Captured the Neon DATABASE_URL");
24003
24086
  return { databaseUrl };
24004
24087
  }
24005
24088
  async function provisionNeon(runner, cwd, options) {
24006
24089
  const addArgs = ["integration", "add", "neon", "--no-env-pull", "--format", "json"];
24007
24090
  if (options.plan) addArgs.push("--plan", options.plan);
24091
+ const spinner10 = p13.spinner();
24092
+ spinner10.start("Provisioning Neon Postgres via Vercel");
24008
24093
  const add = await runVercel(runner, addArgs, {
24009
24094
  cwd,
24010
24095
  mode: "capture",
@@ -24012,6 +24097,7 @@ async function provisionNeon(runner, cwd, options) {
24012
24097
  env: options.env
24013
24098
  });
24014
24099
  if (!add.success) {
24100
+ spinner10.stop("Neon provisioning did not complete");
24015
24101
  const combined = `${add.stdout}
24016
24102
  ${add.stderr}`.trim();
24017
24103
  const detail = combined || void 0;
@@ -24021,39 +24107,20 @@ ${add.stderr}`.trim();
24021
24107
  }
24022
24108
  const meta = parseVercelJson(add.stdout);
24023
24109
  const resourceName = meta?.resourceName ?? meta?.name;
24110
+ spinner10.message("Retrieving the database connection string");
24024
24111
  const databaseUrl = await pullDatabaseUrl(runner, cwd, options.env);
24025
- if (!databaseUrl) return { resourceName, failure: "env-pull-empty" };
24112
+ if (!databaseUrl) {
24113
+ spinner10.stop("No DATABASE_URL came back from Vercel");
24114
+ return { resourceName, failure: "env-pull-empty" };
24115
+ }
24116
+ spinner10.stop("Captured the Neon DATABASE_URL");
24026
24117
  return { databaseUrl, resourceName };
24027
24118
  }
24028
- async function pullDatabaseUrl(runner, cwd, env) {
24029
- const tmpDir = fs34.mkdtempSync(path45.join(os.tmpdir(), "betterstart-vercel-"));
24030
- const tmpEnv = path45.join(tmpDir, ".env.pull");
24031
- try {
24032
- const pull = await runVercel(
24033
- runner,
24034
- ["env", "pull", tmpEnv, "--environment", "development", "--yes"],
24035
- { cwd, mode: "capture", timeoutMs: ENV_PULL_TIMEOUT_MS, env }
24036
- );
24037
- if (!pull.success) return void 0;
24038
- return readDbUrlFromDotenv(tmpEnv);
24039
- } finally {
24040
- try {
24041
- fs34.rmSync(tmpDir, { recursive: true, force: true });
24042
- } catch {
24043
- }
24044
- }
24119
+ function pullDatabaseUrl(runner, cwd, env) {
24120
+ return pullVercelEnvValue(runner, cwd, readDbUrlFromDotenv, env);
24045
24121
  }
24046
24122
  function readDbUrlFromDotenv(filePath) {
24047
- if (!fs34.existsSync(filePath)) return void 0;
24048
- const vars = /* @__PURE__ */ new Map();
24049
- for (const line of fs34.readFileSync(filePath, "utf-8").split("\n")) {
24050
- const trimmed = line.trim();
24051
- if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
24052
- const eq = trimmed.indexOf("=");
24053
- const key = trimmed.slice(0, eq).trim();
24054
- const value = trimmed.slice(eq + 1).replace(/^['"]|['"]$/g, "").trim();
24055
- if (key) vars.set(key, value);
24056
- }
24123
+ const vars = parseDotenvFile(filePath);
24057
24124
  const isPg = (v) => !!v && (v.startsWith("postgres://") || v.startsWith("postgresql://"));
24058
24125
  if (isPg(vars.get("DATABASE_URL"))) return vars.get("DATABASE_URL");
24059
24126
  if (isPg(vars.get("POSTGRES_URL"))) return vars.get("POSTGRES_URL");
@@ -24073,13 +24140,40 @@ function sanitizeVercelProjectName(name) {
24073
24140
  const cleaned = name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[-_.]+/, "").replace(/[-_.]+$/, "").slice(0, 100);
24074
24141
  return cleaned || "my-app";
24075
24142
  }
24143
+ var MAX_NAME_ATTEMPTS = 10;
24144
+ var INSPECT_TIMEOUT_MS = 3e4;
24145
+ function versionedVercelProjectName(base, attempt) {
24146
+ if (attempt <= 1) return base;
24147
+ const suffix = `-v${attempt}`;
24148
+ return `${base.slice(0, 100 - suffix.length)}${suffix}`;
24149
+ }
24076
24150
  async function createVercelProject(runner, cwd, name, env) {
24077
- const projectName = sanitizeVercelProjectName(name);
24078
- await runVercel(runner, ["projects", "add", projectName], {
24079
- cwd,
24080
- mode: "capture",
24081
- env
24082
- });
24151
+ const base = sanitizeVercelProjectName(name);
24152
+ let projectName = base;
24153
+ for (let attempt = 1; attempt <= MAX_NAME_ATTEMPTS; attempt++) {
24154
+ const candidate = versionedVercelProjectName(base, attempt);
24155
+ projectName = candidate;
24156
+ const inspect = await runVercel(runner, ["project", "inspect", candidate], {
24157
+ cwd,
24158
+ mode: "capture",
24159
+ timeoutMs: INSPECT_TIMEOUT_MS,
24160
+ env
24161
+ });
24162
+ if (inspect.success) continue;
24163
+ const add = await runVercel(runner, ["projects", "add", candidate], {
24164
+ cwd,
24165
+ mode: "capture",
24166
+ env
24167
+ });
24168
+ if (add.success) break;
24169
+ if (/already exists/i.test(`${add.stdout}
24170
+ ${add.stderr}`)) continue;
24171
+ break;
24172
+ }
24173
+ try {
24174
+ fs35.rmSync(path46.join(cwd, ".vercel", "project.json"), { force: true });
24175
+ } catch {
24176
+ }
24083
24177
  const link = await runVercel(runner, ["link", "--project", projectName, "--yes"], {
24084
24178
  cwd,
24085
24179
  mode: "capture",
@@ -24102,20 +24196,58 @@ function readLinkedProjectId(cwd) {
24102
24196
  }
24103
24197
  }
24104
24198
 
24199
+ // adapters/next/init/vercel/resend.ts
24200
+ import * as p14 from "@clack/prompts";
24201
+ var INTERACTIVE_PROVISION_TIMEOUT_MS2 = 6e5;
24202
+ async function provisionResendInteractive(runner, cwd, options) {
24203
+ const add = await runVercel(runner, ["integration", "add", "resend", "--no-env-pull"], {
24204
+ cwd,
24205
+ mode: "inherit",
24206
+ timeoutMs: INTERACTIVE_PROVISION_TIMEOUT_MS2,
24207
+ env: options.env
24208
+ });
24209
+ if (!add.success) {
24210
+ return { failure: add.timedOut ? "timeout" : "provision-failed" };
24211
+ }
24212
+ const pullSpinner = p14.spinner();
24213
+ pullSpinner.start("Retrieving the Resend API key");
24214
+ const apiKey = await pullVercelEnvValue(runner, cwd, readResendApiKeyFromDotenv, options.env);
24215
+ if (!apiKey) {
24216
+ pullSpinner.stop("No RESEND_API_KEY came back from Vercel");
24217
+ return { failure: "env-pull-empty" };
24218
+ }
24219
+ pullSpinner.stop("Captured the Resend API key");
24220
+ return { apiKey };
24221
+ }
24222
+ function readResendApiKeyFromDotenv(filePath) {
24223
+ const vars = parseDotenvFile(filePath);
24224
+ const direct = vars.get("RESEND_API_KEY");
24225
+ if (direct) return direct;
24226
+ for (const [key, value] of vars) {
24227
+ if (/RESEND_API_KEY$/.test(key) && value) return value;
24228
+ }
24229
+ for (const value of vars.values()) {
24230
+ if (value.startsWith("re_")) return value;
24231
+ }
24232
+ return void 0;
24233
+ }
24234
+
24105
24235
  // adapters/next/init/vercel/flow.ts
24106
24236
  async function runVercelNeonFlow(options) {
24107
24237
  const env = options.env ?? process.env;
24108
24238
  try {
24239
+ const runnerSpinner = p15.spinner();
24240
+ runnerSpinner.start("Checking for the Vercel CLI");
24109
24241
  const runner = await resolveVercelRunner();
24242
+ runnerSpinner.stop(
24243
+ runner.source === "path" ? "Using the Vercel CLI from your PATH" : `Using ${pc4.cyan(`npx vercel@${VERCEL_CLI_VERSION}`)}`
24244
+ );
24110
24245
  const auth = await ensureVercelAuth(runner, options.cwd, { env });
24111
24246
  if (!auth.authed) {
24112
- p13.log.warn(authFailureMessage(auth.reason));
24247
+ p15.log.warn(authFailureMessage(auth.reason));
24113
24248
  return { ok: false };
24114
24249
  }
24115
- if (auth.username) {
24116
- p13.log.success(`Signed in to Vercel as ${pc4.cyan(auth.username)}`);
24117
- }
24118
- const projectSpinner = p13.spinner();
24250
+ const projectSpinner = p15.spinner();
24119
24251
  projectSpinner.start(`Creating Vercel project ${pc4.cyan(options.projectName)}`);
24120
24252
  const project2 = await createVercelProject(runner, options.cwd, options.projectName, env);
24121
24253
  projectSpinner.stop(
@@ -24123,19 +24255,33 @@ async function runVercelNeonFlow(options) {
24123
24255
  );
24124
24256
  const neon = await provisionNeonForMode(runner, options);
24125
24257
  if (neon.failure || !neon.databaseUrl) {
24126
- p13.log.warn(neonFailureMessage(neon.failure));
24127
- if (neon.detail) p13.log.message(pc4.dim(neon.detail));
24258
+ p15.log.warn(neonFailureMessage(neon.failure));
24259
+ if (neon.detail) p15.log.message(pc4.dim(neon.detail));
24128
24260
  return { ok: false };
24129
24261
  }
24130
- p13.log.success(`Provisioned Neon Postgres for ${pc4.cyan(project2.name)}`);
24262
+ p15.log.success(`Provisioned Neon Postgres for ${pc4.cyan(project2.name)}`);
24263
+ let resendApiKey;
24264
+ if (options.provisionResend && options.interactive) {
24265
+ p15.log.info(
24266
+ `Set up Resend email in the Vercel prompts below ${pc4.dim("(the Free plan is recommended).")}`
24267
+ );
24268
+ const resend = await provisionResendInteractive(runner, options.cwd, { env });
24269
+ if (resend.apiKey) {
24270
+ resendApiKey = resend.apiKey;
24271
+ p15.log.success(`Provisioned Resend email for ${pc4.cyan(project2.name)}`);
24272
+ } else {
24273
+ p15.log.warn(resendFailureMessage(resend.failure));
24274
+ }
24275
+ }
24131
24276
  return {
24132
24277
  ok: true,
24133
24278
  databaseUrl: neon.databaseUrl,
24134
24279
  vercelProjectId: project2.projectId,
24135
- neonResourceName: neon.resourceName
24280
+ neonResourceName: neon.resourceName,
24281
+ resendApiKey
24136
24282
  };
24137
24283
  } catch (error) {
24138
- p13.log.warn(
24284
+ p15.log.warn(
24139
24285
  `Vercel provisioning failed: ${error instanceof Error ? error.message : String(error)}`
24140
24286
  );
24141
24287
  return { ok: false };
@@ -24144,7 +24290,7 @@ async function runVercelNeonFlow(options) {
24144
24290
  function provisionNeonForMode(runner, options) {
24145
24291
  const opts = { plan: options.plan, env: options.env };
24146
24292
  if (options.interactive) {
24147
- p13.log.info(
24293
+ p15.log.info(
24148
24294
  `Create your Neon database in the Vercel prompts below ${pc4.dim("(the Free plan is recommended).")}`
24149
24295
  );
24150
24296
  return provisionNeonInteractive(runner, options.cwd, opts);
@@ -24165,6 +24311,16 @@ function authFailureMessage(reason) {
24165
24311
  return "Could not sign in to Vercel.";
24166
24312
  }
24167
24313
  }
24314
+ function resendFailureMessage(reason) {
24315
+ switch (reason) {
24316
+ case "env-pull-empty":
24317
+ return "Provisioned Resend, but no RESEND_API_KEY came back \u2014 you can pick an email provider next.";
24318
+ case "timeout":
24319
+ return "Resend provisioning timed out \u2014 you can pick an email provider next.";
24320
+ default:
24321
+ return "Resend provisioning did not complete \u2014 you can pick an email provider next.";
24322
+ }
24323
+ }
24168
24324
  function neonFailureMessage(reason) {
24169
24325
  switch (reason) {
24170
24326
  case "terms-required":
@@ -24427,13 +24583,13 @@ async function runSeedCommand(options) {
24427
24583
  }
24428
24584
  );
24429
24585
  });
24430
- const spinner7 = clack.spinner();
24431
- spinner7.start("Creating admin user...");
24586
+ const spinner10 = clack.spinner();
24587
+ spinner10.start("Creating admin user...");
24432
24588
  try {
24433
24589
  const result = await runSeed2(false);
24434
24590
  if (result.code === 2) {
24435
24591
  const existingName = result.stdout.split("\n").find((l) => l.startsWith("EXISTING_USER:"))?.replace("EXISTING_USER:", "")?.trim() || "unknown";
24436
- spinner7.stop(`Account already exists for ${email}`);
24592
+ spinner10.stop(`Account already exists for ${email}`);
24437
24593
  const overwrite = await clack.confirm({
24438
24594
  message: `An admin account (${existingName}) already exists with this email. Replace it?`
24439
24595
  });
@@ -24445,14 +24601,14 @@ async function runSeedCommand(options) {
24445
24601
  }
24446
24602
  return;
24447
24603
  }
24448
- spinner7.start("Replacing admin user...");
24604
+ spinner10.start("Replacing admin user...");
24449
24605
  await runSeed2(true);
24450
- spinner7.stop("Admin user replaced");
24606
+ spinner10.stop("Admin user replaced");
24451
24607
  } else {
24452
- spinner7.stop("Admin user created");
24608
+ spinner10.stop("Admin user created");
24453
24609
  }
24454
24610
  } catch (err) {
24455
- spinner7.stop("Failed to create admin user");
24611
+ spinner10.stop("Failed to create admin user");
24456
24612
  const errMsg = err instanceof Error ? err.message : String(err);
24457
24613
  clack.log.error(errMsg);
24458
24614
  clack.log.info("You can run the seed script manually:");
@@ -24552,7 +24708,7 @@ function removeExistingAdminPaths(cwd, namespaces) {
24552
24708
  return removed;
24553
24709
  }
24554
24710
  async function runInitCommand(name, options) {
24555
- p14.intro(pc5.bgCyan(pc5.black(" Setup Your Dashboard ")));
24711
+ p16.intro(pc5.bgCyan(pc5.black(" Setup Your Dashboard ")));
24556
24712
  let cwd = process.cwd();
24557
24713
  let projectName = path48.basename(cwd);
24558
24714
  let forceMode = Boolean(options.force);
@@ -24561,13 +24717,13 @@ async function runInitCommand(name, options) {
24561
24717
  try {
24562
24718
  namespace = validateAdminNamespace(options.namespace);
24563
24719
  } catch (error) {
24564
- p14.log.error(error instanceof Error ? error.message : String(error));
24720
+ p16.log.error(error instanceof Error ? error.message : String(error));
24565
24721
  process.exit(1);
24566
24722
  }
24567
24723
  }
24568
24724
  if (!options.yes && !options.namespace) {
24569
24725
  const defaultDashboardPath = resolveAdminNamespace(DEFAULT_ADMIN_NAMESPACE).routePath;
24570
- const namespaceInput = await p14.text({
24726
+ const namespaceInput = await p16.text({
24571
24727
  message: "What's the path you want the dashboard to be?",
24572
24728
  placeholder: `eg. ${defaultDashboardPath}`,
24573
24729
  defaultValue: defaultDashboardPath,
@@ -24580,8 +24736,8 @@ async function runInitCommand(name, options) {
24580
24736
  }
24581
24737
  }
24582
24738
  });
24583
- if (p14.isCancel(namespaceInput)) {
24584
- p14.cancel("Setup cancelled.");
24739
+ if (p16.isCancel(namespaceInput)) {
24740
+ p16.cancel("Setup cancelled.");
24585
24741
  process.exit(0);
24586
24742
  }
24587
24743
  namespace = validateAdminDashboardPath(namespaceInput);
@@ -24591,16 +24747,16 @@ async function runInitCommand(name, options) {
24591
24747
  let isFreshProject = false;
24592
24748
  let srcDir;
24593
24749
  if (project2.isExisting) {
24594
- p14.log.info(`Next.js app detected ${pc5.dim("\xB7")} ${pc5.cyan(pm)}`);
24750
+ p16.log.info(`Next.js app detected ${pc5.dim("\xB7")} ${pc5.cyan(pm)}`);
24595
24751
  srcDir = project2.hasSrcDir;
24596
24752
  if (!project2.hasTypeScript) {
24597
- p14.log.error("TypeScript is required. Please add a tsconfig.json first.");
24753
+ p16.log.error("TypeScript is required. Please add a tsconfig.json first.");
24598
24754
  process.exit(1);
24599
24755
  }
24600
24756
  if (forceMode) {
24601
24757
  const nuked = removeExistingAdminPaths(cwd, await resolveForceInitNamespaces(cwd, namespace));
24602
24758
  if (nuked > 0) {
24603
- p14.log.warn(`${pc5.yellow("Force mode:")} removed ${nuked} existing admin paths`);
24759
+ p16.log.warn(`${pc5.yellow("Force mode:")} removed ${nuked} existing admin paths`);
24604
24760
  }
24605
24761
  project2 = detectProject(cwd, namespace);
24606
24762
  } else if (project2.conflicts.length > 0) {
@@ -24609,9 +24765,9 @@ async function runInitCommand(name, options) {
24609
24765
  "",
24610
24766
  pc5.dim(`Use ${pc5.bold("--force")} to remove existing admin files before scaffolding.`)
24611
24767
  );
24612
- p14.note(conflictLines.join("\n"), pc5.yellow("Conflicts"));
24768
+ p16.note(conflictLines.join("\n"), pc5.yellow("Conflicts"));
24613
24769
  if (!options.yes) {
24614
- const proceed = await p14.confirm({
24770
+ const proceed = await p16.confirm({
24615
24771
  message: [
24616
24772
  `Continue with ${pc5.bold(pc5.cyan("--force"))}?`,
24617
24773
  `${pc5.cyan("\u2502")} ${pc5.dim("This will force overwrite the existing admin code.")}`,
@@ -24619,8 +24775,8 @@ async function runInitCommand(name, options) {
24619
24775
  ].join("\n"),
24620
24776
  initialValue: true
24621
24777
  });
24622
- if (p14.isCancel(proceed) || !proceed) {
24623
- p14.cancel("Setup cancelled.");
24778
+ if (p16.isCancel(proceed) || !proceed) {
24779
+ p16.cancel("Setup cancelled.");
24624
24780
  process.exit(0);
24625
24781
  }
24626
24782
  forceMode = true;
@@ -24629,23 +24785,23 @@ async function runInitCommand(name, options) {
24629
24785
  await resolveForceInitNamespaces(cwd, namespace)
24630
24786
  );
24631
24787
  if (nuked > 0) {
24632
- p14.log.warn(`${pc5.yellow("Force mode:")} removed ${nuked} existing admin paths`);
24788
+ p16.log.warn(`${pc5.yellow("Force mode:")} removed ${nuked} existing admin paths`);
24633
24789
  }
24634
24790
  project2 = detectProject(cwd, namespace);
24635
24791
  }
24636
24792
  }
24637
24793
  } else {
24638
- p14.log.info("No Next.js app found \u2014 Running the fresh project mode...");
24794
+ p16.log.info("No Next.js app found \u2014 Running the fresh project mode...");
24639
24795
  let projectPrompt;
24640
24796
  try {
24641
24797
  projectPrompt = options.yes ? resolveNonInteractiveProject(name) : await promptProject(name);
24642
24798
  } catch (error) {
24643
- p14.log.error(error instanceof Error ? error.message : String(error));
24799
+ p16.log.error(error instanceof Error ? error.message : String(error));
24644
24800
  process.exit(1);
24645
24801
  }
24646
24802
  srcDir = projectPrompt.useSrcDir;
24647
24803
  if (!options.yes) {
24648
- const pmChoice = await p14.select({
24804
+ const pmChoice = await p16.select({
24649
24805
  message: "Which package manager do you want to use?",
24650
24806
  options: [
24651
24807
  { value: "pnpm", label: "pnpm", hint: "recommended" },
@@ -24654,8 +24810,8 @@ async function runInitCommand(name, options) {
24654
24810
  { value: "bun", label: "bun" }
24655
24811
  ]
24656
24812
  });
24657
- if (p14.isCancel(pmChoice)) {
24658
- p14.cancel("Setup cancelled.");
24813
+ if (p16.isCancel(pmChoice)) {
24814
+ p16.cancel("Setup cancelled.");
24659
24815
  process.exit(0);
24660
24816
  }
24661
24817
  pm = pmChoice;
@@ -24677,7 +24833,7 @@ async function runInitCommand(name, options) {
24677
24833
  ];
24678
24834
  if (srcDir) cnaArgs.push("--src-dir");
24679
24835
  else cnaArgs.push("--no-src-dir");
24680
- const createNextAppSpinner = p14.spinner();
24836
+ const createNextAppSpinner = p16.spinner();
24681
24837
  createNextAppSpinner.start(`Creating a Next.js app (latest)`);
24682
24838
  const createNextAppResult = await runQuietCommand(bin, cnaArgs, {
24683
24839
  cwd,
@@ -24689,8 +24845,8 @@ async function runInitCommand(name, options) {
24689
24845
  process.stderr.write(`${createNextAppResult.output.trimEnd()}
24690
24846
  `);
24691
24847
  }
24692
- p14.log.error(createNextAppResult.error);
24693
- p14.log.info(
24848
+ p16.log.error(createNextAppResult.error);
24849
+ p16.log.info(
24694
24850
  `You can create the project manually:
24695
24851
  ${pc5.cyan(`npx create-next-app@latest ${projectPrompt.projectName} --typescript --tailwind --app`)}
24696
24852
  Then run ${pc5.cyan("betterstart init")} inside it.`
@@ -24704,11 +24860,11 @@ async function runInitCommand(name, options) {
24704
24860
  );
24705
24861
  if (!hasPackageJson || !hasNextConfig) {
24706
24862
  createNextAppSpinner.stop(`Failed to create Next.js app: ${displayName}`);
24707
- p14.log.error(
24863
+ p16.log.error(
24708
24864
  "create-next-app completed but the project was not created. This can happen with nested npx calls."
24709
24865
  );
24710
24866
  const manualCmd = `npx create-next-app@latest ${projectPrompt.projectName} --typescript --tailwind --app`;
24711
- p14.log.info(
24867
+ p16.log.info(
24712
24868
  `Create the project manually:
24713
24869
  ${pc5.cyan(manualCmd)}
24714
24870
  Then run ${pc5.cyan("betterstart init")} inside it.`
@@ -24721,46 +24877,13 @@ async function runInitCommand(name, options) {
24721
24877
  project2 = detectProject(cwd, namespace);
24722
24878
  isFreshProject = true;
24723
24879
  }
24724
- const selectedPlugins = (() => {
24725
- if (options.plugins) {
24726
- return parsePluginList(options.plugins);
24727
- }
24728
- return [];
24729
- })();
24730
- const selectedIntegrations = (() => {
24731
- if (options.integrations) {
24732
- return parseIntegrationList(options.integrations);
24733
- }
24734
- return [];
24735
- })();
24736
- let collectedIntegrationConfig = {
24737
- sections: [],
24738
- overwriteKeys: /* @__PURE__ */ new Set()
24739
- };
24740
- let pluginSelection;
24741
- if (selectedPlugins.length > 0 || selectedIntegrations.length > 0) {
24742
- pluginSelection = {
24743
- plugins: selectedPlugins,
24744
- integrations: selectedIntegrations,
24745
- storage: selectedIntegrations.includes("r2") ? "r2" : "local"
24746
- };
24747
- } else if (options.yes) {
24748
- pluginSelection = { plugins: [], integrations: [], storage: "local" };
24749
- } else {
24750
- const promptResult = await promptPlugins(cwd);
24751
- collectedIntegrationConfig = promptResult.integrationConfig;
24752
- pluginSelection = {
24753
- plugins: promptResult.plugins,
24754
- integrations: promptResult.integrations,
24755
- storage: promptResult.storage
24756
- };
24757
- }
24758
24880
  let databaseUrl;
24881
+ let vercelResendApiKey;
24759
24882
  const existingDbUrl = readExistingDbUrl(cwd);
24760
24883
  if (options.yes) {
24761
24884
  if (options.databaseUrl) {
24762
24885
  if (!isValidDbUrl(options.databaseUrl)) {
24763
- p14.log.error(
24886
+ p16.log.error(
24764
24887
  `Invalid database URL. Must start with ${pc5.cyan("postgres://")} or ${pc5.cyan("postgresql://")}`
24765
24888
  );
24766
24889
  process.exit(1);
@@ -24779,33 +24902,76 @@ async function runInitCommand(name, options) {
24779
24902
  if (flow.ok && flow.databaseUrl) {
24780
24903
  databaseUrl = flow.databaseUrl;
24781
24904
  } else {
24782
- p14.log.warn("Vercel provisioning did not complete; continuing without a database URL.");
24905
+ p16.log.warn("Vercel provisioning did not complete; continuing without a database URL.");
24783
24906
  }
24784
24907
  }
24785
24908
  } else if (existingDbUrl) {
24786
24909
  const masked = maskDbUrl(existingDbUrl);
24787
- p14.log.info(`Using existing database URL from .env.local ${pc5.dim(`(${masked})`)}`);
24910
+ p16.log.info(`Using existing database URL from .env.local ${pc5.dim(`(${masked})`)}`);
24788
24911
  databaseUrl = existingDbUrl;
24789
24912
  } else {
24790
- const dbResult = await promptDatabase({ allowVercel: options.vercel !== false });
24791
- if (dbResult.provider === "vercel-cli") {
24913
+ const servicesResult = await promptServices({ allowVercel: options.vercel !== false });
24914
+ if (servicesResult.provider === "vercel-cli") {
24792
24915
  const flow = await runVercelNeonFlow({
24793
24916
  cwd,
24794
24917
  projectName,
24795
24918
  interactive: true,
24796
24919
  plan: options.vercelPlan,
24797
- env: process.env
24920
+ env: process.env,
24921
+ provisionResend: true
24798
24922
  });
24799
24923
  if (flow.ok && flow.databaseUrl) {
24800
24924
  databaseUrl = flow.databaseUrl;
24925
+ vercelResendApiKey = flow.resendApiKey;
24801
24926
  } else {
24802
- p14.log.info("Falling back to a manual database connection string.");
24927
+ p16.log.info("Falling back to a manual database connection string.");
24803
24928
  openBrowserVercelNeon();
24804
24929
  databaseUrl = await promptConnectionString();
24805
24930
  }
24806
24931
  } else {
24807
- databaseUrl = dbResult.url;
24932
+ databaseUrl = servicesResult.url;
24933
+ }
24934
+ }
24935
+ const selectedPlugins = (() => {
24936
+ if (options.plugins) {
24937
+ return parsePluginList(options.plugins);
24938
+ }
24939
+ return [];
24940
+ })();
24941
+ const selectedIntegrations = (() => {
24942
+ if (options.integrations) {
24943
+ return parseIntegrationList(options.integrations);
24944
+ }
24945
+ return [];
24946
+ })();
24947
+ let collectedIntegrationConfig = {
24948
+ sections: [],
24949
+ overwriteKeys: /* @__PURE__ */ new Set()
24950
+ };
24951
+ let pluginSelection;
24952
+ if (selectedPlugins.length > 0 || selectedIntegrations.length > 0) {
24953
+ pluginSelection = {
24954
+ plugins: selectedPlugins,
24955
+ integrations: selectedIntegrations,
24956
+ storage: selectedIntegrations.includes("r2") ? "r2" : "local"
24957
+ };
24958
+ if (vercelResendApiKey) {
24959
+ collectedIntegrationConfig.sections.push({
24960
+ header: "Email (Resend)",
24961
+ vars: [{ key: "BETTERSTART_RESEND_API_KEY", value: vercelResendApiKey }]
24962
+ });
24963
+ collectedIntegrationConfig.overwriteKeys.add("BETTERSTART_RESEND_API_KEY");
24808
24964
  }
24965
+ } else if (options.yes) {
24966
+ pluginSelection = { plugins: [], integrations: [], storage: "local" };
24967
+ } else {
24968
+ const promptResult = await promptPlugins(cwd, { vercelResendApiKey });
24969
+ collectedIntegrationConfig = promptResult.integrationConfig;
24970
+ pluginSelection = {
24971
+ plugins: promptResult.plugins,
24972
+ integrations: promptResult.integrations,
24973
+ storage: promptResult.storage
24974
+ };
24809
24975
  }
24810
24976
  const nextMajorVersion = detectNextMajorVersion(cwd);
24811
24977
  const config = {
@@ -24816,7 +24982,7 @@ async function runInitCommand(name, options) {
24816
24982
  config.paths = config.frameworkConfig.next.paths;
24817
24983
  config.database.migrationsDir = deriveMigrationsDir(namespace);
24818
24984
  const results = [];
24819
- const s = p14.spinner();
24985
+ const s = p16.spinner();
24820
24986
  s.start("Directory structure");
24821
24987
  const baseFiles = scaffoldBase({
24822
24988
  cwd,
@@ -24893,7 +25059,7 @@ async function runInitCommand(name, options) {
24893
25059
  });
24894
25060
  s.stop("");
24895
25061
  process.stdout.write("\x1B[2A\x1B[J");
24896
- p14.note(noteLines.join("\n"), "Scaffolded admin");
25062
+ p16.note(noteLines.join("\n"), "Scaffolded admin");
24897
25063
  const drizzleConfigPath = path48.join(cwd, "drizzle.config.ts");
24898
25064
  if (!dbFiles.includes("drizzle.config.ts") && fs37.existsSync(drizzleConfigPath)) {
24899
25065
  if (forceMode) {
@@ -24903,20 +25069,20 @@ async function runInitCommand(name, options) {
24903
25069
  readNamespacedTemplate("drizzle.config.ts", namespace),
24904
25070
  "utf-8"
24905
25071
  );
24906
- p14.log.success("Updated drizzle.config.ts");
25072
+ p16.log.success("Updated drizzle.config.ts");
24907
25073
  } else if (!options.yes) {
24908
- const overwrite = await p14.confirm({
25074
+ const overwrite = await p16.confirm({
24909
25075
  message: "drizzle.config.ts already exists. Overwrite with latest version?",
24910
25076
  initialValue: true
24911
25077
  });
24912
- if (!p14.isCancel(overwrite) && overwrite) {
25078
+ if (!p16.isCancel(overwrite) && overwrite) {
24913
25079
  const { readNamespacedTemplate } = await import("./template-reader-X6KFP7B3.js");
24914
25080
  fs37.writeFileSync(
24915
25081
  drizzleConfigPath,
24916
25082
  readNamespacedTemplate("drizzle.config.ts", namespace),
24917
25083
  "utf-8"
24918
25084
  );
24919
- p14.log.success("Updated drizzle.config.ts");
25085
+ p16.log.success("Updated drizzle.config.ts");
24920
25086
  }
24921
25087
  }
24922
25088
  }
@@ -24942,8 +25108,8 @@ async function runInitCommand(name, options) {
24942
25108
  depsInstalled = true;
24943
25109
  } else {
24944
25110
  s.stop("Failed to install dependencies");
24945
- p14.log.warning(depsResult.error ?? "Unknown error");
24946
- p14.log.info(
25111
+ p16.log.warning(depsResult.error ?? "Unknown error");
25112
+ p16.log.info(
24947
25113
  `You can install them manually:
24948
25114
  ${pc5.cyan(`${pm} add ${depsResult.dependencies.join(" ")}`)}
24949
25115
  ${pc5.cyan(`${pm} add -D ${depsResult.devDeps.join(" ")}`)}`
@@ -25032,10 +25198,10 @@ async function runInitCommand(name, options) {
25032
25198
  ` ${pc5.yellow("\u25B2")} Local filesystem storage is only for development or self-hosted persistent-disk deployments. Use Cloudflare R2 or AWS S3 for hosted/serverless production.`
25033
25199
  );
25034
25200
  }
25035
- p14.note(installLines.join("\n"), "Installed");
25201
+ p16.note(installLines.join("\n"), "Installed");
25036
25202
  let dbPushed = false;
25037
25203
  if (depsResult.success && options.skipMigration && hasDbUrl(cwd)) {
25038
- p14.log.info(`Skipping database schema push ${pc5.dim("(--skip-migration)")}`);
25204
+ p16.log.info(`Skipping database schema push ${pc5.dim("(--skip-migration)")}`);
25039
25205
  } else if (depsResult.success && hasDbUrl(cwd)) {
25040
25206
  let driverReady = hasDrizzleKitPostgresDriverDependency(cwd);
25041
25207
  if (!driverReady) {
@@ -25050,8 +25216,8 @@ async function runInitCommand(name, options) {
25050
25216
  });
25051
25217
  if (!driverResult.success) {
25052
25218
  s.stop("Database push failed");
25053
- p14.log.warning(driverResult.error ?? `Failed to install ${DRIZZLE_KIT_POSTGRES_DRIVER_DEP}`);
25054
- p14.log.info(
25219
+ p16.log.warning(driverResult.error ?? `Failed to install ${DRIZZLE_KIT_POSTGRES_DRIVER_DEP}`);
25220
+ p16.log.info(
25055
25221
  `Install ${DRIZZLE_KIT_POSTGRES_DRIVER_DEP} manually, then run: ${pc5.cyan(drizzlePushCommand(pm))}`
25056
25222
  );
25057
25223
  } else {
@@ -25062,7 +25228,7 @@ async function runInitCommand(name, options) {
25062
25228
  if (driverReady) {
25063
25229
  const useTerminalForDbPush = shouldUseTerminalForDbPush(options);
25064
25230
  if (useTerminalForDbPush) {
25065
- p14.log.info(`Running ${pc5.cyan("drizzle-kit push --force")}`);
25231
+ p16.log.info(`Running ${pc5.cyan("drizzle-kit push --force")}`);
25066
25232
  } else {
25067
25233
  s.start("Pushing database schema (drizzle-kit push)");
25068
25234
  }
@@ -25071,13 +25237,13 @@ async function runInitCommand(name, options) {
25071
25237
  if (useTerminalForDbPush) {
25072
25238
  const verification = await verifyDatabaseReachable(cwd);
25073
25239
  if (!verification.success) {
25074
- p14.log.warning(verification.error);
25075
- p14.log.error("Database was not reachable. Aborting setup.");
25240
+ p16.log.warning(verification.error);
25241
+ p16.log.error("Database was not reachable. Aborting setup.");
25076
25242
  process.exit(1);
25077
25243
  }
25078
25244
  }
25079
25245
  if (useTerminalForDbPush) {
25080
- p14.log.success("Database schema pushed");
25246
+ p16.log.success("Database schema pushed");
25081
25247
  } else {
25082
25248
  s.stop(`${pc5.green("\u2713")} Database schema pushed`);
25083
25249
  }
@@ -25087,12 +25253,12 @@ async function runInitCommand(name, options) {
25087
25253
  s.stop("Database push failed");
25088
25254
  }
25089
25255
  const pushError = pushResult.error ?? "Unknown error";
25090
- p14.log.warning(pushError);
25256
+ p16.log.warning(pushError);
25091
25257
  if (isDatabaseReachabilityError(pushError)) {
25092
- p14.log.error("Database was not reachable. Aborting setup.");
25258
+ p16.log.error("Database was not reachable. Aborting setup.");
25093
25259
  process.exit(1);
25094
25260
  }
25095
- p14.log.info(`You can run it manually: ${pc5.cyan(drizzlePushCommand(pm))}`);
25261
+ p16.log.info(`You can run it manually: ${pc5.cyan(drizzlePushCommand(pm))}`);
25096
25262
  }
25097
25263
  }
25098
25264
  }
@@ -25101,7 +25267,7 @@ async function runInitCommand(name, options) {
25101
25267
  let seedSuccess = false;
25102
25268
  let adminAccountReady = false;
25103
25269
  if (dbPushed && options.skipAdminCreation) {
25104
- p14.log.info(
25270
+ p16.log.info(
25105
25271
  `Skipping admin user creation ${pc5.dim(`(use ${betterstartExecCommand(pm, "seed")} later)`)}`
25106
25272
  );
25107
25273
  }
@@ -25111,14 +25277,14 @@ async function runInitCommand(name, options) {
25111
25277
  let replaceExistingAdmin = false;
25112
25278
  const adminCheck = await checkExistingAdmin(cwd, adminDir, authBasePath);
25113
25279
  if (adminCheck.error) {
25114
- p14.log.warning(`Could not verify existing admin account ${pc5.dim(`(${adminCheck.error})`)}`);
25280
+ p16.log.warning(`Could not verify existing admin account ${pc5.dim(`(${adminCheck.error})`)}`);
25115
25281
  if (isDatabaseReachabilityError(adminCheck.error)) {
25116
- p14.log.error("Database was not reachable. Aborting setup.");
25282
+ p16.log.error("Database was not reachable. Aborting setup.");
25117
25283
  process.exit(1);
25118
25284
  }
25119
25285
  } else if (adminCheck.existingAdmin) {
25120
25286
  const existingAdminLabel = formatAdminIdentity(adminCheck.existingAdmin);
25121
- const adminAction = await p14.select({
25287
+ const adminAction = await p16.select({
25122
25288
  message: "Found an already existing admin account. Do you want to replace it or skip?",
25123
25289
  options: [
25124
25290
  {
@@ -25132,34 +25298,34 @@ async function runInitCommand(name, options) {
25132
25298
  }
25133
25299
  ]
25134
25300
  });
25135
- if (p14.isCancel(adminAction)) {
25136
- p14.cancel("Setup cancelled.");
25301
+ if (p16.isCancel(adminAction)) {
25302
+ p16.cancel("Setup cancelled.");
25137
25303
  process.exit(0);
25138
25304
  }
25139
25305
  if (adminAction === "skip") {
25140
25306
  adminAccountReady = true;
25141
- p14.log.info(`Keeping existing admin account ${pc5.dim(`(${existingAdminLabel})`)}`);
25307
+ p16.log.info(`Keeping existing admin account ${pc5.dim(`(${existingAdminLabel})`)}`);
25142
25308
  } else {
25143
25309
  replaceExistingAdmin = true;
25144
25310
  }
25145
25311
  }
25146
25312
  if (!adminAccountReady) {
25147
- p14.note(
25313
+ p16.note(
25148
25314
  pc5.dim(
25149
25315
  replaceExistingAdmin ? "Replace the existing admin account to access the admin." : "Create your first admin user to access the admin."
25150
25316
  ),
25151
25317
  "Admin account"
25152
25318
  );
25153
- const credentials = await p14.group(
25319
+ const credentials = await p16.group(
25154
25320
  {
25155
- email: () => p14.text({
25321
+ email: () => p16.text({
25156
25322
  message: "Admin email",
25157
25323
  placeholder: "admin@example.com",
25158
25324
  validate: (v) => {
25159
25325
  if (!v || !v.includes("@")) return "Please enter a valid email";
25160
25326
  }
25161
25327
  }),
25162
- password: () => p14.password({
25328
+ password: () => p16.password({
25163
25329
  message: "Admin password",
25164
25330
  validate: (v) => {
25165
25331
  if (!v || v.length < 8) return "Password must be at least 8 characters";
@@ -25168,7 +25334,7 @@ async function runInitCommand(name, options) {
25168
25334
  },
25169
25335
  {
25170
25336
  onCancel: () => {
25171
- p14.cancel("Setup cancelled.");
25337
+ p16.cancel("Setup cancelled.");
25172
25338
  process.exit(0);
25173
25339
  }
25174
25340
  }
@@ -25187,11 +25353,11 @@ async function runInitCommand(name, options) {
25187
25353
  );
25188
25354
  if (seedResult.existingUser) {
25189
25355
  s.stop(`${pc5.yellow("\u25B2")} An account already exists for ${credentials.email}`);
25190
- const replace = await p14.confirm({
25356
+ const replace = await p16.confirm({
25191
25357
  message: "Replace the existing account with this email?",
25192
25358
  initialValue: false
25193
25359
  });
25194
- if (!p14.isCancel(replace) && replace) {
25360
+ if (!p16.isCancel(replace) && replace) {
25195
25361
  seedOverwriteMode = "email";
25196
25362
  s.start("Replacing admin user");
25197
25363
  seedResult = await runSeed(
@@ -25212,14 +25378,14 @@ async function runInitCommand(name, options) {
25212
25378
  adminAccountReady = true;
25213
25379
  } else if (seedResult.error) {
25214
25380
  s.stop(`${pc5.red("\u2717")} Failed to create admin user`);
25215
- p14.note(
25381
+ p16.note(
25216
25382
  `${pc5.red(seedResult.error)}
25217
25383
 
25218
25384
  Run manually: ${pc5.cyan(betterstartExecCommand(pm, "seed"))}`,
25219
25385
  pc5.red("Seed failed")
25220
25386
  );
25221
25387
  if (isDatabaseReachabilityError(seedResult.error)) {
25222
- p14.log.error("Database was not reachable. Aborting setup.");
25388
+ p16.log.error("Database was not reachable. Aborting setup.");
25223
25389
  process.exit(1);
25224
25390
  }
25225
25391
  }
@@ -25273,15 +25439,15 @@ Run manually: ${pc5.cyan(betterstartExecCommand(pm, "seed"))}`,
25273
25439
  ` ${step++}. Run ${pc5.cyan(betterstartExecCommand(pm, "add --integration <name>"))} to install integrations later`
25274
25440
  );
25275
25441
  summaryLines.push("", "Next steps:", ...nextSteps);
25276
- p14.note(summaryLines.join("\n"), "Admin scaffolded successfully");
25442
+ p16.note(summaryLines.join("\n"), "Admin scaffolded successfully");
25277
25443
  if (!options.yes && !options.skipDevServerStart) {
25278
25444
  const devCmd = runCommand(pm, "dev");
25279
- const startDev = await p14.confirm({
25445
+ const startDev = await p16.confirm({
25280
25446
  message: "Start the development server?",
25281
25447
  initialValue: true
25282
25448
  });
25283
- if (!p14.isCancel(startDev) && startDev) {
25284
- p14.outro(`Starting ${pc5.cyan(devCmd)}...`);
25449
+ if (!p16.isCancel(startDev) && startDev) {
25450
+ p16.outro(`Starting ${pc5.cyan(devCmd)}...`);
25285
25451
  await startManagedDevServer(cwd, devCmd, adminLoginUrl, {
25286
25452
  email: seedSuccess && seedEmail ? seedEmail : void 0,
25287
25453
  password: seedSuccess && seedPassword ? seedPassword : void 0
@@ -25289,7 +25455,7 @@ Run manually: ${pc5.cyan(betterstartExecCommand(pm, "seed"))}`,
25289
25455
  return;
25290
25456
  }
25291
25457
  }
25292
- p14.outro("Done!");
25458
+ p16.outro("Done!");
25293
25459
  }
25294
25460
  function isValidDbUrl(url) {
25295
25461
  return url.startsWith("postgres://") || url.startsWith("postgresql://");
@@ -25676,7 +25842,7 @@ function printAdminReadyNote(state) {
25676
25842
  lines.unshift(`Password: ${pc5.cyan(state.adminPassword)}`);
25677
25843
  lines.unshift(`Admin user: ${pc5.cyan(state.adminEmail)}`);
25678
25844
  }
25679
- p14.note(lines.join("\n"), "Admin ready");
25845
+ p16.note(lines.join("\n"), "Admin ready");
25680
25846
  }
25681
25847
  function shouldSuppressDevServerStartupLine(line) {
25682
25848
  const plain = stripAnsi(line).trim();
@@ -25788,7 +25954,7 @@ function startManagedDevServer(cwd, devCmd, adminLoginUrl, adminCredentials) {
25788
25954
 
25789
25955
  // adapters/next/commands/list-integrations.ts
25790
25956
  import path49 from "path";
25791
- import * as p15 from "@clack/prompts";
25957
+ import * as p17 from "@clack/prompts";
25792
25958
  async function runListIntegrationsCommand(options) {
25793
25959
  const cwd = options.cwd ? path49.resolve(options.cwd) : process.cwd();
25794
25960
  const config = await resolveConfig(cwd);
@@ -25797,15 +25963,15 @@ async function runListIntegrationsCommand(options) {
25797
25963
  const status = installedIntegrations.has(integration.id) ? "installed" : "available";
25798
25964
  return `${integration.id.padEnd(10)} ${status.padEnd(10)} ${integration.kind.padEnd(8)} ${integration.description}`;
25799
25965
  });
25800
- p15.note(lines.join("\n"), "BetterStart integrations");
25801
- p15.outro(
25966
+ p17.note(lines.join("\n"), "BetterStart integrations");
25967
+ p17.outro(
25802
25968
  `${installedIntegrations.size} installed, ${lines.length - installedIntegrations.size} available`
25803
25969
  );
25804
25970
  }
25805
25971
 
25806
25972
  // adapters/next/commands/list-plugins.ts
25807
25973
  import path50 from "path";
25808
- import * as p16 from "@clack/prompts";
25974
+ import * as p18 from "@clack/prompts";
25809
25975
  async function runListPluginsCommand(options) {
25810
25976
  const cwd = options.cwd ? path50.resolve(options.cwd) : process.cwd();
25811
25977
  const config = await resolveConfig(cwd);
@@ -25814,34 +25980,34 @@ async function runListPluginsCommand(options) {
25814
25980
  const status = installedPlugins.has(plugin.id) ? "installed" : "available";
25815
25981
  return `${plugin.id.padEnd(10)} ${status.padEnd(10)} ${plugin.kind.padEnd(12)} ${plugin.description}`;
25816
25982
  });
25817
- p16.note(lines.join("\n"), "BetterStart plugins");
25818
- p16.outro(`${installedPlugins.size} installed, ${lines.length - installedPlugins.size} available`);
25983
+ p18.note(lines.join("\n"), "BetterStart plugins");
25984
+ p18.outro(`${installedPlugins.size} installed, ${lines.length - installedPlugins.size} available`);
25819
25985
  }
25820
25986
 
25821
25987
  // adapters/next/commands/remove.ts
25822
25988
  import path51 from "path";
25823
- import * as p17 from "@clack/prompts";
25989
+ import * as p19 from "@clack/prompts";
25824
25990
  async function runRemoveCommand(items, options) {
25825
25991
  const removeIntegrationsMode = Boolean(options.integration);
25826
25992
  if (!removeIntegrationsMode && items.includes("core")) {
25827
- p17.log.error("The core Admin cannot be removed.");
25993
+ p19.log.error("The core Admin cannot be removed.");
25828
25994
  process.exit(1);
25829
25995
  }
25830
25996
  const pluginIds = items.filter(isPluginId);
25831
25997
  const integrationIds = items.filter(isIntegrationId);
25832
25998
  if (!removeIntegrationsMode && integrationIds.length > 0) {
25833
- p17.log.error(
25999
+ p19.log.error(
25834
26000
  `Integration IDs require --integration. Run \`betterstart remove --integration ${integrationIds.join(" ")}\`.`
25835
26001
  );
25836
26002
  process.exit(1);
25837
26003
  }
25838
26004
  if (removeIntegrationsMode && pluginIds.length > 0) {
25839
- p17.log.error(`Plugin IDs cannot be removed with --integration: ${pluginIds.join(", ")}`);
26005
+ p19.log.error(`Plugin IDs cannot be removed with --integration: ${pluginIds.join(", ")}`);
25840
26006
  process.exit(1);
25841
26007
  }
25842
26008
  const invalidItems = removeIntegrationsMode ? items.filter((integrationId) => !isIntegrationId(integrationId)) : items.filter((pluginId) => !isPluginId(pluginId));
25843
26009
  if (invalidItems.length > 0) {
25844
- p17.log.error(
26010
+ p19.log.error(
25845
26011
  removeIntegrationsMode ? formatUnknownIntegrationMessage(invalidItems) : formatUnknownPluginMessage(invalidItems)
25846
26012
  );
25847
26013
  process.exit(1);
@@ -25850,12 +26016,12 @@ async function runRemoveCommand(items, options) {
25850
26016
  const config = await resolveConfig(cwd);
25851
26017
  const pm = detectPackageManager(cwd);
25852
26018
  if (!options.force) {
25853
- const confirmed = await p17.confirm({
26019
+ const confirmed = await p19.confirm({
25854
26020
  message: `Remove ${removeIntegrationsMode ? "integration" : "plugin"}${items.length === 1 ? "" : "s"} ${items.join(", ")}?`,
25855
26021
  initialValue: false
25856
26022
  });
25857
- if (p17.isCancel(confirmed) || !confirmed) {
25858
- p17.cancel(`${removeIntegrationsMode ? "Integration" : "Plugin"} removal cancelled.`);
26023
+ if (p19.isCancel(confirmed) || !confirmed) {
26024
+ p19.cancel(`${removeIntegrationsMode ? "Integration" : "Plugin"} removal cancelled.`);
25859
26025
  process.exit(0);
25860
26026
  }
25861
26027
  }
@@ -25868,13 +26034,13 @@ async function runRemoveCommand(items, options) {
25868
26034
  });
25869
26035
  writeConfigFile(cwd, result2.config);
25870
26036
  if (result2.removed.length === 0) {
25871
- p17.outro("No integrations were removed.");
26037
+ p19.outro("No integrations were removed.");
25872
26038
  return;
25873
26039
  }
25874
26040
  if (result2.warnings.length > 0) {
25875
- p17.note(result2.warnings.join("\n"), "Warnings");
26041
+ p19.note(result2.warnings.join("\n"), "Warnings");
25876
26042
  }
25877
- p17.outro(
26043
+ p19.outro(
25878
26044
  `Removed integration${result2.removed.length === 1 ? "" : "s"}: ${result2.removed.join(", ")}`
25879
26045
  );
25880
26046
  return;
@@ -25887,13 +26053,13 @@ async function runRemoveCommand(items, options) {
25887
26053
  });
25888
26054
  writeConfigFile(cwd, result.config);
25889
26055
  if (result.removed.length === 0) {
25890
- p17.outro("No plugins were removed.");
26056
+ p19.outro("No plugins were removed.");
25891
26057
  return;
25892
26058
  }
25893
26059
  if (result.warnings.length > 0) {
25894
- p17.note(result.warnings.join("\n"), "Warnings");
26060
+ p19.note(result.warnings.join("\n"), "Warnings");
25895
26061
  }
25896
- p17.outro(`Removed plugin${result.removed.length === 1 ? "" : "s"}: ${result.removed.join(", ")}`);
26062
+ p19.outro(`Removed plugin${result.removed.length === 1 ? "" : "s"}: ${result.removed.join(", ")}`);
25897
26063
  }
25898
26064
 
25899
26065
  // adapters/next/commands/remove-schema.ts
@@ -26042,7 +26208,7 @@ async function runRemoveSchemaCommand(schemaName, options) {
26042
26208
  // adapters/next/commands/uninstall.ts
26043
26209
  import fs40 from "fs";
26044
26210
  import path53 from "path";
26045
- import * as p18 from "@clack/prompts";
26211
+ import * as p20 from "@clack/prompts";
26046
26212
  import pc6 from "picocolors";
26047
26213
 
26048
26214
  // adapters/next/commands/uninstall-cleaners.ts
@@ -26264,8 +26430,8 @@ function buildUninstallPlan(cwd, namespaceValue) {
26264
26430
  count: configFiles.length,
26265
26431
  unit: configFiles.length === 1 ? "file" : "files",
26266
26432
  execute() {
26267
- for (const p19 of configPaths) {
26268
- if (fs40.existsSync(p19)) fs40.unlinkSync(p19);
26433
+ for (const p21 of configPaths) {
26434
+ if (fs40.existsSync(p21)) fs40.unlinkSync(p21);
26269
26435
  }
26270
26436
  }
26271
26437
  });
@@ -26329,7 +26495,7 @@ function buildUninstallPlan(cwd, namespaceValue) {
26329
26495
  }
26330
26496
  async function runUninstallCommand(options) {
26331
26497
  const cwd = options.cwd ? path53.resolve(options.cwd) : process.cwd();
26332
- p18.intro(pc6.bgRed(pc6.white(" BetterStart Uninstall ")));
26498
+ p20.intro(pc6.bgRed(pc6.white(" BetterStart Uninstall ")));
26333
26499
  let namespace = DEFAULT_ADMIN_NAMESPACE;
26334
26500
  try {
26335
26501
  const config = await resolveConfig(cwd);
@@ -26338,8 +26504,8 @@ async function runUninstallCommand(options) {
26338
26504
  }
26339
26505
  const steps = buildUninstallPlan(cwd, namespace);
26340
26506
  if (steps.length === 0) {
26341
- p18.log.success(`${pc6.green("\u2713")} Nothing to remove \u2014 project is already clean.`);
26342
- p18.outro("Done");
26507
+ p20.log.success(`${pc6.green("\u2713")} Nothing to remove \u2014 project is already clean.`);
26508
+ p20.outro("Done");
26343
26509
  return;
26344
26510
  }
26345
26511
  const planLines = steps.map((step) => {
@@ -26347,18 +26513,18 @@ async function runUninstallCommand(options) {
26347
26513
  const countLabel = pc6.dim(`${step.count} ${step.unit}`);
26348
26514
  return `${pc6.red("\xD7")} ${names} ${countLabel}`;
26349
26515
  });
26350
- p18.note(planLines.join("\n"), "Uninstall plan");
26516
+ p20.note(planLines.join("\n"), "Uninstall plan");
26351
26517
  if (!options.force) {
26352
- const confirmed = await p18.confirm({
26518
+ const confirmed = await p20.confirm({
26353
26519
  message: "Proceed with uninstall?",
26354
26520
  initialValue: false
26355
26521
  });
26356
- if (p18.isCancel(confirmed) || !confirmed) {
26357
- p18.cancel("Uninstall cancelled.");
26522
+ if (p20.isCancel(confirmed) || !confirmed) {
26523
+ p20.cancel("Uninstall cancelled.");
26358
26524
  process.exit(0);
26359
26525
  }
26360
26526
  }
26361
- const s = p18.spinner();
26527
+ const s = p20.spinner();
26362
26528
  s.start(steps[0].label);
26363
26529
  for (const step of steps) {
26364
26530
  s.message(step.label);
@@ -26366,8 +26532,8 @@ async function runUninstallCommand(options) {
26366
26532
  }
26367
26533
  const parts = steps.map((step) => `${step.count} ${step.unit}`);
26368
26534
  s.stop(`Removed ${parts.join(", ")}`);
26369
- p18.note(pc6.dim("Database tables were NOT dropped \u2014 drop them manually if needed."), "Next steps");
26370
- p18.outro("Uninstall complete");
26535
+ p20.note(pc6.dim("Database tables were NOT dropped \u2014 drop them manually if needed."), "Next steps");
26536
+ p20.outro("Uninstall complete");
26371
26537
  }
26372
26538
 
26373
26539
  // adapters/next/commands/update-component.ts