@betterstart/cli 0.1.25 → 0.1.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 CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-SAPJG4NO.js";
5
5
 
6
6
  // src/cli.ts
7
- import { Command as Command7 } from "commander";
7
+ import { Command as Command8 } 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 p5 = toPascalCase3(str);
905
- return p5.charAt(0).toLowerCase() + p5.slice(1);
904
+ const p6 = toPascalCase3(str);
905
+ return p6.charAt(0).toLowerCase() + p6.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 = (p5) => path4.relative(cwd, p5);
996
+ const rel = (p6) => path4.relative(cwd, p6);
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");
@@ -1981,8 +1981,8 @@ function toPascalCase4(str) {
1981
1981
  return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
1982
1982
  }
1983
1983
  function toCamelCase2(str) {
1984
- const p5 = toPascalCase4(str);
1985
- return p5.charAt(0).toLowerCase() + p5.slice(1);
1984
+ const p6 = toPascalCase4(str);
1985
+ return p6.charAt(0).toLowerCase() + p6.slice(1);
1986
1986
  }
1987
1987
  function toKebabCase3(str) {
1988
1988
  return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
@@ -2697,8 +2697,8 @@ function toPascalCase5(str) {
2697
2697
  return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
2698
2698
  }
2699
2699
  function toCamelCase3(str) {
2700
- const p5 = toPascalCase5(str);
2701
- return p5.charAt(0).toLowerCase() + p5.slice(1);
2700
+ const p6 = toPascalCase5(str);
2701
+ return p6.charAt(0).toLowerCase() + p6.slice(1);
2702
2702
  }
2703
2703
  function singularize2(str) {
2704
2704
  if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
@@ -2758,11 +2758,11 @@ function generateActions(schema, cwd, actionsDir, options = {}) {
2758
2758
  const listFieldsWithRels = findListFieldsWithRelationships(dbFields);
2759
2759
  const hasListRels = listFieldsWithRels.length > 0;
2760
2760
  const allListRelQueries = [];
2761
- for (const { field: listField, path: path41 } of listFieldsWithRels) {
2761
+ for (const { field: listField, path: path42 } of listFieldsWithRels) {
2762
2762
  const rels = (listField.fields || []).filter((f) => f.type === "relationship" && f.relationship);
2763
2763
  for (const relField of rels) {
2764
2764
  allListRelQueries.push({
2765
- fieldPath: path41.join("_"),
2765
+ fieldPath: path42.join("_"),
2766
2766
  relField,
2767
2767
  relTable: toCamelCase3(relField.relationship),
2768
2768
  listFieldName: listField.name
@@ -3421,8 +3421,8 @@ function toPascalCase6(str) {
3421
3421
  return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
3422
3422
  }
3423
3423
  function toCamelCase4(str) {
3424
- const p5 = toPascalCase6(str);
3425
- return p5.charAt(0).toLowerCase() + p5.slice(1);
3424
+ const p6 = toPascalCase6(str);
3425
+ return p6.charAt(0).toLowerCase() + p6.slice(1);
3426
3426
  }
3427
3427
  function singularize3(str) {
3428
3428
  if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
@@ -4564,8 +4564,8 @@ function toPascalCase9(str) {
4564
4564
  return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
4565
4565
  }
4566
4566
  function toCamelCase5(str) {
4567
- const p5 = toPascalCase9(str);
4568
- return p5.charAt(0).toLowerCase() + p5.slice(1);
4567
+ const p6 = toPascalCase9(str);
4568
+ return p6.charAt(0).toLowerCase() + p6.slice(1);
4569
4569
  }
4570
4570
  function singularize6(str) {
4571
4571
  if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
@@ -4813,8 +4813,8 @@ function toPascalCase10(str) {
4813
4813
  return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
4814
4814
  }
4815
4815
  function toCamelCase6(str) {
4816
- const p5 = toPascalCase10(str);
4817
- return p5.charAt(0).toLowerCase() + p5.slice(1);
4816
+ const p6 = toPascalCase10(str);
4817
+ return p6.charAt(0).toLowerCase() + p6.slice(1);
4818
4818
  }
4819
4819
  function singularize7(str) {
4820
4820
  if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
@@ -7045,8 +7045,8 @@ function toPascalCase16(str) {
7045
7045
  return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
7046
7046
  }
7047
7047
  function toCamelCase7(str) {
7048
- const p5 = toPascalCase16(str);
7049
- return p5.charAt(0).toLowerCase() + p5.slice(1);
7048
+ const p6 = toPascalCase16(str);
7049
+ return p6.charAt(0).toLowerCase() + p6.slice(1);
7050
7050
  }
7051
7051
  function singularize12(str) {
7052
7052
  if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
@@ -11675,7 +11675,6 @@ var CORE_DEPS = [
11675
11675
  "input-otp",
11676
11676
  "react-resizable-panels",
11677
11677
  "recharts",
11678
- "shadcn",
11679
11678
  "tw-animate-css",
11680
11679
  "usehooks-ts",
11681
11680
  "vaul"
@@ -12018,6 +12017,7 @@ import { authClient } from '@cms/auth/client'
12018
12017
  import { Button } from '@cms/components/ui/button'
12019
12018
  import { Input } from '@cms/components/ui/input'
12020
12019
  import { Label } from '@cms/components/ui/label'
12020
+ import { LoaderCircle } from 'lucide-react'
12021
12021
  import { useRouter } from 'next/navigation'
12022
12022
  import * as React from 'react'
12023
12023
 
@@ -12090,6 +12090,7 @@ export function LoginForm() {
12090
12090
  </div>
12091
12091
 
12092
12092
  <Button type="submit" className="w-full" size="lg" disabled={isLoading}>
12093
+ {isLoading && <LoaderCircle className="animate-spin" />}
12093
12094
  {isLoading ? 'Signing in...' : 'Sign In'}
12094
12095
  </Button>
12095
12096
  </form>
@@ -13510,9 +13511,32 @@ const auth = betterAuth({
13510
13511
  const EMAIL = process.env.SEED_EMAIL!
13511
13512
  const PASSWORD = process.env.SEED_PASSWORD!
13512
13513
  const NAME = process.env.SEED_NAME || 'Admin'
13514
+ const OVERWRITE = process.env.SEED_OVERWRITE === 'true'
13513
13515
 
13514
13516
  async function main() {
13515
- console.log('\\n Creating admin user...')
13517
+ // Check if user already exists
13518
+ const existing = await db
13519
+ .select({ id: schema.user.id, name: schema.user.name })
13520
+ .from(schema.user)
13521
+ .where(eq(schema.user.email, EMAIL))
13522
+ .then((rows: { id: string; name: string }[]) => rows[0])
13523
+
13524
+ if (existing && !OVERWRITE) {
13525
+ // Exit code 2 signals "user exists" to the CLI
13526
+ console.log(\`EXISTING_USER:\${existing.name}\`)
13527
+ process.exit(2)
13528
+ }
13529
+
13530
+ if (existing && OVERWRITE) {
13531
+ console.log('\\n Replacing existing admin user...')
13532
+ // Remove existing account + session rows first (foreign key refs)
13533
+ await db.delete(schema.session).where(eq(schema.session.userId, existing.id))
13534
+ await db.delete(schema.account).where(eq(schema.account.userId, existing.id))
13535
+ await db.delete(schema.user).where(eq(schema.user.id, existing.id))
13536
+ } else {
13537
+ console.log('\\n Creating admin user...')
13538
+ }
13539
+
13516
13540
  console.log(\` Email: \${EMAIL}\\n\`)
13517
13541
 
13518
13542
  const result = await auth.api.signUpEmail({
@@ -13529,7 +13553,7 @@ async function main() {
13529
13553
  .set({ role: 'admin' })
13530
13554
  .where(eq(schema.user.id, result.user.id))
13531
13555
 
13532
- console.log(\` Admin user created: \${EMAIL}\`)
13556
+ console.log(\` Admin user \${existing ? 'replaced' : 'created'}: \${EMAIL}\`)
13533
13557
  console.log(' Role: admin\\n')
13534
13558
  process.exit(0)
13535
13559
  }
@@ -13587,22 +13611,52 @@ var seedCommand = new Command2("seed").description("Create the initial admin use
13587
13611
  fs31.mkdirSync(scriptsDir, { recursive: true });
13588
13612
  }
13589
13613
  fs31.writeFileSync(seedPath, buildSeedScript(), "utf-8");
13590
- const spinner4 = clack.spinner();
13591
- spinner4.start("Creating admin user...");
13592
- try {
13593
- const { execFileSync: execFileSync5 } = await import("child_process");
13594
- const tsxBin = path36.join(cwd, "node_modules", ".bin", "tsx");
13595
- execFileSync5(tsxBin, [seedPath], {
13614
+ const { execFile } = await import("child_process");
13615
+ const tsxBin = path36.join(cwd, "node_modules", ".bin", "tsx");
13616
+ const runSeed2 = (overwrite) => new Promise((resolve, reject) => {
13617
+ execFile(tsxBin, [seedPath], {
13596
13618
  cwd,
13597
- stdio: "pipe",
13598
13619
  env: {
13599
13620
  ...process.env,
13600
13621
  SEED_EMAIL: email,
13601
13622
  SEED_PASSWORD: password3,
13602
- SEED_NAME: name || "Admin"
13623
+ SEED_NAME: name || "Admin",
13624
+ ...overwrite ? { SEED_OVERWRITE: "true" } : {}
13625
+ }
13626
+ }, (err, stdout, stderr) => {
13627
+ if (err && "code" in err && err.code === 2) {
13628
+ resolve({ code: 2, stdout });
13629
+ } else if (err) {
13630
+ reject(new Error(stderr || err.message));
13631
+ } else {
13632
+ resolve({ code: 0, stdout });
13603
13633
  }
13604
13634
  });
13605
- spinner4.stop("Admin user created");
13635
+ });
13636
+ const spinner4 = clack.spinner();
13637
+ spinner4.start("Creating admin user...");
13638
+ try {
13639
+ const result = await runSeed2(false);
13640
+ if (result.code === 2) {
13641
+ const existingName = result.stdout.split("\n").find((l) => l.startsWith("EXISTING_USER:"))?.replace("EXISTING_USER:", "")?.trim() || "unknown";
13642
+ spinner4.stop(`Account already exists for ${email}`);
13643
+ const overwrite = await clack.confirm({
13644
+ message: `An admin account (${existingName}) already exists with this email. Replace it?`
13645
+ });
13646
+ if (clack.isCancel(overwrite) || !overwrite) {
13647
+ clack.cancel("Seed cancelled.");
13648
+ try {
13649
+ fs31.unlinkSync(seedPath);
13650
+ } catch {
13651
+ }
13652
+ process.exit(0);
13653
+ }
13654
+ spinner4.start("Replacing admin user...");
13655
+ await runSeed2(true);
13656
+ spinner4.stop("Admin user replaced");
13657
+ } else {
13658
+ spinner4.stop("Admin user created");
13659
+ }
13606
13660
  } catch (err) {
13607
13661
  spinner4.stop("Failed to create admin user");
13608
13662
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -14097,8 +14151,8 @@ function toPascalCase17(str) {
14097
14151
  return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
14098
14152
  }
14099
14153
  function toCamelCase8(str) {
14100
- const p5 = toPascalCase17(str);
14101
- return p5.charAt(0).toLowerCase() + p5.slice(1);
14154
+ const p6 = toPascalCase17(str);
14155
+ return p6.charAt(0).toLowerCase() + p6.slice(1);
14102
14156
  }
14103
14157
  function singularize13(str) {
14104
14158
  if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
@@ -14339,33 +14393,345 @@ var updateDepsCommand = new Command5("update-deps").description("Install or upda
14339
14393
  clack2.outro("Dependencies updated");
14340
14394
  });
14341
14395
 
14342
- // src/commands/update-styles.ts
14343
- import fs34 from "fs";
14396
+ // src/commands/uninstall.ts
14397
+ import fs35 from "fs";
14344
14398
  import path40 from "path";
14345
- import * as clack3 from "@clack/prompts";
14399
+ import * as p5 from "@clack/prompts";
14400
+ import pc3 from "picocolors";
14346
14401
  import { Command as Command6 } from "commander";
14347
- var updateStylesCommand = new Command6("update-styles").description("Replace cms-globals.css with the latest version from the CLI").option("--cwd <path>", "Project root path").action(async (options) => {
14402
+
14403
+ // src/commands/uninstall-cleaners.ts
14404
+ import fs34 from "fs";
14405
+ function stripJsonComments2(input) {
14406
+ let result = "";
14407
+ let i = 0;
14408
+ while (i < input.length) {
14409
+ if (input[i] === '"') {
14410
+ let j = i + 1;
14411
+ while (j < input.length) {
14412
+ if (input[j] === "\\") {
14413
+ j += 2;
14414
+ continue;
14415
+ }
14416
+ if (input[j] === '"') {
14417
+ j++;
14418
+ break;
14419
+ }
14420
+ j++;
14421
+ }
14422
+ result += input.slice(i, j);
14423
+ i = j;
14424
+ } else if (input[i] === "/" && input[i + 1] === "/") {
14425
+ const nl = input.indexOf("\n", i);
14426
+ i = nl === -1 ? input.length : nl;
14427
+ } else if (input[i] === "/" && input[i + 1] === "*") {
14428
+ const end = input.indexOf("*/", i + 2);
14429
+ i = end === -1 ? input.length : end + 2;
14430
+ } else {
14431
+ result += input[i];
14432
+ i++;
14433
+ }
14434
+ }
14435
+ return result;
14436
+ }
14437
+ function cleanTsconfig(tsconfigPath) {
14438
+ if (!fs34.existsSync(tsconfigPath)) return [];
14439
+ const raw = fs34.readFileSync(tsconfigPath, "utf-8");
14440
+ const stripped = stripJsonComments2(raw).replace(/,\s*([\]}])/g, "$1");
14441
+ let tsconfig;
14442
+ try {
14443
+ tsconfig = JSON.parse(stripped);
14444
+ } catch {
14445
+ return [];
14446
+ }
14447
+ const compilerOptions = tsconfig.compilerOptions ?? {};
14448
+ const paths = compilerOptions.paths ?? {};
14449
+ const removed = [];
14450
+ for (const key of Object.keys(paths)) {
14451
+ if (key.startsWith("@cms/") || key === "@cms/*") {
14452
+ removed.push(key);
14453
+ delete paths[key];
14454
+ }
14455
+ }
14456
+ if (removed.length === 0) return [];
14457
+ if (Object.keys(paths).length === 0) {
14458
+ delete compilerOptions.paths;
14459
+ } else {
14460
+ compilerOptions.paths = paths;
14461
+ }
14462
+ tsconfig.compilerOptions = compilerOptions;
14463
+ fs34.writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
14464
+ `, "utf-8");
14465
+ return removed;
14466
+ }
14467
+ function cleanCss(cssPath) {
14468
+ if (!fs34.existsSync(cssPath)) return [];
14469
+ const content = fs34.readFileSync(cssPath, "utf-8");
14470
+ const lines = content.split("\n");
14471
+ const sourcePattern = /^@source\s+"[^"]*cms[^"]*";\s*$/;
14472
+ const removed = [];
14473
+ const kept = [];
14474
+ for (const line of lines) {
14475
+ if (sourcePattern.test(line)) {
14476
+ removed.push(line.trim());
14477
+ } else {
14478
+ kept.push(line);
14479
+ }
14480
+ }
14481
+ if (removed.length === 0) return [];
14482
+ const cleaned = kept.join("\n").replace(/\n{3,}/g, "\n\n");
14483
+ fs34.writeFileSync(cssPath, cleaned, "utf-8");
14484
+ return removed;
14485
+ }
14486
+ function cleanEnvFile(envPath) {
14487
+ if (!fs34.existsSync(envPath)) return [];
14488
+ const content = fs34.readFileSync(envPath, "utf-8");
14489
+ const lines = content.split("\n");
14490
+ const removed = [];
14491
+ const kept = [];
14492
+ const headerPattern = /^# =+$/;
14493
+ const headerTextPattern = /^# BetterStart CMS$/;
14494
+ for (let i = 0; i < lines.length; i++) {
14495
+ const line = lines[i];
14496
+ const trimmed = line.trim();
14497
+ if (trimmed.match(/^BETTERSTART_\w+=/)) {
14498
+ const key = trimmed.split("=")[0];
14499
+ removed.push(key);
14500
+ continue;
14501
+ }
14502
+ if (headerPattern.test(trimmed)) {
14503
+ const next = lines[i + 1]?.trim();
14504
+ const afterNext = lines[i + 2]?.trim();
14505
+ if (next && headerTextPattern.test(next) && afterNext && headerPattern.test(afterNext)) {
14506
+ i += 2;
14507
+ continue;
14508
+ }
14509
+ }
14510
+ if (trimmed.startsWith("#") && !headerPattern.test(trimmed)) {
14511
+ const nextNonEmpty = findNextNonEmptyLine(lines, i + 1);
14512
+ if (nextNonEmpty !== null && nextNonEmpty.match(/^BETTERSTART_\w+=/)) {
14513
+ continue;
14514
+ }
14515
+ }
14516
+ kept.push(line);
14517
+ }
14518
+ if (removed.length === 0) return [];
14519
+ let result = kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
14520
+ if (result === "") {
14521
+ fs34.unlinkSync(envPath);
14522
+ } else {
14523
+ fs34.writeFileSync(envPath, `${result}
14524
+ `, "utf-8");
14525
+ }
14526
+ return removed;
14527
+ }
14528
+ function findNextNonEmptyLine(lines, startIndex) {
14529
+ for (let i = startIndex; i < lines.length; i++) {
14530
+ const trimmed = lines[i].trim();
14531
+ if (trimmed !== "") return trimmed;
14532
+ }
14533
+ return null;
14534
+ }
14535
+
14536
+ // src/commands/uninstall.ts
14537
+ function findMainCss2(cwd) {
14538
+ const candidates = [
14539
+ "src/app/globals.css",
14540
+ "app/globals.css",
14541
+ "src/app/global.css",
14542
+ "app/global.css",
14543
+ "src/app/app.css",
14544
+ "app/app.css",
14545
+ "src/globals.css",
14546
+ "globals.css"
14547
+ ];
14548
+ for (const candidate of candidates) {
14549
+ const filePath = path40.join(cwd, candidate);
14550
+ if (fs35.existsSync(filePath)) return filePath;
14551
+ }
14552
+ return void 0;
14553
+ }
14554
+ function isCLICreatedBiome(biomePath) {
14555
+ if (!fs35.existsSync(biomePath)) return false;
14556
+ try {
14557
+ const content = JSON.parse(fs35.readFileSync(biomePath, "utf-8"));
14558
+ 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");
14559
+ } catch {
14560
+ return false;
14561
+ }
14562
+ }
14563
+ function buildUninstallPlan(cwd) {
14564
+ const steps = [];
14565
+ const hasSrc = fs35.existsSync(path40.join(cwd, "src"));
14566
+ const appBase = hasSrc ? "src/app" : "app";
14567
+ const dirs = [];
14568
+ const cmsDir = path40.join(cwd, "cms");
14569
+ const cmsRouteGroup = path40.join(cwd, appBase, "(cms)");
14570
+ if (fs35.existsSync(cmsDir)) dirs.push("cms/");
14571
+ if (fs35.existsSync(cmsRouteGroup)) dirs.push(`${appBase}/(cms)/`);
14572
+ if (dirs.length > 0) {
14573
+ steps.push({
14574
+ label: "CMS directories",
14575
+ items: dirs,
14576
+ execute() {
14577
+ if (fs35.existsSync(cmsDir)) fs35.rmSync(cmsDir, { recursive: true, force: true });
14578
+ if (fs35.existsSync(cmsRouteGroup)) fs35.rmSync(cmsRouteGroup, { recursive: true, force: true });
14579
+ }
14580
+ });
14581
+ }
14582
+ const configFiles = [];
14583
+ const configPaths = [];
14584
+ const candidates = [
14585
+ ["cms.config.ts", path40.join(cwd, "cms.config.ts")],
14586
+ ["drizzle.config.ts", path40.join(cwd, "drizzle.config.ts")],
14587
+ ["CMS.md", path40.join(cwd, "CMS.md")]
14588
+ ];
14589
+ for (const [label, fullPath] of candidates) {
14590
+ if (fs35.existsSync(fullPath)) {
14591
+ configFiles.push(label);
14592
+ configPaths.push(fullPath);
14593
+ }
14594
+ }
14595
+ const biomePath = path40.join(cwd, "biome.json");
14596
+ if (isCLICreatedBiome(biomePath)) {
14597
+ configFiles.push("biome.json (CLI-created)");
14598
+ configPaths.push(biomePath);
14599
+ }
14600
+ if (configFiles.length > 0) {
14601
+ steps.push({
14602
+ label: "Config files",
14603
+ items: configFiles,
14604
+ execute() {
14605
+ for (const p6 of configPaths) {
14606
+ if (fs35.existsSync(p6)) fs35.unlinkSync(p6);
14607
+ }
14608
+ }
14609
+ });
14610
+ }
14611
+ const tsconfigPath = path40.join(cwd, "tsconfig.json");
14612
+ if (fs35.existsSync(tsconfigPath)) {
14613
+ const content = fs35.readFileSync(tsconfigPath, "utf-8");
14614
+ if (content.includes("@cms/")) {
14615
+ steps.push({
14616
+ label: "tsconfig.json path aliases",
14617
+ items: ["Remove all @cms/* paths from compilerOptions.paths"],
14618
+ execute() {
14619
+ cleanTsconfig(tsconfigPath);
14620
+ }
14621
+ });
14622
+ }
14623
+ }
14624
+ const cssFile = findMainCss2(cwd);
14625
+ if (cssFile) {
14626
+ const cssContent = fs35.readFileSync(cssFile, "utf-8");
14627
+ const sourcePattern = /^@source\s+"[^"]*cms[^"]*";\s*$/m;
14628
+ if (sourcePattern.test(cssContent)) {
14629
+ const relCss = path40.relative(cwd, cssFile);
14630
+ steps.push({
14631
+ label: `CSS @source lines (${relCss})`,
14632
+ items: ["Remove @source lines referencing cms/"],
14633
+ execute() {
14634
+ cleanCss(cssFile);
14635
+ }
14636
+ });
14637
+ }
14638
+ }
14639
+ const envPath = path40.join(cwd, ".env.local");
14640
+ if (fs35.existsSync(envPath)) {
14641
+ const envContent = fs35.readFileSync(envPath, "utf-8");
14642
+ const bsVars = envContent.split("\n").filter((l) => l.trim().match(/^BETTERSTART_\w+=/)).map((l) => l.split("=")[0]);
14643
+ if (bsVars.length > 0) {
14644
+ steps.push({
14645
+ label: ".env.local variables",
14646
+ items: bsVars,
14647
+ execute() {
14648
+ cleanEnvFile(envPath);
14649
+ }
14650
+ });
14651
+ }
14652
+ }
14653
+ return steps;
14654
+ }
14655
+ 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) => {
14348
14656
  const cwd = options.cwd ? path40.resolve(options.cwd) : process.cwd();
14657
+ p5.intro(pc3.bgRed(pc3.white(" BetterStart Uninstall ")));
14658
+ const steps = buildUninstallPlan(cwd);
14659
+ if (steps.length === 0) {
14660
+ p5.log.info("Nothing to remove \u2014 project is already clean.");
14661
+ p5.outro("Done");
14662
+ return;
14663
+ }
14664
+ p5.log.warn("The following will be removed/modified:\n");
14665
+ for (const step of steps) {
14666
+ p5.log.message(` ${pc3.bold(step.label)}`);
14667
+ for (const item of step.items) {
14668
+ p5.log.message(` ${pc3.red("\xD7")} ${pc3.dim(item)}`);
14669
+ }
14670
+ p5.log.message("");
14671
+ }
14672
+ if (!options.force) {
14673
+ const confirmed = await p5.confirm({
14674
+ message: `Proceed with uninstall? (${steps.length} ${steps.length === 1 ? "area" : "areas"})`,
14675
+ initialValue: false
14676
+ });
14677
+ if (p5.isCancel(confirmed) || !confirmed) {
14678
+ p5.cancel("Uninstall cancelled.");
14679
+ process.exit(0);
14680
+ }
14681
+ }
14682
+ let completedCount = 0;
14683
+ for (const step of steps) {
14684
+ step.execute();
14685
+ completedCount++;
14686
+ p5.log.success(`Removed: ${step.label}`);
14687
+ }
14688
+ p5.log.message("");
14689
+ if (completedCount === 0) {
14690
+ p5.log.info("No changes were made.");
14691
+ } else {
14692
+ p5.note(
14693
+ pc3.dim("Database tables were NOT dropped \u2014 drop them manually if needed."),
14694
+ "Next steps"
14695
+ );
14696
+ }
14697
+ if (findMainCss2(cwd)) {
14698
+ p5.log.info(
14699
+ pc3.dim(
14700
+ "Note: @theme tokens were left in your CSS \u2014 they're harmless and may be shared with your own styles."
14701
+ )
14702
+ );
14703
+ }
14704
+ p5.outro(completedCount > 0 ? "Uninstall complete" : "Done");
14705
+ });
14706
+
14707
+ // src/commands/update-styles.ts
14708
+ import fs36 from "fs";
14709
+ import path41 from "path";
14710
+ import * as clack3 from "@clack/prompts";
14711
+ import { Command as Command7 } from "commander";
14712
+ var updateStylesCommand = new Command7("update-styles").description("Replace cms-globals.css with the latest version from the CLI").option("--cwd <path>", "Project root path").action(async (options) => {
14713
+ const cwd = options.cwd ? path41.resolve(options.cwd) : process.cwd();
14349
14714
  clack3.intro("BetterStart Update Styles");
14350
14715
  const config = await resolveConfig(cwd);
14351
14716
  const cmsDir = config.paths?.cms ?? "./cms";
14352
- const targetPath = path40.join(cwd, cmsDir, "cms-globals.css");
14353
- if (!fs34.existsSync(targetPath)) {
14354
- clack3.cancel(`cms-globals.css not found at ${path40.relative(cwd, targetPath)}`);
14717
+ const targetPath = path41.join(cwd, cmsDir, "cms-globals.css");
14718
+ if (!fs36.existsSync(targetPath)) {
14719
+ clack3.cancel(`cms-globals.css not found at ${path41.relative(cwd, targetPath)}`);
14355
14720
  process.exit(1);
14356
14721
  }
14357
- fs34.writeFileSync(targetPath, cmsGlobalsCssTemplate(), "utf-8");
14358
- clack3.log.success(`Updated ${path40.relative(cwd, targetPath)}`);
14722
+ fs36.writeFileSync(targetPath, cmsGlobalsCssTemplate(), "utf-8");
14723
+ clack3.log.success(`Updated ${path41.relative(cwd, targetPath)}`);
14359
14724
  clack3.outro("Styles updated");
14360
14725
  });
14361
14726
 
14362
14727
  // src/cli.ts
14363
- var program = new Command7();
14728
+ var program = new Command8();
14364
14729
  program.name("betterstart").description("Scaffold a full-featured CMS into any Next.js 16 application").version("0.1.0");
14365
14730
  program.addCommand(initCommand);
14366
14731
  program.addCommand(generateCommand);
14367
14732
  program.addCommand(removeCommand);
14368
14733
  program.addCommand(seedCommand);
14734
+ program.addCommand(uninstallCommand);
14369
14735
  program.addCommand(updateDepsCommand);
14370
14736
  program.addCommand(updateStylesCommand);
14371
14737
  program.parse();