betterstart-cli 0.0.92 → 0.0.93

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
@@ -52,6 +52,9 @@ async function loadConfigFile(configPath) {
52
52
  function isInteractiveSession() {
53
53
  return Boolean(process.stdin.isTTY) && process.env.CI !== "true";
54
54
  }
55
+ function isInteractiveTerminalSession() {
56
+ return isInteractiveSession() && Boolean(process.stdout.isTTY);
57
+ }
55
58
 
56
59
  // core-engine/commands/runtime.ts
57
60
  import { Argument, Command } from "commander";
@@ -185,15 +188,18 @@ function createUpdateStylesCommand(runtime) {
185
188
  // core-engine/commands/default-action.ts
186
189
  var ADD_COMMAND = "add";
187
190
  var CREATE_COMMAND = "create";
191
+ var GENERATE_COMMAND = "generate";
188
192
  var INIT_COMMAND = "init";
189
193
  var REMOVE_COMMAND = "remove";
190
194
  var REMOVE_SCHEMA_COMMAND = "remove-schema";
195
+ var UPDATE_COMMAND = "update";
196
+ var ALL_FLAG = "--all";
191
197
  var CANCELLED = /* @__PURE__ */ Symbol("cancelled");
192
198
  function cancelled(value) {
193
199
  return value === CANCELLED;
194
200
  }
195
201
  async function runDefaultAction(program2, runtime) {
196
- if (!isInteractiveSession()) {
202
+ if (!isInteractiveTerminalSession()) {
197
203
  program2.outputHelp();
198
204
  return;
199
205
  }
@@ -238,6 +244,24 @@ async function promptRequiredArguments(name, runtime, cwd) {
238
244
  if (name === ADD_COMMAND || name === REMOVE_COMMAND) {
239
245
  return promptInstallables(name, runtime, cwd);
240
246
  }
247
+ if (name === GENERATE_COMMAND) {
248
+ const schemas = await runtime.listSchemaChoices(cwd);
249
+ if (schemas.length === 0) {
250
+ p.log.warn("No schemas to generate.");
251
+ return CANCELLED;
252
+ }
253
+ const target = await p.select({
254
+ message: "What do you want to generate?",
255
+ options: [
256
+ ...schemas.map((value) => ({ value, label: value })),
257
+ { value: ALL_FLAG, label: "All schemas", hint: "regenerate everything" }
258
+ ]
259
+ });
260
+ return p.isCancel(target) ? CANCELLED : [target];
261
+ }
262
+ if (name === UPDATE_COMMAND) {
263
+ return promptComponents(runtime, cwd);
264
+ }
241
265
  if (name === REMOVE_SCHEMA_COMMAND) {
242
266
  const schemas = await runtime.listSchemaChoices(cwd);
243
267
  if (schemas.length === 0) {
@@ -252,6 +276,31 @@ async function promptRequiredArguments(name, runtime, cwd) {
252
276
  }
253
277
  return [];
254
278
  }
279
+ async function promptComponents(runtime, cwd) {
280
+ const scope = await p.select({
281
+ message: "What do you want to update?",
282
+ options: [
283
+ { value: ALL_FLAG, label: "All components", hint: "every installed component" },
284
+ { value: "pick", label: "Choose components" }
285
+ ]
286
+ });
287
+ if (p.isCancel(scope)) {
288
+ return CANCELLED;
289
+ }
290
+ if (scope === ALL_FLAG) {
291
+ return [ALL_FLAG];
292
+ }
293
+ const components = await runtime.listComponentChoices(cwd);
294
+ if (components.length === 0) {
295
+ p.log.warn("No components available to update.");
296
+ return CANCELLED;
297
+ }
298
+ const selected = await p.multiselect({
299
+ message: "Components to update",
300
+ options: components.map((value) => ({ value, label: value }))
301
+ });
302
+ return p.isCancel(selected) ? CANCELLED : selected;
303
+ }
255
304
  async function promptInstallables(name, runtime, cwd) {
256
305
  const removing = name === REMOVE_COMMAND;
257
306
  const choices = await runtime.listInstallableChoices(cwd);
@@ -25055,8 +25104,16 @@ function scaffoldLayout({ cwd, config }) {
25055
25104
  readTemplate("pages/settings/forms/forms-settings-page-content.tsx")
25056
25105
  );
25057
25106
  write(
25058
- path43.join(settingsFormsDir, "edit-form-notifications-dialog.tsx"),
25059
- readTemplate("pages/settings/forms/edit-form-notifications-dialog.tsx")
25107
+ path43.join(settingsFormsDir, "forms-settings-columns.tsx"),
25108
+ readTemplate("pages/settings/forms/forms-settings-columns.tsx")
25109
+ );
25110
+ write(
25111
+ path43.join(settingsFormsDir, "forms-settings-table.tsx"),
25112
+ readTemplate("pages/settings/forms/forms-settings-table.tsx")
25113
+ );
25114
+ write(
25115
+ path43.join(settingsFormsDir, "form-notifications-drawer.tsx"),
25116
+ readTemplate("pages/settings/forms/form-notifications-drawer.tsx")
25060
25117
  );
25061
25118
  const settingsWebhooksDir = path43.join(settingsDir, "webhooks");
25062
25119
  write(
@@ -28914,581 +28971,13 @@ async function runListPresetsCommand(options) {
28914
28971
  }
28915
28972
 
28916
28973
  // adapters/next/commands/menu-choices.ts
28917
- import path55 from "path";
28918
- async function listInstallableChoices(cwd) {
28919
- const config = await resolveConfigOrExit(cwd);
28920
- const installedPresets = new Set(config.presets.installed);
28921
- const installedIntegrations = new Set(config.integrations.installed);
28922
- return {
28923
- presets: listAvailablePresets().map((preset) => ({
28924
- id: preset.id,
28925
- description: preset.description,
28926
- installed: installedPresets.has(preset.id)
28927
- })),
28928
- integrations: listAvailableIntegrations().map((integration) => ({
28929
- id: integration.id,
28930
- description: integration.description,
28931
- installed: installedIntegrations.has(integration.id)
28932
- }))
28933
- };
28934
- }
28935
- async function listSchemaChoices(cwd) {
28936
- const config = await resolveConfigOrExit(cwd);
28937
- const paths = resolveProjectPaths(config);
28938
- return listSchemaNames(path55.join(cwd, ...paths.schemasDir.split("/")));
28939
- }
28940
-
28941
- // adapters/next/commands/remove.ts
28942
28974
  import path56 from "path";
28943
- import * as p29 from "@clack/prompts";
28944
- async function runRemoveCommand(items, options) {
28945
- const removeIntegrationsMode = Boolean(options.integration);
28946
- if (!removeIntegrationsMode && items.includes("core")) {
28947
- p29.log.error("The core Admin cannot be removed.");
28948
- process.exit(1);
28949
- }
28950
- const presetIds = items.filter(isPresetId);
28951
- const integrationIds = items.filter(isIntegrationId);
28952
- if (!removeIntegrationsMode && integrationIds.length > 0) {
28953
- p29.log.error(
28954
- `Integration IDs require --integration. Run \`betterstart remove --integration ${integrationIds.join(" ")}\`.`
28955
- );
28956
- process.exit(1);
28957
- }
28958
- if (removeIntegrationsMode && presetIds.length > 0) {
28959
- p29.log.error(`Preset IDs cannot be removed with --integration: ${presetIds.join(", ")}`);
28960
- process.exit(1);
28961
- }
28962
- const invalidItems = removeIntegrationsMode ? items.filter((integrationId) => !isIntegrationId(integrationId)) : items.filter((presetId) => !isPresetId(presetId));
28963
- if (invalidItems.length > 0) {
28964
- p29.log.error(
28965
- removeIntegrationsMode ? formatUnknownIntegrationMessage(invalidItems) : formatUnknownPresetMessage(invalidItems)
28966
- );
28967
- process.exit(1);
28968
- }
28969
- const cwd = options.cwd ? path56.resolve(options.cwd) : process.cwd();
28970
- const config = await resolveConfigOrExit(cwd);
28971
- const pm = detectPackageManager(cwd);
28972
- if (!options.force) {
28973
- const confirmed = await p29.confirm({
28974
- message: `Remove ${removeIntegrationsMode ? "integration" : "preset"}${items.length === 1 ? "" : "s"} ${items.join(", ")}?`,
28975
- initialValue: false
28976
- });
28977
- if (p29.isCancel(confirmed) || !confirmed) {
28978
- p29.cancel(`${removeIntegrationsMode ? "Integration" : "Preset"} removal cancelled.`);
28979
- process.exit(0);
28980
- }
28981
- }
28982
- if (removeIntegrationsMode) {
28983
- const result2 = await removeIntegrations({
28984
- cwd,
28985
- config,
28986
- pm,
28987
- integrationIds
28988
- });
28989
- writeConfigFile(cwd, result2.config);
28990
- if (result2.removed.length === 0) {
28991
- p29.outro("No integrations were removed.");
28992
- return;
28993
- }
28994
- if (result2.warnings.length > 0) {
28995
- p29.note(result2.warnings.join("\n"), "Warnings");
28996
- }
28997
- p29.outro(
28998
- `Removed integration${result2.removed.length === 1 ? "" : "s"}: ${result2.removed.join(", ")}`
28999
- );
29000
- return;
29001
- }
29002
- const result = await removePresets({
29003
- cwd,
29004
- config,
29005
- pm,
29006
- presetIds
29007
- });
29008
- writeConfigFile(cwd, result.config);
29009
- if (result.removed.length === 0) {
29010
- p29.outro("No presets were removed.");
29011
- return;
29012
- }
29013
- if (result.warnings.length > 0) {
29014
- p29.note(result.warnings.join("\n"), "Warnings");
29015
- }
29016
- p29.outro(`Removed preset${result.removed.length === 1 ? "" : "s"}: ${result.removed.join(", ")}`);
29017
- }
29018
28975
 
29019
- // adapters/next/commands/remove-schema.ts
28976
+ // adapters/next/commands/update-component.ts
28977
+ import { execFileSync as execFileSync5 } from "child_process";
29020
28978
  import fs42 from "fs";
29021
- import path57 from "path";
28979
+ import path55 from "path";
29022
28980
  import * as clack3 from "@clack/prompts";
29023
- function removePath2(cwd, filePath) {
29024
- const fullPath = path57.join(cwd, ...filePath.split("/"));
29025
- const existed = fs42.existsSync(fullPath);
29026
- fs42.rmSync(fullPath, { recursive: true, force: true });
29027
- return existed;
29028
- }
29029
- function cleanupEmptyDirs3(cwd, deletedPaths, configPaths) {
29030
- const stopRoots = /* @__PURE__ */ new Set([
29031
- path57.join(cwd, ...configPaths.adminDir.split("/")),
29032
- path57.join(cwd, ...configPaths.adminNavigationDir.split("/")),
29033
- path57.join(cwd, ...configPaths.adminWebhookEventsDir.split("/")),
29034
- path57.join(cwd, ...configPaths.pagesDir.split("/"))
29035
- ]);
29036
- for (const deletedPath of deletedPaths) {
29037
- let current = path57.dirname(path57.join(cwd, ...deletedPath.split("/")));
29038
- while (!stopRoots.has(current)) {
29039
- if (!fs42.existsSync(current)) {
29040
- current = path57.dirname(current);
29041
- continue;
29042
- }
29043
- const entries = fs42.readdirSync(current);
29044
- if (entries.length > 0) {
29045
- break;
29046
- }
29047
- fs42.rmdirSync(current);
29048
- current = path57.dirname(current);
29049
- }
29050
- }
29051
- }
29052
- function resolveSchemaOwnerForRemoval(cwd, schemaName) {
29053
- const explicitOwner = getSchemaOwner(cwd, schemaName);
29054
- if (explicitOwner) {
29055
- return explicitOwner;
29056
- }
29057
- if (schemaName === "settings") {
29058
- return "core";
29059
- }
29060
- return "user";
29061
- }
29062
- async function runRemoveSchemaCommand(schemaName, options) {
29063
- const owner = resolveSchemaOwnerForRemoval(
29064
- options.cwd ? path57.resolve(options.cwd) : process.cwd(),
29065
- schemaName
29066
- );
29067
- if (owner === "core") {
29068
- clack3.log.error(
29069
- `"${schemaName}" is a core Admin module and cannot be removed with remove-schema.`
29070
- );
29071
- process.exit(1);
29072
- }
29073
- if (owner.startsWith("preset:")) {
29074
- clack3.log.error(`"${schemaName}" is owned by ${owner}. Remove the owning preset instead.`);
29075
- process.exit(1);
29076
- }
29077
- const cwd = options.cwd ? path57.resolve(options.cwd) : process.cwd();
29078
- const config = await resolveConfigOrExit(cwd);
29079
- const paths = resolveProjectPaths(config);
29080
- const manifest = loadManifest(cwd, schemaName);
29081
- if (!snapshotRootExists(cwd)) {
29082
- clack3.log.error(UNSUPPORTED_PROJECT_STATE_MESSAGE);
29083
- process.exit(1);
29084
- }
29085
- if (!manifest) {
29086
- clack3.log.error(UNSUPPORTED_PROJECT_STATE_MESSAGE);
29087
- process.exit(1);
29088
- }
29089
- if (!options.force) {
29090
- if (!isInteractiveSession()) {
29091
- clack3.log.error(
29092
- `Removing generated files for ${schemaName} needs confirmation. Re-run with --force.`
29093
- );
29094
- process.exit(1);
29095
- }
29096
- const confirmed = await clack3.confirm({
29097
- message: `Remove generated files for ${schemaName}?`,
29098
- initialValue: false
29099
- });
29100
- if (clack3.isCancel(confirmed) || !confirmed) {
29101
- clack3.cancel("Cancelled.");
29102
- return;
29103
- }
29104
- }
29105
- const deletedPaths = [];
29106
- for (const file of [...manifest.files.map((entry) => entry.path), ...manifest.skipped]) {
29107
- if (removePath2(cwd, file)) {
29108
- deletedPaths.push(file);
29109
- }
29110
- }
29111
- const loaded = (() => {
29112
- try {
29113
- return loadSchema(path57.join(cwd, ...paths.schemasDir.split("/")), schemaName);
29114
- } catch {
29115
- return null;
29116
- }
29117
- })();
29118
- const kebabName = toKebabCase(schemaName);
29119
- if (loaded?.type === "form") {
29120
- if (removePath2(cwd, `${paths.adminActionsDir}/${schemaName}`)) {
29121
- deletedPaths.push(`${paths.adminActionsDir}/${schemaName}`);
29122
- }
29123
- if (removePath2(cwd, `${paths.pagesDir}/forms/${kebabName}`)) {
29124
- deletedPaths.push(`${paths.pagesDir}/forms/${kebabName}`);
29125
- }
29126
- } else {
29127
- if (removePath2(cwd, `${paths.adminActionsDir}/${schemaName}`)) {
29128
- deletedPaths.push(`${paths.adminActionsDir}/${schemaName}`);
29129
- }
29130
- if (removePath2(cwd, `${paths.pagesDir}/${schemaName}`)) {
29131
- deletedPaths.push(`${paths.pagesDir}/${schemaName}`);
29132
- }
29133
- }
29134
- cleanupEmptyDirs3(cwd, deletedPaths, paths);
29135
- deleteSnapshot(cwd, schemaName);
29136
- if (hasTombstone(cwd, schemaName)) {
29137
- clearTombstone(cwd, schemaName);
29138
- }
29139
- writeTombstone(cwd, schemaName);
29140
- await applyGeneratedFiles({
29141
- cwd,
29142
- config,
29143
- scope: BARREL_SCOPE,
29144
- schemaJson: { name: BARREL_SCOPE },
29145
- generatedFiles: renderBarrelFiles(cwd, config),
29146
- force: false,
29147
- interactive: false
29148
- });
29149
- clack3.log.info(
29150
- `Tombstone written: .betterstart/snapshots/_removed/${schemaName}
29151
- Schema JSON preserved.`
29152
- );
29153
- clack3.outro(`Removed generated files for ${schemaName}`);
29154
- }
29155
-
29156
- // adapters/next/commands/uninstall.ts
29157
- import fs44 from "fs";
29158
- import path58 from "path";
29159
- import * as p30 from "@clack/prompts";
29160
- import pc11 from "picocolors";
29161
-
29162
- // adapters/next/commands/uninstall-cleaners.ts
29163
- import fs43 from "fs";
29164
- function stripJsonComments2(input) {
29165
- let result = "";
29166
- let i = 0;
29167
- while (i < input.length) {
29168
- if (input[i] === '"') {
29169
- let j = i + 1;
29170
- while (j < input.length) {
29171
- if (input[j] === "\\") {
29172
- j += 2;
29173
- continue;
29174
- }
29175
- if (input[j] === '"') {
29176
- j++;
29177
- break;
29178
- }
29179
- j++;
29180
- }
29181
- result += input.slice(i, j);
29182
- i = j;
29183
- } else if (input[i] === "/" && input[i + 1] === "/") {
29184
- const nl = input.indexOf("\n", i);
29185
- i = nl === -1 ? input.length : nl;
29186
- } else if (input[i] === "/" && input[i + 1] === "*") {
29187
- const end = input.indexOf("*/", i + 2);
29188
- i = end === -1 ? input.length : end + 2;
29189
- } else {
29190
- result += input[i];
29191
- i++;
29192
- }
29193
- }
29194
- return result;
29195
- }
29196
- function cleanTsconfig(tsconfigPath, aliasRoot = "@admin") {
29197
- if (!fs43.existsSync(tsconfigPath)) return [];
29198
- const raw = fs43.readFileSync(tsconfigPath, "utf-8");
29199
- const stripped = stripJsonComments2(raw).replace(/,\s*([\]}])/g, "$1");
29200
- let tsconfig;
29201
- try {
29202
- tsconfig = JSON.parse(stripped);
29203
- } catch {
29204
- return [];
29205
- }
29206
- const compilerOptions = tsconfig.compilerOptions ?? {};
29207
- const paths = compilerOptions.paths ?? {};
29208
- const removed = [];
29209
- for (const key of Object.keys(paths)) {
29210
- if (key.startsWith("@admin/") || key === "@admin/*" || key.startsWith(`${aliasRoot}/`) || key === `${aliasRoot}/*`) {
29211
- removed.push(key);
29212
- delete paths[key];
29213
- }
29214
- }
29215
- if (removed.length === 0) return [];
29216
- if (Object.keys(paths).length === 0) {
29217
- compilerOptions.paths = void 0;
29218
- } else {
29219
- compilerOptions.paths = paths;
29220
- }
29221
- tsconfig.compilerOptions = compilerOptions;
29222
- fs43.writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
29223
- `, "utf-8");
29224
- return removed;
29225
- }
29226
- function cleanCss(cssPath, namespace = "admin") {
29227
- if (!fs43.existsSync(cssPath)) return [];
29228
- const content = fs43.readFileSync(cssPath, "utf-8");
29229
- const lines = content.split("\n");
29230
- const sourcePattern = new RegExp(`^@source\\s+"[^"]*(?:admin|${namespace})[^"]*";\\s*$`);
29231
- const removed = [];
29232
- const kept = [];
29233
- for (const line of lines) {
29234
- if (sourcePattern.test(line)) {
29235
- removed.push(line.trim());
29236
- } else {
29237
- kept.push(line);
29238
- }
29239
- }
29240
- if (removed.length === 0) return [];
29241
- const cleaned = kept.join("\n").replace(/\n{3,}/g, "\n\n");
29242
- fs43.writeFileSync(cssPath, cleaned, "utf-8");
29243
- return removed;
29244
- }
29245
- function cleanEnvFile(envPath) {
29246
- if (!fs43.existsSync(envPath)) return [];
29247
- const content = fs43.readFileSync(envPath, "utf-8");
29248
- const lines = content.split("\n");
29249
- const removed = [];
29250
- const kept = [];
29251
- const headerPattern = /^# =+$/;
29252
- const headerTextPattern = /^# BetterStart Admin$/;
29253
- for (let i = 0; i < lines.length; i++) {
29254
- const line = lines[i];
29255
- const trimmed = line.trim();
29256
- if (trimmed.match(/^BETTERSTART_\w+=/)) {
29257
- const key = trimmed.split("=")[0];
29258
- removed.push(key);
29259
- continue;
29260
- }
29261
- if (headerPattern.test(trimmed)) {
29262
- const next = lines[i + 1]?.trim();
29263
- const afterNext = lines[i + 2]?.trim();
29264
- if (next && headerTextPattern.test(next) && afterNext && headerPattern.test(afterNext)) {
29265
- i += 2;
29266
- continue;
29267
- }
29268
- }
29269
- if (trimmed.startsWith("#") && !headerPattern.test(trimmed)) {
29270
- const nextNonEmpty = findNextNonEmptyLine(lines, i + 1);
29271
- if (nextNonEmpty?.match(/^BETTERSTART_\w+=/)) {
29272
- continue;
29273
- }
29274
- }
29275
- kept.push(line);
29276
- }
29277
- if (removed.length === 0) return [];
29278
- const result = kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
29279
- if (result === "") {
29280
- fs43.unlinkSync(envPath);
29281
- } else {
29282
- fs43.writeFileSync(envPath, `${result}
29283
- `, "utf-8");
29284
- }
29285
- return removed;
29286
- }
29287
- function findNextNonEmptyLine(lines, startIndex) {
29288
- for (let i = startIndex; i < lines.length; i++) {
29289
- const trimmed = lines[i].trim();
29290
- if (trimmed !== "") return trimmed;
29291
- }
29292
- return null;
29293
- }
29294
-
29295
- // adapters/next/commands/uninstall.ts
29296
- function findMainCss2(cwd) {
29297
- const candidates = [
29298
- "src/app/globals.css",
29299
- "app/globals.css",
29300
- "src/app/global.css",
29301
- "app/global.css",
29302
- "src/app/app.css",
29303
- "app/app.css",
29304
- "src/globals.css",
29305
- "globals.css"
29306
- ];
29307
- for (const candidate of candidates) {
29308
- const filePath = path58.join(cwd, candidate);
29309
- if (fs44.existsSync(filePath)) return filePath;
29310
- }
29311
- return void 0;
29312
- }
29313
- function isCLICreatedBiome(biomePath) {
29314
- if (!fs44.existsSync(biomePath)) return false;
29315
- try {
29316
- const content = JSON.parse(fs44.readFileSync(biomePath, "utf-8"));
29317
- 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");
29318
- } catch {
29319
- return false;
29320
- }
29321
- }
29322
- function buildUninstallPlan(cwd, namespaceValue) {
29323
- const steps = [];
29324
- const namespace = resolveAdminNamespace(namespaceValue);
29325
- const hasSrc = fs44.existsSync(path58.join(cwd, "src"));
29326
- const appBase = hasSrc ? "src/app" : "app";
29327
- const dirs = [];
29328
- const adminDir = path58.join(cwd, namespace.segment);
29329
- const legacyAdminDir = path58.join(cwd, "admin");
29330
- const adminRouteGroup = path58.join(cwd, appBase, namespace.routeGroup);
29331
- const legacyAdminRouteGroup = path58.join(cwd, appBase, "(admin)");
29332
- if (fs44.existsSync(adminDir)) dirs.push(`${namespace.segment}/`);
29333
- if (namespace.segment !== "admin" && fs44.existsSync(legacyAdminDir)) dirs.push("admin/");
29334
- if (fs44.existsSync(adminRouteGroup)) dirs.push(`${appBase}/${namespace.routeGroup}/`);
29335
- if (namespace.segment !== "admin" && fs44.existsSync(legacyAdminRouteGroup))
29336
- dirs.push(`${appBase}/(admin)/`);
29337
- if (dirs.length > 0) {
29338
- steps.push({
29339
- label: "Admin directories",
29340
- items: dirs,
29341
- count: dirs.length,
29342
- unit: dirs.length === 1 ? "directory" : "directories",
29343
- execute() {
29344
- if (fs44.existsSync(adminDir)) fs44.rmSync(adminDir, { recursive: true, force: true });
29345
- if (fs44.existsSync(legacyAdminDir))
29346
- fs44.rmSync(legacyAdminDir, { recursive: true, force: true });
29347
- if (fs44.existsSync(adminRouteGroup)) {
29348
- fs44.rmSync(adminRouteGroup, { recursive: true, force: true });
29349
- }
29350
- if (fs44.existsSync(legacyAdminRouteGroup)) {
29351
- fs44.rmSync(legacyAdminRouteGroup, { recursive: true, force: true });
29352
- }
29353
- }
29354
- });
29355
- }
29356
- const configFiles = [];
29357
- const configPaths = [];
29358
- const candidates = [
29359
- [CONFIG_FILE_NAME, path58.join(cwd, CONFIG_FILE_NAME)],
29360
- ["drizzle.config.ts", path58.join(cwd, "drizzle.config.ts")],
29361
- ["ADMIN.md", path58.join(cwd, "ADMIN.md")]
29362
- ];
29363
- for (const [label, fullPath] of candidates) {
29364
- if (fs44.existsSync(fullPath)) {
29365
- configFiles.push(label);
29366
- configPaths.push(fullPath);
29367
- }
29368
- }
29369
- const biomePath = path58.join(cwd, "biome.json");
29370
- if (isCLICreatedBiome(biomePath)) {
29371
- configFiles.push("biome.json (CLI-created)");
29372
- configPaths.push(biomePath);
29373
- }
29374
- if (configFiles.length > 0) {
29375
- steps.push({
29376
- label: "Config files",
29377
- items: configFiles,
29378
- count: configFiles.length,
29379
- unit: configFiles.length === 1 ? "file" : "files",
29380
- execute() {
29381
- for (const p32 of configPaths) {
29382
- if (fs44.existsSync(p32)) fs44.unlinkSync(p32);
29383
- }
29384
- }
29385
- });
29386
- }
29387
- const tsconfigPath = path58.join(cwd, "tsconfig.json");
29388
- if (fs44.existsSync(tsconfigPath)) {
29389
- const content = fs44.readFileSync(tsconfigPath, "utf-8");
29390
- const aliasMatches = [
29391
- ...content.match(/"@admin\//g) ?? [],
29392
- ...content.match(new RegExp(`"${namespace.alias}/`, "g")) ?? []
29393
- ];
29394
- if (aliasMatches && aliasMatches.length > 0) {
29395
- const aliasCount = aliasMatches.length;
29396
- steps.push({
29397
- label: "tsconfig.json path aliases",
29398
- items: [`${namespace.alias}/* aliases in tsconfig.json`],
29399
- count: aliasCount,
29400
- unit: aliasCount === 1 ? "alias" : "aliases",
29401
- execute() {
29402
- cleanTsconfig(tsconfigPath, namespace.alias);
29403
- }
29404
- });
29405
- }
29406
- }
29407
- const cssFile = findMainCss2(cwd);
29408
- if (cssFile) {
29409
- const cssContent = fs44.readFileSync(cssFile, "utf-8");
29410
- const sourceLines = cssContent.split("\n").filter(
29411
- (l) => new RegExp(`^@source\\s+"[^"]*(?:admin|${namespace.segment})[^"]*";\\s*$`).test(l)
29412
- );
29413
- if (sourceLines.length > 0) {
29414
- const relCss = path58.relative(cwd, cssFile);
29415
- steps.push({
29416
- label: `CSS @source lines (${relCss})`,
29417
- items: [`@source lines in ${relCss}`],
29418
- count: sourceLines.length,
29419
- unit: sourceLines.length === 1 ? "line" : "lines",
29420
- execute() {
29421
- cleanCss(cssFile, namespace.segment);
29422
- }
29423
- });
29424
- }
29425
- }
29426
- const envPath = path58.join(cwd, ".env.local");
29427
- if (fs44.existsSync(envPath)) {
29428
- const envContent = fs44.readFileSync(envPath, "utf-8");
29429
- const bsVars = envContent.split("\n").filter((l) => l.trim().match(/^BETTERSTART_\w+=/)).map((l) => l.split("=")[0]);
29430
- if (bsVars.length > 0) {
29431
- steps.push({
29432
- label: ".env.local variables",
29433
- items: ["BETTERSTART_* vars in .env.local"],
29434
- count: bsVars.length,
29435
- unit: bsVars.length === 1 ? "variable" : "variables",
29436
- execute() {
29437
- cleanEnvFile(envPath);
29438
- }
29439
- });
29440
- }
29441
- }
29442
- return steps;
29443
- }
29444
- async function runUninstallCommand(options) {
29445
- const cwd = options.cwd ? path58.resolve(options.cwd) : process.cwd();
29446
- p30.intro(pc11.bgRed(pc11.white(" BetterStart Uninstall ")));
29447
- let namespace = DEFAULT_ADMIN_NAMESPACE;
29448
- try {
29449
- const config = await resolveConfig(cwd);
29450
- namespace = config.frameworkConfig.next.namespace;
29451
- } catch {
29452
- }
29453
- const steps = buildUninstallPlan(cwd, namespace);
29454
- if (steps.length === 0) {
29455
- p30.log.success(`${pc11.green("\u2713")} Nothing to remove \u2014 project is already clean.`);
29456
- p30.outro("Project already clean");
29457
- return;
29458
- }
29459
- const planLines = steps.map((step) => {
29460
- const names = step.items.join(" ");
29461
- const countLabel = pc11.dim(`${step.count} ${step.unit}`);
29462
- return `${pc11.red("\u2717")} ${names} ${countLabel}`;
29463
- });
29464
- p30.note(planLines.join("\n"), "Uninstall plan");
29465
- if (!options.force) {
29466
- const confirmed = await p30.confirm({
29467
- message: "Proceed with uninstall?",
29468
- initialValue: false
29469
- });
29470
- if (p30.isCancel(confirmed) || !confirmed) {
29471
- p30.cancel("Uninstall cancelled.");
29472
- process.exit(0);
29473
- }
29474
- }
29475
- const s = spinner2();
29476
- s.start(steps[0].label);
29477
- for (const step of steps) {
29478
- s.message(step.label);
29479
- step.execute();
29480
- }
29481
- const parts = steps.map((step) => `${step.count} ${step.unit}`);
29482
- s.stop(`Removed ${parts.join(", ")}`);
29483
- p30.note(pc11.dim("Database tables were NOT dropped \u2014 drop them manually if needed."), "Next steps");
29484
- p30.outro("Uninstall complete");
29485
- }
29486
-
29487
- // adapters/next/commands/update-component.ts
29488
- import { execFileSync as execFileSync5 } from "child_process";
29489
- import fs45 from "fs";
29490
- import path59 from "path";
29491
- import * as clack4 from "@clack/prompts";
29492
28981
  import fsExtra from "fs-extra";
29493
28982
  var STATIC_CUSTOM_DEPENDENCIES = {
29494
28983
  "content-editor": [
@@ -29738,24 +29227,24 @@ function applyNamespaceToTemplateEntry(entry, config, cwd) {
29738
29227
  };
29739
29228
  }
29740
29229
  function writeNamespacedFile(srcPath, destPath, namespace) {
29741
- fs45.writeFileSync(
29230
+ fs42.writeFileSync(
29742
29231
  destPath,
29743
- applyAdminNamespaceToContent(fs45.readFileSync(srcPath, "utf-8"), namespace),
29232
+ applyAdminNamespaceToContent(fs42.readFileSync(srcPath, "utf-8"), namespace),
29744
29233
  "utf-8"
29745
29234
  );
29746
29235
  }
29747
29236
  function copyNamespacedDirectory(srcDir, destDir, namespace) {
29748
- const entries = fs45.readdirSync(srcDir, { withFileTypes: true });
29237
+ const entries = fs42.readdirSync(srcDir, { withFileTypes: true });
29749
29238
  for (const entry of entries) {
29750
29239
  const namespacedName = applyAdminNamespaceToPath(entry.name, namespace);
29751
- const srcPath = path59.join(srcDir, entry.name);
29752
- const destPath = path59.join(destDir, namespacedName);
29240
+ const srcPath = path55.join(srcDir, entry.name);
29241
+ const destPath = path55.join(destDir, namespacedName);
29753
29242
  if (entry.isDirectory()) {
29754
29243
  fsExtra.ensureDirSync(destPath);
29755
29244
  copyNamespacedDirectory(srcPath, destPath, namespace);
29756
29245
  continue;
29757
29246
  }
29758
- fsExtra.ensureDirSync(path59.dirname(destPath));
29247
+ fsExtra.ensureDirSync(path55.dirname(destPath));
29759
29248
  writeNamespacedFile(srcPath, destPath, namespace);
29760
29249
  }
29761
29250
  }
@@ -29766,10 +29255,10 @@ function hasIntegration(config, integrationId) {
29766
29255
  return config.integrations.installed.includes(integrationId);
29767
29256
  }
29768
29257
  function readProjectPackageJson2(cwd) {
29769
- const pkgPath = path59.join(cwd, "package.json");
29770
- if (!fs45.existsSync(pkgPath)) return null;
29258
+ const pkgPath = path55.join(cwd, "package.json");
29259
+ if (!fs42.existsSync(pkgPath)) return null;
29771
29260
  try {
29772
- return JSON.parse(fs45.readFileSync(pkgPath, "utf-8"));
29261
+ return JSON.parse(fs42.readFileSync(pkgPath, "utf-8"));
29773
29262
  } catch {
29774
29263
  return null;
29775
29264
  }
@@ -30653,13 +30142,38 @@ var TEMPLATE_REGISTRY = {
30653
30142
  relPath: "app/(admin)/admin/(authenticated)/settings/forms/forms-settings-page-content.tsx",
30654
30143
  content: () => readTemplate("pages/settings/forms/forms-settings-page-content.tsx"),
30655
30144
  base: "cwd",
30656
- dependencies: ["edit-form-notifications-dialog", "use-webhooks"]
30145
+ dependencies: [
30146
+ "form-notifications-drawer",
30147
+ "forms-settings-columns",
30148
+ "forms-settings-table",
30149
+ "page-header",
30150
+ "use-webhooks"
30151
+ ]
30152
+ },
30153
+ "forms-settings-columns": {
30154
+ relPath: "app/(admin)/admin/(authenticated)/settings/forms/forms-settings-columns.tsx",
30155
+ content: () => readTemplate("pages/settings/forms/forms-settings-columns.tsx"),
30156
+ base: "cwd"
30157
+ },
30158
+ "forms-settings-table": {
30159
+ relPath: "app/(admin)/admin/(authenticated)/settings/forms/forms-settings-table.tsx",
30160
+ content: () => readTemplate("pages/settings/forms/forms-settings-table.tsx"),
30161
+ base: "cwd",
30162
+ dependencies: ["data-grid"]
30657
30163
  },
30658
- "edit-form-notifications-dialog": {
30659
- relPath: "app/(admin)/admin/(authenticated)/settings/forms/edit-form-notifications-dialog.tsx",
30660
- content: () => readTemplate("pages/settings/forms/edit-form-notifications-dialog.tsx"),
30164
+ "form-notifications-drawer": {
30165
+ relPath: "app/(admin)/admin/(authenticated)/settings/forms/form-notifications-drawer.tsx",
30166
+ content: () => readTemplate("pages/settings/forms/form-notifications-drawer.tsx"),
30661
30167
  base: "cwd",
30662
- dependencies: ["form-settings-action"]
30168
+ dependencies: [
30169
+ "button",
30170
+ "card",
30171
+ "drawer",
30172
+ "form",
30173
+ "form-settings-action",
30174
+ "scroll-area",
30175
+ "textarea"
30176
+ ]
30663
30177
  },
30664
30178
  "webhooks-page": {
30665
30179
  relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/page.tsx",
@@ -31277,577 +30791,1151 @@ function getStaticUiComponents() {
31277
30791
  function getStaticUiComponentEntries() {
31278
30792
  return getStaticAssetComponentEntries("ui");
31279
30793
  }
31280
- function getStaticCustomComponents() {
31281
- return getStaticAssetComponents("custom");
30794
+ function getStaticCustomComponents() {
30795
+ return getStaticAssetComponents("custom");
30796
+ }
30797
+ function getStaticCustomComponentEntries() {
30798
+ return getStaticAssetComponentEntries("custom");
30799
+ }
30800
+ function getStaticAssetComponents(assetDirectory) {
30801
+ return getStaticAssetComponentEntries(assetDirectory).map((entry) => entry.name);
30802
+ }
30803
+ function getStaticAssetComponentEntries(assetDirectory) {
30804
+ const assetDir = resolveCliAssetPath("shared-assets", "react-admin", assetDirectory);
30805
+ if (!fs42.existsSync(assetDir)) return [];
30806
+ const components = [];
30807
+ for (const entry of fs42.readdirSync(assetDir, { withFileTypes: true })) {
30808
+ if (entry.isFile() && (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts"))) {
30809
+ components.push({
30810
+ name: entry.name.replace(/\.(tsx|ts)$/, ""),
30811
+ file: entry.name
30812
+ });
30813
+ continue;
30814
+ }
30815
+ if (!entry.isDirectory()) {
30816
+ continue;
30817
+ }
30818
+ const indexFile = ["index.tsx", "index.ts"].find(
30819
+ (file) => fs42.existsSync(path55.join(assetDir, entry.name, file))
30820
+ );
30821
+ if (indexFile) {
30822
+ components.push({
30823
+ name: entry.name,
30824
+ file: path55.join(entry.name, indexFile)
30825
+ });
30826
+ }
30827
+ }
30828
+ return components.sort((a, b) => a.name.localeCompare(b.name));
30829
+ }
30830
+ function findStaticAssetFile(assetDir, componentName) {
30831
+ if (!fs42.existsSync(assetDir)) return void 0;
30832
+ const isNestedComponentName = /[\\/]/.test(componentName);
30833
+ const nestedComponentName = componentName.split(/[\\/]/).filter(Boolean).join(path55.sep);
30834
+ if (isNestedComponentName && nestedComponentName && !path55.isAbsolute(componentName) && !nestedComponentName.split(path55.sep).includes("..")) {
30835
+ for (const extension of [".tsx", ".ts"]) {
30836
+ const relPath = `${nestedComponentName}${extension}`;
30837
+ const filePath = path55.join(assetDir, relPath);
30838
+ if (fs42.existsSync(filePath) && fs42.statSync(filePath).isFile()) {
30839
+ return relPath;
30840
+ }
30841
+ }
30842
+ }
30843
+ if (!isNestedComponentName && !componentName.includes("..") && !path55.isAbsolute(componentName)) {
30844
+ for (const extension of [".tsx", ".ts"]) {
30845
+ const relPath = path55.join(componentName, `index${extension}`);
30846
+ const filePath = path55.join(assetDir, relPath);
30847
+ if (fs42.existsSync(filePath) && fs42.statSync(filePath).isFile()) {
30848
+ return relPath;
30849
+ }
30850
+ }
30851
+ }
30852
+ return fs42.readdirSync(assetDir, { withFileTypes: true }).find(
30853
+ (entry) => entry.isFile() && (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts")) && entry.name.replace(/\.(tsx|ts)$/, "") === componentName
30854
+ )?.name;
31282
30855
  }
31283
- function getStaticCustomComponentEntries() {
31284
- return getStaticAssetComponentEntries("custom");
30856
+ function getAllComponentNames() {
30857
+ const staticUi = getStaticUiComponents();
30858
+ const staticCustom = getStaticCustomComponents();
30859
+ const templateKeys = Object.keys(TEMPLATE_REGISTRY);
30860
+ return [.../* @__PURE__ */ new Set([...staticUi, ...staticCustom, ...templateKeys, "tiptap"])].sort();
31285
30861
  }
31286
- function getStaticAssetComponents(assetDirectory) {
31287
- return getStaticAssetComponentEntries(assetDirectory).map((entry) => entry.name);
30862
+ function getAllComponentNamesForConfig(config) {
30863
+ const staticUi = getStaticUiComponents();
30864
+ const staticCustom = getStaticCustomComponents();
30865
+ const templateKeys = Object.entries(TEMPLATE_REGISTRY).filter(
30866
+ ([, entry]) => (!entry.requiredPreset || hasPreset(config, entry.requiredPreset)) && (!entry.requiredIntegration || hasIntegration(config, entry.requiredIntegration))
30867
+ ).map(([name]) => name);
30868
+ return [.../* @__PURE__ */ new Set([...staticUi, ...staticCustom, ...templateKeys, "tiptap"])].sort();
31288
30869
  }
31289
- function getStaticAssetComponentEntries(assetDirectory) {
31290
- const assetDir = resolveCliAssetPath("shared-assets", "react-admin", assetDirectory);
31291
- if (!fs45.existsSync(assetDir)) return [];
31292
- const components = [];
31293
- for (const entry of fs45.readdirSync(assetDir, { withFileTypes: true })) {
31294
- if (entry.isFile() && (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts"))) {
31295
- components.push({
31296
- name: entry.name.replace(/\.(tsx|ts)$/, ""),
31297
- file: entry.name
30870
+ async function runUpdateCommand(components, options) {
30871
+ const cwd = options.cwd ? path55.resolve(options.cwd) : process.cwd();
30872
+ const normalizedOnly = normalizeShadcnPresetOnly(options.only);
30873
+ validateShadcnPresetOptions(components, options);
30874
+ if (options.json && !options.list) {
30875
+ clack3.cancel("--json can only be used with --list.");
30876
+ process.exit(1);
30877
+ }
30878
+ if (options.list) {
30879
+ const uiComponents = getStaticUiComponentEntries();
30880
+ const customComponents = getStaticCustomComponentEntries();
30881
+ const templateKeys = Object.keys(TEMPLATE_REGISTRY).sort();
30882
+ const templatePath = (name) => {
30883
+ const entry = TEMPLATE_REGISTRY[name];
30884
+ return entry.displayPath ?? (typeof entry.relPath === "string" ? entry.relPath : "");
30885
+ };
30886
+ if (options.json) {
30887
+ const items = [
30888
+ ...uiComponents.map((component) => ({
30889
+ name: component.name,
30890
+ path: `components/ui/${component.file}`,
30891
+ kind: "ui"
30892
+ })),
30893
+ ...customComponents.map((component) => ({
30894
+ name: component.name,
30895
+ path: `components/custom/${component.file}`,
30896
+ kind: "custom"
30897
+ })),
30898
+ ...templateKeys.map((name) => ({ name, path: templatePath(name), kind: "template" })),
30899
+ { name: "tiptap", path: "components/custom/content-editor/tiptap-*/", kind: "special" }
30900
+ ];
30901
+ console.log(JSON.stringify(items, null, 2));
30902
+ return;
30903
+ }
30904
+ const all = getAllComponentNames();
30905
+ clack3.intro("Available components");
30906
+ clack3.note(
30907
+ renderTableRows(
30908
+ uiComponents.map((component) => [component.name, `components/ui/${component.file}`])
30909
+ ).join("\n"),
30910
+ `Shadcn UI Components (${uiComponents.length})`
30911
+ );
30912
+ clack3.note(
30913
+ renderTableRows(
30914
+ customComponents.map((component) => [component.name, `components/custom/${component.file}`])
30915
+ ).join("\n"),
30916
+ `Custom Components (${customComponents.length})`
30917
+ );
30918
+ clack3.note(
30919
+ renderTableRows(templateKeys.map((name) => [name, templatePath(name)])).join("\n"),
30920
+ `Template Components (${templateKeys.length})`
30921
+ );
30922
+ clack3.note(
30923
+ renderTableRows([["tiptap", "components/custom/content-editor/tiptap-*/ (all files)"]]).join(
30924
+ "\n"
30925
+ ),
30926
+ "Special"
30927
+ );
30928
+ clack3.outro(`${all.length} components available`);
30929
+ return;
30930
+ }
30931
+ const config = await resolveConfigOrExit(cwd);
30932
+ const admin = path55.resolve(cwd, config.paths.admin);
30933
+ if (!fs42.existsSync(admin)) {
30934
+ clack3.cancel(
30935
+ `Admin directory not found at ${config.paths.admin}. Run 'betterstart init' first.`
30936
+ );
30937
+ process.exit(1);
30938
+ }
30939
+ if (options.shadcnPreset) {
30940
+ runShadcnPresetUpdate({
30941
+ cwd,
30942
+ config,
30943
+ preset: options.shadcnPreset.trim(),
30944
+ only: normalizedOnly
30945
+ });
30946
+ return;
30947
+ }
30948
+ if (!options.all && components.length === 0) {
30949
+ clack3.log.error(
30950
+ "Provide component names or use --all. Run with --list to see available components."
30951
+ );
30952
+ process.exit(1);
30953
+ }
30954
+ clack3.intro("BetterStart Update Components");
30955
+ const toUpdate = options.all ? getAllComponentNamesForConfig(config) : components;
30956
+ const uiDir = resolveCliAssetPath("shared-assets", "react-admin", "ui");
30957
+ const customDir = resolveCliAssetPath("shared-assets", "react-admin", "custom");
30958
+ let updated = 0;
30959
+ let skipped = 0;
30960
+ const updatedTemplateNames = /* @__PURE__ */ new Set();
30961
+ const updatedStaticNames = /* @__PURE__ */ new Set();
30962
+ const requiredPackageDependencies = /* @__PURE__ */ new Set();
30963
+ const pendingWrites = [];
30964
+ function trackPackageDependencies(name) {
30965
+ for (const dependency of COMPONENT_PACKAGE_DEPENDENCIES[name] ?? []) {
30966
+ requiredPackageDependencies.add(dependency);
30967
+ }
30968
+ }
30969
+ function writeTemplateEntry(name, entry) {
30970
+ if (updatedTemplateNames.has(name)) {
30971
+ return false;
30972
+ }
30973
+ if (entry.requiredPreset && !hasPreset(config, entry.requiredPreset)) {
30974
+ clack3.log.warn(`${name} requires the ${entry.requiredPreset} preset and was skipped.`);
30975
+ skipped++;
30976
+ return false;
30977
+ }
30978
+ if (entry.requiredIntegration && !hasIntegration(config, entry.requiredIntegration)) {
30979
+ clack3.log.warn(
30980
+ `${name} requires the ${entry.requiredIntegration} integration and was skipped.`
30981
+ );
30982
+ skipped++;
30983
+ return false;
30984
+ }
30985
+ const { relPath, content } = applyNamespaceToTemplateEntry(entry, config, cwd);
30986
+ const baseDir = entry.base === "cwd" ? cwd : admin;
30987
+ const destPath = path55.join(baseDir, relPath);
30988
+ if (entry.preserveExisting && fs42.existsSync(destPath)) {
30989
+ clack3.log.info(`Preserved ${relPath}`);
30990
+ updatedTemplateNames.add(name);
30991
+ skipped++;
30992
+ return false;
30993
+ }
30994
+ pendingWrites.push({
30995
+ displayPath: relPath,
30996
+ write: () => {
30997
+ fsExtra.ensureDirSync(path55.dirname(destPath));
30998
+ fs42.writeFileSync(destPath, content, "utf-8");
30999
+ clack3.log.success(`Updated ${relPath}`);
31000
+ }
31001
+ });
31002
+ updatedTemplateNames.add(name);
31003
+ trackPackageDependencies(name);
31004
+ updated++;
31005
+ return true;
31006
+ }
31007
+ function writeTemplateEntryWithDependencies(name, entry) {
31008
+ const wroteEntry = writeTemplateEntry(name, entry);
31009
+ if (!wroteEntry) {
31010
+ return false;
31011
+ }
31012
+ for (const dependencyName of entry.dependencies ?? []) {
31013
+ writeNamedDependency(dependencyName);
31014
+ }
31015
+ return true;
31016
+ }
31017
+ function writeStaticAssetEntry(assetDirectory, name) {
31018
+ const key = `${assetDirectory}:${name}`;
31019
+ if (updatedStaticNames.has(key)) {
31020
+ return false;
31021
+ }
31022
+ const assetDir = assetDirectory === "ui" ? uiDir : customDir;
31023
+ const assetFile = findStaticAssetFile(assetDir, name);
31024
+ if (!assetFile) {
31025
+ return false;
31026
+ }
31027
+ const namespace = config.frameworkConfig.next.namespace;
31028
+ const namespacedAssetFile = applyAdminNamespaceToPath(assetFile, namespace);
31029
+ const destPath = path55.join(admin, "components", assetDirectory, namespacedAssetFile);
31030
+ pendingWrites.push({
31031
+ displayPath: `components/${assetDirectory}/${namespacedAssetFile}`,
31032
+ write: () => {
31033
+ fsExtra.ensureDirSync(path55.dirname(destPath));
31034
+ writeNamespacedFile(path55.join(assetDir, assetFile), destPath, namespace);
31035
+ clack3.log.success(`Updated components/${assetDirectory}/${namespacedAssetFile}`);
31036
+ }
31037
+ });
31038
+ if (assetDirectory === "custom") {
31039
+ const assetSubdir = path55.join(assetDir, name);
31040
+ if (fs42.existsSync(assetSubdir) && fs42.statSync(assetSubdir).isDirectory()) {
31041
+ const namespacedName = applyAdminNamespaceToPath(name, namespace);
31042
+ const destSubdir = path55.join(admin, "components", assetDirectory, namespacedName);
31043
+ pendingWrites.push({
31044
+ displayPath: `components/${assetDirectory}/${namespacedName}/ (template files)`,
31045
+ write: () => {
31046
+ fsExtra.ensureDirSync(destSubdir);
31047
+ copyNamespacedDirectory(assetSubdir, destSubdir, namespace);
31048
+ clack3.log.success(
31049
+ `Updated components/${assetDirectory}/${namespacedName}/ (template files)`
31050
+ );
31051
+ }
31052
+ });
31053
+ }
31054
+ }
31055
+ updatedStaticNames.add(key);
31056
+ trackPackageDependencies(name);
31057
+ updated++;
31058
+ if (assetDirectory === "custom") {
31059
+ for (const dependencyName of STATIC_CUSTOM_DEPENDENCIES[name] ?? []) {
31060
+ writeNamedDependency(dependencyName);
31061
+ }
31062
+ }
31063
+ return true;
31064
+ }
31065
+ function writeTiptapTemplates() {
31066
+ const key = "custom:tiptap";
31067
+ if (updatedStaticNames.has(key)) {
31068
+ return true;
31069
+ }
31070
+ const srcBaseDir = resolveCliAssetPath(
31071
+ "shared-assets",
31072
+ "react-admin",
31073
+ "custom",
31074
+ "content-editor"
31075
+ );
31076
+ const namespace = config.frameworkConfig.next.namespace;
31077
+ const destBaseDir = path55.join(admin, "components", "custom", "content-editor");
31078
+ if (!fs42.existsSync(srcBaseDir)) {
31079
+ return false;
31080
+ }
31081
+ const dirsToCopy = [];
31082
+ for (const directory of TIPTAP_CONTENT_EDITOR_DIRECTORIES) {
31083
+ const srcDir = path55.join(srcBaseDir, directory);
31084
+ if (!fs42.existsSync(srcDir)) {
31085
+ continue;
31086
+ }
31087
+ dirsToCopy.push({
31088
+ srcDir,
31089
+ destDir: path55.join(destBaseDir, applyAdminNamespaceToPath(directory, namespace))
31298
31090
  });
31091
+ }
31092
+ if (dirsToCopy.length === 0) {
31093
+ return false;
31094
+ }
31095
+ pendingWrites.push({
31096
+ displayPath: "components/custom/content-editor/tiptap-*/ (template files)",
31097
+ write: () => {
31098
+ for (const { srcDir, destDir } of dirsToCopy) {
31099
+ fsExtra.ensureDirSync(destDir);
31100
+ copyNamespacedDirectory(srcDir, destDir, namespace);
31101
+ }
31102
+ clack3.log.success("Updated components/custom/content-editor/tiptap-*/ (template files)");
31103
+ removeLegacyDirectory(path55.join(admin, "components", "custom", "tiptap"));
31104
+ }
31105
+ });
31106
+ updatedStaticNames.add(key);
31107
+ trackPackageDependencies("tiptap");
31108
+ updated++;
31109
+ for (const dependencyName of TIPTAP_TEMPLATE_DEPENDENCIES) {
31110
+ writeNamedDependency(dependencyName);
31111
+ }
31112
+ return true;
31113
+ }
31114
+ function writeNamedDependency(name) {
31115
+ const entry = TEMPLATE_REGISTRY[name];
31116
+ if (entry) {
31117
+ writeTemplateEntryWithDependencies(name, entry);
31118
+ return;
31119
+ }
31120
+ if (writeStaticAssetEntry("ui", name)) {
31121
+ return;
31122
+ }
31123
+ if (writeStaticAssetEntry("custom", name)) {
31124
+ return;
31125
+ }
31126
+ if (name === "tiptap") {
31127
+ writeTiptapTemplates();
31128
+ }
31129
+ }
31130
+ for (const name of toUpdate) {
31131
+ if (TEMPLATE_REGISTRY[name]) {
31132
+ const entry = TEMPLATE_REGISTRY[name];
31133
+ writeTemplateEntryWithDependencies(name, entry);
31299
31134
  continue;
31300
31135
  }
31301
- if (!entry.isDirectory()) {
31136
+ if (writeStaticAssetEntry("ui", name)) {
31302
31137
  continue;
31303
31138
  }
31304
- const indexFile = ["index.tsx", "index.ts"].find(
31305
- (file) => fs45.existsSync(path59.join(assetDir, entry.name, file))
31306
- );
31307
- if (indexFile) {
31308
- components.push({
31309
- name: entry.name,
31310
- file: path59.join(entry.name, indexFile)
31311
- });
31139
+ if (writeStaticAssetEntry("custom", name)) {
31140
+ continue;
31312
31141
  }
31313
- }
31314
- return components.sort((a, b) => a.name.localeCompare(b.name));
31315
- }
31316
- function findStaticAssetFile(assetDir, componentName) {
31317
- if (!fs45.existsSync(assetDir)) return void 0;
31318
- const isNestedComponentName = /[\\/]/.test(componentName);
31319
- const nestedComponentName = componentName.split(/[\\/]/).filter(Boolean).join(path59.sep);
31320
- if (isNestedComponentName && nestedComponentName && !path59.isAbsolute(componentName) && !nestedComponentName.split(path59.sep).includes("..")) {
31321
- for (const extension of [".tsx", ".ts"]) {
31322
- const relPath = `${nestedComponentName}${extension}`;
31323
- const filePath = path59.join(assetDir, relPath);
31324
- if (fs45.existsSync(filePath) && fs45.statSync(filePath).isFile()) {
31325
- return relPath;
31142
+ if (name === "tiptap") {
31143
+ if (!writeTiptapTemplates()) {
31144
+ clack3.log.warn("tiptap templates not found");
31145
+ skipped++;
31326
31146
  }
31147
+ continue;
31327
31148
  }
31149
+ clack3.log.warn(`Unknown component: ${name}`);
31150
+ skipped++;
31328
31151
  }
31329
- if (!isNestedComponentName && !componentName.includes("..") && !path59.isAbsolute(componentName)) {
31330
- for (const extension of [".tsx", ".ts"]) {
31331
- const relPath = path59.join(componentName, `index${extension}`);
31332
- const filePath = path59.join(assetDir, relPath);
31333
- if (fs45.existsSync(filePath) && fs45.statSync(filePath).isFile()) {
31334
- return relPath;
31152
+ if (pendingWrites.length > 0) {
31153
+ const displayPaths = pendingWrites.map((entry) => entry.displayPath);
31154
+ const preview = displayPaths.slice(0, MAX_OVERWRITE_PREVIEW_ROWS);
31155
+ if (displayPaths.length > preview.length) {
31156
+ preview.push(`\u2026 and ${displayPaths.length - preview.length} more`);
31157
+ }
31158
+ clack3.note(
31159
+ preview.join("\n"),
31160
+ `Overwriting ${displayPaths.length} file${displayPaths.length === 1 ? "" : "s"}`
31161
+ );
31162
+ if (!options.yes) {
31163
+ if (!isInteractiveSession()) {
31164
+ clack3.log.error(
31165
+ `Overwriting ${displayPaths.length} file${displayPaths.length === 1 ? "" : "s"} needs confirmation. Re-run with --yes.`
31166
+ );
31167
+ process.exit(1);
31168
+ }
31169
+ const proceed = await clack3.confirm({
31170
+ message: "Overwrite these files with the latest templates?",
31171
+ initialValue: true
31172
+ });
31173
+ if (clack3.isCancel(proceed) || !proceed) {
31174
+ clack3.cancel("Update cancelled.");
31175
+ process.exit(0);
31335
31176
  }
31336
31177
  }
31178
+ for (const entry of pendingWrites) {
31179
+ entry.write();
31180
+ }
31337
31181
  }
31338
- return fs45.readdirSync(assetDir, { withFileTypes: true }).find(
31339
- (entry) => entry.isFile() && (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts")) && entry.name.replace(/\.(tsx|ts)$/, "") === componentName
31340
- )?.name;
31341
- }
31342
- function getAllComponentNames() {
31343
- const staticUi = getStaticUiComponents();
31344
- const staticCustom = getStaticCustomComponents();
31345
- const templateKeys = Object.keys(TEMPLATE_REGISTRY);
31346
- return [.../* @__PURE__ */ new Set([...staticUi, ...staticCustom, ...templateKeys, "tiptap"])].sort();
31182
+ syncInstalledPresetManifests(cwd, config);
31183
+ syncInstalledIntegrationManifests(cwd, config);
31184
+ const projectPackageJson = readProjectPackageJson2(cwd);
31185
+ const missingPackageDependencies = Array.from(requiredPackageDependencies).filter(
31186
+ (dependency) => !hasDeclaredPackage2(projectPackageJson, dependency)
31187
+ );
31188
+ if (missingPackageDependencies.length > 0) {
31189
+ clack3.log.warn(
31190
+ `Updated components require package dependencies that are not declared: ${missingPackageDependencies.join(", ")}. Run 'pnpm betterstart update-deps' before building.`
31191
+ );
31192
+ }
31193
+ clack3.outro(
31194
+ `Updated ${updated} component${updated !== 1 ? "s" : ""}${skipped > 0 ? `, ${skipped} skipped` : ""}`
31195
+ );
31347
31196
  }
31348
- function getAllComponentNamesForConfig(config) {
31349
- const staticUi = getStaticUiComponents();
31350
- const staticCustom = getStaticCustomComponents();
31351
- const templateKeys = Object.entries(TEMPLATE_REGISTRY).filter(
31352
- ([, entry]) => (!entry.requiredPreset || hasPreset(config, entry.requiredPreset)) && (!entry.requiredIntegration || hasIntegration(config, entry.requiredIntegration))
31353
- ).map(([name]) => name);
31354
- return [.../* @__PURE__ */ new Set([...staticUi, ...staticCustom, ...templateKeys, "tiptap"])].sort();
31197
+ function removeLegacyDirectory(dirPath) {
31198
+ if (fs42.existsSync(dirPath)) {
31199
+ fs42.rmSync(dirPath, { recursive: true, force: true });
31200
+ }
31355
31201
  }
31356
- async function runUpdateCommand(components, options) {
31357
- const cwd = options.cwd ? path59.resolve(options.cwd) : process.cwd();
31358
- const normalizedOnly = normalizeShadcnPresetOnly(options.only);
31359
- validateShadcnPresetOptions(components, options);
31360
- if (options.json && !options.list) {
31361
- clack4.cancel("--json can only be used with --list.");
31202
+ function validateShadcnPresetOptions(components, options) {
31203
+ if (options.only && !options.shadcnPreset) {
31204
+ clack3.cancel("--only can only be used with --shadcn-preset.");
31362
31205
  process.exit(1);
31363
31206
  }
31207
+ if (!options.shadcnPreset) {
31208
+ return;
31209
+ }
31364
31210
  if (options.list) {
31365
- const uiComponents = getStaticUiComponentEntries();
31366
- const customComponents = getStaticCustomComponentEntries();
31367
- const templateKeys = Object.keys(TEMPLATE_REGISTRY).sort();
31368
- const templatePath = (name) => {
31369
- const entry = TEMPLATE_REGISTRY[name];
31370
- return entry.displayPath ?? (typeof entry.relPath === "string" ? entry.relPath : "");
31371
- };
31372
- if (options.json) {
31373
- const items = [
31374
- ...uiComponents.map((component) => ({
31375
- name: component.name,
31376
- path: `components/ui/${component.file}`,
31377
- kind: "ui"
31378
- })),
31379
- ...customComponents.map((component) => ({
31380
- name: component.name,
31381
- path: `components/custom/${component.file}`,
31382
- kind: "custom"
31383
- })),
31384
- ...templateKeys.map((name) => ({ name, path: templatePath(name), kind: "template" })),
31385
- { name: "tiptap", path: "components/custom/content-editor/tiptap-*/", kind: "special" }
31386
- ];
31387
- console.log(JSON.stringify(items, null, 2));
31388
- return;
31389
- }
31390
- const all = getAllComponentNames();
31391
- clack4.intro("Available components");
31392
- clack4.note(
31393
- renderTableRows(
31394
- uiComponents.map((component) => [component.name, `components/ui/${component.file}`])
31395
- ).join("\n"),
31396
- `Shadcn UI Components (${uiComponents.length})`
31211
+ clack3.cancel("--list cannot be combined with --shadcn-preset.");
31212
+ process.exit(1);
31213
+ }
31214
+ if (options.all) {
31215
+ clack3.cancel("--all cannot be combined with --shadcn-preset.");
31216
+ process.exit(1);
31217
+ }
31218
+ if (components.length > 0) {
31219
+ clack3.cancel("Component names cannot be combined with --shadcn-preset.");
31220
+ process.exit(1);
31221
+ }
31222
+ }
31223
+ function normalizeShadcnPresetOnly(value) {
31224
+ if (!value) {
31225
+ return void 0;
31226
+ }
31227
+ const parts = value.split(",").map((part) => part.trim().toLowerCase()).filter(Boolean);
31228
+ if (parts.length !== 1 || parts[0] !== "theme") {
31229
+ clack3.cancel(
31230
+ "Admin-only shadcn preset updates currently support --only theme. Omit --only to apply the full preset."
31397
31231
  );
31398
- clack4.note(
31399
- renderTableRows(
31400
- customComponents.map((component) => [component.name, `components/custom/${component.file}`])
31401
- ).join("\n"),
31402
- `Custom Components (${customComponents.length})`
31232
+ process.exit(1);
31233
+ }
31234
+ return "theme";
31235
+ }
31236
+ function runShadcnPresetUpdate({
31237
+ cwd,
31238
+ config,
31239
+ preset,
31240
+ only
31241
+ }) {
31242
+ const namespace = resolveAdminNamespace(config.frameworkConfig.next.namespace);
31243
+ const adminGlobalsPath = path55.join(cwd, config.paths.admin, namespace.globalsFile);
31244
+ const componentsJsonPath = path55.join(cwd, "components.json");
31245
+ const shadcnBackupPath = `${componentsJsonPath}.bak`;
31246
+ const restoreAfterApplyPaths = [
31247
+ componentsJsonPath,
31248
+ shadcnBackupPath,
31249
+ path55.join(cwd, config.paths.admin, "lib", "utils.ts"),
31250
+ ...getHostProjectFilesToRestore(cwd)
31251
+ ];
31252
+ if (!preset) {
31253
+ clack3.cancel("--shadcn-preset requires a preset code, preset name, or preset URL.");
31254
+ process.exit(1);
31255
+ }
31256
+ if (!fs42.existsSync(adminGlobalsPath)) {
31257
+ clack3.cancel(
31258
+ `Admin globals file not found at ${path55.relative(cwd, adminGlobalsPath)}. Run 'betterstart update admin-globals' first.`
31403
31259
  );
31404
- clack4.note(
31405
- renderTableRows(templateKeys.map((name) => [name, templatePath(name)])).join("\n"),
31406
- `Template Components (${templateKeys.length})`
31260
+ process.exit(1);
31261
+ }
31262
+ const shadcnBin = resolveLocalShadcnBin(cwd);
31263
+ const restoreSnapshots = restoreAfterApplyPaths.map((filePath) => ({
31264
+ filePath,
31265
+ snapshot: snapshotFile(filePath)
31266
+ }));
31267
+ clack3.intro("BetterStart Shadcn Preset");
31268
+ clack3.log.info(`Applying preset to ${path55.join(config.paths.admin, "components/ui")}`);
31269
+ let failed = false;
31270
+ try {
31271
+ fs42.writeFileSync(
31272
+ componentsJsonPath,
31273
+ `${JSON.stringify(createAdminShadcnComponentsJson(config), null, 2)}
31274
+ `,
31275
+ "utf-8"
31407
31276
  );
31408
- clack4.note(
31409
- renderTableRows([["tiptap", "components/custom/content-editor/tiptap-*/ (all files)"]]).join(
31410
- "\n"
31411
- ),
31412
- "Special"
31277
+ const args = ["apply", "--preset", preset, "--cwd", cwd, "--yes"];
31278
+ if (only) {
31279
+ args.push("--only", only);
31280
+ }
31281
+ execFileSync5(shadcnBin, args, { cwd, stdio: "inherit" });
31282
+ } catch {
31283
+ failed = true;
31284
+ } finally {
31285
+ for (const { filePath, snapshot } of restoreSnapshots.reverse()) {
31286
+ restoreFile(filePath, snapshot);
31287
+ }
31288
+ }
31289
+ if (failed) {
31290
+ clack3.cancel("shadcn preset application failed.");
31291
+ process.exit(1);
31292
+ }
31293
+ clack3.outro(
31294
+ only ? `Applied shadcn preset parts (${only}) to the Admin.` : "Applied shadcn preset to Admin UI components and styles."
31295
+ );
31296
+ }
31297
+ function createAdminShadcnComponentsJson(config) {
31298
+ const adminPath = toPosixPath(config.paths.admin);
31299
+ const namespace = resolveAdminNamespace(config.frameworkConfig.next.namespace);
31300
+ return {
31301
+ $schema: "https://ui.shadcn.com/schema.json",
31302
+ style: "new-york",
31303
+ rsc: true,
31304
+ tsx: true,
31305
+ tailwind: {
31306
+ config: "",
31307
+ css: `${adminPath}/${namespace.globalsFile}`,
31308
+ baseColor: "neutral",
31309
+ cssVariables: true,
31310
+ prefix: ""
31311
+ },
31312
+ iconLibrary: "lucide",
31313
+ aliases: {
31314
+ components: `${namespace.alias}/components`,
31315
+ ui: `${namespace.alias}/components/ui`,
31316
+ hooks: `${namespace.alias}/hooks`,
31317
+ lib: `${namespace.alias}/lib`,
31318
+ utils: `${namespace.alias}/utils/shared/cn`
31319
+ }
31320
+ };
31321
+ }
31322
+ function toPosixPath(value) {
31323
+ return value.replace(/\\/g, "/");
31324
+ }
31325
+ function resolveLocalShadcnBin(cwd) {
31326
+ const binName = process.platform === "win32" ? "shadcn.cmd" : "shadcn";
31327
+ const shadcnBin = path55.join(cwd, "node_modules", ".bin", binName);
31328
+ if (!fs42.existsSync(shadcnBin)) {
31329
+ clack3.cancel(
31330
+ `shadcn is not installed in this project. Run 'betterstart update-deps' and try again.`
31413
31331
  );
31414
- clack4.outro(`${all.length} components available`);
31332
+ process.exit(1);
31333
+ }
31334
+ return shadcnBin;
31335
+ }
31336
+ function getHostProjectFilesToRestore(cwd) {
31337
+ const hostRelativePaths = [
31338
+ "app/layout.tsx",
31339
+ "app/layout.ts",
31340
+ "app/layout.jsx",
31341
+ "app/layout.js",
31342
+ "src/app/layout.tsx",
31343
+ "src/app/layout.ts",
31344
+ "src/app/layout.jsx",
31345
+ "src/app/layout.js",
31346
+ "app/globals.css",
31347
+ "src/app/globals.css"
31348
+ ];
31349
+ return hostRelativePaths.map((relativePath) => path55.join(cwd, relativePath));
31350
+ }
31351
+ function snapshotFile(filePath) {
31352
+ if (!fs42.existsSync(filePath)) {
31353
+ return { existed: false };
31354
+ }
31355
+ return { existed: true, content: fs42.readFileSync(filePath, "utf-8") };
31356
+ }
31357
+ function restoreFile(filePath, snapshot) {
31358
+ if (snapshot.existed) {
31359
+ fs42.writeFileSync(filePath, snapshot.content ?? "", "utf-8");
31415
31360
  return;
31416
31361
  }
31362
+ if (fs42.existsSync(filePath)) {
31363
+ fs42.rmSync(filePath, { force: true });
31364
+ }
31365
+ }
31366
+
31367
+ // adapters/next/commands/menu-choices.ts
31368
+ async function listInstallableChoices(cwd) {
31417
31369
  const config = await resolveConfigOrExit(cwd);
31418
- const admin = path59.resolve(cwd, config.paths.admin);
31419
- if (!fs45.existsSync(admin)) {
31420
- clack4.cancel(
31421
- `Admin directory not found at ${config.paths.admin}. Run 'betterstart init' first.`
31370
+ const installedPresets = new Set(config.presets.installed);
31371
+ const installedIntegrations = new Set(config.integrations.installed);
31372
+ return {
31373
+ presets: listAvailablePresets().map((preset) => ({
31374
+ id: preset.id,
31375
+ description: preset.description,
31376
+ installed: installedPresets.has(preset.id)
31377
+ })),
31378
+ integrations: listAvailableIntegrations().map((integration) => ({
31379
+ id: integration.id,
31380
+ description: integration.description,
31381
+ installed: installedIntegrations.has(integration.id)
31382
+ }))
31383
+ };
31384
+ }
31385
+ async function listSchemaChoices(cwd) {
31386
+ const config = await resolveConfigOrExit(cwd);
31387
+ const paths = resolveProjectPaths(config);
31388
+ return listSchemaNames(path56.join(cwd, ...paths.schemasDir.split("/")));
31389
+ }
31390
+ async function listComponentChoices(cwd) {
31391
+ const config = await resolveConfigOrExit(cwd);
31392
+ return getAllComponentNamesForConfig(config);
31393
+ }
31394
+
31395
+ // adapters/next/commands/remove.ts
31396
+ import path57 from "path";
31397
+ import * as p29 from "@clack/prompts";
31398
+ async function runRemoveCommand(items, options) {
31399
+ const removeIntegrationsMode = Boolean(options.integration);
31400
+ if (!removeIntegrationsMode && items.includes("core")) {
31401
+ p29.log.error("The core Admin cannot be removed.");
31402
+ process.exit(1);
31403
+ }
31404
+ const presetIds = items.filter(isPresetId);
31405
+ const integrationIds = items.filter(isIntegrationId);
31406
+ if (!removeIntegrationsMode && integrationIds.length > 0) {
31407
+ p29.log.error(
31408
+ `Integration IDs require --integration. Run \`betterstart remove --integration ${integrationIds.join(" ")}\`.`
31422
31409
  );
31423
31410
  process.exit(1);
31424
31411
  }
31425
- if (options.shadcnPreset) {
31426
- runShadcnPresetUpdate({
31427
- cwd,
31428
- config,
31429
- preset: options.shadcnPreset.trim(),
31430
- only: normalizedOnly
31431
- });
31432
- return;
31412
+ if (removeIntegrationsMode && presetIds.length > 0) {
31413
+ p29.log.error(`Preset IDs cannot be removed with --integration: ${presetIds.join(", ")}`);
31414
+ process.exit(1);
31433
31415
  }
31434
- if (!options.all && components.length === 0) {
31435
- clack4.log.error(
31436
- "Provide component names or use --all. Run with --list to see available components."
31416
+ const invalidItems = removeIntegrationsMode ? items.filter((integrationId) => !isIntegrationId(integrationId)) : items.filter((presetId) => !isPresetId(presetId));
31417
+ if (invalidItems.length > 0) {
31418
+ p29.log.error(
31419
+ removeIntegrationsMode ? formatUnknownIntegrationMessage(invalidItems) : formatUnknownPresetMessage(invalidItems)
31437
31420
  );
31438
31421
  process.exit(1);
31439
31422
  }
31440
- clack4.intro("BetterStart Update Components");
31441
- const toUpdate = options.all ? getAllComponentNamesForConfig(config) : components;
31442
- const uiDir = resolveCliAssetPath("shared-assets", "react-admin", "ui");
31443
- const customDir = resolveCliAssetPath("shared-assets", "react-admin", "custom");
31444
- let updated = 0;
31445
- let skipped = 0;
31446
- const updatedTemplateNames = /* @__PURE__ */ new Set();
31447
- const updatedStaticNames = /* @__PURE__ */ new Set();
31448
- const requiredPackageDependencies = /* @__PURE__ */ new Set();
31449
- const pendingWrites = [];
31450
- function trackPackageDependencies(name) {
31451
- for (const dependency of COMPONENT_PACKAGE_DEPENDENCIES[name] ?? []) {
31452
- requiredPackageDependencies.add(dependency);
31453
- }
31454
- }
31455
- function writeTemplateEntry(name, entry) {
31456
- if (updatedTemplateNames.has(name)) {
31457
- return false;
31458
- }
31459
- if (entry.requiredPreset && !hasPreset(config, entry.requiredPreset)) {
31460
- clack4.log.warn(`${name} requires the ${entry.requiredPreset} preset and was skipped.`);
31461
- skipped++;
31462
- return false;
31463
- }
31464
- if (entry.requiredIntegration && !hasIntegration(config, entry.requiredIntegration)) {
31465
- clack4.log.warn(
31466
- `${name} requires the ${entry.requiredIntegration} integration and was skipped.`
31467
- );
31468
- skipped++;
31469
- return false;
31470
- }
31471
- const { relPath, content } = applyNamespaceToTemplateEntry(entry, config, cwd);
31472
- const baseDir = entry.base === "cwd" ? cwd : admin;
31473
- const destPath = path59.join(baseDir, relPath);
31474
- if (entry.preserveExisting && fs45.existsSync(destPath)) {
31475
- clack4.log.info(`Preserved ${relPath}`);
31476
- updatedTemplateNames.add(name);
31477
- skipped++;
31478
- return false;
31479
- }
31480
- pendingWrites.push({
31481
- displayPath: relPath,
31482
- write: () => {
31483
- fsExtra.ensureDirSync(path59.dirname(destPath));
31484
- fs45.writeFileSync(destPath, content, "utf-8");
31485
- clack4.log.success(`Updated ${relPath}`);
31486
- }
31423
+ const cwd = options.cwd ? path57.resolve(options.cwd) : process.cwd();
31424
+ const config = await resolveConfigOrExit(cwd);
31425
+ const pm = detectPackageManager(cwd);
31426
+ if (!options.force) {
31427
+ const confirmed = await p29.confirm({
31428
+ message: `Remove ${removeIntegrationsMode ? "integration" : "preset"}${items.length === 1 ? "" : "s"} ${items.join(", ")}?`,
31429
+ initialValue: false
31487
31430
  });
31488
- updatedTemplateNames.add(name);
31489
- trackPackageDependencies(name);
31490
- updated++;
31491
- return true;
31492
- }
31493
- function writeTemplateEntryWithDependencies(name, entry) {
31494
- const wroteEntry = writeTemplateEntry(name, entry);
31495
- if (!wroteEntry) {
31496
- return false;
31497
- }
31498
- for (const dependencyName of entry.dependencies ?? []) {
31499
- writeNamedDependency(dependencyName);
31431
+ if (p29.isCancel(confirmed) || !confirmed) {
31432
+ p29.cancel(`${removeIntegrationsMode ? "Integration" : "Preset"} removal cancelled.`);
31433
+ process.exit(0);
31500
31434
  }
31501
- return true;
31502
31435
  }
31503
- function writeStaticAssetEntry(assetDirectory, name) {
31504
- const key = `${assetDirectory}:${name}`;
31505
- if (updatedStaticNames.has(key)) {
31506
- return false;
31507
- }
31508
- const assetDir = assetDirectory === "ui" ? uiDir : customDir;
31509
- const assetFile = findStaticAssetFile(assetDir, name);
31510
- if (!assetFile) {
31511
- return false;
31512
- }
31513
- const namespace = config.frameworkConfig.next.namespace;
31514
- const namespacedAssetFile = applyAdminNamespaceToPath(assetFile, namespace);
31515
- const destPath = path59.join(admin, "components", assetDirectory, namespacedAssetFile);
31516
- pendingWrites.push({
31517
- displayPath: `components/${assetDirectory}/${namespacedAssetFile}`,
31518
- write: () => {
31519
- fsExtra.ensureDirSync(path59.dirname(destPath));
31520
- writeNamespacedFile(path59.join(assetDir, assetFile), destPath, namespace);
31521
- clack4.log.success(`Updated components/${assetDirectory}/${namespacedAssetFile}`);
31522
- }
31436
+ if (removeIntegrationsMode) {
31437
+ const result2 = await removeIntegrations({
31438
+ cwd,
31439
+ config,
31440
+ pm,
31441
+ integrationIds
31523
31442
  });
31524
- if (assetDirectory === "custom") {
31525
- const assetSubdir = path59.join(assetDir, name);
31526
- if (fs45.existsSync(assetSubdir) && fs45.statSync(assetSubdir).isDirectory()) {
31527
- const namespacedName = applyAdminNamespaceToPath(name, namespace);
31528
- const destSubdir = path59.join(admin, "components", assetDirectory, namespacedName);
31529
- pendingWrites.push({
31530
- displayPath: `components/${assetDirectory}/${namespacedName}/ (template files)`,
31531
- write: () => {
31532
- fsExtra.ensureDirSync(destSubdir);
31533
- copyNamespacedDirectory(assetSubdir, destSubdir, namespace);
31534
- clack4.log.success(
31535
- `Updated components/${assetDirectory}/${namespacedName}/ (template files)`
31536
- );
31537
- }
31538
- });
31539
- }
31540
- }
31541
- updatedStaticNames.add(key);
31542
- trackPackageDependencies(name);
31543
- updated++;
31544
- if (assetDirectory === "custom") {
31545
- for (const dependencyName of STATIC_CUSTOM_DEPENDENCIES[name] ?? []) {
31546
- writeNamedDependency(dependencyName);
31547
- }
31443
+ writeConfigFile(cwd, result2.config);
31444
+ if (result2.removed.length === 0) {
31445
+ p29.outro("No integrations were removed.");
31446
+ return;
31548
31447
  }
31549
- return true;
31550
- }
31551
- function writeTiptapTemplates() {
31552
- const key = "custom:tiptap";
31553
- if (updatedStaticNames.has(key)) {
31554
- return true;
31448
+ if (result2.warnings.length > 0) {
31449
+ p29.note(result2.warnings.join("\n"), "Warnings");
31555
31450
  }
31556
- const srcBaseDir = resolveCliAssetPath(
31557
- "shared-assets",
31558
- "react-admin",
31559
- "custom",
31560
- "content-editor"
31451
+ p29.outro(
31452
+ `Removed integration${result2.removed.length === 1 ? "" : "s"}: ${result2.removed.join(", ")}`
31561
31453
  );
31562
- const namespace = config.frameworkConfig.next.namespace;
31563
- const destBaseDir = path59.join(admin, "components", "custom", "content-editor");
31564
- if (!fs45.existsSync(srcBaseDir)) {
31565
- return false;
31566
- }
31567
- const dirsToCopy = [];
31568
- for (const directory of TIPTAP_CONTENT_EDITOR_DIRECTORIES) {
31569
- const srcDir = path59.join(srcBaseDir, directory);
31570
- if (!fs45.existsSync(srcDir)) {
31454
+ return;
31455
+ }
31456
+ const result = await removePresets({
31457
+ cwd,
31458
+ config,
31459
+ pm,
31460
+ presetIds
31461
+ });
31462
+ writeConfigFile(cwd, result.config);
31463
+ if (result.removed.length === 0) {
31464
+ p29.outro("No presets were removed.");
31465
+ return;
31466
+ }
31467
+ if (result.warnings.length > 0) {
31468
+ p29.note(result.warnings.join("\n"), "Warnings");
31469
+ }
31470
+ p29.outro(`Removed preset${result.removed.length === 1 ? "" : "s"}: ${result.removed.join(", ")}`);
31471
+ }
31472
+
31473
+ // adapters/next/commands/remove-schema.ts
31474
+ import fs43 from "fs";
31475
+ import path58 from "path";
31476
+ import * as clack4 from "@clack/prompts";
31477
+ function removePath2(cwd, filePath) {
31478
+ const fullPath = path58.join(cwd, ...filePath.split("/"));
31479
+ const existed = fs43.existsSync(fullPath);
31480
+ fs43.rmSync(fullPath, { recursive: true, force: true });
31481
+ return existed;
31482
+ }
31483
+ function cleanupEmptyDirs3(cwd, deletedPaths, configPaths) {
31484
+ const stopRoots = /* @__PURE__ */ new Set([
31485
+ path58.join(cwd, ...configPaths.adminDir.split("/")),
31486
+ path58.join(cwd, ...configPaths.adminNavigationDir.split("/")),
31487
+ path58.join(cwd, ...configPaths.adminWebhookEventsDir.split("/")),
31488
+ path58.join(cwd, ...configPaths.pagesDir.split("/"))
31489
+ ]);
31490
+ for (const deletedPath of deletedPaths) {
31491
+ let current = path58.dirname(path58.join(cwd, ...deletedPath.split("/")));
31492
+ while (!stopRoots.has(current)) {
31493
+ if (!fs43.existsSync(current)) {
31494
+ current = path58.dirname(current);
31571
31495
  continue;
31572
31496
  }
31573
- dirsToCopy.push({
31574
- srcDir,
31575
- destDir: path59.join(destBaseDir, applyAdminNamespaceToPath(directory, namespace))
31576
- });
31577
- }
31578
- if (dirsToCopy.length === 0) {
31579
- return false;
31580
- }
31581
- pendingWrites.push({
31582
- displayPath: "components/custom/content-editor/tiptap-*/ (template files)",
31583
- write: () => {
31584
- for (const { srcDir, destDir } of dirsToCopy) {
31585
- fsExtra.ensureDirSync(destDir);
31586
- copyNamespacedDirectory(srcDir, destDir, namespace);
31587
- }
31588
- clack4.log.success("Updated components/custom/content-editor/tiptap-*/ (template files)");
31589
- removeLegacyDirectory(path59.join(admin, "components", "custom", "tiptap"));
31590
- }
31591
- });
31592
- updatedStaticNames.add(key);
31593
- trackPackageDependencies("tiptap");
31594
- updated++;
31595
- for (const dependencyName of TIPTAP_TEMPLATE_DEPENDENCIES) {
31596
- writeNamedDependency(dependencyName);
31597
- }
31598
- return true;
31599
- }
31600
- function writeNamedDependency(name) {
31601
- const entry = TEMPLATE_REGISTRY[name];
31602
- if (entry) {
31603
- writeTemplateEntryWithDependencies(name, entry);
31604
- return;
31497
+ const entries = fs43.readdirSync(current);
31498
+ if (entries.length > 0) {
31499
+ break;
31500
+ }
31501
+ fs43.rmdirSync(current);
31502
+ current = path58.dirname(current);
31605
31503
  }
31606
- if (writeStaticAssetEntry("ui", name)) {
31607
- return;
31504
+ }
31505
+ }
31506
+ function resolveSchemaOwnerForRemoval(cwd, schemaName) {
31507
+ const explicitOwner = getSchemaOwner(cwd, schemaName);
31508
+ if (explicitOwner) {
31509
+ return explicitOwner;
31510
+ }
31511
+ if (schemaName === "settings") {
31512
+ return "core";
31513
+ }
31514
+ return "user";
31515
+ }
31516
+ async function runRemoveSchemaCommand(schemaName, options) {
31517
+ const owner = resolveSchemaOwnerForRemoval(
31518
+ options.cwd ? path58.resolve(options.cwd) : process.cwd(),
31519
+ schemaName
31520
+ );
31521
+ if (owner === "core") {
31522
+ clack4.log.error(
31523
+ `"${schemaName}" is a core Admin module and cannot be removed with remove-schema.`
31524
+ );
31525
+ process.exit(1);
31526
+ }
31527
+ if (owner.startsWith("preset:")) {
31528
+ clack4.log.error(`"${schemaName}" is owned by ${owner}. Remove the owning preset instead.`);
31529
+ process.exit(1);
31530
+ }
31531
+ const cwd = options.cwd ? path58.resolve(options.cwd) : process.cwd();
31532
+ const config = await resolveConfigOrExit(cwd);
31533
+ const paths = resolveProjectPaths(config);
31534
+ const manifest = loadManifest(cwd, schemaName);
31535
+ if (!snapshotRootExists(cwd)) {
31536
+ clack4.log.error(UNSUPPORTED_PROJECT_STATE_MESSAGE);
31537
+ process.exit(1);
31538
+ }
31539
+ if (!manifest) {
31540
+ clack4.log.error(UNSUPPORTED_PROJECT_STATE_MESSAGE);
31541
+ process.exit(1);
31542
+ }
31543
+ if (!options.force) {
31544
+ if (!isInteractiveSession()) {
31545
+ clack4.log.error(
31546
+ `Removing generated files for ${schemaName} needs confirmation. Re-run with --force.`
31547
+ );
31548
+ process.exit(1);
31608
31549
  }
31609
- if (writeStaticAssetEntry("custom", name)) {
31550
+ const confirmed = await clack4.confirm({
31551
+ message: `Remove generated files for ${schemaName}?`,
31552
+ initialValue: false
31553
+ });
31554
+ if (clack4.isCancel(confirmed) || !confirmed) {
31555
+ clack4.cancel("Cancelled.");
31610
31556
  return;
31611
31557
  }
31612
- if (name === "tiptap") {
31613
- writeTiptapTemplates();
31614
- }
31615
31558
  }
31616
- for (const name of toUpdate) {
31617
- if (TEMPLATE_REGISTRY[name]) {
31618
- const entry = TEMPLATE_REGISTRY[name];
31619
- writeTemplateEntryWithDependencies(name, entry);
31620
- continue;
31621
- }
31622
- if (writeStaticAssetEntry("ui", name)) {
31623
- continue;
31559
+ const deletedPaths = [];
31560
+ for (const file of [...manifest.files.map((entry) => entry.path), ...manifest.skipped]) {
31561
+ if (removePath2(cwd, file)) {
31562
+ deletedPaths.push(file);
31624
31563
  }
31625
- if (writeStaticAssetEntry("custom", name)) {
31626
- continue;
31564
+ }
31565
+ const loaded = (() => {
31566
+ try {
31567
+ return loadSchema(path58.join(cwd, ...paths.schemasDir.split("/")), schemaName);
31568
+ } catch {
31569
+ return null;
31627
31570
  }
31628
- if (name === "tiptap") {
31629
- if (!writeTiptapTemplates()) {
31630
- clack4.log.warn("tiptap templates not found");
31631
- skipped++;
31632
- }
31633
- continue;
31571
+ })();
31572
+ const kebabName = toKebabCase(schemaName);
31573
+ if (loaded?.type === "form") {
31574
+ if (removePath2(cwd, `${paths.adminActionsDir}/${schemaName}`)) {
31575
+ deletedPaths.push(`${paths.adminActionsDir}/${schemaName}`);
31634
31576
  }
31635
- clack4.log.warn(`Unknown component: ${name}`);
31636
- skipped++;
31637
- }
31638
- if (pendingWrites.length > 0) {
31639
- const displayPaths = pendingWrites.map((entry) => entry.displayPath);
31640
- const preview = displayPaths.slice(0, MAX_OVERWRITE_PREVIEW_ROWS);
31641
- if (displayPaths.length > preview.length) {
31642
- preview.push(`\u2026 and ${displayPaths.length - preview.length} more`);
31577
+ if (removePath2(cwd, `${paths.pagesDir}/forms/${kebabName}`)) {
31578
+ deletedPaths.push(`${paths.pagesDir}/forms/${kebabName}`);
31643
31579
  }
31644
- clack4.note(
31645
- preview.join("\n"),
31646
- `Overwriting ${displayPaths.length} file${displayPaths.length === 1 ? "" : "s"}`
31647
- );
31648
- if (!options.yes) {
31649
- if (!isInteractiveSession()) {
31650
- clack4.log.error(
31651
- `Overwriting ${displayPaths.length} file${displayPaths.length === 1 ? "" : "s"} needs confirmation. Re-run with --yes.`
31652
- );
31653
- process.exit(1);
31654
- }
31655
- const proceed = await clack4.confirm({
31656
- message: "Overwrite these files with the latest templates?",
31657
- initialValue: true
31658
- });
31659
- if (clack4.isCancel(proceed) || !proceed) {
31660
- clack4.cancel("Update cancelled.");
31661
- process.exit(0);
31662
- }
31580
+ } else {
31581
+ if (removePath2(cwd, `${paths.adminActionsDir}/${schemaName}`)) {
31582
+ deletedPaths.push(`${paths.adminActionsDir}/${schemaName}`);
31663
31583
  }
31664
- for (const entry of pendingWrites) {
31665
- entry.write();
31584
+ if (removePath2(cwd, `${paths.pagesDir}/${schemaName}`)) {
31585
+ deletedPaths.push(`${paths.pagesDir}/${schemaName}`);
31666
31586
  }
31667
31587
  }
31668
- syncInstalledPresetManifests(cwd, config);
31669
- syncInstalledIntegrationManifests(cwd, config);
31670
- const projectPackageJson = readProjectPackageJson2(cwd);
31671
- const missingPackageDependencies = Array.from(requiredPackageDependencies).filter(
31672
- (dependency) => !hasDeclaredPackage2(projectPackageJson, dependency)
31673
- );
31674
- if (missingPackageDependencies.length > 0) {
31675
- clack4.log.warn(
31676
- `Updated components require package dependencies that are not declared: ${missingPackageDependencies.join(", ")}. Run 'pnpm betterstart update-deps' before building.`
31677
- );
31588
+ cleanupEmptyDirs3(cwd, deletedPaths, paths);
31589
+ deleteSnapshot(cwd, schemaName);
31590
+ if (hasTombstone(cwd, schemaName)) {
31591
+ clearTombstone(cwd, schemaName);
31678
31592
  }
31679
- clack4.outro(
31680
- `Updated ${updated} component${updated !== 1 ? "s" : ""}${skipped > 0 ? `, ${skipped} skipped` : ""}`
31593
+ writeTombstone(cwd, schemaName);
31594
+ await applyGeneratedFiles({
31595
+ cwd,
31596
+ config,
31597
+ scope: BARREL_SCOPE,
31598
+ schemaJson: { name: BARREL_SCOPE },
31599
+ generatedFiles: renderBarrelFiles(cwd, config),
31600
+ force: false,
31601
+ interactive: false
31602
+ });
31603
+ clack4.log.info(
31604
+ `Tombstone written: .betterstart/snapshots/_removed/${schemaName}
31605
+ Schema JSON preserved.`
31681
31606
  );
31607
+ clack4.outro(`Removed generated files for ${schemaName}`);
31682
31608
  }
31683
- function removeLegacyDirectory(dirPath) {
31684
- if (fs45.existsSync(dirPath)) {
31685
- fs45.rmSync(dirPath, { recursive: true, force: true });
31609
+
31610
+ // adapters/next/commands/uninstall.ts
31611
+ import fs45 from "fs";
31612
+ import path59 from "path";
31613
+ import * as p30 from "@clack/prompts";
31614
+ import pc11 from "picocolors";
31615
+
31616
+ // adapters/next/commands/uninstall-cleaners.ts
31617
+ import fs44 from "fs";
31618
+ function stripJsonComments2(input) {
31619
+ let result = "";
31620
+ let i = 0;
31621
+ while (i < input.length) {
31622
+ if (input[i] === '"') {
31623
+ let j = i + 1;
31624
+ while (j < input.length) {
31625
+ if (input[j] === "\\") {
31626
+ j += 2;
31627
+ continue;
31628
+ }
31629
+ if (input[j] === '"') {
31630
+ j++;
31631
+ break;
31632
+ }
31633
+ j++;
31634
+ }
31635
+ result += input.slice(i, j);
31636
+ i = j;
31637
+ } else if (input[i] === "/" && input[i + 1] === "/") {
31638
+ const nl = input.indexOf("\n", i);
31639
+ i = nl === -1 ? input.length : nl;
31640
+ } else if (input[i] === "/" && input[i + 1] === "*") {
31641
+ const end = input.indexOf("*/", i + 2);
31642
+ i = end === -1 ? input.length : end + 2;
31643
+ } else {
31644
+ result += input[i];
31645
+ i++;
31646
+ }
31686
31647
  }
31648
+ return result;
31687
31649
  }
31688
- function validateShadcnPresetOptions(components, options) {
31689
- if (options.only && !options.shadcnPreset) {
31690
- clack4.cancel("--only can only be used with --shadcn-preset.");
31691
- process.exit(1);
31650
+ function cleanTsconfig(tsconfigPath, aliasRoot = "@admin") {
31651
+ if (!fs44.existsSync(tsconfigPath)) return [];
31652
+ const raw = fs44.readFileSync(tsconfigPath, "utf-8");
31653
+ const stripped = stripJsonComments2(raw).replace(/,\s*([\]}])/g, "$1");
31654
+ let tsconfig;
31655
+ try {
31656
+ tsconfig = JSON.parse(stripped);
31657
+ } catch {
31658
+ return [];
31692
31659
  }
31693
- if (!options.shadcnPreset) {
31694
- return;
31660
+ const compilerOptions = tsconfig.compilerOptions ?? {};
31661
+ const paths = compilerOptions.paths ?? {};
31662
+ const removed = [];
31663
+ for (const key of Object.keys(paths)) {
31664
+ if (key.startsWith("@admin/") || key === "@admin/*" || key.startsWith(`${aliasRoot}/`) || key === `${aliasRoot}/*`) {
31665
+ removed.push(key);
31666
+ delete paths[key];
31667
+ }
31695
31668
  }
31696
- if (options.list) {
31697
- clack4.cancel("--list cannot be combined with --shadcn-preset.");
31698
- process.exit(1);
31669
+ if (removed.length === 0) return [];
31670
+ if (Object.keys(paths).length === 0) {
31671
+ compilerOptions.paths = void 0;
31672
+ } else {
31673
+ compilerOptions.paths = paths;
31699
31674
  }
31700
- if (options.all) {
31701
- clack4.cancel("--all cannot be combined with --shadcn-preset.");
31702
- process.exit(1);
31675
+ tsconfig.compilerOptions = compilerOptions;
31676
+ fs44.writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
31677
+ `, "utf-8");
31678
+ return removed;
31679
+ }
31680
+ function cleanCss(cssPath, namespace = "admin") {
31681
+ if (!fs44.existsSync(cssPath)) return [];
31682
+ const content = fs44.readFileSync(cssPath, "utf-8");
31683
+ const lines = content.split("\n");
31684
+ const sourcePattern = new RegExp(`^@source\\s+"[^"]*(?:admin|${namespace})[^"]*";\\s*$`);
31685
+ const removed = [];
31686
+ const kept = [];
31687
+ for (const line of lines) {
31688
+ if (sourcePattern.test(line)) {
31689
+ removed.push(line.trim());
31690
+ } else {
31691
+ kept.push(line);
31692
+ }
31703
31693
  }
31704
- if (components.length > 0) {
31705
- clack4.cancel("Component names cannot be combined with --shadcn-preset.");
31706
- process.exit(1);
31694
+ if (removed.length === 0) return [];
31695
+ const cleaned = kept.join("\n").replace(/\n{3,}/g, "\n\n");
31696
+ fs44.writeFileSync(cssPath, cleaned, "utf-8");
31697
+ return removed;
31698
+ }
31699
+ function cleanEnvFile(envPath) {
31700
+ if (!fs44.existsSync(envPath)) return [];
31701
+ const content = fs44.readFileSync(envPath, "utf-8");
31702
+ const lines = content.split("\n");
31703
+ const removed = [];
31704
+ const kept = [];
31705
+ const headerPattern = /^# =+$/;
31706
+ const headerTextPattern = /^# BetterStart Admin$/;
31707
+ for (let i = 0; i < lines.length; i++) {
31708
+ const line = lines[i];
31709
+ const trimmed = line.trim();
31710
+ if (trimmed.match(/^BETTERSTART_\w+=/)) {
31711
+ const key = trimmed.split("=")[0];
31712
+ removed.push(key);
31713
+ continue;
31714
+ }
31715
+ if (headerPattern.test(trimmed)) {
31716
+ const next = lines[i + 1]?.trim();
31717
+ const afterNext = lines[i + 2]?.trim();
31718
+ if (next && headerTextPattern.test(next) && afterNext && headerPattern.test(afterNext)) {
31719
+ i += 2;
31720
+ continue;
31721
+ }
31722
+ }
31723
+ if (trimmed.startsWith("#") && !headerPattern.test(trimmed)) {
31724
+ const nextNonEmpty = findNextNonEmptyLine(lines, i + 1);
31725
+ if (nextNonEmpty?.match(/^BETTERSTART_\w+=/)) {
31726
+ continue;
31727
+ }
31728
+ }
31729
+ kept.push(line);
31730
+ }
31731
+ if (removed.length === 0) return [];
31732
+ const result = kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
31733
+ if (result === "") {
31734
+ fs44.unlinkSync(envPath);
31735
+ } else {
31736
+ fs44.writeFileSync(envPath, `${result}
31737
+ `, "utf-8");
31707
31738
  }
31739
+ return removed;
31708
31740
  }
31709
- function normalizeShadcnPresetOnly(value) {
31710
- if (!value) {
31711
- return void 0;
31712
- }
31713
- const parts = value.split(",").map((part) => part.trim().toLowerCase()).filter(Boolean);
31714
- if (parts.length !== 1 || parts[0] !== "theme") {
31715
- clack4.cancel(
31716
- "Admin-only shadcn preset updates currently support --only theme. Omit --only to apply the full preset."
31717
- );
31718
- process.exit(1);
31741
+ function findNextNonEmptyLine(lines, startIndex) {
31742
+ for (let i = startIndex; i < lines.length; i++) {
31743
+ const trimmed = lines[i].trim();
31744
+ if (trimmed !== "") return trimmed;
31719
31745
  }
31720
- return "theme";
31746
+ return null;
31721
31747
  }
31722
- function runShadcnPresetUpdate({
31723
- cwd,
31724
- config,
31725
- preset,
31726
- only
31727
- }) {
31728
- const namespace = resolveAdminNamespace(config.frameworkConfig.next.namespace);
31729
- const adminGlobalsPath = path59.join(cwd, config.paths.admin, namespace.globalsFile);
31730
- const componentsJsonPath = path59.join(cwd, "components.json");
31731
- const shadcnBackupPath = `${componentsJsonPath}.bak`;
31732
- const restoreAfterApplyPaths = [
31733
- componentsJsonPath,
31734
- shadcnBackupPath,
31735
- path59.join(cwd, config.paths.admin, "lib", "utils.ts"),
31736
- ...getHostProjectFilesToRestore(cwd)
31748
+
31749
+ // adapters/next/commands/uninstall.ts
31750
+ function findMainCss2(cwd) {
31751
+ const candidates = [
31752
+ "src/app/globals.css",
31753
+ "app/globals.css",
31754
+ "src/app/global.css",
31755
+ "app/global.css",
31756
+ "src/app/app.css",
31757
+ "app/app.css",
31758
+ "src/globals.css",
31759
+ "globals.css"
31737
31760
  ];
31738
- if (!preset) {
31739
- clack4.cancel("--shadcn-preset requires a preset code, preset name, or preset URL.");
31740
- process.exit(1);
31741
- }
31742
- if (!fs45.existsSync(adminGlobalsPath)) {
31743
- clack4.cancel(
31744
- `Admin globals file not found at ${path59.relative(cwd, adminGlobalsPath)}. Run 'betterstart update admin-globals' first.`
31745
- );
31746
- process.exit(1);
31761
+ for (const candidate of candidates) {
31762
+ const filePath = path59.join(cwd, candidate);
31763
+ if (fs45.existsSync(filePath)) return filePath;
31747
31764
  }
31748
- const shadcnBin = resolveLocalShadcnBin(cwd);
31749
- const restoreSnapshots = restoreAfterApplyPaths.map((filePath) => ({
31750
- filePath,
31751
- snapshot: snapshotFile(filePath)
31752
- }));
31753
- clack4.intro("BetterStart Shadcn Preset");
31754
- clack4.log.info(`Applying preset to ${path59.join(config.paths.admin, "components/ui")}`);
31755
- let failed = false;
31765
+ return void 0;
31766
+ }
31767
+ function isCLICreatedBiome(biomePath) {
31768
+ if (!fs45.existsSync(biomePath)) return false;
31756
31769
  try {
31757
- fs45.writeFileSync(
31758
- componentsJsonPath,
31759
- `${JSON.stringify(createAdminShadcnComponentsJson(config), null, 2)}
31760
- `,
31761
- "utf-8"
31762
- );
31763
- const args = ["apply", "--preset", preset, "--cwd", cwd, "--yes"];
31764
- if (only) {
31765
- args.push("--only", only);
31766
- }
31767
- execFileSync5(shadcnBin, args, { cwd, stdio: "inherit" });
31770
+ const content = JSON.parse(fs45.readFileSync(biomePath, "utf-8"));
31771
+ 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");
31768
31772
  } catch {
31769
- failed = true;
31770
- } finally {
31771
- for (const { filePath, snapshot } of restoreSnapshots.reverse()) {
31772
- restoreFile(filePath, snapshot);
31773
+ return false;
31774
+ }
31775
+ }
31776
+ function buildUninstallPlan(cwd, namespaceValue) {
31777
+ const steps = [];
31778
+ const namespace = resolveAdminNamespace(namespaceValue);
31779
+ const hasSrc = fs45.existsSync(path59.join(cwd, "src"));
31780
+ const appBase = hasSrc ? "src/app" : "app";
31781
+ const dirs = [];
31782
+ const adminDir = path59.join(cwd, namespace.segment);
31783
+ const legacyAdminDir = path59.join(cwd, "admin");
31784
+ const adminRouteGroup = path59.join(cwd, appBase, namespace.routeGroup);
31785
+ const legacyAdminRouteGroup = path59.join(cwd, appBase, "(admin)");
31786
+ if (fs45.existsSync(adminDir)) dirs.push(`${namespace.segment}/`);
31787
+ if (namespace.segment !== "admin" && fs45.existsSync(legacyAdminDir)) dirs.push("admin/");
31788
+ if (fs45.existsSync(adminRouteGroup)) dirs.push(`${appBase}/${namespace.routeGroup}/`);
31789
+ if (namespace.segment !== "admin" && fs45.existsSync(legacyAdminRouteGroup))
31790
+ dirs.push(`${appBase}/(admin)/`);
31791
+ if (dirs.length > 0) {
31792
+ steps.push({
31793
+ label: "Admin directories",
31794
+ items: dirs,
31795
+ count: dirs.length,
31796
+ unit: dirs.length === 1 ? "directory" : "directories",
31797
+ execute() {
31798
+ if (fs45.existsSync(adminDir)) fs45.rmSync(adminDir, { recursive: true, force: true });
31799
+ if (fs45.existsSync(legacyAdminDir))
31800
+ fs45.rmSync(legacyAdminDir, { recursive: true, force: true });
31801
+ if (fs45.existsSync(adminRouteGroup)) {
31802
+ fs45.rmSync(adminRouteGroup, { recursive: true, force: true });
31803
+ }
31804
+ if (fs45.existsSync(legacyAdminRouteGroup)) {
31805
+ fs45.rmSync(legacyAdminRouteGroup, { recursive: true, force: true });
31806
+ }
31807
+ }
31808
+ });
31809
+ }
31810
+ const configFiles = [];
31811
+ const configPaths = [];
31812
+ const candidates = [
31813
+ [CONFIG_FILE_NAME, path59.join(cwd, CONFIG_FILE_NAME)],
31814
+ ["drizzle.config.ts", path59.join(cwd, "drizzle.config.ts")],
31815
+ ["ADMIN.md", path59.join(cwd, "ADMIN.md")]
31816
+ ];
31817
+ for (const [label, fullPath] of candidates) {
31818
+ if (fs45.existsSync(fullPath)) {
31819
+ configFiles.push(label);
31820
+ configPaths.push(fullPath);
31773
31821
  }
31774
31822
  }
31775
- if (failed) {
31776
- clack4.cancel("shadcn preset application failed.");
31777
- process.exit(1);
31823
+ const biomePath = path59.join(cwd, "biome.json");
31824
+ if (isCLICreatedBiome(biomePath)) {
31825
+ configFiles.push("biome.json (CLI-created)");
31826
+ configPaths.push(biomePath);
31778
31827
  }
31779
- clack4.outro(
31780
- only ? `Applied shadcn preset parts (${only}) to the Admin.` : "Applied shadcn preset to Admin UI components and styles."
31781
- );
31782
- }
31783
- function createAdminShadcnComponentsJson(config) {
31784
- const adminPath = toPosixPath(config.paths.admin);
31785
- const namespace = resolveAdminNamespace(config.frameworkConfig.next.namespace);
31786
- return {
31787
- $schema: "https://ui.shadcn.com/schema.json",
31788
- style: "new-york",
31789
- rsc: true,
31790
- tsx: true,
31791
- tailwind: {
31792
- config: "",
31793
- css: `${adminPath}/${namespace.globalsFile}`,
31794
- baseColor: "neutral",
31795
- cssVariables: true,
31796
- prefix: ""
31797
- },
31798
- iconLibrary: "lucide",
31799
- aliases: {
31800
- components: `${namespace.alias}/components`,
31801
- ui: `${namespace.alias}/components/ui`,
31802
- hooks: `${namespace.alias}/hooks`,
31803
- lib: `${namespace.alias}/lib`,
31804
- utils: `${namespace.alias}/utils/shared/cn`
31828
+ if (configFiles.length > 0) {
31829
+ steps.push({
31830
+ label: "Config files",
31831
+ items: configFiles,
31832
+ count: configFiles.length,
31833
+ unit: configFiles.length === 1 ? "file" : "files",
31834
+ execute() {
31835
+ for (const p32 of configPaths) {
31836
+ if (fs45.existsSync(p32)) fs45.unlinkSync(p32);
31837
+ }
31838
+ }
31839
+ });
31840
+ }
31841
+ const tsconfigPath = path59.join(cwd, "tsconfig.json");
31842
+ if (fs45.existsSync(tsconfigPath)) {
31843
+ const content = fs45.readFileSync(tsconfigPath, "utf-8");
31844
+ const aliasMatches = [
31845
+ ...content.match(/"@admin\//g) ?? [],
31846
+ ...content.match(new RegExp(`"${namespace.alias}/`, "g")) ?? []
31847
+ ];
31848
+ if (aliasMatches && aliasMatches.length > 0) {
31849
+ const aliasCount = aliasMatches.length;
31850
+ steps.push({
31851
+ label: "tsconfig.json path aliases",
31852
+ items: [`${namespace.alias}/* aliases in tsconfig.json`],
31853
+ count: aliasCount,
31854
+ unit: aliasCount === 1 ? "alias" : "aliases",
31855
+ execute() {
31856
+ cleanTsconfig(tsconfigPath, namespace.alias);
31857
+ }
31858
+ });
31805
31859
  }
31806
- };
31807
- }
31808
- function toPosixPath(value) {
31809
- return value.replace(/\\/g, "/");
31810
- }
31811
- function resolveLocalShadcnBin(cwd) {
31812
- const binName = process.platform === "win32" ? "shadcn.cmd" : "shadcn";
31813
- const shadcnBin = path59.join(cwd, "node_modules", ".bin", binName);
31814
- if (!fs45.existsSync(shadcnBin)) {
31815
- clack4.cancel(
31816
- `shadcn is not installed in this project. Run 'betterstart update-deps' and try again.`
31860
+ }
31861
+ const cssFile = findMainCss2(cwd);
31862
+ if (cssFile) {
31863
+ const cssContent = fs45.readFileSync(cssFile, "utf-8");
31864
+ const sourceLines = cssContent.split("\n").filter(
31865
+ (l) => new RegExp(`^@source\\s+"[^"]*(?:admin|${namespace.segment})[^"]*";\\s*$`).test(l)
31817
31866
  );
31818
- process.exit(1);
31867
+ if (sourceLines.length > 0) {
31868
+ const relCss = path59.relative(cwd, cssFile);
31869
+ steps.push({
31870
+ label: `CSS @source lines (${relCss})`,
31871
+ items: [`@source lines in ${relCss}`],
31872
+ count: sourceLines.length,
31873
+ unit: sourceLines.length === 1 ? "line" : "lines",
31874
+ execute() {
31875
+ cleanCss(cssFile, namespace.segment);
31876
+ }
31877
+ });
31878
+ }
31819
31879
  }
31820
- return shadcnBin;
31821
- }
31822
- function getHostProjectFilesToRestore(cwd) {
31823
- const hostRelativePaths = [
31824
- "app/layout.tsx",
31825
- "app/layout.ts",
31826
- "app/layout.jsx",
31827
- "app/layout.js",
31828
- "src/app/layout.tsx",
31829
- "src/app/layout.ts",
31830
- "src/app/layout.jsx",
31831
- "src/app/layout.js",
31832
- "app/globals.css",
31833
- "src/app/globals.css"
31834
- ];
31835
- return hostRelativePaths.map((relativePath) => path59.join(cwd, relativePath));
31836
- }
31837
- function snapshotFile(filePath) {
31838
- if (!fs45.existsSync(filePath)) {
31839
- return { existed: false };
31880
+ const envPath = path59.join(cwd, ".env.local");
31881
+ if (fs45.existsSync(envPath)) {
31882
+ const envContent = fs45.readFileSync(envPath, "utf-8");
31883
+ const bsVars = envContent.split("\n").filter((l) => l.trim().match(/^BETTERSTART_\w+=/)).map((l) => l.split("=")[0]);
31884
+ if (bsVars.length > 0) {
31885
+ steps.push({
31886
+ label: ".env.local variables",
31887
+ items: ["BETTERSTART_* vars in .env.local"],
31888
+ count: bsVars.length,
31889
+ unit: bsVars.length === 1 ? "variable" : "variables",
31890
+ execute() {
31891
+ cleanEnvFile(envPath);
31892
+ }
31893
+ });
31894
+ }
31840
31895
  }
31841
- return { existed: true, content: fs45.readFileSync(filePath, "utf-8") };
31896
+ return steps;
31842
31897
  }
31843
- function restoreFile(filePath, snapshot) {
31844
- if (snapshot.existed) {
31845
- fs45.writeFileSync(filePath, snapshot.content ?? "", "utf-8");
31898
+ async function runUninstallCommand(options) {
31899
+ const cwd = options.cwd ? path59.resolve(options.cwd) : process.cwd();
31900
+ p30.intro(pc11.bgRed(pc11.white(" BetterStart Uninstall ")));
31901
+ let namespace = DEFAULT_ADMIN_NAMESPACE;
31902
+ try {
31903
+ const config = await resolveConfig(cwd);
31904
+ namespace = config.frameworkConfig.next.namespace;
31905
+ } catch {
31906
+ }
31907
+ const steps = buildUninstallPlan(cwd, namespace);
31908
+ if (steps.length === 0) {
31909
+ p30.log.success(`${pc11.green("\u2713")} Nothing to remove \u2014 project is already clean.`);
31910
+ p30.outro("Project already clean");
31846
31911
  return;
31847
31912
  }
31848
- if (fs45.existsSync(filePath)) {
31849
- fs45.rmSync(filePath, { force: true });
31913
+ const planLines = steps.map((step) => {
31914
+ const names = step.items.join(" ");
31915
+ const countLabel = pc11.dim(`${step.count} ${step.unit}`);
31916
+ return `${pc11.red("\u2717")} ${names} ${countLabel}`;
31917
+ });
31918
+ p30.note(planLines.join("\n"), "Uninstall plan");
31919
+ if (!options.force) {
31920
+ const confirmed = await p30.confirm({
31921
+ message: "Proceed with uninstall?",
31922
+ initialValue: false
31923
+ });
31924
+ if (p30.isCancel(confirmed) || !confirmed) {
31925
+ p30.cancel("Uninstall cancelled.");
31926
+ process.exit(0);
31927
+ }
31928
+ }
31929
+ const s = spinner2();
31930
+ s.start(steps[0].label);
31931
+ for (const step of steps) {
31932
+ s.message(step.label);
31933
+ step.execute();
31850
31934
  }
31935
+ const parts = steps.map((step) => `${step.count} ${step.unit}`);
31936
+ s.stop(`Removed ${parts.join(", ")}`);
31937
+ p30.note(pc11.dim("Database tables were NOT dropped \u2014 drop them manually if needed."), "Next steps");
31938
+ p30.outro("Uninstall complete");
31851
31939
  }
31852
31940
 
31853
31941
  // adapters/next/commands/update-deps.ts
@@ -31913,6 +32001,7 @@ async function runUpdateStylesCommand(options) {
31913
32001
 
31914
32002
  // adapters/next/commands-runtime.ts
31915
32003
  var nextCommandRuntime = {
32004
+ listComponentChoices,
31916
32005
  listInstallableChoices,
31917
32006
  listSchemaChoices,
31918
32007
  runAdd: runAddCommand,