betterstart-cli 0.0.26 → 0.0.28

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
@@ -112,7 +112,7 @@ function createInitCommand(runtime) {
112
112
  return new Command("init").description("Scaffold Admin into a new or existing Next.js app").argument("[name]", "Project name (creates new directory if fresh project)").option("--plugins <plugins>", "Comma-separated content plugin IDs (e.g. blog)").option("--integrations <integrations>", "Comma-separated integration IDs (e.g. r2,resend)").option("--namespace <name>", "Generated admin namespace (e.g. admin, dashboard)").option("-y, --yes", "Skip all prompts (accept defaults)").option("--skip-migration", "Skip running drizzle-kit push after scaffolding").option("--skip-admin-creation", "Skip prompting to create the initial admin user").option("--skip-dev-server-start", "Skip prompting to start the development server").option(
113
113
  "--database-url <url>",
114
114
  "PostgreSQL database connection string (postgres:// or postgresql://)"
115
- ).option("--no-vercel", "Disable the Vercel (Neon) provisioning flow (manual connection only)").option("--vercel-plan <id>", "Neon plan id passed to `vercel integration add neon`").option("--force", "Overwrite all existing Admin files (nuclear option)").action(
115
+ ).option("--no-vercel", "Disable the Vercel (Neon) provisioning flow (manual connection only)").option("--vercel-plan <id>", "Neon plan id passed to `vercel integration add neon`").option("--skip-deploy", "Skip the post-scaffold deploy to the linked Vercel project").option("--force", "Overwrite all existing Admin files (nuclear option)").action(
116
116
  (name, options) => runtime.runInit(name, options)
117
117
  );
118
118
  }
@@ -836,11 +836,48 @@ var resendIntegration = {
836
836
  configure: "resend"
837
837
  };
838
838
 
839
+ // adapters/next/integrations/vercel-blob/integration.ts
840
+ var vercelBlobIntegration = {
841
+ id: "vercel-blob",
842
+ title: "Vercel Blob",
843
+ description: "Remote media storage on Vercel Blob",
844
+ kind: "storage",
845
+ version: "1.0.0",
846
+ dependencies: [],
847
+ conflicts: [],
848
+ files: [
849
+ {
850
+ outputPath: "lib/actions/vercel-blob/provider.ts",
851
+ templatePath: "actions/vercel-blob.ts"
852
+ }
853
+ ],
854
+ packageDependencies: ["@vercel/blob"],
855
+ devDependencies: [],
856
+ capabilities: {
857
+ storageProvider: {
858
+ provider: "vercel-blob",
859
+ importPath: "@admin/actions/vercel-blob/provider",
860
+ exportName: "vercelBlobStorageProvider"
861
+ }
862
+ },
863
+ envSections: [
864
+ {
865
+ // BLOB_READ_WRITE_TOKEN is deliberately unprefixed: it is the name the
866
+ // @vercel/blob SDK reads by default and the one Vercel injects into
867
+ // deployments when a Blob store is connected to the project.
868
+ header: "Storage (Vercel Blob)",
869
+ vars: [{ key: "BLOB_READ_WRITE_TOKEN", value: "" }]
870
+ }
871
+ ],
872
+ configure: "vercel-blob"
873
+ };
874
+
839
875
  // adapters/next/integration-registry.ts
840
876
  var integrationRegistry = {
841
877
  mailchimp: mailchimpIntegration,
842
878
  resend: resendIntegration,
843
- r2: r2Integration
879
+ r2: r2Integration,
880
+ "vercel-blob": vercelBlobIntegration
844
881
  };
845
882
  function listIntegrationDefinitions() {
846
883
  return Object.values(integrationRegistry);
@@ -2551,15 +2588,15 @@ function collectSchemaSlotLayoutErrors(schema, errors) {
2551
2588
  }
2552
2589
  collectSlotLayoutErrors(input.slot, "slot", errors);
2553
2590
  }
