@betterstart/cli 0.1.26 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +339 -282
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6561,29 +6561,29 @@ function updateNavigation(schema, cwd, cmsDir, options = {}) {
|
|
|
6561
6561
|
icon: schema.icon
|
|
6562
6562
|
};
|
|
6563
6563
|
if (schema.navGroup) {
|
|
6564
|
-
let
|
|
6565
|
-
if (!
|
|
6566
|
-
|
|
6564
|
+
let group2 = items.find((item) => item.label === schema.navGroup?.label);
|
|
6565
|
+
if (!group2) {
|
|
6566
|
+
group2 = {
|
|
6567
6567
|
label: schema.navGroup.label,
|
|
6568
6568
|
href: "#",
|
|
6569
6569
|
icon: schema.navGroup.icon,
|
|
6570
6570
|
children: []
|
|
6571
6571
|
};
|
|
6572
|
-
items.push(
|
|
6572
|
+
items.push(group2);
|
|
6573
6573
|
}
|
|
6574
|
-
if (!
|
|
6575
|
-
|
|
6574
|
+
if (!group2.children) {
|
|
6575
|
+
group2.children = [];
|
|
6576
6576
|
}
|
|
6577
|
-
const existingChild =
|
|
6577
|
+
const existingChild = group2.children.findIndex((c) => c.href === entityHref);
|
|
6578
6578
|
if (existingChild >= 0) {
|
|
6579
6579
|
if (options.force) {
|
|
6580
|
-
|
|
6580
|
+
group2.children[existingChild] = newItem;
|
|
6581
6581
|
} else {
|
|
6582
6582
|
return { files: [] };
|
|
6583
6583
|
}
|
|
6584
6584
|
} else {
|
|
6585
|
-
|
|
6586
|
-
|
|
6585
|
+
group2.children.push(newItem);
|
|
6586
|
+
group2.children.sort((a, b) => a.label.localeCompare(b.label));
|
|
6587
6587
|
}
|
|
6588
6588
|
if (schema.navGroup.icon && !iconImports.includes(schema.navGroup.icon)) {
|
|
6589
6589
|
iconImports.push(schema.navGroup.icon);
|
|
@@ -7640,9 +7640,6 @@ function detectPackageManager(cwd) {
|
|
|
7640
7640
|
}
|
|
7641
7641
|
return "npm";
|
|
7642
7642
|
}
|
|
7643
|
-
function installCommand(pm) {
|
|
7644
|
-
return pm === "yarn" ? "yarn" : `${pm} install`;
|
|
7645
|
-
}
|
|
7646
7643
|
function runCommand(pm, script) {
|
|
7647
7644
|
switch (pm) {
|
|
7648
7645
|
case "pnpm":
|
|
@@ -7975,14 +7972,6 @@ function openBrowser(url) {
|
|
|
7975
7972
|
// src/init/prompts/features.ts
|
|
7976
7973
|
import * as p2 from "@clack/prompts";
|
|
7977
7974
|
async function promptFeatures(presetOverride) {
|
|
7978
|
-
const includeEmail = await p2.confirm({
|
|
7979
|
-
message: "Include email system? (Resend + React Email)",
|
|
7980
|
-
initialValue: true
|
|
7981
|
-
});
|
|
7982
|
-
if (p2.isCancel(includeEmail)) {
|
|
7983
|
-
p2.cancel("Setup cancelled.");
|
|
7984
|
-
process.exit(0);
|
|
7985
|
-
}
|
|
7986
7975
|
let preset;
|
|
7987
7976
|
if (presetOverride && isValidPreset(presetOverride)) {
|
|
7988
7977
|
preset = presetOverride;
|
|
@@ -8002,7 +7991,7 @@ async function promptFeatures(presetOverride) {
|
|
|
8002
7991
|
}
|
|
8003
7992
|
preset = selected;
|
|
8004
7993
|
}
|
|
8005
|
-
return { includeEmail, preset };
|
|
7994
|
+
return { includeEmail: true, preset };
|
|
8006
7995
|
}
|
|
8007
7996
|
function isValidPreset(value) {
|
|
8008
7997
|
return value === "blank" || value === "blog" || value === "full";
|
|
@@ -11678,7 +11667,6 @@ var CORE_DEPS = [
|
|
|
11678
11667
|
"input-otp",
|
|
11679
11668
|
"react-resizable-panels",
|
|
11680
11669
|
"recharts",
|
|
11681
|
-
"shadcn",
|
|
11682
11670
|
"tw-animate-css",
|
|
11683
11671
|
"usehooks-ts",
|
|
11684
11672
|
"vaul"
|
|
@@ -12021,6 +12009,7 @@ import { authClient } from '@cms/auth/client'
|
|
|
12021
12009
|
import { Button } from '@cms/components/ui/button'
|
|
12022
12010
|
import { Input } from '@cms/components/ui/input'
|
|
12023
12011
|
import { Label } from '@cms/components/ui/label'
|
|
12012
|
+
import { LoaderCircle } from 'lucide-react'
|
|
12024
12013
|
import { useRouter } from 'next/navigation'
|
|
12025
12014
|
import * as React from 'react'
|
|
12026
12015
|
|
|
@@ -12093,6 +12082,7 @@ export function LoginForm() {
|
|
|
12093
12082
|
</div>
|
|
12094
12083
|
|
|
12095
12084
|
<Button type="submit" className="w-full" size="lg" disabled={isLoading}>
|
|
12085
|
+
{isLoading && <LoaderCircle className="animate-spin" />}
|
|
12096
12086
|
{isLoading ? 'Signing in...' : 'Sign In'}
|
|
12097
12087
|
</Button>
|
|
12098
12088
|
</form>
|
|
@@ -13513,9 +13503,32 @@ const auth = betterAuth({
|
|
|
13513
13503
|
const EMAIL = process.env.SEED_EMAIL!
|
|
13514
13504
|
const PASSWORD = process.env.SEED_PASSWORD!
|
|
13515
13505
|
const NAME = process.env.SEED_NAME || 'Admin'
|
|
13506
|
+
const OVERWRITE = process.env.SEED_OVERWRITE === 'true'
|
|
13516
13507
|
|
|
13517
13508
|
async function main() {
|
|
13518
|
-
|
|
13509
|
+
// Check if user already exists
|
|
13510
|
+
const existing = await db
|
|
13511
|
+
.select({ id: schema.user.id, name: schema.user.name })
|
|
13512
|
+
.from(schema.user)
|
|
13513
|
+
.where(eq(schema.user.email, EMAIL))
|
|
13514
|
+
.then((rows: { id: string; name: string }[]) => rows[0])
|
|
13515
|
+
|
|
13516
|
+
if (existing && !OVERWRITE) {
|
|
13517
|
+
// Exit code 2 signals "user exists" to the CLI
|
|
13518
|
+
console.log(\`EXISTING_USER:\${existing.name}\`)
|
|
13519
|
+
process.exit(2)
|
|
13520
|
+
}
|
|
13521
|
+
|
|
13522
|
+
if (existing && OVERWRITE) {
|
|
13523
|
+
console.log('\\n Replacing existing admin user...')
|
|
13524
|
+
// Remove existing account + session rows first (foreign key refs)
|
|
13525
|
+
await db.delete(schema.session).where(eq(schema.session.userId, existing.id))
|
|
13526
|
+
await db.delete(schema.account).where(eq(schema.account.userId, existing.id))
|
|
13527
|
+
await db.delete(schema.user).where(eq(schema.user.id, existing.id))
|
|
13528
|
+
} else {
|
|
13529
|
+
console.log('\\n Creating admin user...')
|
|
13530
|
+
}
|
|
13531
|
+
|
|
13519
13532
|
console.log(\` Email: \${EMAIL}\\n\`)
|
|
13520
13533
|
|
|
13521
13534
|
const result = await auth.api.signUpEmail({
|
|
@@ -13532,7 +13545,7 @@ async function main() {
|
|
|
13532
13545
|
.set({ role: 'admin' })
|
|
13533
13546
|
.where(eq(schema.user.id, result.user.id))
|
|
13534
13547
|
|
|
13535
|
-
console.log(\` Admin user created: \${EMAIL}\`)
|
|
13548
|
+
console.log(\` Admin user \${existing ? 'replaced' : 'created'}: \${EMAIL}\`)
|
|
13536
13549
|
console.log(' Role: admin\\n')
|
|
13537
13550
|
process.exit(0)
|
|
13538
13551
|
}
|
|
@@ -13590,24 +13603,54 @@ var seedCommand = new Command2("seed").description("Create the initial admin use
|
|
|
13590
13603
|
fs31.mkdirSync(scriptsDir, { recursive: true });
|
|
13591
13604
|
}
|
|
13592
13605
|
fs31.writeFileSync(seedPath, buildSeedScript(), "utf-8");
|
|
13593
|
-
const
|
|
13594
|
-
|
|
13595
|
-
|
|
13596
|
-
|
|
13597
|
-
const tsxBin = path36.join(cwd, "node_modules", ".bin", "tsx");
|
|
13598
|
-
execFileSync5(tsxBin, [seedPath], {
|
|
13606
|
+
const { execFile } = await import("child_process");
|
|
13607
|
+
const tsxBin = path36.join(cwd, "node_modules", ".bin", "tsx");
|
|
13608
|
+
const runSeed2 = (overwrite) => new Promise((resolve, reject) => {
|
|
13609
|
+
execFile(tsxBin, [seedPath], {
|
|
13599
13610
|
cwd,
|
|
13600
|
-
stdio: "pipe",
|
|
13601
13611
|
env: {
|
|
13602
13612
|
...process.env,
|
|
13603
13613
|
SEED_EMAIL: email,
|
|
13604
13614
|
SEED_PASSWORD: password3,
|
|
13605
|
-
SEED_NAME: name || "Admin"
|
|
13615
|
+
SEED_NAME: name || "Admin",
|
|
13616
|
+
...overwrite ? { SEED_OVERWRITE: "true" } : {}
|
|
13617
|
+
}
|
|
13618
|
+
}, (err, stdout, stderr) => {
|
|
13619
|
+
if (err && "code" in err && err.code === 2) {
|
|
13620
|
+
resolve({ code: 2, stdout });
|
|
13621
|
+
} else if (err) {
|
|
13622
|
+
reject(new Error(stderr || err.message));
|
|
13623
|
+
} else {
|
|
13624
|
+
resolve({ code: 0, stdout });
|
|
13606
13625
|
}
|
|
13607
13626
|
});
|
|
13608
|
-
|
|
13627
|
+
});
|
|
13628
|
+
const spinner5 = clack.spinner();
|
|
13629
|
+
spinner5.start("Creating admin user...");
|
|
13630
|
+
try {
|
|
13631
|
+
const result = await runSeed2(false);
|
|
13632
|
+
if (result.code === 2) {
|
|
13633
|
+
const existingName = result.stdout.split("\n").find((l) => l.startsWith("EXISTING_USER:"))?.replace("EXISTING_USER:", "")?.trim() || "unknown";
|
|
13634
|
+
spinner5.stop(`Account already exists for ${email}`);
|
|
13635
|
+
const overwrite = await clack.confirm({
|
|
13636
|
+
message: `An admin account (${existingName}) already exists with this email. Replace it?`
|
|
13637
|
+
});
|
|
13638
|
+
if (clack.isCancel(overwrite) || !overwrite) {
|
|
13639
|
+
clack.cancel("Seed cancelled.");
|
|
13640
|
+
try {
|
|
13641
|
+
fs31.unlinkSync(seedPath);
|
|
13642
|
+
} catch {
|
|
13643
|
+
}
|
|
13644
|
+
process.exit(0);
|
|
13645
|
+
}
|
|
13646
|
+
spinner5.start("Replacing admin user...");
|
|
13647
|
+
await runSeed2(true);
|
|
13648
|
+
spinner5.stop("Admin user replaced");
|
|
13649
|
+
} else {
|
|
13650
|
+
spinner5.stop("Admin user created");
|
|
13651
|
+
}
|
|
13609
13652
|
} catch (err) {
|
|
13610
|
-
|
|
13653
|
+
spinner5.stop("Failed to create admin user");
|
|
13611
13654
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
13612
13655
|
clack.log.error(errMsg);
|
|
13613
13656
|
clack.log.info("You can run the seed script manually:");
|
|
@@ -13640,21 +13683,19 @@ var initCommand = new Command3("init").description("Scaffold CMS into a new or e
|
|
|
13640
13683
|
let isFreshProject = false;
|
|
13641
13684
|
let srcDir;
|
|
13642
13685
|
if (project.isExisting) {
|
|
13643
|
-
p4.log.info(`
|
|
13644
|
-
p4.log.info(`Package manager: ${pc2.cyan(pm)}`);
|
|
13686
|
+
p4.log.info(`Next.js project detected ${pc2.dim("\xB7")} ${pc2.cyan(pm)}`);
|
|
13645
13687
|
srcDir = project.hasSrcDir;
|
|
13646
13688
|
if (!project.hasTypeScript) {
|
|
13647
13689
|
p4.log.error("TypeScript is required. Please add a tsconfig.json first.");
|
|
13648
13690
|
process.exit(1);
|
|
13649
13691
|
}
|
|
13650
13692
|
if (project.conflicts.length > 0) {
|
|
13651
|
-
|
|
13652
|
-
|
|
13653
|
-
|
|
13654
|
-
}
|
|
13693
|
+
const conflictLines = project.conflicts.map((c) => `${pc2.yellow("\u25B2")} ${c}`);
|
|
13694
|
+
conflictLines.push("", pc2.dim("Existing files will not be overwritten."));
|
|
13695
|
+
p4.note(conflictLines.join("\n"), pc2.yellow("Conflicts"));
|
|
13655
13696
|
if (!options.yes) {
|
|
13656
13697
|
const proceed = await p4.confirm({
|
|
13657
|
-
message: "Continue anyway?
|
|
13698
|
+
message: "Continue anyway?",
|
|
13658
13699
|
initialValue: true
|
|
13659
13700
|
});
|
|
13660
13701
|
if (p4.isCancel(proceed) || !proceed) {
|
|
@@ -13763,30 +13804,62 @@ var initCommand = new Command3("init").description("Scaffold CMS into a new or e
|
|
|
13763
13804
|
...getDefaultConfig(srcDir),
|
|
13764
13805
|
features: { email: features.includeEmail }
|
|
13765
13806
|
};
|
|
13807
|
+
const results = [];
|
|
13766
13808
|
const s = p4.spinner();
|
|
13767
|
-
s.start("
|
|
13809
|
+
s.start("Directory structure");
|
|
13768
13810
|
const baseFiles = scaffoldBase({ cwd, config });
|
|
13769
|
-
|
|
13770
|
-
s.
|
|
13811
|
+
results.push({ label: "Directory structure", result: `${baseFiles.length} files` });
|
|
13812
|
+
s.message("TypeScript aliases");
|
|
13771
13813
|
const tsResult = scaffoldTsconfig(cwd);
|
|
13772
|
-
|
|
13773
|
-
|
|
13814
|
+
results.push({
|
|
13815
|
+
label: "TypeScript aliases",
|
|
13816
|
+
result: tsResult.added.length > 0 ? `${tsResult.added.length} paths` : "already set"
|
|
13817
|
+
});
|
|
13818
|
+
s.message("Tailwind CSS");
|
|
13774
13819
|
const twResult = scaffoldTailwind(cwd, srcDir);
|
|
13775
|
-
|
|
13776
|
-
|
|
13777
|
-
|
|
13778
|
-
|
|
13779
|
-
|
|
13780
|
-
s.stop("No CSS file found (will configure later)");
|
|
13781
|
-
}
|
|
13782
|
-
s.start("Setting up environment variables...");
|
|
13820
|
+
results.push({
|
|
13821
|
+
label: "Tailwind CSS",
|
|
13822
|
+
result: twResult.appended ? "updated" : twResult.file ? "already set" : "no CSS file"
|
|
13823
|
+
});
|
|
13824
|
+
s.message("Environment variables");
|
|
13783
13825
|
const envResult = scaffoldEnv(cwd, { includeEmail: features.includeEmail, databaseUrl });
|
|
13784
|
-
const
|
|
13785
|
-
|
|
13786
|
-
|
|
13787
|
-
|
|
13826
|
+
const envCount = envResult.added.length + envResult.updated.length;
|
|
13827
|
+
results.push({
|
|
13828
|
+
label: "Environment variables",
|
|
13829
|
+
result: envCount > 0 ? `${envCount} vars` : "already set"
|
|
13830
|
+
});
|
|
13831
|
+
s.message("Database");
|
|
13788
13832
|
const dbFiles = scaffoldDatabase({ cwd, config });
|
|
13789
|
-
|
|
13833
|
+
results.push({ label: "Database", result: `${dbFiles.length} files` });
|
|
13834
|
+
s.message("Authentication");
|
|
13835
|
+
const authFiles = scaffoldAuth({ cwd, config });
|
|
13836
|
+
results.push({ label: "Authentication", result: `${authFiles.length} files` });
|
|
13837
|
+
s.message("Components");
|
|
13838
|
+
const compFiles = scaffoldComponents({ cwd, config });
|
|
13839
|
+
results.push({ label: "Components", result: `${compFiles.length} files` });
|
|
13840
|
+
s.message("Pages & layouts");
|
|
13841
|
+
const layoutFiles = scaffoldLayout({ cwd, config });
|
|
13842
|
+
results.push({ label: "Pages & layouts", result: `${layoutFiles.length} files` });
|
|
13843
|
+
s.message("API routes");
|
|
13844
|
+
const apiFiles = scaffoldApiRoutes({ cwd, config });
|
|
13845
|
+
results.push({ label: "API routes", result: `${apiFiles.length} routes` });
|
|
13846
|
+
s.message("Linter");
|
|
13847
|
+
let linterResult;
|
|
13848
|
+
if (project.linter.type === "none") {
|
|
13849
|
+
const biomeResult = scaffoldBiome(cwd, project.linter);
|
|
13850
|
+
linterResult = biomeResult.installed ? "biome (new)" : "none";
|
|
13851
|
+
} else {
|
|
13852
|
+
linterResult = project.linter.type;
|
|
13853
|
+
}
|
|
13854
|
+
results.push({ label: "Linter", result: linterResult });
|
|
13855
|
+
const maxLabel = Math.max(...results.map((r) => r.label.length));
|
|
13856
|
+
const noteLines = results.map((r) => {
|
|
13857
|
+
const padded = r.label.padEnd(maxLabel + 3);
|
|
13858
|
+
return `${pc2.green("\u2713")} ${padded}${pc2.dim(r.result)}`;
|
|
13859
|
+
});
|
|
13860
|
+
s.stop("");
|
|
13861
|
+
process.stdout.write("\x1B[2A\x1B[J");
|
|
13862
|
+
p4.note(noteLines.join("\n"), "Scaffolded CMS");
|
|
13790
13863
|
const drizzleConfigPath = path37.join(cwd, "drizzle.config.ts");
|
|
13791
13864
|
if (!dbFiles.includes("drizzle.config.ts") && fs32.existsSync(drizzleConfigPath)) {
|
|
13792
13865
|
if (!options.yes) {
|
|
@@ -13801,42 +13874,17 @@ var initCommand = new Command3("init").description("Scaffold CMS into a new or e
|
|
|
13801
13874
|
}
|
|
13802
13875
|
}
|
|
13803
13876
|
}
|
|
13804
|
-
s.start("
|
|
13805
|
-
const authFiles = scaffoldAuth({ cwd, config });
|
|
13806
|
-
s.stop(`Created ${authFiles.length} auth files`);
|
|
13807
|
-
s.start("Copying CMS components...");
|
|
13808
|
-
const compFiles = scaffoldComponents({ cwd, config });
|
|
13809
|
-
s.stop(`Created ${compFiles.length} component files`);
|
|
13810
|
-
s.start("Creating CMS pages and layouts...");
|
|
13811
|
-
const layoutFiles = scaffoldLayout({ cwd, config });
|
|
13812
|
-
s.stop(`Created ${layoutFiles.length} page files`);
|
|
13813
|
-
s.start("Creating API routes...");
|
|
13814
|
-
const apiFiles = scaffoldApiRoutes({ cwd, config });
|
|
13815
|
-
s.stop(`Created ${apiFiles.length} API routes`);
|
|
13816
|
-
s.start("Checking for linter...");
|
|
13817
|
-
if (project.linter.type === "none") {
|
|
13818
|
-
s.stop("No linter found");
|
|
13819
|
-
s.start("Setting up Biome linter...");
|
|
13820
|
-
const biomeResult = scaffoldBiome(cwd, project.linter);
|
|
13821
|
-
if (biomeResult.installed) {
|
|
13822
|
-
s.stop("Created biome.json");
|
|
13823
|
-
} else {
|
|
13824
|
-
s.stop(`Biome skipped: ${biomeResult.skippedReason}`);
|
|
13825
|
-
}
|
|
13826
|
-
} else {
|
|
13827
|
-
s.stop(`Linter: ${pc2.cyan(project.linter.type)} (${project.linter.configFile})`);
|
|
13828
|
-
}
|
|
13829
|
-
s.start("Installing dependencies (this may take a minute)...");
|
|
13877
|
+
s.start("Installing dependencies (this may take a minute)");
|
|
13830
13878
|
const depsResult = await installDependenciesAsync({
|
|
13831
13879
|
cwd,
|
|
13832
13880
|
pm,
|
|
13833
13881
|
includeEmail: features.includeEmail,
|
|
13834
13882
|
includeBiome: project.linter.type === "none"
|
|
13835
13883
|
});
|
|
13884
|
+
let depsInstalled = false;
|
|
13836
13885
|
if (depsResult.success) {
|
|
13837
|
-
s.stop(
|
|
13838
|
-
|
|
13839
|
-
);
|
|
13886
|
+
s.stop("");
|
|
13887
|
+
depsInstalled = true;
|
|
13840
13888
|
} else {
|
|
13841
13889
|
s.stop("Failed to install dependencies");
|
|
13842
13890
|
p4.log.warning(depsResult.error ?? "Unknown error");
|
|
@@ -13846,24 +13894,59 @@ var initCommand = new Command3("init").description("Scaffold CMS into a new or e
|
|
|
13846
13894
|
${pc2.cyan(`${pm} add -D ${depsResult.devDeps.join(" ")}`)}`
|
|
13847
13895
|
);
|
|
13848
13896
|
}
|
|
13849
|
-
|
|
13897
|
+
if (depsInstalled) {
|
|
13898
|
+
process.stdout.write("\x1B[2A\x1B[J");
|
|
13899
|
+
}
|
|
13900
|
+
s.start(`Applying ${features.preset} preset`);
|
|
13850
13901
|
const presetResult = scaffoldPreset({ cwd, config, preset: features.preset });
|
|
13902
|
+
{
|
|
13903
|
+
const entityNames = [];
|
|
13904
|
+
const formNames = [];
|
|
13905
|
+
const schemasDir = path37.join(cwd, config.paths.schemas);
|
|
13906
|
+
const formsDir = path37.join(schemasDir, "forms");
|
|
13907
|
+
if (fs32.existsSync(schemasDir)) {
|
|
13908
|
+
for (const f of fs32.readdirSync(schemasDir)) {
|
|
13909
|
+
if (f.endsWith(".json")) entityNames.push(f.replace(".json", ""));
|
|
13910
|
+
}
|
|
13911
|
+
}
|
|
13912
|
+
if (fs32.existsSync(formsDir)) {
|
|
13913
|
+
for (const f of fs32.readdirSync(formsDir)) {
|
|
13914
|
+
if (f.endsWith(".json")) formNames.push(f.replace(".json", ""));
|
|
13915
|
+
}
|
|
13916
|
+
}
|
|
13917
|
+
regenerateCmsDoc(cwd, config, {
|
|
13918
|
+
preset: features.preset,
|
|
13919
|
+
schemas: entityNames,
|
|
13920
|
+
forms: formNames
|
|
13921
|
+
});
|
|
13922
|
+
}
|
|
13923
|
+
s.stop("");
|
|
13924
|
+
process.stdout.write("\x1B[2A\x1B[J");
|
|
13925
|
+
const installLines = [];
|
|
13926
|
+
if (depsInstalled) {
|
|
13927
|
+
installLines.push(
|
|
13928
|
+
`${pc2.green("\u2713")} Dependencies ${pc2.dim(`${depsResult.coreDeps.length} deps + ${depsResult.devDeps.length} dev deps`)}`
|
|
13929
|
+
);
|
|
13930
|
+
}
|
|
13851
13931
|
if (presetResult.errors.length > 0) {
|
|
13852
|
-
|
|
13932
|
+
installLines.push(
|
|
13933
|
+
`${pc2.yellow("\u25B2")} Preset ${pc2.dim(`${features.preset} \u2014 ${presetResult.errors.length} warning(s)`)}`
|
|
13934
|
+
);
|
|
13853
13935
|
for (const err of presetResult.errors) {
|
|
13854
|
-
|
|
13936
|
+
installLines.push(` ${pc2.dim(err)}`);
|
|
13855
13937
|
}
|
|
13856
13938
|
} else {
|
|
13857
|
-
|
|
13858
|
-
|
|
13939
|
+
installLines.push(
|
|
13940
|
+
`${pc2.green("\u2713")} Preset ${pc2.dim(`${features.preset} \u2014 ${presetResult.schemas.length} schemas, ${presetResult.generatedFiles.length} files`)}`
|
|
13859
13941
|
);
|
|
13860
13942
|
}
|
|
13943
|
+
p4.note(installLines.join("\n"), "Installed");
|
|
13861
13944
|
let dbPushed = false;
|
|
13862
13945
|
if (depsResult.success && hasDbUrl(cwd)) {
|
|
13863
|
-
s.start("Pushing database schema (drizzle-kit push)
|
|
13946
|
+
s.start("Pushing database schema (drizzle-kit push)");
|
|
13864
13947
|
const pushResult = await runDrizzlePush(cwd);
|
|
13865
13948
|
if (pushResult.success) {
|
|
13866
|
-
s.stop("Database schema pushed
|
|
13949
|
+
s.stop(`${pc2.green("\u2713")} Database schema pushed`);
|
|
13867
13950
|
dbPushed = true;
|
|
13868
13951
|
} else {
|
|
13869
13952
|
s.stop("Database push failed");
|
|
@@ -13875,66 +13958,62 @@ var initCommand = new Command3("init").description("Scaffold CMS into a new or e
|
|
|
13875
13958
|
let seedPassword;
|
|
13876
13959
|
let seedSuccess = false;
|
|
13877
13960
|
if (dbPushed && !options.yes) {
|
|
13878
|
-
p4.
|
|
13879
|
-
const
|
|
13880
|
-
|
|
13881
|
-
|
|
13882
|
-
|
|
13883
|
-
|
|
13961
|
+
p4.note(pc2.dim("Create your first admin user to access the CMS."), "Admin account");
|
|
13962
|
+
const credentials = await p4.group(
|
|
13963
|
+
{
|
|
13964
|
+
email: () => p4.text({
|
|
13965
|
+
message: "Admin email",
|
|
13966
|
+
placeholder: "admin@example.com",
|
|
13967
|
+
validate: (v) => {
|
|
13968
|
+
if (!v || !v.includes("@")) return "Please enter a valid email";
|
|
13969
|
+
}
|
|
13970
|
+
}),
|
|
13971
|
+
password: () => p4.password({
|
|
13972
|
+
message: "Admin password",
|
|
13973
|
+
validate: (v) => {
|
|
13974
|
+
if (!v || v.length < 8) return "Password must be at least 8 characters";
|
|
13975
|
+
}
|
|
13976
|
+
})
|
|
13977
|
+
},
|
|
13978
|
+
{
|
|
13979
|
+
onCancel: () => {
|
|
13980
|
+
p4.cancel("Setup cancelled.");
|
|
13981
|
+
process.exit(0);
|
|
13982
|
+
}
|
|
13884
13983
|
}
|
|
13885
|
-
|
|
13886
|
-
|
|
13887
|
-
|
|
13888
|
-
|
|
13889
|
-
|
|
13890
|
-
|
|
13891
|
-
|
|
13892
|
-
|
|
13893
|
-
|
|
13984
|
+
);
|
|
13985
|
+
seedEmail = credentials.email;
|
|
13986
|
+
seedPassword = credentials.password;
|
|
13987
|
+
s.start("Creating admin user");
|
|
13988
|
+
let seedResult = await runSeed(cwd, config.paths?.cms ?? "./cms", credentials.email, credentials.password);
|
|
13989
|
+
if (seedResult.existingUser) {
|
|
13990
|
+
s.stop(`${pc2.yellow("\u25B2")} Admin user already exists (${seedResult.existingUser})`);
|
|
13991
|
+
const replace = await p4.confirm({
|
|
13992
|
+
message: "Replace existing admin user?",
|
|
13993
|
+
initialValue: false
|
|
13994
|
+
});
|
|
13995
|
+
if (!p4.isCancel(replace) && replace) {
|
|
13996
|
+
s.start("Replacing admin user");
|
|
13997
|
+
seedResult = await runSeed(cwd, config.paths?.cms ?? "./cms", credentials.email, credentials.password, true);
|
|
13998
|
+
} else {
|
|
13999
|
+
seedSuccess = true;
|
|
13894
14000
|
}
|
|
13895
|
-
});
|
|
13896
|
-
if (p4.isCancel(password3)) {
|
|
13897
|
-
p4.cancel("Setup cancelled.");
|
|
13898
|
-
process.exit(0);
|
|
13899
14001
|
}
|
|
13900
|
-
seedEmail = email;
|
|
13901
|
-
seedPassword = password3;
|
|
13902
|
-
s.start("Creating admin user...");
|
|
13903
|
-
const seedResult = await runSeed(cwd, config.paths?.cms ?? "./cms", email, password3);
|
|
13904
14002
|
if (seedResult.success) {
|
|
13905
|
-
s.stop("Admin user created
|
|
14003
|
+
s.stop(`${pc2.green("\u2713")} Admin user created`);
|
|
13906
14004
|
seedSuccess = true;
|
|
13907
|
-
} else {
|
|
13908
|
-
s.stop("Failed to create admin user
|
|
13909
|
-
p4.
|
|
13910
|
-
|
|
13911
|
-
|
|
13912
|
-
|
|
13913
|
-
|
|
13914
|
-
|
|
13915
|
-
const entityNames = [];
|
|
13916
|
-
const formNames = [];
|
|
13917
|
-
const schemasDir = path37.join(cwd, config.paths.schemas);
|
|
13918
|
-
const formsDir = path37.join(schemasDir, "forms");
|
|
13919
|
-
if (fs32.existsSync(schemasDir)) {
|
|
13920
|
-
for (const f of fs32.readdirSync(schemasDir)) {
|
|
13921
|
-
if (f.endsWith(".json")) entityNames.push(f.replace(".json", ""));
|
|
13922
|
-
}
|
|
13923
|
-
}
|
|
13924
|
-
if (fs32.existsSync(formsDir)) {
|
|
13925
|
-
for (const f of fs32.readdirSync(formsDir)) {
|
|
13926
|
-
if (f.endsWith(".json")) formNames.push(f.replace(".json", ""));
|
|
13927
|
-
}
|
|
14005
|
+
} else if (!seedSuccess && seedResult.error) {
|
|
14006
|
+
s.stop(`${pc2.red("\u2717")} Failed to create admin user`);
|
|
14007
|
+
p4.note(
|
|
14008
|
+
`${pc2.red(seedResult.error)}
|
|
14009
|
+
|
|
14010
|
+
Run manually: ${pc2.cyan("npx betterstart seed")}`,
|
|
14011
|
+
pc2.red("Seed failed")
|
|
14012
|
+
);
|
|
13928
14013
|
}
|
|
13929
|
-
regenerateCmsDoc(cwd, config, {
|
|
13930
|
-
preset: features.preset,
|
|
13931
|
-
schemas: entityNames,
|
|
13932
|
-
forms: formNames
|
|
13933
|
-
});
|
|
13934
14014
|
}
|
|
13935
|
-
s.stop("Generated CMS.md");
|
|
13936
14015
|
if (isFreshProject) {
|
|
13937
|
-
s.start("Creating initial git commit
|
|
14016
|
+
s.start("Creating initial git commit");
|
|
13938
14017
|
try {
|
|
13939
14018
|
execFileSync4("git", ["init"], { cwd, stdio: "pipe" });
|
|
13940
14019
|
execFileSync4("git", ["add", "."], { cwd, stdio: "pipe" });
|
|
@@ -13950,7 +14029,6 @@ var initCommand = new Command3("init").description("Scaffold CMS into a new or e
|
|
|
13950
14029
|
const totalFiles = baseFiles.length + dbFiles.length + authFiles.length + compFiles.length + layoutFiles.length + apiFiles.length;
|
|
13951
14030
|
const summaryLines = [
|
|
13952
14031
|
`Preset: ${pc2.cyan(features.preset)}`,
|
|
13953
|
-
`Email: ${features.includeEmail ? pc2.green("yes") : pc2.dim("no")}`,
|
|
13954
14032
|
`Files created: ${pc2.cyan(String(totalFiles))}`,
|
|
13955
14033
|
`Env vars: ${envResult.added.length} added, ${envResult.skipped.length} skipped`
|
|
13956
14034
|
];
|
|
@@ -14040,26 +14118,14 @@ function hasDbUrl(cwd) {
|
|
|
14040
14118
|
}
|
|
14041
14119
|
return false;
|
|
14042
14120
|
}
|
|
14043
|
-
|
|
14121
|
+
function runSeed(cwd, cmsDir, email, password3, overwrite = false) {
|
|
14044
14122
|
const scriptsDir = path37.join(cwd, cmsDir, "scripts");
|
|
14045
14123
|
const seedPath = path37.join(scriptsDir, "seed.ts");
|
|
14046
14124
|
if (!fs32.existsSync(scriptsDir)) {
|
|
14047
14125
|
fs32.mkdirSync(scriptsDir, { recursive: true });
|
|
14048
14126
|
}
|
|
14049
14127
|
fs32.writeFileSync(seedPath, buildSeedScript(), "utf-8");
|
|
14050
|
-
|
|
14051
|
-
const tsxBin = path37.join(cwd, "node_modules", ".bin", "tsx");
|
|
14052
|
-
execFileSync4(tsxBin, [seedPath], {
|
|
14053
|
-
cwd,
|
|
14054
|
-
stdio: "pipe",
|
|
14055
|
-
timeout: 3e4,
|
|
14056
|
-
env: { ...process.env, SEED_EMAIL: email, SEED_PASSWORD: password3, SEED_NAME: "Admin" }
|
|
14057
|
-
});
|
|
14058
|
-
return { success: true, error: null };
|
|
14059
|
-
} catch (err) {
|
|
14060
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
14061
|
-
return { success: false, error: msg };
|
|
14062
|
-
} finally {
|
|
14128
|
+
const cleanup = () => {
|
|
14063
14129
|
try {
|
|
14064
14130
|
fs32.unlinkSync(seedPath);
|
|
14065
14131
|
if (fs32.existsSync(scriptsDir) && fs32.readdirSync(scriptsDir).length === 0) {
|
|
@@ -14067,7 +14133,71 @@ async function runSeed(cwd, cmsDir, email, password3) {
|
|
|
14067
14133
|
}
|
|
14068
14134
|
} catch {
|
|
14069
14135
|
}
|
|
14070
|
-
}
|
|
14136
|
+
};
|
|
14137
|
+
return new Promise((resolve) => {
|
|
14138
|
+
const tsxBin = path37.join(cwd, "node_modules", ".bin", "tsx");
|
|
14139
|
+
const child = spawn2(tsxBin, [seedPath], {
|
|
14140
|
+
cwd,
|
|
14141
|
+
stdio: "pipe",
|
|
14142
|
+
env: {
|
|
14143
|
+
...process.env,
|
|
14144
|
+
SEED_EMAIL: email,
|
|
14145
|
+
SEED_PASSWORD: password3,
|
|
14146
|
+
SEED_NAME: "Admin",
|
|
14147
|
+
...overwrite ? { SEED_OVERWRITE: "true" } : {}
|
|
14148
|
+
}
|
|
14149
|
+
});
|
|
14150
|
+
let stdout = "";
|
|
14151
|
+
let stderr = "";
|
|
14152
|
+
child.stdout?.on("data", (chunk) => {
|
|
14153
|
+
stdout += chunk.toString();
|
|
14154
|
+
});
|
|
14155
|
+
child.stderr?.on("data", (chunk) => {
|
|
14156
|
+
stderr += chunk.toString();
|
|
14157
|
+
});
|
|
14158
|
+
const timeout = setTimeout(() => {
|
|
14159
|
+
child.kill();
|
|
14160
|
+
cleanup();
|
|
14161
|
+
resolve({ success: false, error: "Seed timed out after 30 seconds" });
|
|
14162
|
+
}, 3e4);
|
|
14163
|
+
child.on("close", (code) => {
|
|
14164
|
+
clearTimeout(timeout);
|
|
14165
|
+
cleanup();
|
|
14166
|
+
if (code === 0) {
|
|
14167
|
+
resolve({ success: true, error: null });
|
|
14168
|
+
} else if (code === 2) {
|
|
14169
|
+
const name = stdout.match(/EXISTING_USER:(.+)/)?.[1]?.trim();
|
|
14170
|
+
resolve({ success: false, error: null, existingUser: name ?? email });
|
|
14171
|
+
} else {
|
|
14172
|
+
resolve({ success: false, error: parseSeedError(stdout, stderr) });
|
|
14173
|
+
}
|
|
14174
|
+
});
|
|
14175
|
+
child.on("error", (err) => {
|
|
14176
|
+
clearTimeout(timeout);
|
|
14177
|
+
cleanup();
|
|
14178
|
+
resolve({ success: false, error: parseSeedError("", err.message) });
|
|
14179
|
+
});
|
|
14180
|
+
});
|
|
14181
|
+
}
|
|
14182
|
+
function parseSeedError(stdout, stderr) {
|
|
14183
|
+
const combined = `${stdout}
|
|
14184
|
+
${stderr}`;
|
|
14185
|
+
const seedFailed = combined.match(/Seed failed:\s*(.+)/)?.[1]?.trim();
|
|
14186
|
+
if (seedFailed) return seedFailed;
|
|
14187
|
+
if (combined.includes("Failed to create user")) return "Auth API failed to create user";
|
|
14188
|
+
if (combined.includes("ECONNREFUSED") || combined.includes("connection refused"))
|
|
14189
|
+
return "Could not connect to database";
|
|
14190
|
+
if (combined.includes("BETTERSTART_DATABASE_URL"))
|
|
14191
|
+
return "Database URL is missing or invalid";
|
|
14192
|
+
if (combined.includes("password authentication failed"))
|
|
14193
|
+
return "Database authentication failed \u2014 check your connection string";
|
|
14194
|
+
if (combined.includes("does not exist") && combined.includes("relation"))
|
|
14195
|
+
return "Database tables not found \u2014 run npx drizzle-kit push first";
|
|
14196
|
+
if (combined.includes("MODULE_NOT_FOUND") || combined.includes("Cannot find module"))
|
|
14197
|
+
return "Missing dependencies \u2014 run your package manager install first";
|
|
14198
|
+
const firstLine = stderr.split("\n").map((l) => l.trim()).find((l) => l.length > 0 && !l.startsWith("at ") && !l.startsWith("node:"));
|
|
14199
|
+
if (firstLine) return firstLine;
|
|
14200
|
+
return "Unknown error \u2014 run npx betterstart seed for details";
|
|
14071
14201
|
}
|
|
14072
14202
|
function runDrizzlePush(cwd) {
|
|
14073
14203
|
return new Promise((resolve) => {
|
|
@@ -14481,44 +14611,6 @@ function findNextNonEmptyLine(lines, startIndex) {
|
|
|
14481
14611
|
}
|
|
14482
14612
|
return null;
|
|
14483
14613
|
}
|
|
14484
|
-
function cleanPackageJsonDeps(pkgPath, allDeps, allDevDeps) {
|
|
14485
|
-
if (!fs34.existsSync(pkgPath)) return { removed: [], removedDev: [] };
|
|
14486
|
-
const content = fs34.readFileSync(pkgPath, "utf-8");
|
|
14487
|
-
let pkg;
|
|
14488
|
-
try {
|
|
14489
|
-
pkg = JSON.parse(content);
|
|
14490
|
-
} catch {
|
|
14491
|
-
return { removed: [], removedDev: [] };
|
|
14492
|
-
}
|
|
14493
|
-
const deps = pkg.dependencies ?? {};
|
|
14494
|
-
const devDeps = pkg.devDependencies ?? {};
|
|
14495
|
-
const depNames = new Set(allDeps.map((d) => d.split("@").slice(0, d.startsWith("@") ? 2 : 1).join("@")));
|
|
14496
|
-
const devDepNames = new Set(
|
|
14497
|
-
allDevDeps.map((d) => d.split("@").slice(0, d.startsWith("@") ? 2 : 1).join("@"))
|
|
14498
|
-
);
|
|
14499
|
-
const removed = [];
|
|
14500
|
-
for (const name of Object.keys(deps)) {
|
|
14501
|
-
if (depNames.has(name)) {
|
|
14502
|
-
delete deps[name];
|
|
14503
|
-
removed.push(name);
|
|
14504
|
-
}
|
|
14505
|
-
}
|
|
14506
|
-
const removedDev = [];
|
|
14507
|
-
for (const name of Object.keys(devDeps)) {
|
|
14508
|
-
if (devDepNames.has(name)) {
|
|
14509
|
-
delete devDeps[name];
|
|
14510
|
-
removedDev.push(name);
|
|
14511
|
-
}
|
|
14512
|
-
}
|
|
14513
|
-
if (removed.length === 0 && removedDev.length === 0) {
|
|
14514
|
-
return { removed: [], removedDev: [] };
|
|
14515
|
-
}
|
|
14516
|
-
pkg.dependencies = deps;
|
|
14517
|
-
pkg.devDependencies = devDeps;
|
|
14518
|
-
fs34.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
14519
|
-
`, "utf-8");
|
|
14520
|
-
return { removed, removedDev };
|
|
14521
|
-
}
|
|
14522
14614
|
|
|
14523
14615
|
// src/commands/uninstall.ts
|
|
14524
14616
|
function findMainCss2(cwd) {
|
|
@@ -14560,6 +14652,8 @@ function buildUninstallPlan(cwd) {
|
|
|
14560
14652
|
steps.push({
|
|
14561
14653
|
label: "CMS directories",
|
|
14562
14654
|
items: dirs,
|
|
14655
|
+
count: dirs.length,
|
|
14656
|
+
unit: dirs.length === 1 ? "directory" : "directories",
|
|
14563
14657
|
execute() {
|
|
14564
14658
|
if (fs35.existsSync(cmsDir)) fs35.rmSync(cmsDir, { recursive: true, force: true });
|
|
14565
14659
|
if (fs35.existsSync(cmsRouteGroup)) fs35.rmSync(cmsRouteGroup, { recursive: true, force: true });
|
|
@@ -14588,6 +14682,8 @@ function buildUninstallPlan(cwd) {
|
|
|
14588
14682
|
steps.push({
|
|
14589
14683
|
label: "Config files",
|
|
14590
14684
|
items: configFiles,
|
|
14685
|
+
count: configFiles.length,
|
|
14686
|
+
unit: configFiles.length === 1 ? "file" : "files",
|
|
14591
14687
|
execute() {
|
|
14592
14688
|
for (const p6 of configPaths) {
|
|
14593
14689
|
if (fs35.existsSync(p6)) fs35.unlinkSync(p6);
|
|
@@ -14598,10 +14694,14 @@ function buildUninstallPlan(cwd) {
|
|
|
14598
14694
|
const tsconfigPath = path40.join(cwd, "tsconfig.json");
|
|
14599
14695
|
if (fs35.existsSync(tsconfigPath)) {
|
|
14600
14696
|
const content = fs35.readFileSync(tsconfigPath, "utf-8");
|
|
14601
|
-
|
|
14697
|
+
const aliasMatches = content.match(/"@cms\//g);
|
|
14698
|
+
if (aliasMatches && aliasMatches.length > 0) {
|
|
14699
|
+
const aliasCount = aliasMatches.length;
|
|
14602
14700
|
steps.push({
|
|
14603
14701
|
label: "tsconfig.json path aliases",
|
|
14604
|
-
items: [
|
|
14702
|
+
items: [`@cms/* aliases in tsconfig.json`],
|
|
14703
|
+
count: aliasCount,
|
|
14704
|
+
unit: aliasCount === 1 ? "alias" : "aliases",
|
|
14605
14705
|
execute() {
|
|
14606
14706
|
cleanTsconfig(tsconfigPath);
|
|
14607
14707
|
}
|
|
@@ -14611,12 +14711,14 @@ function buildUninstallPlan(cwd) {
|
|
|
14611
14711
|
const cssFile = findMainCss2(cwd);
|
|
14612
14712
|
if (cssFile) {
|
|
14613
14713
|
const cssContent = fs35.readFileSync(cssFile, "utf-8");
|
|
14614
|
-
const
|
|
14615
|
-
if (
|
|
14714
|
+
const sourceLines = cssContent.split("\n").filter((l) => /^@source\s+"[^"]*cms[^"]*";\s*$/.test(l));
|
|
14715
|
+
if (sourceLines.length > 0) {
|
|
14616
14716
|
const relCss = path40.relative(cwd, cssFile);
|
|
14617
14717
|
steps.push({
|
|
14618
14718
|
label: `CSS @source lines (${relCss})`,
|
|
14619
|
-
items: [
|
|
14719
|
+
items: [`@source lines in ${relCss}`],
|
|
14720
|
+
count: sourceLines.length,
|
|
14721
|
+
unit: sourceLines.length === 1 ? "line" : "lines",
|
|
14620
14722
|
execute() {
|
|
14621
14723
|
cleanCss(cssFile);
|
|
14622
14724
|
}
|
|
@@ -14630,39 +14732,15 @@ function buildUninstallPlan(cwd) {
|
|
|
14630
14732
|
if (bsVars.length > 0) {
|
|
14631
14733
|
steps.push({
|
|
14632
14734
|
label: ".env.local variables",
|
|
14633
|
-
items:
|
|
14735
|
+
items: ["BETTERSTART_* vars in .env.local"],
|
|
14736
|
+
count: bsVars.length,
|
|
14737
|
+
unit: bsVars.length === 1 ? "variable" : "variables",
|
|
14634
14738
|
execute() {
|
|
14635
14739
|
cleanEnvFile(envPath);
|
|
14636
14740
|
}
|
|
14637
14741
|
});
|
|
14638
14742
|
}
|
|
14639
14743
|
}
|
|
14640
|
-
const pkgPath = path40.join(cwd, "package.json");
|
|
14641
|
-
if (fs35.existsSync(pkgPath)) {
|
|
14642
|
-
const allCoreDeps = [...CORE_DEPS, ...EMAIL_DEPS];
|
|
14643
|
-
const allDevDeps = [...DEV_DEPS, ...BIOME_DEV_DEPS];
|
|
14644
|
-
const coreNames = new Set(
|
|
14645
|
-
allCoreDeps.map((d) => d.split("@").slice(0, d.startsWith("@") ? 2 : 1).join("@"))
|
|
14646
|
-
);
|
|
14647
|
-
const devNames = new Set(
|
|
14648
|
-
allDevDeps.map((d) => d.split("@").slice(0, d.startsWith("@") ? 2 : 1).join("@"))
|
|
14649
|
-
);
|
|
14650
|
-
const pkgContent = JSON.parse(fs35.readFileSync(pkgPath, "utf-8"));
|
|
14651
|
-
const deps = Object.keys(pkgContent.dependencies ?? {}).filter((n) => coreNames.has(n));
|
|
14652
|
-
const devDeps = Object.keys(pkgContent.devDependencies ?? {}).filter((n) => devNames.has(n));
|
|
14653
|
-
if (deps.length > 0 || devDeps.length > 0) {
|
|
14654
|
-
const items = [];
|
|
14655
|
-
if (deps.length > 0) items.push(`${deps.length} dependencies`);
|
|
14656
|
-
if (devDeps.length > 0) items.push(`${devDeps.length} devDependencies`);
|
|
14657
|
-
steps.push({
|
|
14658
|
-
label: "package.json dependencies",
|
|
14659
|
-
items,
|
|
14660
|
-
execute() {
|
|
14661
|
-
cleanPackageJsonDeps(pkgPath, allCoreDeps, allDevDeps);
|
|
14662
|
-
}
|
|
14663
|
-
});
|
|
14664
|
-
}
|
|
14665
|
-
}
|
|
14666
14744
|
return steps;
|
|
14667
14745
|
}
|
|
14668
14746
|
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) => {
|
|
@@ -14670,60 +14748,39 @@ var uninstallCommand = new Command6("uninstall").description("Remove all CMS fil
|
|
|
14670
14748
|
p5.intro(pc3.bgRed(pc3.white(" BetterStart Uninstall ")));
|
|
14671
14749
|
const steps = buildUninstallPlan(cwd);
|
|
14672
14750
|
if (steps.length === 0) {
|
|
14673
|
-
p5.log.
|
|
14751
|
+
p5.log.success(pc3.green("\u2713") + " Nothing to remove \u2014 project is already clean.");
|
|
14674
14752
|
p5.outro("Done");
|
|
14675
14753
|
return;
|
|
14676
14754
|
}
|
|
14677
|
-
|
|
14678
|
-
|
|
14679
|
-
|
|
14680
|
-
|
|
14681
|
-
|
|
14682
|
-
|
|
14683
|
-
|
|
14684
|
-
|
|
14685
|
-
|
|
14686
|
-
|
|
14687
|
-
|
|
14688
|
-
|
|
14689
|
-
|
|
14690
|
-
|
|
14691
|
-
});
|
|
14692
|
-
if (p5.isCancel(confirmed)) {
|
|
14693
|
-
p5.cancel("Uninstall cancelled.");
|
|
14694
|
-
process.exit(0);
|
|
14695
|
-
}
|
|
14696
|
-
if (!confirmed) {
|
|
14697
|
-
p5.log.info(pc3.dim(`Skipped: ${step.label}`));
|
|
14698
|
-
continue;
|
|
14699
|
-
}
|
|
14755
|
+
const planLines = steps.map((step) => {
|
|
14756
|
+
const names = step.items.join(" ");
|
|
14757
|
+
const countLabel = pc3.dim(`${step.count} ${step.unit}`);
|
|
14758
|
+
return `${pc3.red("\xD7")} ${names} ${countLabel}`;
|
|
14759
|
+
});
|
|
14760
|
+
p5.note(planLines.join("\n"), "Uninstall plan");
|
|
14761
|
+
if (!options.force) {
|
|
14762
|
+
const confirmed = await p5.confirm({
|
|
14763
|
+
message: "Proceed with uninstall?",
|
|
14764
|
+
initialValue: false
|
|
14765
|
+
});
|
|
14766
|
+
if (p5.isCancel(confirmed) || !confirmed) {
|
|
14767
|
+
p5.cancel("Uninstall cancelled.");
|
|
14768
|
+
process.exit(0);
|
|
14700
14769
|
}
|
|
14701
|
-
step.execute();
|
|
14702
|
-
completedCount++;
|
|
14703
|
-
p5.log.success(`Removed: ${step.label}`);
|
|
14704
|
-
}
|
|
14705
|
-
p5.log.message("");
|
|
14706
|
-
if (completedCount === 0) {
|
|
14707
|
-
p5.log.info("No changes were made.");
|
|
14708
|
-
} else {
|
|
14709
|
-
const pm = detectPackageManager(cwd);
|
|
14710
|
-
p5.note(
|
|
14711
|
-
[
|
|
14712
|
-
`Run ${pc3.cyan(installCommand(pm))} to clean up node_modules.`,
|
|
14713
|
-
"",
|
|
14714
|
-
pc3.dim("Database tables were NOT dropped \u2014 drop them manually if needed.")
|
|
14715
|
-
].join("\n"),
|
|
14716
|
-
"Next steps"
|
|
14717
|
-
);
|
|
14718
14770
|
}
|
|
14719
|
-
|
|
14720
|
-
|
|
14721
|
-
|
|
14722
|
-
|
|
14723
|
-
|
|
14724
|
-
);
|
|
14771
|
+
const s = p5.spinner();
|
|
14772
|
+
s.start(steps[0].label);
|
|
14773
|
+
for (const step of steps) {
|
|
14774
|
+
s.message(step.label);
|
|
14775
|
+
step.execute();
|
|
14725
14776
|
}
|
|
14726
|
-
|
|
14777
|
+
const parts = steps.map((step) => `${step.count} ${step.unit}`);
|
|
14778
|
+
s.stop(`Removed ${parts.join(", ")}`);
|
|
14779
|
+
p5.note(
|
|
14780
|
+
pc3.dim("Database tables were NOT dropped \u2014 drop them manually if needed."),
|
|
14781
|
+
"Next steps"
|
|
14782
|
+
);
|
|
14783
|
+
p5.outro("Uninstall complete");
|
|
14727
14784
|
});
|
|
14728
14785
|
|
|
14729
14786
|
// src/commands/update-styles.ts
|