betterstart-cli 0.0.27 → 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) {
@@ -3068,26 +3105,20 @@ async function resolvePasswordEnvValue(options) {
3068
3105
  options.overwriteEnvKeys.add(options.key);
3069
3106
  return result.trim();
3070
3107
  }
3071
- async function collectResendConfig(cwd, presetApiKey) {
3108
+ async function collectResendConfig(cwd) {
3072
3109
  const overwriteEnvKeys = /* @__PURE__ */ new Set();
3073
- let apiKey;
3074
- if (presetApiKey?.trim()) {
3075
- apiKey = presetApiKey.trim();
3076
- overwriteEnvKeys.add("BETTERSTART_RESEND_API_KEY");
3077
- } else {
3078
- apiKey = await resolvePasswordEnvValue({
3079
- cwd,
3080
- key: "BETTERSTART_RESEND_API_KEY",
3081
- message: "Resend API key",
3082
- cancelMessage: "Resend configuration cancelled.",
3083
- overwriteEnvKeys,
3084
- validate(value) {
3085
- if (!value?.trim()) {
3086
- return "Enter a Resend API key.";
3087
- }
3110
+ const apiKey = await resolvePasswordEnvValue({
3111
+ cwd,
3112
+ key: "BETTERSTART_RESEND_API_KEY",
3113
+ message: "Resend API key",
3114
+ cancelMessage: "Resend configuration cancelled.",
3115
+ overwriteEnvKeys,
3116
+ validate(value) {
3117
+ if (!value?.trim()) {
3118
+ return "Enter a Resend API key.";
3088
3119
  }
3089
- });
3090
- }
3120
+ }
3121
+ });
3091
3122
  const fromAddress = await resolveTextEnvValue({
3092
3123
  cwd,
3093
3124
  key: "BETTERSTART_EMAIL_FROM",
@@ -3203,12 +3234,45 @@ async function promptR2Config(cwd) {
3203
3234
  updatedEnvKeys: envResult.updated
3204
3235
  };
3205
3236
  }
3206
- async function collectIntegrationConfig(cwd, integrationIds, options) {
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
+ }
3270
+ async function collectIntegrationConfig(cwd, integrationIds) {
3207
3271
  const sections = [];
3208
3272
  const overwriteKeys = /* @__PURE__ */ new Set();
3209
3273
  for (const integrationId of resolveIntegrationInstallOrder(integrationIds)) {
3210
3274
  const definition = getIntegrationDefinition(integrationId);
3211
- const collected = definition.configure === "resend" ? await collectResendConfig(cwd, options?.resendApiKey) : 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;
3212
3276
  if (!collected) {
3213
3277
  continue;
3214
3278
  }
@@ -3242,6 +3306,17 @@ async function configureIntegration(definition, cwd, interactive) {
3242
3306
  updatedEnvKeys: envResult2.updated
3243
3307
  };
3244
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
+ }
3245
3320
  const envResult = appendEnvVars(cwd, definition.envSections);
3246
3321
  return {
3247
3322
  configured: true,
@@ -4240,9 +4315,9 @@ function getReadFieldType(field) {
4240
4315
  }
4241
4316
  return getFieldType(field, "output");
4242
4317
  }
4243
- function collectReadSelectFields(fields, path57 = []) {
4318
+ function collectReadSelectFields(fields, path59 = []) {
4244
4319
  const matches = fields.flatMap((field) => {
4245
- const currentPath = [...path57, field.name];
4320
+ const currentPath = [...path59, field.name];
4246
4321
  const matches2 = field.type === "select" ? [{ field, path: currentPath }] : [];
4247
4322
  if (field.fields) {
4248
4323
  matches2.push(...collectReadSelectFields(field.fields, currentPath));
@@ -5289,14 +5364,14 @@ function buildStepsConstant(steps) {
5289
5364
  ${entries.join(",\n")}
5290
5365
  ]`;
5291
5366
  }
5292
- function buildComponentSource(p21) {
5367
+ function buildComponentSource(p22) {
5293
5368
  const providerOpen = ` <NuqsAdapter>
5294
5369
  <React.Suspense fallback={null}>
5295
- <${p21.pascal}FormInner />
5370
+ <${p22.pascal}FormInner />
5296
5371
  </React.Suspense>
5297
5372
  </NuqsAdapter>`;
5298
5373
  const exportWrapper = `
5299
- export function ${p21.pascal}Form() {
5374
+ export function ${p22.pascal}Form() {
5300
5375
  return (
5301
5376
  ${providerOpen}
5302
5377
  )
@@ -5305,22 +5380,22 @@ ${providerOpen}
5305
5380
  return `'use client'
5306
5381
 
5307
5382
  import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'
5308
- import { ChevronLeft, ChevronRight${p21.hasListFields ? ", Trash2" : ""} } from 'lucide-react'
5383
+ import { ChevronLeft, ChevronRight${p22.hasListFields ? ", Trash2" : ""} } from 'lucide-react'
5309
5384
  import { createParser, useQueryState } from 'nuqs'
5310
5385
  import { NuqsAdapter } from 'nuqs/adapters/next/app'
5311
5386
  import * as React from 'react'
5312
- ${p21.rhfImport}
5387
+ ${p22.rhfImport}
5313
5388
  import { z } from 'zod/v3'
5314
- import { create${p21.pascal}Submission } from '@admin/actions/${p21.actionImportPath}'
5389
+ import { create${p22.pascal}Submission } from '@admin/actions/${p22.actionImportPath}'
5315
5390
 
5316
5391
  const formSchema = z.object({
5317
- ${p21.zodFields}
5392
+ ${p22.zodFields}
5318
5393
  })
5319
5394
 
5320
5395
  type FormValues = z.infer<typeof formSchema>
5321
5396
  ${buildFieldErrorHelper()}
5322
5397
 
5323
- ${p21.stepsConst}
5398
+ ${p22.stepsConst}
5324
5399
 
5325
5400
  const stepParser = createParser({
5326
5401
  parse(value) {
@@ -5338,7 +5413,7 @@ const stepParser = createParser({
5338
5413
  }
5339
5414
  }).withDefault(0)
5340
5415
 
5341
- function ${p21.pascal}FormInner() {
5416
+ function ${p22.pascal}FormInner() {
5342
5417
  const [currentStep, setCurrentStep] = useQueryState('step', stepParser)
5343
5418
  const [submitted, setSubmitted] = React.useState(false)
5344
5419
  const [submitting, startSubmitTransition] = React.useTransition()
@@ -5346,11 +5421,11 @@ function ${p21.pascal}FormInner() {
5346
5421
  const form = useForm<FormValues>({
5347
5422
  resolver: standardSchemaResolver(formSchema),
5348
5423
  defaultValues: {
5349
- ${p21.defaults}
5424
+ ${p22.defaults}
5350
5425
  },
5351
5426
  })
5352
5427
 
5353
- ${p21.fieldArraySetup}${p21.watchSetup}
5428
+ ${p22.fieldArraySetup}${p22.watchSetup}
5354
5429
  async function handleNext() {
5355
5430
  const stepFields = STEPS[currentStep].fields as (keyof FormValues)[]
5356
5431
  const isValid = await form.trigger(stepFields, { shouldFocus: true })
@@ -5366,9 +5441,9 @@ ${p21.fieldArraySetup}${p21.watchSetup}
5366
5441
  function onSubmit(values: FormValues) {
5367
5442
  startSubmitTransition(async () => {
5368
5443
  try {
5369
- const result = await create${p21.pascal}Submission(values)
5444
+ const result = await create${p22.pascal}Submission(values)
5370
5445
  if (result.success) {
5371
- ${p21.successHandler}
5446
+ ${p22.successHandler}
5372
5447
  } else {
5373
5448
  form.setError('root', { message: result.error || 'Something went wrong' })
5374
5449
  }
@@ -5382,7 +5457,7 @@ ${p21.fieldArraySetup}${p21.watchSetup}
5382
5457
  return (
5383
5458
  <div className="rounded-sm border p-6 text-center">
5384
5459
  <h3 className="text-lg font-semibold">Thank you!</h3>
5385
- <p className="mt-2 text-muted-foreground">${p21.successMessage}</p>
5460
+ <p className="mt-2 text-muted-foreground">${p22.successMessage}</p>
5386
5461
  </div>
5387
5462
  )
5388
5463
  }
@@ -5399,7 +5474,7 @@ ${p21.fieldArraySetup}${p21.watchSetup}
5399
5474
 
5400
5475
  {/* Step content */}
5401
5476
  <div key={currentStep} className="animate-in fade-in duration-300 space-y-6">
5402
- ${p21.stepContentBlocks}
5477
+ ${p22.stepContentBlocks}
5403
5478
  </div>
5404
5479
 
5405
5480
  {form.formState.errors.root && (
@@ -5433,7 +5508,7 @@ ${p21.stepContentBlocks}
5433
5508
  onClick={() => form.handleSubmit(onSubmit)()}
5434
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"
5435
5510
  >
5436
- {submitting ? 'Submitting...' : ${p21.submitText}}
5511
+ {submitting ? 'Submitting...' : ${p22.submitText}}
5437
5512
  </button>
5438
5513
  )}
5439
5514
  </div>
@@ -13537,14 +13612,14 @@ function safeIdentifier(value, fallback) {
13537
13612
  const base = pascal ? `${pascal.charAt(0).toLowerCase()}${pascal.slice(1)}` : fallback;
13538
13613
  return /^[A-Za-z_$]/.test(base) ? base : fallback;
13539
13614
  }
13540
- function pathExpression(path57) {
13541
- 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(".")}\``;
13542
13617
  }
13543
- function pathNameProp(path57) {
13544
- if (path57.every((part) => typeof part === "string")) {
13545
- 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(".")}"`;
13546
13621
  }
13547
- return `{${pathExpression(path57)}}`;
13622
+ return `{${pathExpression(path59)}}`;
13548
13623
  }
13549
13624
  function findTitlePath(fields) {
13550
13625
  const stringTypes = ["string", "varchar", "text"];
@@ -13566,23 +13641,23 @@ function findTitlePath(fields) {
13566
13641
  function requiredLeafPaths(fields, prefix = []) {
13567
13642
  return fields.flatMap((field) => {
13568
13643
  if (field.hidden) return [];
13569
- const path57 = [...prefix, field.name];
13644
+ const path59 = [...prefix, field.name];
13570
13645
  if ((field.type === "group" || field.type === "section") && field.fields?.length) {
13571
- return requiredLeafPaths(field.fields, path57);
13646
+ return requiredLeafPaths(field.fields, path59);
13572
13647
  }
13573
- if (field.required) return [path57.join(".")];
13648
+ if (field.required) return [path59.join(".")];
13574
13649
  return [];
13575
13650
  });
13576
13651
  }
13577
13652
  function validationTriggerExpression(basePath, requiredPaths) {
13578
13653
  if (requiredPaths.length === 0) return `\`${basePath}.\${expandedIndex}\` as never`;
13579
- const paths = requiredPaths.map((path57) => `\`${basePath}.\${expandedIndex}.${path57}\``).join(", ");
13654
+ const paths = requiredPaths.map((path59) => `\`${basePath}.\${expandedIndex}.${path59}\``).join(", ");
13580
13655
  return `[${paths}] as never`;
13581
13656
  }
13582
- function renderTextInputField(field, indent, label, nestedHint, path57) {
13657
+ function renderTextInputField(field, indent, label, nestedHint, path59) {
13583
13658
  return `${indent}<FormField
13584
13659
  ${indent} control={form.control}
13585
- ${indent} name=${pathNameProp(path57)}
13660
+ ${indent} name=${pathNameProp(path59)}
13586
13661
  ${indent} render={({ field: formField }) => (
13587
13662
  ${indent} <FormItem${formItemProps(field)}>
13588
13663
  ${indent} ${labelWithDescription(formLabel(field, label), nestedHint, `${indent} `)}
@@ -13594,11 +13669,11 @@ ${indent} </FormItem>
13594
13669
  ${indent} )}
13595
13670
  ${indent}/>`;
13596
13671
  }
13597
- function renderNestedField(field, indent, path57, depth) {
13672
+ function renderNestedField(field, indent, path59, depth) {
13598
13673
  const nestedLabel = field.label || field.name;
13599
13674
  const nestedHint = field.hint ? `<FormDescription>${field.hint}</FormDescription>` : "";
13600
13675
  if ((field.type === "group" || field.type === "section") && field.fields?.length) {
13601
- 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");
13602
13677
  if (!groupFields) return "";
13603
13678
  const heading = nestedLabel && nestedLabel !== field.name ? `${indent}<h3 className="text-base font-medium">${nestedLabel}</h3>
13604
13679
  ` : "";
@@ -13608,7 +13683,7 @@ ${groupFields}
13608
13683
  ${indent}</div>`;
13609
13684
  }
13610
13685
  if (field.type === "list" && field.fields?.length) {
13611
- return renderNestedObjectListField(field, indent, nestedLabel, path57, depth);
13686
+ return renderNestedObjectListField(field, indent, nestedLabel, path59, depth);
13612
13687
  }
13613
13688
  if (field.type === "list") {
13614
13689
  const hideLabelProp = field.label ? "" : `
@@ -13616,7 +13691,7 @@ ${indent} hideLabel`;
13616
13691
  const descriptionProp = field.hint ? `
13617
13692
  ${indent} description={${JSON.stringify(field.hint)}}` : "";
13618
13693
  return `${indent}<DynamicListField
13619
- ${indent} name={${pathExpression(path57)}}
13694
+ ${indent} name={${pathExpression(path59)}}
13620
13695
  ${indent} label="${nestedLabel}"${hideLabelProp}${descriptionProp}
13621
13696
  ${indent} disabled={isPending}${field.maxItems ? `
13622
13697
  ${indent} maxItems={${field.maxItems}}` : ""}
@@ -13626,7 +13701,7 @@ ${indent}/>`;
13626
13701
  if (field.type === "boolean") {
13627
13702
  return `${indent}<FormField
13628
13703
  ${indent} control={form.control}
13629
- ${indent} name=${pathNameProp(path57)}
13704
+ ${indent} name=${pathNameProp(path59)}
13630
13705
  ${indent} render={({ field: formField }) => (
13631
13706
  ${indent} <FormItem${formItemProps(field, "flex flex-row items-start space-x-3 space-y-0")}>
13632
13707
  ${indent} <FormControl>
@@ -13641,7 +13716,7 @@ ${indent}/>`;
13641
13716
  const acceptProp = field.type === "image" ? ' accept="image/*"' : field.type === "video" ? ' accept="video/*"' : "";
13642
13717
  return `${indent}<FormField
13643
13718
  ${indent} control={form.control}
13644
- ${indent} name=${pathNameProp(path57)}
13719
+ ${indent} name=${pathNameProp(path59)}
13645
13720
  ${indent} render={({ field: formField }) => (
13646
13721
  ${indent} <FormItem${formItemProps(field)}>
13647
13722
  ${indent} ${labelWithDescription(formLabel(field, nestedLabel), nestedHint, `${indent} `)}
@@ -13656,7 +13731,7 @@ ${indent}/>`;
13656
13731
  if (field.type === "icon") {
13657
13732
  return `${indent}<FormField
13658
13733
  ${indent} control={form.control}
13659
- ${indent} name=${pathNameProp(path57)}
13734
+ ${indent} name=${pathNameProp(path59)}
13660
13735
  ${indent} render={({ field: formField }) => (
13661
13736
  ${indent} <FormItem${formItemProps(field)}>
13662
13737
  ${indent} ${labelWithDescription(formLabel(field, nestedLabel), nestedHint, `${indent} `)}
@@ -13673,7 +13748,7 @@ ${indent}/>`;
13673
13748
  const emptyValue = field.required ? "undefined" : "null";
13674
13749
  return `${indent}<FormField
13675
13750
  ${indent} control={form.control}
13676
- ${indent} name=${pathNameProp(path57)}
13751
+ ${indent} name=${pathNameProp(path59)}
13677
13752
  ${indent} render={({ field: formField }) => (
13678
13753
  ${indent} <FormItem${formItemProps(field)}>
13679
13754
  ${indent} ${labelWithDescription(formLabel(field, nestedLabel), nestedHint, `${indent} `)}
@@ -13698,16 +13773,16 @@ ${indent} </FormItem>
13698
13773
  ${indent} )}
13699
13774
  ${indent}/>`;
13700
13775
  }
13701
- return renderTextInputField(field, indent, nestedLabel, nestedHint, path57);
13776
+ return renderTextInputField(field, indent, nestedLabel, nestedHint, path59);
13702
13777
  }
13703
- function renderNestedObjectListField(field, indent, label, path57, depth) {
13778
+ function renderNestedObjectListField(field, indent, label, path59, depth) {
13704
13779
  const singularLabel = singularize(label);
13705
13780
  const itemIndexVar = `${safeIdentifier(field.name, "nestedList")}Index${depth}`;
13706
13781
  const titlePath = findTitlePath(field.fields ?? []);
13707
13782
  const titlePathSuffix = titlePath ? `.${titlePath.join(".")}` : "";
13708
13783
  const renderTitleProp = titlePath ? `
13709
13784
  ${indent} renderTitle={(${itemIndexVar}) =>
13710
- ${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(
13711
13786
  "."
13712
13787
  )}.\${${itemIndexVar}}${titlePathSuffix}\` as never) || \`${singularLabel} \${${itemIndexVar} + 1}\`)
13713
13788
  ${indent} }` : "";
@@ -13715,7 +13790,7 @@ ${indent} }` : "";
13715
13790
  (child) => renderNestedField(
13716
13791
  child,
13717
13792
  `${indent} `,
13718
- [...path57, { expression: itemIndexVar }, child.name],
13793
+ [...path59, { expression: itemIndexVar }, child.name],
13719
13794
  depth + 1
13720
13795
  )
13721
13796
  ).filter(Boolean).join("\n");
@@ -13730,7 +13805,7 @@ ${indent} maxItems={${field.maxItems}}` : "";
13730
13805
  const validatePathsProp = validatePaths.length > 0 ? `
13731
13806
  ${indent} validatePaths={${JSON.stringify(validatePaths)}}` : "";
13732
13807
  return `${indent}<NestedObjectListField
13733
- ${indent} name={${pathExpression(path57)}}
13808
+ ${indent} name={${pathExpression(path59)}}
13734
13809
  ${indent} label={${JSON.stringify(label)}}
13735
13810
  ${indent} singularLabel={${JSON.stringify(singularLabel)}}
13736
13811
  ${indent} defaultValue={${defaultItem}}
@@ -17374,7 +17449,7 @@ function applyTextEdits(content, edits) {
17374
17449
  content
17375
17450
  );
17376
17451
  }
17377
- function collectPreviewChanges(path57, beforeContent, afterContent) {
17452
+ function collectPreviewChanges(path59, beforeContent, afterContent) {
17378
17453
  const beforeLines = beforeContent.split("\n");
17379
17454
  const afterLines = afterContent.split("\n");
17380
17455
  const maxLength = Math.max(beforeLines.length, afterLines.length);
@@ -17386,7 +17461,7 @@ function collectPreviewChanges(path57, beforeContent, afterContent) {
17386
17461
  continue;
17387
17462
  }
17388
17463
  changes.push({
17389
- path: path57,
17464
+ path: path59,
17390
17465
  line: index + 1,
17391
17466
  before: before.trim(),
17392
17467
  after: after.trim()
@@ -18997,7 +19072,7 @@ async function runAddCommand(items, options) {
18997
19072
 
18998
19073
  // adapters/next/commands/add-field.ts
18999
19074
  import path30 from "path";
19000
- import * as p7 from "@clack/prompts";
19075
+ import * as p8 from "@clack/prompts";
19001
19076
 
19002
19077
  // core-engine/schema/add-field.ts
19003
19078
  import fs23 from "fs";
@@ -19649,7 +19724,7 @@ function getTabFields(tab) {
19649
19724
  // adapters/next/commands/generate.ts
19650
19725
  import fs24 from "fs";
19651
19726
  import path29 from "path";
19652
- import * as p5 from "@clack/prompts";
19727
+ import * as p6 from "@clack/prompts";
19653
19728
 
19654
19729
  // core-engine/snapshots/rename-detector.ts
19655
19730
  function levenshtein(a, b) {
@@ -19741,6 +19816,43 @@ function diffSchemas(before, after) {
19741
19816
  };
19742
19817
  }
19743
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
+
19744
19856
  // adapters/next/commands/generate.ts
19745
19857
  function isInteractiveSession2() {
19746
19858
  return Boolean(process.stdin.isTTY) && process.env.CI !== "true";
@@ -19807,7 +19919,7 @@ async function ensureMarkdownRendererDependencies(cwd, required) {
19807
19919
  );
19808
19920
  if (dependencies.length === 0 && devDependencies.length === 0) return;
19809
19921
  const pm = detectPackageManager(cwd);
19810
- const s = p5.spinner();
19922
+ const s = spinner2();
19811
19923
  s.start("Installing markdown renderer dependencies");
19812
19924
  const result = await installDependenciesAsync({
19813
19925
  cwd,
@@ -19866,11 +19978,11 @@ function printSchemaDiff(scope, loaded, cwd) {
19866
19978
  }
19867
19979
  }
19868
19980
  async function promptConfirm(message) {
19869
- const result = await p5.confirm({
19981
+ const result = await p6.confirm({
19870
19982
  message,
19871
19983
  initialValue: true
19872
19984
  });
19873
- if (p5.isCancel(result)) {
19985
+ if (p6.isCancel(result)) {
19874
19986
  throw new Error("Generation cancelled.");
19875
19987
  }
19876
19988
  return Boolean(result);
@@ -20283,34 +20395,34 @@ async function runGenerateCommand(schemaName, options) {
20283
20395
  }
20284
20396
 
20285
20397
  // adapters/next/commands/schema-prompts.ts
20286
- import * as p6 from "@clack/prompts";
20398
+ import * as p7 from "@clack/prompts";
20287
20399
  function isInteractiveSession3() {
20288
20400
  return Boolean(process.stdin.isTTY) && process.env.CI !== "true";
20289
20401
  }
20290
20402
  function fail(message) {
20291
- p6.log.error(message);
20403
+ p7.log.error(message);
20292
20404
  process.exit(1);
20293
20405
  }
20294
20406
  function cancel2(context) {
20295
- p6.cancel(context.cancelMessage);
20407
+ p7.cancel(context.cancelMessage);
20296
20408
  process.exit(0);
20297
20409
  }
20298
20410
  function cancelIfNeeded(context, value) {
20299
- if (p6.isCancel(value)) {
20411
+ if (p7.isCancel(value)) {
20300
20412
  cancel2(context);
20301
20413
  }
20302
20414
  return value;
20303
20415
  }
20304
20416
  async function promptConfirm2(context, message, initialValue = true) {
20305
- const confirmed = await p6.confirm({ message, initialValue });
20306
- if (p6.isCancel(confirmed)) {
20417
+ const confirmed = await p7.confirm({ message, initialValue });
20418
+ if (p7.isCancel(confirmed)) {
20307
20419
  cancel2(context);
20308
20420
  }
20309
20421
  return Boolean(confirmed);
20310
20422
  }
20311
20423
  async function promptText(context, message, optionsOrDefaultValue) {
20312
20424
  const options = typeof optionsOrDefaultValue === "string" ? { defaultValue: optionsOrDefaultValue } : optionsOrDefaultValue ?? {};
20313
- const value = await p6.text({
20425
+ const value = await p7.text({
20314
20426
  message,
20315
20427
  defaultValue: options.defaultValue,
20316
20428
  placeholder: options.placeholder,
@@ -20327,7 +20439,7 @@ async function promptText(context, message, optionsOrDefaultValue) {
20327
20439
  return String(cancelIfNeeded(context, value)).trim();
20328
20440
  }
20329
20441
  async function promptOptionalText(context, message) {
20330
- const value = await p6.text({
20442
+ const value = await p7.text({
20331
20443
  message,
20332
20444
  placeholder: "Leave blank to skip"
20333
20445
  });
@@ -20335,7 +20447,7 @@ async function promptOptionalText(context, message) {
20335
20447
  return result || void 0;
20336
20448
  }
20337
20449
  async function promptSelectValue(context, message, options, initialValue) {
20338
- const value = await p6.select({
20450
+ const value = await p7.select({
20339
20451
  message,
20340
20452
  options,
20341
20453
  initialValue
@@ -20554,7 +20666,7 @@ async function shouldPromptRequired(type) {
20554
20666
  return !["group", "section", "tabs", "separator"].includes(type);
20555
20667
  }
20556
20668
  async function promptFormFileOptions(context) {
20557
- const selected = await p6.multiselect({
20669
+ const selected = await p7.multiselect({
20558
20670
  message: "Field options",
20559
20671
  options: [
20560
20672
  { value: "required", label: "Required field?" },
@@ -20593,7 +20705,7 @@ async function promptFieldOptions(context, kind, type, creatable) {
20593
20705
  try {
20594
20706
  return parseInteractiveOptionsList(value);
20595
20707
  } catch (error) {
20596
- p6.log.error(error instanceof Error ? error.message : String(error));
20708
+ p7.log.error(error instanceof Error ? error.message : String(error));
20597
20709
  }
20598
20710
  }
20599
20711
  }
@@ -20711,7 +20823,7 @@ async function promptChildFields(context, kind, schemasDir, scope, fields, depth
20711
20823
  const addChild = fields.length === 0 ? await promptConfirm2(context, "Add a child field?", true) : await promptConfirm2(context, "Add another child field?", false);
20712
20824
  if (!addChild) {
20713
20825
  if (fields.length === 0) {
20714
- p6.log.warn("Containers need at least one child field.");
20826
+ p7.log.warn("Containers need at least one child field.");
20715
20827
  continue;
20716
20828
  }
20717
20829
  return;
@@ -20736,7 +20848,7 @@ async function promptTabsField(context, kind, schemasDir, scope, name, label, de
20736
20848
  const addTab = field.tabs.length === 0 ? await promptConfirm2(context, "Add a tab?", true) : await promptConfirm2(context, "Add another tab?", false);
20737
20849
  if (!addTab) {
20738
20850
  if (field.tabs.length === 0) {
20739
- p6.log.warn("Tabs need at least one tab.");
20851
+ p7.log.warn("Tabs need at least one tab.");
20740
20852
  continue;
20741
20853
  }
20742
20854
  return field;
@@ -20761,7 +20873,7 @@ async function promptTabsField(context, kind, schemasDir, scope, name, label, de
20761
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);
20762
20874
  if (!addChild) {
20763
20875
  if (mainFields.length + sidebarFields.length === 0) {
20764
- p6.log.warn("Tabs need at least one child field.");
20876
+ p7.log.warn("Tabs need at least one child field.");
20765
20877
  continue;
20766
20878
  }
20767
20879
  break;
@@ -20832,7 +20944,7 @@ async function applyAdvancedOptions(context, field, kind, type, options) {
20832
20944
  if (Number.isInteger(parsed) && parsed > 0) {
20833
20945
  record.length = parsed;
20834
20946
  } else {
20835
- p6.log.warn("Skipped invalid length.");
20947
+ p7.log.warn("Skipped invalid length.");
20836
20948
  }
20837
20949
  }
20838
20950
  }
@@ -20990,11 +21102,11 @@ function hasNonInteractiveFieldOptions(options) {
20990
21102
  );
20991
21103
  }
20992
21104
  function fail2(message) {
20993
- p7.log.error(message);
21105
+ p8.log.error(message);
20994
21106
  process.exit(1);
20995
21107
  }
20996
21108
  function cancel4(message = "Add field cancelled.") {
20997
- p7.cancel(message);
21109
+ p8.cancel(message);
20998
21110
  process.exit(0);
20999
21111
  }
21000
21112
  function schemaNameFromLoaded(loaded) {
@@ -21099,7 +21211,7 @@ function printPreview(loaded, owner, firstTimeGeneration, insertion, options) {
21099
21211
  } else {
21100
21212
  lines.push("schema integration: none");
21101
21213
  }
21102
- p7.note(
21214
+ p8.note(
21103
21215
  `${lines.join("\n")}
21104
21216
 
21105
21217
  ${stringifyProjectJson(insertion.field).trim()}`,
@@ -21132,7 +21244,7 @@ Re-run with --yes to confirm these warning cases.`);
21132
21244
  }
21133
21245
  return;
21134
21246
  }
21135
- p7.note(warnings.join("\n"), "Warnings");
21247
+ p8.note(warnings.join("\n"), "Warnings");
21136
21248
  const confirmed = await promptConfirm3("Continue with these warnings?", true);
21137
21249
  if (!confirmed) {
21138
21250
  cancel4();
@@ -21251,7 +21363,7 @@ ${validationErrors.map((error) => ` - ${error}`).join("\n")}`
21251
21363
  }
21252
21364
  }
21253
21365
  writeAuthoredGeneratedSchema(loaded);
21254
- p7.log.success(`Updated ${path30.relative(cwd, loaded.filePath)}`);
21366
+ p8.log.success(`Updated ${path30.relative(cwd, loaded.filePath)}`);
21255
21367
  try {
21256
21368
  await runGenerateCommand(selectedSchemaName, {
21257
21369
  force: false,
@@ -21262,8 +21374,8 @@ ${validationErrors.map((error) => ` - ${error}`).join("\n")}`
21262
21374
  cwd
21263
21375
  });
21264
21376
  } catch (error) {
21265
- p7.log.error(error instanceof Error ? error.message : String(error));
21266
- p7.log.info(
21377
+ p8.log.error(error instanceof Error ? error.message : String(error));
21378
+ p8.log.info(
21267
21379
  `Schema JSON was kept. Re-run \`betterstart generate ${selectedSchemaName}${options.skipMigration ? " --skip-migration" : ""}\` after resolving the issue.`
21268
21380
  );
21269
21381
  process.exit(1);
@@ -21273,7 +21385,7 @@ ${validationErrors.map((error) => ` - ${error}`).join("\n")}`
21273
21385
  // adapters/next/commands/create.ts
21274
21386
  import fs25 from "fs";
21275
21387
  import path31 from "path";
21276
- import * as p8 from "@clack/prompts";
21388
+ import * as p9 from "@clack/prompts";
21277
21389
  var CREATE_PROMPT_CONTEXT = {
21278
21390
  cancelMessage: "Create schema cancelled."
21279
21391
  };
@@ -21464,7 +21576,7 @@ async function promptTabSlotFields(kind, schemasDir, titleField) {
21464
21576
  );
21465
21577
  if (!addField) {
21466
21578
  if (!hasFields) {
21467
- p8.log.warn("Tabs need at least one child field.");
21579
+ p9.log.warn("Tabs need at least one child field.");
21468
21580
  continue;
21469
21581
  }
21470
21582
  return { main, sidebar };
@@ -21512,7 +21624,7 @@ async function promptSchemaTab(kind, schemasDir, titleField, existingTabs) {
21512
21624
  while (fields.length === 0) {
21513
21625
  await promptAdditionalSchemaFields(kind, schemasDir, fields);
21514
21626
  if (fields.length === 0) {
21515
- p8.log.warn("Tabs need at least one child field.");
21627
+ p9.log.warn("Tabs need at least one child field.");
21516
21628
  }
21517
21629
  }
21518
21630
  }
@@ -21602,7 +21714,7 @@ async function promptFormSubmissionColumns(schema) {
21602
21714
  }
21603
21715
  const existingColumnNames = schema.columns ? new Set(schema.columns.map((column) => column.accessorKey)) : void 0;
21604
21716
  const initialValues = existingColumnNames ? fields.filter((field) => existingColumnNames.has(field.name)).map((field) => field.name) : fields.map((field) => field.name);
21605
- const selected = await p8.multiselect({
21717
+ const selected = await p9.multiselect({
21606
21718
  message: "Submission table columns",
21607
21719
  options: fields.map((field) => ({
21608
21720
  value: field.name,
@@ -21611,7 +21723,7 @@ async function promptFormSubmissionColumns(schema) {
21611
21723
  initialValues,
21612
21724
  required: false
21613
21725
  });
21614
- if (p8.isCancel(selected)) {
21726
+ if (p9.isCancel(selected)) {
21615
21727
  cancel2(CREATE_PROMPT_CONTEXT);
21616
21728
  }
21617
21729
  const selectedNames = new Set(selected);
@@ -21671,7 +21783,7 @@ function schemaFilePath(kind, schemasDir, schemaName) {
21671
21783
  return kind === "form" ? path31.join(schemasDir, "forms", `${schemaName}.json`) : path31.join(schemasDir, `${schemaName}.json`);
21672
21784
  }
21673
21785
  function printPreview2(loaded, cwd) {
21674
- p8.note(
21786
+ p9.note(
21675
21787
  `schema: ${loaded.name} (${loaded.kind})
21676
21788
  path: ${path31.relative(cwd, loaded.filePath)}
21677
21789
  owner: user
@@ -21726,7 +21838,7 @@ ${validationErrors.map((error) => ` - ${error}`).join("\n")}`
21726
21838
  }
21727
21839
  fs25.mkdirSync(path31.dirname(filePath), { recursive: true });
21728
21840
  writeAuthoredGeneratedSchema(loaded);
21729
- p8.log.success(`Created ${path31.relative(cwd, filePath)}`);
21841
+ p9.log.success(`Created ${path31.relative(cwd, filePath)}`);
21730
21842
  try {
21731
21843
  await runGenerateCommand(metadata.name, {
21732
21844
  force: false,
@@ -21737,8 +21849,8 @@ ${validationErrors.map((error) => ` - ${error}`).join("\n")}`
21737
21849
  cwd
21738
21850
  });
21739
21851
  } catch (error) {
21740
- p8.log.error(error instanceof Error ? error.message : String(error));
21741
- p8.log.info(
21852
+ p9.log.error(error instanceof Error ? error.message : String(error));
21853
+ p9.log.info(
21742
21854
  `Schema JSON was kept. Re-run \`betterstart generate ${metadata.name}${options.skipMigration ? " --skip-migration" : ""}\` after resolving the issue.`
21743
21855
  );
21744
21856
  process.exit(1);
@@ -21747,14 +21859,13 @@ ${validationErrors.map((error) => ` - ${error}`).join("\n")}`
21747
21859
 
21748
21860
  // adapters/next/commands/init.ts
21749
21861
  import { execFileSync as execFileSync5, spawn as spawn4 } from "child_process";
21750
- import fs37 from "fs";
21751
- import path48 from "path";
21752
- import * as p16 from "@clack/prompts";
21862
+ import fs39 from "fs";
21863
+ import path50 from "path";
21864
+ import * as p17 from "@clack/prompts";
21753
21865
 
21754
21866
  // adapters/next/init/prompts/database.ts
21755
21867
  import { execFileSync as execFileSync4 } from "child_process";
21756
- import { styleText } from "util";
21757
- import * as p9 from "@clack/prompts";
21868
+ import * as p10 from "@clack/prompts";
21758
21869
  import pc from "picocolors";
21759
21870
  var VERCEL_NEON_URL = "https://vercel.com/dashboard/integrations/checkout/neon";
21760
21871
  async function promptServices(options) {
@@ -21762,28 +21873,24 @@ async function promptServices(options) {
21762
21873
  const url2 = await promptConnectionString();
21763
21874
  return { provider: "manual", url: url2 };
21764
21875
  }
21765
- const choice = await p9.select({
21766
- message: `How would you like to connect your services?
21767
- ${styleText(
21768
- "dim",
21769
- "Database, Email, and File Storage"
21770
- )}`,
21876
+ const choice = await p10.select({
21877
+ message: "Connect a PostgreSQL Database",
21771
21878
  options: [
21772
21879
  {
21773
21880
  value: "vercel",
21774
21881
  label: "Vercel",
21775
- hint: "provision Neon Postgres + Resend email via the Vercel CLI"
21882
+ hint: "Automatically provision a Neon Postgres database"
21776
21883
  },
21777
21884
  {
21778
21885
  value: "manual",
21779
21886
  label: "Manual",
21780
- hint: "enter connection details yourself"
21887
+ hint: "Manually enter connection details"
21781
21888
  }
21782
21889
  ],
21783
21890
  initialValue: "vercel"
21784
21891
  });
21785
- if (p9.isCancel(choice)) {
21786
- p9.cancel("Setup cancelled.");
21892
+ if (p10.isCancel(choice)) {
21893
+ p10.cancel("Setup cancelled.");
21787
21894
  process.exit(0);
21788
21895
  }
21789
21896
  if (choice === "vercel") {
@@ -21794,12 +21901,12 @@ ${styleText(
21794
21901
  }
21795
21902
  function openBrowserVercelNeon() {
21796
21903
  openBrowser(VERCEL_NEON_URL);
21797
- p9.log.info(
21904
+ p10.log.info(
21798
21905
  `Opening Vercel... Create a Neon Postgres database, then copy the ${pc.cyan("DATABASE_URL")} from the dashboard.`
21799
21906
  );
21800
21907
  }
21801
21908
  async function promptConnectionString() {
21802
- const input = await p9.text({
21909
+ const input = await p10.text({
21803
21910
  message: "Paste your PostgreSQL connection string",
21804
21911
  placeholder: "postgres://user:pass@host/db",
21805
21912
  validate(val) {
@@ -21813,8 +21920,8 @@ async function promptConnectionString() {
21813
21920
  }
21814
21921
  }
21815
21922
  });
21816
- if (p9.isCancel(input)) {
21817
- p9.cancel("Setup cancelled.");
21923
+ if (p10.isCancel(input)) {
21924
+ p10.cancel("Setup cancelled.");
21818
21925
  process.exit(0);
21819
21926
  }
21820
21927
  return input.replace(/^['"]|['"]$/g, "").trim();
@@ -21834,9 +21941,9 @@ function openBrowser(url) {
21834
21941
  }
21835
21942
 
21836
21943
  // adapters/next/init/prompts/plugins.ts
21837
- import { styleText as styleText2 } from "util";
21838
- import * as p10 from "@clack/prompts";
21839
- async function promptPlugins(cwd, options) {
21944
+ import { styleText } from "util";
21945
+ import * as p11 from "@clack/prompts";
21946
+ async function promptPlugins(cwd, options = {}) {
21840
21947
  const sections = [];
21841
21948
  const overwriteKeys = /* @__PURE__ */ new Set();
21842
21949
  const mergeIntegrationConfig = (collected) => {
@@ -21845,90 +21952,94 @@ async function promptPlugins(cwd, options) {
21845
21952
  overwriteKeys.add(key);
21846
21953
  }
21847
21954
  };
21848
- const selectedPlugins = await p10.multiselect({
21849
- message: `Select presets
21850
- ${styleText2("dim", "Press [Spacebar] to select/unselect")}`,
21955
+ const storage = await p11.select({
21956
+ message: "Choose a file storage",
21851
21957
  options: [
21852
21958
  {
21853
- value: "blog",
21854
- label: "Blog",
21855
- hint: "Posts"
21856
- }
21857
- ],
21858
- required: false,
21859
- initialValues: ["blog"]
21860
- });
21861
- if (p10.isCancel(selectedPlugins)) {
21862
- p10.cancel("Setup cancelled.");
21863
- process.exit(0);
21864
- }
21865
- const storage = await p10.select({
21866
- message: "Choose a file storage mode",
21867
- options: [
21959
+ value: "vercel-blob",
21960
+ label: "Vercel Blob"
21961
+ },
21868
21962
  {
21869
21963
  value: "r2",
21870
- label: "Cloudflare R2",
21871
- hint: "recommended for hosted/serverless production"
21964
+ label: "Cloudflare R2"
21872
21965
  },
21873
21966
  {
21874
21967
  value: "local",
21875
21968
  label: "Local filesystem (public/media)",
21876
- hint: "development or self-hosted persistent disk"
21969
+ hint: "\u26A0 For testing only"
21877
21970
  }
21878
21971
  ],
21879
- initialValue: "r2"
21972
+ initialValue: "vercel-blob"
21880
21973
  });
21881
- if (p10.isCancel(storage)) {
21882
- p10.cancel("Setup cancelled.");
21974
+ if (p11.isCancel(storage)) {
21975
+ p11.cancel("Setup cancelled.");
21883
21976
  process.exit(0);
21884
21977
  }
21885
21978
  if (storage === "r2") {
21886
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
+ }
21997
+ }
21998
+ const selectedPlugins = await p11.multiselect({
21999
+ message: "Select presets",
22000
+ options: [
22001
+ {
22002
+ value: "blog",
22003
+ label: "Blog",
22004
+ hint: "Posts"
22005
+ }
22006
+ ],
22007
+ required: false,
22008
+ initialValues: ["blog"]
22009
+ });
22010
+ if (p11.isCancel(selectedPlugins)) {
22011
+ p11.cancel("Setup cancelled.");
22012
+ process.exit(0);
21887
22013
  }
21888
22014
  const integrations = [];
21889
- if (options?.vercelResendApiKey) {
21890
- p10.log.info(`Email provider: Resend ${styleText2("dim", "(connected via Vercel)")}`);
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) {
21891
22028
  integrations.push("resend");
21892
- mergeIntegrationConfig(
21893
- await collectIntegrationConfig(cwd, ["resend"], {
21894
- resendApiKey: options.vercelResendApiKey
21895
- })
21896
- );
22029
+ mergeIntegrationConfig(await collectIntegrationConfig(cwd, ["resend"]));
21897
22030
  } else {
21898
- const selectedEmailProvider = await p10.select({
21899
- message: `Which email provider do you want to set up?
21900
- ${styleText2(
21901
- "dim",
21902
- "Auth + Forms email delivery"
21903
- )}`,
21904
- options: [
21905
- {
21906
- value: "resend",
21907
- label: "Resend"
21908
- },
21909
- {
21910
- value: "skip",
21911
- label: "Skip"
21912
- },
21913
- {
21914
- value: "cloudflare",
21915
- label: `Cloudflare ${styleText2("dim", "(coming soon)")}`,
21916
- disabled: true
21917
- }
21918
- ],
21919
- initialValue: "resend"
22031
+ sections.push({
22032
+ header: "Email (Resend)",
22033
+ vars: [
22034
+ { key: "BETTERSTART_RESEND_API_KEY", value: "" },
22035
+ { key: "BETTERSTART_EMAIL_FROM", value: "" }
22036
+ ]
21920
22037
  });
21921
- if (p10.isCancel(selectedEmailProvider)) {
21922
- p10.cancel("Setup cancelled.");
21923
- process.exit(0);
21924
- }
21925
- if (selectedEmailProvider === "resend") {
21926
- integrations.push("resend");
21927
- mergeIntegrationConfig(await collectIntegrationConfig(cwd, ["resend"]));
21928
- }
21929
22038
  }
21930
22039
  if (storage === "r2") {
21931
22040
  integrations.push("r2");
22041
+ } else if (storage === "vercel-blob") {
22042
+ integrations.push("vercel-blob");
21932
22043
  }
21933
22044
  return {
21934
22045
  plugins: selectedPlugins,
@@ -21939,10 +22050,10 @@ ${styleText2(
21939
22050
  }
21940
22051
 
21941
22052
  // adapters/next/init/prompts/project.ts
21942
- import * as p11 from "@clack/prompts";
22053
+ import * as p12 from "@clack/prompts";
21943
22054
  import pc2 from "picocolors";
21944
22055
  async function promptProject(defaultName) {
21945
- const projectName = await p11.text({
22056
+ const projectName = await p12.text({
21946
22057
  message: [
21947
22058
  "What is your project name?",
21948
22059
  `${pc2.cyan("\u2502")} ${pc2.dim("If you don't want a directory created with the project name, just press `")}${pc2.bold(
@@ -21961,16 +22072,16 @@ async function promptProject(defaultName) {
21961
22072
  return void 0;
21962
22073
  }
21963
22074
  });
21964
- if (p11.isCancel(projectName)) {
21965
- p11.cancel("Setup cancelled.");
22075
+ if (p12.isCancel(projectName)) {
22076
+ p12.cancel("Setup cancelled.");
21966
22077
  process.exit(0);
21967
22078
  }
21968
- const useSrcDir = await p11.confirm({
22079
+ const useSrcDir = await p12.confirm({
21969
22080
  message: "Use src/ directory?",
21970
22081
  initialValue: false
21971
22082
  });
21972
- if (p11.isCancel(useSrcDir)) {
21973
- p11.cancel("Setup cancelled.");
22083
+ if (p12.isCancel(useSrcDir)) {
22084
+ p12.cancel("Setup cancelled.");
21974
22085
  process.exit(0);
21975
22086
  }
21976
22087
  return { projectName: projectName.trim(), useSrcDir };
@@ -22967,6 +23078,18 @@ function shouldOverwriteAuthUrl(cwd, devPort) {
22967
23078
  if (!existingAuthUrl) return false;
22968
23079
  return existingAuthUrl === LEGACY_DEFAULT_AUTH_URL && existingAuthUrl !== `http://localhost:${devPort}`;
22969
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
+ }
22970
23093
  function scaffoldEnv(cwd, options) {
22971
23094
  const devPort = detectDevPort(cwd);
22972
23095
  const sections = getCoreEnvSections(options.databaseUrl, devPort, options.namespace ?? "admin");
@@ -23849,15 +23972,16 @@ function scaffoldTsconfig(cwd, config) {
23849
23972
  }
23850
23973
 
23851
23974
  // adapters/next/init/vercel/flow.ts
23852
- import * as p15 from "@clack/prompts";
23853
- import pc4 from "picocolors";
23975
+ import * as p16 from "@clack/prompts";
23976
+ import pc6 from "picocolors";
23854
23977
 
23855
23978
  // adapters/next/init/vercel/auth.ts
23856
- import * as p12 from "@clack/prompts";
23979
+ import * as p13 from "@clack/prompts";
23857
23980
  import pc3 from "picocolors";
23858
23981
 
23859
23982
  // adapters/next/init/vercel/runner.ts
23860
23983
  import { spawn as spawn3 } from "child_process";
23984
+ import { stripVTControlCharacters as stripVTControlCharacters2 } from "util";
23861
23985
  var VERCEL_CLI_VERSION = "latest";
23862
23986
  var DEFAULT_CAPTURE_TIMEOUT_MS = 12e4;
23863
23987
  function pathRunner() {
@@ -23874,18 +23998,43 @@ async function resolveVercelRunner() {
23874
23998
  });
23875
23999
  return probe.success ? pathRunner() : npxRunner();
23876
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
+ }
23877
24020
  function runVercel(runner, args, options) {
23878
24021
  const timeoutMs = options.timeoutMs ?? DEFAULT_CAPTURE_TIMEOUT_MS;
23879
24022
  const argv = [...runner.prefix, ...args];
23880
24023
  return new Promise((resolve) => {
23881
24024
  const child = spawn3(runner.bin, argv, {
23882
24025
  cwd: options.cwd,
23883
- stdio: options.mode === "inherit" ? "inherit" : "pipe",
23884
- 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 }
23885
24030
  });
23886
24031
  let stdout = "";
23887
24032
  let stderr = "";
23888
24033
  let timedOut = false;
24034
+ if (options.mode === "capture" && options.stdinInput !== void 0) {
24035
+ child.stdin?.write(options.stdinInput);
24036
+ child.stdin?.end();
24037
+ }
23889
24038
  if (options.mode === "capture") {
23890
24039
  child.stdout?.on("data", (chunk) => {
23891
24040
  stdout += chunk.toString();
@@ -23893,6 +24042,30 @@ function runVercel(runner, args, options) {
23893
24042
  child.stderr?.on("data", (chunk) => {
23894
24043
  stderr += chunk.toString();
23895
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
+ });
23896
24069
  }
23897
24070
  const timeout = setTimeout(() => {
23898
24071
  timedOut = true;
@@ -23963,6 +24136,19 @@ function extractOutermostJson(text7) {
23963
24136
  // adapters/next/init/vercel/auth.ts
23964
24137
  var WHOAMI_TIMEOUT_MS = 3e4;
23965
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
+ }
23966
24152
  async function checkVercelAuth(runner, cwd, env) {
23967
24153
  const result = await runVercel(runner, ["whoami"], {
23968
24154
  cwd,
@@ -23978,35 +24164,38 @@ async function checkVercelAuth(runner, cwd, env) {
23978
24164
  }
23979
24165
  async function ensureVercelAuth(runner, cwd, options) {
23980
24166
  const env = options.env ?? process.env;
23981
- const spinner10 = p12.spinner();
23982
- spinner10.start(
23983
- runner.source === "npx" ? "Checking Vercel sign-in (first run downloads the Vercel CLI)" : "Checking Vercel sign-in"
24167
+ const checkSpinner = spinner2();
24168
+ checkSpinner.start(
24169
+ runner.source === "npx" ? "Checking Vercel, This may take a moment" : "Checking Vercel"
23984
24170
  );
23985
24171
  const existing = await checkVercelAuth(runner, cwd, env);
23986
24172
  if (existing.authed) {
23987
- spinner10.stop(signedInMessage(existing.username));
24173
+ if (options.quietIfAuthed) {
24174
+ checkSpinner.clear();
24175
+ } else {
24176
+ checkSpinner.stop(signedInMessage(existing.username));
24177
+ }
23988
24178
  return existing;
23989
24179
  }
23990
24180
  if (env.VERCEL_TOKEN) {
23991
- spinner10.stop("Could not verify VERCEL_TOKEN with Vercel");
24181
+ checkSpinner.stop("Could not verify VERCEL_TOKEN with Vercel");
23992
24182
  return { authed: false, reason: "login-failed" };
23993
24183
  }
23994
- spinner10.stop("Not signed in to Vercel yet");
23995
- p12.log.info(
23996
- `Sign in to Vercel to continue \u2014 a browser link and code will appear below. ${pc3.dim(
23997
- "(or press Ctrl-C to enter a connection string manually)"
23998
- )}`
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)")}`
23999
24187
  );
24000
24188
  const login = await runVercel(runner, ["login"], {
24001
24189
  cwd,
24002
- mode: "inherit",
24190
+ mode: "stream",
24191
+ streamLineFilter: showVercelLoginLine,
24003
24192
  timeoutMs: LOGIN_TIMEOUT_MS,
24004
24193
  env
24005
24194
  });
24006
24195
  if (!login.success) {
24007
24196
  return { authed: false, reason: login.timedOut ? "timeout" : "login-cancelled" };
24008
24197
  }
24009
- const verifySpinner = p12.spinner();
24198
+ const verifySpinner = spinner2();
24010
24199
  verifySpinner.start("Verifying Vercel sign-in");
24011
24200
  const after = await checkVercelAuth(runner, cwd, env);
24012
24201
  if (after.authed) {
@@ -24020,21 +24209,22 @@ function signedInMessage(username) {
24020
24209
  return username ? `Signed in to Vercel as ${pc3.cyan(username)}` : "Signed in to Vercel";
24021
24210
  }
24022
24211
 
24023
- // adapters/next/init/vercel/neon.ts
24024
- import * as p13 from "@clack/prompts";
24212
+ // adapters/next/init/vercel/blob.ts
24213
+ import * as p14 from "@clack/prompts";
24214
+ import pc4 from "picocolors";
24025
24215
 
24026
24216
  // adapters/next/init/vercel/env-pull.ts
24027
24217
  import fs34 from "fs";
24028
24218
  import os from "os";
24029
24219
  import path45 from "path";
24030
24220
  var ENV_PULL_TIMEOUT_MS = 6e4;
24031
- async function pullVercelEnvValue(runner, cwd, read, env) {
24221
+ async function pullVercelEnvValue(runner, cwd, read, env, environment = "development") {
24032
24222
  const tmpDir = fs34.mkdtempSync(path45.join(os.tmpdir(), "betterstart-vercel-"));
24033
24223
  const tmpEnv = path45.join(tmpDir, ".env.pull");
24034
24224
  try {
24035
24225
  const pull = await runVercel(
24036
24226
  runner,
24037
- ["env", "pull", tmpEnv, "--environment", "development", "--yes"],
24227
+ ["env", "pull", tmpEnv, "--environment", environment, "--yes"],
24038
24228
  { cwd, mode: "capture", timeoutMs: ENV_PULL_TIMEOUT_MS, env }
24039
24229
  );
24040
24230
  if (!pull.success) return void 0;
@@ -24060,147 +24250,403 @@ function parseDotenvFile(filePath) {
24060
24250
  return vars;
24061
24251
  }
24062
24252
 
24063
- // adapters/next/init/vercel/neon.ts
24064
- var PROVISION_TIMEOUT_MS = 18e4;
24065
- var INTERACTIVE_PROVISION_TIMEOUT_MS = 6e5;
24066
- async function provisionNeonInteractive(runner, cwd, options) {
24067
- const addArgs = ["integration", "add", "neon", "--no-env-pull"];
24068
- if (options.plan) addArgs.push("--plan", options.plan);
24069
- const add = await runVercel(runner, addArgs, {
24253
+ // adapters/next/init/vercel/project.ts
24254
+ import fs35 from "fs";
24255
+ import path46 from "path";
24256
+ function sanitizeVercelProjectName(name) {
24257
+ const cleaned = name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[-_.]+/, "").replace(/[-_.]+$/, "").slice(0, 100);
24258
+ return cleaned || "my-app";
24259
+ }
24260
+ var MAX_NAME_ATTEMPTS = 10;
24261
+ var INSPECT_TIMEOUT_MS = 3e4;
24262
+ function versionedVercelProjectName(base, attempt) {
24263
+ if (attempt <= 1) return base;
24264
+ const suffix = `-v${attempt}`;
24265
+ return `${base.slice(0, 100 - suffix.length)}${suffix}`;
24266
+ }
24267
+ async function createVercelProject(runner, cwd, name, env) {
24268
+ const base = sanitizeVercelProjectName(name);
24269
+ let projectName = base;
24270
+ for (let attempt = 1; attempt <= MAX_NAME_ATTEMPTS; attempt++) {
24271
+ const candidate = versionedVercelProjectName(base, attempt);
24272
+ projectName = candidate;
24273
+ const inspect = await runVercel(runner, ["project", "inspect", candidate], {
24274
+ cwd,
24275
+ mode: "capture",
24276
+ timeoutMs: INSPECT_TIMEOUT_MS,
24277
+ env
24278
+ });
24279
+ if (inspect.success) continue;
24280
+ const add = await runVercel(runner, ["projects", "add", candidate], {
24281
+ cwd,
24282
+ mode: "capture",
24283
+ env
24284
+ });
24285
+ if (add.success) break;
24286
+ if (/already exists/i.test(`${add.stdout}
24287
+ ${add.stderr}`)) continue;
24288
+ break;
24289
+ }
24290
+ try {
24291
+ fs35.rmSync(path46.join(cwd, ".vercel", "project.json"), { force: true });
24292
+ } catch {
24293
+ }
24294
+ const link = await runVercel(runner, ["link", "--project", projectName, "--yes"], {
24070
24295
  cwd,
24071
- mode: "inherit",
24072
- timeoutMs: INTERACTIVE_PROVISION_TIMEOUT_MS,
24073
- env: options.env
24296
+ mode: "capture",
24297
+ env
24074
24298
  });
24299
+ return {
24300
+ name: projectName,
24301
+ linked: link.success,
24302
+ projectId: readLinkedProjectId(cwd)
24303
+ };
24304
+ }
24305
+ function readLinkedProjectId(cwd) {
24306
+ return readLinkedProjectJson(cwd)?.projectId;
24307
+ }
24308
+ function readLinkedProjectName(cwd) {
24309
+ return readLinkedProjectJson(cwd)?.projectName;
24310
+ }
24311
+ function readLinkedProjectJson(cwd) {
24312
+ try {
24313
+ const projectJsonPath = path46.join(cwd, ".vercel", "project.json");
24314
+ if (!fs35.existsSync(projectJsonPath)) return void 0;
24315
+ return JSON.parse(fs35.readFileSync(projectJsonPath, "utf-8"));
24316
+ } catch {
24317
+ return void 0;
24318
+ }
24319
+ }
24320
+
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 }
24343
+ );
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
+ );
24075
24373
  if (!add.success) {
24076
24374
  return { failure: add.timedOut ? "timeout" : "provision-failed" };
24077
24375
  }
24078
- const pullSpinner = p13.spinner();
24079
- pullSpinner.start("Retrieving the database connection string");
24080
- const databaseUrl = await pullDatabaseUrl(runner, cwd, options.env);
24081
- if (!databaseUrl) {
24082
- pullSpinner.stop("No DATABASE_URL came back from Vercel");
24083
- return { failure: "env-pull-empty" };
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" };
24084
24382
  }
24085
- pullSpinner.stop("Captured the Neon DATABASE_URL");
24086
- return { databaseUrl };
24383
+ pullSpinner.clear();
24384
+ return { token, storeName: quiet.storeName };
24087
24385
  }
24088
- async function provisionNeon(runner, cwd, options) {
24089
- const addArgs = ["integration", "add", "neon", "--no-env-pull", "--format", "json"];
24090
- if (options.plan) addArgs.push("--plan", options.plan);
24091
- const spinner10 = p13.spinner();
24092
- spinner10.start("Provisioning Neon Postgres via Vercel");
24093
- const add = await runVercel(runner, addArgs, {
24094
- cwd,
24095
- mode: "capture",
24096
- timeoutMs: PROVISION_TIMEOUT_MS,
24097
- env: options.env
24098
- });
24099
- if (!add.success) {
24100
- spinner10.stop("Neon provisioning did not complete");
24101
- const combined = `${add.stdout}
24102
- ${add.stderr}`.trim();
24103
- const detail = combined || void 0;
24104
- if (/terms/i.test(combined)) return { failure: "terms-required", detail };
24105
- if (/claim/i.test(combined)) return { failure: "claim-required", detail };
24106
- return { failure: add.timedOut ? "timeout" : "provision-failed", detail };
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
+ };
24107
24398
  }
24108
- const meta = parseVercelJson(add.stdout);
24109
- const resourceName = meta?.resourceName ?? meta?.name;
24110
- spinner10.message("Retrieving the database connection string");
24111
- const databaseUrl = await pullDatabaseUrl(runner, cwd, options.env);
24112
- if (!databaseUrl) {
24113
- spinner10.stop("No DATABASE_URL came back from Vercel");
24114
- return { resourceName, failure: "env-pull-empty" };
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" };
24115
24404
  }
24116
- spinner10.stop("Captured the Neon DATABASE_URL");
24117
- return { databaseUrl, resourceName };
24405
+ provisionSpinner.clear();
24406
+ return { token, storeName };
24118
24407
  }
24119
- function pullDatabaseUrl(runner, cwd, env) {
24120
- return pullVercelEnvValue(runner, cwd, readDbUrlFromDotenv, env);
24408
+ function pullBlobToken(runner, cwd, env) {
24409
+ return pullVercelEnvValue(runner, cwd, readBlobTokenFromDotenv, env);
24121
24410
  }
24122
- function readDbUrlFromDotenv(filePath) {
24411
+ function readBlobTokenFromDotenv(filePath) {
24123
24412
  const vars = parseDotenvFile(filePath);
24124
- const isPg = (v) => !!v && (v.startsWith("postgres://") || v.startsWith("postgresql://"));
24125
- if (isPg(vars.get("DATABASE_URL"))) return vars.get("DATABASE_URL");
24126
- if (isPg(vars.get("POSTGRES_URL"))) return vars.get("POSTGRES_URL");
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");
24127
24415
  for (const [key, value] of vars) {
24128
- if (/DATABASE_URL$/.test(key) && isPg(value)) return value;
24416
+ if (/_READ_WRITE_TOKEN$/.test(key) && isBlobToken(value)) return value;
24129
24417
  }
24130
24418
  for (const value of vars.values()) {
24131
- if (isPg(value)) return value;
24419
+ if (isBlobToken(value)) return value;
24132
24420
  }
24133
24421
  return void 0;
24134
24422
  }
24135
24423
 
24136
- // adapters/next/init/vercel/project.ts
24137
- import fs35 from "fs";
24138
- import path46 from "path";
24139
- function sanitizeVercelProjectName(name) {
24140
- const cleaned = name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[-_.]+/, "").replace(/[-_.]+$/, "").slice(0, 100);
24141
- return cleaned || "my-app";
24142
- }
24143
- var MAX_NAME_ATTEMPTS = 10;
24144
- var INSPECT_TIMEOUT_MS = 3e4;
24145
- function versionedVercelProjectName(base, attempt) {
24146
- if (attempt <= 1) return base;
24147
- const suffix = `-v${attempt}`;
24148
- return `${base.slice(0, 100 - suffix.length)}${suffix}`;
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;
24149
24449
  }
24150
- async function createVercelProject(runner, cwd, name, env) {
24151
- const base = sanitizeVercelProjectName(name);
24152
- let projectName = base;
24153
- for (let attempt = 1; attempt <= MAX_NAME_ATTEMPTS; attempt++) {
24154
- const candidate = versionedVercelProjectName(base, attempt);
24155
- projectName = candidate;
24156
- const inspect = await runVercel(runner, ["project", "inspect", candidate], {
24157
- cwd,
24158
- mode: "capture",
24159
- timeoutMs: INSPECT_TIMEOUT_MS,
24160
- env
24161
- });
24162
- if (inspect.success) continue;
24163
- const add = await runVercel(runner, ["projects", "add", candidate], {
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"], {
24164
24462
  cwd,
24165
24463
  mode: "capture",
24166
- env
24464
+ timeoutMs: ENV_ADD_TIMEOUT_MS,
24465
+ env,
24466
+ stdinInput: `${value}
24467
+ `
24167
24468
  });
24168
- if (add.success) break;
24169
- if (/already exists/i.test(`${add.stdout}
24170
- ${add.stderr}`)) continue;
24171
- break;
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 [];
24172
24497
  }
24498
+ const original = fs36.readFileSync(packageJsonPath, "utf-8");
24499
+ let parsed;
24173
24500
  try {
24174
- fs35.rmSync(path46.join(cwd, ".vercel", "project.json"), { force: true });
24501
+ parsed = JSON.parse(original);
24175
24502
  } catch {
24503
+ return [];
24176
24504
  }
24177
- const link = await runVercel(runner, ["link", "--project", projectName, "--yes"], {
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"], {
24178
24570
  cwd,
24179
24571
  mode: "capture",
24572
+ timeoutMs: DEPLOY_TIMEOUT_MS,
24180
24573
  env
24181
24574
  });
24575
+ if (deploy.success) {
24576
+ return { url: parseDeployedUrl(deploy.stdout, deploy.stderr) };
24577
+ }
24182
24578
  return {
24183
- name: projectName,
24184
- linked: link.success,
24185
- projectId: readLinkedProjectId(cwd)
24579
+ failure: deploy.timedOut ? "timeout" : "deploy-failed",
24580
+ detail: outputTail(`${deploy.stdout}
24581
+ ${deploy.stderr}`)
24186
24582
  };
24187
24583
  }
24188
- function readLinkedProjectId(cwd) {
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;
24189
24596
  try {
24190
- const projectJsonPath = path46.join(cwd, ".vercel", "project.json");
24191
- if (!fs35.existsSync(projectJsonPath)) return void 0;
24192
- const parsed = JSON.parse(fs35.readFileSync(projectJsonPath, "utf-8"));
24193
- return parsed.projectId;
24597
+ original = fs37.readFileSync(envPath, "utf-8");
24194
24598
  } catch {
24195
- return void 0;
24599
+ original = void 0;
24196
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
+ };
24197
24615
  }
24198
24616
 
24199
- // adapters/next/init/vercel/resend.ts
24200
- import * as p14 from "@clack/prompts";
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;
24201
24621
  var INTERACTIVE_PROVISION_TIMEOUT_MS2 = 6e5;
24202
- async function provisionResendInteractive(runner, cwd, options) {
24203
- const add = await runVercel(runner, ["integration", "add", "resend", "--no-env-pull"], {
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, {
24204
24650
  cwd,
24205
24651
  mode: "inherit",
24206
24652
  timeoutMs: INTERACTIVE_PROVISION_TIMEOUT_MS2,
@@ -24209,25 +24655,60 @@ async function provisionResendInteractive(runner, cwd, options) {
24209
24655
  if (!add.success) {
24210
24656
  return { failure: add.timedOut ? "timeout" : "provision-failed" };
24211
24657
  }
24212
- const pullSpinner = p14.spinner();
24213
- pullSpinner.start("Retrieving the Resend API key");
24214
- const apiKey = await pullVercelEnvValue(runner, cwd, readResendApiKeyFromDotenv, options.env);
24215
- if (!apiKey) {
24216
- pullSpinner.stop("No RESEND_API_KEY came back from Vercel");
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");
24217
24663
  return { failure: "env-pull-empty" };
24218
24664
  }
24219
- pullSpinner.stop("Captured the Resend API key");
24220
- return { apiKey };
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 };
24221
24698
  }
24222
- function readResendApiKeyFromDotenv(filePath) {
24699
+ function pullDatabaseUrl(runner, cwd, env) {
24700
+ return pullVercelEnvValue(runner, cwd, readDbUrlFromDotenv, env);
24701
+ }
24702
+ function readDbUrlFromDotenv(filePath) {
24223
24703
  const vars = parseDotenvFile(filePath);
24224
- const direct = vars.get("RESEND_API_KEY");
24225
- if (direct) return direct;
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");
24226
24707
  for (const [key, value] of vars) {
24227
- if (/RESEND_API_KEY$/.test(key) && value) return value;
24708
+ if (/DATABASE_URL$/.test(key) && isPg(value)) return value;
24228
24709
  }
24229
24710
  for (const value of vars.values()) {
24230
- if (value.startsWith("re_")) return value;
24711
+ if (isPg(value)) return value;
24231
24712
  }
24232
24713
  return void 0;
24233
24714
  }
@@ -24235,67 +24716,170 @@ function readResendApiKeyFromDotenv(filePath) {
24235
24716
  // adapters/next/init/vercel/flow.ts
24236
24717
  async function runVercelNeonFlow(options) {
24237
24718
  const env = options.env ?? process.env;
24719
+ const envGuard = guardEnvLocal(options.cwd);
24238
24720
  try {
24239
- const runnerSpinner = p15.spinner();
24721
+ const runnerSpinner = spinner2();
24240
24722
  runnerSpinner.start("Checking for the Vercel CLI");
24241
24723
  const runner = await resolveVercelRunner();
24242
- runnerSpinner.stop(
24243
- runner.source === "path" ? "Using the Vercel CLI from your PATH" : `Using ${pc4.cyan(`npx vercel@${VERCEL_CLI_VERSION}`)}`
24244
- );
24724
+ runnerSpinner.clear();
24245
24725
  const auth = await ensureVercelAuth(runner, options.cwd, { env });
24246
24726
  if (!auth.authed) {
24247
- p15.log.warn(authFailureMessage(auth.reason));
24727
+ p16.log.warn(authFailureMessage(auth.reason));
24248
24728
  return { ok: false };
24249
24729
  }
24250
- const projectSpinner = p15.spinner();
24251
- projectSpinner.start(`Creating Vercel project ${pc4.cyan(options.projectName)}`);
24730
+ const projectSpinner = spinner2();
24731
+ projectSpinner.start(`Creating a Vercel project ${pc6.cyan(options.projectName)}`);
24252
24732
  const project2 = await createVercelProject(runner, options.cwd, options.projectName, env);
24253
24733
  projectSpinner.stop(
24254
- project2.linked ? `Linked Vercel project ${pc4.cyan(project2.name)}` : `Using Vercel project ${pc4.cyan(project2.name)}`
24734
+ project2.linked ? `Linked Vercel project ${pc6.cyan(project2.name)}` : `Using Vercel project ${pc6.cyan(project2.name)}`
24255
24735
  );
24256
24736
  const neon = await provisionNeonForMode(runner, options);
24257
24737
  if (neon.failure || !neon.databaseUrl) {
24258
- p15.log.warn(neonFailureMessage(neon.failure));
24259
- if (neon.detail) p15.log.message(pc4.dim(neon.detail));
24738
+ p16.log.warn(neonFailureMessage(neon.failure));
24739
+ if (neon.detail) p16.log.message(pc6.dim(neon.detail));
24260
24740
  return { ok: false };
24261
24741
  }
24262
- p15.log.success(`Provisioned Neon Postgres for ${pc4.cyan(project2.name)}`);
24263
- let resendApiKey;
24264
- if (options.provisionResend && options.interactive) {
24265
- p15.log.info(
24266
- `Set up Resend email in the Vercel prompts below ${pc4.dim("(the Free plan is recommended).")}`
24267
- );
24268
- const resend = await provisionResendInteractive(runner, options.cwd, { env });
24269
- if (resend.apiKey) {
24270
- resendApiKey = resend.apiKey;
24271
- p15.log.success(`Provisioned Resend email for ${pc4.cyan(project2.name)}`);
24272
- } else {
24273
- p15.log.warn(resendFailureMessage(resend.failure));
24274
- }
24275
- }
24276
24742
  return {
24277
24743
  ok: true,
24278
24744
  databaseUrl: neon.databaseUrl,
24279
24745
  vercelProjectId: project2.projectId,
24280
- neonResourceName: neon.resourceName,
24281
- resendApiKey
24746
+ neonResourceName: neon.resourceName
24282
24747
  };
24283
24748
  } catch (error) {
24284
- p15.log.warn(
24749
+ p16.log.warn(
24285
24750
  `Vercel provisioning failed: ${error instanceof Error ? error.message : String(error)}`
24286
24751
  );
24287
24752
  return { ok: false };
24753
+ } finally {
24754
+ envGuard.restore();
24288
24755
  }
24289
24756
  }
24290
- function provisionNeonForMode(runner, options) {
24291
- const opts = { plan: options.plan, env: options.env };
24292
- if (options.interactive) {
24293
- p15.log.info(
24294
- `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)}`
24295
24795
  );
24296
- 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();
24297
24871
  }
24298
- 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);
24299
24883
  }
24300
24884
  function authFailureMessage(reason) {
24301
24885
  switch (reason) {
@@ -24311,14 +24895,22 @@ function authFailureMessage(reason) {
24311
24895
  return "Could not sign in to Vercel.";
24312
24896
  }
24313
24897
  }
24314
- function resendFailureMessage(reason) {
24898
+ function blobFailureMessage(reason) {
24315
24899
  switch (reason) {
24316
24900
  case "env-pull-empty":
24317
- return "Provisioned Resend, but no RESEND_API_KEY came back \u2014 you can pick an email provider next.";
24901
+ return "Created a Blob store, but no BLOB_READ_WRITE_TOKEN came back \u2014 falling back to manual entry.";
24318
24902
  case "timeout":
24319
- return "Resend provisioning timed out \u2014 you can pick an email provider next.";
24903
+ return "Vercel Blob provisioning timed out \u2014 falling back to manual entry.";
24320
24904
  default:
24321
- return "Resend provisioning did not complete \u2014 you can pick an email provider next.";
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.";
24322
24914
  }
24323
24915
  }
24324
24916
  function neonFailureMessage(reason) {
@@ -24337,11 +24929,11 @@ function neonFailureMessage(reason) {
24337
24929
  }
24338
24930
 
24339
24931
  // adapters/next/commands/init.ts
24340
- import pc5 from "picocolors";
24932
+ import pc7 from "picocolors";
24341
24933
 
24342
24934
  // adapters/next/commands/seed.ts
24343
- import fs36 from "fs";
24344
- import path47 from "path";
24935
+ import fs38 from "fs";
24936
+ import path49 from "path";
24345
24937
  import * as clack from "@clack/prompts";
24346
24938
  function buildSeedScript(authBasePath = "/api/admin/auth") {
24347
24939
  return `/**
@@ -24509,7 +25101,7 @@ main().catch((err) => {
24509
25101
  `;
24510
25102
  }
24511
25103
  async function runSeedCommand(options) {
24512
- const cwd = options.cwd ? path47.resolve(options.cwd) : process.cwd();
25104
+ const cwd = options.cwd ? path49.resolve(options.cwd) : process.cwd();
24513
25105
  clack.intro("BetterStart Seed");
24514
25106
  let config;
24515
25107
  try {
@@ -24549,15 +25141,15 @@ async function runSeedCommand(options) {
24549
25141
  clack.cancel("Cancelled.");
24550
25142
  return;
24551
25143
  }
24552
- const scriptsDir = path47.join(cwd, adminDir, "scripts");
24553
- const seedPath = path47.join(scriptsDir, "seed.ts");
24554
- if (!fs36.existsSync(scriptsDir)) {
24555
- 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 });
24556
25148
  }
24557
25149
  const namespace = resolveAdminNamespace(config.frameworkConfig.next.namespace);
24558
- fs36.writeFileSync(seedPath, buildSeedScript(`${namespace.apiPath}/auth`), "utf-8");
25150
+ fs38.writeFileSync(seedPath, buildSeedScript(`${namespace.apiPath}/auth`), "utf-8");
24559
25151
  const { execFile } = await import("child_process");
24560
- const tsxBin = path47.join(cwd, "node_modules", ".bin", "tsx");
25152
+ const tsxBin = path49.join(cwd, "node_modules", ".bin", "tsx");
24561
25153
  const runSeed2 = (overwrite) => new Promise((resolve, reject) => {
24562
25154
  execFile(
24563
25155
  tsxBin,
@@ -24583,45 +25175,45 @@ async function runSeedCommand(options) {
24583
25175
  }
24584
25176
  );
24585
25177
  });
24586
- const spinner10 = clack.spinner();
24587
- spinner10.start("Creating admin user...");
25178
+ const seedSpinner = spinner2();
25179
+ seedSpinner.start("Creating admin user...");
24588
25180
  try {
24589
25181
  const result = await runSeed2(false);
24590
25182
  if (result.code === 2) {
24591
25183
  const existingName = result.stdout.split("\n").find((l) => l.startsWith("EXISTING_USER:"))?.replace("EXISTING_USER:", "")?.trim() || "unknown";
24592
- spinner10.stop(`Account already exists for ${email}`);
25184
+ seedSpinner.stop(`Account already exists for ${email}`);
24593
25185
  const overwrite = await clack.confirm({
24594
25186
  message: `An admin account (${existingName}) already exists with this email. Replace it?`
24595
25187
  });
24596
25188
  if (clack.isCancel(overwrite) || !overwrite) {
24597
25189
  clack.cancel("Seed cancelled.");
24598
25190
  try {
24599
- fs36.unlinkSync(seedPath);
25191
+ fs38.unlinkSync(seedPath);
24600
25192
  } catch {
24601
25193
  }
24602
25194
  return;
24603
25195
  }
24604
- spinner10.start("Replacing admin user...");
25196
+ seedSpinner.start("Replacing admin user...");
24605
25197
  await runSeed2(true);
24606
- spinner10.stop("Admin user replaced");
25198
+ seedSpinner.stop("Admin user replaced");
24607
25199
  } else {
24608
- spinner10.stop("Admin user created");
25200
+ seedSpinner.stop("Admin user created");
24609
25201
  }
24610
25202
  } catch (err) {
24611
- spinner10.stop("Failed to create admin user");
25203
+ seedSpinner.stop("Failed to create admin user");
24612
25204
  const errMsg = err instanceof Error ? err.message : String(err);
24613
25205
  clack.log.error(errMsg);
24614
25206
  clack.log.info("You can run the seed script manually:");
24615
25207
  clack.log.info(
24616
- ` SEED_EMAIL="${email}" SEED_PASSWORD="..." npx tsx ${path47.relative(cwd, seedPath)}`
25208
+ ` SEED_EMAIL="${email}" SEED_PASSWORD="..." npx tsx ${path49.relative(cwd, seedPath)}`
24617
25209
  );
24618
25210
  clack.outro("");
24619
25211
  process.exit(1);
24620
25212
  }
24621
25213
  try {
24622
- fs36.unlinkSync(seedPath);
24623
- if (fs36.existsSync(scriptsDir) && fs36.readdirSync(scriptsDir).length === 0) {
24624
- fs36.rmdirSync(scriptsDir);
25214
+ fs38.unlinkSync(seedPath);
25215
+ if (fs38.existsSync(scriptsDir) && fs38.readdirSync(scriptsDir).length === 0) {
25216
+ fs38.rmdirSync(scriptsDir);
24625
25217
  }
24626
25218
  } catch {
24627
25219
  }
@@ -24642,6 +25234,18 @@ function readNamespaceFromConfigSource(source) {
24642
25234
  const match = source.match(/\bnamespace\s*:\s*(['"`])([^'"`]+)\1/);
24643
25235
  return normalizeNamespaceForRemoval(match?.[2]);
24644
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
+ }
24645
25249
  function resolveNonInteractiveProject(name) {
24646
25250
  const projectName = name?.trim() || "my-app";
24647
25251
  if (projectName !== "." && !/^[a-z0-9_-]+$/i.test(projectName)) {
@@ -24650,11 +25254,11 @@ function resolveNonInteractiveProject(name) {
24650
25254
  return { projectName, useSrcDir: false };
24651
25255
  }
24652
25256
  async function readExistingConfigNamespace(cwd) {
24653
- const configPath = path48.resolve(cwd, "admin.config.ts");
24654
- if (!fs37.existsSync(configPath)) {
25257
+ const configPath = path50.resolve(cwd, "admin.config.ts");
25258
+ if (!fs39.existsSync(configPath)) {
24655
25259
  return;
24656
25260
  }
24657
- const sourceNamespace = readNamespaceFromConfigSource(fs37.readFileSync(configPath, "utf-8"));
25261
+ const sourceNamespace = readNamespaceFromConfigSource(fs39.readFileSync(configPath, "utf-8"));
24658
25262
  if (sourceNamespace) {
24659
25263
  return sourceNamespace;
24660
25264
  }
@@ -24667,7 +25271,7 @@ async function readExistingConfigNamespace(cwd) {
24667
25271
  }
24668
25272
  async function resolveForceInitNamespaces(cwd, targetNamespace) {
24669
25273
  const namespaces = /* @__PURE__ */ new Set([validateAdminNamespace(targetNamespace)]);
24670
- const hasConfig = fs37.existsSync(path48.resolve(cwd, "admin.config.ts"));
25274
+ const hasConfig = fs39.existsSync(path50.resolve(cwd, "admin.config.ts"));
24671
25275
  const existingNamespace = await readExistingConfigNamespace(cwd);
24672
25276
  if (existingNamespace) {
24673
25277
  namespaces.add(existingNamespace);
@@ -24692,39 +25296,51 @@ function removeExistingAdminPaths(cwd, namespaces) {
24692
25296
  const nukeFiles = ["admin.config.ts", "ADMIN.md", "drizzle.config.ts"];
24693
25297
  let removed = 0;
24694
25298
  for (const dir of nukeDirs) {
24695
- const fullPath = path48.resolve(cwd, dir);
24696
- if (fs37.existsSync(fullPath)) {
24697
- 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 });
24698
25302
  removed++;
24699
25303
  }
24700
25304
  }
24701
25305
  for (const file of nukeFiles) {
24702
- const fullPath = path48.resolve(cwd, file);
24703
- if (fs37.existsSync(fullPath)) {
24704
- fs37.unlinkSync(fullPath);
25306
+ const fullPath = path50.resolve(cwd, file);
25307
+ if (fs39.existsSync(fullPath)) {
25308
+ fs39.unlinkSync(fullPath);
24705
25309
  removed++;
24706
25310
  }
24707
25311
  }
24708
25312
  return removed;
24709
25313
  }
24710
25314
  async function runInitCommand(name, options) {
24711
- p16.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
+ );
24712
25328
  let cwd = process.cwd();
24713
- let projectName = path48.basename(cwd);
25329
+ let projectName = path50.basename(cwd);
24714
25330
  let forceMode = Boolean(options.force);
24715
25331
  let namespace = DEFAULT_ADMIN_NAMESPACE;
24716
25332
  if (options.namespace) {
24717
25333
  try {
24718
25334
  namespace = validateAdminNamespace(options.namespace);
24719
25335
  } catch (error) {
24720
- p16.log.error(error instanceof Error ? error.message : String(error));
25336
+ p17.log.error(error instanceof Error ? error.message : String(error));
24721
25337
  process.exit(1);
24722
25338
  }
24723
25339
  }
24724
25340
  if (!options.yes && !options.namespace) {
24725
25341
  const defaultDashboardPath = resolveAdminNamespace(DEFAULT_ADMIN_NAMESPACE).routePath;
24726
- const namespaceInput = await p16.text({
24727
- message: "What's the path you want the dashboard to be?",
25342
+ const namespaceInput = await p17.text({
25343
+ message: "Enter the dashboard path",
24728
25344
  placeholder: `eg. ${defaultDashboardPath}`,
24729
25345
  defaultValue: defaultDashboardPath,
24730
25346
  validate(value) {
@@ -24736,8 +25352,8 @@ async function runInitCommand(name, options) {
24736
25352
  }
24737
25353
  }
24738
25354
  });
24739
- if (p16.isCancel(namespaceInput)) {
24740
- p16.cancel("Setup cancelled.");
25355
+ if (p17.isCancel(namespaceInput)) {
25356
+ p17.cancel("Setup cancelled.");
24741
25357
  process.exit(0);
24742
25358
  }
24743
25359
  namespace = validateAdminDashboardPath(namespaceInput);
@@ -24747,36 +25363,35 @@ async function runInitCommand(name, options) {
24747
25363
  let isFreshProject = false;
24748
25364
  let srcDir;
24749
25365
  if (project2.isExisting) {
24750
- p16.log.info(`Next.js app detected ${pc5.dim("\xB7")} ${pc5.cyan(pm)}`);
24751
25366
  srcDir = project2.hasSrcDir;
24752
25367
  if (!project2.hasTypeScript) {
24753
- p16.log.error("TypeScript is required. Please add a tsconfig.json first.");
25368
+ p17.log.error("TypeScript is required. Please add a tsconfig.json first.");
24754
25369
  process.exit(1);
24755
25370
  }
24756
25371
  if (forceMode) {
24757
25372
  const nuked = removeExistingAdminPaths(cwd, await resolveForceInitNamespaces(cwd, namespace));
24758
25373
  if (nuked > 0) {
24759
- p16.log.warn(`${pc5.yellow("Force mode:")} removed ${nuked} existing admin paths`);
25374
+ p17.log.warn(`${pc7.yellow("Force mode:")} removed ${nuked} existing admin paths`);
24760
25375
  }
24761
25376
  project2 = detectProject(cwd, namespace);
24762
25377
  } else if (project2.conflicts.length > 0) {
24763
- const conflictLines = project2.conflicts.map((c) => `${pc5.yellow("\u25B2")} ${c}`);
25378
+ const conflictLines = project2.conflicts.map((c) => `${pc7.yellow("\u25B2")} ${c}`);
24764
25379
  conflictLines.push(
24765
25380
  "",
24766
- 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.`)
24767
25382
  );
24768
- p16.note(conflictLines.join("\n"), pc5.yellow("Conflicts"));
25383
+ p17.note(conflictLines.join("\n"), pc7.yellow("Conflicts"));
24769
25384
  if (!options.yes) {
24770
- const proceed = await p16.confirm({
25385
+ const proceed = await p17.confirm({
24771
25386
  message: [
24772
- `Continue with ${pc5.bold(pc5.cyan("--force"))}?`,
24773
- `${pc5.cyan("\u2502")} ${pc5.dim("This will force overwrite the existing admin code.")}`,
24774
- 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")
24775
25390
  ].join("\n"),
24776
25391
  initialValue: true
24777
25392
  });
24778
- if (p16.isCancel(proceed) || !proceed) {
24779
- p16.cancel("Setup cancelled.");
25393
+ if (p17.isCancel(proceed) || !proceed) {
25394
+ p17.cancel("Setup cancelled.");
24780
25395
  process.exit(0);
24781
25396
  }
24782
25397
  forceMode = true;
@@ -24785,23 +25400,23 @@ async function runInitCommand(name, options) {
24785
25400
  await resolveForceInitNamespaces(cwd, namespace)
24786
25401
  );
24787
25402
  if (nuked > 0) {
24788
- p16.log.warn(`${pc5.yellow("Force mode:")} removed ${nuked} existing admin paths`);
25403
+ p17.log.warn(`${pc7.yellow("Force mode:")} removed ${nuked} existing admin paths`);
24789
25404
  }
24790
25405
  project2 = detectProject(cwd, namespace);
24791
25406
  }
24792
25407
  }
24793
25408
  } else {
24794
- p16.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...");
24795
25410
  let projectPrompt;
24796
25411
  try {
24797
25412
  projectPrompt = options.yes ? resolveNonInteractiveProject(name) : await promptProject(name);
24798
25413
  } catch (error) {
24799
- p16.log.error(error instanceof Error ? error.message : String(error));
25414
+ p17.log.error(error instanceof Error ? error.message : String(error));
24800
25415
  process.exit(1);
24801
25416
  }
24802
25417
  srcDir = projectPrompt.useSrcDir;
24803
25418
  if (!options.yes) {
24804
- const pmChoice = await p16.select({
25419
+ const pmChoice = await p17.select({
24805
25420
  message: "Which package manager do you want to use?",
24806
25421
  options: [
24807
25422
  { value: "pnpm", label: "pnpm", hint: "recommended" },
@@ -24810,13 +25425,13 @@ async function runInitCommand(name, options) {
24810
25425
  { value: "bun", label: "bun" }
24811
25426
  ]
24812
25427
  });
24813
- if (p16.isCancel(pmChoice)) {
24814
- p16.cancel("Setup cancelled.");
25428
+ if (p17.isCancel(pmChoice)) {
25429
+ p17.cancel("Setup cancelled.");
24815
25430
  process.exit(0);
24816
25431
  }
24817
25432
  pm = pmChoice;
24818
25433
  }
24819
- const displayName = projectPrompt.projectName === "." ? path48.basename(cwd) : projectPrompt.projectName;
25434
+ const displayName = projectPrompt.projectName === "." ? path50.basename(cwd) : projectPrompt.projectName;
24820
25435
  projectName = displayName;
24821
25436
  const { bin, prefix } = createNextAppCommand(pm);
24822
25437
  const cnaArgs = [
@@ -24833,7 +25448,7 @@ async function runInitCommand(name, options) {
24833
25448
  ];
24834
25449
  if (srcDir) cnaArgs.push("--src-dir");
24835
25450
  else cnaArgs.push("--no-src-dir");
24836
- const createNextAppSpinner = p16.spinner();
25451
+ const createNextAppSpinner = spinner2();
24837
25452
  createNextAppSpinner.start(`Creating a Next.js app (latest)`);
24838
25453
  const createNextAppResult = await runQuietCommand(bin, cnaArgs, {
24839
25454
  cwd,
@@ -24845,46 +25460,45 @@ async function runInitCommand(name, options) {
24845
25460
  process.stderr.write(`${createNextAppResult.output.trimEnd()}
24846
25461
  `);
24847
25462
  }
24848
- p16.log.error(createNextAppResult.error);
24849
- p16.log.info(
25463
+ p17.log.error(createNextAppResult.error);
25464
+ p17.log.info(
24850
25465
  `You can create the project manually:
24851
- ${pc5.cyan(`npx create-next-app@latest ${projectPrompt.projectName} --typescript --tailwind --app`)}
24852
- 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.`
24853
25468
  );
24854
25469
  process.exit(1);
24855
25470
  }
24856
- cwd = path48.resolve(cwd, projectPrompt.projectName);
24857
- 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"));
24858
25473
  const hasNextConfig = ["next.config.ts", "next.config.js", "next.config.mjs"].some(
24859
- (f) => fs37.existsSync(path48.join(cwd, f))
25474
+ (f) => fs39.existsSync(path50.join(cwd, f))
24860
25475
  );
24861
25476
  if (!hasPackageJson || !hasNextConfig) {
24862
25477
  createNextAppSpinner.stop(`Failed to create Next.js app: ${displayName}`);
24863
- p16.log.error(
25478
+ p17.log.error(
24864
25479
  "create-next-app completed but the project was not created. This can happen with nested npx calls."
24865
25480
  );
24866
25481
  const manualCmd = `npx create-next-app@latest ${projectPrompt.projectName} --typescript --tailwind --app`;
24867
- p16.log.info(
25482
+ p17.log.info(
24868
25483
  `Create the project manually:
24869
- ${pc5.cyan(manualCmd)}
24870
- Then run ${pc5.cyan("betterstart init")} inside it.`
25484
+ ${pc7.cyan(manualCmd)}
25485
+ Then run ${pc7.cyan("betterstart init")} inside it.`
24871
25486
  );
24872
25487
  process.exit(1);
24873
25488
  }
24874
25489
  const installedNextVersion = detectInstalledNextVersion(cwd);
24875
- const nextVersionSuffix = installedNextVersion ? `${pc5.cyan(`v${installedNextVersion}`)}` : "";
25490
+ const nextVersionSuffix = installedNextVersion ? `${pc7.cyan(`v${installedNextVersion}`)}` : "";
24876
25491
  createNextAppSpinner.stop(`Created a Next.js ${nextVersionSuffix} app in ${displayName}`);
24877
25492
  project2 = detectProject(cwd, namespace);
24878
25493
  isFreshProject = true;
24879
25494
  }
24880
25495
  let databaseUrl;
24881
- let vercelResendApiKey;
24882
25496
  const existingDbUrl = readExistingDbUrl(cwd);
24883
25497
  if (options.yes) {
24884
25498
  if (options.databaseUrl) {
24885
25499
  if (!isValidDbUrl(options.databaseUrl)) {
24886
- p16.log.error(
24887
- `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://")}`
24888
25502
  );
24889
25503
  process.exit(1);
24890
25504
  }
@@ -24901,13 +25515,15 @@ async function runInitCommand(name, options) {
24901
25515
  });
24902
25516
  if (flow.ok && flow.databaseUrl) {
24903
25517
  databaseUrl = flow.databaseUrl;
25518
+ persistDatabaseUrl(cwd, databaseUrl);
25519
+ p17.log.success(`Saved DATABASE_URL to ${pc7.cyan(".env.local")}`);
24904
25520
  } else {
24905
- p16.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.");
24906
25522
  }
24907
25523
  }
24908
25524
  } else if (existingDbUrl) {
24909
25525
  const masked = maskDbUrl(existingDbUrl);
24910
- p16.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})`)}`);
24911
25527
  databaseUrl = existingDbUrl;
24912
25528
  } else {
24913
25529
  const servicesResult = await promptServices({ allowVercel: options.vercel !== false });
@@ -24917,14 +25533,14 @@ async function runInitCommand(name, options) {
24917
25533
  projectName,
24918
25534
  interactive: true,
24919
25535
  plan: options.vercelPlan,
24920
- env: process.env,
24921
- provisionResend: true
25536
+ env: process.env
24922
25537
  });
24923
25538
  if (flow.ok && flow.databaseUrl) {
24924
25539
  databaseUrl = flow.databaseUrl;
24925
- vercelResendApiKey = flow.resendApiKey;
25540
+ persistDatabaseUrl(cwd, databaseUrl);
25541
+ p17.log.success(`Saved DATABASE_URL to ${pc7.cyan(".env.local")}`);
24926
25542
  } else {
24927
- p16.log.info("Falling back to a manual database connection string.");
25543
+ p17.log.info("Falling back to a manual database connection string.");
24928
25544
  openBrowserVercelNeon();
24929
25545
  databaseUrl = await promptConnectionString();
24930
25546
  }
@@ -24949,23 +25565,43 @@ async function runInitCommand(name, options) {
24949
25565
  overwriteKeys: /* @__PURE__ */ new Set()
24950
25566
  };
24951
25567
  let pluginSelection;
25568
+ const vercelBlobAllowed = options.vercel !== false;
24952
25569
  if (selectedPlugins.length > 0 || selectedIntegrations.length > 0) {
24953
25570
  pluginSelection = {
24954
25571
  plugins: selectedPlugins,
24955
25572
  integrations: selectedIntegrations,
24956
- storage: selectedIntegrations.includes("r2") ? "r2" : "local"
25573
+ storage: selectedIntegrations.includes("vercel-blob") ? "vercel-blob" : selectedIntegrations.includes("r2") ? "r2" : "local"
24957
25574
  };
24958
- if (vercelResendApiKey) {
24959
- collectedIntegrationConfig.sections.push({
24960
- header: "Email (Resend)",
24961
- vars: [{ key: "BETTERSTART_RESEND_API_KEY", value: vercelResendApiKey }]
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
24962
25581
  });
24963
- collectedIntegrationConfig.overwriteKeys.add("BETTERSTART_RESEND_API_KEY");
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
+ }
24964
25598
  }
24965
25599
  } else if (options.yes) {
24966
25600
  pluginSelection = { plugins: [], integrations: [], storage: "local" };
24967
25601
  } else {
24968
- const promptResult = await promptPlugins(cwd, { vercelResendApiKey });
25602
+ const promptResult = await promptPlugins(cwd, {
25603
+ provisionVercelBlob: vercelBlobAllowed ? () => runVercelBlobFlow({ cwd, projectName, interactive: true, env: process.env }) : void 0
25604
+ });
24969
25605
  collectedIntegrationConfig = promptResult.integrationConfig;
24970
25606
  pluginSelection = {
24971
25607
  plugins: promptResult.plugins,
@@ -24982,7 +25618,7 @@ async function runInitCommand(name, options) {
24982
25618
  config.paths = config.frameworkConfig.next.paths;
24983
25619
  config.database.migrationsDir = deriveMigrationsDir(namespace);
24984
25620
  const results = [];
24985
- const s = p16.spinner();
25621
+ const s = spinner2();
24986
25622
  s.start("Directory structure");
24987
25623
  const baseFiles = scaffoldBase({
24988
25624
  cwd,
@@ -25055,34 +25691,34 @@ async function runInitCommand(name, options) {
25055
25691
  const maxLabel = Math.max(...results.map((r) => r.label.length));
25056
25692
  const noteLines = results.map((r) => {
25057
25693
  const padded = r.label.padEnd(maxLabel + 3);
25058
- return `${pc5.green("\u2713")} ${padded}${pc5.dim(r.result)}`;
25694
+ return `${pc7.green("\u2713")} ${padded}${pc7.dim(r.result)}`;
25059
25695
  });
25060
25696
  s.stop("");
25061
25697
  process.stdout.write("\x1B[2A\x1B[J");
25062
- p16.note(noteLines.join("\n"), "Scaffolded admin");
25063
- const drizzleConfigPath = path48.join(cwd, "drizzle.config.ts");
25064
- 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)) {
25065
25701
  if (forceMode) {
25066
25702
  const { readNamespacedTemplate } = await import("./template-reader-X6KFP7B3.js");
25067
- fs37.writeFileSync(
25703
+ fs39.writeFileSync(
25068
25704
  drizzleConfigPath,
25069
25705
  readNamespacedTemplate("drizzle.config.ts", namespace),
25070
25706
  "utf-8"
25071
25707
  );
25072
- p16.log.success("Updated drizzle.config.ts");
25708
+ p17.log.success("Updated drizzle.config.ts");
25073
25709
  } else if (!options.yes) {
25074
- const overwrite = await p16.confirm({
25710
+ const overwrite = await p17.confirm({
25075
25711
  message: "drizzle.config.ts already exists. Overwrite with latest version?",
25076
25712
  initialValue: true
25077
25713
  });
25078
- if (!p16.isCancel(overwrite) && overwrite) {
25714
+ if (!p17.isCancel(overwrite) && overwrite) {
25079
25715
  const { readNamespacedTemplate } = await import("./template-reader-X6KFP7B3.js");
25080
- fs37.writeFileSync(
25716
+ fs39.writeFileSync(
25081
25717
  drizzleConfigPath,
25082
25718
  readNamespacedTemplate("drizzle.config.ts", namespace),
25083
25719
  "utf-8"
25084
25720
  );
25085
- p16.log.success("Updated drizzle.config.ts");
25721
+ p17.log.success("Updated drizzle.config.ts");
25086
25722
  }
25087
25723
  }
25088
25724
  }
@@ -25108,11 +25744,11 @@ async function runInitCommand(name, options) {
25108
25744
  depsInstalled = true;
25109
25745
  } else {
25110
25746
  s.stop("Failed to install dependencies");
25111
- p16.log.warning(depsResult.error ?? "Unknown error");
25112
- p16.log.info(
25747
+ p17.log.warning(depsResult.error ?? "Unknown error");
25748
+ p17.log.info(
25113
25749
  `You can install them manually:
25114
- ${pc5.cyan(`${pm} add ${depsResult.dependencies.join(" ")}`)}
25115
- ${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(" ")}`)}`
25116
25752
  );
25117
25753
  }
25118
25754
  if (depsInstalled) {
@@ -25162,46 +25798,46 @@ async function runInitCommand(name, options) {
25162
25798
  process.stdout.write("\x1B[2A\x1B[J");
25163
25799
  }
25164
25800
  writeConfigFile(cwd, resolvedIntegrationInstallResult.config);
25165
- const usesLocalStorage = resolvedIntegrationInstallResult.config.storage.provider !== "r2";
25801
+ const usesLocalStorage = resolvedIntegrationInstallResult.config.storage.provider === "local";
25166
25802
  const installLines = [];
25167
25803
  if (depsInstalled) {
25168
25804
  installLines.push(
25169
- `${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`)}`
25170
25806
  );
25171
25807
  }
25172
25808
  if (coreSchemasResult.errors.length > 0) {
25173
25809
  installLines.push(
25174
- `${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)`)}`
25175
25811
  );
25176
25812
  for (const err of coreSchemasResult.errors) {
25177
- installLines.push(` ${pc5.dim(err)}`);
25813
+ installLines.push(` ${pc7.dim(err)}`);
25178
25814
  }
25179
25815
  } else {
25180
25816
  installLines.push(
25181
- `${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`)}`
25182
25818
  );
25183
25819
  }
25184
25820
  installLines.push(
25185
- `${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")}`
25186
25822
  );
25187
25823
  for (const warning of resolvedPluginInstallResult.warnings) {
25188
- installLines.push(` ${pc5.yellow("\u25B2")} ${warning}`);
25824
+ installLines.push(` ${pc7.yellow("\u25B2")} ${warning}`);
25189
25825
  }
25190
25826
  installLines.push(
25191
- `${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")}`
25192
25828
  );
25193
25829
  for (const warning of resolvedIntegrationInstallResult.warnings) {
25194
- installLines.push(` ${pc5.yellow("\u25B2")} ${warning}`);
25830
+ installLines.push(` ${pc7.yellow("\u25B2")} ${warning}`);
25195
25831
  }
25196
25832
  if (usesLocalStorage) {
25197
25833
  installLines.push(
25198
- ` ${pc5.yellow("\u25B2")} Local filesystem storage is only for development or self-hosted persistent-disk deployments. Use Cloudflare R2 or AWS S3 for hosted/serverless production.`
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.`
25199
25835
  );
25200
25836
  }
25201
- p16.note(installLines.join("\n"), "Installed");
25837
+ p17.note(installLines.join("\n"), "Installed");
25202
25838
  let dbPushed = false;
25203
25839
  if (depsResult.success && options.skipMigration && hasDbUrl(cwd)) {
25204
- p16.log.info(`Skipping database schema push ${pc5.dim("(--skip-migration)")}`);
25840
+ p17.log.info(`Skipping database schema push ${pc7.dim("(--skip-migration)")}`);
25205
25841
  } else if (depsResult.success && hasDbUrl(cwd)) {
25206
25842
  let driverReady = hasDrizzleKitPostgresDriverDependency(cwd);
25207
25843
  if (!driverReady) {
@@ -25216,19 +25852,19 @@ async function runInitCommand(name, options) {
25216
25852
  });
25217
25853
  if (!driverResult.success) {
25218
25854
  s.stop("Database push failed");
25219
- p16.log.warning(driverResult.error ?? `Failed to install ${DRIZZLE_KIT_POSTGRES_DRIVER_DEP}`);
25220
- p16.log.info(
25221
- `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))}`
25222
25858
  );
25223
25859
  } else {
25224
25860
  driverReady = hasDrizzleKitPostgresDriverDependency(cwd);
25225
- s.stop(`${pc5.green("\u2713")} Drizzle Kit Postgres driver dependency installed`);
25861
+ s.stop(`${pc7.green("\u2713")} Drizzle Kit Postgres driver dependency installed`);
25226
25862
  }
25227
25863
  }
25228
25864
  if (driverReady) {
25229
25865
  const useTerminalForDbPush = shouldUseTerminalForDbPush(options);
25230
25866
  if (useTerminalForDbPush) {
25231
- p16.log.info(`Running ${pc5.cyan("drizzle-kit push --force")}`);
25867
+ p17.log.info(`Running ${pc7.cyan("drizzle-kit push --force")}`);
25232
25868
  } else {
25233
25869
  s.start("Pushing database schema (drizzle-kit push)");
25234
25870
  }
@@ -25237,15 +25873,15 @@ async function runInitCommand(name, options) {
25237
25873
  if (useTerminalForDbPush) {
25238
25874
  const verification = await verifyDatabaseReachable(cwd);
25239
25875
  if (!verification.success) {
25240
- p16.log.warning(verification.error);
25241
- p16.log.error("Database was not reachable. Aborting setup.");
25876
+ p17.log.warning(verification.error);
25877
+ p17.log.error("Database was not reachable. Aborting setup.");
25242
25878
  process.exit(1);
25243
25879
  }
25244
25880
  }
25245
25881
  if (useTerminalForDbPush) {
25246
- p16.log.success("Database schema pushed");
25882
+ p17.log.success("Database schema pushed");
25247
25883
  } else {
25248
- s.stop(`${pc5.green("\u2713")} Database schema pushed`);
25884
+ s.stop(`${pc7.green("\u2713")} Database schema pushed`);
25249
25885
  }
25250
25886
  dbPushed = true;
25251
25887
  } else {
@@ -25253,12 +25889,12 @@ async function runInitCommand(name, options) {
25253
25889
  s.stop("Database push failed");
25254
25890
  }
25255
25891
  const pushError = pushResult.error ?? "Unknown error";
25256
- p16.log.warning(pushError);
25892
+ p17.log.warning(pushError);
25257
25893
  if (isDatabaseReachabilityError(pushError)) {
25258
- p16.log.error("Database was not reachable. Aborting setup.");
25894
+ p17.log.error("Database was not reachable. Aborting setup.");
25259
25895
  process.exit(1);
25260
25896
  }
25261
- p16.log.info(`You can run it manually: ${pc5.cyan(drizzlePushCommand(pm))}`);
25897
+ p17.log.info(`You can run it manually: ${pc7.cyan(drizzlePushCommand(pm))}`);
25262
25898
  }
25263
25899
  }
25264
25900
  }
@@ -25267,8 +25903,8 @@ async function runInitCommand(name, options) {
25267
25903
  let seedSuccess = false;
25268
25904
  let adminAccountReady = false;
25269
25905
  if (dbPushed && options.skipAdminCreation) {
25270
- p16.log.info(
25271
- `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)`)}`
25272
25908
  );
25273
25909
  }
25274
25910
  if (dbPushed && !options.yes && !options.skipAdminCreation) {
@@ -25277,14 +25913,14 @@ async function runInitCommand(name, options) {
25277
25913
  let replaceExistingAdmin = false;
25278
25914
  const adminCheck = await checkExistingAdmin(cwd, adminDir, authBasePath);
25279
25915
  if (adminCheck.error) {
25280
- p16.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})`)}`);
25281
25917
  if (isDatabaseReachabilityError(adminCheck.error)) {
25282
- p16.log.error("Database was not reachable. Aborting setup.");
25918
+ p17.log.error("Database was not reachable. Aborting setup.");
25283
25919
  process.exit(1);
25284
25920
  }
25285
25921
  } else if (adminCheck.existingAdmin) {
25286
25922
  const existingAdminLabel = formatAdminIdentity(adminCheck.existingAdmin);
25287
- const adminAction = await p16.select({
25923
+ const adminAction = await p17.select({
25288
25924
  message: "Found an already existing admin account. Do you want to replace it or skip?",
25289
25925
  options: [
25290
25926
  {
@@ -25298,34 +25934,34 @@ async function runInitCommand(name, options) {
25298
25934
  }
25299
25935
  ]
25300
25936
  });
25301
- if (p16.isCancel(adminAction)) {
25302
- p16.cancel("Setup cancelled.");
25937
+ if (p17.isCancel(adminAction)) {
25938
+ p17.cancel("Setup cancelled.");
25303
25939
  process.exit(0);
25304
25940
  }
25305
25941
  if (adminAction === "skip") {
25306
25942
  adminAccountReady = true;
25307
- p16.log.info(`Keeping existing admin account ${pc5.dim(`(${existingAdminLabel})`)}`);
25943
+ p17.log.info(`Keeping existing admin account ${pc7.dim(`(${existingAdminLabel})`)}`);
25308
25944
  } else {
25309
25945
  replaceExistingAdmin = true;
25310
25946
  }
25311
25947
  }
25312
25948
  if (!adminAccountReady) {
25313
- p16.note(
25314
- pc5.dim(
25315
- 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."
25316
25952
  ),
25317
25953
  "Admin account"
25318
25954
  );
25319
- const credentials = await p16.group(
25955
+ const credentials = await p17.group(
25320
25956
  {
25321
- email: () => p16.text({
25957
+ email: () => p17.text({
25322
25958
  message: "Admin email",
25323
25959
  placeholder: "admin@example.com",
25324
25960
  validate: (v) => {
25325
25961
  if (!v || !v.includes("@")) return "Please enter a valid email";
25326
25962
  }
25327
25963
  }),
25328
- password: () => p16.password({
25964
+ password: () => p17.password({
25329
25965
  message: "Admin password",
25330
25966
  validate: (v) => {
25331
25967
  if (!v || v.length < 8) return "Password must be at least 8 characters";
@@ -25334,7 +25970,7 @@ async function runInitCommand(name, options) {
25334
25970
  },
25335
25971
  {
25336
25972
  onCancel: () => {
25337
- p16.cancel("Setup cancelled.");
25973
+ p17.cancel("Setup cancelled.");
25338
25974
  process.exit(0);
25339
25975
  }
25340
25976
  }
@@ -25352,12 +25988,12 @@ async function runInitCommand(name, options) {
25352
25988
  seedOverwriteMode
25353
25989
  );
25354
25990
  if (seedResult.existingUser) {
25355
- s.stop(`${pc5.yellow("\u25B2")} An account already exists for ${credentials.email}`);
25356
- const replace = await p16.confirm({
25991
+ s.stop(`${pc7.yellow("\u25B2")} An account already exists for ${credentials.email}`);
25992
+ const replace = await p17.confirm({
25357
25993
  message: "Replace the existing account with this email?",
25358
25994
  initialValue: false
25359
25995
  });
25360
- if (!p16.isCancel(replace) && replace) {
25996
+ if (!p17.isCancel(replace) && replace) {
25361
25997
  seedOverwriteMode = "email";
25362
25998
  s.start("Replacing admin user");
25363
25999
  seedResult = await runSeed(
@@ -25372,20 +26008,20 @@ async function runInitCommand(name, options) {
25372
26008
  }
25373
26009
  if (seedResult.success) {
25374
26010
  s.stop(
25375
- 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`
25376
26012
  );
25377
26013
  seedSuccess = true;
25378
26014
  adminAccountReady = true;
25379
26015
  } else if (seedResult.error) {
25380
- s.stop(`${pc5.red("\u2717")} Failed to create admin user`);
25381
- p16.note(
25382
- `${pc5.red(seedResult.error)}
26016
+ s.stop(`${pc7.red("\u2717")} Failed to create admin user`);
26017
+ p17.note(
26018
+ `${pc7.red(seedResult.error)}
25383
26019
 
25384
- Run manually: ${pc5.cyan(betterstartExecCommand(pm, "seed"))}`,
25385
- pc5.red("Seed failed")
26020
+ Run manually: ${pc7.cyan(betterstartExecCommand(pm, "seed"))}`,
26021
+ pc7.red("Seed failed")
25386
26022
  );
25387
26023
  if (isDatabaseReachabilityError(seedResult.error)) {
25388
- p16.log.error("Database was not reachable. Aborting setup.");
26024
+ p17.log.error("Database was not reachable. Aborting setup.");
25389
26025
  process.exit(1);
25390
26026
  }
25391
26027
  }
@@ -25411,43 +26047,37 @@ Run manually: ${pc5.cyan(betterstartExecCommand(pm, "seed"))}`,
25411
26047
  const installedPluginList = resolvedPluginInstallResult.installed.length > 0 ? resolvedPluginInstallResult.installed.join(", ") : "core only";
25412
26048
  const installedIntegrationList = resolvedIntegrationInstallResult.installed.length > 0 ? resolvedIntegrationInstallResult.installed.join(", ") : "none";
25413
26049
  const summaryLines = [
25414
- `Plugins: ${pc5.cyan(installedPluginList)}`,
25415
- `Integrations: ${pc5.cyan(installedIntegrationList)}`,
25416
- `Storage: ${pc5.cyan(usesLocalStorage ? "local filesystem" : "Cloudflare R2")}`,
25417
- `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))}`,
25418
26054
  `Env vars: ${envResult.added.length} added, ${envResult.skipped.length} skipped`
25419
26055
  ];
25420
- const nextSteps = [];
25421
- let step = 1;
25422
- const envStepLabel = databaseUrl ? `Fill in remaining values in ${pc5.cyan(".env.local")}` : `Fill in values in ${pc5.cyan(".env.local")}`;
25423
- nextSteps.push(` ${step++}. ${envStepLabel}`);
25424
- if (!dbPushed) {
25425
- nextSteps.push(` ${step++}. Run ${pc5.cyan(drizzlePushCommand(pm))} to sync the database`);
25426
- }
25427
- if (!adminAccountReady) {
25428
- nextSteps.push(
25429
- ` ${step++}. Run ${pc5.cyan(betterstartExecCommand(pm, "seed"))} to create an admin user`
25430
- );
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
+ }
25431
26073
  }
25432
- nextSteps.push(
25433
- ` ${step++}. Run ${pc5.cyan(runCommand(pm, "dev"))} and open ${pc5.cyan(adminLoginUrl)}`
25434
- );
25435
- nextSteps.push(
25436
- ` ${step++}. Run ${pc5.cyan(betterstartExecCommand(pm, "generate <schema>"))} to create content types`
25437
- );
25438
- nextSteps.push(
25439
- ` ${step++}. Run ${pc5.cyan(betterstartExecCommand(pm, "add --integration <name>"))} to install integrations later`
25440
- );
25441
- summaryLines.push("", "Next steps:", ...nextSteps);
25442
- p16.note(summaryLines.join("\n"), "Admin scaffolded successfully");
25443
26074
  if (!options.yes && !options.skipDevServerStart) {
25444
26075
  const devCmd = runCommand(pm, "dev");
25445
- const startDev = await p16.confirm({
26076
+ const startDev = await p17.confirm({
25446
26077
  message: "Start the development server?",
25447
26078
  initialValue: true
25448
26079
  });
25449
- if (!p16.isCancel(startDev) && startDev) {
25450
- p16.outro(`Starting ${pc5.cyan(devCmd)}...`);
26080
+ if (!p17.isCancel(startDev) && startDev) {
25451
26081
  await startManagedDevServer(cwd, devCmd, adminLoginUrl, {
25452
26082
  email: seedSuccess && seedEmail ? seedEmail : void 0,
25453
26083
  password: seedSuccess && seedPassword ? seedPassword : void 0
@@ -25455,15 +26085,15 @@ Run manually: ${pc5.cyan(betterstartExecCommand(pm, "seed"))}`,
25455
26085
  return;
25456
26086
  }
25457
26087
  }
25458
- p16.outro("Done!");
26088
+ p17.outro("Done!");
25459
26089
  }
25460
26090
  function isValidDbUrl(url) {
25461
26091
  return url.startsWith("postgres://") || url.startsWith("postgresql://");
25462
26092
  }
25463
26093
  function readExistingDbUrl(cwd) {
25464
- const envPath = path48.join(cwd, ".env.local");
25465
- if (!fs37.existsSync(envPath)) return void 0;
25466
- 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");
25467
26097
  for (const line of content.split("\n")) {
25468
26098
  const trimmed = line.trim();
25469
26099
  if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
@@ -25486,9 +26116,9 @@ function maskDbUrl(url) {
25486
26116
  }
25487
26117
  }
25488
26118
  function hasDbUrl(cwd) {
25489
- const envPath = path48.join(cwd, ".env.local");
25490
- if (!fs37.existsSync(envPath)) return false;
25491
- 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");
25492
26122
  for (const line of content.split("\n")) {
25493
26123
  const trimmed = line.trim();
25494
26124
  if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
@@ -25502,20 +26132,20 @@ function hasDbUrl(cwd) {
25502
26132
  return false;
25503
26133
  }
25504
26134
  function detectInstalledNextVersion(cwd) {
25505
- const installedNextPackagePath = path48.join(cwd, "node_modules", "next", "package.json");
25506
- if (fs37.existsSync(installedNextPackagePath)) {
26135
+ const installedNextPackagePath = path50.join(cwd, "node_modules", "next", "package.json");
26136
+ if (fs39.existsSync(installedNextPackagePath)) {
25507
26137
  try {
25508
- const installedPkg = JSON.parse(fs37.readFileSync(installedNextPackagePath, "utf-8"));
26138
+ const installedPkg = JSON.parse(fs39.readFileSync(installedNextPackagePath, "utf-8"));
25509
26139
  if (typeof installedPkg.version === "string" && installedPkg.version.length > 0) {
25510
26140
  return installedPkg.version;
25511
26141
  }
25512
26142
  } catch {
25513
26143
  }
25514
26144
  }
25515
- const projectPackagePath = path48.join(cwd, "package.json");
25516
- if (!fs37.existsSync(projectPackagePath)) return void 0;
26145
+ const projectPackagePath = path50.join(cwd, "package.json");
26146
+ if (!fs39.existsSync(projectPackagePath)) return void 0;
25517
26147
  try {
25518
- const projectPkg = JSON.parse(fs37.readFileSync(projectPackagePath, "utf-8"));
26148
+ const projectPkg = JSON.parse(fs39.readFileSync(projectPackagePath, "utf-8"));
25519
26149
  const nextVersion = projectPkg.dependencies?.next ?? projectPkg.devDependencies?.next;
25520
26150
  if (typeof nextVersion !== "string" || nextVersion.length === 0) {
25521
26151
  return void 0;
@@ -25647,23 +26277,23 @@ main().catch((error) => {
25647
26277
  };
25648
26278
  }
25649
26279
  function runSeedScript(cwd, adminDir, authBasePath, envOverrides) {
25650
- const scriptsDir = path48.join(cwd, adminDir, "scripts");
25651
- const seedPath = path48.join(scriptsDir, "seed.ts");
25652
- if (!fs37.existsSync(scriptsDir)) {
25653
- 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 });
25654
26284
  }
25655
- fs37.writeFileSync(seedPath, buildSeedScript(authBasePath), "utf-8");
26285
+ fs39.writeFileSync(seedPath, buildSeedScript(authBasePath), "utf-8");
25656
26286
  const cleanup = () => {
25657
26287
  try {
25658
- fs37.unlinkSync(seedPath);
25659
- if (fs37.existsSync(scriptsDir) && fs37.readdirSync(scriptsDir).length === 0) {
25660
- fs37.rmdirSync(scriptsDir);
26288
+ fs39.unlinkSync(seedPath);
26289
+ if (fs39.existsSync(scriptsDir) && fs39.readdirSync(scriptsDir).length === 0) {
26290
+ fs39.rmdirSync(scriptsDir);
25661
26291
  }
25662
26292
  } catch {
25663
26293
  }
25664
26294
  };
25665
26295
  return new Promise((resolve) => {
25666
- const tsxBin = path48.join(cwd, "node_modules", ".bin", "tsx");
26296
+ const tsxBin = path50.join(cwd, "node_modules", ".bin", "tsx");
25667
26297
  const child = spawn4(tsxBin, [seedPath], {
25668
26298
  cwd,
25669
26299
  stdio: "pipe",
@@ -25837,12 +26467,12 @@ function stripAnsi(value) {
25837
26467
  return value.replace(ANSI_ESCAPE_PATTERN, "");
25838
26468
  }
25839
26469
  function printAdminReadyNote(state) {
25840
- const lines = [`Admin: ${pc5.cyan(state.adminLoginUrl)}`];
26470
+ const lines = [`Admin: ${pc7.cyan(state.adminLoginUrl)}`];
25841
26471
  if (state.adminEmail && state.adminPassword) {
25842
- lines.unshift(`Password: ${pc5.cyan(state.adminPassword)}`);
25843
- 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)}`);
25844
26474
  }
25845
- p16.note(lines.join("\n"), "Admin ready");
26475
+ p17.note(lines.join("\n"), "Admin ready");
25846
26476
  }
25847
26477
  function shouldSuppressDevServerStartupLine(line) {
25848
26478
  const plain = stripAnsi(line).trim();
@@ -25852,7 +26482,7 @@ function shouldSuppressDevServerStartupLine(line) {
25852
26482
  if (plain.includes("Ready in ")) {
25853
26483
  return { suppress: true, isReadyLine: true };
25854
26484
  }
25855
- 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 ")) {
25856
26486
  return { suppress: true, isReadyLine: false };
25857
26487
  }
25858
26488
  if (plain.startsWith("\u2713 ") && !plain.includes("Ready in ")) {
@@ -25953,75 +26583,75 @@ function startManagedDevServer(cwd, devCmd, adminLoginUrl, adminCredentials) {
25953
26583
  }
25954
26584
 
25955
26585
  // adapters/next/commands/list-integrations.ts
25956
- import path49 from "path";
25957
- import * as p17 from "@clack/prompts";
26586
+ import path51 from "path";
26587
+ import * as p18 from "@clack/prompts";
25958
26588
  async function runListIntegrationsCommand(options) {
25959
- const cwd = options.cwd ? path49.resolve(options.cwd) : process.cwd();
26589
+ const cwd = options.cwd ? path51.resolve(options.cwd) : process.cwd();
25960
26590
  const config = await resolveConfig(cwd);
25961
26591
  const installedIntegrations = new Set(config.integrations.installed);
25962
26592
  const lines = listAvailableIntegrations().map((integration) => {
25963
26593
  const status = installedIntegrations.has(integration.id) ? "installed" : "available";
25964
- 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}`;
25965
26595
  });
25966
- p17.note(lines.join("\n"), "BetterStart integrations");
25967
- p17.outro(
26596
+ p18.note(lines.join("\n"), "BetterStart integrations");
26597
+ p18.outro(
25968
26598
  `${installedIntegrations.size} installed, ${lines.length - installedIntegrations.size} available`
25969
26599
  );
25970
26600
  }
25971
26601
 
25972
26602
  // adapters/next/commands/list-plugins.ts
25973
- import path50 from "path";
25974
- import * as p18 from "@clack/prompts";
26603
+ import path52 from "path";
26604
+ import * as p19 from "@clack/prompts";
25975
26605
  async function runListPluginsCommand(options) {
25976
- const cwd = options.cwd ? path50.resolve(options.cwd) : process.cwd();
26606
+ const cwd = options.cwd ? path52.resolve(options.cwd) : process.cwd();
25977
26607
  const config = await resolveConfig(cwd);
25978
26608
  const installedPlugins = new Set(config.plugins.installed);
25979
26609
  const lines = listAvailablePlugins().map((plugin) => {
25980
26610
  const status = installedPlugins.has(plugin.id) ? "installed" : "available";
25981
26611
  return `${plugin.id.padEnd(10)} ${status.padEnd(10)} ${plugin.kind.padEnd(12)} ${plugin.description}`;
25982
26612
  });
25983
- p18.note(lines.join("\n"), "BetterStart plugins");
25984
- p18.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`);
25985
26615
  }
25986
26616
 
25987
26617
  // adapters/next/commands/remove.ts
25988
- import path51 from "path";
25989
- import * as p19 from "@clack/prompts";
26618
+ import path53 from "path";
26619
+ import * as p20 from "@clack/prompts";
25990
26620
  async function runRemoveCommand(items, options) {
25991
26621
  const removeIntegrationsMode = Boolean(options.integration);
25992
26622
  if (!removeIntegrationsMode && items.includes("core")) {
25993
- p19.log.error("The core Admin cannot be removed.");
26623
+ p20.log.error("The core Admin cannot be removed.");
25994
26624
  process.exit(1);
25995
26625
  }
25996
26626
  const pluginIds = items.filter(isPluginId);
25997
26627
  const integrationIds = items.filter(isIntegrationId);
25998
26628
  if (!removeIntegrationsMode && integrationIds.length > 0) {
25999
- p19.log.error(
26629
+ p20.log.error(
26000
26630
  `Integration IDs require --integration. Run \`betterstart remove --integration ${integrationIds.join(" ")}\`.`
26001
26631
  );
26002
26632
  process.exit(1);
26003
26633
  }
26004
26634
  if (removeIntegrationsMode && pluginIds.length > 0) {
26005
- p19.log.error(`Plugin IDs cannot be removed with --integration: ${pluginIds.join(", ")}`);
26635
+ p20.log.error(`Plugin IDs cannot be removed with --integration: ${pluginIds.join(", ")}`);
26006
26636
  process.exit(1);
26007
26637
  }
26008
26638
  const invalidItems = removeIntegrationsMode ? items.filter((integrationId) => !isIntegrationId(integrationId)) : items.filter((pluginId) => !isPluginId(pluginId));
26009
26639
  if (invalidItems.length > 0) {
26010
- p19.log.error(
26640
+ p20.log.error(
26011
26641
  removeIntegrationsMode ? formatUnknownIntegrationMessage(invalidItems) : formatUnknownPluginMessage(invalidItems)
26012
26642
  );
26013
26643
  process.exit(1);
26014
26644
  }
26015
- const cwd = options.cwd ? path51.resolve(options.cwd) : process.cwd();
26645
+ const cwd = options.cwd ? path53.resolve(options.cwd) : process.cwd();
26016
26646
  const config = await resolveConfig(cwd);
26017
26647
  const pm = detectPackageManager(cwd);
26018
26648
  if (!options.force) {
26019
- const confirmed = await p19.confirm({
26649
+ const confirmed = await p20.confirm({
26020
26650
  message: `Remove ${removeIntegrationsMode ? "integration" : "plugin"}${items.length === 1 ? "" : "s"} ${items.join(", ")}?`,
26021
26651
  initialValue: false
26022
26652
  });
26023
- if (p19.isCancel(confirmed) || !confirmed) {
26024
- p19.cancel(`${removeIntegrationsMode ? "Integration" : "Plugin"} removal cancelled.`);
26653
+ if (p20.isCancel(confirmed) || !confirmed) {
26654
+ p20.cancel(`${removeIntegrationsMode ? "Integration" : "Plugin"} removal cancelled.`);
26025
26655
  process.exit(0);
26026
26656
  }
26027
26657
  }
@@ -26034,13 +26664,13 @@ async function runRemoveCommand(items, options) {
26034
26664
  });
26035
26665
  writeConfigFile(cwd, result2.config);
26036
26666
  if (result2.removed.length === 0) {
26037
- p19.outro("No integrations were removed.");
26667
+ p20.outro("No integrations were removed.");
26038
26668
  return;
26039
26669
  }
26040
26670
  if (result2.warnings.length > 0) {
26041
- p19.note(result2.warnings.join("\n"), "Warnings");
26671
+ p20.note(result2.warnings.join("\n"), "Warnings");
26042
26672
  }
26043
- p19.outro(
26673
+ p20.outro(
26044
26674
  `Removed integration${result2.removed.length === 1 ? "" : "s"}: ${result2.removed.join(", ")}`
26045
26675
  );
26046
26676
  return;
@@ -26053,18 +26683,18 @@ async function runRemoveCommand(items, options) {
26053
26683
  });
26054
26684
  writeConfigFile(cwd, result.config);
26055
26685
  if (result.removed.length === 0) {
26056
- p19.outro("No plugins were removed.");
26686
+ p20.outro("No plugins were removed.");
26057
26687
  return;
26058
26688
  }
26059
26689
  if (result.warnings.length > 0) {
26060
- p19.note(result.warnings.join("\n"), "Warnings");
26690
+ p20.note(result.warnings.join("\n"), "Warnings");
26061
26691
  }
26062
- p19.outro(`Removed plugin${result.removed.length === 1 ? "" : "s"}: ${result.removed.join(", ")}`);
26692
+ p20.outro(`Removed plugin${result.removed.length === 1 ? "" : "s"}: ${result.removed.join(", ")}`);
26063
26693
  }
26064
26694
 
26065
26695
  // adapters/next/commands/remove-schema.ts
26066
- import fs38 from "fs";
26067
- import path52 from "path";
26696
+ import fs40 from "fs";
26697
+ import path54 from "path";
26068
26698
  import readline from "readline";
26069
26699
  async function promptConfirm4(message) {
26070
26700
  const rl = readline.createInterface({
@@ -26079,30 +26709,30 @@ async function promptConfirm4(message) {
26079
26709
  });
26080
26710
  }
26081
26711
  function removePath2(cwd, filePath) {
26082
- const fullPath = path52.join(cwd, ...filePath.split("/"));
26083
- const existed = fs38.existsSync(fullPath);
26084
- 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 });
26085
26715
  return existed;
26086
26716
  }
26087
26717
  function cleanupEmptyDirs3(cwd, deletedPaths, configPaths) {
26088
26718
  const stopRoots = /* @__PURE__ */ new Set([
26089
- path52.join(cwd, ...configPaths.adminDir.split("/")),
26090
- path52.join(cwd, ...configPaths.adminNavigationDir.split("/")),
26091
- 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("/"))
26092
26722
  ]);
26093
26723
  for (const deletedPath of deletedPaths) {
26094
- let current = path52.dirname(path52.join(cwd, ...deletedPath.split("/")));
26724
+ let current = path54.dirname(path54.join(cwd, ...deletedPath.split("/")));
26095
26725
  while (!stopRoots.has(current)) {
26096
- if (!fs38.existsSync(current)) {
26097
- current = path52.dirname(current);
26726
+ if (!fs40.existsSync(current)) {
26727
+ current = path54.dirname(current);
26098
26728
  continue;
26099
26729
  }
26100
- const entries = fs38.readdirSync(current);
26730
+ const entries = fs40.readdirSync(current);
26101
26731
  if (entries.length > 0) {
26102
26732
  break;
26103
26733
  }
26104
- fs38.rmdirSync(current);
26105
- current = path52.dirname(current);
26734
+ fs40.rmdirSync(current);
26735
+ current = path54.dirname(current);
26106
26736
  }
26107
26737
  }
26108
26738
  }
@@ -26118,7 +26748,7 @@ function resolveSchemaOwnerForRemoval(cwd, schemaName) {
26118
26748
  }
26119
26749
  async function runRemoveSchemaCommand(schemaName, options) {
26120
26750
  const owner = resolveSchemaOwnerForRemoval(
26121
- options.cwd ? path52.resolve(options.cwd) : process.cwd(),
26751
+ options.cwd ? path54.resolve(options.cwd) : process.cwd(),
26122
26752
  schemaName
26123
26753
  );
26124
26754
  if (owner === "core") {
@@ -26131,7 +26761,7 @@ async function runRemoveSchemaCommand(schemaName, options) {
26131
26761
  console.error(` "${schemaName}" is owned by ${owner}. Remove the owning plugin instead.`);
26132
26762
  process.exit(1);
26133
26763
  }
26134
- const cwd = options.cwd ? path52.resolve(options.cwd) : process.cwd();
26764
+ const cwd = options.cwd ? path54.resolve(options.cwd) : process.cwd();
26135
26765
  const config = await resolveConfig(cwd);
26136
26766
  const paths = resolveProjectPaths(config);
26137
26767
  const manifest = loadManifest(cwd, schemaName);
@@ -26162,7 +26792,7 @@ async function runRemoveSchemaCommand(schemaName, options) {
26162
26792
  }
26163
26793
  const loaded = (() => {
26164
26794
  try {
26165
- return loadSchema(path52.join(cwd, ...paths.schemasDir.split("/")), schemaName);
26795
+ return loadSchema(path54.join(cwd, ...paths.schemasDir.split("/")), schemaName);
26166
26796
  } catch {
26167
26797
  return null;
26168
26798
  }
@@ -26206,13 +26836,13 @@ async function runRemoveSchemaCommand(schemaName, options) {
26206
26836
  }
26207
26837
 
26208
26838
  // adapters/next/commands/uninstall.ts
26209
- import fs40 from "fs";
26210
- import path53 from "path";
26211
- import * as p20 from "@clack/prompts";
26212
- 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";
26213
26843
 
26214
26844
  // adapters/next/commands/uninstall-cleaners.ts
26215
- import fs39 from "fs";
26845
+ import fs41 from "fs";
26216
26846
  function stripJsonComments2(input) {
26217
26847
  let result = "";
26218
26848
  let i = 0;
@@ -26246,8 +26876,8 @@ function stripJsonComments2(input) {
26246
26876
  return result;
26247
26877
  }
26248
26878
  function cleanTsconfig(tsconfigPath, aliasRoot = "@admin") {
26249
- if (!fs39.existsSync(tsconfigPath)) return [];
26250
- const raw = fs39.readFileSync(tsconfigPath, "utf-8");
26879
+ if (!fs41.existsSync(tsconfigPath)) return [];
26880
+ const raw = fs41.readFileSync(tsconfigPath, "utf-8");
26251
26881
  const stripped = stripJsonComments2(raw).replace(/,\s*([\]}])/g, "$1");
26252
26882
  let tsconfig;
26253
26883
  try {
@@ -26271,13 +26901,13 @@ function cleanTsconfig(tsconfigPath, aliasRoot = "@admin") {
26271
26901
  compilerOptions.paths = paths;
26272
26902
  }
26273
26903
  tsconfig.compilerOptions = compilerOptions;
26274
- fs39.writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
26904
+ fs41.writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
26275
26905
  `, "utf-8");
26276
26906
  return removed;
26277
26907
  }
26278
26908
  function cleanCss(cssPath, namespace = "admin") {
26279
- if (!fs39.existsSync(cssPath)) return [];
26280
- const content = fs39.readFileSync(cssPath, "utf-8");
26909
+ if (!fs41.existsSync(cssPath)) return [];
26910
+ const content = fs41.readFileSync(cssPath, "utf-8");
26281
26911
  const lines = content.split("\n");
26282
26912
  const sourcePattern = new RegExp(`^@source\\s+"[^"]*(?:admin|${namespace})[^"]*";\\s*$`);
26283
26913
  const removed = [];
@@ -26291,12 +26921,12 @@ function cleanCss(cssPath, namespace = "admin") {
26291
26921
  }
26292
26922
  if (removed.length === 0) return [];
26293
26923
  const cleaned = kept.join("\n").replace(/\n{3,}/g, "\n\n");
26294
- fs39.writeFileSync(cssPath, cleaned, "utf-8");
26924
+ fs41.writeFileSync(cssPath, cleaned, "utf-8");
26295
26925
  return removed;
26296
26926
  }
26297
26927
  function cleanEnvFile(envPath) {
26298
- if (!fs39.existsSync(envPath)) return [];
26299
- const content = fs39.readFileSync(envPath, "utf-8");
26928
+ if (!fs41.existsSync(envPath)) return [];
26929
+ const content = fs41.readFileSync(envPath, "utf-8");
26300
26930
  const lines = content.split("\n");
26301
26931
  const removed = [];
26302
26932
  const kept = [];
@@ -26329,9 +26959,9 @@ function cleanEnvFile(envPath) {
26329
26959
  if (removed.length === 0) return [];
26330
26960
  const result = kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
26331
26961
  if (result === "") {
26332
- fs39.unlinkSync(envPath);
26962
+ fs41.unlinkSync(envPath);
26333
26963
  } else {
26334
- fs39.writeFileSync(envPath, `${result}
26964
+ fs41.writeFileSync(envPath, `${result}
26335
26965
  `, "utf-8");
26336
26966
  }
26337
26967
  return removed;
@@ -26357,15 +26987,15 @@ function findMainCss2(cwd) {
26357
26987
  "globals.css"
26358
26988
  ];
26359
26989
  for (const candidate of candidates) {
26360
- const filePath = path53.join(cwd, candidate);
26361
- if (fs40.existsSync(filePath)) return filePath;
26990
+ const filePath = path55.join(cwd, candidate);
26991
+ if (fs42.existsSync(filePath)) return filePath;
26362
26992
  }
26363
26993
  return void 0;
26364
26994
  }
26365
26995
  function isCLICreatedBiome(biomePath) {
26366
- if (!fs40.existsSync(biomePath)) return false;
26996
+ if (!fs42.existsSync(biomePath)) return false;
26367
26997
  try {
26368
- const content = JSON.parse(fs40.readFileSync(biomePath, "utf-8"));
26998
+ const content = JSON.parse(fs42.readFileSync(biomePath, "utf-8"));
26369
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");
26370
27000
  } catch {
26371
27001
  return false;
@@ -26374,17 +27004,17 @@ function isCLICreatedBiome(biomePath) {
26374
27004
  function buildUninstallPlan(cwd, namespaceValue) {
26375
27005
  const steps = [];
26376
27006
  const namespace = resolveAdminNamespace(namespaceValue);
26377
- const hasSrc = fs40.existsSync(path53.join(cwd, "src"));
27007
+ const hasSrc = fs42.existsSync(path55.join(cwd, "src"));
26378
27008
  const appBase = hasSrc ? "src/app" : "app";
26379
27009
  const dirs = [];
26380
- const adminDir = path53.join(cwd, namespace.segment);
26381
- const legacyAdminDir = path53.join(cwd, "admin");
26382
- const adminRouteGroup = path53.join(cwd, appBase, namespace.routeGroup);
26383
- const legacyAdminRouteGroup = path53.join(cwd, appBase, "(admin)");
26384
- if (fs40.existsSync(adminDir)) dirs.push(`${namespace.segment}/`);
26385
- if (namespace.segment !== "admin" && fs40.existsSync(legacyAdminDir)) dirs.push("admin/");
26386
- if (fs40.existsSync(adminRouteGroup)) dirs.push(`${appBase}/${namespace.routeGroup}/`);
26387
- 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))
26388
27018
  dirs.push(`${appBase}/(admin)/`);
26389
27019
  if (dirs.length > 0) {
26390
27020
  steps.push({
@@ -26393,14 +27023,14 @@ function buildUninstallPlan(cwd, namespaceValue) {
26393
27023
  count: dirs.length,
26394
27024
  unit: dirs.length === 1 ? "directory" : "directories",
26395
27025
  execute() {
26396
- if (fs40.existsSync(adminDir)) fs40.rmSync(adminDir, { recursive: true, force: true });
26397
- if (fs40.existsSync(legacyAdminDir))
26398
- fs40.rmSync(legacyAdminDir, { recursive: true, force: true });
26399
- if (fs40.existsSync(adminRouteGroup)) {
26400
- 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 });
26401
27031
  }
26402
- if (fs40.existsSync(legacyAdminRouteGroup)) {
26403
- fs40.rmSync(legacyAdminRouteGroup, { recursive: true, force: true });
27032
+ if (fs42.existsSync(legacyAdminRouteGroup)) {
27033
+ fs42.rmSync(legacyAdminRouteGroup, { recursive: true, force: true });
26404
27034
  }
26405
27035
  }
26406
27036
  });
@@ -26408,17 +27038,17 @@ function buildUninstallPlan(cwd, namespaceValue) {
26408
27038
  const configFiles = [];
26409
27039
  const configPaths = [];
26410
27040
  const candidates = [
26411
- ["admin.config.ts", path53.join(cwd, "admin.config.ts")],
26412
- ["drizzle.config.ts", path53.join(cwd, "drizzle.config.ts")],
26413
- ["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")]
26414
27044
  ];
26415
27045
  for (const [label, fullPath] of candidates) {
26416
- if (fs40.existsSync(fullPath)) {
27046
+ if (fs42.existsSync(fullPath)) {
26417
27047
  configFiles.push(label);
26418
27048
  configPaths.push(fullPath);
26419
27049
  }
26420
27050
  }
26421
- const biomePath = path53.join(cwd, "biome.json");
27051
+ const biomePath = path55.join(cwd, "biome.json");
26422
27052
  if (isCLICreatedBiome(biomePath)) {
26423
27053
  configFiles.push("biome.json (CLI-created)");
26424
27054
  configPaths.push(biomePath);
@@ -26430,15 +27060,15 @@ function buildUninstallPlan(cwd, namespaceValue) {
26430
27060
  count: configFiles.length,
26431
27061
  unit: configFiles.length === 1 ? "file" : "files",
26432
27062
  execute() {
26433
- for (const p21 of configPaths) {
26434
- if (fs40.existsSync(p21)) fs40.unlinkSync(p21);
27063
+ for (const p22 of configPaths) {
27064
+ if (fs42.existsSync(p22)) fs42.unlinkSync(p22);
26435
27065
  }
26436
27066
  }
26437
27067
  });
26438
27068
  }
26439
- const tsconfigPath = path53.join(cwd, "tsconfig.json");
26440
- if (fs40.existsSync(tsconfigPath)) {
26441
- 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");
26442
27072
  const aliasMatches = [
26443
27073
  ...content.match(/"@admin\//g) ?? [],
26444
27074
  ...content.match(new RegExp(`"${namespace.alias}/`, "g")) ?? []
@@ -26458,12 +27088,12 @@ function buildUninstallPlan(cwd, namespaceValue) {
26458
27088
  }
26459
27089
  const cssFile = findMainCss2(cwd);
26460
27090
  if (cssFile) {
26461
- const cssContent = fs40.readFileSync(cssFile, "utf-8");
27091
+ const cssContent = fs42.readFileSync(cssFile, "utf-8");
26462
27092
  const sourceLines = cssContent.split("\n").filter(
26463
27093
  (l) => new RegExp(`^@source\\s+"[^"]*(?:admin|${namespace.segment})[^"]*";\\s*$`).test(l)
26464
27094
  );
26465
27095
  if (sourceLines.length > 0) {
26466
- const relCss = path53.relative(cwd, cssFile);
27096
+ const relCss = path55.relative(cwd, cssFile);
26467
27097
  steps.push({
26468
27098
  label: `CSS @source lines (${relCss})`,
26469
27099
  items: [`@source lines in ${relCss}`],
@@ -26475,9 +27105,9 @@ function buildUninstallPlan(cwd, namespaceValue) {
26475
27105
  });
26476
27106
  }
26477
27107
  }
26478
- const envPath = path53.join(cwd, ".env.local");
26479
- if (fs40.existsSync(envPath)) {
26480
- 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");
26481
27111
  const bsVars = envContent.split("\n").filter((l) => l.trim().match(/^BETTERSTART_\w+=/)).map((l) => l.split("=")[0]);
26482
27112
  if (bsVars.length > 0) {
26483
27113
  steps.push({
@@ -26494,8 +27124,8 @@ function buildUninstallPlan(cwd, namespaceValue) {
26494
27124
  return steps;
26495
27125
  }
26496
27126
  async function runUninstallCommand(options) {
26497
- const cwd = options.cwd ? path53.resolve(options.cwd) : process.cwd();
26498
- p20.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 ")));
26499
27129
  let namespace = DEFAULT_ADMIN_NAMESPACE;
26500
27130
  try {
26501
27131
  const config = await resolveConfig(cwd);
@@ -26504,27 +27134,27 @@ async function runUninstallCommand(options) {
26504
27134
  }
26505
27135
  const steps = buildUninstallPlan(cwd, namespace);
26506
27136
  if (steps.length === 0) {
26507
- p20.log.success(`${pc6.green("\u2713")} Nothing to remove \u2014 project is already clean.`);
26508
- p20.outro("Done");
27137
+ p21.log.success(`${pc8.green("\u2713")} Nothing to remove \u2014 project is already clean.`);
27138
+ p21.outro("Done");
26509
27139
  return;
26510
27140
  }
26511
27141
  const planLines = steps.map((step) => {
26512
27142
  const names = step.items.join(" ");
26513
- const countLabel = pc6.dim(`${step.count} ${step.unit}`);
26514
- return `${pc6.red("\xD7")} ${names} ${countLabel}`;
27143
+ const countLabel = pc8.dim(`${step.count} ${step.unit}`);
27144
+ return `${pc8.red("\xD7")} ${names} ${countLabel}`;
26515
27145
  });
26516
- p20.note(planLines.join("\n"), "Uninstall plan");
27146
+ p21.note(planLines.join("\n"), "Uninstall plan");
26517
27147
  if (!options.force) {
26518
- const confirmed = await p20.confirm({
27148
+ const confirmed = await p21.confirm({
26519
27149
  message: "Proceed with uninstall?",
26520
27150
  initialValue: false
26521
27151
  });
26522
- if (p20.isCancel(confirmed) || !confirmed) {
26523
- p20.cancel("Uninstall cancelled.");
27152
+ if (p21.isCancel(confirmed) || !confirmed) {
27153
+ p21.cancel("Uninstall cancelled.");
26524
27154
  process.exit(0);
26525
27155
  }
26526
27156
  }
26527
- const s = p20.spinner();
27157
+ const s = spinner2();
26528
27158
  s.start(steps[0].label);
26529
27159
  for (const step of steps) {
26530
27160
  s.message(step.label);
@@ -26532,14 +27162,14 @@ async function runUninstallCommand(options) {
26532
27162
  }
26533
27163
  const parts = steps.map((step) => `${step.count} ${step.unit}`);
26534
27164
  s.stop(`Removed ${parts.join(", ")}`);
26535
- p20.note(pc6.dim("Database tables were NOT dropped \u2014 drop them manually if needed."), "Next steps");
26536
- p20.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");
26537
27167
  }
26538
27168
 
26539
27169
  // adapters/next/commands/update-component.ts
26540
27170
  import { execFileSync as execFileSync6 } from "child_process";
26541
- import fs41 from "fs";
26542
- import path54 from "path";
27171
+ import fs43 from "fs";
27172
+ import path56 from "path";
26543
27173
  import * as clack2 from "@clack/prompts";
26544
27174
  import fsExtra from "fs-extra";
26545
27175
  var STATIC_CUSTOM_DEPENDENCIES = {
@@ -26789,24 +27419,24 @@ function applyNamespaceToTemplateEntry(entry, config, cwd) {
26789
27419
  };
26790
27420
  }
26791
27421
  function writeNamespacedFile(srcPath, destPath, namespace) {
26792
- fs41.writeFileSync(
27422
+ fs43.writeFileSync(
26793
27423
  destPath,
26794
- applyAdminNamespaceToContent(fs41.readFileSync(srcPath, "utf-8"), namespace),
27424
+ applyAdminNamespaceToContent(fs43.readFileSync(srcPath, "utf-8"), namespace),
26795
27425
  "utf-8"
26796
27426
  );
26797
27427
  }
26798
27428
  function copyNamespacedDirectory(srcDir, destDir, namespace) {
26799
- const entries = fs41.readdirSync(srcDir, { withFileTypes: true });
27429
+ const entries = fs43.readdirSync(srcDir, { withFileTypes: true });
26800
27430
  for (const entry of entries) {
26801
27431
  const namespacedName = applyAdminNamespaceToPath(entry.name, namespace);
26802
- const srcPath = path54.join(srcDir, entry.name);
26803
- const destPath = path54.join(destDir, namespacedName);
27432
+ const srcPath = path56.join(srcDir, entry.name);
27433
+ const destPath = path56.join(destDir, namespacedName);
26804
27434
  if (entry.isDirectory()) {
26805
27435
  fsExtra.ensureDirSync(destPath);
26806
27436
  copyNamespacedDirectory(srcPath, destPath, namespace);
26807
27437
  continue;
26808
27438
  }
26809
- fsExtra.ensureDirSync(path54.dirname(destPath));
27439
+ fsExtra.ensureDirSync(path56.dirname(destPath));
26810
27440
  writeNamespacedFile(srcPath, destPath, namespace);
26811
27441
  }
26812
27442
  }
@@ -26817,10 +27447,10 @@ function hasIntegration(config, integrationId) {
26817
27447
  return config.integrations.installed.includes(integrationId);
26818
27448
  }
26819
27449
  function readProjectPackageJson2(cwd) {
26820
- const pkgPath = path54.join(cwd, "package.json");
26821
- if (!fs41.existsSync(pkgPath)) return null;
27450
+ const pkgPath = path56.join(cwd, "package.json");
27451
+ if (!fs43.existsSync(pkgPath)) return null;
26822
27452
  try {
26823
- return JSON.parse(fs41.readFileSync(pkgPath, "utf-8"));
27453
+ return JSON.parse(fs43.readFileSync(pkgPath, "utf-8"));
26824
27454
  } catch {
26825
27455
  return null;
26826
27456
  }
@@ -27733,6 +28363,11 @@ var TEMPLATE_REGISTRY = {
27733
28363
  requiredIntegration: "r2",
27734
28364
  content: () => readIntegrationTemplate("r2", "actions/r2.ts")
27735
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
+ },
27736
28371
  "form-settings-action": {
27737
28372
  relPath: "lib/actions/forms/index.ts",
27738
28373
  content: () => readTemplate("lib/actions/forms/index.ts"),
@@ -28111,9 +28746,9 @@ function getStaticAssetComponents(assetDirectory) {
28111
28746
  }
28112
28747
  function getStaticAssetComponentEntries(assetDirectory) {
28113
28748
  const assetDir = resolveCliAssetPath("shared-assets", "react-admin", assetDirectory);
28114
- if (!fs41.existsSync(assetDir)) return [];
28749
+ if (!fs43.existsSync(assetDir)) return [];
28115
28750
  const components = [];
28116
- for (const entry of fs41.readdirSync(assetDir, { withFileTypes: true })) {
28751
+ for (const entry of fs43.readdirSync(assetDir, { withFileTypes: true })) {
28117
28752
  if (entry.isFile() && (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts"))) {
28118
28753
  components.push({
28119
28754
  name: entry.name.replace(/\.(tsx|ts)$/, ""),
@@ -28125,40 +28760,40 @@ function getStaticAssetComponentEntries(assetDirectory) {
28125
28760
  continue;
28126
28761
  }
28127
28762
  const indexFile = ["index.tsx", "index.ts"].find(
28128
- (file) => fs41.existsSync(path54.join(assetDir, entry.name, file))
28763
+ (file) => fs43.existsSync(path56.join(assetDir, entry.name, file))
28129
28764
  );
28130
28765
  if (indexFile) {
28131
28766
  components.push({
28132
28767
  name: entry.name,
28133
- file: path54.join(entry.name, indexFile)
28768
+ file: path56.join(entry.name, indexFile)
28134
28769
  });
28135
28770
  }
28136
28771
  }
28137
28772
  return components.sort((a, b) => a.name.localeCompare(b.name));
28138
28773
  }
28139
28774
  function findStaticAssetFile(assetDir, componentName) {
28140
- if (!fs41.existsSync(assetDir)) return void 0;
28775
+ if (!fs43.existsSync(assetDir)) return void 0;
28141
28776
  const isNestedComponentName = /[\\/]/.test(componentName);
28142
- const nestedComponentName = componentName.split(/[\\/]/).filter(Boolean).join(path54.sep);
28143
- 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("..")) {
28144
28779
  for (const extension of [".tsx", ".ts"]) {
28145
28780
  const relPath = `${nestedComponentName}${extension}`;
28146
- const filePath = path54.join(assetDir, relPath);
28147
- if (fs41.existsSync(filePath) && fs41.statSync(filePath).isFile()) {
28781
+ const filePath = path56.join(assetDir, relPath);
28782
+ if (fs43.existsSync(filePath) && fs43.statSync(filePath).isFile()) {
28148
28783
  return relPath;
28149
28784
  }
28150
28785
  }
28151
28786
  }
28152
- if (!isNestedComponentName && !componentName.includes("..") && !path54.isAbsolute(componentName)) {
28787
+ if (!isNestedComponentName && !componentName.includes("..") && !path56.isAbsolute(componentName)) {
28153
28788
  for (const extension of [".tsx", ".ts"]) {
28154
- const relPath = path54.join(componentName, `index${extension}`);
28155
- const filePath = path54.join(assetDir, relPath);
28156
- 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()) {
28157
28792
  return relPath;
28158
28793
  }
28159
28794
  }
28160
28795
  }
28161
- return fs41.readdirSync(assetDir, { withFileTypes: true }).find(
28796
+ return fs43.readdirSync(assetDir, { withFileTypes: true }).find(
28162
28797
  (entry) => entry.isFile() && (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts")) && entry.name.replace(/\.(tsx|ts)$/, "") === componentName
28163
28798
  )?.name;
28164
28799
  }
@@ -28177,7 +28812,7 @@ function getAllComponentNamesForConfig(config) {
28177
28812
  return [.../* @__PURE__ */ new Set([...staticUi, ...staticCustom, ...templateKeys, "tiptap"])].sort();
28178
28813
  }
28179
28814
  async function runUpdateCommand(components, options) {
28180
- const cwd = options.cwd ? path54.resolve(options.cwd) : process.cwd();
28815
+ const cwd = options.cwd ? path56.resolve(options.cwd) : process.cwd();
28181
28816
  const normalizedOnly = normalizeShadcnPresetOnly(options.only);
28182
28817
  validateShadcnPresetOptions(components, options);
28183
28818
  if (options.list) {
@@ -28221,8 +28856,8 @@ async function runUpdateCommand(components, options) {
28221
28856
  return;
28222
28857
  }
28223
28858
  const config = await resolveConfig(cwd);
28224
- const admin = path54.resolve(cwd, config.paths.admin);
28225
- if (!fs41.existsSync(admin)) {
28859
+ const admin = path56.resolve(cwd, config.paths.admin);
28860
+ if (!fs43.existsSync(admin)) {
28226
28861
  clack2.cancel(
28227
28862
  `Admin directory not found at ${config.paths.admin}. Run 'betterstart init' first.`
28228
28863
  );
@@ -28275,15 +28910,15 @@ async function runUpdateCommand(components, options) {
28275
28910
  }
28276
28911
  const { relPath, content } = applyNamespaceToTemplateEntry(entry, config, cwd);
28277
28912
  const baseDir = entry.base === "cwd" ? cwd : admin;
28278
- const destPath = path54.join(baseDir, relPath);
28279
- if (entry.preserveExisting && fs41.existsSync(destPath)) {
28913
+ const destPath = path56.join(baseDir, relPath);
28914
+ if (entry.preserveExisting && fs43.existsSync(destPath)) {
28280
28915
  clack2.log.info(`Preserved ${relPath}`);
28281
28916
  updatedTemplateNames.add(name);
28282
28917
  skipped++;
28283
28918
  return false;
28284
28919
  }
28285
- fsExtra.ensureDirSync(path54.dirname(destPath));
28286
- fs41.writeFileSync(destPath, content, "utf-8");
28920
+ fsExtra.ensureDirSync(path56.dirname(destPath));
28921
+ fs43.writeFileSync(destPath, content, "utf-8");
28287
28922
  clack2.log.success(`Updated ${relPath}`);
28288
28923
  updatedTemplateNames.add(name);
28289
28924
  trackPackageDependencies(name);
@@ -28312,15 +28947,15 @@ async function runUpdateCommand(components, options) {
28312
28947
  }
28313
28948
  const namespace = config.frameworkConfig.next.namespace;
28314
28949
  const namespacedAssetFile = applyAdminNamespaceToPath(assetFile, namespace);
28315
- const destPath = path54.join(admin, "components", assetDirectory, namespacedAssetFile);
28316
- fsExtra.ensureDirSync(path54.dirname(destPath));
28317
- 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);
28318
28953
  clack2.log.success(`Updated components/${assetDirectory}/${namespacedAssetFile}`);
28319
28954
  if (assetDirectory === "custom") {
28320
- const assetSubdir = path54.join(assetDir, name);
28321
- if (fs41.existsSync(assetSubdir) && fs41.statSync(assetSubdir).isDirectory()) {
28955
+ const assetSubdir = path56.join(assetDir, name);
28956
+ if (fs43.existsSync(assetSubdir) && fs43.statSync(assetSubdir).isDirectory()) {
28322
28957
  const namespacedName = applyAdminNamespaceToPath(name, namespace);
28323
- const destSubdir = path54.join(admin, "components", assetDirectory, namespacedName);
28958
+ const destSubdir = path56.join(admin, "components", assetDirectory, namespacedName);
28324
28959
  fsExtra.emptyDirSync(destSubdir);
28325
28960
  copyNamespacedDirectory(assetSubdir, destSubdir, namespace);
28326
28961
  clack2.log.success(`Updated components/${assetDirectory}/${namespacedName}/ (all files)`);
@@ -28348,17 +28983,17 @@ async function runUpdateCommand(components, options) {
28348
28983
  "content-editor"
28349
28984
  );
28350
28985
  const namespace = config.frameworkConfig.next.namespace;
28351
- const destBaseDir = path54.join(admin, "components", "custom", "content-editor");
28352
- if (!fs41.existsSync(srcBaseDir)) {
28986
+ const destBaseDir = path56.join(admin, "components", "custom", "content-editor");
28987
+ if (!fs43.existsSync(srcBaseDir)) {
28353
28988
  return false;
28354
28989
  }
28355
28990
  let copied = false;
28356
28991
  for (const directory of TIPTAP_CONTENT_EDITOR_DIRECTORIES) {
28357
- const srcDir = path54.join(srcBaseDir, directory);
28358
- if (!fs41.existsSync(srcDir)) {
28992
+ const srcDir = path56.join(srcBaseDir, directory);
28993
+ if (!fs43.existsSync(srcDir)) {
28359
28994
  continue;
28360
28995
  }
28361
- const destDir = path54.join(destBaseDir, applyAdminNamespaceToPath(directory, namespace));
28996
+ const destDir = path56.join(destBaseDir, applyAdminNamespaceToPath(directory, namespace));
28362
28997
  fsExtra.emptyDirSync(destDir);
28363
28998
  copyNamespacedDirectory(srcDir, destDir, namespace);
28364
28999
  copied = true;
@@ -28370,7 +29005,7 @@ async function runUpdateCommand(components, options) {
28370
29005
  updatedStaticNames.add(key);
28371
29006
  trackPackageDependencies("tiptap");
28372
29007
  updated++;
28373
- removeLegacyDirectory(path54.join(admin, "components", "custom", "tiptap"));
29008
+ removeLegacyDirectory(path56.join(admin, "components", "custom", "tiptap"));
28374
29009
  for (const dependencyName of TIPTAP_TEMPLATE_DEPENDENCIES) {
28375
29010
  writeNamedDependency(dependencyName);
28376
29011
  }
@@ -28430,8 +29065,8 @@ async function runUpdateCommand(components, options) {
28430
29065
  );
28431
29066
  }
28432
29067
  function removeLegacyDirectory(dirPath) {
28433
- if (fs41.existsSync(dirPath)) {
28434
- fs41.rmSync(dirPath, { recursive: true, force: true });
29068
+ if (fs43.existsSync(dirPath)) {
29069
+ fs43.rmSync(dirPath, { recursive: true, force: true });
28435
29070
  }
28436
29071
  }
28437
29072
  function validateShadcnPresetOptions(components, options) {
@@ -28475,22 +29110,22 @@ function runShadcnPresetUpdate({
28475
29110
  only
28476
29111
  }) {
28477
29112
  const namespace = resolveAdminNamespace(config.frameworkConfig.next.namespace);
28478
- const adminGlobalsPath = path54.join(cwd, config.paths.admin, namespace.globalsFile);
28479
- 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");
28480
29115
  const shadcnBackupPath = `${componentsJsonPath}.bak`;
28481
29116
  const restoreAfterApplyPaths = [
28482
29117
  componentsJsonPath,
28483
29118
  shadcnBackupPath,
28484
- path54.join(cwd, config.paths.admin, "lib", "utils.ts"),
29119
+ path56.join(cwd, config.paths.admin, "lib", "utils.ts"),
28485
29120
  ...getHostProjectFilesToRestore(cwd)
28486
29121
  ];
28487
29122
  if (!preset) {
28488
29123
  clack2.cancel("--shadcn-preset requires a preset code, preset name, or preset URL.");
28489
29124
  process.exit(1);
28490
29125
  }
28491
- if (!fs41.existsSync(adminGlobalsPath)) {
29126
+ if (!fs43.existsSync(adminGlobalsPath)) {
28492
29127
  clack2.cancel(
28493
- `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.`
28494
29129
  );
28495
29130
  process.exit(1);
28496
29131
  }
@@ -28500,10 +29135,10 @@ function runShadcnPresetUpdate({
28500
29135
  snapshot: snapshotFile(filePath)
28501
29136
  }));
28502
29137
  clack2.intro("BetterStart Shadcn Preset");
28503
- 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")}`);
28504
29139
  let failed = false;
28505
29140
  try {
28506
- fs41.writeFileSync(
29141
+ fs43.writeFileSync(
28507
29142
  componentsJsonPath,
28508
29143
  `${JSON.stringify(createAdminShadcnComponentsJson(config), null, 2)}
28509
29144
  `,
@@ -28559,8 +29194,8 @@ function toPosixPath(value) {
28559
29194
  }
28560
29195
  function resolveLocalShadcnBin(cwd) {
28561
29196
  const binName = process.platform === "win32" ? "shadcn.cmd" : "shadcn";
28562
- const shadcnBin = path54.join(cwd, "node_modules", ".bin", binName);
28563
- if (!fs41.existsSync(shadcnBin)) {
29197
+ const shadcnBin = path56.join(cwd, "node_modules", ".bin", binName);
29198
+ if (!fs43.existsSync(shadcnBin)) {
28564
29199
  clack2.cancel(
28565
29200
  `shadcn is not installed in this project. Run 'betterstart update-deps' and try again.`
28566
29201
  );
@@ -28581,29 +29216,29 @@ function getHostProjectFilesToRestore(cwd) {
28581
29216
  "app/globals.css",
28582
29217
  "src/app/globals.css"
28583
29218
  ];
28584
- return hostRelativePaths.map((relativePath) => path54.join(cwd, relativePath));
29219
+ return hostRelativePaths.map((relativePath) => path56.join(cwd, relativePath));
28585
29220
  }
28586
29221
  function snapshotFile(filePath) {
28587
- if (!fs41.existsSync(filePath)) {
29222
+ if (!fs43.existsSync(filePath)) {
28588
29223
  return { existed: false };
28589
29224
  }
28590
- return { existed: true, content: fs41.readFileSync(filePath, "utf-8") };
29225
+ return { existed: true, content: fs43.readFileSync(filePath, "utf-8") };
28591
29226
  }
28592
29227
  function restoreFile(filePath, snapshot) {
28593
29228
  if (snapshot.existed) {
28594
- fs41.writeFileSync(filePath, snapshot.content ?? "", "utf-8");
29229
+ fs43.writeFileSync(filePath, snapshot.content ?? "", "utf-8");
28595
29230
  return;
28596
29231
  }
28597
- if (fs41.existsSync(filePath)) {
28598
- fs41.rmSync(filePath, { force: true });
29232
+ if (fs43.existsSync(filePath)) {
29233
+ fs43.rmSync(filePath, { force: true });
28599
29234
  }
28600
29235
  }
28601
29236
 
28602
29237
  // adapters/next/commands/update-deps.ts
28603
- import path55 from "path";
29238
+ import path57 from "path";
28604
29239
  import * as clack3 from "@clack/prompts";
28605
29240
  async function runUpdateDepsCommand(options) {
28606
- const cwd = options.cwd ? path55.resolve(options.cwd) : process.cwd();
29241
+ const cwd = options.cwd ? path57.resolve(options.cwd) : process.cwd();
28607
29242
  clack3.intro("BetterStart Update Dependencies");
28608
29243
  const pm = detectPackageManager(cwd);
28609
29244
  clack3.log.info(`Package manager: ${pm}`);
@@ -28614,7 +29249,7 @@ async function runUpdateDepsCommand(options) {
28614
29249
  false
28615
29250
  );
28616
29251
  const cliDependencyPlan = getCliDependencySyncPlan(cwd);
28617
- const s = clack3.spinner();
29252
+ const s = spinner2();
28618
29253
  s.start("Installing dependencies...");
28619
29254
  const result = await installDependenciesAsync({
28620
29255
  cwd,
@@ -28637,26 +29272,26 @@ async function runUpdateDepsCommand(options) {
28637
29272
  }
28638
29273
 
28639
29274
  // adapters/next/commands/update-styles.ts
28640
- import fs42 from "fs";
28641
- import path56 from "path";
29275
+ import fs44 from "fs";
29276
+ import path58 from "path";
28642
29277
  import * as clack4 from "@clack/prompts";
28643
29278
  async function runUpdateStylesCommand(options) {
28644
- const cwd = options.cwd ? path56.resolve(options.cwd) : process.cwd();
29279
+ const cwd = options.cwd ? path58.resolve(options.cwd) : process.cwd();
28645
29280
  clack4.intro("BetterStart Update Styles");
28646
29281
  const config = await resolveConfig(cwd);
28647
29282
  const adminDir = config.paths.admin;
28648
29283
  const namespace = resolveAdminNamespace(config.frameworkConfig.next.namespace);
28649
- const targetPath = path56.join(cwd, adminDir, namespace.globalsFile);
28650
- if (!fs42.existsSync(targetPath)) {
28651
- 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)}`);
28652
29287
  process.exit(1);
28653
29288
  }
28654
- fs42.writeFileSync(
29289
+ fs44.writeFileSync(
28655
29290
  targetPath,
28656
29291
  applyAdminNamespaceToContent(readTemplate("admin-globals.css"), namespace.segment),
28657
29292
  "utf-8"
28658
29293
  );
28659
- clack4.log.success(`Updated ${path56.relative(cwd, targetPath)}`);
29294
+ clack4.log.success(`Updated ${path58.relative(cwd, targetPath)}`);
28660
29295
  clack4.outro("Styles updated");
28661
29296
  }
28662
29297