2554
- function collectSlotLayoutErrors(value, path57, errors) {
2591
+ function collectSlotLayoutErrors(value, path59, errors) {
2555
2592
  if (!isRecord(value)) {
2556
- errors.push(`${path57} must be an object with "main" and/or "sidebar" field groups.`);
2593
+ errors.push(`${path59} must be an object with "main" and/or "sidebar" field groups.`);
2557
2594
  return;
2558
2595
  }
2559
2596
  const validKeys = /* @__PURE__ */ new Set(["main", "sidebar"]);
2560
2597
  for (const key of Object.keys(value)) {
2561
2598
  if (!validKeys.has(key)) {
2562
- errors.push(`${path57} has unsupported key "${key}". Expected "main" or "sidebar".`);
2599
+ errors.push(`${path59} has unsupported key "${key}". Expected "main" or "sidebar".`);
2563
2600
  }
2564
2601
  }
2565
2602
  const areas = [
@@ -2570,24 +2607,24 @@ function collectSlotLayoutErrors(value, path57, errors) {
2570
2607
  for (const [name, area] of areas) {
2571
2608
  if (area === void 0) continue;
2572
2609
  hasArea = true;
2573
- collectSlotAreaErrors(area, `${path57}.${name}`, errors);
2610
+ collectSlotAreaErrors(area, `${path59}.${name}`, errors);
2574
2611
  }
2575
2612
  if (!hasArea) {
2576
- errors.push(`${path57} must define at least one of "main" or "sidebar".`);
2613
+ errors.push(`${path59} must define at least one of "main" or "sidebar".`);
2577
2614
  }
2578
2615
  }
2579
- function collectSlotAreaErrors(value, path57, errors) {
2616
+ function collectSlotAreaErrors(value, path59, errors) {
2580
2617
  if (!isRecord(value)) {
2581
- errors.push(`${path57} must be an object with a "fields" array.`);
2618
+ errors.push(`${path59} must be an object with a "fields" array.`);
2582
2619
  return;
2583
2620
  }
2584
2621
  const fields = value.fields;
2585
2622
  if (!Array.isArray(fields)) {
2586
- errors.push(`${path57}.fields must be an array.`);
2623
+ errors.push(`${path59}.fields must be an array.`);
2587
2624
  return;
2588
2625
  }
2589
2626
  for (const field of fields) {
2590
- walkSlot(field, `${path57}.fields.${field.name ?? "unnamed"}`, errors);
2627
+ walkSlot(field, `${path59}.fields.${field.name ?? "unnamed"}`, errors);
2591
2628
  }
2592
2629
  }
2593
2630
  function collectInvalidHeightErrors(topLevelFields, rootPath, errors) {
@@ -3197,12 +3234,45 @@ async function promptR2Config(cwd) {
3197
3234
  updatedEnvKeys: envResult.updated
3198
3235
  };
3199
3236
  }
3237
+ async function collectVercelBlobConfig(cwd) {
3238
+ const overwriteEnvKeys = /* @__PURE__ */ new Set();
3239
+ const token = await resolvePasswordEnvValue({
3240
+ cwd,
3241
+ key: "BLOB_READ_WRITE_TOKEN",
3242
+ message: "Vercel Blob read-write token (from the store on vercel.com/dashboard/stores)",
3243
+ cancelMessage: "Vercel Blob configuration cancelled.",
3244
+ overwriteEnvKeys,
3245
+ validate(value) {
3246
+ if (!value?.trim()) {
3247
+ return "Enter a Vercel Blob read-write token.";
3248
+ }
3249
+ }
3250
+ });
3251
+ return {
3252
+ sections: [
3253
+ {
3254
+ header: "Storage (Vercel Blob)",
3255
+ vars: [{ key: "BLOB_READ_WRITE_TOKEN", value: token.trim() }]
3256
+ }
3257
+ ],
3258
+ overwriteKeys: overwriteEnvKeys
3259
+ };
3260
+ }
3261
+ async function promptVercelBlobConfig(cwd) {
3262
+ const collected = await collectVercelBlobConfig(cwd);
3263
+ const envResult = appendEnvVars(cwd, collected.sections, collected.overwriteKeys);
3264
+ return {
3265
+ configured: true,
3266
+ addedEnvKeys: envResult.added,
3267
+ updatedEnvKeys: envResult.updated
3268
+ };
3269
+ }
3200
3270
  async function collectIntegrationConfig(cwd, integrationIds) {
3201
3271
  const sections = [];
3202
3272
  const overwriteKeys = /* @__PURE__ */ new Set();
3203
3273
  for (const integrationId of resolveIntegrationInstallOrder(integrationIds)) {
3204
3274
  const definition = getIntegrationDefinition(integrationId);
3205
- const collected = definition.configure === "resend" ? await collectResendConfig(cwd) : definition.configure === "r2" ? await collectR2Config(cwd) : void 0;
3275
+ const collected = definition.configure === "resend" ? await collectResendConfig(cwd) : definition.configure === "r2" ? await collectR2Config(cwd) : definition.configure === "vercel-blob" ? await collectVercelBlobConfig(cwd) : void 0;
3206
3276
  if (!collected) {
3207
3277
  continue;
3208
3278
  }
@@ -3236,6 +3306,17 @@ async function configureIntegration(definition, cwd, interactive) {
3236
3306
  updatedEnvKeys: envResult2.updated
3237
3307
  };
3238
3308
  }
3309
+ if (definition.configure === "vercel-blob") {
3310
+ if (interactive) {
3311
+ return promptVercelBlobConfig(cwd);
3312
+ }
3313
+ const envResult2 = appendEnvVars(cwd, definition.envSections);
3314
+ return {
3315
+ configured: hasRequiredIntegrationEnv(definition, cwd),
3316
+ addedEnvKeys: envResult2.added,
3317
+ updatedEnvKeys: envResult2.updated
3318
+ };
3319
+ }
3239
3320
  const envResult = appendEnvVars(cwd, definition.envSections);
3240
3321
  return {
3241
3322
  configured: true,
@@ -4234,9 +4315,9 @@ function getReadFieldType(field) {
4234
4315
  }
4235
4316
  return getFieldType(field, "output");
4236
4317
  }
4237
- function collectReadSelectFields(fields, path57 = []) {
4318
+ function collectReadSelectFields(fields, path59 = []) {
4238
4319
  const matches = fields.flatMap((field) => {
4239
- const currentPath = [...path57, field.name];
4320
+ const currentPath = [...path59, field.name];
4240
4321
  const matches2 = field.type === "select" ? [{ field, path: currentPath }] : [];
4241
4322
  if (field.fields) {
4242
4323
  matches2.push(...collectReadSelectFields(field.fields, currentPath));
@@ -5283,14 +5364,14 @@ function buildStepsConstant(steps) {
5283
5364
  ${entries.join(",\n")}
5284
5365
  ]`;
5285
5366
  }
5286
- function buildComponentSource(p20) {
5367
+ function buildComponentSource(p22) {
5287
5368
  const providerOpen = ` <NuqsAdapter>
5288
5369
  <React.Suspense fallback={null}>
5289
- <${p20.pascal}FormInner />
5370
+ <${p22.pascal}FormInner />
5290
5371
  </React.Suspense>
5291
5372
  </NuqsAdapter>`;
5292
5373
  const exportWrapper = `
5293
- export function ${p20.pascal}Form() {
5374
+ export function ${p22.pascal}Form() {
5294
5375
  return (
5295
5376
  ${providerOpen}
5296
5377
  )
@@ -5299,22 +5380,22 @@ ${providerOpen}
5299
5380
  return `'use client'
5300
5381
 
5301
5382
  import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
5302
- import { ChevronLeft, ChevronRight${p20.hasListFields ? ", Trash2" : ""} } from 'lucide-react'
5383
+ import { ChevronLeft, ChevronRight${p22.hasListFields ? ", Trash2" : ""} } from 'lucide-react'
5303
5384
  import { createParser, useQueryState } from 'nuqs'
5304
5385
  import { NuqsAdapter } from 'nuqs/adapters/next/app'
5305
5386
  import * as React from 'react'
5306
- ${p20.rhfImport}
5387
+ ${p22.rhfImport}
5307
5388
  import { z } from 'zod/v3'
5308
- import { create${p20.pascal}Submission } from '@admin/actions/${p20.actionImportPath}'
5389
+ import { create${p22.pascal}Submission } from '@admin/actions/${p22.actionImportPath}'
5309
5390
 
5310
5391
  const formSchema = z.object({
5311
- ${p20.zodFields}
5392
+ ${p22.zodFields}
5312
5393
  })
5313
5394
 
5314
5395
  type FormValues = z.infer<typeof formSchema>
5315
5396
  ${buildFieldErrorHelper()}
5316
5397
 
5317
- ${p20.stepsConst}
5398
+ ${p22.stepsConst}
5318
5399
 
5319
5400
  const stepParser = createParser({
5320
5401
  parse(value) {
@@ -5332,7 +5413,7 @@ const stepParser = createParser({
5332
5413
  }
5333
5414
  }).withDefault(0)
5334
5415
 
5335
- function ${p20.pascal}FormInner() {
5416
+ function ${p22.pascal}FormInner() {
5336
5417
  const [currentStep, setCurrentStep] = useQueryState('step', stepParser)
5337
5418
  const [submitted, setSubmitted] = React.useState(false)
5338
5419
  const [submitting, startSubmitTransition] = React.useTransition()
@@ -5340,11 +5421,11 @@ function ${p20.pascal}FormInner() {
5340
5421
  const form = useForm<FormValues>({
5341
5422
  resolver: standardSchemaResolver(formSchema),
5342
5423
  defaultValues: {
5343
- ${p20.defaults}
5424
+ ${p22.defaults}
5344
5425
  },
5345
5426
  })
5346
5427
 
5347
- ${p20.fieldArraySetup}${p20.watchSetup}
5428
+ ${p22.fieldArraySetup}${p22.watchSetup}
5348
5429
  async function handleNext() {
5349
5430
  const stepFields = STEPS[currentStep].fields as (keyof FormValues)[]
5350
5431
  const isValid = await form.trigger(stepFields, { shouldFocus: true })
@@ -5360,9 +5441,9 @@ ${p20.fieldArraySetup}${p20.watchSetup}
5360
5441
  function onSubmit(values: FormValues) {
5361
5442
  startSubmitTransition(async () => {
5362
5443
  try {
5363
- const result = await create${p20.pascal}Submission(values)
5444
+ const result = await create${p22.pascal}Submission(values)
5364
5445
  if (result.success) {
5365
- ${p20.successHandler}
5446
+ ${p22.successHandler}
5366
5447
  } else {
5367
5448
  form.setError('root', { message: result.error || 'Something went wrong' })
5368
5449
  }
@@ -5376,7 +5457,7 @@ ${p20.fieldArraySetup}${p20.watchSetup}
5376
5457
  return (
5377
5458
  <div className="rounded-sm border p-6 text-center">
5378
5459
  <h3 className="text-lg font-semibold">Thank you!</h3>
5379
- <p className="mt-2 text-muted-foreground">${p20.successMessage}</p>
5460
+ <p className="mt-2 text-muted-foreground">${p22.successMessage}</p>
5380
5461
  </div>
5381
5462
  )
5382
5463
  }
@@ -5393,7 +5474,7 @@ ${p20.fieldArraySetup}${p20.watchSetup}
5393
5474
 
5394
5475
  {/* Step content */}
5395
5476
  <div key={currentStep} className="animate-in fade-in duration-300 space-y-6">
5396
- ${p20.stepContentBlocks}
5477
+ ${p22.stepContentBlocks}
5397
5478
  </div>
5398
5479
 
5399
5480
  {form.formState.errors.root && (
@@ -5427,7 +5508,7 @@ ${p20.stepContentBlocks}
5427
5508
  onClick={() => form.handleSubmit(onSubmit)()}
5428
5509
  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
5510
  >
5430
- {submitting ? 'Submitting...' : ${p20.submitText}}
5511
+ {submitting ? 'Submitting...' : ${p22.submitText}}
5431
5512
  </button>
5432
5513
  )}
5433
5514
  </div>
@@ -13531,14 +13612,14 @@ function safeIdentifier(value, fallback) {
13531
13612
  const base = pascal ? `${pascal.charAt(0).toLowerCase()}${pascal.slice(1)}` : fallback;
13532
13613
  return /^[A-Za-z_$]/.test(base) ? base : fallback;
13533
13614
  }
13534
- function pathExpression(path57) {
13535
- return `\`${path57.map((part) => typeof part === "string" ? part : `\${${part.expression}}`).join(".")}\``;
13615
+ function pathExpression(path59) {
13616
+ return `\`${path59.map((part) => typeof part === "string" ? part : `\${${part.expression}}`).join(".")}\``;
13536
13617
  }
13537
- function pathNameProp(path57) {
13538
- if (path57.every((part) => typeof part === "string")) {
13539
- return `"${path57.map((part) => part).join(".")}"`;
13618
+ function pathNameProp(path59) {
13619
+ if (path59.every((part) => typeof part === "string")) {
13620
+ return `"${path59.map((part) => part).join(".")}"`;
13540
13621
  }
13541
- return `{${pathExpression(path57)}}`;
13622
+ return `{${pathExpression(path59)}}`;
13542
13623
  }
13543
13624
  function findTitlePath(fields) {
13544
13625
  const stringTypes = ["string", "varchar", "text"];
@@ -13560,23 +13641,23 @@ function findTitlePath(fields) {
13560
13641
  function requiredLeafPaths(fields, prefix = []) {
13561
13642
  return fields.flatMap((field) => {
13562
13643
  if (field.hidden) return [];
13563
- const path57 = [...prefix, field.name];
13644
+ const path59 = [...prefix, field.name];
13564
13645
  if ((field.type === "group" || field.type === "section") && field.fields?.length) {
13565
- return requiredLeafPaths(field.fields, path57);
13646
+ return requiredLeafPaths(field.fields, path59);
13566
13647
  }
13567
- if (field.required) return [path57.join(".")];
13648
+ if (field.required) return [path59.join(".")];
13568
13649
  return [];
13569
13650
  });
13570
13651
  }
13571
13652
  function validationTriggerExpression(basePath, requiredPaths) {
13572
13653
  if (requiredPaths.length === 0) return `\`${basePath}.\${expandedIndex}\` as never`;
13573
- const paths = requiredPaths.map((path57) => `\`${basePath}.\${expandedIndex}.${path57}\``).join(", ");
13654
+ const paths = requiredPaths.map((path59) => `\`${basePath}.\${expandedIndex}.${path59}\``).join(", ");
13574
13655
  return `[${paths}] as never`;
13575
13656
  }
13576
- function renderTextInputField(field, indent, label, nestedHint, path57) {
13657
+ function renderTextInputField(field, indent, label, nestedHint, path59) {
13577
13658
  return `${indent}<FormField
13578
13659
  ${indent} control={form.control}
13579
- ${indent} name=${pathNameProp(path57)}
13660
+ ${indent} name=${pathNameProp(path59)}
13580
13661
  ${indent} render={({ field: formField }) => (
13581
13662
  ${indent} <FormItem${formItemProps(field)}>
13582
13663
  ${indent} ${labelWithDescription(formLabel(field, label), nestedHint, `${indent} `)}
@@ -13588,11 +13669,11 @@ ${indent} </FormItem>
13588
13669
  ${indent} )}
13589
13670
  ${indent}/>`;
13590
13671
  }
13591
- function renderNestedField(field, indent, path57, depth) {
13672
+ function renderNestedField(field, indent, path59, depth) {
13592
13673
  const nestedLabel = field.label || field.name;
13593
13674
  const nestedHint = field.hint ? `<FormDescription>${field.hint}</FormDescription>` : "";
13594
13675
  if ((field.type === "group" || field.type === "section") && field.fields?.length) {
13595
- const groupFields = field.fields.map((child) => renderNestedField(child, `${indent} `, [...path57, child.name], depth)).filter(Boolean).join("\n");
13676
+ const groupFields = field.fields.map((child) => renderNestedField(child, `${indent} `, [...path59, child.name], depth)).filter(Boolean).join("\n");
13596
13677
  if (!groupFields) return "";
13597
13678
  const heading = nestedLabel && nestedLabel !== field.name ? `${indent}<h3 className="text-base font-medium">${nestedLabel}</h3>
13598
13679
  ` : "";
@@ -13602,7 +13683,7 @@ ${groupFields}
13602
13683
  ${indent}</div>`;
13603
13684
  }
13604
13685
  if (field.type === "list" && field.fields?.length) {
13605
- return renderNestedObjectListField(field, indent, nestedLabel, path57, depth);
13686
+ return renderNestedObjectListField(field, indent, nestedLabel, path59, depth);
13606
13687
  }
13607
13688
  if (field.type === "list") {
13608
13689
  const hideLabelProp = field.label ? "" : `
@@ -13610,7 +13691,7 @@ ${indent} hideLabel`;
13610
13691
  const descriptionProp = field.hint ? `
13611
13692
  ${indent} description={${JSON.stringify(field.hint)}}` : "";
13612
13693
  return `${indent}<DynamicListField
13613
- ${indent} name={${pathExpression(path57)}}
13694
+ ${indent} name={${pathExpression(path59)}}
13614
13695
  ${indent} label="${nestedLabel}"${hideLabelProp}${descriptionProp}
13615
13696
  ${indent} disabled={isPending}${field.maxItems ? `
13616
13697
  ${indent} maxItems={${field.maxItems}}` : ""}
@@ -13620,7 +13701,7 @@ ${indent}/>`;
13620
13701
  if (field.type === "boolean") {
13621
13702
  return `${indent}<FormField
13622
13703
  ${indent} control={form.control}
13623
- ${indent} name=${pathNameProp(path57)}
13704
+ ${indent} name=${pathNameProp(path59)}
13624
13705
  ${indent} render={({ field: formField }) => (
13625
13706
  ${indent} <FormItem${formItemProps(field, "flex flex-row items-start space-x-3 space-y-0")}>
13626
13707
  ${indent} <FormControl>
@@ -13635,7 +13716,7 @@ ${indent}/>`;
13635
13716
  const acceptProp = field.type === "image" ? ' accept="image/*"' : field.type === "video" ? ' accept="video/*"' : "";
13636
13717
  return `${indent}<FormField
13637
13718
  ${indent} control={form.control}
13638
- ${indent} name=${pathNameProp(path57)}
13719
+ ${indent} name=${pathNameProp(path59)}
13639
13720
  ${indent} render={({ field: formField }) => (
13640
13721
  ${indent} <FormItem${formItemProps(field)}>
13641
13722
  ${indent} ${labelWithDescription(formLabel(field, nestedLabel), nestedHint, `${indent} `)}
@@ -13650,7 +13731,7 @@ ${indent}/>`;
13650
13731
  if (field.type === "icon") {
13651
13732
  return `${indent}<FormField
13652
13733
  ${indent} control={form.control}
13653
- ${indent} name=${pathNameProp(path57)}
13734
+ ${indent} name=${pathNameProp(path59)}
13654
13735
  ${indent} render={({ field: formField }) => (
13655
13736
  ${indent} <FormItem${formItemProps(field)}>
13656
13737
  ${indent} ${labelWithDescription(formLabel(field, nestedLabel), nestedHint, `${indent} `)}
@@ -13667,7 +13748,7 @@ ${indent}/>`;
13667
13748
  const emptyValue = field.required ? "undefined" : "null";
13668
13749
  return `${indent}<FormField
13669
13750
  ${indent} control={form.control}
13670
- ${indent} name=${pathNameProp(path57)}
13751
+ ${indent} name=${pathNameProp(path59)}
13671
13752
  ${indent} render={({ field: formField }) => (
13672
13753
  ${indent} <FormItem${formItemProps(field)}>
13673
13754
  ${indent} ${labelWithDescription(formLabel(field, nestedLabel), nestedHint, `${indent} `)}
@@ -13692,16 +13773,16 @@ ${indent} </FormItem>
13692
13773
  ${indent} )}
13693
13774
  ${indent}/>`;
13694
13775
  }
13695
- return renderTextInputField(field, indent, nestedLabel, nestedHint, path57);
13776
+ return renderTextInputField(field, indent, nestedLabel, nestedHint, path59);
13696
13777
  }
13697
- function renderNestedObjectListField(field, indent, label, path57, depth) {
13778
+ function renderNestedObjectListField(field, indent, label, path59, depth) {
13698
13779
  const singularLabel = singularize(label);
13699
13780
  const itemIndexVar = `${safeIdentifier(field.name, "nestedList")}Index${depth}`;
13700
13781
  const titlePath = findTitlePath(field.fields ?? []);
13701
13782
  const titlePathSuffix = titlePath ? `.${titlePath.join(".")}` : "";
13702
13783
  const renderTitleProp = titlePath ? `
13703
13784
  ${indent} renderTitle={(${itemIndexVar}) =>
13704
- ${indent} String(form.watch(\`${path57.map((part) => typeof part === "string" ? part : `\${${part.expression}}`).join(
13785
+ ${indent} String(form.watch(\`${path59.map((part) => typeof part === "string" ? part : `\${${part.expression}}`).join(
13705
13786
  "."
13706
13787
  )}.\${${itemIndexVar}}${titlePathSuffix}\` as never) || \`${singularLabel} \${${itemIndexVar} + 1}\`)
13707
13788
  ${indent} }` : "";
@@ -13709,7 +13790,7 @@ ${indent} }` : "";
13709
13790
  (child) => renderNestedField(
13710
13791
  child,
13711
13792
  `${indent} `,
13712
- [...path57, { expression: itemIndexVar }, child.name],
13793
+ [...path59, { expression: itemIndexVar }, child.name],
13713
13794
  depth + 1
13714
13795
  )
13715
13796
  ).filter(Boolean).join("\n");
@@ -13724,7 +13805,7 @@ ${indent} maxItems={${field.maxItems}}` : "";
13724
13805
  const validatePathsProp = validatePaths.length > 0 ? `
13725
13806
  ${indent} validatePaths={${JSON.stringify(validatePaths)}}` : "";
13726
13807
  return `${indent}<NestedObjectListField
13727
- ${indent} name={${pathExpression(path57)}}
13808
+ ${indent} name={${pathExpression(path59)}}
13728
13809
  ${indent} label={${JSON.stringify(label)}}
13729
13810
  ${indent} singularLabel={${JSON.stringify(singularLabel)}}
13730
13811
  ${indent} defaultValue={${defaultItem}}
@@ -17368,7 +17449,7 @@ function applyTextEdits(content, edits) {
17368
17449
  content
17369
17450
  );
17370
17451
  }
17371
- function collectPreviewChanges(path57, beforeContent, afterContent) {
17452
+ function collectPreviewChanges(path59, beforeContent, afterContent) {
17372
17453
  const beforeLines = beforeContent.split("\n");
17373
17454
  const afterLines = afterContent.split("\n");
17374
17455
  const maxLength = Math.max(beforeLines.length, afterLines.length);
@@ -17380,7 +17461,7 @@ function collectPreviewChanges(path57, beforeContent, afterContent) {
17380
17461
  continue;
17381
17462
  }
17382
17463
  changes.push({
17383
- path: path57,
17464
+ path: path59,
17384
17465
  line: index + 1,
17385
17466
  before: before.trim(),
17386
17467
  after: after.trim()
@@ -18991,7 +19072,7 @@ async function runAddCommand(items, options) {
18991
19072
 
18992
19073
  // adapters/next/commands/add-field.ts
18993
19074
  import path30 from "path";
18994
- import * as p7 from "@clack/prompts";
19075
+ import * as p8 from "@clack/prompts";
18995
19076
 
18996
19077
  // core-engine/schema/add-field.ts
18997
19078
  import fs23 from "fs";
@@ -19643,7 +19724,7 @@ function getTabFields(tab) {
19643
19724
  // adapters/next/commands/generate.ts
19644
19725
  import fs24 from "fs";
19645
19726
  import path29 from "path";
19646
- import * as p5 from "@clack/prompts";
19727
+ import * as p6 from "@clack/prompts";
19647
19728
 
19648
19729
  // core-engine/snapshots/rename-detector.ts
19649
19730
  function levenshtein(a, b) {
@@ -19735,6 +19816,43 @@ function diffSchemas(before, after) {
19735
19816
  };
19736
19817
  }
19737
19818
 
19819
+ // core-engine/utils/spinner.ts
19820
+ import { stripVTControlCharacters } from "util";
19821
+ import * as p5 from "@clack/prompts";
19822
+ var RENDER_OVERHEAD = 7;
19823
+ var MIN_MESSAGE_WIDTH = 8;
19824
+ function fitSpinnerMessage(message) {
19825
+ const columns = process.stdout.columns ?? 80;
19826
+ const max = Math.max(columns - RENDER_OVERHEAD, MIN_MESSAGE_WIDTH);
19827
+ const visible = stripVTControlCharacters(message);
19828
+ if (visible.length <= max) return message;
19829
+ return `${visible.slice(0, max - 1)}\u2026`;
19830
+ }
19831
+ function spinner2(options) {
19832
+ const inner = p5.spinner(options);
19833
+ const guided = options?.withGuide !== false;
19834
+ let started = false;
19835
+ return {
19836
+ start: (message = "") => {
19837
+ started = true;
19838
+ inner.start(fitSpinnerMessage(message));
19839
+ },
19840
+ message: (message = "") => inner.message(fitSpinnerMessage(message)),
19841
+ stop: (message = "") => {
19842
+ started = false;
19843
+ inner.stop(message);
19844
+ },
19845
+ clear: () => {
19846
+ if (!started) return;
19847
+ started = false;
19848
+ inner.clear();
19849
+ if (guided && process.stdout.isTTY && process.env.CI !== "true") {
19850
+ process.stdout.write("\x1B[1A\x1B[2K");
19851
+ }
19852
+ }
19853
+ };
19854
+ }
19855
+
19738
19856
  // adapters/next/commands/generate.ts
19739
19857
  function isInteractiveSession2() {
19740
19858
  return Boolean(process.stdin.isTTY) && process.env.CI !== "true";
@@ -19801,7 +19919,7 @@ async function ensureMarkdownRendererDependencies(cwd, required) {
19801
19919
  );
19802
19920
  if (dependencies.length === 0 && devDependencies.length === 0) return;
19803
19921
  const pm = detectPackageManager(cwd);
19804
- const s = p5.spinner();
19922
+ const s = spinner2();
19805
19923
  s.start("Installing markdown renderer dependencies");
19806
19924
  const result = await installDependenciesAsync({
19807
19925
  cwd,
@@ -19860,11 +19978,11 @@ function printSchemaDiff(scope, loaded, cwd) {
19860
19978
  }
19861
19979
  }
19862
19980
  async function promptConfirm(message) {
19863
- const result = await p5.confirm({
19981
+ const result = await p6.confirm({
19864
19982
  message,
19865
19983
  initialValue: true
19866
19984
  });
19867
- if (p5.isCancel(result)) {
19985
+ if (p6.isCancel(result)) {
19868
19986
  throw new Error("Generation cancelled.");
19869
19987
  }
19870
19988
  return Boolean(result);
@@ -20277,34 +20395,34 @@ async function runGenerateCommand(schemaName, options) {
20277
20395
  }
20278
20396
 
20279
20397
  // adapters/next/commands/schema-prompts.ts
20280
- import * as p6 from "@clack/prompts";
20398
+ import * as p7 from "@clack/prompts";
20281
20399
  function isInteractiveSession3() {
20282
20400
  return Boolean(process.stdin.isTTY) && process.env.CI !== "true";
20283
20401
  }
20284
20402
  function fail(message) {
20285
- p6.log.error(message);
20403
+ p7.log.error(message);
20286
20404
  process.exit(1);
20287
20405
  }
20288
20406
  function cancel2(context) {
20289
- p6.cancel(context.cancelMessage);
20407
+ p7.cancel(context.cancelMessage);
20290
20408
  process.exit(0);
20291
20409
  }
20292
20410
  function cancelIfNeeded(context, value) {
20293
- if (p6.isCancel(value)) {
20411
+ if (p7.isCancel(value)) {
20294
20412
  cancel2(context);
20295
20413
  }
20296
20414
  return value;
20297
20415
  }
20298
20416
  async function promptConfirm2(context, message, initialValue = true) {
20299
- const confirmed = await p6.confirm({ message, initialValue });
20300
- if (p6.isCancel(confirmed)) {
20417
+ const confirmed = await p7.confirm({ message, initialValue });
20418
+ if (p7.isCancel(confirmed)) {
20301
20419
  cancel2(context);
20302
20420
  }
20303
20421
  return Boolean(confirmed);
20304
20422
  }
20305
20423
  async function promptText(context, message, optionsOrDefaultValue) {
20306
20424
  const options = typeof optionsOrDefaultValue === "string" ? { defaultValue: optionsOrDefaultValue } : optionsOrDefaultValue ?? {};
20307
- const value = await p6.text({
20425
+ const value = await p7.text({
20308
20426
  message,
20309
20427
  defaultValue: options.defaultValue,
20310
20428
  placeholder: options.placeholder,
@@ -20321,7 +20439,7 @@ async function promptText(context, message, optionsOrDefaultValue) {
20321
20439
  return String(cancelIfNeeded(context, value)).trim();
20322
20440
  }
20323
20441
  async function promptOptionalText(context, message) {
20324
- const value = await p6.text({
20442
+ const value = await p7.text({
20325
20443
  message,
20326
20444
  placeholder: "Leave blank to skip"
20327
20445
  });
@@ -20329,7 +20447,7 @@ async function promptOptionalText(context, message) {
20329
20447
  return result || void 0;
20330
20448
  }
20331
20449
  async function promptSelectValue(context, message, options, initialValue) {
20332
- const value = await p6.select({
20450
+ const value = await p7.select({
20333
20451
  message,
20334
20452
  options,
20335
20453
  initialValue
@@ -20548,7 +20666,7 @@ async function shouldPromptRequired(type) {
20548
20666
  return !["group", "section", "tabs", "separator"].includes(type);
20549
20667
  }
20550
20668
  async function promptFormFileOptions(context) {
20551
- const selected = await p6.multiselect({
20669
+ const selected = await p7.multiselect({
20552
20670
  message: "Field options",
20553
20671
  options: [
20554
20672
  { value: "required", label: "Required field?" },
@@ -20587,7 +20705,7 @@ async function promptFieldOptions(context, kind, type, creatable) {
20587
20705
  try {
20588
20706
  return parseInteractiveOptionsList(value);
20589
20707
  } catch (error) {
20590
- p6.log.error(error instanceof Error ? error.message : String(error));
20708
+ p7.log.error(error instanceof Error ? error.message : String(error));
20591
20709
  }
20592
20710
  }
20593
20711
  }
@@ -20705,7 +20823,7 @@ async function promptChildFields(context, kind, schemasDir, scope, fields, depth
20705
20823
  const addChild = fields.length === 0 ? await promptConfirm2(context, "Add a child field?", true) : await promptConfirm2(context, "Add another child field?", false);
20706
20824
  if (!addChild) {
20707
20825
  if (fields.length === 0) {
20708
- p6.log.warn("Containers need at least one child field.");
20826
+ p7.log.warn("Containers need at least one child field.");
20709
20827
  continue;
20710
20828
  }
20711
20829
  return;
@@ -20730,7 +20848,7 @@ async function promptTabsField(context, kind, schemasDir, scope, name, label, de
20730
20848
  const addTab = field.tabs.length === 0 ? await promptConfirm2(context, "Add a tab?", true) : await promptConfirm2(context, "Add another tab?", false);
20731
20849
  if (!addTab) {
20732
20850
  if (field.tabs.length === 0) {
20733
- p6.log.warn("Tabs need at least one tab.");
20851
+ p7.log.warn("Tabs need at least one tab.");
20734
20852
  continue;
20735
20853
  }
20736
20854
  return field;
@@ -20755,7 +20873,7 @@ async function promptTabsField(context, kind, schemasDir, scope, name, label, de
20755
20873
  const addChild = mainFields.length + sidebarFields.length === 0 ? await promptConfirm2(context, "Add a tab child field?", true) : await promptConfirm2(context, "Add another tab child field?", false);
20756
20874
  if (!addChild) {
20757
20875
  if (mainFields.length + sidebarFields.length === 0) {
20758
- p6.log.warn("Tabs need at least one child field.");
20876
+ p7.log.warn("Tabs need at least one child field.");
20759
20877
  continue;
20760
20878
  }
20761
20879
  break;
@@ -20826,7 +20944,7 @@ async function applyAdvancedOptions(context, field, kind, type, options) {
20826
20944
  if (Number.isInteger(parsed) && parsed > 0) {
20827
20945
  record.length = parsed;
20828
20946
  } else {
20829
- p6.log.warn("Skipped invalid length.");
20947
+ p7.log.warn("Skipped invalid length.");
20830
20948
  }
20831
20949
  }
20832
20950
  }
@@ -20984,11 +21102,11 @@ function hasNonInteractiveFieldOptions(options) {
20984
21102
  );
20985
21103
  }
20986
21104
  function fail2(message) {
20987
- p7.log.error(message);
21105
+ p8.log.error(message);
20988
21106
  process.exit(1);
20989
21107
  }
20990
21108
  function cancel4(message = "Add field cancelled.") {
20991
- p7.cancel(message);
21109
+ p8.cancel(message);
20992
21110
  process.exit(0);
20993
21111
  }
20994
21112
  function schemaNameFromLoaded(loaded) {
@@ -21093,7 +21211,7 @@ function printPreview(loaded, owner, firstTimeGeneration, insertion, options) {
21093
21211
  } else {
21094
21212
  lines.push("schema integration: none");
21095
21213
  }
21096
- p7.note(
21214
+ p8.note(
21097
21215
  `${lines.join("\n")}
21098
21216
 
21099
21217
  ${stringifyProjectJson(insertion.field).trim()}`,
@@ -21126,7 +21244,7 @@ Re-run with --yes to confirm these warning cases.`);
21126
21244
  }
21127
21245
  return;
21128
21246
  }
21129
- p7.note(warnings.join("\n"), "Warnings");
21247
+ p8.note(warnings.join("\n"), "Warnings");
21130
21248
  const confirmed = await promptConfirm3("Continue with these warnings?", true);
21131
21249
  if (!confirmed) {
21132
21250
  cancel4();
@@ -21245,7 +21363,7 @@ ${validationErrors.map((error) => ` - ${error}`).join("\n")}`
21245
21363
  }
21246
21364
  }
21247
21365
  writeAuthoredGeneratedSchema(loaded);
21248
- p7.log.success(`Updated ${path30.relative(cwd, loaded.filePath)}`);
21366
+ p8.log.success(`Updated ${path30.relative(cwd, loaded.filePath)}`);
21249
21367
  try {
21250
21368
  await runGenerateCommand(selectedSchemaName, {
21251
21369
  force: false,
@@ -21256,8 +21374,8 @@ ${validationErrors.map((error) => ` - ${error}`).join("\n")}`
21256
21374
  cwd
21257
21375
  });
21258
21376
  } catch (error) {
21259
- p7.log.error(error instanceof Error ? error.message : String(error));
21260
- p7.log.info(
21377
+ p8.log.error(error instanceof Error ? error.message : String(error));
21378
+ p8.log.info(
21261
21379
  `Schema JSON was kept. Re-run \`betterstart generate ${selectedSchemaName}${options.skipMigration ? " --skip-migration" : ""}\` after resolving the issue.`
21262
21380
  );
21263
21381
  process.exit(1);
@@ -21267,7 +21385,7 @@ ${validationErrors.map((error) => ` - ${error}`).join("\n")}`
21267
21385
  // adapters/next/commands/create.ts
21268
21386
  import fs25 from "fs";
21269
21387
  import path31 from "path";
21270
- import * as p8 from "@clack/prompts";
21388
+ import * as p9 from "@clack/prompts";
21271
21389
  var CREATE_PROMPT_CONTEXT = {
21272
21390
  cancelMessage: "Create schema cancelled."
21273
21391
  };
@@ -21458,7 +21576,7 @@ async function promptTabSlotFields(kind, schemasDir, titleField) {
21458
21576
  );
21459
21577
  if (!addField) {
21460
21578
  if (!hasFields) {
21461
- p8.log.warn("Tabs need at least one child field.");
21579
+ p9.log.warn("Tabs need at least one child field.");
21462
21580
  continue;
21463
21581
  }
21464
21582
  return { main, sidebar };
@@ -21506,7 +21624,7 @@ async function promptSchemaTab(kind, schemasDir, titleField, existingTabs) {
21506
21624
  while (fields.length === 0) {
21507
21625
  await promptAdditionalSchemaFields(kind, schemasDir, fields);
21508
21626
  if (fields.length === 0) {
21509
- p8.log.warn("Tabs need at least one child field.");
21627
+ p9.log.warn("Tabs need at least one child field.");
21510
21628
  }
21511
21629
  }
21512
21630
  }
@@ -21596,7 +21714,7 @@ async function promptFormSubmissionColumns(schema) {
21596
21714
  }
21597
21715
  const existingColumnNames = schema.columns ? new Set(schema.columns.map((column) => column.accessorKey)) : void 0;
21598
21716
  const initialValues = existingColumnNames ? fields.filter((field) => existingColumnNames.has(field.name)).map((field) => field.name) : fields.map((field) => field.name);
21599
- const selected = await p8.multiselect({
21717
+ const selected = await p9.multiselect({
21600
21718
  message: "Submission table columns",
21601
21719
  options: fields.map((field) => ({
21602
21720
  value: field.name,
@@ -21605,7 +21723,7 @@ async function promptFormSubmissionColumns(schema) {
21605
21723
  initialValues,
21606
21724
  required: false
21607
21725
  });
21608
- if (p8.isCancel(selected)) {
21726
+ if (p9.isCancel(selected)) {
21609
21727
  cancel2(CREATE_PROMPT_CONTEXT);
21610
21728
  }
21611
21729
  const selectedNames = new Set(selected);
@@ -21665,7 +21783,7 @@ function schemaFilePath(kind, schemasDir, schemaName) {
21665
21783
  return kind === "form" ? path31.join(schemasDir, "forms", `${schemaName}.json`) : path31.join(schemasDir, `${schemaName}.json`);
21666
21784
  }
21667
21785
  function printPreview2(loaded, cwd) {
21668
- p8.note(
21786
+ p9.note(
21669
21787
  `schema: ${loaded.name} (${loaded.kind})
21670
21788
  path: ${path31.relative(cwd, loaded.filePath)}
21671
21789
  owner: user
@@ -21720,7 +21838,7 @@ ${validationErrors.map((error) => ` - ${error}`).join("\n")}`
21720
21838
  }
21721
21839
  fs25.mkdirSync(path31.dirname(filePath), { recursive: true });
21722
21840
  writeAuthoredGeneratedSchema(loaded);
21723
- p8.log.success(`Created ${path31.relative(cwd, filePath)}`);
21841
+ p9.log.success(`Created ${path31.relative(cwd, filePath)}`);
21724
21842
  try {
21725
21843
  await runGenerateCommand(metadata.name, {
21726
21844
  force: false,
@@ -21731,8 +21849,8 @@ ${validationErrors.map((error) => ` - ${error}`).join("\n")}`
21731
21849
  cwd
21732
21850
  });
21733
21851
  } catch (error) {
21734
- p8.log.error(error instanceof Error ? error.message : String(error));
21735
- p8.log.info(
21852
+ p9.log.error(error instanceof Error ? error.message : String(error));
21853
+ p9.log.info(
21736
21854
  `Schema JSON was kept. Re-run \`betterstart generate ${metadata.name}${options.skipMigration ? " --skip-migration" : ""}\` after resolving the issue.`
21737
21855
  );
21738
21856
  process.exit(1);
@@ -21741,40 +21859,41 @@ ${validationErrors.map((error) => ` - ${error}`).join("\n")}`
21741
21859
 
21742
21860
  // adapters/next/commands/init.ts
21743
21861
  import { execFileSync as execFileSync5, spawn as spawn4 } from "child_process";
21744
- import fs37 from "fs";
21745
- import path48 from "path";
21746
- import * as p15 from "@clack/prompts";
21862
+ import fs39 from "fs";
21863
+ import path50 from "path";
21864
+ import * as p17 from "@clack/prompts";
21747
21865
 
21748
21866
  // adapters/next/init/prompts/database.ts
21749
21867
  import { execFileSync as execFileSync4 } from "child_process";
21750
- import * as p9 from "@clack/prompts";
21868
+ import * as p10 from "@clack/prompts";
21751
21869
  import pc from "picocolors";
21752
21870
  var VERCEL_NEON_URL = "https://vercel.com/dashboard/integrations/checkout/neon";
21753
- async function promptDatabase(options) {
21871
+ async function promptServices(options) {
21754
21872
  if (!options.allowVercel) {
21755
21873
  const url2 = await promptConnectionString();
21756
21874
  return { provider: "manual", url: url2 };
21757
21875
  }
21758
- const choice = await p9.select({
21759
- message: "How would you like to connect your PostgreSQL database?",
21876
+ const choice = await p10.select({
21877
+ message: "Connect a PostgreSQL Database",
21760
21878
  options: [
21761
21879
  {
21762
- value: "vercel-neon",
21763
- label: "Vercel (Neon)",
21764
- hint: "create & connect a free Postgres database via the Vercel CLI"
21880
+ value: "vercel",
21881
+ label: "Vercel",
21882
+ hint: "Automatically provision a Neon Postgres database"
21765
21883
  },
21766
21884
  {
21767
21885
  value: "manual",
21768
- label: "Enter connection string manually"
21886
+ label: "Manual",
21887
+ hint: "Manually enter connection details"
21769
21888
  }
21770
21889
  ],
21771
- initialValue: "vercel-neon"
21890
+ initialValue: "vercel"
21772
21891
  });
21773
- if (p9.isCancel(choice)) {
21774
- p9.cancel("Setup cancelled.");
21892
+ if (p10.isCancel(choice)) {
21893
+ p10.cancel("Setup cancelled.");
21775
21894
  process.exit(0);
21776
21895
  }
21777
- if (choice === "vercel-neon") {
21896
+ if (choice === "vercel") {
21778
21897
  return { provider: "vercel-cli" };
21779
21898
  }
21780
21899
  const url = await promptConnectionString();
@@ -21782,12 +21901,12 @@ async function promptDatabase(options) {
21782
21901
  }
21783
21902
  function openBrowserVercelNeon() {
21784
21903
  openBrowser(VERCEL_NEON_URL);
21785
- p9.log.info(
21904
+ p10.log.info(
21786
21905
  `Opening Vercel... Create a Neon Postgres database, then copy the ${pc.cyan("DATABASE_URL")} from the dashboard.`
21787
21906
  );
21788
21907
  }
21789
21908
  async function promptConnectionString() {
21790
- const input = await p9.text({
21909
+ const input = await p10.text({
21791
21910
  message: "Paste your PostgreSQL connection string",
21792
21911
  placeholder: "postgres://user:pass@host/db",
21793
21912
  validate(val) {
@@ -21801,8 +21920,8 @@ async function promptConnectionString() {
21801
21920
  }
21802
21921
  }
21803
21922
  });
21804
- if (p9.isCancel(input)) {
21805
- p9.cancel("Setup cancelled.");
21923
+ if (p10.isCancel(input)) {
21924
+ p10.cancel("Setup cancelled.");
21806
21925
  process.exit(0);
21807
21926
  }
21808
21927
  return input.replace(/^['"]|['"]$/g, "").trim();
@@ -21823,8 +21942,8 @@ function openBrowser(url) {
21823
21942
 
21824
21943
  // adapters/next/init/prompts/plugins.ts
21825
21944
  import { styleText } from "util";
21826
- import * as p10 from "@clack/prompts";
21827
- async function promptPlugins(cwd) {
21945
+ import * as p11 from "@clack/prompts";
21946
+ async function promptPlugins(cwd, options = {}) {
21828
21947
  const sections = [];
21829
21948
  const overwriteKeys = /* @__PURE__ */ new Set();
21830
21949
  const mergeIntegrationConfig = (collected) => {
@@ -21833,80 +21952,94 @@ async function promptPlugins(cwd) {
21833
21952
  overwriteKeys.add(key);
21834
21953
  }
21835
21954
  };
21836
- const selectedPlugins = await p10.multiselect({
21837
- message: `Select presets
21838
- ${styleText("dim", "Press [Spacebar] to select/unselect")}`,
21955
+ const storage = await p11.select({
21956
+ message: "Choose a file storage",
21839
21957
  options: [
21840
21958
  {
21841
- value: "blog",
21842
- label: "Blog",
21843
- hint: "Posts"
21844
- }
21845
- ],
21846
- required: false,
21847
- initialValues: ["blog"]
21848
- });
21849
- if (p10.isCancel(selectedPlugins)) {
21850
- p10.cancel("Setup cancelled.");
21851
- process.exit(0);
21852
- }
21853
- const storage = await p10.select({
21854
- message: "Choose a file storage mode",
21855
- options: [
21959
+ value: "vercel-blob",
21960
+ label: "Vercel Blob"
21961
+ },
21856
21962
  {
21857
21963
  value: "r2",
21858
- label: "Cloudflare R2",
21859
- hint: "recommended for hosted/serverless production"
21964
+ label: "Cloudflare R2"
21860
21965
  },
21861
21966
  {
21862
21967
  value: "local",
21863
21968
  label: "Local filesystem (public/media)",
21864
- hint: "development or self-hosted persistent disk"
21969
+ hint: "\u26A0 For testing only"
21865
21970
  }
21866
21971
  ],
21867
- initialValue: "r2"
21972
+ initialValue: "vercel-blob"
21868
21973
  });
21869
- if (p10.isCancel(storage)) {
21870
- p10.cancel("Setup cancelled.");
21974
+ if (p11.isCancel(storage)) {
21975
+ p11.cancel("Setup cancelled.");
21871
21976
  process.exit(0);
21872
21977
  }
21873
21978
  if (storage === "r2") {
21874
21979
  mergeIntegrationConfig(await collectIntegrationConfig(cwd, ["r2"]));
21980
+ } else if (storage === "vercel-blob") {
21981
+ const existingToken = readEnvVar(cwd, "BLOB_READ_WRITE_TOKEN")?.trim();
21982
+ const flow = !existingToken && options.provisionVercelBlob ? await options.provisionVercelBlob() : void 0;
21983
+ if (flow?.ok && flow.token) {
21984
+ sections.push({
21985
+ header: "Storage (Vercel Blob)",
21986
+ vars: [{ key: "BLOB_READ_WRITE_TOKEN", value: flow.token }]
21987
+ });
21988
+ overwriteKeys.add("BLOB_READ_WRITE_TOKEN");
21989
+ } else {
21990
+ if (existingToken) {
21991
+ p11.log.info("Using the existing Vercel Blob token from .env.local");
21992
+ } else if (options.provisionVercelBlob) {
21993
+ p11.log.info("Falling back to a manual Vercel Blob token.");
21994
+ }
21995
+ mergeIntegrationConfig(await collectIntegrationConfig(cwd, ["vercel-blob"]));
21996
+ }
21875
21997
  }
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
- )}`,
21998
+ const selectedPlugins = await p11.multiselect({
21999
+ message: "Select presets",
21882
22000
  options: [
21883
22001
  {
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
22002
+ value: "blog",
22003
+ label: "Blog",
22004
+ hint: "Posts"
21895
22005
  }
21896
22006
  ],
21897
- initialValue: "resend"
22007
+ required: false,
22008
+ initialValues: ["blog"]
21898
22009
  });
21899
- if (p10.isCancel(selectedEmailProvider)) {
21900
- p10.cancel("Setup cancelled.");
22010
+ if (p11.isCancel(selectedPlugins)) {
22011
+ p11.cancel("Setup cancelled.");
21901
22012
  process.exit(0);
21902
22013
  }
21903
22014
  const integrations = [];
21904
- if (selectedEmailProvider === "resend") {
22015
+ const setupResend = await p11.confirm({
22016
+ message: `Do you want to setup resend.com emails?
22017
+ ${styleText(
22018
+ "dim",
22019
+ "This will be used for form submission confirmations, password resets etc."
22020
+ )}`,
22021
+ initialValue: true
22022
+ });
22023
+ if (p11.isCancel(setupResend)) {
22024
+ p11.cancel("Setup cancelled.");
22025
+ process.exit(0);
22026
+ }
22027
+ if (setupResend) {
21905
22028
  integrations.push("resend");
21906
22029
  mergeIntegrationConfig(await collectIntegrationConfig(cwd, ["resend"]));
22030
+ } else {
22031
+ sections.push({
22032
+ header: "Email (Resend)",
22033
+ vars: [
22034
+ { key: "BETTERSTART_RESEND_API_KEY", value: "" },
22035
+ { key: "BETTERSTART_EMAIL_FROM", value: "" }
22036
+ ]
22037
+ });
21907
22038
  }
21908
22039
  if (storage === "r2") {
21909
22040
  integrations.push("r2");
22041
+ } else if (storage === "vercel-blob") {
22042
+ integrations.push("vercel-blob");
21910
22043
  }
21911
22044
  return {
21912
22045
  plugins: selectedPlugins,
@@ -21917,10 +22050,10 @@ ${styleText(
21917
22050
  }
21918
22051
 
21919
22052
  // adapters/next/init/prompts/project.ts
21920
- import * as p11 from "@clack/prompts";
22053
+ import * as p12 from "@clack/prompts";
21921
22054
  import pc2 from "picocolors";
21922
22055
  async function promptProject(defaultName) {
21923
- const projectName = await p11.text({
22056
+ const projectName = await p12.text({
21924
22057
  message: [
21925
22058
  "What is your project name?",
21926
22059
  `${pc2.cyan("\u2502")} ${pc2.dim("If you don't want a directory created with the project name, just press `")}${pc2.bold(
@@ -21939,16 +22072,16 @@ async function promptProject(defaultName) {
21939
22072
  return void 0;
21940
22073
  }
21941
22074
  });
21942
- if (p11.isCancel(projectName)) {
21943
- p11.cancel("Setup cancelled.");
22075
+ if (p12.isCancel(projectName)) {
22076
+ p12.cancel("Setup cancelled.");
21944
22077
  process.exit(0);
21945
22078
  }
21946
- const useSrcDir = await p11.confirm({
22079
+ const useSrcDir = await p12.confirm({
21947
22080
  message: "Use src/ directory?",
21948
22081
  initialValue: false
21949
22082
  });
21950
- if (p11.isCancel(useSrcDir)) {
21951
- p11.cancel("Setup cancelled.");
22083
+ if (p12.isCancel(useSrcDir)) {
22084
+ p12.cancel("Setup cancelled.");
21952
22085
  process.exit(0);
21953
22086
  }
21954
22087
  return { projectName: projectName.trim(), useSrcDir };
@@ -22945,6 +23078,18 @@ function shouldOverwriteAuthUrl(cwd, devPort) {
22945
23078
  if (!existingAuthUrl) return false;
22946
23079
  return existingAuthUrl === LEGACY_DEFAULT_AUTH_URL && existingAuthUrl !== `http://localhost:${devPort}`;
22947
23080
  }
23081
+ function persistDatabaseUrl(cwd, databaseUrl) {
23082
+ appendEnvVars(
23083
+ cwd,
23084
+ [
23085
+ {
23086
+ header: "Database (PostgreSQL)",
23087
+ vars: [{ key: "DATABASE_URL", value: databaseUrl }]
23088
+ }
23089
+ ],
23090
+ /* @__PURE__ */ new Set(["DATABASE_URL"])
23091
+ );
23092
+ }
22948
23093
  function scaffoldEnv(cwd, options) {
22949
23094
  const devPort = detectDevPort(cwd);
22950
23095
  const sections = getCoreEnvSections(options.databaseUrl, devPort, options.namespace ?? "admin");
@@ -23827,15 +23972,16 @@ function scaffoldTsconfig(cwd, config) {
23827
23972
  }
23828
23973
 
23829
23974
  // adapters/next/init/vercel/flow.ts
23830
- import * as p14 from "@clack/prompts";
23831
- import pc4 from "picocolors";
23975
+ import * as p16 from "@clack/prompts";
23976
+ import pc6 from "picocolors";
23832
23977
 
23833
23978
  // adapters/next/init/vercel/auth.ts
23834
- import * as p12 from "@clack/prompts";
23979
+ import * as p13 from "@clack/prompts";
23835
23980
  import pc3 from "picocolors";
23836
23981
 
23837
23982
  // adapters/next/init/vercel/runner.ts
23838
23983
  import { spawn as spawn3 } from "child_process";
23984
+ import { stripVTControlCharacters as stripVTControlCharacters2 } from "util";
23839
23985
  var VERCEL_CLI_VERSION = "latest";
23840
23986
  var DEFAULT_CAPTURE_TIMEOUT_MS = 12e4;
23841
23987
  function pathRunner() {
@@ -23852,18 +23998,43 @@ async function resolveVercelRunner() {
23852
23998
  });
23853
23999
  return probe.success ? pathRunner() : npxRunner();
23854
24000
  }
24001
+ function createFilteredLineForwarder(shouldShow, write) {
24002
+ let pending = "";
24003
+ const emit = (line) => {
24004
+ if (shouldShow(stripVTControlCharacters2(line).trim())) write(`${line}
24005
+ `);
24006
+ };
24007
+ return {
24008
+ push(chunk) {
24009
+ pending += chunk;
24010
+ const lines = pending.split(/\r\n|\n|\r/);
24011
+ pending = lines.pop() ?? "";
24012
+ for (const line of lines) emit(line);
24013
+ },
24014
+ flush() {
24015
+ if (pending !== "") emit(pending);
24016
+ pending = "";
24017
+ }
24018
+ };
24019
+ }
23855
24020
  function runVercel(runner, args, options) {
23856
24021
  const timeoutMs = options.timeoutMs ?? DEFAULT_CAPTURE_TIMEOUT_MS;
23857
24022
  const argv = [...runner.prefix, ...args];
23858
24023
  return new Promise((resolve) => {
23859
24024
  const child = spawn3(runner.bin, argv, {
23860
24025
  cwd: options.cwd,
23861
- stdio: options.mode === "inherit" ? "inherit" : "pipe",
23862
- env: { ...process.env, ...options.env }
24026
+ stdio: options.mode === "inherit" ? "inherit" : options.mode === "stream" ? ["inherit", "pipe", "pipe"] : "pipe",
24027
+ // npm_config_loglevel silences npx's "npm warn deprecated" install noise
24028
+ // on the npx runner; harmless for a PATH-installed vercel.
24029
+ env: { ...process.env, npm_config_loglevel: "error", ...options.env }
23863
24030
  });
23864
24031
  let stdout = "";
23865
24032
  let stderr = "";
23866
24033
  let timedOut = false;
24034
+ if (options.mode === "capture" && options.stdinInput !== void 0) {
24035
+ child.stdin?.write(options.stdinInput);
24036
+ child.stdin?.end();
24037
+ }
23867
24038
  if (options.mode === "capture") {
23868
24039
  child.stdout?.on("data", (chunk) => {
23869
24040
  stdout += chunk.toString();
@@ -23871,6 +24042,30 @@ function runVercel(runner, args, options) {
23871
24042
  child.stderr?.on("data", (chunk) => {
23872
24043
  stderr += chunk.toString();
23873
24044
  });
24045
+ } else if (options.mode === "stream") {
24046
+ const shouldShow = options.streamLineFilter ?? (() => true);
24047
+ const outForwarder = createFilteredLineForwarder(
24048
+ shouldShow,
24049
+ (text7) => process.stdout.write(text7)
24050
+ );
24051
+ const errForwarder = createFilteredLineForwarder(
24052
+ shouldShow,
24053
+ (text7) => process.stderr.write(text7)
24054
+ );
24055
+ child.stdout?.on("data", (chunk) => {
24056
+ const text7 = chunk.toString();
24057
+ stdout += text7;
24058
+ outForwarder.push(text7);
24059
+ });
24060
+ child.stderr?.on("data", (chunk) => {
24061
+ const text7 = chunk.toString();
24062
+ stderr += text7;
24063
+ errForwarder.push(text7);
24064
+ });
24065
+ child.on("close", () => {
24066
+ outForwarder.flush();
24067
+ errForwarder.flush();
24068
+ });
23874
24069
  }
23875
24070
  const timeout = setTimeout(() => {
23876
24071
  timedOut = true;
@@ -23941,6 +24136,19 @@ function extractOutermostJson(text7) {
23941
24136
  // adapters/next/init/vercel/auth.ts
23942
24137
  var WHOAMI_TIMEOUT_MS = 3e4;
23943
24138
  var LOGIN_TIMEOUT_MS = 3e5;
24139
+ var LOGIN_NOISE_PATTERNS = [
24140
+ /^vercel cli \d/i,
24141
+ /^>?$/,
24142
+ /^npm (warn|notice)/i,
24143
+ /congratulations! you are now signed in/i,
24144
+ /^to deploy something/i,
24145
+ /to deploy every commit automatically/i,
24146
+ /connect a git repository/i,
24147
+ /vercel\.link\/git/i
24148
+ ];
24149
+ function showVercelLoginLine(line) {
24150
+ return !LOGIN_NOISE_PATTERNS.some((pattern) => pattern.test(line));
24151
+ }
23944
24152
  async function checkVercelAuth(runner, cwd, env) {
23945
24153
  const result = await runVercel(runner, ["whoami"], {
23946
24154
  cwd,
@@ -23956,35 +24164,38 @@ async function checkVercelAuth(runner, cwd, env) {
23956
24164
  }
23957
24165
  async function ensureVercelAuth(runner, cwd, options) {
23958
24166
  const env = options.env ?? process.env;
23959
- const spinner9 = p12.spinner();
23960
- spinner9.start(
23961
- runner.source === "npx" ? "Checking Vercel sign-in (first run downloads the Vercel CLI)" : "Checking Vercel sign-in"
24167
+ const checkSpinner = spinner2();
24168
+ checkSpinner.start(
24169
+ runner.source === "npx" ? "Checking Vercel, This may take a moment" : "Checking Vercel"
23962
24170
  );
23963
24171
  const existing = await checkVercelAuth(runner, cwd, env);
23964
24172
  if (existing.authed) {
23965
- spinner9.stop(signedInMessage(existing.username));
24173
+ if (options.quietIfAuthed) {
24174
+ checkSpinner.clear();
24175
+ } else {
24176
+ checkSpinner.stop(signedInMessage(existing.username));
24177
+ }
23966
24178
  return existing;
23967
24179
  }
23968
24180
  if (env.VERCEL_TOKEN) {
23969
- spinner9.stop("Could not verify VERCEL_TOKEN with Vercel");
24181
+ checkSpinner.stop("Could not verify VERCEL_TOKEN with Vercel");
23970
24182
  return { authed: false, reason: "login-failed" };
23971
24183
  }
23972
- spinner9.stop("Not signed in to Vercel yet");
23973
- p12.log.info(
23974
- `Sign in to Vercel to continue \u2014 a browser link and code will appear below. ${pc3.dim(
23975
- "(or press Ctrl-C to enter a connection string manually)"
23976
- )}`
24184
+ checkSpinner.clear();
24185
+ p13.log.info(
24186
+ `Sign in to Vercel to continue ${pc3.dim("(or press Ctrl-C to enter a connection string manually)")}`
23977
24187
  );
23978
24188
  const login = await runVercel(runner, ["login"], {
23979
24189
  cwd,
23980
- mode: "inherit",
24190
+ mode: "stream",
24191
+ streamLineFilter: showVercelLoginLine,
23981
24192
  timeoutMs: LOGIN_TIMEOUT_MS,
23982
24193
  env
23983
24194
  });
23984
24195
  if (!login.success) {
23985
24196
  return { authed: false, reason: login.timedOut ? "timeout" : "login-cancelled" };
23986
24197
  }
23987
- const verifySpinner = p12.spinner();
24198
+ const verifySpinner = spinner2();
23988
24199
  verifySpinner.start("Verifying Vercel sign-in");
23989
24200
  const after = await checkVercelAuth(runner, cwd, env);
23990
24201
  if (after.authed) {
@@ -23998,78 +24209,26 @@ function signedInMessage(username) {
23998
24209
  return username ? `Signed in to Vercel as ${pc3.cyan(username)}` : "Signed in to Vercel";
23999
24210
  }
24000
24211
 
24001
- // adapters/next/init/vercel/neon.ts
24212
+ // adapters/next/init/vercel/blob.ts
24213
+ import * as p14 from "@clack/prompts";
24214
+ import pc4 from "picocolors";
24215
+
24216
+ // adapters/next/init/vercel/env-pull.ts
24002
24217
  import fs34 from "fs";
24003
24218
  import os from "os";
24004
24219
  import path45 from "path";
24005
- import * as p13 from "@clack/prompts";
24006
- var PROVISION_TIMEOUT_MS = 18e4;
24007
- var INTERACTIVE_PROVISION_TIMEOUT_MS = 6e5;
24008
24220
  var ENV_PULL_TIMEOUT_MS = 6e4;
24009
- async function provisionNeonInteractive(runner, cwd, options) {
24010
- const addArgs = ["integration", "add", "neon", "--no-env-pull"];
24011
- if (options.plan) addArgs.push("--plan", options.plan);
24012
- const add = await runVercel(runner, addArgs, {
24013
- cwd,
24014
- mode: "inherit",
24015
- timeoutMs: INTERACTIVE_PROVISION_TIMEOUT_MS,
24016
- env: options.env
24017
- });
24018
- if (!add.success) {
24019
- return { failure: add.timedOut ? "timeout" : "provision-failed" };
24020
- }
24021
- const pullSpinner = p13.spinner();
24022
- pullSpinner.start("Retrieving the database connection string");
24023
- const databaseUrl = await pullDatabaseUrl(runner, cwd, options.env);
24024
- if (!databaseUrl) {
24025
- pullSpinner.stop("No DATABASE_URL came back from Vercel");
24026
- return { failure: "env-pull-empty" };
24027
- }
24028
- pullSpinner.stop("Captured the Neon DATABASE_URL");
24029
- return { databaseUrl };
24030
- }
24031
- async function provisionNeon(runner, cwd, options) {
24032
- const addArgs = ["integration", "add", "neon", "--no-env-pull", "--format", "json"];
24033
- if (options.plan) addArgs.push("--plan", options.plan);
24034
- const spinner9 = p13.spinner();
24035
- spinner9.start("Provisioning Neon Postgres via Vercel");
24036
- const add = await runVercel(runner, addArgs, {
24037
- cwd,
24038
- mode: "capture",
24039
- timeoutMs: PROVISION_TIMEOUT_MS,
24040
- env: options.env
24041
- });
24042
- if (!add.success) {
24043
- spinner9.stop("Neon provisioning did not complete");
24044
- const combined = `${add.stdout}
24045
- ${add.stderr}`.trim();
24046
- const detail = combined || void 0;
24047
- if (/terms/i.test(combined)) return { failure: "terms-required", detail };
24048
- if (/claim/i.test(combined)) return { failure: "claim-required", detail };
24049
- return { failure: add.timedOut ? "timeout" : "provision-failed", detail };
24050
- }
24051
- const meta = parseVercelJson(add.stdout);
24052
- const resourceName = meta?.resourceName ?? meta?.name;
24053
- spinner9.message("Retrieving the database connection string");
24054
- const databaseUrl = await pullDatabaseUrl(runner, cwd, options.env);
24055
- if (!databaseUrl) {
24056
- spinner9.stop("No DATABASE_URL came back from Vercel");
24057
- return { resourceName, failure: "env-pull-empty" };
24058
- }
24059
- spinner9.stop("Captured the Neon DATABASE_URL");
24060
- return { databaseUrl, resourceName };
24061
- }
24062
- async function pullDatabaseUrl(runner, cwd, env) {
24221
+ async function pullVercelEnvValue(runner, cwd, read, env, environment = "development") {
24063
24222
  const tmpDir = fs34.mkdtempSync(path45.join(os.tmpdir(), "betterstart-vercel-"));
24064
24223
  const tmpEnv = path45.join(tmpDir, ".env.pull");
24065
24224
  try {
24066
24225
  const pull = await runVercel(
24067
24226
  runner,
24068
- ["env", "pull", tmpEnv, "--environment", "development", "--yes"],
24227
+ ["env", "pull", tmpEnv, "--environment", environment, "--yes"],
24069
24228
  { cwd, mode: "capture", timeoutMs: ENV_PULL_TIMEOUT_MS, env }
24070
24229
  );
24071
24230
  if (!pull.success) return void 0;
24072
- return readDbUrlFromDotenv(tmpEnv);
24231
+ return read(tmpEnv);
24073
24232
  } finally {
24074
24233
  try {
24075
24234
  fs34.rmSync(tmpDir, { recursive: true, force: true });
@@ -24077,9 +24236,9 @@ async function pullDatabaseUrl(runner, cwd, env) {
24077
24236
  }
24078
24237
  }
24079
24238
  }
24080
- function readDbUrlFromDotenv(filePath) {
24081
- if (!fs34.existsSync(filePath)) return void 0;
24239
+ function parseDotenvFile(filePath) {
24082
24240
  const vars = /* @__PURE__ */ new Map();
24241
+ if (!fs34.existsSync(filePath)) return vars;
24083
24242
  for (const line of fs34.readFileSync(filePath, "utf-8").split("\n")) {
24084
24243
  const trimmed = line.trim();
24085
24244
  if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
@@ -24088,16 +24247,7 @@ function readDbUrlFromDotenv(filePath) {
24088
24247
  const value = trimmed.slice(eq + 1).replace(/^['"]|['"]$/g, "").trim();
24089
24248
  if (key) vars.set(key, value);
24090
24249
  }
24091
- const isPg = (v) => !!v && (v.startsWith("postgres://") || v.startsWith("postgresql://"));
24092
- if (isPg(vars.get("DATABASE_URL"))) return vars.get("DATABASE_URL");
24093
- if (isPg(vars.get("POSTGRES_URL"))) return vars.get("POSTGRES_URL");
24094
- for (const [key, value] of vars) {
24095
- if (/DATABASE_URL$/.test(key) && isPg(value)) return value;
24096
- }
24097
- for (const value of vars.values()) {
24098
- if (isPg(value)) return value;
24099
- }
24100
- return void 0;
24250
+ return vars;
24101
24251
  }
24102
24252
 
24103
24253
  // adapters/next/init/vercel/project.ts
@@ -24153,44 +24303,442 @@ ${add.stderr}`)) continue;
24153
24303
  };
24154
24304
  }
24155
24305
  function readLinkedProjectId(cwd) {
24306
+ return readLinkedProjectJson(cwd)?.projectId;
24307
+ }
24308
+ function readLinkedProjectName(cwd) {
24309
+ return readLinkedProjectJson(cwd)?.projectName;
24310
+ }
24311
+ function readLinkedProjectJson(cwd) {
24156
24312
  try {
24157
24313
  const projectJsonPath = path46.join(cwd, ".vercel", "project.json");
24158
24314
  if (!fs35.existsSync(projectJsonPath)) return void 0;
24159
- const parsed = JSON.parse(fs35.readFileSync(projectJsonPath, "utf-8"));
24160
- return parsed.projectId;
24315
+ return JSON.parse(fs35.readFileSync(projectJsonPath, "utf-8"));
24161
24316
  } catch {
24162
24317
  return void 0;
24163
24318
  }
24164
24319
  }
24165
24320
 
24166
- // adapters/next/init/vercel/flow.ts
24167
- async function runVercelNeonFlow(options) {
24168
- const env = options.env ?? process.env;
24169
- try {
24170
- const runnerSpinner = p14.spinner();
24171
- runnerSpinner.start("Checking for the Vercel CLI");
24172
- const runner = await resolveVercelRunner();
24173
- runnerSpinner.stop(
24174
- runner.source === "path" ? "Using the Vercel CLI from your PATH" : `Using ${pc4.cyan(`npx vercel@${VERCEL_CLI_VERSION}`)}`
24175
- );
24176
- const auth = await ensureVercelAuth(runner, options.cwd, { env });
24177
- if (!auth.authed) {
24178
- p14.log.warn(authFailureMessage(auth.reason));
24179
- return { ok: false };
24180
- }
24181
- const projectSpinner = p14.spinner();
24182
- projectSpinner.start(`Creating Vercel project ${pc4.cyan(options.projectName)}`);
24183
- const project2 = await createVercelProject(runner, options.cwd, options.projectName, env);
24184
- projectSpinner.stop(
24185
- project2.linked ? `Linked Vercel project ${pc4.cyan(project2.name)}` : `Using Vercel project ${pc4.cyan(project2.name)}`
24321
+ // adapters/next/init/vercel/blob.ts
24322
+ var PROVISION_TIMEOUT_MS = 18e4;
24323
+ var INTERACTIVE_PROVISION_TIMEOUT_MS = 6e5;
24324
+ var MAX_STORE_NAME_ATTEMPTS = 5;
24325
+ function blobStoreName(projectName) {
24326
+ return sanitizeVercelProjectName(`${projectName}-blob`);
24327
+ }
24328
+ function isNameTaken(result) {
24329
+ return /already exists|already taken|already in use|name.*taken/i.test(
24330
+ `${result.stdout}
24331
+ ${result.stderr}`
24332
+ );
24333
+ }
24334
+ async function createStoreQuiet(runner, cwd, baseName, env) {
24335
+ let result;
24336
+ let storeName = baseName;
24337
+ for (let attempt = 1; attempt <= MAX_STORE_NAME_ATTEMPTS; attempt++) {
24338
+ storeName = versionedVercelProjectName(baseName, attempt);
24339
+ result = await runVercel(
24340
+ runner,
24341
+ ["blob", "create-store", storeName, "--access", "public", "--yes"],
24342
+ { cwd, mode: "capture", timeoutMs: PROVISION_TIMEOUT_MS, env }
24186
24343
  );
24187
- const neon = await provisionNeonForMode(runner, options);
24188
- if (neon.failure || !neon.databaseUrl) {
24189
- p14.log.warn(neonFailureMessage(neon.failure));
24190
- if (neon.detail) p14.log.message(pc4.dim(neon.detail));
24191
- return { ok: false };
24192
- }
24193
- p14.log.success(`Provisioned Neon Postgres for ${pc4.cyan(project2.name)}`);
24344
+ if (result.success || !isNameTaken(result)) break;
24345
+ }
24346
+ return { result, storeName };
24347
+ }
24348
+ async function provisionBlobStoreInteractive(runner, cwd, options) {
24349
+ const quietSpinner = spinner2();
24350
+ quietSpinner.start("Creating your Vercel Blob store");
24351
+ const quiet = await createStoreQuiet(runner, cwd, options.name, options.env);
24352
+ if (quiet.result.success) {
24353
+ quietSpinner.message("Retrieving the Blob read-write token");
24354
+ const token2 = await pullBlobToken(runner, cwd, options.env);
24355
+ if (!token2) {
24356
+ quietSpinner.stop("No BLOB_READ_WRITE_TOKEN came back from Vercel");
24357
+ return { storeName: quiet.storeName, failure: "env-pull-empty" };
24358
+ }
24359
+ quietSpinner.clear();
24360
+ return { token: token2, storeName: quiet.storeName };
24361
+ }
24362
+ quietSpinner.clear();
24363
+ p14.log.info(
24364
+ `Create your Blob store in the Vercel prompts below ${pc4.dim(
24365
+ "(connect it to all environments)."
24366
+ )}`
24367
+ );
24368
+ const add = await runVercel(
24369
+ runner,
24370
+ ["blob", "create-store", quiet.storeName, "--access", "public"],
24371
+ { cwd, mode: "inherit", timeoutMs: INTERACTIVE_PROVISION_TIMEOUT_MS, env: options.env }
24372
+ );
24373
+ if (!add.success) {
24374
+ return { failure: add.timedOut ? "timeout" : "provision-failed" };
24375
+ }
24376
+ const pullSpinner = spinner2();
24377
+ pullSpinner.start("Retrieving the Blob read-write token");
24378
+ const token = await pullBlobToken(runner, cwd, options.env);
24379
+ if (!token) {
24380
+ pullSpinner.stop("No BLOB_READ_WRITE_TOKEN came back from Vercel");
24381
+ return { storeName: quiet.storeName, failure: "env-pull-empty" };
24382
+ }
24383
+ pullSpinner.clear();
24384
+ return { token, storeName: quiet.storeName };
24385
+ }
24386
+ async function provisionBlobStore(runner, cwd, options) {
24387
+ const provisionSpinner = spinner2();
24388
+ provisionSpinner.start("Creating your Vercel Blob store");
24389
+ const { result, storeName } = await createStoreQuiet(runner, cwd, options.name, options.env);
24390
+ if (!result.success) {
24391
+ provisionSpinner.stop("Vercel Blob provisioning did not complete");
24392
+ const combined = `${result.stdout}
24393
+ ${result.stderr}`.trim();
24394
+ return {
24395
+ failure: result.timedOut ? "timeout" : "provision-failed",
24396
+ detail: combined || void 0
24397
+ };
24398
+ }
24399
+ provisionSpinner.message("Retrieving the Blob read-write token");
24400
+ const token = await pullBlobToken(runner, cwd, options.env);
24401
+ if (!token) {
24402
+ provisionSpinner.stop("No BLOB_READ_WRITE_TOKEN came back from Vercel");
24403
+ return { storeName, failure: "env-pull-empty" };
24404
+ }
24405
+ provisionSpinner.clear();
24406
+ return { token, storeName };
24407
+ }
24408
+ function pullBlobToken(runner, cwd, env) {
24409
+ return pullVercelEnvValue(runner, cwd, readBlobTokenFromDotenv, env);
24410
+ }
24411
+ function readBlobTokenFromDotenv(filePath) {
24412
+ const vars = parseDotenvFile(filePath);
24413
+ const isBlobToken = (v) => !!v && v.startsWith("vercel_blob_rw_");
24414
+ if (isBlobToken(vars.get("BLOB_READ_WRITE_TOKEN"))) return vars.get("BLOB_READ_WRITE_TOKEN");
24415
+ for (const [key, value] of vars) {
24416
+ if (/_READ_WRITE_TOKEN$/.test(key) && isBlobToken(value)) return value;
24417
+ }
24418
+ for (const value of vars.values()) {
24419
+ if (isBlobToken(value)) return value;
24420
+ }
24421
+ return void 0;
24422
+ }
24423
+
24424
+ // adapters/next/init/vercel/deploy.ts
24425
+ import fs36 from "fs";
24426
+ import path47 from "path";
24427
+ import { stripVTControlCharacters as stripVTControlCharacters3 } from "util";
24428
+ var DEPLOY_TIMEOUT_MS = 9e5;
24429
+ var ENV_ADD_TIMEOUT_MS = 3e4;
24430
+ var DETAIL_MAX_LINES = 15;
24431
+ var ENV_SYNC_SKIP_KEYS = /* @__PURE__ */ new Set([
24432
+ "VERCEL_OIDC_TOKEN",
24433
+ "BETTERSTART_AUTH_URL",
24434
+ "NEXT_PUBLIC_BETTERSTART_AUTH_URL"
24435
+ ]);
24436
+ function isPlaceholderValue(value) {
24437
+ return value === "postgresql://..." || value.startsWith("your_");
24438
+ }
24439
+ function collectDeployEnvVars(cwd, existingProductionKeys) {
24440
+ const local = parseDotenvFile(path47.join(cwd, ".env.local"));
24441
+ const vars = [];
24442
+ for (const [key, value] of local) {
24443
+ if (ENV_SYNC_SKIP_KEYS.has(key)) continue;
24444
+ if (!value || isPlaceholderValue(value)) continue;
24445
+ if (existingProductionKeys.has(key)) continue;
24446
+ vars.push({ key, value });
24447
+ }
24448
+ return vars;
24449
+ }
24450
+ async function syncVercelProductionEnv(runner, cwd, env) {
24451
+ const existingKeys = await pullVercelEnvValue(
24452
+ runner,
24453
+ cwd,
24454
+ (filePath) => new Set(parseDotenvFile(filePath).keys()),
24455
+ env,
24456
+ "production"
24457
+ ) ?? /* @__PURE__ */ new Set();
24458
+ const synced = [];
24459
+ const failed = [];
24460
+ for (const { key, value } of collectDeployEnvVars(cwd, existingKeys)) {
24461
+ const add = await runVercel(runner, ["env", "add", key, "production"], {
24462
+ cwd,
24463
+ mode: "capture",
24464
+ timeoutMs: ENV_ADD_TIMEOUT_MS,
24465
+ env,
24466
+ stdinInput: `${value}
24467
+ `
24468
+ });
24469
+ if (add.success) synced.push(key);
24470
+ else failed.push(key);
24471
+ }
24472
+ return { synced, failed };
24473
+ }
24474
+ var LOCAL_PROTOCOL_PATTERN = /^(workspace|link|file|portal):/;
24475
+ var ADMIN_CONFIG_IMPORT_PATTERN = /^import\s+\{[^}]*\bdefineConfig\b[^}]*\}\s+from\s+['"]betterstart-cli['"];?[^\S\n]*$/m;
24476
+ var ADMIN_CONFIG_IMPORT_SHIM = "const defineConfig = <T>(config: T): T => config";
24477
+ function guardProjectForDeploy(cwd) {
24478
+ const restores = [];
24479
+ const localSpecDeps = guardPackageJson(cwd, restores);
24480
+ guardAdminConfig(cwd, restores);
24481
+ return {
24482
+ localSpecDeps,
24483
+ restore() {
24484
+ for (const restoreFile2 of restores) {
24485
+ try {
24486
+ restoreFile2();
24487
+ } catch {
24488
+ }
24489
+ }
24490
+ }
24491
+ };
24492
+ }
24493
+ function guardPackageJson(cwd, restores) {
24494
+ const packageJsonPath = path47.join(cwd, "package.json");
24495
+ if (!fs36.existsSync(packageJsonPath)) {
24496
+ return [];
24497
+ }
24498
+ const original = fs36.readFileSync(packageJsonPath, "utf-8");
24499
+ let parsed;
24500
+ try {
24501
+ parsed = JSON.parse(original);
24502
+ } catch {
24503
+ return [];
24504
+ }
24505
+ const localSpecDeps = [];
24506
+ let removed = false;
24507
+ for (const section of ["dependencies", "devDependencies"]) {
24508
+ const deps = parsed[section];
24509
+ if (!deps || typeof deps !== "object") continue;
24510
+ const record = deps;
24511
+ if ("betterstart-cli" in record) {
24512
+ Reflect.deleteProperty(record, "betterstart-cli");
24513
+ removed = true;
24514
+ }
24515
+ for (const [name, spec] of Object.entries(record)) {
24516
+ if (typeof spec === "string" && LOCAL_PROTOCOL_PATTERN.test(spec)) {
24517
+ localSpecDeps.push(name);
24518
+ }
24519
+ }
24520
+ }
24521
+ if (removed) {
24522
+ fs36.writeFileSync(packageJsonPath, `${JSON.stringify(parsed, null, 2)}
24523
+ `, "utf-8");
24524
+ restores.push(() => fs36.writeFileSync(packageJsonPath, original, "utf-8"));
24525
+ }
24526
+ return localSpecDeps;
24527
+ }
24528
+ function guardAdminConfig(cwd, restores) {
24529
+ const adminConfigPath = path47.join(cwd, "admin.config.ts");
24530
+ if (!fs36.existsSync(adminConfigPath)) {
24531
+ return;
24532
+ }
24533
+ const original = fs36.readFileSync(adminConfigPath, "utf-8");
24534
+ if (!ADMIN_CONFIG_IMPORT_PATTERN.test(original)) {
24535
+ return;
24536
+ }
24537
+ fs36.writeFileSync(
24538
+ adminConfigPath,
24539
+ original.replace(ADMIN_CONFIG_IMPORT_PATTERN, ADMIN_CONFIG_IMPORT_SHIM),
24540
+ "utf-8"
24541
+ );
24542
+ restores.push(() => fs36.writeFileSync(adminConfigPath, original, "utf-8"));
24543
+ }
24544
+ function ensureVercelJsonFramework(cwd) {
24545
+ const vercelJsonPath = path47.join(cwd, "vercel.json");
24546
+ if (fs36.existsSync(vercelJsonPath)) {
24547
+ return "exists";
24548
+ }
24549
+ fs36.writeFileSync(
24550
+ vercelJsonPath,
24551
+ `${JSON.stringify(
24552
+ { $schema: "https://openapi.vercel.sh/vercel.json", framework: "nextjs" },
24553
+ null,
24554
+ 2
24555
+ )}
24556
+ `,
24557
+ "utf-8"
24558
+ );
24559
+ return "created";
24560
+ }
24561
+ function parseDeployedUrl(stdout, stderr) {
24562
+ const plainErr = stripVTControlCharacters3(stderr);
24563
+ const production = plainErr.match(/Production:\s+(https:\/\/\S+)/i)?.[1];
24564
+ if (production) return production;
24565
+ const fromStdout = stripVTControlCharacters3(stdout).match(/https:\/\/\S+/)?.[0];
24566
+ return fromStdout;
24567
+ }
24568
+ async function deployVercelProject(runner, cwd, env) {
24569
+ const deploy = await runVercel(runner, ["deploy", "--prod", "--yes"], {
24570
+ cwd,
24571
+ mode: "capture",
24572
+ timeoutMs: DEPLOY_TIMEOUT_MS,
24573
+ env
24574
+ });
24575
+ if (deploy.success) {
24576
+ return { url: parseDeployedUrl(deploy.stdout, deploy.stderr) };
24577
+ }
24578
+ return {
24579
+ failure: deploy.timedOut ? "timeout" : "deploy-failed",
24580
+ detail: outputTail(`${deploy.stdout}
24581
+ ${deploy.stderr}`)
24582
+ };
24583
+ }
24584
+ function outputTail(output) {
24585
+ const lines = stripVTControlCharacters3(output).split("\n").map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
24586
+ if (lines.length === 0) return void 0;
24587
+ return lines.slice(-DETAIL_MAX_LINES).join("\n");
24588
+ }
24589
+
24590
+ // adapters/next/init/vercel/env-guard.ts
24591
+ import fs37 from "fs";
24592
+ import path48 from "path";
24593
+ function guardEnvLocal(cwd) {
24594
+ const envPath = path48.join(cwd, ".env.local");
24595
+ let original;
24596
+ try {
24597
+ original = fs37.readFileSync(envPath, "utf-8");
24598
+ } catch {
24599
+ original = void 0;
24600
+ }
24601
+ return {
24602
+ restore() {
24603
+ try {
24604
+ const current = fs37.existsSync(envPath) ? fs37.readFileSync(envPath, "utf-8") : void 0;
24605
+ if (current === original) return;
24606
+ if (original === void 0) {
24607
+ fs37.rmSync(envPath, { force: true });
24608
+ } else {
24609
+ fs37.writeFileSync(envPath, original);
24610
+ }
24611
+ } catch {
24612
+ }
24613
+ }
24614
+ };
24615
+ }
24616
+
24617
+ // adapters/next/init/vercel/neon.ts
24618
+ import * as p15 from "@clack/prompts";
24619
+ import pc5 from "picocolors";
24620
+ var PROVISION_TIMEOUT_MS2 = 18e4;
24621
+ var INTERACTIVE_PROVISION_TIMEOUT_MS2 = 6e5;
24622
+ async function provisionNeonInteractive(runner, cwd, options) {
24623
+ const addArgs = ["integration", "add", "neon", "--no-env-pull"];
24624
+ if (options.plan) addArgs.push("--plan", options.plan);
24625
+ const quietSpinner = spinner2();
24626
+ quietSpinner.start("Creating your Neon database");
24627
+ const quiet = await runVercel(runner, addArgs, {
24628
+ cwd,
24629
+ mode: "capture",
24630
+ timeoutMs: PROVISION_TIMEOUT_MS2,
24631
+ env: options.env
24632
+ });
24633
+ if (quiet.success) {
24634
+ quietSpinner.message("Retrieving the database connection string");
24635
+ const databaseUrl2 = await pullDatabaseUrl(runner, cwd, options.env);
24636
+ if (!databaseUrl2) {
24637
+ quietSpinner.stop("No DATABASE_URL came back from Vercel");
24638
+ return { failure: "env-pull-empty" };
24639
+ }
24640
+ quietSpinner.clear();
24641
+ return { databaseUrl: databaseUrl2 };
24642
+ }
24643
+ quietSpinner.clear();
24644
+ p15.log.info(
24645
+ `Create your Neon database in the Vercel prompts below ${pc5.dim(
24646
+ "(the Free plan is recommended)."
24647
+ )}`
24648
+ );
24649
+ const add = await runVercel(runner, addArgs, {
24650
+ cwd,
24651
+ mode: "inherit",
24652
+ timeoutMs: INTERACTIVE_PROVISION_TIMEOUT_MS2,
24653
+ env: options.env
24654
+ });
24655
+ if (!add.success) {
24656
+ return { failure: add.timedOut ? "timeout" : "provision-failed" };
24657
+ }
24658
+ const pullSpinner = spinner2();
24659
+ pullSpinner.start("Retrieving the database connection string");
24660
+ const databaseUrl = await pullDatabaseUrl(runner, cwd, options.env);
24661
+ if (!databaseUrl) {
24662
+ pullSpinner.stop("No DATABASE_URL came back from Vercel");
24663
+ return { failure: "env-pull-empty" };
24664
+ }
24665
+ pullSpinner.clear();
24666
+ return { databaseUrl };
24667
+ }
24668
+ async function provisionNeon(runner, cwd, options) {
24669
+ const addArgs = ["integration", "add", "neon", "--no-env-pull", "--format", "json"];
24670
+ if (options.plan) addArgs.push("--plan", options.plan);
24671
+ const provisionSpinner = spinner2();
24672
+ provisionSpinner.start("Creating your Neon database");
24673
+ const add = await runVercel(runner, addArgs, {
24674
+ cwd,
24675
+ mode: "capture",
24676
+ timeoutMs: PROVISION_TIMEOUT_MS2,
24677
+ env: options.env
24678
+ });
24679
+ if (!add.success) {
24680
+ provisionSpinner.stop("Neon provisioning did not complete");
24681
+ const combined = `${add.stdout}
24682
+ ${add.stderr}`.trim();
24683
+ const detail = combined || void 0;
24684
+ if (/terms/i.test(combined)) return { failure: "terms-required", detail };
24685
+ if (/claim/i.test(combined)) return { failure: "claim-required", detail };
24686
+ return { failure: add.timedOut ? "timeout" : "provision-failed", detail };
24687
+ }
24688
+ const meta = parseVercelJson(add.stdout);
24689
+ const resourceName = meta?.resourceName ?? meta?.name;
24690
+ provisionSpinner.message("Retrieving the database connection string");
24691
+ const databaseUrl = await pullDatabaseUrl(runner, cwd, options.env);
24692
+ if (!databaseUrl) {
24693
+ provisionSpinner.stop("No DATABASE_URL came back from Vercel");
24694
+ return { resourceName, failure: "env-pull-empty" };
24695
+ }
24696
+ provisionSpinner.clear();
24697
+ return { databaseUrl, resourceName };
24698
+ }
24699
+ function pullDatabaseUrl(runner, cwd, env) {
24700
+ return pullVercelEnvValue(runner, cwd, readDbUrlFromDotenv, env);
24701
+ }
24702
+ function readDbUrlFromDotenv(filePath) {
24703
+ const vars = parseDotenvFile(filePath);
24704
+ const isPg = (v) => !!v && (v.startsWith("postgres://") || v.startsWith("postgresql://"));
24705
+ if (isPg(vars.get("DATABASE_URL"))) return vars.get("DATABASE_URL");
24706
+ if (isPg(vars.get("POSTGRES_URL"))) return vars.get("POSTGRES_URL");
24707
+ for (const [key, value] of vars) {
24708
+ if (/DATABASE_URL$/.test(key) && isPg(value)) return value;
24709
+ }
24710
+ for (const value of vars.values()) {
24711
+ if (isPg(value)) return value;
24712
+ }
24713
+ return void 0;
24714
+ }
24715
+
24716
+ // adapters/next/init/vercel/flow.ts
24717
+ async function runVercelNeonFlow(options) {
24718
+ const env = options.env ?? process.env;
24719
+ const envGuard = guardEnvLocal(options.cwd);
24720
+ try {
24721
+ const runnerSpinner = spinner2();
24722
+ runnerSpinner.start("Checking for the Vercel CLI");
24723
+ const runner = await resolveVercelRunner();
24724
+ runnerSpinner.clear();
24725
+ const auth = await ensureVercelAuth(runner, options.cwd, { env });
24726
+ if (!auth.authed) {
24727
+ p16.log.warn(authFailureMessage(auth.reason));
24728
+ return { ok: false };
24729
+ }
24730
+ const projectSpinner = spinner2();
24731
+ projectSpinner.start(`Creating a Vercel project ${pc6.cyan(options.projectName)}`);
24732
+ const project2 = await createVercelProject(runner, options.cwd, options.projectName, env);
24733
+ projectSpinner.stop(
24734
+ project2.linked ? `Linked Vercel project ${pc6.cyan(project2.name)}` : `Using Vercel project ${pc6.cyan(project2.name)}`
24735
+ );
24736
+ const neon = await provisionNeonForMode(runner, options);
24737
+ if (neon.failure || !neon.databaseUrl) {
24738
+ p16.log.warn(neonFailureMessage(neon.failure));
24739
+ if (neon.detail) p16.log.message(pc6.dim(neon.detail));
24740
+ return { ok: false };
24741
+ }
24194
24742
  return {
24195
24743
  ok: true,
24196
24744
  databaseUrl: neon.databaseUrl,
@@ -24198,21 +24746,140 @@ async function runVercelNeonFlow(options) {
24198
24746
  neonResourceName: neon.resourceName
24199
24747
  };
24200
24748
  } catch (error) {
24201
- p14.log.warn(
24749
+ p16.log.warn(
24202
24750
  `Vercel provisioning failed: ${error instanceof Error ? error.message : String(error)}`
24203
24751
  );
24204
24752
  return { ok: false };
24753
+ } finally {
24754
+ envGuard.restore();
24205
24755
  }
24206
24756
  }
24207
- function provisionNeonForMode(runner, options) {
24208
- const opts = { plan: options.plan, env: options.env };
24209
- if (options.interactive) {
24210
- p14.log.info(
24211
- `Create your Neon database in the Vercel prompts below ${pc4.dim("(the Free plan is recommended).")}`
24757
+ async function runVercelBlobFlow(options) {
24758
+ const env = options.env ?? process.env;
24759
+ const envGuard = guardEnvLocal(options.cwd);
24760
+ try {
24761
+ const runnerSpinner = spinner2();
24762
+ runnerSpinner.start("Checking for the Vercel CLI");
24763
+ const runner = await resolveVercelRunner();
24764
+ runnerSpinner.clear();
24765
+ const auth = await ensureVercelAuth(runner, options.cwd, { env, quietIfAuthed: true });
24766
+ if (!auth.authed) {
24767
+ p16.log.warn(authFailureMessage(auth.reason));
24768
+ return { ok: false };
24769
+ }
24770
+ let projectId = readLinkedProjectId(options.cwd);
24771
+ if (!projectId) {
24772
+ const projectSpinner = spinner2();
24773
+ projectSpinner.start(`Creating a Vercel project ${pc6.cyan(options.projectName)}`);
24774
+ const project2 = await createVercelProject(runner, options.cwd, options.projectName, env);
24775
+ projectSpinner.stop(
24776
+ project2.linked ? `Linked Vercel project ${pc6.cyan(project2.name)}` : `Using Vercel project ${pc6.cyan(project2.name)}`
24777
+ );
24778
+ projectId = project2.projectId;
24779
+ }
24780
+ const blob = await provisionBlobForMode(runner, options);
24781
+ if (blob.failure || !blob.token) {
24782
+ p16.log.warn(blobFailureMessage(blob.failure));
24783
+ if (blob.detail) p16.log.message(pc6.dim(blob.detail));
24784
+ return { ok: false };
24785
+ }
24786
+ return {
24787
+ ok: true,
24788
+ token: blob.token,
24789
+ vercelProjectId: projectId,
24790
+ blobStoreName: blob.storeName
24791
+ };
24792
+ } catch (error) {
24793
+ p16.log.warn(
24794
+ `Vercel provisioning failed: ${error instanceof Error ? error.message : String(error)}`
24212
24795
  );
24213
- return provisionNeonInteractive(runner, options.cwd, opts);
24796
+ return { ok: false };
24797
+ } finally {
24798
+ envGuard.restore();
24799
+ }
24800
+ }
24801
+ async function runVercelDeployFlow(options) {
24802
+ const env = options.env ?? process.env;
24803
+ const envGuard = guardEnvLocal(options.cwd);
24804
+ try {
24805
+ const runnerSpinner = spinner2();
24806
+ runnerSpinner.start("Checking for the Vercel CLI");
24807
+ const runner = await resolveVercelRunner();
24808
+ runnerSpinner.clear();
24809
+ const auth = await ensureVercelAuth(runner, options.cwd, { env, quietIfAuthed: true });
24810
+ if (!auth.authed) {
24811
+ p16.log.warn(authFailureMessage(auth.reason));
24812
+ printManualDeployHint();
24813
+ return { ok: false };
24814
+ }
24815
+ if (!readLinkedProjectId(options.cwd)) {
24816
+ const projectSpinner = spinner2();
24817
+ projectSpinner.start(`Creating a Vercel project ${pc6.cyan(options.projectName)}`);
24818
+ const project2 = await createVercelProject(runner, options.cwd, options.projectName, env);
24819
+ projectSpinner.stop(
24820
+ project2.linked ? `Linked Vercel project ${pc6.cyan(project2.name)}` : `Using Vercel project ${pc6.cyan(project2.name)}`
24821
+ );
24822
+ }
24823
+ const envSpinner = spinner2();
24824
+ envSpinner.start("Syncing environment variables to Vercel");
24825
+ const sync = await syncVercelProductionEnv(runner, options.cwd, env);
24826
+ if (sync.synced.length > 0) {
24827
+ envSpinner.stop(
24828
+ `Synced ${sync.synced.length} environment variable${sync.synced.length === 1 ? "" : "s"} to Vercel`
24829
+ );
24830
+ } else {
24831
+ envSpinner.clear();
24832
+ }
24833
+ for (const key of sync.failed) {
24834
+ p16.log.warn(
24835
+ `Could not set ${pc6.cyan(key)} on Vercel \u2014 add it in the project's environment settings.`
24836
+ );
24837
+ }
24838
+ if (ensureVercelJsonFramework(options.cwd) === "created") {
24839
+ p16.log.success(`Created ${pc6.cyan("vercel.json")} with the Next.js framework preset`);
24840
+ }
24841
+ const packageGuard = guardProjectForDeploy(options.cwd);
24842
+ for (const dep of packageGuard.localSpecDeps) {
24843
+ p16.log.warn(
24844
+ `Dependency ${pc6.cyan(dep)} uses a local spec that cannot install on Vercel \u2014 the remote build may fail.`
24845
+ );
24846
+ }
24847
+ const deploySpinner = spinner2();
24848
+ deploySpinner.start("Deploying to Vercel (this may take a few minutes)");
24849
+ let deploy;
24850
+ try {
24851
+ deploy = await deployVercelProject(runner, options.cwd, env);
24852
+ } finally {
24853
+ packageGuard.restore();
24854
+ }
24855
+ if (deploy.failure) {
24856
+ deploySpinner.stop(`${pc6.yellow("\u25B2")} ${deployFailureMessage(deploy.failure)}`);
24857
+ if (deploy.detail) p16.log.message(pc6.dim(deploy.detail));
24858
+ printManualDeployHint();
24859
+ return { ok: false };
24860
+ }
24861
+ const linkedName = readLinkedProjectName(options.cwd);
24862
+ const url = linkedName ? `https://${linkedName}.vercel.app` : deploy.url;
24863
+ deploySpinner.stop(url ? `Deployed ${pc6.cyan(url)}` : "Deployed to Vercel");
24864
+ return { ok: true, url, syncedEnvKeys: sync.synced };
24865
+ } catch (error) {
24866
+ p16.log.warn(`Vercel deploy failed: ${error instanceof Error ? error.message : String(error)}`);
24867
+ printManualDeployHint();
24868
+ return { ok: false };
24869
+ } finally {
24870
+ envGuard.restore();
24214
24871
  }
24215
- return provisionNeon(runner, options.cwd, opts);
24872
+ }
24873
+ function printManualDeployHint() {
24874
+ p16.log.info(`You can deploy manually: ${pc6.cyan("vercel deploy --prod")}`);
24875
+ }
24876
+ function provisionBlobForMode(runner, options) {
24877
+ const opts = { name: blobStoreName(options.projectName), env: options.env };
24878
+ return options.interactive ? provisionBlobStoreInteractive(runner, options.cwd, opts) : provisionBlobStore(runner, options.cwd, opts);
24879
+ }
24880
+ function provisionNeonForMode(runner, options) {
24881
+ const opts = { plan: options.plan, env: options.env };
24882
+ return options.interactive ? provisionNeonInteractive(runner, options.cwd, opts) : provisionNeon(runner, options.cwd, opts);
24216
24883
  }
24217
24884
  function authFailureMessage(reason) {
24218
24885
  switch (reason) {
@@ -24228,6 +24895,24 @@ function authFailureMessage(reason) {
24228
24895
  return "Could not sign in to Vercel.";
24229
24896
  }
24230
24897
  }
24898
+ function blobFailureMessage(reason) {
24899
+ switch (reason) {
24900
+ case "env-pull-empty":
24901
+ return "Created a Blob store, but no BLOB_READ_WRITE_TOKEN came back \u2014 falling back to manual entry.";
24902
+ case "timeout":
24903
+ return "Vercel Blob provisioning timed out \u2014 falling back to manual entry.";
24904
+ default:
24905
+ return "Vercel Blob provisioning did not complete \u2014 falling back to manual entry.";
24906
+ }
24907
+ }
24908
+ function deployFailureMessage(reason) {
24909
+ switch (reason) {
24910
+ case "timeout":
24911
+ return "Vercel deploy timed out.";
24912
+ default:
24913
+ return "Vercel deploy did not complete.";
24914
+ }
24915
+ }
24231
24916
  function neonFailureMessage(reason) {
24232
24917
  switch (reason) {
24233
24918
  case "terms-required":
@@ -24244,11 +24929,11 @@ function neonFailureMessage(reason) {
24244
24929
  }
24245
24930
 
24246
24931
  // adapters/next/commands/init.ts
24247
- import pc5 from "picocolors";
24932
+ import pc7 from "picocolors";
24248
24933
 
24249
24934
  // adapters/next/commands/seed.ts
24250
- import fs36 from "fs";
24251
- import path47 from "path";
24935
+ import fs38 from "fs";
24936
+ import path49 from "path";
24252
24937
  import * as clack from "@clack/prompts";
24253
24938
  function buildSeedScript(authBasePath = "/api/admin/auth") {
24254
24939
  return `/**
@@ -24416,7 +25101,7 @@ main().catch((err) => {
24416
25101
  `;
24417
25102
  }
24418
25103
  async function runSeedCommand(options) {
24419
- const cwd = options.cwd ? path47.resolve(options.cwd) : process.cwd();
25104
+ const cwd = options.cwd ? path49.resolve(options.cwd) : process.cwd();
24420
25105
  clack.intro("BetterStart Seed");
24421
25106
  let config;
24422
25107
  try {
@@ -24456,15 +25141,15 @@ async function runSeedCommand(options) {
24456
25141
  clack.cancel("Cancelled.");
24457
25142
  return;
24458
25143
  }
24459
- const scriptsDir = path47.join(cwd, adminDir, "scripts");
24460
- const seedPath = path47.join(scriptsDir, "seed.ts");
24461
- if (!fs36.existsSync(scriptsDir)) {
24462
- fs36.mkdirSync(scriptsDir, { recursive: true });
25144
+ const scriptsDir = path49.join(cwd, adminDir, "scripts");
25145
+ const seedPath = path49.join(scriptsDir, "seed.ts");
25146
+ if (!fs38.existsSync(scriptsDir)) {
25147
+ fs38.mkdirSync(scriptsDir, { recursive: true });
24463
25148
  }
24464
25149
  const namespace = resolveAdminNamespace(config.frameworkConfig.next.namespace);
24465
- fs36.writeFileSync(seedPath, buildSeedScript(`${namespace.apiPath}/auth`), "utf-8");
25150
+ fs38.writeFileSync(seedPath, buildSeedScript(`${namespace.apiPath}/auth`), "utf-8");
24466
25151
  const { execFile } = await import("child_process");
24467
- const tsxBin = path47.join(cwd, "node_modules", ".bin", "tsx");
25152
+ const tsxBin = path49.join(cwd, "node_modules", ".bin", "tsx");
24468
25153
  const runSeed2 = (overwrite) => new Promise((resolve, reject) => {
24469
25154
  execFile(
24470
25155
  tsxBin,
@@ -24490,45 +25175,45 @@ async function runSeedCommand(options) {
24490
25175
  }
24491
25176
  );
24492
25177
  });
24493
- const spinner9 = clack.spinner();
24494
- spinner9.start("Creating admin user...");
25178
+ const seedSpinner = spinner2();
25179
+ seedSpinner.start("Creating admin user...");
24495
25180
  try {
24496
25181
  const result = await runSeed2(false);
24497
25182
  if (result.code === 2) {
24498
25183
  const existingName = result.stdout.split("\n").find((l) => l.startsWith("EXISTING_USER:"))?.replace("EXISTING_USER:", "")?.trim() || "unknown";
24499
- spinner9.stop(`Account already exists for ${email}`);
25184
+ seedSpinner.stop(`Account already exists for ${email}`);
24500
25185
  const overwrite = await clack.confirm({
24501
25186
  message: `An admin account (${existingName}) already exists with this email. Replace it?`
24502
25187
  });
24503
25188
  if (clack.isCancel(overwrite) || !overwrite) {
24504
25189
  clack.cancel("Seed cancelled.");
24505
25190
  try {
24506
- fs36.unlinkSync(seedPath);
25191
+ fs38.unlinkSync(seedPath);
24507
25192
  } catch {
24508
25193
  }
24509
25194
  return;
24510
25195
  }
24511
- spinner9.start("Replacing admin user...");
25196
+ seedSpinner.start("Replacing admin user...");
24512
25197
  await runSeed2(true);
24513
- spinner9.stop("Admin user replaced");
25198
+ seedSpinner.stop("Admin user replaced");
24514
25199
  } else {
24515
- spinner9.stop("Admin user created");
25200
+ seedSpinner.stop("Admin user created");
24516
25201
  }
24517
25202
  } catch (err) {
24518
- spinner9.stop("Failed to create admin user");
25203
+ seedSpinner.stop("Failed to create admin user");
24519
25204
  const errMsg = err instanceof Error ? err.message : String(err);
24520
25205
  clack.log.error(errMsg);
24521
25206
  clack.log.info("You can run the seed script manually:");
24522
25207
  clack.log.info(
24523
- ` SEED_EMAIL="${email}" SEED_PASSWORD="..." npx tsx ${path47.relative(cwd, seedPath)}`
25208
+ ` SEED_EMAIL="${email}" SEED_PASSWORD="..." npx tsx ${path49.relative(cwd, seedPath)}`
24524
25209
  );
24525
25210
  clack.outro("");
24526
25211
  process.exit(1);
24527
25212
  }
24528
25213
  try {
24529
- fs36.unlinkSync(seedPath);
24530
- if (fs36.existsSync(scriptsDir) && fs36.readdirSync(scriptsDir).length === 0) {
24531
- fs36.rmdirSync(scriptsDir);
25214
+ fs38.unlinkSync(seedPath);
25215
+ if (fs38.existsSync(scriptsDir) && fs38.readdirSync(scriptsDir).length === 0) {
25216
+ fs38.rmdirSync(scriptsDir);
24532
25217
  }
24533
25218
  } catch {
24534
25219
  }
@@ -24549,6 +25234,18 @@ function readNamespaceFromConfigSource(source) {
24549
25234
  const match = source.match(/\bnamespace\s*:\s*(['"`])([^'"`]+)\1/);
24550
25235
  return normalizeNamespaceForRemoval(match?.[2]);
24551
25236
  }
25237
+ function formatStorageProviderLabel(provider) {
25238
+ switch (provider) {
25239
+ case "vercel-blob":
25240
+ return "Vercel Blob";
25241
+ case "r2":
25242
+ return "Cloudflare R2";
25243
+ case "local":
25244
+ return "local filesystem";
25245
+ default:
25246
+ return provider;
25247
+ }
25248
+ }
24552
25249
  function resolveNonInteractiveProject(name) {
24553
25250
  const projectName = name?.trim() || "my-app";
24554
25251
  if (projectName !== "." && !/^[a-z0-9_-]+$/i.test(projectName)) {
@@ -24557,11 +25254,11 @@ function resolveNonInteractiveProject(name) {
24557
25254
  return { projectName, useSrcDir: false };
24558
25255
  }
24559
25256
  async function readExistingConfigNamespace(cwd) {
24560
- const configPath = path48.resolve(cwd, "admin.config.ts");
24561
- if (!fs37.existsSync(configPath)) {
25257
+ const configPath = path50.resolve(cwd, "admin.config.ts");
25258
+ if (!fs39.existsSync(configPath)) {
24562
25259
  return;
24563
25260
  }
24564
- const sourceNamespace = readNamespaceFromConfigSource(fs37.readFileSync(configPath, "utf-8"));
25261
+ const sourceNamespace = readNamespaceFromConfigSource(fs39.readFileSync(configPath, "utf-8"));
24565
25262
  if (sourceNamespace) {
24566
25263
  return sourceNamespace;
24567
25264
  }
@@ -24574,7 +25271,7 @@ async function readExistingConfigNamespace(cwd) {
24574
25271
  }
24575
25272
  async function resolveForceInitNamespaces(cwd, targetNamespace) {
24576
25273
  const namespaces = /* @__PURE__ */ new Set([validateAdminNamespace(targetNamespace)]);
24577
- const hasConfig = fs37.existsSync(path48.resolve(cwd, "admin.config.ts"));
25274
+ const hasConfig = fs39.existsSync(path50.resolve(cwd, "admin.config.ts"));
24578
25275
  const existingNamespace = await readExistingConfigNamespace(cwd);
24579
25276
  if (existingNamespace) {
24580
25277
  namespaces.add(existingNamespace);
@@ -24599,39 +25296,51 @@ function removeExistingAdminPaths(cwd, namespaces) {
24599
25296
  const nukeFiles = ["admin.config.ts", "ADMIN.md", "drizzle.config.ts"];
24600
25297
  let removed = 0;
24601
25298
  for (const dir of nukeDirs) {
24602
- const fullPath = path48.resolve(cwd, dir);
24603
- if (fs37.existsSync(fullPath)) {
24604
- fs37.rmSync(fullPath, { recursive: true, force: true });
25299
+ const fullPath = path50.resolve(cwd, dir);
25300
+ if (fs39.existsSync(fullPath)) {
25301
+ fs39.rmSync(fullPath, { recursive: true, force: true });
24605
25302
  removed++;
24606
25303
  }
24607
25304
  }
24608
25305
  for (const file of nukeFiles) {
24609
- const fullPath = path48.resolve(cwd, file);
24610
- if (fs37.existsSync(fullPath)) {
24611
- fs37.unlinkSync(fullPath);
25306
+ const fullPath = path50.resolve(cwd, file);
25307
+ if (fs39.existsSync(fullPath)) {
25308
+ fs39.unlinkSync(fullPath);
24612
25309
  removed++;
24613
25310
  }
24614
25311
  }
24615
25312
  return removed;
24616
25313
  }
24617
25314
  async function runInitCommand(name, options) {
24618
- p15.intro(pc5.bgCyan(pc5.black(" Setup Your Dashboard ")));
25315
+ p17.box(
25316
+ `
25317
+ \u2584 \u2597 \u2597 \u2584\u2596\u2597 \u2597
25318
+ \u2599\u2598\u2588\u258C\u259C\u2598\u259C\u2598\u2588\u258C\u259B\u2598\u259A \u259C\u2598\u2580\u258C\u259B\u2598\u259C\u2598
25319
+ \u2599\u2598\u2599\u2596\u2590\u2596\u2590\u2596\u2599\u2596\u258C \u2584\u258C\u2590\u2596\u2588\u258C\u258C \u2590\u2596
25320
+ `,
25321
+ "Setup your Next.js Dashboard",
25322
+ {
25323
+ rounded: true,
25324
+ contentAlign: "center",
25325
+ titleAlign: "center"
25326
+ }
25327
+ );
24619
25328
  let cwd = process.cwd();
24620
- let projectName = path48.basename(cwd);
25329
+ let projectName = path50.basename(cwd);
24621
25330
  let forceMode = Boolean(options.force);
24622
25331
  let namespace = DEFAULT_ADMIN_NAMESPACE;
24623
25332
  if (options.namespace) {
24624
25333
  try {
24625
25334
  namespace = validateAdminNamespace(options.namespace);
24626
25335
  } catch (error) {
24627
- p15.log.error(error instanceof Error ? error.message : String(error));
25336
+ p17.log.error(error instanceof Error ? error.message : String(error));
24628
25337
  process.exit(1);
24629
25338
  }
24630
25339
  }
24631
25340
  if (!options.yes && !options.namespace) {
24632
25341
  const defaultDashboardPath = resolveAdminNamespace(DEFAULT_ADMIN_NAMESPACE).routePath;
24633
- const namespaceInput = await p15.text({
24634
- message: "What's the path you want the dashboard to be?",
25342
+ const namespaceInput = await p17.text({
25343
+ message: "Enter the dashboard path",
24635
25344
  placeholder: `eg. ${defaultDashboardPath}`,
24636
25345
  defaultValue: defaultDashboardPath,
24637
25346
  validate(value) {
@@ -24643,8 +25352,8 @@ async function runInitCommand(name, options) {
24643
25352
  }
24644
25353
  }
24645
25354
  });
24646
- if (p15.isCancel(namespaceInput)) {
24647
- p15.cancel("Setup cancelled.");
25355
+ if (p17.isCancel(namespaceInput)) {
25356
+ p17.cancel("Setup cancelled.");
24648
25357
  process.exit(0);
24649
25358
  }
24650
25359
  namespace = validateAdminDashboardPath(namespaceInput);
@@ -24654,36 +25363,35 @@ async function runInitCommand(name, options) {
24654
25363
  let isFreshProject = false;
24655
25364
  let srcDir;
24656
25365
  if (project2.isExisting) {
24657
- p15.log.info(`Next.js app detected ${pc5.dim("\xB7")} ${pc5.cyan(pm)}`);
24658
25366
  srcDir = project2.hasSrcDir;
24659
25367
  if (!project2.hasTypeScript) {
24660
- p15.log.error("TypeScript is required. Please add a tsconfig.json first.");
25368
+ p17.log.error("TypeScript is required. Please add a tsconfig.json first.");
24661
25369
  process.exit(1);
24662
25370
  }
24663
25371
  if (forceMode) {
24664
25372
  const nuked = removeExistingAdminPaths(cwd, await resolveForceInitNamespaces(cwd, namespace));
24665
25373
  if (nuked > 0) {
24666
- p15.log.warn(`${pc5.yellow("Force mode:")} removed ${nuked} existing admin paths`);
25374
+ p17.log.warn(`${pc7.yellow("Force mode:")} removed ${nuked} existing admin paths`);
24667
25375
  }
24668
25376
  project2 = detectProject(cwd, namespace);
24669
25377
  } else if (project2.conflicts.length > 0) {
24670
- const conflictLines = project2.conflicts.map((c) => `${pc5.yellow("\u25B2")} ${c}`);
25378
+ const conflictLines = project2.conflicts.map((c) => `${pc7.yellow("\u25B2")} ${c}`);
24671
25379
  conflictLines.push(
24672
25380
  "",
24673
- pc5.dim(`Use ${pc5.bold("--force")} to remove existing admin files before scaffolding.`)
25381
+ pc7.dim(`Use ${pc7.bold("--force")} to remove existing admin files before scaffolding.`)
24674
25382
  );
24675
- p15.note(conflictLines.join("\n"), pc5.yellow("Conflicts"));
25383
+ p17.note(conflictLines.join("\n"), pc7.yellow("Conflicts"));
24676
25384
  if (!options.yes) {
24677
- const proceed = await p15.confirm({
25385
+ const proceed = await p17.confirm({
24678
25386
  message: [
24679
- `Continue with ${pc5.bold(pc5.cyan("--force"))}?`,
24680
- `${pc5.cyan("\u2502")} ${pc5.dim("This will force overwrite the existing admin code.")}`,
24681
- pc5.cyan("\u2502")
25387
+ `Continue with ${pc7.bold(pc7.cyan("--force"))}?`,
25388
+ `${pc7.cyan("\u2502")} ${pc7.dim("This will force overwrite the existing admin code.")}`,
25389
+ pc7.cyan("\u2502")
24682
25390
  ].join("\n"),
24683
25391
  initialValue: true
24684
25392
  });
24685
- if (p15.isCancel(proceed) || !proceed) {
24686
- p15.cancel("Setup cancelled.");
25393
+ if (p17.isCancel(proceed) || !proceed) {
25394
+ p17.cancel("Setup cancelled.");
24687
25395
  process.exit(0);
24688
25396
  }
24689
25397
  forceMode = true;
@@ -24692,23 +25400,23 @@ async function runInitCommand(name, options) {
24692
25400
  await resolveForceInitNamespaces(cwd, namespace)
24693
25401
  );
24694
25402
  if (nuked > 0) {
24695
- p15.log.warn(`${pc5.yellow("Force mode:")} removed ${nuked} existing admin paths`);
25403
+ p17.log.warn(`${pc7.yellow("Force mode:")} removed ${nuked} existing admin paths`);
24696
25404
  }
24697
25405
  project2 = detectProject(cwd, namespace);
24698
25406
  }
24699
25407
  }
24700
25408
  } else {
24701
- p15.log.info("No Next.js app found \u2014 Running the fresh project mode...");
25409
+ p17.log.info("No Next.js app found \u2014 Running the fresh project mode...");
24702
25410
  let projectPrompt;
24703
25411
  try {
24704
25412
  projectPrompt = options.yes ? resolveNonInteractiveProject(name) : await promptProject(name);
24705
25413
  } catch (error) {
24706
- p15.log.error(error instanceof Error ? error.message : String(error));
25414
+ p17.log.error(error instanceof Error ? error.message : String(error));
24707
25415
  process.exit(1);
24708
25416
  }
24709
25417
  srcDir = projectPrompt.useSrcDir;
24710
25418
  if (!options.yes) {
24711
- const pmChoice = await p15.select({
25419
+ const pmChoice = await p17.select({
24712
25420
  message: "Which package manager do you want to use?",
24713
25421
  options: [
24714
25422
  { value: "pnpm", label: "pnpm", hint: "recommended" },
@@ -24717,13 +25425,13 @@ async function runInitCommand(name, options) {
24717
25425
  { value: "bun", label: "bun" }
24718
25426
  ]
24719
25427
  });
24720
- if (p15.isCancel(pmChoice)) {
24721
- p15.cancel("Setup cancelled.");
25428
+ if (p17.isCancel(pmChoice)) {
25429
+ p17.cancel("Setup cancelled.");
24722
25430
  process.exit(0);
24723
25431
  }
24724
25432
  pm = pmChoice;
24725
25433
  }
24726
- const displayName = projectPrompt.projectName === "." ? path48.basename(cwd) : projectPrompt.projectName;
25434
+ const displayName = projectPrompt.projectName === "." ? path50.basename(cwd) : projectPrompt.projectName;
24727
25435
  projectName = displayName;
24728
25436
  const { bin, prefix } = createNextAppCommand(pm);
24729
25437
  const cnaArgs = [
@@ -24740,7 +25448,7 @@ async function runInitCommand(name, options) {
24740
25448
  ];
24741
25449
  if (srcDir) cnaArgs.push("--src-dir");
24742
25450
  else cnaArgs.push("--no-src-dir");
24743
- const createNextAppSpinner = p15.spinner();
25451
+ const createNextAppSpinner = spinner2();
24744
25452
  createNextAppSpinner.start(`Creating a Next.js app (latest)`);
24745
25453
  const createNextAppResult = await runQuietCommand(bin, cnaArgs, {
24746
25454
  cwd,
@@ -24752,79 +25460,45 @@ async function runInitCommand(name, options) {
24752
25460
  process.stderr.write(`${createNextAppResult.output.trimEnd()}
24753
25461
  `);
24754
25462
  }
24755
- p15.log.error(createNextAppResult.error);
24756
- p15.log.info(
25463
+ p17.log.error(createNextAppResult.error);
25464
+ p17.log.info(
24757
25465
  `You can create the project manually:
24758
- ${pc5.cyan(`npx create-next-app@latest ${projectPrompt.projectName} --typescript --tailwind --app`)}
24759
- Then run ${pc5.cyan("betterstart init")} inside it.`
25466
+ ${pc7.cyan(`npx create-next-app@latest ${projectPrompt.projectName} --typescript --tailwind --app`)}
25467
+ Then run ${pc7.cyan("betterstart init")} inside it.`
24760
25468
  );
24761
25469
  process.exit(1);
24762
25470
  }
24763
- cwd = path48.resolve(cwd, projectPrompt.projectName);
24764
- const hasPackageJson = fs37.existsSync(path48.join(cwd, "package.json"));
25471
+ cwd = path50.resolve(cwd, projectPrompt.projectName);
25472
+ const hasPackageJson = fs39.existsSync(path50.join(cwd, "package.json"));
24765
25473
  const hasNextConfig = ["next.config.ts", "next.config.js", "next.config.mjs"].some(
24766
- (f) => fs37.existsSync(path48.join(cwd, f))
25474
+ (f) => fs39.existsSync(path50.join(cwd, f))
24767
25475
  );
24768
25476
  if (!hasPackageJson || !hasNextConfig) {
24769
25477
  createNextAppSpinner.stop(`Failed to create Next.js app: ${displayName}`);
24770
- p15.log.error(
25478
+ p17.log.error(
24771
25479
  "create-next-app completed but the project was not created. This can happen with nested npx calls."
24772
25480
  );
24773
25481
  const manualCmd = `npx create-next-app@latest ${projectPrompt.projectName} --typescript --tailwind --app`;
24774
- p15.log.info(
25482
+ p17.log.info(
24775
25483
  `Create the project manually:
24776
- ${pc5.cyan(manualCmd)}
24777
- Then run ${pc5.cyan("betterstart init")} inside it.`
25484
+ ${pc7.cyan(manualCmd)}
25485
+ Then run ${pc7.cyan("betterstart init")} inside it.`
24778
25486
  );
24779
25487
  process.exit(1);
24780
25488
  }
24781
25489
  const installedNextVersion = detectInstalledNextVersion(cwd);
24782
- const nextVersionSuffix = installedNextVersion ? `${pc5.cyan(`v${installedNextVersion}`)}` : "";
25490
+ const nextVersionSuffix = installedNextVersion ? `${pc7.cyan(`v${installedNextVersion}`)}` : "";
24783
25491
  createNextAppSpinner.stop(`Created a Next.js ${nextVersionSuffix} app in ${displayName}`);
24784
25492
  project2 = detectProject(cwd, namespace);
24785
25493
  isFreshProject = true;
24786
25494
  }
24787
- const selectedPlugins = (() => {
24788
- if (options.plugins) {
24789
- return parsePluginList(options.plugins);
24790
- }
24791
- return [];
24792
- })();
24793
- const selectedIntegrations = (() => {
24794
- if (options.integrations) {
24795
- return parseIntegrationList(options.integrations);
24796
- }
24797
- return [];
24798
- })();
24799
- let collectedIntegrationConfig = {
24800
- sections: [],
24801
- overwriteKeys: /* @__PURE__ */ new Set()
24802
- };
24803
- let pluginSelection;
24804
- if (selectedPlugins.length > 0 || selectedIntegrations.length > 0) {
24805
- pluginSelection = {
24806
- plugins: selectedPlugins,
24807
- integrations: selectedIntegrations,
24808
- storage: selectedIntegrations.includes("r2") ? "r2" : "local"
24809
- };
24810
- } else if (options.yes) {
24811
- pluginSelection = { plugins: [], integrations: [], storage: "local" };
24812
- } else {
24813
- const promptResult = await promptPlugins(cwd);
24814
- collectedIntegrationConfig = promptResult.integrationConfig;
24815
- pluginSelection = {
24816
- plugins: promptResult.plugins,
24817
- integrations: promptResult.integrations,
24818
- storage: promptResult.storage
24819
- };
24820
- }
24821
25495
  let databaseUrl;
24822
25496
  const existingDbUrl = readExistingDbUrl(cwd);
24823
25497
  if (options.yes) {
24824
25498
  if (options.databaseUrl) {
24825
25499
  if (!isValidDbUrl(options.databaseUrl)) {
24826
- p15.log.error(
24827
- `Invalid database URL. Must start with ${pc5.cyan("postgres://")} or ${pc5.cyan("postgresql://")}`
25500
+ p17.log.error(
25501
+ `Invalid database URL. Must start with ${pc7.cyan("postgres://")} or ${pc7.cyan("postgresql://")}`
24828
25502
  );
24829
25503
  process.exit(1);
24830
25504
  }
@@ -24841,17 +25515,19 @@ async function runInitCommand(name, options) {
24841
25515
  });
24842
25516
  if (flow.ok && flow.databaseUrl) {
24843
25517
  databaseUrl = flow.databaseUrl;
25518
+ persistDatabaseUrl(cwd, databaseUrl);
25519
+ p17.log.success(`Saved DATABASE_URL to ${pc7.cyan(".env.local")}`);
24844
25520
  } else {
24845
- p15.log.warn("Vercel provisioning did not complete; continuing without a database URL.");
25521
+ p17.log.warn("Vercel provisioning did not complete; continuing without a database URL.");
24846
25522
  }
24847
25523
  }
24848
25524
  } else if (existingDbUrl) {
24849
25525
  const masked = maskDbUrl(existingDbUrl);
24850
- p15.log.info(`Using existing database URL from .env.local ${pc5.dim(`(${masked})`)}`);
25526
+ p17.log.info(`Using existing database URL from .env.local ${pc7.dim(`(${masked})`)}`);
24851
25527
  databaseUrl = existingDbUrl;
24852
25528
  } else {
24853
- const dbResult = await promptDatabase({ allowVercel: options.vercel !== false });
24854
- if (dbResult.provider === "vercel-cli") {
25529
+ const servicesResult = await promptServices({ allowVercel: options.vercel !== false });
25530
+ if (servicesResult.provider === "vercel-cli") {
24855
25531
  const flow = await runVercelNeonFlow({
24856
25532
  cwd,
24857
25533
  projectName,
@@ -24861,14 +25537,77 @@ async function runInitCommand(name, options) {
24861
25537
  });
24862
25538
  if (flow.ok && flow.databaseUrl) {
24863
25539
  databaseUrl = flow.databaseUrl;
25540
+ persistDatabaseUrl(cwd, databaseUrl);
25541
+ p17.log.success(`Saved DATABASE_URL to ${pc7.cyan(".env.local")}`);
24864
25542
  } else {
24865
- p15.log.info("Falling back to a manual database connection string.");
25543
+ p17.log.info("Falling back to a manual database connection string.");
24866
25544
  openBrowserVercelNeon();
24867
25545
  databaseUrl = await promptConnectionString();
24868
25546
  }
24869
25547
  } else {
24870
- databaseUrl = dbResult.url;
25548
+ databaseUrl = servicesResult.url;
25549
+ }
25550
+ }
25551
+ const selectedPlugins = (() => {
25552
+ if (options.plugins) {
25553
+ return parsePluginList(options.plugins);
25554
+ }
25555
+ return [];
25556
+ })();
25557
+ const selectedIntegrations = (() => {
25558
+ if (options.integrations) {
25559
+ return parseIntegrationList(options.integrations);
25560
+ }
25561
+ return [];
25562
+ })();
25563
+ let collectedIntegrationConfig = {
25564
+ sections: [],
25565
+ overwriteKeys: /* @__PURE__ */ new Set()
25566
+ };
25567
+ let pluginSelection;
25568
+ const vercelBlobAllowed = options.vercel !== false;
25569
+ if (selectedPlugins.length > 0 || selectedIntegrations.length > 0) {
25570
+ pluginSelection = {
25571
+ plugins: selectedPlugins,
25572
+ integrations: selectedIntegrations,
25573
+ storage: selectedIntegrations.includes("vercel-blob") ? "vercel-blob" : selectedIntegrations.includes("r2") ? "r2" : "local"
25574
+ };
25575
+ if (pluginSelection.storage === "vercel-blob" && !readEnvVar(cwd, "BLOB_READ_WRITE_TOKEN")?.trim() && vercelBlobAllowed && (!options.yes || process.env.VERCEL_TOKEN)) {
25576
+ const flow = await runVercelBlobFlow({
25577
+ cwd,
25578
+ projectName,
25579
+ interactive: !options.yes,
25580
+ env: process.env
25581
+ });
25582
+ if (flow.ok && flow.token) {
25583
+ collectedIntegrationConfig.sections.push({
25584
+ header: "Storage (Vercel Blob)",
25585
+ vars: [{ key: "BLOB_READ_WRITE_TOKEN", value: flow.token }]
25586
+ });
25587
+ collectedIntegrationConfig.overwriteKeys.add("BLOB_READ_WRITE_TOKEN");
25588
+ } else if (!options.yes) {
25589
+ p17.log.info("Falling back to a manual Vercel Blob token.");
25590
+ const collected = await collectIntegrationConfig(cwd, ["vercel-blob"]);
25591
+ collectedIntegrationConfig.sections.push(...collected.sections);
25592
+ for (const key of collected.overwriteKeys) {
25593
+ collectedIntegrationConfig.overwriteKeys.add(key);
25594
+ }
25595
+ } else {
25596
+ p17.log.warn("Vercel Blob provisioning did not complete; continuing without a Blob token.");
25597
+ }
24871
25598
  }
25599
+ } else if (options.yes) {
25600
+ pluginSelection = { plugins: [], integrations: [], storage: "local" };
25601
+ } else {
25602
+ const promptResult = await promptPlugins(cwd, {
25603
+ provisionVercelBlob: vercelBlobAllowed ? () => runVercelBlobFlow({ cwd, projectName, interactive: true, env: process.env }) : void 0
25604
+ });
25605
+ collectedIntegrationConfig = promptResult.integrationConfig;
25606
+ pluginSelection = {
25607
+ plugins: promptResult.plugins,
25608
+ integrations: promptResult.integrations,
25609
+ storage: promptResult.storage
25610
+ };
24872
25611
  }
24873
25612
  const nextMajorVersion = detectNextMajorVersion(cwd);
24874
25613
  const config = {
@@ -24879,7 +25618,7 @@ async function runInitCommand(name, options) {
24879
25618
  config.paths = config.frameworkConfig.next.paths;
24880
25619
  config.database.migrationsDir = deriveMigrationsDir(namespace);
24881
25620
  const results = [];
24882
- const s = p15.spinner();
25621
+ const s = spinner2();
24883
25622
  s.start("Directory structure");
24884
25623
  const baseFiles = scaffoldBase({
24885
25624
  cwd,
@@ -24952,34 +25691,34 @@ async function runInitCommand(name, options) {
24952
25691
  const maxLabel = Math.max(...results.map((r) => r.label.length));
24953
25692
  const noteLines = results.map((r) => {
24954
25693
  const padded = r.label.padEnd(maxLabel + 3);
24955
- return `${pc5.green("\u2713")} ${padded}${pc5.dim(r.result)}`;
25694
+ return `${pc7.green("\u2713")} ${padded}${pc7.dim(r.result)}`;
24956
25695
  });
24957
25696
  s.stop("");
24958
25697
  process.stdout.write("\x1B[2A\x1B[J");
24959
- p15.note(noteLines.join("\n"), "Scaffolded admin");
24960
- const drizzleConfigPath = path48.join(cwd, "drizzle.config.ts");
24961
- if (!dbFiles.includes("drizzle.config.ts") && fs37.existsSync(drizzleConfigPath)) {
25698
+ p17.note(noteLines.join("\n"), "Scaffolded admin");
25699
+ const drizzleConfigPath = path50.join(cwd, "drizzle.config.ts");
25700
+ if (!dbFiles.includes("drizzle.config.ts") && fs39.existsSync(drizzleConfigPath)) {
24962
25701
  if (forceMode) {
24963
25702
  const { readNamespacedTemplate } = await import("./template-reader-X6KFP7B3.js");
24964
- fs37.writeFileSync(
25703
+ fs39.writeFileSync(
24965
25704
  drizzleConfigPath,
24966
25705
  readNamespacedTemplate("drizzle.config.ts", namespace),
24967
25706
  "utf-8"
24968
25707
  );
24969
- p15.log.success("Updated drizzle.config.ts");
25708
+ p17.log.success("Updated drizzle.config.ts");
24970
25709
  } else if (!options.yes) {
24971
- const overwrite = await p15.confirm({
25710
+ const overwrite = await p17.confirm({
24972
25711
  message: "drizzle.config.ts already exists. Overwrite with latest version?",
24973
25712
  initialValue: true
24974
25713
  });
24975
- if (!p15.isCancel(overwrite) && overwrite) {
25714
+ if (!p17.isCancel(overwrite) && overwrite) {
24976
25715
  const { readNamespacedTemplate } = await import("./template-reader-X6KFP7B3.js");
24977
- fs37.writeFileSync(
25716
+ fs39.writeFileSync(
24978
25717
  drizzleConfigPath,
24979
25718
  readNamespacedTemplate("drizzle.config.ts", namespace),
24980
25719
  "utf-8"
24981
25720
  );
24982
- p15.log.success("Updated drizzle.config.ts");
25721
+ p17.log.success("Updated drizzle.config.ts");
24983
25722
  }
24984
25723
  }
24985
25724
  }
@@ -25005,11 +25744,11 @@ async function runInitCommand(name, options) {
25005
25744
  depsInstalled = true;
25006
25745
  } else {
25007
25746
  s.stop("Failed to install dependencies");
25008
- p15.log.warning(depsResult.error ?? "Unknown error");
25009
- p15.log.info(
25747
+ p17.log.warning(depsResult.error ?? "Unknown error");
25748
+ p17.log.info(
25010
25749
  `You can install them manually:
25011
- ${pc5.cyan(`${pm} add ${depsResult.dependencies.join(" ")}`)}
25012
- ${pc5.cyan(`${pm} add -D ${depsResult.devDeps.join(" ")}`)}`
25750
+ ${pc7.cyan(`${pm} add ${depsResult.dependencies.join(" ")}`)}
25751
+ ${pc7.cyan(`${pm} add -D ${depsResult.devDeps.join(" ")}`)}`
25013
25752
  );
25014
25753
  }
25015
25754
  if (depsInstalled) {
@@ -25059,46 +25798,46 @@ async function runInitCommand(name, options) {
25059
25798
  process.stdout.write("\x1B[2A\x1B[J");
25060
25799
  }
25061
25800
  writeConfigFile(cwd, resolvedIntegrationInstallResult.config);
25062
- const usesLocalStorage = resolvedIntegrationInstallResult.config.storage.provider !== "r2";
25801
+ const usesLocalStorage = resolvedIntegrationInstallResult.config.storage.provider === "local";
25063
25802
  const installLines = [];
25064
25803
  if (depsInstalled) {
25065
25804
  installLines.push(
25066
- `${pc5.green("\u2713")} Dependencies ${pc5.dim(`${depsResult.dependencies.length} deps + ${depsResult.devDeps.length} dev deps`)}`
25805
+ `${pc7.green("\u2713")} Dependencies ${pc7.dim(`${depsResult.dependencies.length} deps + ${depsResult.devDeps.length} dev deps`)}`
25067
25806
  );
25068
25807
  }
25069
25808
  if (coreSchemasResult.errors.length > 0) {
25070
25809
  installLines.push(
25071
- `${pc5.yellow("\u25B2")} Core schemas ${pc5.dim(`${coreSchemasResult.errors.length} warning(s)`)}`
25810
+ `${pc7.yellow("\u25B2")} Core schemas ${pc7.dim(`${coreSchemasResult.errors.length} warning(s)`)}`
25072
25811
  );
25073
25812
  for (const err of coreSchemasResult.errors) {
25074
- installLines.push(` ${pc5.dim(err)}`);
25813
+ installLines.push(` ${pc7.dim(err)}`);
25075
25814
  }
25076
25815
  } else {
25077
25816
  installLines.push(
25078
- `${pc5.green("\u2713")} Core schemas ${pc5.dim(`${coreSchemasResult.schemas.length} schemas, ${coreSchemasResult.generatedFiles.length} files`)}`
25817
+ `${pc7.green("\u2713")} Core schemas ${pc7.dim(`${coreSchemasResult.schemas.length} schemas, ${coreSchemasResult.generatedFiles.length} files`)}`
25079
25818
  );
25080
25819
  }
25081
25820
  installLines.push(
25082
- `${pc5.green("\u2713")} Plugins ${pc5.dim(resolvedPluginInstallResult.installed.length > 0 ? resolvedPluginInstallResult.installed.join(", ") : "core only")}`
25821
+ `${pc7.green("\u2713")} Plugins ${pc7.dim(resolvedPluginInstallResult.installed.length > 0 ? resolvedPluginInstallResult.installed.join(", ") : "core only")}`
25083
25822
  );
25084
25823
  for (const warning of resolvedPluginInstallResult.warnings) {
25085
- installLines.push(` ${pc5.yellow("\u25B2")} ${warning}`);
25824
+ installLines.push(` ${pc7.yellow("\u25B2")} ${warning}`);
25086
25825
  }
25087
25826
  installLines.push(
25088
- `${pc5.green("\u2713")} Integrations ${pc5.dim(resolvedIntegrationInstallResult.installed.length > 0 ? resolvedIntegrationInstallResult.installed.join(", ") : "none")}`
25827
+ `${pc7.green("\u2713")} Integrations ${pc7.dim(resolvedIntegrationInstallResult.installed.length > 0 ? resolvedIntegrationInstallResult.installed.join(", ") : "none")}`
25089
25828
  );
25090
25829
  for (const warning of resolvedIntegrationInstallResult.warnings) {
25091
- installLines.push(` ${pc5.yellow("\u25B2")} ${warning}`);
25830
+ installLines.push(` ${pc7.yellow("\u25B2")} ${warning}`);
25092
25831
  }
25093
25832
  if (usesLocalStorage) {
25094
25833
  installLines.push(
25095
- ` ${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.`
25834
+ ` ${pc7.yellow("\u25B2")} Local filesystem storage is only for development or self-hosted persistent-disk deployments. Use Vercel Blob or Cloudflare R2 for hosted/serverless production.`
25096
25835
  );
25097
25836
  }
25098
- p15.note(installLines.join("\n"), "Installed");
25837
+ p17.note(installLines.join("\n"), "Installed");
25099
25838
  let dbPushed = false;
25100
25839
  if (depsResult.success && options.skipMigration && hasDbUrl(cwd)) {
25101
- p15.log.info(`Skipping database schema push ${pc5.dim("(--skip-migration)")}`);
25840
+ p17.log.info(`Skipping database schema push ${pc7.dim("(--skip-migration)")}`);
25102
25841
  } else if (depsResult.success && hasDbUrl(cwd)) {
25103
25842
  let driverReady = hasDrizzleKitPostgresDriverDependency(cwd);
25104
25843
  if (!driverReady) {
@@ -25113,19 +25852,19 @@ async function runInitCommand(name, options) {
25113
25852
  });
25114
25853
  if (!driverResult.success) {
25115
25854
  s.stop("Database push failed");
25116
- p15.log.warning(driverResult.error ?? `Failed to install ${DRIZZLE_KIT_POSTGRES_DRIVER_DEP}`);
25117
- p15.log.info(
25118
- `Install ${DRIZZLE_KIT_POSTGRES_DRIVER_DEP} manually, then run: ${pc5.cyan(drizzlePushCommand(pm))}`
25855
+ p17.log.warning(driverResult.error ?? `Failed to install ${DRIZZLE_KIT_POSTGRES_DRIVER_DEP}`);
25856
+ p17.log.info(
25857
+ `Install ${DRIZZLE_KIT_POSTGRES_DRIVER_DEP} manually, then run: ${pc7.cyan(drizzlePushCommand(pm))}`
25119
25858
  );
25120
25859
  } else {
25121
25860
  driverReady = hasDrizzleKitPostgresDriverDependency(cwd);
25122
- s.stop(`${pc5.green("\u2713")} Drizzle Kit Postgres driver dependency installed`);
25861
+ s.stop(`${pc7.green("\u2713")} Drizzle Kit Postgres driver dependency installed`);
25123
25862
  }
25124
25863
  }
25125
25864
  if (driverReady) {
25126
25865
  const useTerminalForDbPush = shouldUseTerminalForDbPush(options);
25127
25866
  if (useTerminalForDbPush) {
25128
- p15.log.info(`Running ${pc5.cyan("drizzle-kit push --force")}`);
25867
+ p17.log.info(`Running ${pc7.cyan("drizzle-kit push --force")}`);
25129
25868
  } else {
25130
25869
  s.start("Pushing database schema (drizzle-kit push)");
25131
25870
  }
@@ -25134,15 +25873,15 @@ async function runInitCommand(name, options) {
25134
25873
  if (useTerminalForDbPush) {
25135
25874
  const verification = await verifyDatabaseReachable(cwd);
25136
25875
  if (!verification.success) {
25137
- p15.log.warning(verification.error);
25138
- p15.log.error("Database was not reachable. Aborting setup.");
25876
+ p17.log.warning(verification.error);
25877
+ p17.log.error("Database was not reachable. Aborting setup.");
25139
25878
  process.exit(1);
25140
25879
  }
25141
25880
  }
25142
25881
  if (useTerminalForDbPush) {
25143
- p15.log.success("Database schema pushed");
25882
+ p17.log.success("Database schema pushed");
25144
25883
  } else {
25145
- s.stop(`${pc5.green("\u2713")} Database schema pushed`);
25884
+ s.stop(`${pc7.green("\u2713")} Database schema pushed`);
25146
25885
  }
25147
25886
  dbPushed = true;
25148
25887
  } else {
@@ -25150,12 +25889,12 @@ async function runInitCommand(name, options) {
25150
25889
  s.stop("Database push failed");
25151
25890
  }
25152
25891
  const pushError = pushResult.error ?? "Unknown error";
25153
- p15.log.warning(pushError);
25892
+ p17.log.warning(pushError);
25154
25893
  if (isDatabaseReachabilityError(pushError)) {
25155
- p15.log.error("Database was not reachable. Aborting setup.");
25894
+ p17.log.error("Database was not reachable. Aborting setup.");
25156
25895
  process.exit(1);
25157
25896
  }
25158
- p15.log.info(`You can run it manually: ${pc5.cyan(drizzlePushCommand(pm))}`);
25897
+ p17.log.info(`You can run it manually: ${pc7.cyan(drizzlePushCommand(pm))}`);
25159
25898
  }
25160
25899
  }
25161
25900
  }
@@ -25164,8 +25903,8 @@ async function runInitCommand(name, options) {
25164
25903
  let seedSuccess = false;
25165
25904
  let adminAccountReady = false;
25166
25905
  if (dbPushed && options.skipAdminCreation) {
25167
- p15.log.info(
25168
- `Skipping admin user creation ${pc5.dim(`(use ${betterstartExecCommand(pm, "seed")} later)`)}`
25906
+ p17.log.info(
25907
+ `Skipping admin user creation ${pc7.dim(`(use ${betterstartExecCommand(pm, "seed")} later)`)}`
25169
25908
  );
25170
25909
  }
25171
25910
  if (dbPushed && !options.yes && !options.skipAdminCreation) {
@@ -25174,14 +25913,14 @@ async function runInitCommand(name, options) {
25174
25913
  let replaceExistingAdmin = false;
25175
25914
  const adminCheck = await checkExistingAdmin(cwd, adminDir, authBasePath);
25176
25915
  if (adminCheck.error) {
25177
- p15.log.warning(`Could not verify existing admin account ${pc5.dim(`(${adminCheck.error})`)}`);
25916
+ p17.log.warning(`Could not verify existing admin account ${pc7.dim(`(${adminCheck.error})`)}`);
25178
25917
  if (isDatabaseReachabilityError(adminCheck.error)) {
25179
- p15.log.error("Database was not reachable. Aborting setup.");
25918
+ p17.log.error("Database was not reachable. Aborting setup.");
25180
25919
  process.exit(1);
25181
25920
  }
25182
25921
  } else if (adminCheck.existingAdmin) {
25183
25922
  const existingAdminLabel = formatAdminIdentity(adminCheck.existingAdmin);
25184
- const adminAction = await p15.select({
25923
+ const adminAction = await p17.select({
25185
25924
  message: "Found an already existing admin account. Do you want to replace it or skip?",
25186
25925
  options: [
25187
25926
  {
@@ -25195,34 +25934,34 @@ async function runInitCommand(name, options) {
25195
25934
  }
25196
25935
  ]
25197
25936
  });
25198
- if (p15.isCancel(adminAction)) {
25199
- p15.cancel("Setup cancelled.");
25937
+ if (p17.isCancel(adminAction)) {
25938
+ p17.cancel("Setup cancelled.");
25200
25939
  process.exit(0);
25201
25940
  }
25202
25941
  if (adminAction === "skip") {
25203
25942
  adminAccountReady = true;
25204
- p15.log.info(`Keeping existing admin account ${pc5.dim(`(${existingAdminLabel})`)}`);
25943
+ p17.log.info(`Keeping existing admin account ${pc7.dim(`(${existingAdminLabel})`)}`);
25205
25944
  } else {
25206
25945
  replaceExistingAdmin = true;
25207
25946
  }
25208
25947
  }
25209
25948
  if (!adminAccountReady) {
25210
- p15.note(
25211
- pc5.dim(
25212
- replaceExistingAdmin ? "Replace the existing admin account to access the admin." : "Create your first admin user to access the admin."
25949
+ p17.note(
25950
+ pc7.dim(
25951
+ replaceExistingAdmin ? "Replace the existing admin account to access the admin." : "Create your first admin user to access the dashboard."
25213
25952
  ),
25214
25953
  "Admin account"
25215
25954
  );
25216
- const credentials = await p15.group(
25955
+ const credentials = await p17.group(
25217
25956
  {
25218
- email: () => p15.text({
25957
+ email: () => p17.text({
25219
25958
  message: "Admin email",
25220
25959
  placeholder: "admin@example.com",
25221
25960
  validate: (v) => {
25222
25961
  if (!v || !v.includes("@")) return "Please enter a valid email";
25223
25962
  }
25224
25963
  }),
25225
- password: () => p15.password({
25964
+ password: () => p17.password({
25226
25965
  message: "Admin password",
25227
25966
  validate: (v) => {
25228
25967
  if (!v || v.length < 8) return "Password must be at least 8 characters";
@@ -25231,7 +25970,7 @@ async function runInitCommand(name, options) {
25231
25970
  },
25232
25971
  {
25233
25972
  onCancel: () => {
25234
- p15.cancel("Setup cancelled.");
25973
+ p17.cancel("Setup cancelled.");
25235
25974
  process.exit(0);
25236
25975
  }
25237
25976
  }
@@ -25249,12 +25988,12 @@ async function runInitCommand(name, options) {
25249
25988
  seedOverwriteMode
25250
25989
  );
25251
25990
  if (seedResult.existingUser) {
25252
- s.stop(`${pc5.yellow("\u25B2")} An account already exists for ${credentials.email}`);
25253
- const replace = await p15.confirm({
25991
+ s.stop(`${pc7.yellow("\u25B2")} An account already exists for ${credentials.email}`);
25992
+ const replace = await p17.confirm({
25254
25993
  message: "Replace the existing account with this email?",
25255
25994
  initialValue: false
25256
25995
  });
25257
- if (!p15.isCancel(replace) && replace) {
25996
+ if (!p17.isCancel(replace) && replace) {
25258
25997
  seedOverwriteMode = "email";
25259
25998
  s.start("Replacing admin user");
25260
25999
  seedResult = await runSeed(
@@ -25269,20 +26008,20 @@ async function runInitCommand(name, options) {
25269
26008
  }
25270
26009
  if (seedResult.success) {
25271
26010
  s.stop(
25272
- seedOverwriteMode === "admin" ? `${pc5.green("\u2713")} Admin user replaced` : `${pc5.green("\u2713")} Admin user created`
26011
+ seedOverwriteMode === "admin" ? `${pc7.green("\u2713")} Admin user replaced` : `${pc7.green("\u2713")} Admin user created`
25273
26012
  );
25274
26013
  seedSuccess = true;
25275
26014
  adminAccountReady = true;
25276
26015
  } else if (seedResult.error) {
25277
- s.stop(`${pc5.red("\u2717")} Failed to create admin user`);
25278
- p15.note(
25279
- `${pc5.red(seedResult.error)}
26016
+ s.stop(`${pc7.red("\u2717")} Failed to create admin user`);
26017
+ p17.note(
26018
+ `${pc7.red(seedResult.error)}
25280
26019
 
25281
- Run manually: ${pc5.cyan(betterstartExecCommand(pm, "seed"))}`,
25282
- pc5.red("Seed failed")
26020
+ Run manually: ${pc7.cyan(betterstartExecCommand(pm, "seed"))}`,
26021
+ pc7.red("Seed failed")
25283
26022
  );
25284
26023
  if (isDatabaseReachabilityError(seedResult.error)) {
25285
- p15.log.error("Database was not reachable. Aborting setup.");
26024
+ p17.log.error("Database was not reachable. Aborting setup.");
25286
26025
  process.exit(1);
25287
26026
  }
25288
26027
  }
@@ -25308,43 +26047,37 @@ Run manually: ${pc5.cyan(betterstartExecCommand(pm, "seed"))}`,
25308
26047
  const installedPluginList = resolvedPluginInstallResult.installed.length > 0 ? resolvedPluginInstallResult.installed.join(", ") : "core only";
25309
26048
  const installedIntegrationList = resolvedIntegrationInstallResult.installed.length > 0 ? resolvedIntegrationInstallResult.installed.join(", ") : "none";
25310
26049
  const summaryLines = [
25311
- `Plugins: ${pc5.cyan(installedPluginList)}`,
25312
- `Integrations: ${pc5.cyan(installedIntegrationList)}`,
25313
- `Storage: ${pc5.cyan(usesLocalStorage ? "local filesystem" : "Cloudflare R2")}`,
25314
- `Files created: ${pc5.cyan(String(totalFiles))}`,
26050
+ `Plugins: ${pc7.cyan(installedPluginList)}`,
26051
+ `Integrations: ${pc7.cyan(installedIntegrationList)}`,
26052
+ `Storage: ${pc7.cyan(formatStorageProviderLabel(resolvedIntegrationInstallResult.config.storage.provider))}`,
26053
+ `Files created: ${pc7.cyan(String(totalFiles))}`,
25315
26054
  `Env vars: ${envResult.added.length} added, ${envResult.skipped.length} skipped`
25316
26055
  ];
25317
- const nextSteps = [];
25318
- let step = 1;
25319
- const envStepLabel = databaseUrl ? `Fill in remaining values in ${pc5.cyan(".env.local")}` : `Fill in values in ${pc5.cyan(".env.local")}`;
25320
- nextSteps.push(` ${step++}. ${envStepLabel}`);
25321
- if (!dbPushed) {
25322
- nextSteps.push(` ${step++}. Run ${pc5.cyan(drizzlePushCommand(pm))} to sync the database`);
25323
- }
25324
- if (!adminAccountReady) {
25325
- nextSteps.push(
25326
- ` ${step++}. Run ${pc5.cyan(betterstartExecCommand(pm, "seed"))} to create an admin user`
25327
- );
26056
+ p17.note(summaryLines.join("\n"), "Admin scaffolded successfully");
26057
+ if (options.vercel !== false && !options.skipDeploy) {
26058
+ const linkedVercelProject = Boolean(readLinkedProjectId(cwd));
26059
+ if (!options.yes) {
26060
+ const deployNow = await p17.confirm({
26061
+ message: "Deploy to Vercel now?",
26062
+ initialValue: linkedVercelProject
26063
+ });
26064
+ if (!p17.isCancel(deployNow) && deployNow) {
26065
+ await runVercelDeployFlow({ cwd, projectName, env: process.env });
26066
+ }
26067
+ } else if (linkedVercelProject && process.env.VERCEL_TOKEN) {
26068
+ const deployFlow = await runVercelDeployFlow({ cwd, projectName, env: process.env });
26069
+ if (!deployFlow.ok) {
26070
+ p17.log.warn("Vercel deploy did not complete; continuing.");
26071
+ }
26072
+ }
25328
26073
  }
25329
- nextSteps.push(
25330
- ` ${step++}. Run ${pc5.cyan(runCommand(pm, "dev"))} and open ${pc5.cyan(adminLoginUrl)}`
25331
- );
25332
- nextSteps.push(
25333
- ` ${step++}. Run ${pc5.cyan(betterstartExecCommand(pm, "generate <schema>"))} to create content types`
25334
- );
25335
- nextSteps.push(
25336
- ` ${step++}. Run ${pc5.cyan(betterstartExecCommand(pm, "add --integration <name>"))} to install integrations later`
25337
- );
25338
- summaryLines.push("", "Next steps:", ...nextSteps);
25339
- p15.note(summaryLines.join("\n"), "Admin scaffolded successfully");
25340
26074
  if (!options.yes && !options.skipDevServerStart) {
25341
26075
  const devCmd = runCommand(pm, "dev");
25342
- const startDev = await p15.confirm({
26076
+ const startDev = await p17.confirm({
25343
26077
  message: "Start the development server?",
25344
26078
  initialValue: true
25345
26079
  });
25346
- if (!p15.isCancel(startDev) && startDev) {
25347
- p15.outro(`Starting ${pc5.cyan(devCmd)}...`);
26080
+ if (!p17.isCancel(startDev) && startDev) {
25348
26081
  await startManagedDevServer(cwd, devCmd, adminLoginUrl, {
25349
26082
  email: seedSuccess && seedEmail ? seedEmail : void 0,
25350
26083
  password: seedSuccess && seedPassword ? seedPassword : void 0
@@ -25352,15 +26085,15 @@ Run manually: ${pc5.cyan(betterstartExecCommand(pm, "seed"))}`,
25352
26085
  return;
25353
26086
  }
25354
26087
  }
25355
- p15.outro("Done!");
26088
+ p17.outro("Done!");
25356
26089
  }
25357
26090
  function isValidDbUrl(url) {
25358
26091
  return url.startsWith("postgres://") || url.startsWith("postgresql://");
25359
26092
  }
25360
26093
  function readExistingDbUrl(cwd) {
25361
- const envPath = path48.join(cwd, ".env.local");
25362
- if (!fs37.existsSync(envPath)) return void 0;
25363
- const content = fs37.readFileSync(envPath, "utf-8");
26094
+ const envPath = path50.join(cwd, ".env.local");
26095
+ if (!fs39.existsSync(envPath)) return void 0;
26096
+ const content = fs39.readFileSync(envPath, "utf-8");
25364
26097
  for (const line of content.split("\n")) {
25365
26098
  const trimmed = line.trim();
25366
26099
  if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
@@ -25383,9 +26116,9 @@ function maskDbUrl(url) {
25383
26116
  }
25384
26117
  }
25385
26118
  function hasDbUrl(cwd) {
25386
- const envPath = path48.join(cwd, ".env.local");
25387
- if (!fs37.existsSync(envPath)) return false;
25388
- const content = fs37.readFileSync(envPath, "utf-8");
26119
+ const envPath = path50.join(cwd, ".env.local");
26120
+ if (!fs39.existsSync(envPath)) return false;
26121
+ const content = fs39.readFileSync(envPath, "utf-8");
25389
26122
  for (const line of content.split("\n")) {
25390
26123
  const trimmed = line.trim();
25391
26124
  if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
@@ -25399,20 +26132,20 @@ function hasDbUrl(cwd) {
25399
26132
  return false;
25400
26133
  }
25401
26134
  function detectInstalledNextVersion(cwd) {
25402
- const installedNextPackagePath = path48.join(cwd, "node_modules", "next", "package.json");
25403
- if (fs37.existsSync(installedNextPackagePath)) {
26135
+ const installedNextPackagePath = path50.join(cwd, "node_modules", "next", "package.json");
26136
+ if (fs39.existsSync(installedNextPackagePath)) {
25404
26137
  try {
25405
- const installedPkg = JSON.parse(fs37.readFileSync(installedNextPackagePath, "utf-8"));
26138
+ const installedPkg = JSON.parse(fs39.readFileSync(installedNextPackagePath, "utf-8"));
25406
26139
  if (typeof installedPkg.version === "string" && installedPkg.version.length > 0) {
25407
26140
  return installedPkg.version;
25408
26141
  }
25409
26142
  } catch {
25410
26143
  }
25411
26144
  }
25412
- const projectPackagePath = path48.join(cwd, "package.json");
25413
- if (!fs37.existsSync(projectPackagePath)) return void 0;
26145
+ const projectPackagePath = path50.join(cwd, "package.json");
26146
+ if (!fs39.existsSync(projectPackagePath)) return void 0;
25414
26147
  try {
25415
- const projectPkg = JSON.parse(fs37.readFileSync(projectPackagePath, "utf-8"));
26148
+ const projectPkg = JSON.parse(fs39.readFileSync(projectPackagePath, "utf-8"));
25416
26149
  const nextVersion = projectPkg.dependencies?.next ?? projectPkg.devDependencies?.next;
25417
26150
  if (typeof nextVersion !== "string" || nextVersion.length === 0) {
25418
26151
  return void 0;
@@ -25544,23 +26277,23 @@ main().catch((error) => {
25544
26277
  };
25545
26278
  }
25546
26279
  function runSeedScript(cwd, adminDir, authBasePath, envOverrides) {
25547
- const scriptsDir = path48.join(cwd, adminDir, "scripts");
25548
- const seedPath = path48.join(scriptsDir, "seed.ts");
25549
- if (!fs37.existsSync(scriptsDir)) {
25550
- fs37.mkdirSync(scriptsDir, { recursive: true });
26280
+ const scriptsDir = path50.join(cwd, adminDir, "scripts");
26281
+ const seedPath = path50.join(scriptsDir, "seed.ts");
26282
+ if (!fs39.existsSync(scriptsDir)) {
26283
+ fs39.mkdirSync(scriptsDir, { recursive: true });
25551
26284
  }
25552
- fs37.writeFileSync(seedPath, buildSeedScript(authBasePath), "utf-8");
26285
+ fs39.writeFileSync(seedPath, buildSeedScript(authBasePath), "utf-8");
25553
26286
  const cleanup = () => {
25554
26287
  try {
25555
- fs37.unlinkSync(seedPath);
25556
- if (fs37.existsSync(scriptsDir) && fs37.readdirSync(scriptsDir).length === 0) {
25557
- fs37.rmdirSync(scriptsDir);
26288
+ fs39.unlinkSync(seedPath);
26289
+ if (fs39.existsSync(scriptsDir) && fs39.readdirSync(scriptsDir).length === 0) {
26290
+ fs39.rmdirSync(scriptsDir);
25558
26291
  }
25559
26292
  } catch {
25560
26293
  }
25561
26294
  };
25562
26295
  return new Promise((resolve) => {
25563
- const tsxBin = path48.join(cwd, "node_modules", ".bin", "tsx");
26296
+ const tsxBin = path50.join(cwd, "node_modules", ".bin", "tsx");
25564
26297
  const child = spawn4(tsxBin, [seedPath], {
25565
26298
  cwd,
25566
26299
  stdio: "pipe",
@@ -25734,12 +26467,12 @@ function stripAnsi(value) {
25734
26467
  return value.replace(ANSI_ESCAPE_PATTERN, "");
25735
26468
  }
25736
26469
  function printAdminReadyNote(state) {
25737
- const lines = [`Admin: ${pc5.cyan(state.adminLoginUrl)}`];
26470
+ const lines = [`Admin: ${pc7.cyan(state.adminLoginUrl)}`];
25738
26471
  if (state.adminEmail && state.adminPassword) {
25739
- lines.unshift(`Password: ${pc5.cyan(state.adminPassword)}`);
25740
- lines.unshift(`Admin user: ${pc5.cyan(state.adminEmail)}`);
26472
+ lines.unshift(`Password: ${pc7.cyan(state.adminPassword)}`);
26473
+ lines.unshift(`Admin user: ${pc7.cyan(state.adminEmail)}`);
25741
26474
  }
25742
- p15.note(lines.join("\n"), "Admin ready");
26475
+ p17.note(lines.join("\n"), "Admin ready");
25743
26476
  }
25744
26477
  function shouldSuppressDevServerStartupLine(line) {
25745
26478
  const plain = stripAnsi(line).trim();
@@ -25749,7 +26482,7 @@ function shouldSuppressDevServerStartupLine(line) {
25749
26482
  if (plain.includes("Ready in ")) {
25750
26483
  return { suppress: true, isReadyLine: true };
25751
26484
  }
25752
- if (plain.startsWith("> ") || plain.startsWith("\u25B2 Next.js ") || plain.startsWith("- ") || plain.startsWith("\xB7 ") || plain.startsWith("\u2A2F ")) {
26485
+ if (plain.startsWith("> ") || plain.startsWith("$ ") || plain.startsWith("\u25B2 Next.js ") || plain.startsWith("- ") || plain.startsWith("\xB7 ") || plain.startsWith("\u2A2F ")) {
25753
26486
  return { suppress: true, isReadyLine: false };
25754
26487
  }
25755
26488
  if (plain.startsWith("\u2713 ") && !plain.includes("Ready in ")) {
@@ -25850,75 +26583,75 @@ function startManagedDevServer(cwd, devCmd, adminLoginUrl, adminCredentials) {
25850
26583
  }
25851
26584
 
25852
26585
  // adapters/next/commands/list-integrations.ts
25853
- import path49 from "path";
25854
- import * as p16 from "@clack/prompts";
26586
+ import path51 from "path";
26587
+ import * as p18 from "@clack/prompts";
25855
26588
  async function runListIntegrationsCommand(options) {
25856
- const cwd = options.cwd ? path49.resolve(options.cwd) : process.cwd();
26589
+ const cwd = options.cwd ? path51.resolve(options.cwd) : process.cwd();
25857
26590
  const config = await resolveConfig(cwd);
25858
26591
  const installedIntegrations = new Set(config.integrations.installed);
25859
26592
  const lines = listAvailableIntegrations().map((integration) => {
25860
26593
  const status = installedIntegrations.has(integration.id) ? "installed" : "available";
25861
- return `${integration.id.padEnd(10)} ${status.padEnd(10)} ${integration.kind.padEnd(8)} ${integration.description}`;
26594
+ return `${integration.id.padEnd(12)} ${status.padEnd(10)} ${integration.kind.padEnd(8)} ${integration.description}`;
25862
26595
  });
25863
- p16.note(lines.join("\n"), "BetterStart integrations");
25864
- p16.outro(
26596
+ p18.note(lines.join("\n"), "BetterStart integrations");
26597
+ p18.outro(
25865
26598
  `${installedIntegrations.size} installed, ${lines.length - installedIntegrations.size} available`
25866
26599
  );
25867
26600
  }
25868
26601
 
25869
26602
  // adapters/next/commands/list-plugins.ts
25870
- import path50 from "path";
25871
- import * as p17 from "@clack/prompts";
26603
+ import path52 from "path";
26604
+ import * as p19 from "@clack/prompts";
25872
26605
  async function runListPluginsCommand(options) {
25873
- const cwd = options.cwd ? path50.resolve(options.cwd) : process.cwd();
26606
+ const cwd = options.cwd ? path52.resolve(options.cwd) : process.cwd();
25874
26607
  const config = await resolveConfig(cwd);
25875
26608
  const installedPlugins = new Set(config.plugins.installed);
25876
26609
  const lines = listAvailablePlugins().map((plugin) => {
25877
26610
  const status = installedPlugins.has(plugin.id) ? "installed" : "available";
25878
26611
  return `${plugin.id.padEnd(10)} ${status.padEnd(10)} ${plugin.kind.padEnd(12)} ${plugin.description}`;
25879
26612
  });
25880
- p17.note(lines.join("\n"), "BetterStart plugins");
25881
- p17.outro(`${installedPlugins.size} installed, ${lines.length - installedPlugins.size} available`);
26613
+ p19.note(lines.join("\n"), "BetterStart plugins");
26614
+ p19.outro(`${installedPlugins.size} installed, ${lines.length - installedPlugins.size} available`);
25882
26615
  }
25883
26616
 
25884
26617
  // adapters/next/commands/remove.ts
25885
- import path51 from "path";
25886
- import * as p18 from "@clack/prompts";
26618
+ import path53 from "path";
26619
+ import * as p20 from "@clack/prompts";
25887
26620
  async function runRemoveCommand(items, options) {
25888
26621
  const removeIntegrationsMode = Boolean(options.integration);
25889
26622
  if (!removeIntegrationsMode && items.includes("core")) {
25890
- p18.log.error("The core Admin cannot be removed.");
26623
+ p20.log.error("The core Admin cannot be removed.");
25891
26624
  process.exit(1);
25892
26625
  }
25893
26626
  const pluginIds = items.filter(isPluginId);
25894
26627
  const integrationIds = items.filter(isIntegrationId);
25895
26628
  if (!removeIntegrationsMode && integrationIds.length > 0) {
25896
- p18.log.error(
26629
+ p20.log.error(
25897
26630
  `Integration IDs require --integration. Run \`betterstart remove --integration ${integrationIds.join(" ")}\`.`
25898
26631
  );
25899
26632
  process.exit(1);
25900
26633
  }
25901
26634
  if (removeIntegrationsMode && pluginIds.length > 0) {
25902
- p18.log.error(`Plugin IDs cannot be removed with --integration: ${pluginIds.join(", ")}`);
26635
+ p20.log.error(`Plugin IDs cannot be removed with --integration: ${pluginIds.join(", ")}`);
25903
26636
  process.exit(1);
25904
26637
  }
25905
26638
  const invalidItems = removeIntegrationsMode ? items.filter((integrationId) => !isIntegrationId(integrationId)) : items.filter((pluginId) => !isPluginId(pluginId));
25906
26639
  if (invalidItems.length > 0) {
25907
- p18.log.error(
26640
+ p20.log.error(
25908
26641
  removeIntegrationsMode ? formatUnknownIntegrationMessage(invalidItems) : formatUnknownPluginMessage(invalidItems)
25909
26642
  );
25910
26643
  process.exit(1);
25911
26644
  }
25912
- const cwd = options.cwd ? path51.resolve(options.cwd) : process.cwd();
26645
+ const cwd = options.cwd ? path53.resolve(options.cwd) : process.cwd();
25913
26646
  const config = await resolveConfig(cwd);
25914
26647
  const pm = detectPackageManager(cwd);
25915
26648
  if (!options.force) {
25916
- const confirmed = await p18.confirm({
26649
+ const confirmed = await p20.confirm({
25917
26650
  message: `Remove ${removeIntegrationsMode ? "integration" : "plugin"}${items.length === 1 ? "" : "s"} ${items.join(", ")}?`,
25918
26651
  initialValue: false
25919
26652
  });
25920
- if (p18.isCancel(confirmed) || !confirmed) {
25921
- p18.cancel(`${removeIntegrationsMode ? "Integration" : "Plugin"} removal cancelled.`);
26653
+ if (p20.isCancel(confirmed) || !confirmed) {
26654
+ p20.cancel(`${removeIntegrationsMode ? "Integration" : "Plugin"} removal cancelled.`);
25922
26655
  process.exit(0);
25923
26656
  }
25924
26657
  }
@@ -25931,13 +26664,13 @@ async function runRemoveCommand(items, options) {
25931
26664
  });
25932
26665
  writeConfigFile(cwd, result2.config);
25933
26666
  if (result2.removed.length === 0) {
25934
- p18.outro("No integrations were removed.");
26667
+ p20.outro("No integrations were removed.");
25935
26668
  return;
25936
26669
  }
25937
26670
  if (result2.warnings.length > 0) {
25938
- p18.note(result2.warnings.join("\n"), "Warnings");
26671
+ p20.note(result2.warnings.join("\n"), "Warnings");
25939
26672
  }
25940
- p18.outro(
26673
+ p20.outro(
25941
26674
  `Removed integration${result2.removed.length === 1 ? "" : "s"}: ${result2.removed.join(", ")}`
25942
26675
  );
25943
26676
  return;
@@ -25950,18 +26683,18 @@ async function runRemoveCommand(items, options) {
25950
26683
  });
25951
26684
  writeConfigFile(cwd, result.config);
25952
26685
  if (result.removed.length === 0) {
25953
- p18.outro("No plugins were removed.");
26686
+ p20.outro("No plugins were removed.");
25954
26687
  return;
25955
26688
  }
25956
26689
  if (result.warnings.length > 0) {
25957
- p18.note(result.warnings.join("\n"), "Warnings");
26690
+ p20.note(result.warnings.join("\n"), "Warnings");
25958
26691
  }
25959
- p18.outro(`Removed plugin${result.removed.length === 1 ? "" : "s"}: ${result.removed.join(", ")}`);
26692
+ p20.outro(`Removed plugin${result.removed.length === 1 ? "" : "s"}: ${result.removed.join(", ")}`);
25960
26693
  }
25961
26694
 
25962
26695
  // adapters/next/commands/remove-schema.ts
25963
- import fs38 from "fs";
25964
- import path52 from "path";
26696
+ import fs40 from "fs";
26697
+ import path54 from "path";
25965
26698
  import readline from "readline";
25966
26699
  async function promptConfirm4(message) {
25967
26700
  const rl = readline.createInterface({
@@ -25976,30 +26709,30 @@ async function promptConfirm4(message) {
25976
26709
  });
25977
26710
  }
25978
26711
  function removePath2(cwd, filePath) {
25979
- const fullPath = path52.join(cwd, ...filePath.split("/"));
25980
- const existed = fs38.existsSync(fullPath);
25981
- fs38.rmSync(fullPath, { recursive: true, force: true });
26712
+ const fullPath = path54.join(cwd, ...filePath.split("/"));
26713
+ const existed = fs40.existsSync(fullPath);
26714
+ fs40.rmSync(fullPath, { recursive: true, force: true });
25982
26715
  return existed;
25983
26716
  }
25984
26717
  function cleanupEmptyDirs3(cwd, deletedPaths, configPaths) {
25985
26718
  const stopRoots = /* @__PURE__ */ new Set([
25986
- path52.join(cwd, ...configPaths.adminDir.split("/")),
25987
- path52.join(cwd, ...configPaths.adminNavigationDir.split("/")),
25988
- path52.join(cwd, ...configPaths.pagesDir.split("/"))
26719
+ path54.join(cwd, ...configPaths.adminDir.split("/")),
26720
+ path54.join(cwd, ...configPaths.adminNavigationDir.split("/")),
26721
+ path54.join(cwd, ...configPaths.pagesDir.split("/"))
25989
26722
  ]);
25990
26723
  for (const deletedPath of deletedPaths) {
25991
- let current = path52.dirname(path52.join(cwd, ...deletedPath.split("/")));
26724
+ let current = path54.dirname(path54.join(cwd, ...deletedPath.split("/")));
25992
26725
  while (!stopRoots.has(current)) {
25993
- if (!fs38.existsSync(current)) {
25994
- current = path52.dirname(current);
26726
+ if (!fs40.existsSync(current)) {
26727
+ current = path54.dirname(current);
25995
26728
  continue;
25996
26729
  }
25997
- const entries = fs38.readdirSync(current);
26730
+ const entries = fs40.readdirSync(current);
25998
26731
  if (entries.length > 0) {
25999
26732
  break;
26000
26733
  }
26001
- fs38.rmdirSync(current);
26002
- current = path52.dirname(current);
26734
+ fs40.rmdirSync(current);
26735
+ current = path54.dirname(current);
26003
26736
  }
26004
26737
  }
26005
26738
  }
@@ -26015,7 +26748,7 @@ function resolveSchemaOwnerForRemoval(cwd, schemaName) {
26015
26748
  }
26016
26749
  async function runRemoveSchemaCommand(schemaName, options) {
26017
26750
  const owner = resolveSchemaOwnerForRemoval(
26018
- options.cwd ? path52.resolve(options.cwd) : process.cwd(),
26751
+ options.cwd ? path54.resolve(options.cwd) : process.cwd(),
26019
26752
  schemaName
26020
26753
  );
26021
26754
  if (owner === "core") {
@@ -26028,7 +26761,7 @@ async function runRemoveSchemaCommand(schemaName, options) {
26028
26761
  console.error(` "${schemaName}" is owned by ${owner}. Remove the owning plugin instead.`);
26029
26762
  process.exit(1);
26030
26763
  }
26031
- const cwd = options.cwd ? path52.resolve(options.cwd) : process.cwd();
26764
+ const cwd = options.cwd ? path54.resolve(options.cwd) : process.cwd();
26032
26765
  const config = await resolveConfig(cwd);
26033
26766
  const paths = resolveProjectPaths(config);
26034
26767
  const manifest = loadManifest(cwd, schemaName);
@@ -26059,7 +26792,7 @@ async function runRemoveSchemaCommand(schemaName, options) {
26059
26792
  }
26060
26793
  const loaded = (() => {
26061
26794
  try {
26062
- return loadSchema(path52.join(cwd, ...paths.schemasDir.split("/")), schemaName);
26795
+ return loadSchema(path54.join(cwd, ...paths.schemasDir.split("/")), schemaName);
26063
26796
  } catch {
26064
26797
  return null;
26065
26798
  }
@@ -26103,13 +26836,13 @@ async function runRemoveSchemaCommand(schemaName, options) {
26103
26836
  }
26104
26837
 
26105
26838
  // adapters/next/commands/uninstall.ts
26106
- import fs40 from "fs";
26107
- import path53 from "path";
26108
- import * as p19 from "@clack/prompts";
26109
- import pc6 from "picocolors";
26839
+ import fs42 from "fs";
26840
+ import path55 from "path";
26841
+ import * as p21 from "@clack/prompts";
26842
+ import pc8 from "picocolors";
26110
26843
 
26111
26844
  // adapters/next/commands/uninstall-cleaners.ts
26112
- import fs39 from "fs";
26845
+ import fs41 from "fs";
26113
26846
  function stripJsonComments2(input) {
26114
26847
  let result = "";
26115
26848
  let i = 0;
@@ -26143,8 +26876,8 @@ function stripJsonComments2(input) {
26143
26876
  return result;
26144
26877
  }
26145
26878
  function cleanTsconfig(tsconfigPath, aliasRoot = "@admin") {
26146
- if (!fs39.existsSync(tsconfigPath)) return [];
26147
- const raw = fs39.readFileSync(tsconfigPath, "utf-8");
26879
+ if (!fs41.existsSync(tsconfigPath)) return [];
26880
+ const raw = fs41.readFileSync(tsconfigPath, "utf-8");
26148
26881
  const stripped = stripJsonComments2(raw).replace(/,\s*([\]}])/g, "$1");
26149
26882
  let tsconfig;
26150
26883
  try {
@@ -26168,13 +26901,13 @@ function cleanTsconfig(tsconfigPath, aliasRoot = "@admin") {
26168
26901
  compilerOptions.paths = paths;
26169
26902
  }
26170
26903
  tsconfig.compilerOptions = compilerOptions;
26171
- fs39.writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
26904
+ fs41.writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
26172
26905
  `, "utf-8");
26173
26906
  return removed;
26174
26907
  }
26175
26908
  function cleanCss(cssPath, namespace = "admin") {
26176
- if (!fs39.existsSync(cssPath)) return [];
26177
- const content = fs39.readFileSync(cssPath, "utf-8");
26909
+ if (!fs41.existsSync(cssPath)) return [];
26910
+ const content = fs41.readFileSync(cssPath, "utf-8");
26178
26911
  const lines = content.split("\n");
26179
26912
  const sourcePattern = new RegExp(`^@source\\s+"[^"]*(?:admin|${namespace})[^"]*";\\s*$`);
26180
26913
  const removed = [];
@@ -26188,12 +26921,12 @@ function cleanCss(cssPath, namespace = "admin") {
26188
26921
  }
26189
26922
  if (removed.length === 0) return [];
26190
26923
  const cleaned = kept.join("\n").replace(/\n{3,}/g, "\n\n");
26191
- fs39.writeFileSync(cssPath, cleaned, "utf-8");
26924
+ fs41.writeFileSync(cssPath, cleaned, "utf-8");
26192
26925
  return removed;
26193
26926
  }
26194
26927
  function cleanEnvFile(envPath) {
26195
- if (!fs39.existsSync(envPath)) return [];
26196
- const content = fs39.readFileSync(envPath, "utf-8");
26928
+ if (!fs41.existsSync(envPath)) return [];
26929
+ const content = fs41.readFileSync(envPath, "utf-8");
26197
26930
  const lines = content.split("\n");
26198
26931
  const removed = [];
26199
26932
  const kept = [];
@@ -26226,9 +26959,9 @@ function cleanEnvFile(envPath) {
26226
26959
  if (removed.length === 0) return [];
26227
26960
  const result = kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
26228
26961
  if (result === "") {
26229
- fs39.unlinkSync(envPath);
26962
+ fs41.unlinkSync(envPath);
26230
26963
  } else {
26231
- fs39.writeFileSync(envPath, `${result}
26964
+ fs41.writeFileSync(envPath, `${result}
26232
26965
  `, "utf-8");
26233
26966
  }
26234
26967
  return removed;
@@ -26254,15 +26987,15 @@ function findMainCss2(cwd) {
26254
26987
  "globals.css"
26255
26988
  ];
26256
26989
  for (const candidate of candidates) {
26257
- const filePath = path53.join(cwd, candidate);
26258
- if (fs40.existsSync(filePath)) return filePath;
26990
+ const filePath = path55.join(cwd, candidate);
26991
+ if (fs42.existsSync(filePath)) return filePath;
26259
26992
  }
26260
26993
  return void 0;
26261
26994
  }
26262
26995
  function isCLICreatedBiome(biomePath) {
26263
- if (!fs40.existsSync(biomePath)) return false;
26996
+ if (!fs42.existsSync(biomePath)) return false;
26264
26997
  try {
26265
- const content = JSON.parse(fs40.readFileSync(biomePath, "utf-8"));
26998
+ const content = JSON.parse(fs42.readFileSync(biomePath, "utf-8"));
26266
26999
  return content.$schema?.includes("biomejs.dev") && content.formatter?.indentStyle === "space" && content.javascript?.formatter?.quoteStyle === "single" && Array.isArray(content.files?.ignore) && content.files.ignore.includes(".next");
26267
27000
  } catch {
26268
27001
  return false;
@@ -26271,17 +27004,17 @@ function isCLICreatedBiome(biomePath) {
26271
27004
  function buildUninstallPlan(cwd, namespaceValue) {
26272
27005
  const steps = [];
26273
27006
  const namespace = resolveAdminNamespace(namespaceValue);
26274
- const hasSrc = fs40.existsSync(path53.join(cwd, "src"));
27007
+ const hasSrc = fs42.existsSync(path55.join(cwd, "src"));
26275
27008
  const appBase = hasSrc ? "src/app" : "app";
26276
27009
  const dirs = [];
26277
- const adminDir = path53.join(cwd, namespace.segment);
26278
- const legacyAdminDir = path53.join(cwd, "admin");
26279
- const adminRouteGroup = path53.join(cwd, appBase, namespace.routeGroup);
26280
- const legacyAdminRouteGroup = path53.join(cwd, appBase, "(admin)");
26281
- if (fs40.existsSync(adminDir)) dirs.push(`${namespace.segment}/`);
26282
- if (namespace.segment !== "admin" && fs40.existsSync(legacyAdminDir)) dirs.push("admin/");
26283
- if (fs40.existsSync(adminRouteGroup)) dirs.push(`${appBase}/${namespace.routeGroup}/`);
26284
- if (namespace.segment !== "admin" && fs40.existsSync(legacyAdminRouteGroup))
27010
+ const adminDir = path55.join(cwd, namespace.segment);
27011
+ const legacyAdminDir = path55.join(cwd, "admin");
27012
+ const adminRouteGroup = path55.join(cwd, appBase, namespace.routeGroup);
27013
+ const legacyAdminRouteGroup = path55.join(cwd, appBase, "(admin)");
27014
+ if (fs42.existsSync(adminDir)) dirs.push(`${namespace.segment}/`);
27015
+ if (namespace.segment !== "admin" && fs42.existsSync(legacyAdminDir)) dirs.push("admin/");
27016
+ if (fs42.existsSync(adminRouteGroup)) dirs.push(`${appBase}/${namespace.routeGroup}/`);
27017
+ if (namespace.segment !== "admin" && fs42.existsSync(legacyAdminRouteGroup))
26285
27018
  dirs.push(`${appBase}/(admin)/`);
26286
27019
  if (dirs.length > 0) {
26287
27020
  steps.push({
@@ -26290,14 +27023,14 @@ function buildUninstallPlan(cwd, namespaceValue) {
26290
27023
  count: dirs.length,
26291
27024
  unit: dirs.length === 1 ? "directory" : "directories",
26292
27025
  execute() {
26293
- if (fs40.existsSync(adminDir)) fs40.rmSync(adminDir, { recursive: true, force: true });
26294
- if (fs40.existsSync(legacyAdminDir))
26295
- fs40.rmSync(legacyAdminDir, { recursive: true, force: true });
26296
- if (fs40.existsSync(adminRouteGroup)) {
26297
- fs40.rmSync(adminRouteGroup, { recursive: true, force: true });
27026
+ if (fs42.existsSync(adminDir)) fs42.rmSync(adminDir, { recursive: true, force: true });
27027
+ if (fs42.existsSync(legacyAdminDir))
27028
+ fs42.rmSync(legacyAdminDir, { recursive: true, force: true });
27029
+ if (fs42.existsSync(adminRouteGroup)) {
27030
+ fs42.rmSync(adminRouteGroup, { recursive: true, force: true });
26298
27031
  }
26299
- if (fs40.existsSync(legacyAdminRouteGroup)) {
26300
- fs40.rmSync(legacyAdminRouteGroup, { recursive: true, force: true });
27032
+ if (fs42.existsSync(legacyAdminRouteGroup)) {
27033
+ fs42.rmSync(legacyAdminRouteGroup, { recursive: true, force: true });
26301
27034
  }
26302
27035
  }
26303
27036
  });
@@ -26305,17 +27038,17 @@ function buildUninstallPlan(cwd, namespaceValue) {
26305
27038
  const configFiles = [];
26306
27039
  const configPaths = [];
26307
27040
  const candidates = [
26308
- ["admin.config.ts", path53.join(cwd, "admin.config.ts")],
26309
- ["drizzle.config.ts", path53.join(cwd, "drizzle.config.ts")],
26310
- ["ADMIN.md", path53.join(cwd, "ADMIN.md")]
27041
+ ["admin.config.ts", path55.join(cwd, "admin.config.ts")],
27042
+ ["drizzle.config.ts", path55.join(cwd, "drizzle.config.ts")],
27043
+ ["ADMIN.md", path55.join(cwd, "ADMIN.md")]
26311
27044
  ];
26312
27045
  for (const [label, fullPath] of candidates) {
26313
- if (fs40.existsSync(fullPath)) {
27046
+ if (fs42.existsSync(fullPath)) {
26314
27047
  configFiles.push(label);
26315
27048
  configPaths.push(fullPath);
26316
27049
  }
26317
27050
  }
26318
- const biomePath = path53.join(cwd, "biome.json");
27051
+ const biomePath = path55.join(cwd, "biome.json");
26319
27052
  if (isCLICreatedBiome(biomePath)) {
26320
27053
  configFiles.push("biome.json (CLI-created)");
26321
27054
  configPaths.push(biomePath);
@@ -26327,15 +27060,15 @@ function buildUninstallPlan(cwd, namespaceValue) {
26327
27060
  count: configFiles.length,
26328
27061
  unit: configFiles.length === 1 ? "file" : "files",
26329
27062
  execute() {
26330
- for (const p20 of configPaths) {
26331
- if (fs40.existsSync(p20)) fs40.unlinkSync(p20);
27063
+ for (const p22 of configPaths) {
27064
+ if (fs42.existsSync(p22)) fs42.unlinkSync(p22);
26332
27065
  }
26333
27066
  }
26334
27067
  });
26335
27068
  }
26336
- const tsconfigPath = path53.join(cwd, "tsconfig.json");
26337
- if (fs40.existsSync(tsconfigPath)) {
26338
- const content = fs40.readFileSync(tsconfigPath, "utf-8");
27069
+ const tsconfigPath = path55.join(cwd, "tsconfig.json");
27070
+ if (fs42.existsSync(tsconfigPath)) {
27071
+ const content = fs42.readFileSync(tsconfigPath, "utf-8");
26339
27072
  const aliasMatches = [
26340
27073
  ...content.match(/"@admin\//g) ?? [],
26341
27074
  ...content.match(new RegExp(`"${namespace.alias}/`, "g")) ?? []
@@ -26355,12 +27088,12 @@ function buildUninstallPlan(cwd, namespaceValue) {
26355
27088
  }
26356
27089
  const cssFile = findMainCss2(cwd);
26357
27090
  if (cssFile) {
26358
- const cssContent = fs40.readFileSync(cssFile, "utf-8");
27091
+ const cssContent = fs42.readFileSync(cssFile, "utf-8");
26359
27092
  const sourceLines = cssContent.split("\n").filter(
26360
27093
  (l) => new RegExp(`^@source\\s+"[^"]*(?:admin|${namespace.segment})[^"]*";\\s*$`).test(l)
26361
27094
  );
26362
27095
  if (sourceLines.length > 0) {
26363
- const relCss = path53.relative(cwd, cssFile);
27096
+ const relCss = path55.relative(cwd, cssFile);
26364
27097
  steps.push({
26365
27098
  label: `CSS @source lines (${relCss})`,
26366
27099
  items: [`@source lines in ${relCss}`],
@@ -26372,9 +27105,9 @@ function buildUninstallPlan(cwd, namespaceValue) {
26372
27105
  });
26373
27106
  }
26374
27107
  }
26375
- const envPath = path53.join(cwd, ".env.local");
26376
- if (fs40.existsSync(envPath)) {
26377
- const envContent = fs40.readFileSync(envPath, "utf-8");
27108
+ const envPath = path55.join(cwd, ".env.local");
27109
+ if (fs42.existsSync(envPath)) {
27110
+ const envContent = fs42.readFileSync(envPath, "utf-8");
26378
27111
  const bsVars = envContent.split("\n").filter((l) => l.trim().match(/^BETTERSTART_\w+=/)).map((l) => l.split("=")[0]);
26379
27112
  if (bsVars.length > 0) {
26380
27113
  steps.push({
@@ -26391,8 +27124,8 @@ function buildUninstallPlan(cwd, namespaceValue) {
26391
27124
  return steps;
26392
27125
  }
26393
27126
  async function runUninstallCommand(options) {
26394
- const cwd = options.cwd ? path53.resolve(options.cwd) : process.cwd();
26395
- p19.intro(pc6.bgRed(pc6.white(" BetterStart Uninstall ")));
27127
+ const cwd = options.cwd ? path55.resolve(options.cwd) : process.cwd();
27128
+ p21.intro(pc8.bgRed(pc8.white(" BetterStart Uninstall ")));
26396
27129
  let namespace = DEFAULT_ADMIN_NAMESPACE;
26397
27130
  try {
26398
27131
  const config = await resolveConfig(cwd);
@@ -26401,27 +27134,27 @@ async function runUninstallCommand(options) {
26401
27134
  }
26402
27135
  const steps = buildUninstallPlan(cwd, namespace);
26403
27136
  if (steps.length === 0) {
26404
- p19.log.success(`${pc6.green("\u2713")} Nothing to remove \u2014 project is already clean.`);
26405
- p19.outro("Done");
27137
+ p21.log.success(`${pc8.green("\u2713")} Nothing to remove \u2014 project is already clean.`);
27138
+ p21.outro("Done");
26406
27139
  return;
26407
27140
  }
26408
27141
  const planLines = steps.map((step) => {
26409
27142
  const names = step.items.join(" ");
26410
- const countLabel = pc6.dim(`${step.count} ${step.unit}`);
26411
- return `${pc6.red("\xD7")} ${names} ${countLabel}`;
27143
+ const countLabel = pc8.dim(`${step.count} ${step.unit}`);
27144
+ return `${pc8.red("\xD7")} ${names} ${countLabel}`;
26412
27145
  });
26413
- p19.note(planLines.join("\n"), "Uninstall plan");
27146
+ p21.note(planLines.join("\n"), "Uninstall plan");
26414
27147
  if (!options.force) {
26415
- const confirmed = await p19.confirm({
27148
+ const confirmed = await p21.confirm({
26416
27149
  message: "Proceed with uninstall?",
26417
27150
  initialValue: false
26418
27151
  });
26419
- if (p19.isCancel(confirmed) || !confirmed) {
26420
- p19.cancel("Uninstall cancelled.");
27152
+ if (p21.isCancel(confirmed) || !confirmed) {
27153
+ p21.cancel("Uninstall cancelled.");
26421
27154
  process.exit(0);
26422
27155
  }
26423
27156
  }
26424
- const s = p19.spinner();
27157
+ const s = spinner2();
26425
27158
  s.start(steps[0].label);
26426
27159
  for (const step of steps) {
26427
27160
  s.message(step.label);
@@ -26429,14 +27162,14 @@ async function runUninstallCommand(options) {
26429
27162
  }
26430
27163
  const parts = steps.map((step) => `${step.count} ${step.unit}`);
26431
27164
  s.stop(`Removed ${parts.join(", ")}`);
26432
- p19.note(pc6.dim("Database tables were NOT dropped \u2014 drop them manually if needed."), "Next steps");
26433
- p19.outro("Uninstall complete");
27165
+ p21.note(pc8.dim("Database tables were NOT dropped \u2014 drop them manually if needed."), "Next steps");
27166
+ p21.outro("Uninstall complete");
26434
27167
  }
26435
27168
 
26436
27169
  // adapters/next/commands/update-component.ts
26437
27170
  import { execFileSync as execFileSync6 } from "child_process";
26438
- import fs41 from "fs";
26439
- import path54 from "path";
27171
+ import fs43 from "fs";
27172
+ import path56 from "path";
26440
27173
  import * as clack2 from "@clack/prompts";
26441
27174
  import fsExtra from "fs-extra";
26442
27175
  var STATIC_CUSTOM_DEPENDENCIES = {
@@ -26686,24 +27419,24 @@ function applyNamespaceToTemplateEntry(entry, config, cwd) {
26686
27419
  };
26687
27420
  }
26688
27421
  function writeNamespacedFile(srcPath, destPath, namespace) {
26689
- fs41.writeFileSync(
27422
+ fs43.writeFileSync(
26690
27423
  destPath,
26691
- applyAdminNamespaceToContent(fs41.readFileSync(srcPath, "utf-8"), namespace),
27424
+ applyAdminNamespaceToContent(fs43.readFileSync(srcPath, "utf-8"), namespace),
26692
27425
  "utf-8"
26693
27426
  );
26694
27427
  }
26695
27428
  function copyNamespacedDirectory(srcDir, destDir, namespace) {
26696
- const entries = fs41.readdirSync(srcDir, { withFileTypes: true });
27429
+ const entries = fs43.readdirSync(srcDir, { withFileTypes: true });
26697
27430
  for (const entry of entries) {
26698
27431
  const namespacedName = applyAdminNamespaceToPath(entry.name, namespace);
26699
- const srcPath = path54.join(srcDir, entry.name);
26700
- const destPath = path54.join(destDir, namespacedName);
27432
+ const srcPath = path56.join(srcDir, entry.name);
27433
+ const destPath = path56.join(destDir, namespacedName);
26701
27434
  if (entry.isDirectory()) {
26702
27435
  fsExtra.ensureDirSync(destPath);
26703
27436
  copyNamespacedDirectory(srcPath, destPath, namespace);
26704
27437
  continue;
26705
27438
  }
26706
- fsExtra.ensureDirSync(path54.dirname(destPath));
27439
+ fsExtra.ensureDirSync(path56.dirname(destPath));
26707
27440
  writeNamespacedFile(srcPath, destPath, namespace);
26708
27441
  }
26709
27442
  }
@@ -26714,10 +27447,10 @@ function hasIntegration(config, integrationId) {
26714
27447
  return config.integrations.installed.includes(integrationId);
26715
27448
  }
26716
27449
  function readProjectPackageJson2(cwd) {
26717
- const pkgPath = path54.join(cwd, "package.json");
26718
- if (!fs41.existsSync(pkgPath)) return null;
27450
+ const pkgPath = path56.join(cwd, "package.json");
27451
+ if (!fs43.existsSync(pkgPath)) return null;
26719
27452
  try {
26720
- return JSON.parse(fs41.readFileSync(pkgPath, "utf-8"));
27453
+ return JSON.parse(fs43.readFileSync(pkgPath, "utf-8"));
26721
27454
  } catch {
26722
27455
  return null;
26723
27456
  }
@@ -27630,6 +28363,11 @@ var TEMPLATE_REGISTRY = {
27630
28363
  requiredIntegration: "r2",
27631
28364
  content: () => readIntegrationTemplate("r2", "actions/r2.ts")
27632
28365
  },
28366
+ "storage-vercel-blob-provider": {
28367
+ relPath: "lib/actions/vercel-blob/provider.ts",
28368
+ requiredIntegration: "vercel-blob",
28369
+ content: () => readIntegrationTemplate("vercel-blob", "actions/vercel-blob.ts")
28370
+ },
27633
28371
  "form-settings-action": {
27634
28372
  relPath: "lib/actions/forms/index.ts",
27635
28373
  content: () => readTemplate("lib/actions/forms/index.ts"),
@@ -28008,9 +28746,9 @@ function getStaticAssetComponents(assetDirectory) {
28008
28746
  }
28009
28747
  function getStaticAssetComponentEntries(assetDirectory) {
28010
28748
  const assetDir = resolveCliAssetPath("shared-assets", "react-admin", assetDirectory);
28011
- if (!fs41.existsSync(assetDir)) return [];
28749
+ if (!fs43.existsSync(assetDir)) return [];
28012
28750
  const components = [];
28013
- for (const entry of fs41.readdirSync(assetDir, { withFileTypes: true })) {
28751
+ for (const entry of fs43.readdirSync(assetDir, { withFileTypes: true })) {
28014
28752
  if (entry.isFile() && (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts"))) {
28015
28753
  components.push({
28016
28754
  name: entry.name.replace(/\.(tsx|ts)$/, ""),
@@ -28022,40 +28760,40 @@ function getStaticAssetComponentEntries(assetDirectory) {
28022
28760
  continue;
28023
28761
  }
28024
28762
  const indexFile = ["index.tsx", "index.ts"].find(
28025
- (file) => fs41.existsSync(path54.join(assetDir, entry.name, file))
28763
+ (file) => fs43.existsSync(path56.join(assetDir, entry.name, file))
28026
28764
  );
28027
28765
  if (indexFile) {
28028
28766
  components.push({
28029
28767
  name: entry.name,
28030
- file: path54.join(entry.name, indexFile)
28768
+ file: path56.join(entry.name, indexFile)
28031
28769
  });
28032
28770
  }
28033
28771
  }
28034
28772
  return components.sort((a, b) => a.name.localeCompare(b.name));
28035
28773
  }
28036
28774
  function findStaticAssetFile(assetDir, componentName) {
28037
- if (!fs41.existsSync(assetDir)) return void 0;
28775
+ if (!fs43.existsSync(assetDir)) return void 0;
28038
28776
  const isNestedComponentName = /[\\/]/.test(componentName);
28039
- const nestedComponentName = componentName.split(/[\\/]/).filter(Boolean).join(path54.sep);
28040
- if (isNestedComponentName && nestedComponentName && !path54.isAbsolute(componentName) && !nestedComponentName.split(path54.sep).includes("..")) {
28777
+ const nestedComponentName = componentName.split(/[\\/]/).filter(Boolean).join(path56.sep);
28778
+ if (isNestedComponentName && nestedComponentName && !path56.isAbsolute(componentName) && !nestedComponentName.split(path56.sep).includes("..")) {
28041
28779
  for (const extension of [".tsx", ".ts"]) {
28042
28780
  const relPath = `${nestedComponentName}${extension}`;
28043
- const filePath = path54.join(assetDir, relPath);
28044
- if (fs41.existsSync(filePath) && fs41.statSync(filePath).isFile()) {
28781
+ const filePath = path56.join(assetDir, relPath);
28782
+ if (fs43.existsSync(filePath) && fs43.statSync(filePath).isFile()) {
28045
28783
  return relPath;
28046
28784
  }
28047
28785
  }
28048
28786
  }
28049
- if (!isNestedComponentName && !componentName.includes("..") && !path54.isAbsolute(componentName)) {
28787
+ if (!isNestedComponentName && !componentName.includes("..") && !path56.isAbsolute(componentName)) {
28050
28788
  for (const extension of [".tsx", ".ts"]) {
28051
- const relPath = path54.join(componentName, `index${extension}`);
28052
- const filePath = path54.join(assetDir, relPath);
28053
- if (fs41.existsSync(filePath) && fs41.statSync(filePath).isFile()) {
28789
+ const relPath = path56.join(componentName, `index${extension}`);
28790
+ const filePath = path56.join(assetDir, relPath);
28791
+ if (fs43.existsSync(filePath) && fs43.statSync(filePath).isFile()) {
28054
28792
  return relPath;
28055
28793
  }
28056
28794
  }
28057
28795
  }
28058
- return fs41.readdirSync(assetDir, { withFileTypes: true }).find(
28796
+ return fs43.readdirSync(assetDir, { withFileTypes: true }).find(
28059
28797
  (entry) => entry.isFile() && (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts")) && entry.name.replace(/\.(tsx|ts)$/, "") === componentName
28060
28798
  )?.name;
28061
28799
  }
@@ -28074,7 +28812,7 @@ function getAllComponentNamesForConfig(config) {
28074
28812
  return [.../* @__PURE__ */ new Set([...staticUi, ...staticCustom, ...templateKeys, "tiptap"])].sort();
28075
28813
  }
28076
28814
  async function runUpdateCommand(components, options) {
28077
- const cwd = options.cwd ? path54.resolve(options.cwd) : process.cwd();
28815
+ const cwd = options.cwd ? path56.resolve(options.cwd) : process.cwd();
28078
28816
  const normalizedOnly = normalizeShadcnPresetOnly(options.only);
28079
28817
  validateShadcnPresetOptions(components, options);
28080
28818
  if (options.list) {
@@ -28118,8 +28856,8 @@ async function runUpdateCommand(components, options) {
28118
28856
  return;
28119
28857
  }
28120
28858
  const config = await resolveConfig(cwd);
28121
- const admin = path54.resolve(cwd, config.paths.admin);
28122
- if (!fs41.existsSync(admin)) {
28859
+ const admin = path56.resolve(cwd, config.paths.admin);
28860
+ if (!fs43.existsSync(admin)) {
28123
28861
  clack2.cancel(
28124
28862
  `Admin directory not found at ${config.paths.admin}. Run 'betterstart init' first.`
28125
28863
  );
@@ -28172,15 +28910,15 @@ async function runUpdateCommand(components, options) {
28172
28910
  }
28173
28911
  const { relPath, content } = applyNamespaceToTemplateEntry(entry, config, cwd);
28174
28912
  const baseDir = entry.base === "cwd" ? cwd : admin;
28175
- const destPath = path54.join(baseDir, relPath);
28176
- if (entry.preserveExisting && fs41.existsSync(destPath)) {
28913
+ const destPath = path56.join(baseDir, relPath);
28914
+ if (entry.preserveExisting && fs43.existsSync(destPath)) {
28177
28915
  clack2.log.info(`Preserved ${relPath}`);
28178
28916
  updatedTemplateNames.add(name);
28179
28917
  skipped++;
28180
28918
  return false;
28181
28919
  }
28182
- fsExtra.ensureDirSync(path54.dirname(destPath));
28183
- fs41.writeFileSync(destPath, content, "utf-8");
28920
+ fsExtra.ensureDirSync(path56.dirname(destPath));
28921
+ fs43.writeFileSync(destPath, content, "utf-8");
28184
28922
  clack2.log.success(`Updated ${relPath}`);
28185
28923
  updatedTemplateNames.add(name);
28186
28924
  trackPackageDependencies(name);
@@ -28209,15 +28947,15 @@ async function runUpdateCommand(components, options) {
28209
28947
  }
28210
28948
  const namespace = config.frameworkConfig.next.namespace;
28211
28949
  const namespacedAssetFile = applyAdminNamespaceToPath(assetFile, namespace);
28212
- const destPath = path54.join(admin, "components", assetDirectory, namespacedAssetFile);
28213
- fsExtra.ensureDirSync(path54.dirname(destPath));
28214
- writeNamespacedFile(path54.join(assetDir, assetFile), destPath, namespace);
28950
+ const destPath = path56.join(admin, "components", assetDirectory, namespacedAssetFile);
28951
+ fsExtra.ensureDirSync(path56.dirname(destPath));
28952
+ writeNamespacedFile(path56.join(assetDir, assetFile), destPath, namespace);
28215
28953
  clack2.log.success(`Updated components/${assetDirectory}/${namespacedAssetFile}`);
28216
28954
  if (assetDirectory === "custom") {
28217
- const assetSubdir = path54.join(assetDir, name);
28218
- if (fs41.existsSync(assetSubdir) && fs41.statSync(assetSubdir).isDirectory()) {
28955
+ const assetSubdir = path56.join(assetDir, name);
28956
+ if (fs43.existsSync(assetSubdir) && fs43.statSync(assetSubdir).isDirectory()) {
28219
28957
  const namespacedName = applyAdminNamespaceToPath(name, namespace);
28220
- const destSubdir = path54.join(admin, "components", assetDirectory, namespacedName);
28958
+ const destSubdir = path56.join(admin, "components", assetDirectory, namespacedName);
28221
28959
  fsExtra.emptyDirSync(destSubdir);
28222
28960
  copyNamespacedDirectory(assetSubdir, destSubdir, namespace);
28223
28961
  clack2.log.success(`Updated components/${assetDirectory}/${namespacedName}/ (all files)`);
@@ -28245,17 +28983,17 @@ async function runUpdateCommand(components, options) {
28245
28983
  "content-editor"
28246
28984
  );
28247
28985
  const namespace = config.frameworkConfig.next.namespace;
28248
- const destBaseDir = path54.join(admin, "components", "custom", "content-editor");
28249
- if (!fs41.existsSync(srcBaseDir)) {
28986
+ const destBaseDir = path56.join(admin, "components", "custom", "content-editor");
28987
+ if (!fs43.existsSync(srcBaseDir)) {
28250
28988
  return false;
28251
28989
  }
28252
28990
  let copied = false;
28253
28991
  for (const directory of TIPTAP_CONTENT_EDITOR_DIRECTORIES) {
28254
- const srcDir = path54.join(srcBaseDir, directory);
28255
- if (!fs41.existsSync(srcDir)) {
28992
+ const srcDir = path56.join(srcBaseDir, directory);
28993
+ if (!fs43.existsSync(srcDir)) {
28256
28994
  continue;
28257
28995
  }
28258
- const destDir = path54.join(destBaseDir, applyAdminNamespaceToPath(directory, namespace));
28996
+ const destDir = path56.join(destBaseDir, applyAdminNamespaceToPath(directory, namespace));
28259
28997
  fsExtra.emptyDirSync(destDir);
28260
28998
  copyNamespacedDirectory(srcDir, destDir, namespace);
28261
28999
  copied = true;
@@ -28267,7 +29005,7 @@ async function runUpdateCommand(components, options) {
28267
29005
  updatedStaticNames.add(key);
28268
29006
  trackPackageDependencies("tiptap");
28269
29007
  updated++;
28270
- removeLegacyDirectory(path54.join(admin, "components", "custom", "tiptap"));
29008
+ removeLegacyDirectory(path56.join(admin, "components", "custom", "tiptap"));
28271
29009
  for (const dependencyName of TIPTAP_TEMPLATE_DEPENDENCIES) {
28272
29010
  writeNamedDependency(dependencyName);
28273
29011
  }
@@ -28327,8 +29065,8 @@ async function runUpdateCommand(components, options) {
28327
29065
  );
28328
29066
  }
28329
29067
  function removeLegacyDirectory(dirPath) {
28330
- if (fs41.existsSync(dirPath)) {
28331
- fs41.rmSync(dirPath, { recursive: true, force: true });
29068
+ if (fs43.existsSync(dirPath)) {
29069
+ fs43.rmSync(dirPath, { recursive: true, force: true });
28332
29070
  }
28333
29071
  }
28334
29072
  function validateShadcnPresetOptions(components, options) {
@@ -28372,22 +29110,22 @@ function runShadcnPresetUpdate({
28372
29110
  only
28373
29111
  }) {
28374
29112
  const namespace = resolveAdminNamespace(config.frameworkConfig.next.namespace);
28375
- const adminGlobalsPath = path54.join(cwd, config.paths.admin, namespace.globalsFile);
28376
- const componentsJsonPath = path54.join(cwd, "components.json");
29113
+ const adminGlobalsPath = path56.join(cwd, config.paths.admin, namespace.globalsFile);
29114
+ const componentsJsonPath = path56.join(cwd, "components.json");
28377
29115
  const shadcnBackupPath = `${componentsJsonPath}.bak`;
28378
29116
  const restoreAfterApplyPaths = [
28379
29117
  componentsJsonPath,
28380
29118
  shadcnBackupPath,
28381
- path54.join(cwd, config.paths.admin, "lib", "utils.ts"),
29119
+ path56.join(cwd, config.paths.admin, "lib", "utils.ts"),
28382
29120
  ...getHostProjectFilesToRestore(cwd)
28383
29121
  ];
28384
29122
  if (!preset) {
28385
29123
  clack2.cancel("--shadcn-preset requires a preset code, preset name, or preset URL.");
28386
29124
  process.exit(1);
28387
29125
  }
28388
- if (!fs41.existsSync(adminGlobalsPath)) {
29126
+ if (!fs43.existsSync(adminGlobalsPath)) {
28389
29127
  clack2.cancel(
28390
- `Admin globals file not found at ${path54.relative(cwd, adminGlobalsPath)}. Run 'betterstart update admin-globals' first.`
29128
+ `Admin globals file not found at ${path56.relative(cwd, adminGlobalsPath)}. Run 'betterstart update admin-globals' first.`
28391
29129
  );
28392
29130
  process.exit(1);
28393
29131
  }
@@ -28397,10 +29135,10 @@ function runShadcnPresetUpdate({
28397
29135
  snapshot: snapshotFile(filePath)
28398
29136
  }));
28399
29137
  clack2.intro("BetterStart Shadcn Preset");
28400
- clack2.log.info(`Applying preset to ${path54.join(config.paths.admin, "components/ui")}`);
29138
+ clack2.log.info(`Applying preset to ${path56.join(config.paths.admin, "components/ui")}`);
28401
29139
  let failed = false;
28402
29140
  try {
28403
- fs41.writeFileSync(
29141
+ fs43.writeFileSync(
28404
29142
  componentsJsonPath,
28405
29143
  `${JSON.stringify(createAdminShadcnComponentsJson(config), null, 2)}
28406
29144
  `,
@@ -28456,8 +29194,8 @@ function toPosixPath(value) {
28456
29194
  }
28457
29195
  function resolveLocalShadcnBin(cwd) {
28458
29196
  const binName = process.platform === "win32" ? "shadcn.cmd" : "shadcn";
28459
- const shadcnBin = path54.join(cwd, "node_modules", ".bin", binName);
28460
- if (!fs41.existsSync(shadcnBin)) {
29197
+ const shadcnBin = path56.join(cwd, "node_modules", ".bin", binName);
29198
+ if (!fs43.existsSync(shadcnBin)) {
28461
29199
  clack2.cancel(
28462
29200
  `shadcn is not installed in this project. Run 'betterstart update-deps' and try again.`
28463
29201
  );
@@ -28478,29 +29216,29 @@ function getHostProjectFilesToRestore(cwd) {
28478
29216
  "app/globals.css",
28479
29217
  "src/app/globals.css"
28480
29218
  ];
28481
- return hostRelativePaths.map((relativePath) => path54.join(cwd, relativePath));
29219
+ return hostRelativePaths.map((relativePath) => path56.join(cwd, relativePath));
28482
29220
  }
28483
29221
  function snapshotFile(filePath) {
28484
- if (!fs41.existsSync(filePath)) {
29222
+ if (!fs43.existsSync(filePath)) {
28485
29223
  return { existed: false };
28486
29224
  }
28487
- return { existed: true, content: fs41.readFileSync(filePath, "utf-8") };
29225
+ return { existed: true, content: fs43.readFileSync(filePath, "utf-8") };
28488
29226
  }
28489
29227
  function restoreFile(filePath, snapshot) {
28490
29228
  if (snapshot.existed) {
28491
- fs41.writeFileSync(filePath, snapshot.content ?? "", "utf-8");
29229
+ fs43.writeFileSync(filePath, snapshot.content ?? "", "utf-8");
28492
29230
  return;
28493
29231
  }
28494
- if (fs41.existsSync(filePath)) {
28495
- fs41.rmSync(filePath, { force: true });
29232
+ if (fs43.existsSync(filePath)) {
29233
+ fs43.rmSync(filePath, { force: true });
28496
29234
  }
28497
29235
  }
28498
29236
 
28499
29237
  // adapters/next/commands/update-deps.ts
28500
- import path55 from "path";
29238
+ import path57 from "path";
28501
29239
  import * as clack3 from "@clack/prompts";
28502
29240
  async function runUpdateDepsCommand(options) {
28503
- const cwd = options.cwd ? path55.resolve(options.cwd) : process.cwd();
29241
+ const cwd = options.cwd ? path57.resolve(options.cwd) : process.cwd();
28504
29242
  clack3.intro("BetterStart Update Dependencies");
28505
29243
  const pm = detectPackageManager(cwd);
28506
29244
  clack3.log.info(`Package manager: ${pm}`);
@@ -28511,7 +29249,7 @@ async function runUpdateDepsCommand(options) {
28511
29249
  false
28512
29250
  );
28513
29251
  const cliDependencyPlan = getCliDependencySyncPlan(cwd);
28514
- const s = clack3.spinner();
29252
+ const s = spinner2();
28515
29253
  s.start("Installing dependencies...");
28516
29254
  const result = await installDependenciesAsync({
28517
29255
  cwd,
@@ -28534,26 +29272,26 @@ async function runUpdateDepsCommand(options) {
28534
29272
  }
28535
29273
 
28536
29274
  // adapters/next/commands/update-styles.ts
28537
- import fs42 from "fs";
28538
- import path56 from "path";
29275
+ import fs44 from "fs";
29276
+ import path58 from "path";
28539
29277
  import * as clack4 from "@clack/prompts";
28540
29278
  async function runUpdateStylesCommand(options) {
28541
- const cwd = options.cwd ? path56.resolve(options.cwd) : process.cwd();
29279
+ const cwd = options.cwd ? path58.resolve(options.cwd) : process.cwd();
28542
29280
  clack4.intro("BetterStart Update Styles");
28543
29281
  const config = await resolveConfig(cwd);
28544
29282
  const adminDir = config.paths.admin;
28545
29283
  const namespace = resolveAdminNamespace(config.frameworkConfig.next.namespace);
28546
- const targetPath = path56.join(cwd, adminDir, namespace.globalsFile);
28547
- if (!fs42.existsSync(targetPath)) {
28548
- clack4.cancel(`${namespace.globalsFile} not found at ${path56.relative(cwd, targetPath)}`);
29284
+ const targetPath = path58.join(cwd, adminDir, namespace.globalsFile);
29285
+ if (!fs44.existsSync(targetPath)) {
29286
+ clack4.cancel(`${namespace.globalsFile} not found at ${path58.relative(cwd, targetPath)}`);
28549
29287
  process.exit(1);
28550
29288
  }
28551
- fs42.writeFileSync(
29289
+ fs44.writeFileSync(
28552
29290
  targetPath,
28553
29291
  applyAdminNamespaceToContent(readTemplate("admin-globals.css"), namespace.segment),
28554
29292
  "utf-8"
28555
29293
  );
28556
- clack4.log.success(`Updated ${path56.relative(cwd, targetPath)}`);
29294
+ clack4.log.success(`Updated ${path58.relative(cwd, targetPath)}`);
28557
29295
  clack4.outro("Styles updated");
28558
29296
  }
28559
29297