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,
|
|
2591
|
+
function collectSlotLayoutErrors(value, path59, errors) {
|
|
2555
2592
|
if (!isRecord(value)) {
|
|
2556
|
-
errors.push(`${
|
|
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(`${
|
|
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, `${
|
|
2610
|
+
collectSlotAreaErrors(area, `${path59}.${name}`, errors);
|
|
2574
2611
|
}
|
|
2575
2612
|
if (!hasArea) {
|
|
2576
|
-
errors.push(`${
|
|
2613
|
+
errors.push(`${path59} must define at least one of "main" or "sidebar".`);
|
|
2577
2614
|
}
|
|
2578
2615
|
}
|
|
2579
|
-
function collectSlotAreaErrors(value,
|
|
2616
|
+
function collectSlotAreaErrors(value, path59, errors) {
|
|
2580
2617
|
if (!isRecord(value)) {
|
|
2581
|
-
errors.push(`${
|
|
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(`${
|
|
2623
|
+
errors.push(`${path59}.fields must be an array.`);
|
|
2587
2624
|
return;
|
|
2588
2625
|
}
|
|
2589
2626
|
for (const field of fields) {
|
|
2590
|
-
walkSlot(field, `${
|
|
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
|
|
3108
|
+
async function collectResendConfig(cwd) {
|
|
3072
3109
|
const overwriteEnvKeys = /* @__PURE__ */ new Set();
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
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
|
|
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
|
|
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,
|
|
4318
|
+
function collectReadSelectFields(fields, path59 = []) {
|
|
4244
4319
|
const matches = fields.flatMap((field) => {
|
|
4245
|
-
const currentPath = [...
|
|
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(
|
|
5367
|
+
function buildComponentSource(p22) {
|
|
5293
5368
|
const providerOpen = ` <NuqsAdapter>
|
|
5294
5369
|
<React.Suspense fallback={null}>
|
|
5295
|
-
<${
|
|
5370
|
+
<${p22.pascal}FormInner />
|
|
5296
5371
|
</React.Suspense>
|
|
5297
5372
|
</NuqsAdapter>`;
|
|
5298
5373
|
const exportWrapper = `
|
|
5299
|
-
export function ${
|
|
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${
|
|
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
|
-
${
|
|
5387
|
+
${p22.rhfImport}
|
|
5313
5388
|
import { z } from 'zod/v3'
|
|
5314
|
-
import { create${
|
|
5389
|
+
import { create${p22.pascal}Submission } from '@admin/actions/${p22.actionImportPath}'
|
|
5315
5390
|
|
|
5316
5391
|
const formSchema = z.object({
|
|
5317
|
-
${
|
|
5392
|
+
${p22.zodFields}
|
|
5318
5393
|
})
|
|
5319
5394
|
|
|
5320
5395
|
type FormValues = z.infer<typeof formSchema>
|
|
5321
5396
|
${buildFieldErrorHelper()}
|
|
5322
5397
|
|
|
5323
|
-
${
|
|
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 ${
|
|
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
|
-
${
|
|
5424
|
+
${p22.defaults}
|
|
5350
5425
|
},
|
|
5351
5426
|
})
|
|
5352
5427
|
|
|
5353
|
-
${
|
|
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${
|
|
5444
|
+
const result = await create${p22.pascal}Submission(values)
|
|
5370
5445
|
if (result.success) {
|
|
5371
|
-
${
|
|
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">${
|
|
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
|
-
${
|
|
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...' : ${
|
|
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(
|
|
13541
|
-
return `\`${
|
|
13615
|
+
function pathExpression(path59) {
|
|
13616
|
+
return `\`${path59.map((part) => typeof part === "string" ? part : `\${${part.expression}}`).join(".")}\``;
|
|
13542
13617
|
}
|
|
13543
|
-
function pathNameProp(
|
|
13544
|
-
if (
|
|
13545
|
-
return `"${
|
|
13618
|
+
function pathNameProp(path59) {
|
|
13619
|
+
if (path59.every((part) => typeof part === "string")) {
|
|
13620
|
+
return `"${path59.map((part) => part).join(".")}"`;
|
|
13546
13621
|
}
|
|
13547
|
-
return `{${pathExpression(
|
|
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
|
|
13644
|
+
const path59 = [...prefix, field.name];
|
|
13570
13645
|
if ((field.type === "group" || field.type === "section") && field.fields?.length) {
|
|
13571
|
-
return requiredLeafPaths(field.fields,
|
|
13646
|
+
return requiredLeafPaths(field.fields, path59);
|
|
13572
13647
|
}
|
|
13573
|
-
if (field.required) return [
|
|
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((
|
|
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,
|
|
13657
|
+
function renderTextInputField(field, indent, label, nestedHint, path59) {
|
|
13583
13658
|
return `${indent}<FormField
|
|
13584
13659
|
${indent} control={form.control}
|
|
13585
|
-
${indent} name=${pathNameProp(
|
|
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,
|
|
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} `, [...
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
13776
|
+
return renderTextInputField(field, indent, nestedLabel, nestedHint, path59);
|
|
13702
13777
|
}
|
|
13703
|
-
function renderNestedObjectListField(field, indent, label,
|
|
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(\`${
|
|
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
|
-
[...
|
|
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(
|
|
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(
|
|
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:
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
19981
|
+
const result = await p6.confirm({
|
|
19870
19982
|
message,
|
|
19871
19983
|
initialValue: true
|
|
19872
19984
|
});
|
|
19873
|
-
if (
|
|
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
|
|
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
|
-
|
|
20403
|
+
p7.log.error(message);
|
|
20292
20404
|
process.exit(1);
|
|
20293
20405
|
}
|
|
20294
20406
|
function cancel2(context) {
|
|
20295
|
-
|
|
20407
|
+
p7.cancel(context.cancelMessage);
|
|
20296
20408
|
process.exit(0);
|
|
20297
20409
|
}
|
|
20298
20410
|
function cancelIfNeeded(context, value) {
|
|
20299
|
-
if (
|
|
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
|
|
20306
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21105
|
+
p8.log.error(message);
|
|
20994
21106
|
process.exit(1);
|
|
20995
21107
|
}
|
|
20996
21108
|
function cancel4(message = "Add field cancelled.") {
|
|
20997
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21266
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21741
|
-
|
|
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
|
|
21751
|
-
import
|
|
21752
|
-
import * as
|
|
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
|
|
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
|
|
21766
|
-
message:
|
|
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
|
|
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
|
|
21887
|
+
hint: "Manually enter connection details"
|
|
21781
21888
|
}
|
|
21782
21889
|
],
|
|
21783
21890
|
initialValue: "vercel"
|
|
21784
21891
|
});
|
|
21785
|
-
if (
|
|
21786
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
21817
|
-
|
|
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
|
|
21838
|
-
import * as
|
|
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
|
|
21849
|
-
message:
|
|
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: "
|
|
21854
|
-
label: "
|
|
21855
|
-
|
|
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: "
|
|
21969
|
+
hint: "\u26A0 For testing only"
|
|
21877
21970
|
}
|
|
21878
21971
|
],
|
|
21879
|
-
initialValue: "
|
|
21972
|
+
initialValue: "vercel-blob"
|
|
21880
21973
|
});
|
|
21881
|
-
if (
|
|
21882
|
-
|
|
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
|
-
|
|
21890
|
-
|
|
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
|
-
|
|
21899
|
-
|
|
21900
|
-
|
|
21901
|
-
"
|
|
21902
|
-
|
|
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
|
|
22053
|
+
import * as p12 from "@clack/prompts";
|
|
21943
22054
|
import pc2 from "picocolors";
|
|
21944
22055
|
async function promptProject(defaultName) {
|
|
21945
|
-
const projectName = await
|
|
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 (
|
|
21965
|
-
|
|
22075
|
+
if (p12.isCancel(projectName)) {
|
|
22076
|
+
p12.cancel("Setup cancelled.");
|
|
21966
22077
|
process.exit(0);
|
|
21967
22078
|
}
|
|
21968
|
-
const useSrcDir = await
|
|
22079
|
+
const useSrcDir = await p12.confirm({
|
|
21969
22080
|
message: "Use src/ directory?",
|
|
21970
22081
|
initialValue: false
|
|
21971
22082
|
});
|
|
21972
|
-
if (
|
|
21973
|
-
|
|
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
|
|
23853
|
-
import
|
|
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
|
|
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
|
-
|
|
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
|
|
23982
|
-
|
|
23983
|
-
runner.source === "npx" ? "Checking Vercel
|
|
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
|
-
|
|
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
|
-
|
|
24181
|
+
checkSpinner.stop("Could not verify VERCEL_TOKEN with Vercel");
|
|
23992
24182
|
return { authed: false, reason: "login-failed" };
|
|
23993
24183
|
}
|
|
23994
|
-
|
|
23995
|
-
|
|
23996
|
-
`Sign in to Vercel to continue
|
|
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: "
|
|
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 =
|
|
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/
|
|
24024
|
-
import * as
|
|
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",
|
|
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/
|
|
24064
|
-
|
|
24065
|
-
|
|
24066
|
-
|
|
24067
|
-
const
|
|
24068
|
-
|
|
24069
|
-
|
|
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: "
|
|
24072
|
-
|
|
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 =
|
|
24079
|
-
pullSpinner.start("Retrieving the
|
|
24080
|
-
const
|
|
24081
|
-
if (!
|
|
24082
|
-
pullSpinner.stop("No
|
|
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.
|
|
24086
|
-
return {
|
|
24383
|
+
pullSpinner.clear();
|
|
24384
|
+
return { token, storeName: quiet.storeName };
|
|
24087
24385
|
}
|
|
24088
|
-
async function
|
|
24089
|
-
const
|
|
24090
|
-
|
|
24091
|
-
const
|
|
24092
|
-
|
|
24093
|
-
|
|
24094
|
-
|
|
24095
|
-
|
|
24096
|
-
|
|
24097
|
-
|
|
24098
|
-
|
|
24099
|
-
|
|
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
|
-
|
|
24109
|
-
const
|
|
24110
|
-
|
|
24111
|
-
|
|
24112
|
-
|
|
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
|
-
|
|
24117
|
-
return {
|
|
24405
|
+
provisionSpinner.clear();
|
|
24406
|
+
return { token, storeName };
|
|
24118
24407
|
}
|
|
24119
|
-
function
|
|
24120
|
-
return pullVercelEnvValue(runner, cwd,
|
|
24408
|
+
function pullBlobToken(runner, cwd, env) {
|
|
24409
|
+
return pullVercelEnvValue(runner, cwd, readBlobTokenFromDotenv, env);
|
|
24121
24410
|
}
|
|
24122
|
-
function
|
|
24411
|
+
function readBlobTokenFromDotenv(filePath) {
|
|
24123
24412
|
const vars = parseDotenvFile(filePath);
|
|
24124
|
-
const
|
|
24125
|
-
if (
|
|
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 (/
|
|
24416
|
+
if (/_READ_WRITE_TOKEN$/.test(key) && isBlobToken(value)) return value;
|
|
24129
24417
|
}
|
|
24130
24418
|
for (const value of vars.values()) {
|
|
24131
|
-
if (
|
|
24419
|
+
if (isBlobToken(value)) return value;
|
|
24132
24420
|
}
|
|
24133
24421
|
return void 0;
|
|
24134
24422
|
}
|
|
24135
24423
|
|
|
24136
|
-
// adapters/next/init/vercel/
|
|
24137
|
-
import
|
|
24138
|
-
import
|
|
24139
|
-
|
|
24140
|
-
|
|
24141
|
-
|
|
24142
|
-
|
|
24143
|
-
var
|
|
24144
|
-
|
|
24145
|
-
|
|
24146
|
-
|
|
24147
|
-
|
|
24148
|
-
|
|
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
|
|
24151
|
-
const
|
|
24152
|
-
|
|
24153
|
-
|
|
24154
|
-
|
|
24155
|
-
|
|
24156
|
-
|
|
24157
|
-
|
|
24158
|
-
|
|
24159
|
-
|
|
24160
|
-
|
|
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
|
-
|
|
24464
|
+
timeoutMs: ENV_ADD_TIMEOUT_MS,
|
|
24465
|
+
env,
|
|
24466
|
+
stdinInput: `${value}
|
|
24467
|
+
`
|
|
24167
24468
|
});
|
|
24168
|
-
if (add.success)
|
|
24169
|
-
|
|
24170
|
-
|
|
24171
|
-
|
|
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
|
-
|
|
24501
|
+
parsed = JSON.parse(original);
|
|
24175
24502
|
} catch {
|
|
24503
|
+
return [];
|
|
24176
24504
|
}
|
|
24177
|
-
const
|
|
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
|
-
|
|
24184
|
-
|
|
24185
|
-
|
|
24579
|
+
failure: deploy.timedOut ? "timeout" : "deploy-failed",
|
|
24580
|
+
detail: outputTail(`${deploy.stdout}
|
|
24581
|
+
${deploy.stderr}`)
|
|
24186
24582
|
};
|
|
24187
24583
|
}
|
|
24188
|
-
function
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
24200
|
-
import * as
|
|
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
|
|
24203
|
-
const
|
|
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 =
|
|
24213
|
-
pullSpinner.start("Retrieving the
|
|
24214
|
-
const
|
|
24215
|
-
if (!
|
|
24216
|
-
pullSpinner.stop("No
|
|
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.
|
|
24220
|
-
return {
|
|
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
|
|
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
|
|
24225
|
-
if (
|
|
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 (/
|
|
24708
|
+
if (/DATABASE_URL$/.test(key) && isPg(value)) return value;
|
|
24228
24709
|
}
|
|
24229
24710
|
for (const value of vars.values()) {
|
|
24230
|
-
if (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 =
|
|
24721
|
+
const runnerSpinner = spinner2();
|
|
24240
24722
|
runnerSpinner.start("Checking for the Vercel CLI");
|
|
24241
24723
|
const runner = await resolveVercelRunner();
|
|
24242
|
-
runnerSpinner.
|
|
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
|
-
|
|
24727
|
+
p16.log.warn(authFailureMessage(auth.reason));
|
|
24248
24728
|
return { ok: false };
|
|
24249
24729
|
}
|
|
24250
|
-
const projectSpinner =
|
|
24251
|
-
projectSpinner.start(`Creating Vercel project ${
|
|
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 ${
|
|
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
|
-
|
|
24259
|
-
if (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
|
-
|
|
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
|
|
24291
|
-
const
|
|
24292
|
-
|
|
24293
|
-
|
|
24294
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
24898
|
+
function blobFailureMessage(reason) {
|
|
24315
24899
|
switch (reason) {
|
|
24316
24900
|
case "env-pull-empty":
|
|
24317
|
-
return "
|
|
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 "
|
|
24903
|
+
return "Vercel Blob provisioning timed out \u2014 falling back to manual entry.";
|
|
24320
24904
|
default:
|
|
24321
|
-
return "
|
|
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
|
|
24932
|
+
import pc7 from "picocolors";
|
|
24341
24933
|
|
|
24342
24934
|
// adapters/next/commands/seed.ts
|
|
24343
|
-
import
|
|
24344
|
-
import
|
|
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 ?
|
|
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 =
|
|
24553
|
-
const seedPath =
|
|
24554
|
-
if (!
|
|
24555
|
-
|
|
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
|
-
|
|
25150
|
+
fs38.writeFileSync(seedPath, buildSeedScript(`${namespace.apiPath}/auth`), "utf-8");
|
|
24559
25151
|
const { execFile } = await import("child_process");
|
|
24560
|
-
const tsxBin =
|
|
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
|
|
24587
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25191
|
+
fs38.unlinkSync(seedPath);
|
|
24600
25192
|
} catch {
|
|
24601
25193
|
}
|
|
24602
25194
|
return;
|
|
24603
25195
|
}
|
|
24604
|
-
|
|
25196
|
+
seedSpinner.start("Replacing admin user...");
|
|
24605
25197
|
await runSeed2(true);
|
|
24606
|
-
|
|
25198
|
+
seedSpinner.stop("Admin user replaced");
|
|
24607
25199
|
} else {
|
|
24608
|
-
|
|
25200
|
+
seedSpinner.stop("Admin user created");
|
|
24609
25201
|
}
|
|
24610
25202
|
} catch (err) {
|
|
24611
|
-
|
|
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 ${
|
|
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
|
-
|
|
24623
|
-
if (
|
|
24624
|
-
|
|
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 =
|
|
24654
|
-
if (!
|
|
25257
|
+
const configPath = path50.resolve(cwd, "admin.config.ts");
|
|
25258
|
+
if (!fs39.existsSync(configPath)) {
|
|
24655
25259
|
return;
|
|
24656
25260
|
}
|
|
24657
|
-
const sourceNamespace = readNamespaceFromConfigSource(
|
|
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 =
|
|
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 =
|
|
24696
|
-
if (
|
|
24697
|
-
|
|
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 =
|
|
24703
|
-
if (
|
|
24704
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
24727
|
-
message: "
|
|
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 (
|
|
24740
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) => `${
|
|
25378
|
+
const conflictLines = project2.conflicts.map((c) => `${pc7.yellow("\u25B2")} ${c}`);
|
|
24764
25379
|
conflictLines.push(
|
|
24765
25380
|
"",
|
|
24766
|
-
|
|
25381
|
+
pc7.dim(`Use ${pc7.bold("--force")} to remove existing admin files before scaffolding.`)
|
|
24767
25382
|
);
|
|
24768
|
-
|
|
25383
|
+
p17.note(conflictLines.join("\n"), pc7.yellow("Conflicts"));
|
|
24769
25384
|
if (!options.yes) {
|
|
24770
|
-
const proceed = await
|
|
25385
|
+
const proceed = await p17.confirm({
|
|
24771
25386
|
message: [
|
|
24772
|
-
`Continue with ${
|
|
24773
|
-
`${
|
|
24774
|
-
|
|
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 (
|
|
24779
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
24814
|
-
|
|
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 === "." ?
|
|
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 =
|
|
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
|
-
|
|
24849
|
-
|
|
25463
|
+
p17.log.error(createNextAppResult.error);
|
|
25464
|
+
p17.log.info(
|
|
24850
25465
|
`You can create the project manually:
|
|
24851
|
-
${
|
|
24852
|
-
Then run ${
|
|
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 =
|
|
24857
|
-
const hasPackageJson =
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
25482
|
+
p17.log.info(
|
|
24868
25483
|
`Create the project manually:
|
|
24869
|
-
${
|
|
24870
|
-
Then run ${
|
|
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 ? `${
|
|
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
|
-
|
|
24887
|
-
`Invalid database URL. Must start with ${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25540
|
+
persistDatabaseUrl(cwd, databaseUrl);
|
|
25541
|
+
p17.log.success(`Saved DATABASE_URL to ${pc7.cyan(".env.local")}`);
|
|
24926
25542
|
} else {
|
|
24927
|
-
|
|
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 (
|
|
24959
|
-
|
|
24960
|
-
|
|
24961
|
-
|
|
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
|
-
|
|
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, {
|
|
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 =
|
|
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 `${
|
|
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
|
-
|
|
25063
|
-
const drizzleConfigPath =
|
|
25064
|
-
if (!dbFiles.includes("drizzle.config.ts") &&
|
|
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
|
-
|
|
25703
|
+
fs39.writeFileSync(
|
|
25068
25704
|
drizzleConfigPath,
|
|
25069
25705
|
readNamespacedTemplate("drizzle.config.ts", namespace),
|
|
25070
25706
|
"utf-8"
|
|
25071
25707
|
);
|
|
25072
|
-
|
|
25708
|
+
p17.log.success("Updated drizzle.config.ts");
|
|
25073
25709
|
} else if (!options.yes) {
|
|
25074
|
-
const overwrite = await
|
|
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 (!
|
|
25714
|
+
if (!p17.isCancel(overwrite) && overwrite) {
|
|
25079
25715
|
const { readNamespacedTemplate } = await import("./template-reader-X6KFP7B3.js");
|
|
25080
|
-
|
|
25716
|
+
fs39.writeFileSync(
|
|
25081
25717
|
drizzleConfigPath,
|
|
25082
25718
|
readNamespacedTemplate("drizzle.config.ts", namespace),
|
|
25083
25719
|
"utf-8"
|
|
25084
25720
|
);
|
|
25085
|
-
|
|
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
|
-
|
|
25112
|
-
|
|
25747
|
+
p17.log.warning(depsResult.error ?? "Unknown error");
|
|
25748
|
+
p17.log.info(
|
|
25113
25749
|
`You can install them manually:
|
|
25114
|
-
${
|
|
25115
|
-
${
|
|
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
|
|
25801
|
+
const usesLocalStorage = resolvedIntegrationInstallResult.config.storage.provider === "local";
|
|
25166
25802
|
const installLines = [];
|
|
25167
25803
|
if (depsInstalled) {
|
|
25168
25804
|
installLines.push(
|
|
25169
|
-
`${
|
|
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
|
-
`${
|
|
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(` ${
|
|
25813
|
+
installLines.push(` ${pc7.dim(err)}`);
|
|
25178
25814
|
}
|
|
25179
25815
|
} else {
|
|
25180
25816
|
installLines.push(
|
|
25181
|
-
`${
|
|
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
|
-
`${
|
|
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(` ${
|
|
25824
|
+
installLines.push(` ${pc7.yellow("\u25B2")} ${warning}`);
|
|
25189
25825
|
}
|
|
25190
25826
|
installLines.push(
|
|
25191
|
-
`${
|
|
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(` ${
|
|
25830
|
+
installLines.push(` ${pc7.yellow("\u25B2")} ${warning}`);
|
|
25195
25831
|
}
|
|
25196
25832
|
if (usesLocalStorage) {
|
|
25197
25833
|
installLines.push(
|
|
25198
|
-
` ${
|
|
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
|
-
|
|
25837
|
+
p17.note(installLines.join("\n"), "Installed");
|
|
25202
25838
|
let dbPushed = false;
|
|
25203
25839
|
if (depsResult.success && options.skipMigration && hasDbUrl(cwd)) {
|
|
25204
|
-
|
|
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
|
-
|
|
25220
|
-
|
|
25221
|
-
`Install ${DRIZZLE_KIT_POSTGRES_DRIVER_DEP} manually, then run: ${
|
|
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(`${
|
|
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
|
-
|
|
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
|
-
|
|
25241
|
-
|
|
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
|
-
|
|
25882
|
+
p17.log.success("Database schema pushed");
|
|
25247
25883
|
} else {
|
|
25248
|
-
s.stop(`${
|
|
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
|
-
|
|
25892
|
+
p17.log.warning(pushError);
|
|
25257
25893
|
if (isDatabaseReachabilityError(pushError)) {
|
|
25258
|
-
|
|
25894
|
+
p17.log.error("Database was not reachable. Aborting setup.");
|
|
25259
25895
|
process.exit(1);
|
|
25260
25896
|
}
|
|
25261
|
-
|
|
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
|
-
|
|
25271
|
-
`Skipping admin user creation ${
|
|
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
|
-
|
|
25916
|
+
p17.log.warning(`Could not verify existing admin account ${pc7.dim(`(${adminCheck.error})`)}`);
|
|
25281
25917
|
if (isDatabaseReachabilityError(adminCheck.error)) {
|
|
25282
|
-
|
|
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
|
|
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 (
|
|
25302
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25314
|
-
|
|
25315
|
-
replaceExistingAdmin ? "Replace the existing admin account to access the admin." : "Create your first admin user to access the
|
|
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
|
|
25955
|
+
const credentials = await p17.group(
|
|
25320
25956
|
{
|
|
25321
|
-
email: () =>
|
|
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: () =>
|
|
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
|
-
|
|
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(`${
|
|
25356
|
-
const replace = await
|
|
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 (!
|
|
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" ? `${
|
|
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(`${
|
|
25381
|
-
|
|
25382
|
-
`${
|
|
26016
|
+
s.stop(`${pc7.red("\u2717")} Failed to create admin user`);
|
|
26017
|
+
p17.note(
|
|
26018
|
+
`${pc7.red(seedResult.error)}
|
|
25383
26019
|
|
|
25384
|
-
Run manually: ${
|
|
25385
|
-
|
|
26020
|
+
Run manually: ${pc7.cyan(betterstartExecCommand(pm, "seed"))}`,
|
|
26021
|
+
pc7.red("Seed failed")
|
|
25386
26022
|
);
|
|
25387
26023
|
if (isDatabaseReachabilityError(seedResult.error)) {
|
|
25388
|
-
|
|
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: ${
|
|
25415
|
-
`Integrations: ${
|
|
25416
|
-
`Storage: ${
|
|
25417
|
-
`Files created: ${
|
|
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
|
-
|
|
25421
|
-
|
|
25422
|
-
|
|
25423
|
-
|
|
25424
|
-
|
|
25425
|
-
|
|
25426
|
-
|
|
25427
|
-
|
|
25428
|
-
|
|
25429
|
-
|
|
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
|
|
26076
|
+
const startDev = await p17.confirm({
|
|
25446
26077
|
message: "Start the development server?",
|
|
25447
26078
|
initialValue: true
|
|
25448
26079
|
});
|
|
25449
|
-
if (!
|
|
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
|
-
|
|
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 =
|
|
25465
|
-
if (!
|
|
25466
|
-
const content =
|
|
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 =
|
|
25490
|
-
if (!
|
|
25491
|
-
const content =
|
|
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 =
|
|
25506
|
-
if (
|
|
26135
|
+
const installedNextPackagePath = path50.join(cwd, "node_modules", "next", "package.json");
|
|
26136
|
+
if (fs39.existsSync(installedNextPackagePath)) {
|
|
25507
26137
|
try {
|
|
25508
|
-
const installedPkg = JSON.parse(
|
|
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 =
|
|
25516
|
-
if (!
|
|
26145
|
+
const projectPackagePath = path50.join(cwd, "package.json");
|
|
26146
|
+
if (!fs39.existsSync(projectPackagePath)) return void 0;
|
|
25517
26147
|
try {
|
|
25518
|
-
const projectPkg = JSON.parse(
|
|
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 =
|
|
25651
|
-
const seedPath =
|
|
25652
|
-
if (!
|
|
25653
|
-
|
|
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
|
-
|
|
26285
|
+
fs39.writeFileSync(seedPath, buildSeedScript(authBasePath), "utf-8");
|
|
25656
26286
|
const cleanup = () => {
|
|
25657
26287
|
try {
|
|
25658
|
-
|
|
25659
|
-
if (
|
|
25660
|
-
|
|
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 =
|
|
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: ${
|
|
26470
|
+
const lines = [`Admin: ${pc7.cyan(state.adminLoginUrl)}`];
|
|
25841
26471
|
if (state.adminEmail && state.adminPassword) {
|
|
25842
|
-
lines.unshift(`Password: ${
|
|
25843
|
-
lines.unshift(`Admin user: ${
|
|
26472
|
+
lines.unshift(`Password: ${pc7.cyan(state.adminPassword)}`);
|
|
26473
|
+
lines.unshift(`Admin user: ${pc7.cyan(state.adminEmail)}`);
|
|
25844
26474
|
}
|
|
25845
|
-
|
|
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
|
|
25957
|
-
import * as
|
|
26586
|
+
import path51 from "path";
|
|
26587
|
+
import * as p18 from "@clack/prompts";
|
|
25958
26588
|
async function runListIntegrationsCommand(options) {
|
|
25959
|
-
const cwd = options.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(
|
|
26594
|
+
return `${integration.id.padEnd(12)} ${status.padEnd(10)} ${integration.kind.padEnd(8)} ${integration.description}`;
|
|
25965
26595
|
});
|
|
25966
|
-
|
|
25967
|
-
|
|
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
|
|
25974
|
-
import * as
|
|
26603
|
+
import path52 from "path";
|
|
26604
|
+
import * as p19 from "@clack/prompts";
|
|
25975
26605
|
async function runListPluginsCommand(options) {
|
|
25976
|
-
const cwd = options.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
|
-
|
|
25984
|
-
|
|
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
|
|
25989
|
-
import * as
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ?
|
|
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
|
|
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 (
|
|
26024
|
-
|
|
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
|
-
|
|
26667
|
+
p20.outro("No integrations were removed.");
|
|
26038
26668
|
return;
|
|
26039
26669
|
}
|
|
26040
26670
|
if (result2.warnings.length > 0) {
|
|
26041
|
-
|
|
26671
|
+
p20.note(result2.warnings.join("\n"), "Warnings");
|
|
26042
26672
|
}
|
|
26043
|
-
|
|
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
|
-
|
|
26686
|
+
p20.outro("No plugins were removed.");
|
|
26057
26687
|
return;
|
|
26058
26688
|
}
|
|
26059
26689
|
if (result.warnings.length > 0) {
|
|
26060
|
-
|
|
26690
|
+
p20.note(result.warnings.join("\n"), "Warnings");
|
|
26061
26691
|
}
|
|
26062
|
-
|
|
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
|
|
26067
|
-
import
|
|
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 =
|
|
26083
|
-
const existed =
|
|
26084
|
-
|
|
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
|
-
|
|
26090
|
-
|
|
26091
|
-
|
|
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 =
|
|
26724
|
+
let current = path54.dirname(path54.join(cwd, ...deletedPath.split("/")));
|
|
26095
26725
|
while (!stopRoots.has(current)) {
|
|
26096
|
-
if (!
|
|
26097
|
-
current =
|
|
26726
|
+
if (!fs40.existsSync(current)) {
|
|
26727
|
+
current = path54.dirname(current);
|
|
26098
26728
|
continue;
|
|
26099
26729
|
}
|
|
26100
|
-
const entries =
|
|
26730
|
+
const entries = fs40.readdirSync(current);
|
|
26101
26731
|
if (entries.length > 0) {
|
|
26102
26732
|
break;
|
|
26103
26733
|
}
|
|
26104
|
-
|
|
26105
|
-
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 ?
|
|
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 ?
|
|
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(
|
|
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
|
|
26210
|
-
import
|
|
26211
|
-
import * as
|
|
26212
|
-
import
|
|
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
|
|
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 (!
|
|
26250
|
-
const raw =
|
|
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
|
-
|
|
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 (!
|
|
26280
|
-
const content =
|
|
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
|
-
|
|
26924
|
+
fs41.writeFileSync(cssPath, cleaned, "utf-8");
|
|
26295
26925
|
return removed;
|
|
26296
26926
|
}
|
|
26297
26927
|
function cleanEnvFile(envPath) {
|
|
26298
|
-
if (!
|
|
26299
|
-
const content =
|
|
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
|
-
|
|
26962
|
+
fs41.unlinkSync(envPath);
|
|
26333
26963
|
} else {
|
|
26334
|
-
|
|
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 =
|
|
26361
|
-
if (
|
|
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 (!
|
|
26996
|
+
if (!fs42.existsSync(biomePath)) return false;
|
|
26367
26997
|
try {
|
|
26368
|
-
const content = JSON.parse(
|
|
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 =
|
|
27007
|
+
const hasSrc = fs42.existsSync(path55.join(cwd, "src"));
|
|
26378
27008
|
const appBase = hasSrc ? "src/app" : "app";
|
|
26379
27009
|
const dirs = [];
|
|
26380
|
-
const adminDir =
|
|
26381
|
-
const legacyAdminDir =
|
|
26382
|
-
const adminRouteGroup =
|
|
26383
|
-
const legacyAdminRouteGroup =
|
|
26384
|
-
if (
|
|
26385
|
-
if (namespace.segment !== "admin" &&
|
|
26386
|
-
if (
|
|
26387
|
-
if (namespace.segment !== "admin" &&
|
|
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 (
|
|
26397
|
-
if (
|
|
26398
|
-
|
|
26399
|
-
if (
|
|
26400
|
-
|
|
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 (
|
|
26403
|
-
|
|
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",
|
|
26412
|
-
["drizzle.config.ts",
|
|
26413
|
-
["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 (
|
|
27046
|
+
if (fs42.existsSync(fullPath)) {
|
|
26417
27047
|
configFiles.push(label);
|
|
26418
27048
|
configPaths.push(fullPath);
|
|
26419
27049
|
}
|
|
26420
27050
|
}
|
|
26421
|
-
const biomePath =
|
|
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
|
|
26434
|
-
if (
|
|
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 =
|
|
26440
|
-
if (
|
|
26441
|
-
const content =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
26479
|
-
if (
|
|
26480
|
-
const envContent =
|
|
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 ?
|
|
26498
|
-
|
|
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
|
-
|
|
26508
|
-
|
|
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 =
|
|
26514
|
-
return `${
|
|
27143
|
+
const countLabel = pc8.dim(`${step.count} ${step.unit}`);
|
|
27144
|
+
return `${pc8.red("\xD7")} ${names} ${countLabel}`;
|
|
26515
27145
|
});
|
|
26516
|
-
|
|
27146
|
+
p21.note(planLines.join("\n"), "Uninstall plan");
|
|
26517
27147
|
if (!options.force) {
|
|
26518
|
-
const confirmed = await
|
|
27148
|
+
const confirmed = await p21.confirm({
|
|
26519
27149
|
message: "Proceed with uninstall?",
|
|
26520
27150
|
initialValue: false
|
|
26521
27151
|
});
|
|
26522
|
-
if (
|
|
26523
|
-
|
|
27152
|
+
if (p21.isCancel(confirmed) || !confirmed) {
|
|
27153
|
+
p21.cancel("Uninstall cancelled.");
|
|
26524
27154
|
process.exit(0);
|
|
26525
27155
|
}
|
|
26526
27156
|
}
|
|
26527
|
-
const s =
|
|
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
|
-
|
|
26536
|
-
|
|
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
|
|
26542
|
-
import
|
|
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
|
-
|
|
27422
|
+
fs43.writeFileSync(
|
|
26793
27423
|
destPath,
|
|
26794
|
-
applyAdminNamespaceToContent(
|
|
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 =
|
|
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 =
|
|
26803
|
-
const destPath =
|
|
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(
|
|
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 =
|
|
26821
|
-
if (!
|
|
27450
|
+
const pkgPath = path56.join(cwd, "package.json");
|
|
27451
|
+
if (!fs43.existsSync(pkgPath)) return null;
|
|
26822
27452
|
try {
|
|
26823
|
-
return JSON.parse(
|
|
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 (!
|
|
28749
|
+
if (!fs43.existsSync(assetDir)) return [];
|
|
28115
28750
|
const components = [];
|
|
28116
|
-
for (const entry of
|
|
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) =>
|
|
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:
|
|
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 (!
|
|
28775
|
+
if (!fs43.existsSync(assetDir)) return void 0;
|
|
28141
28776
|
const isNestedComponentName = /[\\/]/.test(componentName);
|
|
28142
|
-
const nestedComponentName = componentName.split(/[\\/]/).filter(Boolean).join(
|
|
28143
|
-
if (isNestedComponentName && nestedComponentName && !
|
|
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 =
|
|
28147
|
-
if (
|
|
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("..") && !
|
|
28787
|
+
if (!isNestedComponentName && !componentName.includes("..") && !path56.isAbsolute(componentName)) {
|
|
28153
28788
|
for (const extension of [".tsx", ".ts"]) {
|
|
28154
|
-
const relPath =
|
|
28155
|
-
const filePath =
|
|
28156
|
-
if (
|
|
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
|
|
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 ?
|
|
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 =
|
|
28225
|
-
if (!
|
|
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 =
|
|
28279
|
-
if (entry.preserveExisting &&
|
|
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(
|
|
28286
|
-
|
|
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 =
|
|
28316
|
-
fsExtra.ensureDirSync(
|
|
28317
|
-
writeNamespacedFile(
|
|
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 =
|
|
28321
|
-
if (
|
|
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 =
|
|
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 =
|
|
28352
|
-
if (!
|
|
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 =
|
|
28358
|
-
if (!
|
|
28992
|
+
const srcDir = path56.join(srcBaseDir, directory);
|
|
28993
|
+
if (!fs43.existsSync(srcDir)) {
|
|
28359
28994
|
continue;
|
|
28360
28995
|
}
|
|
28361
|
-
const destDir =
|
|
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(
|
|
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 (
|
|
28434
|
-
|
|
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 =
|
|
28479
|
-
const componentsJsonPath =
|
|
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
|
-
|
|
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 (!
|
|
29126
|
+
if (!fs43.existsSync(adminGlobalsPath)) {
|
|
28492
29127
|
clack2.cancel(
|
|
28493
|
-
`Admin globals file not found at ${
|
|
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 ${
|
|
29138
|
+
clack2.log.info(`Applying preset to ${path56.join(config.paths.admin, "components/ui")}`);
|
|
28504
29139
|
let failed = false;
|
|
28505
29140
|
try {
|
|
28506
|
-
|
|
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 =
|
|
28563
|
-
if (!
|
|
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) =>
|
|
29219
|
+
return hostRelativePaths.map((relativePath) => path56.join(cwd, relativePath));
|
|
28585
29220
|
}
|
|
28586
29221
|
function snapshotFile(filePath) {
|
|
28587
|
-
if (!
|
|
29222
|
+
if (!fs43.existsSync(filePath)) {
|
|
28588
29223
|
return { existed: false };
|
|
28589
29224
|
}
|
|
28590
|
-
return { existed: true, content:
|
|
29225
|
+
return { existed: true, content: fs43.readFileSync(filePath, "utf-8") };
|
|
28591
29226
|
}
|
|
28592
29227
|
function restoreFile(filePath, snapshot) {
|
|
28593
29228
|
if (snapshot.existed) {
|
|
28594
|
-
|
|
29229
|
+
fs43.writeFileSync(filePath, snapshot.content ?? "", "utf-8");
|
|
28595
29230
|
return;
|
|
28596
29231
|
}
|
|
28597
|
-
if (
|
|
28598
|
-
|
|
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
|
|
29238
|
+
import path57 from "path";
|
|
28604
29239
|
import * as clack3 from "@clack/prompts";
|
|
28605
29240
|
async function runUpdateDepsCommand(options) {
|
|
28606
|
-
const cwd = options.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 =
|
|
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
|
|
28641
|
-
import
|
|
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 ?
|
|
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 =
|
|
28650
|
-
if (!
|
|
28651
|
-
clack4.cancel(`${namespace.globalsFile} not found at ${
|
|
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
|
-
|
|
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 ${
|
|
29294
|
+
clack4.log.success(`Updated ${path58.relative(cwd, targetPath)}`);
|
|
28660
29295
|
clack4.outro("Styles updated");
|
|
28661
29296
|
}
|
|
28662
29297
|
|