betterstart-cli 0.0.26 → 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 +370 -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 ${p20.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 @@ ${p20.fieldArraySetup}${p20.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 @@ ${p20.fieldArraySetup}${p20.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 @@ ${p20.fieldArraySetup}${p20.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 @@ ${p20.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,20 +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;
|
|
23959
|
-
const
|
|
23960
|
-
|
|
23981
|
+
const spinner10 = p12.spinner();
|
|
23982
|
+
spinner10.start(
|
|
23961
23983
|
runner.source === "npx" ? "Checking Vercel sign-in (first run downloads the Vercel CLI)" : "Checking Vercel sign-in"
|
|
23962
23984
|
);
|
|
23963
23985
|
const existing = await checkVercelAuth(runner, cwd, env);
|
|
23964
23986
|
if (existing.authed) {
|
|
23965
|
-
|
|
23987
|
+
spinner10.stop(signedInMessage(existing.username));
|
|
23966
23988
|
return existing;
|
|
23967
23989
|
}
|
|
23968
23990
|
if (env.VERCEL_TOKEN) {
|
|
23969
|
-
|
|
23991
|
+
spinner10.stop("Could not verify VERCEL_TOKEN with Vercel");
|
|
23970
23992
|
return { authed: false, reason: "login-failed" };
|
|
23971
23993
|
}
|
|
23972
|
-
|
|
23994
|
+
spinner10.stop("Not signed in to Vercel yet");
|
|
23973
23995
|
p12.log.info(
|
|
23974
23996
|
`Sign in to Vercel to continue \u2014 a browser link and code will appear below. ${pc3.dim(
|
|
23975
23997
|
"(or press Ctrl-C to enter a connection string manually)"
|
|
@@ -23999,13 +24021,48 @@ function signedInMessage(username) {
|
|
|
23999
24021
|
}
|
|
24000
24022
|
|
|
24001
24023
|
// adapters/next/init/vercel/neon.ts
|
|
24024
|
+
import * as p13 from "@clack/prompts";
|
|
24025
|
+
|
|
24026
|
+
// adapters/next/init/vercel/env-pull.ts
|
|
24002
24027
|
import fs34 from "fs";
|
|
24003
24028
|
import os from "os";
|
|
24004
24029
|
import path45 from "path";
|
|
24005
|
-
|
|
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
|
|
24006
24064
|
var PROVISION_TIMEOUT_MS = 18e4;
|
|
24007
24065
|
var INTERACTIVE_PROVISION_TIMEOUT_MS = 6e5;
|
|
24008
|
-
var ENV_PULL_TIMEOUT_MS = 6e4;
|
|
24009
24066
|
async function provisionNeonInteractive(runner, cwd, options) {
|
|
24010
24067
|
const addArgs = ["integration", "add", "neon", "--no-env-pull"];
|
|
24011
24068
|
if (options.plan) addArgs.push("--plan", options.plan);
|
|
@@ -24031,8 +24088,8 @@ async function provisionNeonInteractive(runner, cwd, options) {
|
|
|
24031
24088
|
async function provisionNeon(runner, cwd, options) {
|
|
24032
24089
|
const addArgs = ["integration", "add", "neon", "--no-env-pull", "--format", "json"];
|
|
24033
24090
|
if (options.plan) addArgs.push("--plan", options.plan);
|
|
24034
|
-
const
|
|
24035
|
-
|
|
24091
|
+
const spinner10 = p13.spinner();
|
|
24092
|
+
spinner10.start("Provisioning Neon Postgres via Vercel");
|
|
24036
24093
|
const add = await runVercel(runner, addArgs, {
|
|
24037
24094
|
cwd,
|
|
24038
24095
|
mode: "capture",
|
|
@@ -24040,7 +24097,7 @@ async function provisionNeon(runner, cwd, options) {
|
|
|
24040
24097
|
env: options.env
|
|
24041
24098
|
});
|
|
24042
24099
|
if (!add.success) {
|
|
24043
|
-
|
|
24100
|
+
spinner10.stop("Neon provisioning did not complete");
|
|
24044
24101
|
const combined = `${add.stdout}
|
|
24045
24102
|
${add.stderr}`.trim();
|
|
24046
24103
|
const detail = combined || void 0;
|
|
@@ -24050,44 +24107,20 @@ ${add.stderr}`.trim();
|
|
|
24050
24107
|
}
|
|
24051
24108
|
const meta = parseVercelJson(add.stdout);
|
|
24052
24109
|
const resourceName = meta?.resourceName ?? meta?.name;
|
|
24053
|
-
|
|
24110
|
+
spinner10.message("Retrieving the database connection string");
|
|
24054
24111
|
const databaseUrl = await pullDatabaseUrl(runner, cwd, options.env);
|
|
24055
24112
|
if (!databaseUrl) {
|
|
24056
|
-
|
|
24113
|
+
spinner10.stop("No DATABASE_URL came back from Vercel");
|
|
24057
24114
|
return { resourceName, failure: "env-pull-empty" };
|
|
24058
24115
|
}
|
|
24059
|
-
|
|
24116
|
+
spinner10.stop("Captured the Neon DATABASE_URL");
|
|
24060
24117
|
return { databaseUrl, resourceName };
|
|
24061
24118
|
}
|
|
24062
|
-
|
|
24063
|
-
|
|
24064
|
-
const tmpEnv = path45.join(tmpDir, ".env.pull");
|
|
24065
|
-
try {
|
|
24066
|
-
const pull = await runVercel(
|
|
24067
|
-
runner,
|
|
24068
|
-
["env", "pull", tmpEnv, "--environment", "development", "--yes"],
|
|
24069
|
-
{ cwd, mode: "capture", timeoutMs: ENV_PULL_TIMEOUT_MS, env }
|
|
24070
|
-
);
|
|
24071
|
-
if (!pull.success) return void 0;
|
|
24072
|
-
return readDbUrlFromDotenv(tmpEnv);
|
|
24073
|
-
} finally {
|
|
24074
|
-
try {
|
|
24075
|
-
fs34.rmSync(tmpDir, { recursive: true, force: true });
|
|
24076
|
-
} catch {
|
|
24077
|
-
}
|
|
24078
|
-
}
|
|
24119
|
+
function pullDatabaseUrl(runner, cwd, env) {
|
|
24120
|
+
return pullVercelEnvValue(runner, cwd, readDbUrlFromDotenv, env);
|
|
24079
24121
|
}
|
|
24080
24122
|
function readDbUrlFromDotenv(filePath) {
|
|
24081
|
-
|
|
24082
|
-
const vars = /* @__PURE__ */ new Map();
|
|
24083
|
-
for (const line of fs34.readFileSync(filePath, "utf-8").split("\n")) {
|
|
24084
|
-
const trimmed = line.trim();
|
|
24085
|
-
if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
|
|
24086
|
-
const eq = trimmed.indexOf("=");
|
|
24087
|
-
const key = trimmed.slice(0, eq).trim();
|
|
24088
|
-
const value = trimmed.slice(eq + 1).replace(/^['"]|['"]$/g, "").trim();
|
|
24089
|
-
if (key) vars.set(key, value);
|
|
24090
|
-
}
|
|
24123
|
+
const vars = parseDotenvFile(filePath);
|
|
24091
24124
|
const isPg = (v) => !!v && (v.startsWith("postgres://") || v.startsWith("postgresql://"));
|
|
24092
24125
|
if (isPg(vars.get("DATABASE_URL"))) return vars.get("DATABASE_URL");
|
|
24093
24126
|
if (isPg(vars.get("POSTGRES_URL"))) return vars.get("POSTGRES_URL");
|
|
@@ -24163,11 +24196,47 @@ function readLinkedProjectId(cwd) {
|
|
|
24163
24196
|
}
|
|
24164
24197
|
}
|
|
24165
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
|
+
|
|
24166
24235
|
// adapters/next/init/vercel/flow.ts
|
|
24167
24236
|
async function runVercelNeonFlow(options) {
|
|
24168
24237
|
const env = options.env ?? process.env;
|
|
24169
24238
|
try {
|
|
24170
|
-
const runnerSpinner =
|
|
24239
|
+
const runnerSpinner = p15.spinner();
|
|
24171
24240
|
runnerSpinner.start("Checking for the Vercel CLI");
|
|
24172
24241
|
const runner = await resolveVercelRunner();
|
|
24173
24242
|
runnerSpinner.stop(
|
|
@@ -24175,10 +24244,10 @@ async function runVercelNeonFlow(options) {
|
|
|
24175
24244
|
);
|
|
24176
24245
|
const auth = await ensureVercelAuth(runner, options.cwd, { env });
|
|
24177
24246
|
if (!auth.authed) {
|
|
24178
|
-
|
|
24247
|
+
p15.log.warn(authFailureMessage(auth.reason));
|
|
24179
24248
|
return { ok: false };
|
|
24180
24249
|
}
|
|
24181
|
-
const projectSpinner =
|
|
24250
|
+
const projectSpinner = p15.spinner();
|
|
24182
24251
|
projectSpinner.start(`Creating Vercel project ${pc4.cyan(options.projectName)}`);
|
|
24183
24252
|
const project2 = await createVercelProject(runner, options.cwd, options.projectName, env);
|
|
24184
24253
|
projectSpinner.stop(
|
|
@@ -24186,19 +24255,33 @@ async function runVercelNeonFlow(options) {
|
|
|
24186
24255
|
);
|
|
24187
24256
|
const neon = await provisionNeonForMode(runner, options);
|
|
24188
24257
|
if (neon.failure || !neon.databaseUrl) {
|
|
24189
|
-
|
|
24190
|
-
if (neon.detail)
|
|
24258
|
+
p15.log.warn(neonFailureMessage(neon.failure));
|
|
24259
|
+
if (neon.detail) p15.log.message(pc4.dim(neon.detail));
|
|
24191
24260
|
return { ok: false };
|
|
24192
24261
|
}
|
|
24193
|
-
|
|
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
|
+
}
|
|
24194
24276
|
return {
|
|
24195
24277
|
ok: true,
|
|
24196
24278
|
databaseUrl: neon.databaseUrl,
|
|
24197
24279
|
vercelProjectId: project2.projectId,
|
|
24198
|
-
neonResourceName: neon.resourceName
|
|
24280
|
+
neonResourceName: neon.resourceName,
|
|
24281
|
+
resendApiKey
|
|
24199
24282
|
};
|
|
24200
24283
|
} catch (error) {
|
|
24201
|
-
|
|
24284
|
+
p15.log.warn(
|
|
24202
24285
|
`Vercel provisioning failed: ${error instanceof Error ? error.message : String(error)}`
|
|
24203
24286
|
);
|
|
24204
24287
|
return { ok: false };
|
|
@@ -24207,7 +24290,7 @@ async function runVercelNeonFlow(options) {
|
|
|
24207
24290
|
function provisionNeonForMode(runner, options) {
|
|
24208
24291
|
const opts = { plan: options.plan, env: options.env };
|
|
24209
24292
|
if (options.interactive) {
|
|
24210
|
-
|
|
24293
|
+
p15.log.info(
|
|
24211
24294
|
`Create your Neon database in the Vercel prompts below ${pc4.dim("(the Free plan is recommended).")}`
|
|
24212
24295
|
);
|
|
24213
24296
|
return provisionNeonInteractive(runner, options.cwd, opts);
|
|
@@ -24228,6 +24311,16 @@ function authFailureMessage(reason) {
|
|
|
24228
24311
|
return "Could not sign in to Vercel.";
|
|
24229
24312
|
}
|
|
24230
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
|
+
}
|
|
24231
24324
|
function neonFailureMessage(reason) {
|
|
24232
24325
|
switch (reason) {
|
|
24233
24326
|
case "terms-required":
|
|
@@ -24490,13 +24583,13 @@ async function runSeedCommand(options) {
|
|
|
24490
24583
|
}
|
|
24491
24584
|
);
|
|
24492
24585
|
});
|
|
24493
|
-
const
|
|
24494
|
-
|
|
24586
|
+
const spinner10 = clack.spinner();
|
|
24587
|
+
spinner10.start("Creating admin user...");
|
|
24495
24588
|
try {
|
|
24496
24589
|
const result = await runSeed2(false);
|
|
24497
24590
|
if (result.code === 2) {
|
|
24498
24591
|
const existingName = result.stdout.split("\n").find((l) => l.startsWith("EXISTING_USER:"))?.replace("EXISTING_USER:", "")?.trim() || "unknown";
|
|
24499
|
-
|
|
24592
|
+
spinner10.stop(`Account already exists for ${email}`);
|
|
24500
24593
|
const overwrite = await clack.confirm({
|
|
24501
24594
|
message: `An admin account (${existingName}) already exists with this email. Replace it?`
|
|
24502
24595
|
});
|
|
@@ -24508,14 +24601,14 @@ async function runSeedCommand(options) {
|
|
|
24508
24601
|
}
|
|
24509
24602
|
return;
|
|
24510
24603
|
}
|
|
24511
|
-
|
|
24604
|
+
spinner10.start("Replacing admin user...");
|
|
24512
24605
|
await runSeed2(true);
|
|
24513
|
-
|
|
24606
|
+
spinner10.stop("Admin user replaced");
|
|
24514
24607
|
} else {
|
|
24515
|
-
|
|
24608
|
+
spinner10.stop("Admin user created");
|
|
24516
24609
|
}
|
|
24517
24610
|
} catch (err) {
|
|
24518
|
-
|
|
24611
|
+
spinner10.stop("Failed to create admin user");
|
|
24519
24612
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
24520
24613
|
clack.log.error(errMsg);
|
|
24521
24614
|
clack.log.info("You can run the seed script manually:");
|
|
@@ -24615,7 +24708,7 @@ function removeExistingAdminPaths(cwd, namespaces) {
|
|
|
24615
24708
|
return removed;
|
|
24616
24709
|
}
|
|
24617
24710
|
async function runInitCommand(name, options) {
|
|
24618
|
-
|
|
24711
|
+
p16.intro(pc5.bgCyan(pc5.black(" Setup Your Dashboard ")));
|
|
24619
24712
|
let cwd = process.cwd();
|
|
24620
24713
|
let projectName = path48.basename(cwd);
|
|
24621
24714
|
let forceMode = Boolean(options.force);
|
|
@@ -24624,13 +24717,13 @@ async function runInitCommand(name, options) {
|
|
|
24624
24717
|
try {
|
|
24625
24718
|
namespace = validateAdminNamespace(options.namespace);
|
|
24626
24719
|
} catch (error) {
|
|
24627
|
-
|
|
24720
|
+
p16.log.error(error instanceof Error ? error.message : String(error));
|
|
24628
24721
|
process.exit(1);
|
|
24629
24722
|
}
|
|
24630
24723
|
}
|
|
24631
24724
|
if (!options.yes && !options.namespace) {
|
|
24632
24725
|
const defaultDashboardPath = resolveAdminNamespace(DEFAULT_ADMIN_NAMESPACE).routePath;
|
|
24633
|
-
const namespaceInput = await
|
|
24726
|
+
const namespaceInput = await p16.text({
|
|
24634
24727
|
message: "What's the path you want the dashboard to be?",
|
|
24635
24728
|
placeholder: `eg. ${defaultDashboardPath}`,
|
|
24636
24729
|
defaultValue: defaultDashboardPath,
|
|
@@ -24643,8 +24736,8 @@ async function runInitCommand(name, options) {
|
|
|
24643
24736
|
}
|
|
24644
24737
|
}
|
|
24645
24738
|
});
|
|
24646
|
-
if (
|
|
24647
|
-
|
|
24739
|
+
if (p16.isCancel(namespaceInput)) {
|
|
24740
|
+
p16.cancel("Setup cancelled.");
|
|
24648
24741
|
process.exit(0);
|
|
24649
24742
|
}
|
|
24650
24743
|
namespace = validateAdminDashboardPath(namespaceInput);
|
|
@@ -24654,16 +24747,16 @@ async function runInitCommand(name, options) {
|
|
|
24654
24747
|
let isFreshProject = false;
|
|
24655
24748
|
let srcDir;
|
|
24656
24749
|
if (project2.isExisting) {
|
|
24657
|
-
|
|
24750
|
+
p16.log.info(`Next.js app detected ${pc5.dim("\xB7")} ${pc5.cyan(pm)}`);
|
|
24658
24751
|
srcDir = project2.hasSrcDir;
|
|
24659
24752
|
if (!project2.hasTypeScript) {
|
|
24660
|
-
|
|
24753
|
+
p16.log.error("TypeScript is required. Please add a tsconfig.json first.");
|
|
24661
24754
|
process.exit(1);
|
|
24662
24755
|
}
|
|
24663
24756
|
if (forceMode) {
|
|
24664
24757
|
const nuked = removeExistingAdminPaths(cwd, await resolveForceInitNamespaces(cwd, namespace));
|
|
24665
24758
|
if (nuked > 0) {
|
|
24666
|
-
|
|
24759
|
+
p16.log.warn(`${pc5.yellow("Force mode:")} removed ${nuked} existing admin paths`);
|
|
24667
24760
|
}
|
|
24668
24761
|
project2 = detectProject(cwd, namespace);
|
|
24669
24762
|
} else if (project2.conflicts.length > 0) {
|
|
@@ -24672,9 +24765,9 @@ async function runInitCommand(name, options) {
|
|
|
24672
24765
|
"",
|
|
24673
24766
|
pc5.dim(`Use ${pc5.bold("--force")} to remove existing admin files before scaffolding.`)
|
|
24674
24767
|
);
|
|
24675
|
-
|
|
24768
|
+
p16.note(conflictLines.join("\n"), pc5.yellow("Conflicts"));
|
|
24676
24769
|
if (!options.yes) {
|
|
24677
|
-
const proceed = await
|
|
24770
|
+
const proceed = await p16.confirm({
|
|
24678
24771
|
message: [
|
|
24679
24772
|
`Continue with ${pc5.bold(pc5.cyan("--force"))}?`,
|
|
24680
24773
|
`${pc5.cyan("\u2502")} ${pc5.dim("This will force overwrite the existing admin code.")}`,
|
|
@@ -24682,8 +24775,8 @@ async function runInitCommand(name, options) {
|
|
|
24682
24775
|
].join("\n"),
|
|
24683
24776
|
initialValue: true
|
|
24684
24777
|
});
|
|
24685
|
-
if (
|
|
24686
|
-
|
|
24778
|
+
if (p16.isCancel(proceed) || !proceed) {
|
|
24779
|
+
p16.cancel("Setup cancelled.");
|
|
24687
24780
|
process.exit(0);
|
|
24688
24781
|
}
|
|
24689
24782
|
forceMode = true;
|
|
@@ -24692,23 +24785,23 @@ async function runInitCommand(name, options) {
|
|
|
24692
24785
|
await resolveForceInitNamespaces(cwd, namespace)
|
|
24693
24786
|
);
|
|
24694
24787
|
if (nuked > 0) {
|
|
24695
|
-
|
|
24788
|
+
p16.log.warn(`${pc5.yellow("Force mode:")} removed ${nuked} existing admin paths`);
|
|
24696
24789
|
}
|
|
24697
24790
|
project2 = detectProject(cwd, namespace);
|
|
24698
24791
|
}
|
|
24699
24792
|
}
|
|
24700
24793
|
} else {
|
|
24701
|
-
|
|
24794
|
+
p16.log.info("No Next.js app found \u2014 Running the fresh project mode...");
|
|
24702
24795
|
let projectPrompt;
|
|
24703
24796
|
try {
|
|
24704
24797
|
projectPrompt = options.yes ? resolveNonInteractiveProject(name) : await promptProject(name);
|
|
24705
24798
|
} catch (error) {
|
|
24706
|
-
|
|
24799
|
+
p16.log.error(error instanceof Error ? error.message : String(error));
|
|
24707
24800
|
process.exit(1);
|
|
24708
24801
|
}
|
|
24709
24802
|
srcDir = projectPrompt.useSrcDir;
|
|
24710
24803
|
if (!options.yes) {
|
|
24711
|
-
const pmChoice = await
|
|
24804
|
+
const pmChoice = await p16.select({
|
|
24712
24805
|
message: "Which package manager do you want to use?",
|
|
24713
24806
|
options: [
|
|
24714
24807
|
{ value: "pnpm", label: "pnpm", hint: "recommended" },
|
|
@@ -24717,8 +24810,8 @@ async function runInitCommand(name, options) {
|
|
|
24717
24810
|
{ value: "bun", label: "bun" }
|
|
24718
24811
|
]
|
|
24719
24812
|
});
|
|
24720
|
-
if (
|
|
24721
|
-
|
|
24813
|
+
if (p16.isCancel(pmChoice)) {
|
|
24814
|
+
p16.cancel("Setup cancelled.");
|
|
24722
24815
|
process.exit(0);
|
|
24723
24816
|
}
|
|
24724
24817
|
pm = pmChoice;
|
|
@@ -24740,7 +24833,7 @@ async function runInitCommand(name, options) {
|
|
|
24740
24833
|
];
|
|
24741
24834
|
if (srcDir) cnaArgs.push("--src-dir");
|
|
24742
24835
|
else cnaArgs.push("--no-src-dir");
|
|
24743
|
-
const createNextAppSpinner =
|
|
24836
|
+
const createNextAppSpinner = p16.spinner();
|
|
24744
24837
|
createNextAppSpinner.start(`Creating a Next.js app (latest)`);
|
|
24745
24838
|
const createNextAppResult = await runQuietCommand(bin, cnaArgs, {
|
|
24746
24839
|
cwd,
|
|
@@ -24752,8 +24845,8 @@ async function runInitCommand(name, options) {
|
|
|
24752
24845
|
process.stderr.write(`${createNextAppResult.output.trimEnd()}
|
|
24753
24846
|
`);
|
|
24754
24847
|
}
|
|
24755
|
-
|
|
24756
|
-
|
|
24848
|
+
p16.log.error(createNextAppResult.error);
|
|
24849
|
+
p16.log.info(
|
|
24757
24850
|
`You can create the project manually:
|
|
24758
24851
|
${pc5.cyan(`npx create-next-app@latest ${projectPrompt.projectName} --typescript --tailwind --app`)}
|
|
24759
24852
|
Then run ${pc5.cyan("betterstart init")} inside it.`
|
|
@@ -24767,11 +24860,11 @@ async function runInitCommand(name, options) {
|
|
|
24767
24860
|
);
|
|
24768
24861
|
if (!hasPackageJson || !hasNextConfig) {
|
|
24769
24862
|
createNextAppSpinner.stop(`Failed to create Next.js app: ${displayName}`);
|
|
24770
|
-
|
|
24863
|
+
p16.log.error(
|
|
24771
24864
|
"create-next-app completed but the project was not created. This can happen with nested npx calls."
|
|
24772
24865
|
);
|
|
24773
24866
|
const manualCmd = `npx create-next-app@latest ${projectPrompt.projectName} --typescript --tailwind --app`;
|
|
24774
|
-
|
|
24867
|
+
p16.log.info(
|
|
24775
24868
|
`Create the project manually:
|
|
24776
24869
|
${pc5.cyan(manualCmd)}
|
|
24777
24870
|
Then run ${pc5.cyan("betterstart init")} inside it.`
|
|
@@ -24784,46 +24877,13 @@ async function runInitCommand(name, options) {
|
|
|
24784
24877
|
project2 = detectProject(cwd, namespace);
|
|
24785
24878
|
isFreshProject = true;
|
|
24786
24879
|
}
|
|
24787
|
-
const selectedPlugins = (() => {
|
|
24788
|
-
if (options.plugins) {
|
|
24789
|
-
return parsePluginList(options.plugins);
|
|
24790
|
-
}
|
|
24791
|
-
return [];
|
|
24792
|
-
})();
|
|
24793
|
-
const selectedIntegrations = (() => {
|
|
24794
|
-
if (options.integrations) {
|
|
24795
|
-
return parseIntegrationList(options.integrations);
|
|
24796
|
-
}
|
|
24797
|
-
return [];
|
|
24798
|
-
})();
|
|
24799
|
-
let collectedIntegrationConfig = {
|
|
24800
|
-
sections: [],
|
|
24801
|
-
overwriteKeys: /* @__PURE__ */ new Set()
|
|
24802
|
-
};
|
|
24803
|
-
let pluginSelection;
|
|
24804
|
-
if (selectedPlugins.length > 0 || selectedIntegrations.length > 0) {
|
|
24805
|
-
pluginSelection = {
|
|
24806
|
-
plugins: selectedPlugins,
|
|
24807
|
-
integrations: selectedIntegrations,
|
|
24808
|
-
storage: selectedIntegrations.includes("r2") ? "r2" : "local"
|
|
24809
|
-
};
|
|
24810
|
-
} else if (options.yes) {
|
|
24811
|
-
pluginSelection = { plugins: [], integrations: [], storage: "local" };
|
|
24812
|
-
} else {
|
|
24813
|
-
const promptResult = await promptPlugins(cwd);
|
|
24814
|
-
collectedIntegrationConfig = promptResult.integrationConfig;
|
|
24815
|
-
pluginSelection = {
|
|
24816
|
-
plugins: promptResult.plugins,
|
|
24817
|
-
integrations: promptResult.integrations,
|
|
24818
|
-
storage: promptResult.storage
|
|
24819
|
-
};
|
|
24820
|
-
}
|
|
24821
24880
|
let databaseUrl;
|
|
24881
|
+
let vercelResendApiKey;
|
|
24822
24882
|
const existingDbUrl = readExistingDbUrl(cwd);
|
|
24823
24883
|
if (options.yes) {
|
|
24824
24884
|
if (options.databaseUrl) {
|
|
24825
24885
|
if (!isValidDbUrl(options.databaseUrl)) {
|
|
24826
|
-
|
|
24886
|
+
p16.log.error(
|
|
24827
24887
|
`Invalid database URL. Must start with ${pc5.cyan("postgres://")} or ${pc5.cyan("postgresql://")}`
|
|
24828
24888
|
);
|
|
24829
24889
|
process.exit(1);
|
|
@@ -24842,34 +24902,77 @@ async function runInitCommand(name, options) {
|
|
|
24842
24902
|
if (flow.ok && flow.databaseUrl) {
|
|
24843
24903
|
databaseUrl = flow.databaseUrl;
|
|
24844
24904
|
} else {
|
|
24845
|
-
|
|
24905
|
+
p16.log.warn("Vercel provisioning did not complete; continuing without a database URL.");
|
|
24846
24906
|
}
|
|
24847
24907
|
}
|
|
24848
24908
|
} else if (existingDbUrl) {
|
|
24849
24909
|
const masked = maskDbUrl(existingDbUrl);
|
|
24850
|
-
|
|
24910
|
+
p16.log.info(`Using existing database URL from .env.local ${pc5.dim(`(${masked})`)}`);
|
|
24851
24911
|
databaseUrl = existingDbUrl;
|
|
24852
24912
|
} else {
|
|
24853
|
-
const
|
|
24854
|
-
if (
|
|
24913
|
+
const servicesResult = await promptServices({ allowVercel: options.vercel !== false });
|
|
24914
|
+
if (servicesResult.provider === "vercel-cli") {
|
|
24855
24915
|
const flow = await runVercelNeonFlow({
|
|
24856
24916
|
cwd,
|
|
24857
24917
|
projectName,
|
|
24858
24918
|
interactive: true,
|
|
24859
24919
|
plan: options.vercelPlan,
|
|
24860
|
-
env: process.env
|
|
24920
|
+
env: process.env,
|
|
24921
|
+
provisionResend: true
|
|
24861
24922
|
});
|
|
24862
24923
|
if (flow.ok && flow.databaseUrl) {
|
|
24863
24924
|
databaseUrl = flow.databaseUrl;
|
|
24925
|
+
vercelResendApiKey = flow.resendApiKey;
|
|
24864
24926
|
} else {
|
|
24865
|
-
|
|
24927
|
+
p16.log.info("Falling back to a manual database connection string.");
|
|
24866
24928
|
openBrowserVercelNeon();
|
|
24867
24929
|
databaseUrl = await promptConnectionString();
|
|
24868
24930
|
}
|
|
24869
24931
|
} else {
|
|
24870
|
-
databaseUrl =
|
|
24932
|
+
databaseUrl = servicesResult.url;
|
|
24871
24933
|
}
|
|
24872
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");
|
|
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
|
+
};
|
|
24975
|
+
}
|
|
24873
24976
|
const nextMajorVersion = detectNextMajorVersion(cwd);
|
|
24874
24977
|
const config = {
|
|
24875
24978
|
...getDefaultConfig(srcDir, nextMajorVersion)
|
|
@@ -24879,7 +24982,7 @@ async function runInitCommand(name, options) {
|
|
|
24879
24982
|
config.paths = config.frameworkConfig.next.paths;
|
|
24880
24983
|
config.database.migrationsDir = deriveMigrationsDir(namespace);
|
|
24881
24984
|
const results = [];
|
|
24882
|
-
const s =
|
|
24985
|
+
const s = p16.spinner();
|
|
24883
24986
|
s.start("Directory structure");
|
|
24884
24987
|
const baseFiles = scaffoldBase({
|
|
24885
24988
|
cwd,
|
|
@@ -24956,7 +25059,7 @@ async function runInitCommand(name, options) {
|
|
|
24956
25059
|
});
|
|
24957
25060
|
s.stop("");
|
|
24958
25061
|
process.stdout.write("\x1B[2A\x1B[J");
|
|
24959
|
-
|
|
25062
|
+
p16.note(noteLines.join("\n"), "Scaffolded admin");
|
|
24960
25063
|
const drizzleConfigPath = path48.join(cwd, "drizzle.config.ts");
|
|
24961
25064
|
if (!dbFiles.includes("drizzle.config.ts") && fs37.existsSync(drizzleConfigPath)) {
|
|
24962
25065
|
if (forceMode) {
|
|
@@ -24966,20 +25069,20 @@ async function runInitCommand(name, options) {
|
|
|
24966
25069
|
readNamespacedTemplate("drizzle.config.ts", namespace),
|
|
24967
25070
|
"utf-8"
|
|
24968
25071
|
);
|
|
24969
|
-
|
|
25072
|
+
p16.log.success("Updated drizzle.config.ts");
|
|
24970
25073
|
} else if (!options.yes) {
|
|
24971
|
-
const overwrite = await
|
|
25074
|
+
const overwrite = await p16.confirm({
|
|
24972
25075
|
message: "drizzle.config.ts already exists. Overwrite with latest version?",
|
|
24973
25076
|
initialValue: true
|
|
24974
25077
|
});
|
|
24975
|
-
if (!
|
|
25078
|
+
if (!p16.isCancel(overwrite) && overwrite) {
|
|
24976
25079
|
const { readNamespacedTemplate } = await import("./template-reader-X6KFP7B3.js");
|
|
24977
25080
|
fs37.writeFileSync(
|
|
24978
25081
|
drizzleConfigPath,
|
|
24979
25082
|
readNamespacedTemplate("drizzle.config.ts", namespace),
|
|
24980
25083
|
"utf-8"
|
|
24981
25084
|
);
|
|
24982
|
-
|
|
25085
|
+
p16.log.success("Updated drizzle.config.ts");
|
|
24983
25086
|
}
|
|
24984
25087
|
}
|
|
24985
25088
|
}
|
|
@@ -25005,8 +25108,8 @@ async function runInitCommand(name, options) {
|
|
|
25005
25108
|
depsInstalled = true;
|
|
25006
25109
|
} else {
|
|
25007
25110
|
s.stop("Failed to install dependencies");
|
|
25008
|
-
|
|
25009
|
-
|
|
25111
|
+
p16.log.warning(depsResult.error ?? "Unknown error");
|
|
25112
|
+
p16.log.info(
|
|
25010
25113
|
`You can install them manually:
|
|
25011
25114
|
${pc5.cyan(`${pm} add ${depsResult.dependencies.join(" ")}`)}
|
|
25012
25115
|
${pc5.cyan(`${pm} add -D ${depsResult.devDeps.join(" ")}`)}`
|
|
@@ -25095,10 +25198,10 @@ async function runInitCommand(name, options) {
|
|
|
25095
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.`
|
|
25096
25199
|
);
|
|
25097
25200
|
}
|
|
25098
|
-
|
|
25201
|
+
p16.note(installLines.join("\n"), "Installed");
|
|
25099
25202
|
let dbPushed = false;
|
|
25100
25203
|
if (depsResult.success && options.skipMigration && hasDbUrl(cwd)) {
|
|
25101
|
-
|
|
25204
|
+
p16.log.info(`Skipping database schema push ${pc5.dim("(--skip-migration)")}`);
|
|
25102
25205
|
} else if (depsResult.success && hasDbUrl(cwd)) {
|
|
25103
25206
|
let driverReady = hasDrizzleKitPostgresDriverDependency(cwd);
|
|
25104
25207
|
if (!driverReady) {
|
|
@@ -25113,8 +25216,8 @@ async function runInitCommand(name, options) {
|
|
|
25113
25216
|
});
|
|
25114
25217
|
if (!driverResult.success) {
|
|
25115
25218
|
s.stop("Database push failed");
|
|
25116
|
-
|
|
25117
|
-
|
|
25219
|
+
p16.log.warning(driverResult.error ?? `Failed to install ${DRIZZLE_KIT_POSTGRES_DRIVER_DEP}`);
|
|
25220
|
+
p16.log.info(
|
|
25118
25221
|
`Install ${DRIZZLE_KIT_POSTGRES_DRIVER_DEP} manually, then run: ${pc5.cyan(drizzlePushCommand(pm))}`
|
|
25119
25222
|
);
|
|
25120
25223
|
} else {
|
|
@@ -25125,7 +25228,7 @@ async function runInitCommand(name, options) {
|
|
|
25125
25228
|
if (driverReady) {
|
|
25126
25229
|
const useTerminalForDbPush = shouldUseTerminalForDbPush(options);
|
|
25127
25230
|
if (useTerminalForDbPush) {
|
|
25128
|
-
|
|
25231
|
+
p16.log.info(`Running ${pc5.cyan("drizzle-kit push --force")}`);
|
|
25129
25232
|
} else {
|
|
25130
25233
|
s.start("Pushing database schema (drizzle-kit push)");
|
|
25131
25234
|
}
|
|
@@ -25134,13 +25237,13 @@ async function runInitCommand(name, options) {
|
|
|
25134
25237
|
if (useTerminalForDbPush) {
|
|
25135
25238
|
const verification = await verifyDatabaseReachable(cwd);
|
|
25136
25239
|
if (!verification.success) {
|
|
25137
|
-
|
|
25138
|
-
|
|
25240
|
+
p16.log.warning(verification.error);
|
|
25241
|
+
p16.log.error("Database was not reachable. Aborting setup.");
|
|
25139
25242
|
process.exit(1);
|
|
25140
25243
|
}
|
|
25141
25244
|
}
|
|
25142
25245
|
if (useTerminalForDbPush) {
|
|
25143
|
-
|
|
25246
|
+
p16.log.success("Database schema pushed");
|
|
25144
25247
|
} else {
|
|
25145
25248
|
s.stop(`${pc5.green("\u2713")} Database schema pushed`);
|
|
25146
25249
|
}
|
|
@@ -25150,12 +25253,12 @@ async function runInitCommand(name, options) {
|
|
|
25150
25253
|
s.stop("Database push failed");
|
|
25151
25254
|
}
|
|
25152
25255
|
const pushError = pushResult.error ?? "Unknown error";
|
|
25153
|
-
|
|
25256
|
+
p16.log.warning(pushError);
|
|
25154
25257
|
if (isDatabaseReachabilityError(pushError)) {
|
|
25155
|
-
|
|
25258
|
+
p16.log.error("Database was not reachable. Aborting setup.");
|
|
25156
25259
|
process.exit(1);
|
|
25157
25260
|
}
|
|
25158
|
-
|
|
25261
|
+
p16.log.info(`You can run it manually: ${pc5.cyan(drizzlePushCommand(pm))}`);
|
|
25159
25262
|
}
|
|
25160
25263
|
}
|
|
25161
25264
|
}
|
|
@@ -25164,7 +25267,7 @@ async function runInitCommand(name, options) {
|
|
|
25164
25267
|
let seedSuccess = false;
|
|
25165
25268
|
let adminAccountReady = false;
|
|
25166
25269
|
if (dbPushed && options.skipAdminCreation) {
|
|
25167
|
-
|
|
25270
|
+
p16.log.info(
|
|
25168
25271
|
`Skipping admin user creation ${pc5.dim(`(use ${betterstartExecCommand(pm, "seed")} later)`)}`
|
|
25169
25272
|
);
|
|
25170
25273
|
}
|
|
@@ -25174,14 +25277,14 @@ async function runInitCommand(name, options) {
|
|
|
25174
25277
|
let replaceExistingAdmin = false;
|
|
25175
25278
|
const adminCheck = await checkExistingAdmin(cwd, adminDir, authBasePath);
|
|
25176
25279
|
if (adminCheck.error) {
|
|
25177
|
-
|
|
25280
|
+
p16.log.warning(`Could not verify existing admin account ${pc5.dim(`(${adminCheck.error})`)}`);
|
|
25178
25281
|
if (isDatabaseReachabilityError(adminCheck.error)) {
|
|
25179
|
-
|
|
25282
|
+
p16.log.error("Database was not reachable. Aborting setup.");
|
|
25180
25283
|
process.exit(1);
|
|
25181
25284
|
}
|
|
25182
25285
|
} else if (adminCheck.existingAdmin) {
|
|
25183
25286
|
const existingAdminLabel = formatAdminIdentity(adminCheck.existingAdmin);
|
|
25184
|
-
const adminAction = await
|
|
25287
|
+
const adminAction = await p16.select({
|
|
25185
25288
|
message: "Found an already existing admin account. Do you want to replace it or skip?",
|
|
25186
25289
|
options: [
|
|
25187
25290
|
{
|
|
@@ -25195,34 +25298,34 @@ async function runInitCommand(name, options) {
|
|
|
25195
25298
|
}
|
|
25196
25299
|
]
|
|
25197
25300
|
});
|
|
25198
|
-
if (
|
|
25199
|
-
|
|
25301
|
+
if (p16.isCancel(adminAction)) {
|
|
25302
|
+
p16.cancel("Setup cancelled.");
|
|
25200
25303
|
process.exit(0);
|
|
25201
25304
|
}
|
|
25202
25305
|
if (adminAction === "skip") {
|
|
25203
25306
|
adminAccountReady = true;
|
|
25204
|
-
|
|
25307
|
+
p16.log.info(`Keeping existing admin account ${pc5.dim(`(${existingAdminLabel})`)}`);
|
|
25205
25308
|
} else {
|
|
25206
25309
|
replaceExistingAdmin = true;
|
|
25207
25310
|
}
|
|
25208
25311
|
}
|
|
25209
25312
|
if (!adminAccountReady) {
|
|
25210
|
-
|
|
25313
|
+
p16.note(
|
|
25211
25314
|
pc5.dim(
|
|
25212
25315
|
replaceExistingAdmin ? "Replace the existing admin account to access the admin." : "Create your first admin user to access the admin."
|
|
25213
25316
|
),
|
|
25214
25317
|
"Admin account"
|
|
25215
25318
|
);
|
|
25216
|
-
const credentials = await
|
|
25319
|
+
const credentials = await p16.group(
|
|
25217
25320
|
{
|
|
25218
|
-
email: () =>
|
|
25321
|
+
email: () => p16.text({
|
|
25219
25322
|
message: "Admin email",
|
|
25220
25323
|
placeholder: "admin@example.com",
|
|
25221
25324
|
validate: (v) => {
|
|
25222
25325
|
if (!v || !v.includes("@")) return "Please enter a valid email";
|
|
25223
25326
|
}
|
|
25224
25327
|
}),
|
|
25225
|
-
password: () =>
|
|
25328
|
+
password: () => p16.password({
|
|
25226
25329
|
message: "Admin password",
|
|
25227
25330
|
validate: (v) => {
|
|
25228
25331
|
if (!v || v.length < 8) return "Password must be at least 8 characters";
|
|
@@ -25231,7 +25334,7 @@ async function runInitCommand(name, options) {
|
|
|
25231
25334
|
},
|
|
25232
25335
|
{
|
|
25233
25336
|
onCancel: () => {
|
|
25234
|
-
|
|
25337
|
+
p16.cancel("Setup cancelled.");
|
|
25235
25338
|
process.exit(0);
|
|
25236
25339
|
}
|
|
25237
25340
|
}
|
|
@@ -25250,11 +25353,11 @@ async function runInitCommand(name, options) {
|
|
|
25250
25353
|
);
|
|
25251
25354
|
if (seedResult.existingUser) {
|
|
25252
25355
|
s.stop(`${pc5.yellow("\u25B2")} An account already exists for ${credentials.email}`);
|
|
25253
|
-
const replace = await
|
|
25356
|
+
const replace = await p16.confirm({
|
|
25254
25357
|
message: "Replace the existing account with this email?",
|
|
25255
25358
|
initialValue: false
|
|
25256
25359
|
});
|
|
25257
|
-
if (!
|
|
25360
|
+
if (!p16.isCancel(replace) && replace) {
|
|
25258
25361
|
seedOverwriteMode = "email";
|
|
25259
25362
|
s.start("Replacing admin user");
|
|
25260
25363
|
seedResult = await runSeed(
|
|
@@ -25275,14 +25378,14 @@ async function runInitCommand(name, options) {
|
|
|
25275
25378
|
adminAccountReady = true;
|
|
25276
25379
|
} else if (seedResult.error) {
|
|
25277
25380
|
s.stop(`${pc5.red("\u2717")} Failed to create admin user`);
|
|
25278
|
-
|
|
25381
|
+
p16.note(
|
|
25279
25382
|
`${pc5.red(seedResult.error)}
|
|
25280
25383
|
|
|
25281
25384
|
Run manually: ${pc5.cyan(betterstartExecCommand(pm, "seed"))}`,
|
|
25282
25385
|
pc5.red("Seed failed")
|
|
25283
25386
|
);
|
|
25284
25387
|
if (isDatabaseReachabilityError(seedResult.error)) {
|
|
25285
|
-
|
|
25388
|
+
p16.log.error("Database was not reachable. Aborting setup.");
|
|
25286
25389
|
process.exit(1);
|
|
25287
25390
|
}
|
|
25288
25391
|
}
|
|
@@ -25336,15 +25439,15 @@ Run manually: ${pc5.cyan(betterstartExecCommand(pm, "seed"))}`,
|
|
|
25336
25439
|
` ${step++}. Run ${pc5.cyan(betterstartExecCommand(pm, "add --integration <name>"))} to install integrations later`
|
|
25337
25440
|
);
|
|
25338
25441
|
summaryLines.push("", "Next steps:", ...nextSteps);
|
|
25339
|
-
|
|
25442
|
+
p16.note(summaryLines.join("\n"), "Admin scaffolded successfully");
|
|
25340
25443
|
if (!options.yes && !options.skipDevServerStart) {
|
|
25341
25444
|
const devCmd = runCommand(pm, "dev");
|
|
25342
|
-
const startDev = await
|
|
25445
|
+
const startDev = await p16.confirm({
|
|
25343
25446
|
message: "Start the development server?",
|
|
25344
25447
|
initialValue: true
|
|
25345
25448
|
});
|
|
25346
|
-
if (!
|
|
25347
|
-
|
|
25449
|
+
if (!p16.isCancel(startDev) && startDev) {
|
|
25450
|
+
p16.outro(`Starting ${pc5.cyan(devCmd)}...`);
|
|
25348
25451
|
await startManagedDevServer(cwd, devCmd, adminLoginUrl, {
|
|
25349
25452
|
email: seedSuccess && seedEmail ? seedEmail : void 0,
|
|
25350
25453
|
password: seedSuccess && seedPassword ? seedPassword : void 0
|
|
@@ -25352,7 +25455,7 @@ Run manually: ${pc5.cyan(betterstartExecCommand(pm, "seed"))}`,
|
|
|
25352
25455
|
return;
|
|
25353
25456
|
}
|
|
25354
25457
|
}
|
|
25355
|
-
|
|
25458
|
+
p16.outro("Done!");
|
|
25356
25459
|
}
|
|
25357
25460
|
function isValidDbUrl(url) {
|
|
25358
25461
|
return url.startsWith("postgres://") || url.startsWith("postgresql://");
|
|
@@ -25739,7 +25842,7 @@ function printAdminReadyNote(state) {
|
|
|
25739
25842
|
lines.unshift(`Password: ${pc5.cyan(state.adminPassword)}`);
|
|
25740
25843
|
lines.unshift(`Admin user: ${pc5.cyan(state.adminEmail)}`);
|
|
25741
25844
|
}
|
|
25742
|
-
|
|
25845
|
+
p16.note(lines.join("\n"), "Admin ready");
|
|
25743
25846
|
}
|
|
25744
25847
|
function shouldSuppressDevServerStartupLine(line) {
|
|
25745
25848
|
const plain = stripAnsi(line).trim();
|
|
@@ -25851,7 +25954,7 @@ function startManagedDevServer(cwd, devCmd, adminLoginUrl, adminCredentials) {
|
|
|
25851
25954
|
|
|
25852
25955
|
// adapters/next/commands/list-integrations.ts
|
|
25853
25956
|
import path49 from "path";
|
|
25854
|
-
import * as
|
|
25957
|
+
import * as p17 from "@clack/prompts";
|
|
25855
25958
|
async function runListIntegrationsCommand(options) {
|
|
25856
25959
|
const cwd = options.cwd ? path49.resolve(options.cwd) : process.cwd();
|
|
25857
25960
|
const config = await resolveConfig(cwd);
|
|
@@ -25860,15 +25963,15 @@ async function runListIntegrationsCommand(options) {
|
|
|
25860
25963
|
const status = installedIntegrations.has(integration.id) ? "installed" : "available";
|
|
25861
25964
|
return `${integration.id.padEnd(10)} ${status.padEnd(10)} ${integration.kind.padEnd(8)} ${integration.description}`;
|
|
25862
25965
|
});
|
|
25863
|
-
|
|
25864
|
-
|
|
25966
|
+
p17.note(lines.join("\n"), "BetterStart integrations");
|
|
25967
|
+
p17.outro(
|
|
25865
25968
|
`${installedIntegrations.size} installed, ${lines.length - installedIntegrations.size} available`
|
|
25866
25969
|
);
|
|
25867
25970
|
}
|
|
25868
25971
|
|
|
25869
25972
|
// adapters/next/commands/list-plugins.ts
|
|
25870
25973
|
import path50 from "path";
|
|
25871
|
-
import * as
|
|
25974
|
+
import * as p18 from "@clack/prompts";
|
|
25872
25975
|
async function runListPluginsCommand(options) {
|
|
25873
25976
|
const cwd = options.cwd ? path50.resolve(options.cwd) : process.cwd();
|
|
25874
25977
|
const config = await resolveConfig(cwd);
|
|
@@ -25877,34 +25980,34 @@ async function runListPluginsCommand(options) {
|
|
|
25877
25980
|
const status = installedPlugins.has(plugin.id) ? "installed" : "available";
|
|
25878
25981
|
return `${plugin.id.padEnd(10)} ${status.padEnd(10)} ${plugin.kind.padEnd(12)} ${plugin.description}`;
|
|
25879
25982
|
});
|
|
25880
|
-
|
|
25881
|
-
|
|
25983
|
+
p18.note(lines.join("\n"), "BetterStart plugins");
|
|
25984
|
+
p18.outro(`${installedPlugins.size} installed, ${lines.length - installedPlugins.size} available`);
|
|
25882
25985
|
}
|
|
25883
25986
|
|
|
25884
25987
|
// adapters/next/commands/remove.ts
|
|
25885
25988
|
import path51 from "path";
|
|
25886
|
-
import * as
|
|
25989
|
+
import * as p19 from "@clack/prompts";
|
|
25887
25990
|
async function runRemoveCommand(items, options) {
|
|
25888
25991
|
const removeIntegrationsMode = Boolean(options.integration);
|
|
25889
25992
|
if (!removeIntegrationsMode && items.includes("core")) {
|
|
25890
|
-
|
|
25993
|
+
p19.log.error("The core Admin cannot be removed.");
|
|
25891
25994
|
process.exit(1);
|
|
25892
25995
|
}
|
|
25893
25996
|
const pluginIds = items.filter(isPluginId);
|
|
25894
25997
|
const integrationIds = items.filter(isIntegrationId);
|
|
25895
25998
|
if (!removeIntegrationsMode && integrationIds.length > 0) {
|
|
25896
|
-
|
|
25999
|
+
p19.log.error(
|
|
25897
26000
|
`Integration IDs require --integration. Run \`betterstart remove --integration ${integrationIds.join(" ")}\`.`
|
|
25898
26001
|
);
|
|
25899
26002
|
process.exit(1);
|
|
25900
26003
|
}
|
|
25901
26004
|
if (removeIntegrationsMode && pluginIds.length > 0) {
|
|
25902
|
-
|
|
26005
|
+
p19.log.error(`Plugin IDs cannot be removed with --integration: ${pluginIds.join(", ")}`);
|
|
25903
26006
|
process.exit(1);
|
|
25904
26007
|
}
|
|
25905
26008
|
const invalidItems = removeIntegrationsMode ? items.filter((integrationId) => !isIntegrationId(integrationId)) : items.filter((pluginId) => !isPluginId(pluginId));
|
|
25906
26009
|
if (invalidItems.length > 0) {
|
|
25907
|
-
|
|
26010
|
+
p19.log.error(
|
|
25908
26011
|
removeIntegrationsMode ? formatUnknownIntegrationMessage(invalidItems) : formatUnknownPluginMessage(invalidItems)
|
|
25909
26012
|
);
|
|
25910
26013
|
process.exit(1);
|
|
@@ -25913,12 +26016,12 @@ async function runRemoveCommand(items, options) {
|
|
|
25913
26016
|
const config = await resolveConfig(cwd);
|
|
25914
26017
|
const pm = detectPackageManager(cwd);
|
|
25915
26018
|
if (!options.force) {
|
|
25916
|
-
const confirmed = await
|
|
26019
|
+
const confirmed = await p19.confirm({
|
|
25917
26020
|
message: `Remove ${removeIntegrationsMode ? "integration" : "plugin"}${items.length === 1 ? "" : "s"} ${items.join(", ")}?`,
|
|
25918
26021
|
initialValue: false
|
|
25919
26022
|
});
|
|
25920
|
-
if (
|
|
25921
|
-
|
|
26023
|
+
if (p19.isCancel(confirmed) || !confirmed) {
|
|
26024
|
+
p19.cancel(`${removeIntegrationsMode ? "Integration" : "Plugin"} removal cancelled.`);
|
|
25922
26025
|
process.exit(0);
|
|
25923
26026
|
}
|
|
25924
26027
|
}
|
|
@@ -25931,13 +26034,13 @@ async function runRemoveCommand(items, options) {
|
|
|
25931
26034
|
});
|
|
25932
26035
|
writeConfigFile(cwd, result2.config);
|
|
25933
26036
|
if (result2.removed.length === 0) {
|
|
25934
|
-
|
|
26037
|
+
p19.outro("No integrations were removed.");
|
|
25935
26038
|
return;
|
|
25936
26039
|
}
|
|
25937
26040
|
if (result2.warnings.length > 0) {
|
|
25938
|
-
|
|
26041
|
+
p19.note(result2.warnings.join("\n"), "Warnings");
|
|
25939
26042
|
}
|
|
25940
|
-
|
|
26043
|
+
p19.outro(
|
|
25941
26044
|
`Removed integration${result2.removed.length === 1 ? "" : "s"}: ${result2.removed.join(", ")}`
|
|
25942
26045
|
);
|
|
25943
26046
|
return;
|
|
@@ -25950,13 +26053,13 @@ async function runRemoveCommand(items, options) {
|
|
|
25950
26053
|
});
|
|
25951
26054
|
writeConfigFile(cwd, result.config);
|
|
25952
26055
|
if (result.removed.length === 0) {
|
|
25953
|
-
|
|
26056
|
+
p19.outro("No plugins were removed.");
|
|
25954
26057
|
return;
|
|
25955
26058
|
}
|
|
25956
26059
|
if (result.warnings.length > 0) {
|
|
25957
|
-
|
|
26060
|
+
p19.note(result.warnings.join("\n"), "Warnings");
|
|
25958
26061
|
}
|
|
25959
|
-
|
|
26062
|
+
p19.outro(`Removed plugin${result.removed.length === 1 ? "" : "s"}: ${result.removed.join(", ")}`);
|
|
25960
26063
|
}
|
|
25961
26064
|
|
|
25962
26065
|
// adapters/next/commands/remove-schema.ts
|
|
@@ -26105,7 +26208,7 @@ async function runRemoveSchemaCommand(schemaName, options) {
|
|
|
26105
26208
|
// adapters/next/commands/uninstall.ts
|
|
26106
26209
|
import fs40 from "fs";
|
|
26107
26210
|
import path53 from "path";
|
|
26108
|
-
import * as
|
|
26211
|
+
import * as p20 from "@clack/prompts";
|
|
26109
26212
|
import pc6 from "picocolors";
|
|
26110
26213
|
|
|
26111
26214
|
// adapters/next/commands/uninstall-cleaners.ts
|
|
@@ -26327,8 +26430,8 @@ function buildUninstallPlan(cwd, namespaceValue) {
|
|
|
26327
26430
|
count: configFiles.length,
|
|
26328
26431
|
unit: configFiles.length === 1 ? "file" : "files",
|
|
26329
26432
|
execute() {
|
|
26330
|
-
for (const
|
|
26331
|
-
if (fs40.existsSync(
|
|
26433
|
+
for (const p21 of configPaths) {
|
|
26434
|
+
if (fs40.existsSync(p21)) fs40.unlinkSync(p21);
|
|
26332
26435
|
}
|
|
26333
26436
|
}
|
|
26334
26437
|
});
|
|
@@ -26392,7 +26495,7 @@ function buildUninstallPlan(cwd, namespaceValue) {
|
|
|
26392
26495
|
}
|
|
26393
26496
|
async function runUninstallCommand(options) {
|
|
26394
26497
|
const cwd = options.cwd ? path53.resolve(options.cwd) : process.cwd();
|
|
26395
|
-
|
|
26498
|
+
p20.intro(pc6.bgRed(pc6.white(" BetterStart Uninstall ")));
|
|
26396
26499
|
let namespace = DEFAULT_ADMIN_NAMESPACE;
|
|
26397
26500
|
try {
|
|
26398
26501
|
const config = await resolveConfig(cwd);
|
|
@@ -26401,8 +26504,8 @@ async function runUninstallCommand(options) {
|
|
|
26401
26504
|
}
|
|
26402
26505
|
const steps = buildUninstallPlan(cwd, namespace);
|
|
26403
26506
|
if (steps.length === 0) {
|
|
26404
|
-
|
|
26405
|
-
|
|
26507
|
+
p20.log.success(`${pc6.green("\u2713")} Nothing to remove \u2014 project is already clean.`);
|
|
26508
|
+
p20.outro("Done");
|
|
26406
26509
|
return;
|
|
26407
26510
|
}
|
|
26408
26511
|
const planLines = steps.map((step) => {
|
|
@@ -26410,18 +26513,18 @@ async function runUninstallCommand(options) {
|
|
|
26410
26513
|
const countLabel = pc6.dim(`${step.count} ${step.unit}`);
|
|
26411
26514
|
return `${pc6.red("\xD7")} ${names} ${countLabel}`;
|
|
26412
26515
|
});
|
|
26413
|
-
|
|
26516
|
+
p20.note(planLines.join("\n"), "Uninstall plan");
|
|
26414
26517
|
if (!options.force) {
|
|
26415
|
-
const confirmed = await
|
|
26518
|
+
const confirmed = await p20.confirm({
|
|
26416
26519
|
message: "Proceed with uninstall?",
|
|
26417
26520
|
initialValue: false
|
|
26418
26521
|
});
|
|
26419
|
-
if (
|
|
26420
|
-
|
|
26522
|
+
if (p20.isCancel(confirmed) || !confirmed) {
|
|
26523
|
+
p20.cancel("Uninstall cancelled.");
|
|
26421
26524
|
process.exit(0);
|
|
26422
26525
|
}
|
|
26423
26526
|
}
|
|
26424
|
-
const s =
|
|
26527
|
+
const s = p20.spinner();
|
|
26425
26528
|
s.start(steps[0].label);
|
|
26426
26529
|
for (const step of steps) {
|
|
26427
26530
|
s.message(step.label);
|
|
@@ -26429,8 +26532,8 @@ async function runUninstallCommand(options) {
|
|
|
26429
26532
|
}
|
|
26430
26533
|
const parts = steps.map((step) => `${step.count} ${step.unit}`);
|
|
26431
26534
|
s.stop(`Removed ${parts.join(", ")}`);
|
|
26432
|
-
|
|
26433
|
-
|
|
26535
|
+
p20.note(pc6.dim("Database tables were NOT dropped \u2014 drop them manually if needed."), "Next steps");
|
|
26536
|
+
p20.outro("Uninstall complete");
|
|
26434
26537
|
}
|
|
26435
26538
|
|
|
26436
26539
|
// adapters/next/commands/update-component.ts
|