@betterstart/cli 0.1.29 → 0.1.31
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 +427 -103
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-6JCWMKSY.js";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
|
-
import { Command as
|
|
7
|
+
import { Command as Command9 } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/commands/generate.ts
|
|
10
10
|
import path22 from "path";
|
|
@@ -901,8 +901,8 @@ function toPascalCase3(str) {
|
|
|
901
901
|
return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
902
902
|
}
|
|
903
903
|
function toCamelCase(str) {
|
|
904
|
-
const
|
|
905
|
-
return
|
|
904
|
+
const p7 = toPascalCase3(str);
|
|
905
|
+
return p7.charAt(0).toLowerCase() + p7.slice(1);
|
|
906
906
|
}
|
|
907
907
|
function toKebabCase(str) {
|
|
908
908
|
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
@@ -993,7 +993,7 @@ function generateFormAdminPages(schema, cwd, pagesDir, options) {
|
|
|
993
993
|
const adminDir = path4.join(cwd, pagesDir, "forms", kebab);
|
|
994
994
|
if (!fs4.existsSync(adminDir)) fs4.mkdirSync(adminDir, { recursive: true });
|
|
995
995
|
const files = [];
|
|
996
|
-
const rel = (
|
|
996
|
+
const rel = (p7) => path4.relative(cwd, p7);
|
|
997
997
|
const pagePath = path4.join(adminDir, "page.tsx");
|
|
998
998
|
if (!fs4.existsSync(pagePath) || options.force) {
|
|
999
999
|
fs4.writeFileSync(pagePath, generatePage(pascal, kebab), "utf-8");
|
|
@@ -1934,8 +1934,8 @@ function toPascalCase4(str) {
|
|
|
1934
1934
|
return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
1935
1935
|
}
|
|
1936
1936
|
function toCamelCase2(str) {
|
|
1937
|
-
const
|
|
1938
|
-
return
|
|
1937
|
+
const p7 = toPascalCase4(str);
|
|
1938
|
+
return p7.charAt(0).toLowerCase() + p7.slice(1);
|
|
1939
1939
|
}
|
|
1940
1940
|
function toKebabCase3(str) {
|
|
1941
1941
|
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
@@ -2650,8 +2650,8 @@ function toPascalCase5(str) {
|
|
|
2650
2650
|
return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
2651
2651
|
}
|
|
2652
2652
|
function toCamelCase3(str) {
|
|
2653
|
-
const
|
|
2654
|
-
return
|
|
2653
|
+
const p7 = toPascalCase5(str);
|
|
2654
|
+
return p7.charAt(0).toLowerCase() + p7.slice(1);
|
|
2655
2655
|
}
|
|
2656
2656
|
function singularize2(str) {
|
|
2657
2657
|
if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
|
|
@@ -2711,11 +2711,11 @@ function generateActions(schema, cwd, actionsDir, options = {}) {
|
|
|
2711
2711
|
const listFieldsWithRels = findListFieldsWithRelationships(dbFields);
|
|
2712
2712
|
const hasListRels = listFieldsWithRels.length > 0;
|
|
2713
2713
|
const allListRelQueries = [];
|
|
2714
|
-
for (const { field: listField, path:
|
|
2714
|
+
for (const { field: listField, path: path43 } of listFieldsWithRels) {
|
|
2715
2715
|
const rels = (listField.fields || []).filter((f) => f.type === "relationship" && f.relationship);
|
|
2716
2716
|
for (const relField of rels) {
|
|
2717
2717
|
allListRelQueries.push({
|
|
2718
|
-
fieldPath:
|
|
2718
|
+
fieldPath: path43.join("_"),
|
|
2719
2719
|
relField,
|
|
2720
2720
|
relTable: toCamelCase3(relField.relationship),
|
|
2721
2721
|
listFieldName: listField.name
|
|
@@ -3374,8 +3374,8 @@ function toPascalCase6(str) {
|
|
|
3374
3374
|
return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
3375
3375
|
}
|
|
3376
3376
|
function toCamelCase4(str) {
|
|
3377
|
-
const
|
|
3378
|
-
return
|
|
3377
|
+
const p7 = toPascalCase6(str);
|
|
3378
|
+
return p7.charAt(0).toLowerCase() + p7.slice(1);
|
|
3379
3379
|
}
|
|
3380
3380
|
function singularize3(str) {
|
|
3381
3381
|
if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
|
|
@@ -4506,8 +4506,8 @@ function toPascalCase9(str) {
|
|
|
4506
4506
|
return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
4507
4507
|
}
|
|
4508
4508
|
function toCamelCase5(str) {
|
|
4509
|
-
const
|
|
4510
|
-
return
|
|
4509
|
+
const p7 = toPascalCase9(str);
|
|
4510
|
+
return p7.charAt(0).toLowerCase() + p7.slice(1);
|
|
4511
4511
|
}
|
|
4512
4512
|
function singularize6(str) {
|
|
4513
4513
|
if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
|
|
@@ -4755,8 +4755,8 @@ function toPascalCase10(str) {
|
|
|
4755
4755
|
return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
4756
4756
|
}
|
|
4757
4757
|
function toCamelCase6(str) {
|
|
4758
|
-
const
|
|
4759
|
-
return
|
|
4758
|
+
const p7 = toPascalCase10(str);
|
|
4759
|
+
return p7.charAt(0).toLowerCase() + p7.slice(1);
|
|
4760
4760
|
}
|
|
4761
4761
|
function singularize7(str) {
|
|
4762
4762
|
if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
|
|
@@ -5417,6 +5417,36 @@ ${indent} </FormControl>${nestedHint}
|
|
|
5417
5417
|
${indent} <FormMessage />
|
|
5418
5418
|
${indent} </FormItem>
|
|
5419
5419
|
${indent} )}
|
|
5420
|
+
${indent} />`;
|
|
5421
|
+
}
|
|
5422
|
+
if (nf.type === "video") {
|
|
5423
|
+
return `${indent} <FormField
|
|
5424
|
+
${indent} control={form.control}
|
|
5425
|
+
${indent} name={\`${field.name}.\${index}.${nf.name}\`}
|
|
5426
|
+
${indent} render={({ field: formField }) => (
|
|
5427
|
+
${indent} <FormItem>
|
|
5428
|
+
${indent} <FormLabel>${nestedLabel}</FormLabel>
|
|
5429
|
+
${indent} <FormControl>
|
|
5430
|
+
${indent} <VideoUploadField value={formField.value} onChange={formField.onChange} onBlur={formField.onBlur} disabled={isPending} maxSizeInMB={100} label="" />
|
|
5431
|
+
${indent} </FormControl>${nestedHint}
|
|
5432
|
+
${indent} <FormMessage />
|
|
5433
|
+
${indent} </FormItem>
|
|
5434
|
+
${indent} )}
|
|
5435
|
+
${indent} />`;
|
|
5436
|
+
}
|
|
5437
|
+
if (nf.type === "media") {
|
|
5438
|
+
return `${indent} <FormField
|
|
5439
|
+
${indent} control={form.control}
|
|
5440
|
+
${indent} name={\`${field.name}.\${index}.${nf.name}\`}
|
|
5441
|
+
${indent} render={({ field: formField }) => (
|
|
5442
|
+
${indent} <FormItem>
|
|
5443
|
+
${indent} <FormLabel>${nestedLabel}</FormLabel>
|
|
5444
|
+
${indent} <FormControl>
|
|
5445
|
+
${indent} <MediaUploadField value={formField.value} onChange={formField.onChange} onBlur={formField.onBlur} disabled={isPending} maxSizeInMB={100} label="" />
|
|
5446
|
+
${indent} </FormControl>${nestedHint}
|
|
5447
|
+
${indent} <FormMessage />
|
|
5448
|
+
${indent} </FormItem>
|
|
5449
|
+
${indent} )}
|
|
5420
5450
|
${indent} />`;
|
|
5421
5451
|
}
|
|
5422
5452
|
return `${indent} <FormField
|
|
@@ -5651,9 +5681,14 @@ function generateForm(schema, cwd, pagesDir, options = {}) {
|
|
|
5651
5681
|
function collectListFields(fields) {
|
|
5652
5682
|
for (const f of fields) {
|
|
5653
5683
|
if (f.type === "list" && f.fields && f.fields.length > 0 && !f.hidden) {
|
|
5654
|
-
|
|
5684
|
+
listFieldsWithNested.push(f);
|
|
5655
5685
|
}
|
|
5656
5686
|
if (f.type === "group" && f.fields) collectListFields(f.fields);
|
|
5687
|
+
if (f.type === "tabs" && f.tabs) {
|
|
5688
|
+
for (const tab of f.tabs) {
|
|
5689
|
+
if (tab.fields) collectListFields(tab.fields);
|
|
5690
|
+
}
|
|
5691
|
+
}
|
|
5657
5692
|
}
|
|
5658
5693
|
}
|
|
5659
5694
|
collectListFields(allFormFields);
|
|
@@ -5979,9 +6014,14 @@ function generateSingleForm(schema, cwd, pagesDir, options = {}) {
|
|
|
5979
6014
|
function collectListFieldsSingle(fields) {
|
|
5980
6015
|
for (const f of fields) {
|
|
5981
6016
|
if (f.type === "list" && f.fields && f.fields.length > 0 && !f.hidden) {
|
|
5982
|
-
|
|
6017
|
+
listFieldsWithNested.push(f);
|
|
5983
6018
|
}
|
|
5984
6019
|
if (f.type === "group" && f.fields) collectListFieldsSingle(f.fields);
|
|
6020
|
+
if (f.type === "tabs" && f.tabs) {
|
|
6021
|
+
for (const tab of f.tabs) {
|
|
6022
|
+
if (tab.fields) collectListFieldsSingle(tab.fields);
|
|
6023
|
+
}
|
|
6024
|
+
}
|
|
5985
6025
|
}
|
|
5986
6026
|
}
|
|
5987
6027
|
collectListFieldsSingle(allFormFields);
|
|
@@ -6935,8 +6975,8 @@ function toPascalCase16(str) {
|
|
|
6935
6975
|
return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
6936
6976
|
}
|
|
6937
6977
|
function toCamelCase7(str) {
|
|
6938
|
-
const
|
|
6939
|
-
return
|
|
6978
|
+
const p7 = toPascalCase16(str);
|
|
6979
|
+
return p7.charAt(0).toLowerCase() + p7.slice(1);
|
|
6940
6980
|
}
|
|
6941
6981
|
function singularize12(str) {
|
|
6942
6982
|
if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
|
|
@@ -7621,7 +7661,7 @@ function runPostGenerate(cwd, schemaName, options = {}) {
|
|
|
7621
7661
|
console.log("\n Running drizzle-kit push...");
|
|
7622
7662
|
const drizzleBin = path21.join(cwd, "node_modules", ".bin", "drizzle-kit");
|
|
7623
7663
|
try {
|
|
7624
|
-
execFileSync2(drizzleBin, ["push", "--force"], { cwd, stdio: "
|
|
7664
|
+
execFileSync2(drizzleBin, ["push", "--force"], { cwd, stdio: "inherit" });
|
|
7625
7665
|
result.dbPush = "success";
|
|
7626
7666
|
console.log(" Database schema synced");
|
|
7627
7667
|
} catch {
|
|
@@ -13409,13 +13449,13 @@ var seedCommand = new Command2("seed").description("Create the initial admin use
|
|
|
13409
13449
|
clack.cancel("Cancelled.");
|
|
13410
13450
|
process.exit(0);
|
|
13411
13451
|
}
|
|
13412
|
-
const
|
|
13452
|
+
const password4 = await clack.password({
|
|
13413
13453
|
message: "Admin password",
|
|
13414
13454
|
validate: (v) => {
|
|
13415
13455
|
if (!v || v.length < 8) return "Password must be at least 8 characters";
|
|
13416
13456
|
}
|
|
13417
13457
|
});
|
|
13418
|
-
if (clack.isCancel(
|
|
13458
|
+
if (clack.isCancel(password4)) {
|
|
13419
13459
|
clack.cancel("Cancelled.");
|
|
13420
13460
|
process.exit(0);
|
|
13421
13461
|
}
|
|
@@ -13445,7 +13485,7 @@ var seedCommand = new Command2("seed").description("Create the initial admin use
|
|
|
13445
13485
|
env: {
|
|
13446
13486
|
...process.env,
|
|
13447
13487
|
SEED_EMAIL: email,
|
|
13448
|
-
SEED_PASSWORD:
|
|
13488
|
+
SEED_PASSWORD: password4,
|
|
13449
13489
|
SEED_NAME: name || "Admin",
|
|
13450
13490
|
...overwrite ? { SEED_OVERWRITE: "true" } : {}
|
|
13451
13491
|
}
|
|
@@ -13461,13 +13501,13 @@ var seedCommand = new Command2("seed").description("Create the initial admin use
|
|
|
13461
13501
|
}
|
|
13462
13502
|
);
|
|
13463
13503
|
});
|
|
13464
|
-
const
|
|
13465
|
-
|
|
13504
|
+
const spinner6 = clack.spinner();
|
|
13505
|
+
spinner6.start("Creating admin user...");
|
|
13466
13506
|
try {
|
|
13467
13507
|
const result = await runSeed2(false);
|
|
13468
13508
|
if (result.code === 2) {
|
|
13469
13509
|
const existingName = result.stdout.split("\n").find((l) => l.startsWith("EXISTING_USER:"))?.replace("EXISTING_USER:", "")?.trim() || "unknown";
|
|
13470
|
-
|
|
13510
|
+
spinner6.stop(`Account already exists for ${email}`);
|
|
13471
13511
|
const overwrite = await clack.confirm({
|
|
13472
13512
|
message: `An admin account (${existingName}) already exists with this email. Replace it?`
|
|
13473
13513
|
});
|
|
@@ -13479,14 +13519,14 @@ var seedCommand = new Command2("seed").description("Create the initial admin use
|
|
|
13479
13519
|
}
|
|
13480
13520
|
process.exit(0);
|
|
13481
13521
|
}
|
|
13482
|
-
|
|
13522
|
+
spinner6.start("Replacing admin user...");
|
|
13483
13523
|
await runSeed2(true);
|
|
13484
|
-
|
|
13524
|
+
spinner6.stop("Admin user replaced");
|
|
13485
13525
|
} else {
|
|
13486
|
-
|
|
13526
|
+
spinner6.stop("Admin user created");
|
|
13487
13527
|
}
|
|
13488
13528
|
} catch (err) {
|
|
13489
|
-
|
|
13529
|
+
spinner6.stop("Failed to create admin user");
|
|
13490
13530
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
13491
13531
|
clack.log.error(errMsg);
|
|
13492
13532
|
clack.log.info("You can run the seed script manually:");
|
|
@@ -13995,7 +14035,7 @@ function hasDbUrl(cwd) {
|
|
|
13995
14035
|
}
|
|
13996
14036
|
return false;
|
|
13997
14037
|
}
|
|
13998
|
-
function runSeed(cwd, cmsDir, email,
|
|
14038
|
+
function runSeed(cwd, cmsDir, email, password4, overwrite = false) {
|
|
13999
14039
|
const scriptsDir = path37.join(cwd, cmsDir, "scripts");
|
|
14000
14040
|
const seedPath = path37.join(scriptsDir, "seed.ts");
|
|
14001
14041
|
if (!fs32.existsSync(scriptsDir)) {
|
|
@@ -14019,7 +14059,7 @@ function runSeed(cwd, cmsDir, email, password3, overwrite = false) {
|
|
|
14019
14059
|
env: {
|
|
14020
14060
|
...process.env,
|
|
14021
14061
|
SEED_EMAIL: email,
|
|
14022
|
-
SEED_PASSWORD:
|
|
14062
|
+
SEED_PASSWORD: password4,
|
|
14023
14063
|
SEED_NAME: "Admin",
|
|
14024
14064
|
...overwrite ? { SEED_OVERWRITE: "true" } : {}
|
|
14025
14065
|
}
|
|
@@ -14106,8 +14146,8 @@ function toPascalCase17(str) {
|
|
|
14106
14146
|
return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
14107
14147
|
}
|
|
14108
14148
|
function toCamelCase8(str) {
|
|
14109
|
-
const
|
|
14110
|
-
return
|
|
14149
|
+
const p7 = toPascalCase17(str);
|
|
14150
|
+
return p7.charAt(0).toLowerCase() + p7.slice(1);
|
|
14111
14151
|
}
|
|
14112
14152
|
function singularize13(str) {
|
|
14113
14153
|
if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
|
|
@@ -14319,15 +14359,298 @@ var removeCommand = new Command4("remove").alias("rm").description("Remove all g
|
|
|
14319
14359
|
console.log("");
|
|
14320
14360
|
});
|
|
14321
14361
|
|
|
14322
|
-
// src/commands/
|
|
14323
|
-
import
|
|
14362
|
+
// src/commands/setup-r2.ts
|
|
14363
|
+
import { execFileSync as execFileSync5, spawnSync } from "child_process";
|
|
14364
|
+
import fs34 from "fs";
|
|
14365
|
+
import os from "os";
|
|
14324
14366
|
import path39 from "path";
|
|
14325
14367
|
import * as p5 from "@clack/prompts";
|
|
14326
14368
|
import { Command as Command5 } from "commander";
|
|
14327
14369
|
import pc3 from "picocolors";
|
|
14370
|
+
var setupR2Command = new Command5("setup-r2").description("Create a Cloudflare R2 bucket and configure storage env vars").option("--cwd <path>", "Project root path").option("--bucket <name>", "Bucket name (skips prompt)").action(async (options) => {
|
|
14371
|
+
const cwd = options.cwd ? path39.resolve(options.cwd) : process.cwd();
|
|
14372
|
+
p5.intro(pc3.bgCyan(pc3.black(" BetterStart \u2014 R2 Storage Setup ")));
|
|
14373
|
+
const s = p5.spinner();
|
|
14374
|
+
s.start("Looking for wrangler CLI");
|
|
14375
|
+
const wrangler = findWrangler(cwd);
|
|
14376
|
+
if (!wrangler) {
|
|
14377
|
+
s.stop(`${pc3.red("\u2717")} Wrangler CLI not found`);
|
|
14378
|
+
p5.log.error(
|
|
14379
|
+
`Install it first:
|
|
14380
|
+
${pc3.cyan("npm install -g wrangler")}
|
|
14381
|
+
${pc3.dim("or")} ${pc3.cyan("npx wrangler --version")}`
|
|
14382
|
+
);
|
|
14383
|
+
process.exit(1);
|
|
14384
|
+
}
|
|
14385
|
+
s.stop(`Wrangler: ${pc3.cyan(wrangler.bin === "npx" ? "npx wrangler" : "wrangler")}`);
|
|
14386
|
+
s.start("Checking Cloudflare authentication");
|
|
14387
|
+
const whoami = runWrangler(wrangler, ["whoami"], { cwd });
|
|
14388
|
+
const whoamiOut = whoami.stdout?.toString() ?? "";
|
|
14389
|
+
if (whoami.status !== 0 || whoamiOut.includes("Not authenticated")) {
|
|
14390
|
+
s.stop(`${pc3.yellow("\u25B2")} Not logged in to Cloudflare`);
|
|
14391
|
+
const login = await p5.confirm({
|
|
14392
|
+
message: "Open browser to log in to Cloudflare?",
|
|
14393
|
+
initialValue: true
|
|
14394
|
+
});
|
|
14395
|
+
if (p5.isCancel(login) || !login) {
|
|
14396
|
+
p5.cancel("Setup cancelled. Run `wrangler login` manually first.");
|
|
14397
|
+
process.exit(0);
|
|
14398
|
+
}
|
|
14399
|
+
s.start("Waiting for Cloudflare login");
|
|
14400
|
+
const loginResult = runWrangler(wrangler, ["login"], { cwd, stdio: "inherit", timeout: 12e4 });
|
|
14401
|
+
if (loginResult.status !== 0) {
|
|
14402
|
+
s.stop(`${pc3.red("\u2717")} Login failed`);
|
|
14403
|
+
p5.cancel("Could not authenticate with Cloudflare. Run `wrangler login` manually.");
|
|
14404
|
+
process.exit(1);
|
|
14405
|
+
}
|
|
14406
|
+
s.stop(`${pc3.green("\u2713")} Logged in to Cloudflare`);
|
|
14407
|
+
} else {
|
|
14408
|
+
const emailMatch = whoamiOut.match(/associated with the email\s+(\S+)/i);
|
|
14409
|
+
const tableMatch = whoamiOut.match(/│\s*([^│]+?)\s*│\s*[a-f0-9]{32}\s*│/);
|
|
14410
|
+
const accountLabel = emailMatch?.[1] ?? tableMatch?.[1]?.trim() ?? "authenticated";
|
|
14411
|
+
s.stop(`Logged in as ${pc3.cyan(accountLabel)}`);
|
|
14412
|
+
}
|
|
14413
|
+
s.start("Fetching account ID");
|
|
14414
|
+
const accountId = extractAccountId(wrangler, cwd);
|
|
14415
|
+
if (!accountId) {
|
|
14416
|
+
s.stop(`${pc3.red("\u2717")} Could not determine account ID`);
|
|
14417
|
+
p5.log.info(
|
|
14418
|
+
`You can find it at: ${pc3.cyan("https://dash.cloudflare.com/?to=/:account/r2")}`
|
|
14419
|
+
);
|
|
14420
|
+
process.exit(1);
|
|
14421
|
+
}
|
|
14422
|
+
s.stop(`Account ID: ${pc3.dim(accountId)}`);
|
|
14423
|
+
let bucketName = options.bucket;
|
|
14424
|
+
if (!bucketName) {
|
|
14425
|
+
const result = await p5.text({
|
|
14426
|
+
message: "R2 bucket name",
|
|
14427
|
+
placeholder: "betterstart-uploads",
|
|
14428
|
+
defaultValue: "betterstart-uploads",
|
|
14429
|
+
validate: (v) => {
|
|
14430
|
+
if (!v || v.length < 3) return "Bucket name must be at least 3 characters";
|
|
14431
|
+
if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(v))
|
|
14432
|
+
return "Bucket name must be lowercase alphanumeric with hyphens, no leading/trailing hyphens";
|
|
14433
|
+
}
|
|
14434
|
+
});
|
|
14435
|
+
if (p5.isCancel(result)) {
|
|
14436
|
+
p5.cancel("Setup cancelled.");
|
|
14437
|
+
process.exit(0);
|
|
14438
|
+
}
|
|
14439
|
+
bucketName = result;
|
|
14440
|
+
}
|
|
14441
|
+
s.start(`Creating R2 bucket: ${bucketName}`);
|
|
14442
|
+
const createResult = runWrangler(wrangler, ["r2", "bucket", "create", bucketName], {
|
|
14443
|
+
cwd,
|
|
14444
|
+
timeout: 3e4
|
|
14445
|
+
});
|
|
14446
|
+
const createOut = (createResult.stdout?.toString() ?? "") + (createResult.stderr?.toString() ?? "");
|
|
14447
|
+
if (createResult.status !== 0) {
|
|
14448
|
+
if (createOut.includes("already exists") || createOut.includes("AlreadyExists")) {
|
|
14449
|
+
s.stop(`Bucket ${pc3.cyan(bucketName)} already exists \u2014 using it`);
|
|
14450
|
+
} else {
|
|
14451
|
+
s.stop("Failed to create bucket");
|
|
14452
|
+
p5.log.error(createOut.trim() || "Unknown error from wrangler");
|
|
14453
|
+
process.exit(1);
|
|
14454
|
+
}
|
|
14455
|
+
} else {
|
|
14456
|
+
s.stop(`Created bucket: ${pc3.cyan(bucketName)}`);
|
|
14457
|
+
}
|
|
14458
|
+
let publicUrl = "";
|
|
14459
|
+
const oauthToken = readWranglerToken();
|
|
14460
|
+
if (oauthToken) {
|
|
14461
|
+
s.start("Enabling public r2.dev URL");
|
|
14462
|
+
const domainResult = await enablePublicDomain(accountId, bucketName, oauthToken);
|
|
14463
|
+
if (domainResult.success && domainResult.domain) {
|
|
14464
|
+
publicUrl = `https://${domainResult.domain}`;
|
|
14465
|
+
s.stop(`Public URL: ${pc3.cyan(publicUrl)}`);
|
|
14466
|
+
} else {
|
|
14467
|
+
s.stop("Could not enable public URL automatically");
|
|
14468
|
+
p5.log.warning(domainResult.error ?? "Unknown error");
|
|
14469
|
+
p5.log.info(
|
|
14470
|
+
`You can enable it manually in the dashboard:
|
|
14471
|
+
${pc3.cyan(`https://dash.cloudflare.com/${accountId}/r2/default/buckets/${bucketName}/settings`)}`
|
|
14472
|
+
);
|
|
14473
|
+
}
|
|
14474
|
+
} else {
|
|
14475
|
+
p5.log.warning("Could not read wrangler OAuth token \u2014 skipping public URL setup");
|
|
14476
|
+
p5.log.info(
|
|
14477
|
+
`Enable it manually: ${pc3.cyan(`https://dash.cloudflare.com/${accountId}/r2/default/buckets/${bucketName}/settings`)}`
|
|
14478
|
+
);
|
|
14479
|
+
}
|
|
14480
|
+
p5.note(
|
|
14481
|
+
[
|
|
14482
|
+
`Create an R2 API token with ${pc3.bold("Object Read & Write")} permission.`,
|
|
14483
|
+
"",
|
|
14484
|
+
`Dashboard: ${pc3.cyan(`https://dash.cloudflare.com/${accountId}/r2/api-tokens`)}`,
|
|
14485
|
+
"",
|
|
14486
|
+
pc3.dim("The dashboard will give you an Access Key ID and Secret Access Key.")
|
|
14487
|
+
].join("\n"),
|
|
14488
|
+
"Create R2 API Token"
|
|
14489
|
+
);
|
|
14490
|
+
const openDashboard = await p5.confirm({
|
|
14491
|
+
message: "Open the R2 API tokens page in your browser?",
|
|
14492
|
+
initialValue: true
|
|
14493
|
+
});
|
|
14494
|
+
if (!p5.isCancel(openDashboard) && openDashboard) {
|
|
14495
|
+
const url = `https://dash.cloudflare.com/${accountId}/r2/api-tokens`;
|
|
14496
|
+
try {
|
|
14497
|
+
execFileSync5("open", [url], { stdio: "pipe", timeout: 5e3 });
|
|
14498
|
+
} catch {
|
|
14499
|
+
p5.log.warning(`Could not open browser. Visit: ${pc3.cyan(url)}`);
|
|
14500
|
+
}
|
|
14501
|
+
}
|
|
14502
|
+
const credentials = await p5.group(
|
|
14503
|
+
{
|
|
14504
|
+
accessKeyId: () => p5.text({
|
|
14505
|
+
message: "R2 Access Key ID",
|
|
14506
|
+
placeholder: "Paste from Cloudflare dashboard",
|
|
14507
|
+
validate: (v) => {
|
|
14508
|
+
if (!v || v.trim().length < 10) return "Please paste a valid Access Key ID";
|
|
14509
|
+
}
|
|
14510
|
+
}),
|
|
14511
|
+
secretAccessKey: () => p5.password({
|
|
14512
|
+
message: "R2 Secret Access Key",
|
|
14513
|
+
validate: (v) => {
|
|
14514
|
+
if (!v || v.trim().length < 10) return "Please paste a valid Secret Access Key";
|
|
14515
|
+
}
|
|
14516
|
+
})
|
|
14517
|
+
},
|
|
14518
|
+
{
|
|
14519
|
+
onCancel: () => {
|
|
14520
|
+
p5.cancel("Setup cancelled.");
|
|
14521
|
+
process.exit(0);
|
|
14522
|
+
}
|
|
14523
|
+
}
|
|
14524
|
+
);
|
|
14525
|
+
s.start("Writing environment variables");
|
|
14526
|
+
const envResult = appendEnvVars(cwd, [
|
|
14527
|
+
{
|
|
14528
|
+
header: "Storage (Cloudflare R2)",
|
|
14529
|
+
vars: [
|
|
14530
|
+
{ key: "BETTERSTART_R2_ACCOUNT_ID", value: accountId },
|
|
14531
|
+
{ key: "BETTERSTART_R2_ACCESS_KEY_ID", value: credentials.accessKeyId.trim() },
|
|
14532
|
+
{ key: "BETTERSTART_R2_SECRET_ACCESS_KEY", value: credentials.secretAccessKey.trim() },
|
|
14533
|
+
{ key: "BETTERSTART_R2_BUCKET_NAME", value: bucketName },
|
|
14534
|
+
...publicUrl ? [{ key: "BETTERSTART_R2_PUBLIC_URL", value: publicUrl }] : []
|
|
14535
|
+
]
|
|
14536
|
+
}
|
|
14537
|
+
], /* @__PURE__ */ new Set([
|
|
14538
|
+
"BETTERSTART_R2_ACCOUNT_ID",
|
|
14539
|
+
"BETTERSTART_R2_ACCESS_KEY_ID",
|
|
14540
|
+
"BETTERSTART_R2_SECRET_ACCESS_KEY",
|
|
14541
|
+
"BETTERSTART_R2_BUCKET_NAME",
|
|
14542
|
+
"BETTERSTART_R2_PUBLIC_URL"
|
|
14543
|
+
]));
|
|
14544
|
+
const totalChanged = envResult.added.length + envResult.updated.length;
|
|
14545
|
+
if (totalChanged > 0) {
|
|
14546
|
+
s.stop(`Updated .env.local ${pc3.dim(`(${envResult.added.length} added, ${envResult.updated.length} updated)`)}`);
|
|
14547
|
+
} else {
|
|
14548
|
+
s.stop("All R2 env vars already set in .env.local");
|
|
14549
|
+
}
|
|
14550
|
+
const summaryLines = [
|
|
14551
|
+
`Bucket: ${pc3.cyan(bucketName)}`,
|
|
14552
|
+
`Account: ${pc3.dim(accountId)}`,
|
|
14553
|
+
`Access Key: ${pc3.dim(credentials.accessKeyId.trim().slice(0, 8) + "...")}`
|
|
14554
|
+
];
|
|
14555
|
+
if (publicUrl) {
|
|
14556
|
+
summaryLines.push(`Public URL: ${pc3.cyan(publicUrl)}`);
|
|
14557
|
+
}
|
|
14558
|
+
summaryLines.push(`Env file: ${pc3.dim(".env.local")}`);
|
|
14559
|
+
p5.note(summaryLines.join("\n"), pc3.green("R2 storage configured"));
|
|
14560
|
+
p5.outro("Done! Your CMS can now upload files to R2.");
|
|
14561
|
+
});
|
|
14562
|
+
function findWrangler(cwd) {
|
|
14563
|
+
const localBin = path39.join(cwd, "node_modules", ".bin", "wrangler");
|
|
14564
|
+
if (fs34.existsSync(localBin)) return { bin: localBin, prefix: [] };
|
|
14565
|
+
const result = spawnSync("which", ["wrangler"], { stdio: "pipe", timeout: 5e3 });
|
|
14566
|
+
if (result.status === 0) {
|
|
14567
|
+
const found = result.stdout?.toString().trim();
|
|
14568
|
+
if (found) return { bin: found, prefix: [] };
|
|
14569
|
+
}
|
|
14570
|
+
const npxResult = spawnSync("npx", ["wrangler", "--version"], {
|
|
14571
|
+
stdio: "pipe",
|
|
14572
|
+
timeout: 15e3
|
|
14573
|
+
});
|
|
14574
|
+
if (npxResult.status === 0) return { bin: "npx", prefix: ["wrangler"] };
|
|
14575
|
+
return null;
|
|
14576
|
+
}
|
|
14577
|
+
function runWrangler(ref, args, opts) {
|
|
14578
|
+
const fullArgs = [...ref.prefix, ...args];
|
|
14579
|
+
return spawnSync(ref.bin, fullArgs, {
|
|
14580
|
+
cwd: opts.cwd,
|
|
14581
|
+
stdio: opts.stdio ?? "pipe",
|
|
14582
|
+
timeout: opts.timeout ?? 15e3
|
|
14583
|
+
});
|
|
14584
|
+
}
|
|
14585
|
+
function extractAccountId(ref, cwd) {
|
|
14586
|
+
const result = runWrangler(ref, ["whoami"], { cwd });
|
|
14587
|
+
const output = result.stdout?.toString() ?? "";
|
|
14588
|
+
const idMatch = output.match(/Account ID[:\s]+([a-f0-9]{32})/i);
|
|
14589
|
+
if (idMatch) return idMatch[1];
|
|
14590
|
+
const hexMatch = output.match(/\b([a-f0-9]{32})\b/);
|
|
14591
|
+
if (hexMatch) return hexMatch[1];
|
|
14592
|
+
return null;
|
|
14593
|
+
}
|
|
14594
|
+
function readWranglerToken() {
|
|
14595
|
+
const candidates = [
|
|
14596
|
+
path39.join(os.homedir(), "Library", "Preferences", ".wrangler", "config", "default.toml"),
|
|
14597
|
+
// macOS
|
|
14598
|
+
path39.join(os.homedir(), ".config", ".wrangler", "config", "default.toml"),
|
|
14599
|
+
// Linux
|
|
14600
|
+
path39.join(os.homedir(), ".wrangler", "config", "default.toml")
|
|
14601
|
+
// fallback
|
|
14602
|
+
];
|
|
14603
|
+
if (process.env.WRANGLER_CONFIG_PATH) {
|
|
14604
|
+
candidates.unshift(process.env.WRANGLER_CONFIG_PATH);
|
|
14605
|
+
}
|
|
14606
|
+
if (process.env.XDG_CONFIG_HOME) {
|
|
14607
|
+
candidates.unshift(
|
|
14608
|
+
path39.join(process.env.XDG_CONFIG_HOME, ".wrangler", "config", "default.toml")
|
|
14609
|
+
);
|
|
14610
|
+
}
|
|
14611
|
+
for (const configPath of candidates) {
|
|
14612
|
+
if (!fs34.existsSync(configPath)) continue;
|
|
14613
|
+
try {
|
|
14614
|
+
const content = fs34.readFileSync(configPath, "utf-8");
|
|
14615
|
+
const match = content.match(/^oauth_token\s*=\s*"([^"]+)"/m);
|
|
14616
|
+
if (match) return match[1];
|
|
14617
|
+
} catch {
|
|
14618
|
+
continue;
|
|
14619
|
+
}
|
|
14620
|
+
}
|
|
14621
|
+
return null;
|
|
14622
|
+
}
|
|
14623
|
+
async function enablePublicDomain(accountId, bucketName, token) {
|
|
14624
|
+
try {
|
|
14625
|
+
const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/r2/buckets/${bucketName}/domains/managed`;
|
|
14626
|
+
const res = await fetch(url, {
|
|
14627
|
+
method: "PUT",
|
|
14628
|
+
headers: {
|
|
14629
|
+
Authorization: `Bearer ${token}`,
|
|
14630
|
+
"Content-Type": "application/json"
|
|
14631
|
+
},
|
|
14632
|
+
body: JSON.stringify({ enabled: true })
|
|
14633
|
+
});
|
|
14634
|
+
const data = await res.json();
|
|
14635
|
+
if (data.success && data.result?.domain) {
|
|
14636
|
+
return { success: true, domain: data.result.domain };
|
|
14637
|
+
}
|
|
14638
|
+
const errMsg = data.errors?.[0]?.message ?? "API returned success=false";
|
|
14639
|
+
return { success: false, error: errMsg };
|
|
14640
|
+
} catch (err) {
|
|
14641
|
+
return { success: false, error: err instanceof Error ? err.message : "fetch failed" };
|
|
14642
|
+
}
|
|
14643
|
+
}
|
|
14644
|
+
|
|
14645
|
+
// src/commands/uninstall.ts
|
|
14646
|
+
import fs36 from "fs";
|
|
14647
|
+
import path40 from "path";
|
|
14648
|
+
import * as p6 from "@clack/prompts";
|
|
14649
|
+
import { Command as Command6 } from "commander";
|
|
14650
|
+
import pc4 from "picocolors";
|
|
14328
14651
|
|
|
14329
14652
|
// src/commands/uninstall-cleaners.ts
|
|
14330
|
-
import
|
|
14653
|
+
import fs35 from "fs";
|
|
14331
14654
|
function stripJsonComments2(input) {
|
|
14332
14655
|
let result = "";
|
|
14333
14656
|
let i = 0;
|
|
@@ -14361,8 +14684,8 @@ function stripJsonComments2(input) {
|
|
|
14361
14684
|
return result;
|
|
14362
14685
|
}
|
|
14363
14686
|
function cleanTsconfig(tsconfigPath) {
|
|
14364
|
-
if (!
|
|
14365
|
-
const raw =
|
|
14687
|
+
if (!fs35.existsSync(tsconfigPath)) return [];
|
|
14688
|
+
const raw = fs35.readFileSync(tsconfigPath, "utf-8");
|
|
14366
14689
|
const stripped = stripJsonComments2(raw).replace(/,\s*([\]}])/g, "$1");
|
|
14367
14690
|
let tsconfig;
|
|
14368
14691
|
try {
|
|
@@ -14386,13 +14709,13 @@ function cleanTsconfig(tsconfigPath) {
|
|
|
14386
14709
|
compilerOptions.paths = paths;
|
|
14387
14710
|
}
|
|
14388
14711
|
tsconfig.compilerOptions = compilerOptions;
|
|
14389
|
-
|
|
14712
|
+
fs35.writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
|
|
14390
14713
|
`, "utf-8");
|
|
14391
14714
|
return removed;
|
|
14392
14715
|
}
|
|
14393
14716
|
function cleanCss(cssPath) {
|
|
14394
|
-
if (!
|
|
14395
|
-
const content =
|
|
14717
|
+
if (!fs35.existsSync(cssPath)) return [];
|
|
14718
|
+
const content = fs35.readFileSync(cssPath, "utf-8");
|
|
14396
14719
|
const lines = content.split("\n");
|
|
14397
14720
|
const sourcePattern = /^@source\s+"[^"]*cms[^"]*";\s*$/;
|
|
14398
14721
|
const removed = [];
|
|
@@ -14406,12 +14729,12 @@ function cleanCss(cssPath) {
|
|
|
14406
14729
|
}
|
|
14407
14730
|
if (removed.length === 0) return [];
|
|
14408
14731
|
const cleaned = kept.join("\n").replace(/\n{3,}/g, "\n\n");
|
|
14409
|
-
|
|
14732
|
+
fs35.writeFileSync(cssPath, cleaned, "utf-8");
|
|
14410
14733
|
return removed;
|
|
14411
14734
|
}
|
|
14412
14735
|
function cleanEnvFile(envPath) {
|
|
14413
|
-
if (!
|
|
14414
|
-
const content =
|
|
14736
|
+
if (!fs35.existsSync(envPath)) return [];
|
|
14737
|
+
const content = fs35.readFileSync(envPath, "utf-8");
|
|
14415
14738
|
const lines = content.split("\n");
|
|
14416
14739
|
const removed = [];
|
|
14417
14740
|
const kept = [];
|
|
@@ -14444,9 +14767,9 @@ function cleanEnvFile(envPath) {
|
|
|
14444
14767
|
if (removed.length === 0) return [];
|
|
14445
14768
|
const result = kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
14446
14769
|
if (result === "") {
|
|
14447
|
-
|
|
14770
|
+
fs35.unlinkSync(envPath);
|
|
14448
14771
|
} else {
|
|
14449
|
-
|
|
14772
|
+
fs35.writeFileSync(envPath, `${result}
|
|
14450
14773
|
`, "utf-8");
|
|
14451
14774
|
}
|
|
14452
14775
|
return removed;
|
|
@@ -14472,15 +14795,15 @@ function findMainCss2(cwd) {
|
|
|
14472
14795
|
"globals.css"
|
|
14473
14796
|
];
|
|
14474
14797
|
for (const candidate of candidates) {
|
|
14475
|
-
const filePath =
|
|
14476
|
-
if (
|
|
14798
|
+
const filePath = path40.join(cwd, candidate);
|
|
14799
|
+
if (fs36.existsSync(filePath)) return filePath;
|
|
14477
14800
|
}
|
|
14478
14801
|
return void 0;
|
|
14479
14802
|
}
|
|
14480
14803
|
function isCLICreatedBiome(biomePath) {
|
|
14481
|
-
if (!
|
|
14804
|
+
if (!fs36.existsSync(biomePath)) return false;
|
|
14482
14805
|
try {
|
|
14483
|
-
const content = JSON.parse(
|
|
14806
|
+
const content = JSON.parse(fs36.readFileSync(biomePath, "utf-8"));
|
|
14484
14807
|
return content.$schema?.includes("biomejs.dev") && content.formatter?.indentStyle === "space" && content.javascript?.formatter?.quoteStyle === "single" && Array.isArray(content.files?.ignore) && content.files.ignore.includes(".next");
|
|
14485
14808
|
} catch {
|
|
14486
14809
|
return false;
|
|
@@ -14488,13 +14811,13 @@ function isCLICreatedBiome(biomePath) {
|
|
|
14488
14811
|
}
|
|
14489
14812
|
function buildUninstallPlan(cwd) {
|
|
14490
14813
|
const steps = [];
|
|
14491
|
-
const hasSrc =
|
|
14814
|
+
const hasSrc = fs36.existsSync(path40.join(cwd, "src"));
|
|
14492
14815
|
const appBase = hasSrc ? "src/app" : "app";
|
|
14493
14816
|
const dirs = [];
|
|
14494
|
-
const cmsDir =
|
|
14495
|
-
const cmsRouteGroup =
|
|
14496
|
-
if (
|
|
14497
|
-
if (
|
|
14817
|
+
const cmsDir = path40.join(cwd, "cms");
|
|
14818
|
+
const cmsRouteGroup = path40.join(cwd, appBase, "(cms)");
|
|
14819
|
+
if (fs36.existsSync(cmsDir)) dirs.push("cms/");
|
|
14820
|
+
if (fs36.existsSync(cmsRouteGroup)) dirs.push(`${appBase}/(cms)/`);
|
|
14498
14821
|
if (dirs.length > 0) {
|
|
14499
14822
|
steps.push({
|
|
14500
14823
|
label: "CMS directories",
|
|
@@ -14502,25 +14825,25 @@ function buildUninstallPlan(cwd) {
|
|
|
14502
14825
|
count: dirs.length,
|
|
14503
14826
|
unit: dirs.length === 1 ? "directory" : "directories",
|
|
14504
14827
|
execute() {
|
|
14505
|
-
if (
|
|
14506
|
-
if (
|
|
14828
|
+
if (fs36.existsSync(cmsDir)) fs36.rmSync(cmsDir, { recursive: true, force: true });
|
|
14829
|
+
if (fs36.existsSync(cmsRouteGroup)) fs36.rmSync(cmsRouteGroup, { recursive: true, force: true });
|
|
14507
14830
|
}
|
|
14508
14831
|
});
|
|
14509
14832
|
}
|
|
14510
14833
|
const configFiles = [];
|
|
14511
14834
|
const configPaths = [];
|
|
14512
14835
|
const candidates = [
|
|
14513
|
-
["cms.config.ts",
|
|
14514
|
-
["drizzle.config.ts",
|
|
14515
|
-
["CMS.md",
|
|
14836
|
+
["cms.config.ts", path40.join(cwd, "cms.config.ts")],
|
|
14837
|
+
["drizzle.config.ts", path40.join(cwd, "drizzle.config.ts")],
|
|
14838
|
+
["CMS.md", path40.join(cwd, "CMS.md")]
|
|
14516
14839
|
];
|
|
14517
14840
|
for (const [label, fullPath] of candidates) {
|
|
14518
|
-
if (
|
|
14841
|
+
if (fs36.existsSync(fullPath)) {
|
|
14519
14842
|
configFiles.push(label);
|
|
14520
14843
|
configPaths.push(fullPath);
|
|
14521
14844
|
}
|
|
14522
14845
|
}
|
|
14523
|
-
const biomePath =
|
|
14846
|
+
const biomePath = path40.join(cwd, "biome.json");
|
|
14524
14847
|
if (isCLICreatedBiome(biomePath)) {
|
|
14525
14848
|
configFiles.push("biome.json (CLI-created)");
|
|
14526
14849
|
configPaths.push(biomePath);
|
|
@@ -14532,15 +14855,15 @@ function buildUninstallPlan(cwd) {
|
|
|
14532
14855
|
count: configFiles.length,
|
|
14533
14856
|
unit: configFiles.length === 1 ? "file" : "files",
|
|
14534
14857
|
execute() {
|
|
14535
|
-
for (const
|
|
14536
|
-
if (
|
|
14858
|
+
for (const p7 of configPaths) {
|
|
14859
|
+
if (fs36.existsSync(p7)) fs36.unlinkSync(p7);
|
|
14537
14860
|
}
|
|
14538
14861
|
}
|
|
14539
14862
|
});
|
|
14540
14863
|
}
|
|
14541
|
-
const tsconfigPath =
|
|
14542
|
-
if (
|
|
14543
|
-
const content =
|
|
14864
|
+
const tsconfigPath = path40.join(cwd, "tsconfig.json");
|
|
14865
|
+
if (fs36.existsSync(tsconfigPath)) {
|
|
14866
|
+
const content = fs36.readFileSync(tsconfigPath, "utf-8");
|
|
14544
14867
|
const aliasMatches = content.match(/"@cms\//g);
|
|
14545
14868
|
if (aliasMatches && aliasMatches.length > 0) {
|
|
14546
14869
|
const aliasCount = aliasMatches.length;
|
|
@@ -14557,10 +14880,10 @@ function buildUninstallPlan(cwd) {
|
|
|
14557
14880
|
}
|
|
14558
14881
|
const cssFile = findMainCss2(cwd);
|
|
14559
14882
|
if (cssFile) {
|
|
14560
|
-
const cssContent =
|
|
14883
|
+
const cssContent = fs36.readFileSync(cssFile, "utf-8");
|
|
14561
14884
|
const sourceLines = cssContent.split("\n").filter((l) => /^@source\s+"[^"]*cms[^"]*";\s*$/.test(l));
|
|
14562
14885
|
if (sourceLines.length > 0) {
|
|
14563
|
-
const relCss =
|
|
14886
|
+
const relCss = path40.relative(cwd, cssFile);
|
|
14564
14887
|
steps.push({
|
|
14565
14888
|
label: `CSS @source lines (${relCss})`,
|
|
14566
14889
|
items: [`@source lines in ${relCss}`],
|
|
@@ -14572,9 +14895,9 @@ function buildUninstallPlan(cwd) {
|
|
|
14572
14895
|
});
|
|
14573
14896
|
}
|
|
14574
14897
|
}
|
|
14575
|
-
const envPath =
|
|
14576
|
-
if (
|
|
14577
|
-
const envContent =
|
|
14898
|
+
const envPath = path40.join(cwd, ".env.local");
|
|
14899
|
+
if (fs36.existsSync(envPath)) {
|
|
14900
|
+
const envContent = fs36.readFileSync(envPath, "utf-8");
|
|
14578
14901
|
const bsVars = envContent.split("\n").filter((l) => l.trim().match(/^BETTERSTART_\w+=/)).map((l) => l.split("=")[0]);
|
|
14579
14902
|
if (bsVars.length > 0) {
|
|
14580
14903
|
steps.push({
|
|
@@ -14590,32 +14913,32 @@ function buildUninstallPlan(cwd) {
|
|
|
14590
14913
|
}
|
|
14591
14914
|
return steps;
|
|
14592
14915
|
}
|
|
14593
|
-
var uninstallCommand = new
|
|
14594
|
-
const cwd = options.cwd ?
|
|
14595
|
-
|
|
14916
|
+
var uninstallCommand = new Command6("uninstall").description("Remove all CMS files and undo modifications made by betterstart init").option("-f, --force", "Skip all confirmation prompts", false).option("--cwd <path>", "Project root path").action(async (options) => {
|
|
14917
|
+
const cwd = options.cwd ? path40.resolve(options.cwd) : process.cwd();
|
|
14918
|
+
p6.intro(pc4.bgRed(pc4.white(" BetterStart Uninstall ")));
|
|
14596
14919
|
const steps = buildUninstallPlan(cwd);
|
|
14597
14920
|
if (steps.length === 0) {
|
|
14598
|
-
|
|
14599
|
-
|
|
14921
|
+
p6.log.success(`${pc4.green("\u2713")} Nothing to remove \u2014 project is already clean.`);
|
|
14922
|
+
p6.outro("Done");
|
|
14600
14923
|
return;
|
|
14601
14924
|
}
|
|
14602
14925
|
const planLines = steps.map((step) => {
|
|
14603
14926
|
const names = step.items.join(" ");
|
|
14604
|
-
const countLabel =
|
|
14605
|
-
return `${
|
|
14927
|
+
const countLabel = pc4.dim(`${step.count} ${step.unit}`);
|
|
14928
|
+
return `${pc4.red("\xD7")} ${names} ${countLabel}`;
|
|
14606
14929
|
});
|
|
14607
|
-
|
|
14930
|
+
p6.note(planLines.join("\n"), "Uninstall plan");
|
|
14608
14931
|
if (!options.force) {
|
|
14609
|
-
const confirmed = await
|
|
14932
|
+
const confirmed = await p6.confirm({
|
|
14610
14933
|
message: "Proceed with uninstall?",
|
|
14611
14934
|
initialValue: false
|
|
14612
14935
|
});
|
|
14613
|
-
if (
|
|
14614
|
-
|
|
14936
|
+
if (p6.isCancel(confirmed) || !confirmed) {
|
|
14937
|
+
p6.cancel("Uninstall cancelled.");
|
|
14615
14938
|
process.exit(0);
|
|
14616
14939
|
}
|
|
14617
14940
|
}
|
|
14618
|
-
const s =
|
|
14941
|
+
const s = p6.spinner();
|
|
14619
14942
|
s.start(steps[0].label);
|
|
14620
14943
|
for (const step of steps) {
|
|
14621
14944
|
s.message(step.label);
|
|
@@ -14623,16 +14946,16 @@ var uninstallCommand = new Command5("uninstall").description("Remove all CMS fil
|
|
|
14623
14946
|
}
|
|
14624
14947
|
const parts = steps.map((step) => `${step.count} ${step.unit}`);
|
|
14625
14948
|
s.stop(`Removed ${parts.join(", ")}`);
|
|
14626
|
-
|
|
14627
|
-
|
|
14949
|
+
p6.note(pc4.dim("Database tables were NOT dropped \u2014 drop them manually if needed."), "Next steps");
|
|
14950
|
+
p6.outro("Uninstall complete");
|
|
14628
14951
|
});
|
|
14629
14952
|
|
|
14630
14953
|
// src/commands/update-deps.ts
|
|
14631
|
-
import
|
|
14954
|
+
import path41 from "path";
|
|
14632
14955
|
import * as clack2 from "@clack/prompts";
|
|
14633
|
-
import { Command as
|
|
14634
|
-
var updateDepsCommand = new
|
|
14635
|
-
const cwd = options.cwd ?
|
|
14956
|
+
import { Command as Command7 } from "commander";
|
|
14957
|
+
var updateDepsCommand = new Command7("update-deps").description("Install or update all CMS dependencies").option("--cwd <path>", "Project root path").action(async (options) => {
|
|
14958
|
+
const cwd = options.cwd ? path41.resolve(options.cwd) : process.cwd();
|
|
14636
14959
|
clack2.intro("BetterStart Update Dependencies");
|
|
14637
14960
|
const pm = detectPackageManager(cwd);
|
|
14638
14961
|
clack2.log.info(`Package manager: ${pm}`);
|
|
@@ -14657,32 +14980,33 @@ var updateDepsCommand = new Command6("update-deps").description("Install or upda
|
|
|
14657
14980
|
});
|
|
14658
14981
|
|
|
14659
14982
|
// src/commands/update-styles.ts
|
|
14660
|
-
import
|
|
14661
|
-
import
|
|
14983
|
+
import fs37 from "fs";
|
|
14984
|
+
import path42 from "path";
|
|
14662
14985
|
import * as clack3 from "@clack/prompts";
|
|
14663
|
-
import { Command as
|
|
14664
|
-
var updateStylesCommand = new
|
|
14665
|
-
const cwd = options.cwd ?
|
|
14986
|
+
import { Command as Command8 } from "commander";
|
|
14987
|
+
var updateStylesCommand = new Command8("update-styles").description("Replace cms-globals.css with the latest version from the CLI").option("--cwd <path>", "Project root path").action(async (options) => {
|
|
14988
|
+
const cwd = options.cwd ? path42.resolve(options.cwd) : process.cwd();
|
|
14666
14989
|
clack3.intro("BetterStart Update Styles");
|
|
14667
14990
|
const config = await resolveConfig(cwd);
|
|
14668
14991
|
const cmsDir = config.paths?.cms ?? "./cms";
|
|
14669
|
-
const targetPath =
|
|
14670
|
-
if (!
|
|
14671
|
-
clack3.cancel(`cms-globals.css not found at ${
|
|
14992
|
+
const targetPath = path42.join(cwd, cmsDir, "cms-globals.css");
|
|
14993
|
+
if (!fs37.existsSync(targetPath)) {
|
|
14994
|
+
clack3.cancel(`cms-globals.css not found at ${path42.relative(cwd, targetPath)}`);
|
|
14672
14995
|
process.exit(1);
|
|
14673
14996
|
}
|
|
14674
|
-
|
|
14675
|
-
clack3.log.success(`Updated ${
|
|
14997
|
+
fs37.writeFileSync(targetPath, cmsGlobalsCssTemplate(), "utf-8");
|
|
14998
|
+
clack3.log.success(`Updated ${path42.relative(cwd, targetPath)}`);
|
|
14676
14999
|
clack3.outro("Styles updated");
|
|
14677
15000
|
});
|
|
14678
15001
|
|
|
14679
15002
|
// src/cli.ts
|
|
14680
|
-
var program = new
|
|
15003
|
+
var program = new Command9();
|
|
14681
15004
|
program.name("betterstart").description("Scaffold a full-featured CMS into any Next.js 16 application").version("0.1.0");
|
|
14682
15005
|
program.addCommand(initCommand);
|
|
14683
15006
|
program.addCommand(generateCommand);
|
|
14684
15007
|
program.addCommand(removeCommand);
|
|
14685
15008
|
program.addCommand(seedCommand);
|
|
15009
|
+
program.addCommand(setupR2Command);
|
|
14686
15010
|
program.addCommand(uninstallCommand);
|
|
14687
15011
|
program.addCommand(updateDepsCommand);
|
|
14688
15012
|
program.addCommand(updateStylesCommand);
|