@betterstart/cli 0.1.27 → 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 +270 -191
- 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);
|
|
@@ -7972,14 +7972,6 @@ function openBrowser(url) {
|
|
|
7972
7972
|
// src/init/prompts/features.ts
|
|
7973
7973
|
import * as p2 from "@clack/prompts";
|
|
7974
7974
|
async function promptFeatures(presetOverride) {
|
|
7975
|
-
const includeEmail = await p2.confirm({
|
|
7976
|
-
message: "Include email system? (Resend + React Email)",
|
|
7977
|
-
initialValue: true
|
|
7978
|
-
});
|
|
7979
|
-
if (p2.isCancel(includeEmail)) {
|
|
7980
|
-
p2.cancel("Setup cancelled.");
|
|
7981
|
-
process.exit(0);
|
|
7982
|
-
}
|
|
7983
7975
|
let preset;
|
|
7984
7976
|
if (presetOverride && isValidPreset(presetOverride)) {
|
|
7985
7977
|
preset = presetOverride;
|
|
@@ -7999,7 +7991,7 @@ async function promptFeatures(presetOverride) {
|
|
|
7999
7991
|
}
|
|
8000
7992
|
preset = selected;
|
|
8001
7993
|
}
|
|
8002
|
-
return { includeEmail, preset };
|
|
7994
|
+
return { includeEmail: true, preset };
|
|
8003
7995
|
}
|
|
8004
7996
|
function isValidPreset(value) {
|
|
8005
7997
|
return value === "blank" || value === "blog" || value === "full";
|
|
@@ -13633,13 +13625,13 @@ var seedCommand = new Command2("seed").description("Create the initial admin use
|
|
|
13633
13625
|
}
|
|
13634
13626
|
});
|
|
13635
13627
|
});
|
|
13636
|
-
const
|
|
13637
|
-
|
|
13628
|
+
const spinner5 = clack.spinner();
|
|
13629
|
+
spinner5.start("Creating admin user...");
|
|
13638
13630
|
try {
|
|
13639
13631
|
const result = await runSeed2(false);
|
|
13640
13632
|
if (result.code === 2) {
|
|
13641
13633
|
const existingName = result.stdout.split("\n").find((l) => l.startsWith("EXISTING_USER:"))?.replace("EXISTING_USER:", "")?.trim() || "unknown";
|
|
13642
|
-
|
|
13634
|
+
spinner5.stop(`Account already exists for ${email}`);
|
|
13643
13635
|
const overwrite = await clack.confirm({
|
|
13644
13636
|
message: `An admin account (${existingName}) already exists with this email. Replace it?`
|
|
13645
13637
|
});
|
|
@@ -13651,14 +13643,14 @@ var seedCommand = new Command2("seed").description("Create the initial admin use
|
|
|
13651
13643
|
}
|
|
13652
13644
|
process.exit(0);
|
|
13653
13645
|
}
|
|
13654
|
-
|
|
13646
|
+
spinner5.start("Replacing admin user...");
|
|
13655
13647
|
await runSeed2(true);
|
|
13656
|
-
|
|
13648
|
+
spinner5.stop("Admin user replaced");
|
|
13657
13649
|
} else {
|
|
13658
|
-
|
|
13650
|
+
spinner5.stop("Admin user created");
|
|
13659
13651
|
}
|
|
13660
13652
|
} catch (err) {
|
|
13661
|
-
|
|
13653
|
+
spinner5.stop("Failed to create admin user");
|
|
13662
13654
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
13663
13655
|
clack.log.error(errMsg);
|
|
13664
13656
|
clack.log.info("You can run the seed script manually:");
|
|
@@ -13691,21 +13683,19 @@ var initCommand = new Command3("init").description("Scaffold CMS into a new or e
|
|
|
13691
13683
|
let isFreshProject = false;
|
|
13692
13684
|
let srcDir;
|
|
13693
13685
|
if (project.isExisting) {
|
|
13694
|
-
p4.log.info(`
|
|
13695
|
-
p4.log.info(`Package manager: ${pc2.cyan(pm)}`);
|
|
13686
|
+
p4.log.info(`Next.js project detected ${pc2.dim("\xB7")} ${pc2.cyan(pm)}`);
|
|
13696
13687
|
srcDir = project.hasSrcDir;
|
|
13697
13688
|
if (!project.hasTypeScript) {
|
|
13698
13689
|
p4.log.error("TypeScript is required. Please add a tsconfig.json first.");
|
|
13699
13690
|
process.exit(1);
|
|
13700
13691
|
}
|
|
13701
13692
|
if (project.conflicts.length > 0) {
|
|
13702
|
-
|
|
13703
|
-
|
|
13704
|
-
|
|
13705
|
-
}
|
|
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"));
|
|
13706
13696
|
if (!options.yes) {
|
|
13707
13697
|
const proceed = await p4.confirm({
|
|
13708
|
-
message: "Continue anyway?
|
|
13698
|
+
message: "Continue anyway?",
|
|
13709
13699
|
initialValue: true
|
|
13710
13700
|
});
|
|
13711
13701
|
if (p4.isCancel(proceed) || !proceed) {
|
|
@@ -13814,30 +13804,62 @@ var initCommand = new Command3("init").description("Scaffold CMS into a new or e
|
|
|
13814
13804
|
...getDefaultConfig(srcDir),
|
|
13815
13805
|
features: { email: features.includeEmail }
|
|
13816
13806
|
};
|
|
13807
|
+
const results = [];
|
|
13817
13808
|
const s = p4.spinner();
|
|
13818
|
-
s.start("
|
|
13809
|
+
s.start("Directory structure");
|
|
13819
13810
|
const baseFiles = scaffoldBase({ cwd, config });
|
|
13820
|
-
|
|
13821
|
-
s.
|
|
13811
|
+
results.push({ label: "Directory structure", result: `${baseFiles.length} files` });
|
|
13812
|
+
s.message("TypeScript aliases");
|
|
13822
13813
|
const tsResult = scaffoldTsconfig(cwd);
|
|
13823
|
-
|
|
13824
|
-
|
|
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");
|
|
13825
13819
|
const twResult = scaffoldTailwind(cwd, srcDir);
|
|
13826
|
-
|
|
13827
|
-
|
|
13828
|
-
|
|
13829
|
-
|
|
13830
|
-
|
|
13831
|
-
s.stop("No CSS file found (will configure later)");
|
|
13832
|
-
}
|
|
13833
|
-
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");
|
|
13834
13825
|
const envResult = scaffoldEnv(cwd, { includeEmail: features.includeEmail, databaseUrl });
|
|
13835
|
-
const
|
|
13836
|
-
|
|
13837
|
-
|
|
13838
|
-
|
|
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");
|
|
13839
13832
|
const dbFiles = scaffoldDatabase({ cwd, config });
|
|
13840
|
-
|
|
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");
|
|
13841
13863
|
const drizzleConfigPath = path37.join(cwd, "drizzle.config.ts");
|
|
13842
13864
|
if (!dbFiles.includes("drizzle.config.ts") && fs32.existsSync(drizzleConfigPath)) {
|
|
13843
13865
|
if (!options.yes) {
|
|
@@ -13852,42 +13874,17 @@ var initCommand = new Command3("init").description("Scaffold CMS into a new or e
|
|
|
13852
13874
|
}
|
|
13853
13875
|
}
|
|
13854
13876
|
}
|
|
13855
|
-
s.start("
|
|
13856
|
-
const authFiles = scaffoldAuth({ cwd, config });
|
|
13857
|
-
s.stop(`Created ${authFiles.length} auth files`);
|
|
13858
|
-
s.start("Copying CMS components...");
|
|
13859
|
-
const compFiles = scaffoldComponents({ cwd, config });
|
|
13860
|
-
s.stop(`Created ${compFiles.length} component files`);
|
|
13861
|
-
s.start("Creating CMS pages and layouts...");
|
|
13862
|
-
const layoutFiles = scaffoldLayout({ cwd, config });
|
|
13863
|
-
s.stop(`Created ${layoutFiles.length} page files`);
|
|
13864
|
-
s.start("Creating API routes...");
|
|
13865
|
-
const apiFiles = scaffoldApiRoutes({ cwd, config });
|
|
13866
|
-
s.stop(`Created ${apiFiles.length} API routes`);
|
|
13867
|
-
s.start("Checking for linter...");
|
|
13868
|
-
if (project.linter.type === "none") {
|
|
13869
|
-
s.stop("No linter found");
|
|
13870
|
-
s.start("Setting up Biome linter...");
|
|
13871
|
-
const biomeResult = scaffoldBiome(cwd, project.linter);
|
|
13872
|
-
if (biomeResult.installed) {
|
|
13873
|
-
s.stop("Created biome.json");
|
|
13874
|
-
} else {
|
|
13875
|
-
s.stop(`Biome skipped: ${biomeResult.skippedReason}`);
|
|
13876
|
-
}
|
|
13877
|
-
} else {
|
|
13878
|
-
s.stop(`Linter: ${pc2.cyan(project.linter.type)} (${project.linter.configFile})`);
|
|
13879
|
-
}
|
|
13880
|
-
s.start("Installing dependencies (this may take a minute)...");
|
|
13877
|
+
s.start("Installing dependencies (this may take a minute)");
|
|
13881
13878
|
const depsResult = await installDependenciesAsync({
|
|
13882
13879
|
cwd,
|
|
13883
13880
|
pm,
|
|
13884
13881
|
includeEmail: features.includeEmail,
|
|
13885
13882
|
includeBiome: project.linter.type === "none"
|
|
13886
13883
|
});
|
|
13884
|
+
let depsInstalled = false;
|
|
13887
13885
|
if (depsResult.success) {
|
|
13888
|
-
s.stop(
|
|
13889
|
-
|
|
13890
|
-
);
|
|
13886
|
+
s.stop("");
|
|
13887
|
+
depsInstalled = true;
|
|
13891
13888
|
} else {
|
|
13892
13889
|
s.stop("Failed to install dependencies");
|
|
13893
13890
|
p4.log.warning(depsResult.error ?? "Unknown error");
|
|
@@ -13897,24 +13894,59 @@ var initCommand = new Command3("init").description("Scaffold CMS into a new or e
|
|
|
13897
13894
|
${pc2.cyan(`${pm} add -D ${depsResult.devDeps.join(" ")}`)}`
|
|
13898
13895
|
);
|
|
13899
13896
|
}
|
|
13900
|
-
|
|
13897
|
+
if (depsInstalled) {
|
|
13898
|
+
process.stdout.write("\x1B[2A\x1B[J");
|
|
13899
|
+
}
|
|
13900
|
+
s.start(`Applying ${features.preset} preset`);
|
|
13901
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
|
+
}
|
|
13902
13931
|
if (presetResult.errors.length > 0) {
|
|
13903
|
-
|
|
13932
|
+
installLines.push(
|
|
13933
|
+
`${pc2.yellow("\u25B2")} Preset ${pc2.dim(`${features.preset} \u2014 ${presetResult.errors.length} warning(s)`)}`
|
|
13934
|
+
);
|
|
13904
13935
|
for (const err of presetResult.errors) {
|
|
13905
|
-
|
|
13936
|
+
installLines.push(` ${pc2.dim(err)}`);
|
|
13906
13937
|
}
|
|
13907
13938
|
} else {
|
|
13908
|
-
|
|
13909
|
-
|
|
13939
|
+
installLines.push(
|
|
13940
|
+
`${pc2.green("\u2713")} Preset ${pc2.dim(`${features.preset} \u2014 ${presetResult.schemas.length} schemas, ${presetResult.generatedFiles.length} files`)}`
|
|
13910
13941
|
);
|
|
13911
13942
|
}
|
|
13943
|
+
p4.note(installLines.join("\n"), "Installed");
|
|
13912
13944
|
let dbPushed = false;
|
|
13913
13945
|
if (depsResult.success && hasDbUrl(cwd)) {
|
|
13914
|
-
s.start("Pushing database schema (drizzle-kit push)
|
|
13946
|
+
s.start("Pushing database schema (drizzle-kit push)");
|
|
13915
13947
|
const pushResult = await runDrizzlePush(cwd);
|
|
13916
13948
|
if (pushResult.success) {
|
|
13917
|
-
s.stop("Database schema pushed
|
|
13949
|
+
s.stop(`${pc2.green("\u2713")} Database schema pushed`);
|
|
13918
13950
|
dbPushed = true;
|
|
13919
13951
|
} else {
|
|
13920
13952
|
s.stop("Database push failed");
|
|
@@ -13926,66 +13958,62 @@ var initCommand = new Command3("init").description("Scaffold CMS into a new or e
|
|
|
13926
13958
|
let seedPassword;
|
|
13927
13959
|
let seedSuccess = false;
|
|
13928
13960
|
if (dbPushed && !options.yes) {
|
|
13929
|
-
p4.
|
|
13930
|
-
const
|
|
13931
|
-
|
|
13932
|
-
|
|
13933
|
-
|
|
13934
|
-
|
|
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
|
+
}
|
|
13935
13983
|
}
|
|
13936
|
-
|
|
13937
|
-
|
|
13938
|
-
|
|
13939
|
-
|
|
13940
|
-
|
|
13941
|
-
|
|
13942
|
-
|
|
13943
|
-
|
|
13944
|
-
|
|
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;
|
|
13945
14000
|
}
|
|
13946
|
-
});
|
|
13947
|
-
if (p4.isCancel(password3)) {
|
|
13948
|
-
p4.cancel("Setup cancelled.");
|
|
13949
|
-
process.exit(0);
|
|
13950
14001
|
}
|
|
13951
|
-
seedEmail = email;
|
|
13952
|
-
seedPassword = password3;
|
|
13953
|
-
s.start("Creating admin user...");
|
|
13954
|
-
const seedResult = await runSeed(cwd, config.paths?.cms ?? "./cms", email, password3);
|
|
13955
14002
|
if (seedResult.success) {
|
|
13956
|
-
s.stop("Admin user created
|
|
14003
|
+
s.stop(`${pc2.green("\u2713")} Admin user created`);
|
|
13957
14004
|
seedSuccess = true;
|
|
13958
|
-
} else {
|
|
13959
|
-
s.stop("Failed to create admin user
|
|
13960
|
-
p4.
|
|
13961
|
-
|
|
13962
|
-
|
|
13963
|
-
|
|
13964
|
-
|
|
13965
|
-
|
|
13966
|
-
const entityNames = [];
|
|
13967
|
-
const formNames = [];
|
|
13968
|
-
const schemasDir = path37.join(cwd, config.paths.schemas);
|
|
13969
|
-
const formsDir = path37.join(schemasDir, "forms");
|
|
13970
|
-
if (fs32.existsSync(schemasDir)) {
|
|
13971
|
-
for (const f of fs32.readdirSync(schemasDir)) {
|
|
13972
|
-
if (f.endsWith(".json")) entityNames.push(f.replace(".json", ""));
|
|
13973
|
-
}
|
|
13974
|
-
}
|
|
13975
|
-
if (fs32.existsSync(formsDir)) {
|
|
13976
|
-
for (const f of fs32.readdirSync(formsDir)) {
|
|
13977
|
-
if (f.endsWith(".json")) formNames.push(f.replace(".json", ""));
|
|
13978
|
-
}
|
|
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
|
+
);
|
|
13979
14013
|
}
|
|
13980
|
-
regenerateCmsDoc(cwd, config, {
|
|
13981
|
-
preset: features.preset,
|
|
13982
|
-
schemas: entityNames,
|
|
13983
|
-
forms: formNames
|
|
13984
|
-
});
|
|
13985
14014
|
}
|
|
13986
|
-
s.stop("Generated CMS.md");
|
|
13987
14015
|
if (isFreshProject) {
|
|
13988
|
-
s.start("Creating initial git commit
|
|
14016
|
+
s.start("Creating initial git commit");
|
|
13989
14017
|
try {
|
|
13990
14018
|
execFileSync4("git", ["init"], { cwd, stdio: "pipe" });
|
|
13991
14019
|
execFileSync4("git", ["add", "."], { cwd, stdio: "pipe" });
|
|
@@ -14001,7 +14029,6 @@ var initCommand = new Command3("init").description("Scaffold CMS into a new or e
|
|
|
14001
14029
|
const totalFiles = baseFiles.length + dbFiles.length + authFiles.length + compFiles.length + layoutFiles.length + apiFiles.length;
|
|
14002
14030
|
const summaryLines = [
|
|
14003
14031
|
`Preset: ${pc2.cyan(features.preset)}`,
|
|
14004
|
-
`Email: ${features.includeEmail ? pc2.green("yes") : pc2.dim("no")}`,
|
|
14005
14032
|
`Files created: ${pc2.cyan(String(totalFiles))}`,
|
|
14006
14033
|
`Env vars: ${envResult.added.length} added, ${envResult.skipped.length} skipped`
|
|
14007
14034
|
];
|
|
@@ -14091,26 +14118,14 @@ function hasDbUrl(cwd) {
|
|
|
14091
14118
|
}
|
|
14092
14119
|
return false;
|
|
14093
14120
|
}
|
|
14094
|
-
|
|
14121
|
+
function runSeed(cwd, cmsDir, email, password3, overwrite = false) {
|
|
14095
14122
|
const scriptsDir = path37.join(cwd, cmsDir, "scripts");
|
|
14096
14123
|
const seedPath = path37.join(scriptsDir, "seed.ts");
|
|
14097
14124
|
if (!fs32.existsSync(scriptsDir)) {
|
|
14098
14125
|
fs32.mkdirSync(scriptsDir, { recursive: true });
|
|
14099
14126
|
}
|
|
14100
14127
|
fs32.writeFileSync(seedPath, buildSeedScript(), "utf-8");
|
|
14101
|
-
|
|
14102
|
-
const tsxBin = path37.join(cwd, "node_modules", ".bin", "tsx");
|
|
14103
|
-
execFileSync4(tsxBin, [seedPath], {
|
|
14104
|
-
cwd,
|
|
14105
|
-
stdio: "pipe",
|
|
14106
|
-
timeout: 3e4,
|
|
14107
|
-
env: { ...process.env, SEED_EMAIL: email, SEED_PASSWORD: password3, SEED_NAME: "Admin" }
|
|
14108
|
-
});
|
|
14109
|
-
return { success: true, error: null };
|
|
14110
|
-
} catch (err) {
|
|
14111
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
14112
|
-
return { success: false, error: msg };
|
|
14113
|
-
} finally {
|
|
14128
|
+
const cleanup = () => {
|
|
14114
14129
|
try {
|
|
14115
14130
|
fs32.unlinkSync(seedPath);
|
|
14116
14131
|
if (fs32.existsSync(scriptsDir) && fs32.readdirSync(scriptsDir).length === 0) {
|
|
@@ -14118,7 +14133,71 @@ async function runSeed(cwd, cmsDir, email, password3) {
|
|
|
14118
14133
|
}
|
|
14119
14134
|
} catch {
|
|
14120
14135
|
}
|
|
14121
|
-
}
|
|
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";
|
|
14122
14201
|
}
|
|
14123
14202
|
function runDrizzlePush(cwd) {
|
|
14124
14203
|
return new Promise((resolve) => {
|
|
@@ -14573,6 +14652,8 @@ function buildUninstallPlan(cwd) {
|
|
|
14573
14652
|
steps.push({
|
|
14574
14653
|
label: "CMS directories",
|
|
14575
14654
|
items: dirs,
|
|
14655
|
+
count: dirs.length,
|
|
14656
|
+
unit: dirs.length === 1 ? "directory" : "directories",
|
|
14576
14657
|
execute() {
|
|
14577
14658
|
if (fs35.existsSync(cmsDir)) fs35.rmSync(cmsDir, { recursive: true, force: true });
|
|
14578
14659
|
if (fs35.existsSync(cmsRouteGroup)) fs35.rmSync(cmsRouteGroup, { recursive: true, force: true });
|
|
@@ -14601,6 +14682,8 @@ function buildUninstallPlan(cwd) {
|
|
|
14601
14682
|
steps.push({
|
|
14602
14683
|
label: "Config files",
|
|
14603
14684
|
items: configFiles,
|
|
14685
|
+
count: configFiles.length,
|
|
14686
|
+
unit: configFiles.length === 1 ? "file" : "files",
|
|
14604
14687
|
execute() {
|
|
14605
14688
|
for (const p6 of configPaths) {
|
|
14606
14689
|
if (fs35.existsSync(p6)) fs35.unlinkSync(p6);
|
|
@@ -14611,10 +14694,14 @@ function buildUninstallPlan(cwd) {
|
|
|
14611
14694
|
const tsconfigPath = path40.join(cwd, "tsconfig.json");
|
|
14612
14695
|
if (fs35.existsSync(tsconfigPath)) {
|
|
14613
14696
|
const content = fs35.readFileSync(tsconfigPath, "utf-8");
|
|
14614
|
-
|
|
14697
|
+
const aliasMatches = content.match(/"@cms\//g);
|
|
14698
|
+
if (aliasMatches && aliasMatches.length > 0) {
|
|
14699
|
+
const aliasCount = aliasMatches.length;
|
|
14615
14700
|
steps.push({
|
|
14616
14701
|
label: "tsconfig.json path aliases",
|
|
14617
|
-
items: [
|
|
14702
|
+
items: [`@cms/* aliases in tsconfig.json`],
|
|
14703
|
+
count: aliasCount,
|
|
14704
|
+
unit: aliasCount === 1 ? "alias" : "aliases",
|
|
14618
14705
|
execute() {
|
|
14619
14706
|
cleanTsconfig(tsconfigPath);
|
|
14620
14707
|
}
|
|
@@ -14624,12 +14711,14 @@ function buildUninstallPlan(cwd) {
|
|
|
14624
14711
|
const cssFile = findMainCss2(cwd);
|
|
14625
14712
|
if (cssFile) {
|
|
14626
14713
|
const cssContent = fs35.readFileSync(cssFile, "utf-8");
|
|
14627
|
-
const
|
|
14628
|
-
if (
|
|
14714
|
+
const sourceLines = cssContent.split("\n").filter((l) => /^@source\s+"[^"]*cms[^"]*";\s*$/.test(l));
|
|
14715
|
+
if (sourceLines.length > 0) {
|
|
14629
14716
|
const relCss = path40.relative(cwd, cssFile);
|
|
14630
14717
|
steps.push({
|
|
14631
14718
|
label: `CSS @source lines (${relCss})`,
|
|
14632
|
-
items: [
|
|
14719
|
+
items: [`@source lines in ${relCss}`],
|
|
14720
|
+
count: sourceLines.length,
|
|
14721
|
+
unit: sourceLines.length === 1 ? "line" : "lines",
|
|
14633
14722
|
execute() {
|
|
14634
14723
|
cleanCss(cssFile);
|
|
14635
14724
|
}
|
|
@@ -14643,7 +14732,9 @@ function buildUninstallPlan(cwd) {
|
|
|
14643
14732
|
if (bsVars.length > 0) {
|
|
14644
14733
|
steps.push({
|
|
14645
14734
|
label: ".env.local variables",
|
|
14646
|
-
items:
|
|
14735
|
+
items: ["BETTERSTART_* vars in .env.local"],
|
|
14736
|
+
count: bsVars.length,
|
|
14737
|
+
unit: bsVars.length === 1 ? "variable" : "variables",
|
|
14647
14738
|
execute() {
|
|
14648
14739
|
cleanEnvFile(envPath);
|
|
14649
14740
|
}
|
|
@@ -14657,21 +14748,19 @@ var uninstallCommand = new Command6("uninstall").description("Remove all CMS fil
|
|
|
14657
14748
|
p5.intro(pc3.bgRed(pc3.white(" BetterStart Uninstall ")));
|
|
14658
14749
|
const steps = buildUninstallPlan(cwd);
|
|
14659
14750
|
if (steps.length === 0) {
|
|
14660
|
-
p5.log.
|
|
14751
|
+
p5.log.success(pc3.green("\u2713") + " Nothing to remove \u2014 project is already clean.");
|
|
14661
14752
|
p5.outro("Done");
|
|
14662
14753
|
return;
|
|
14663
14754
|
}
|
|
14664
|
-
|
|
14665
|
-
|
|
14666
|
-
|
|
14667
|
-
|
|
14668
|
-
|
|
14669
|
-
|
|
14670
|
-
p5.log.message("");
|
|
14671
|
-
}
|
|
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");
|
|
14672
14761
|
if (!options.force) {
|
|
14673
14762
|
const confirmed = await p5.confirm({
|
|
14674
|
-
message:
|
|
14763
|
+
message: "Proceed with uninstall?",
|
|
14675
14764
|
initialValue: false
|
|
14676
14765
|
});
|
|
14677
14766
|
if (p5.isCancel(confirmed) || !confirmed) {
|
|
@@ -14679,29 +14768,19 @@ var uninstallCommand = new Command6("uninstall").description("Remove all CMS fil
|
|
|
14679
14768
|
process.exit(0);
|
|
14680
14769
|
}
|
|
14681
14770
|
}
|
|
14682
|
-
|
|
14771
|
+
const s = p5.spinner();
|
|
14772
|
+
s.start(steps[0].label);
|
|
14683
14773
|
for (const step of steps) {
|
|
14774
|
+
s.message(step.label);
|
|
14684
14775
|
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
14776
|
}
|
|
14697
|
-
|
|
14698
|
-
|
|
14699
|
-
|
|
14700
|
-
|
|
14701
|
-
|
|
14702
|
-
|
|
14703
|
-
|
|
14704
|
-
p5.outro(completedCount > 0 ? "Uninstall complete" : "Done");
|
|
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");
|
|
14705
14784
|
});
|
|
14706
14785
|
|
|
14707
14786
|
// src/commands/update-styles.ts
|