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