betterstart-cli 0.0.91 → 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);
@@ -317,7 +366,7 @@ function requireInitializedProject(command) {
317
366
 
318
367
  // adapters/next/commands/add.ts
319
368
  import path27 from "path";
320
- import * as p8 from "@clack/prompts";
369
+ import * as p9 from "@clack/prompts";
321
370
 
322
371
  // core-engine/config/serialize.ts
323
372
  import fs3 from "fs";
@@ -866,10 +915,93 @@ async function resolveConfigOrExit(cwd) {
866
915
  }
867
916
 
868
917
  // adapters/next/generators/post-generate.ts
869
- import { execFileSync as execFileSync2 } from "child_process";
918
+ import { execFile } from "child_process";
870
919
  import fs8 from "fs";
871
920
  import path10 from "path";
872
- import * as p5 from "@clack/prompts";
921
+ import { promisify } from "util";
922
+ import * as p6 from "@clack/prompts";
923
+
924
+ // core-engine/utils/spinner.ts
925
+ import { stripVTControlCharacters } from "util";
926
+ import * as p3 from "@clack/prompts";
927
+ var RENDER_OVERHEAD = 7;
928
+ var MIN_MESSAGE_WIDTH = 8;
929
+ function fitSpinnerMessage(message) {
930
+ const normalized = message.replace(/\t/g, " ");
931
+ const columns = process.stdout.columns ?? 80;
932
+ const max = Math.max(columns - RENDER_OVERHEAD, MIN_MESSAGE_WIDTH);
933
+ const visible = stripVTControlCharacters(normalized);
934
+ if (visible.length <= max) return normalized;
935
+ return `${visible.slice(0, max - 1)}\u2026`;
936
+ }
937
+ var CLACK_LINE_ROWS = 2;
938
+ var LOG_PREFIX_WIDTH = 3;
939
+ function clackPromptRows(message, value) {
940
+ const columns = process.stdout.columns ?? 80;
941
+ const rows = (text7) => Math.max(1, Math.ceil((stripVTControlCharacters(text7).length + LOG_PREFIX_WIDTH) / columns));
942
+ return 1 + rows(message) + rows(value);
943
+ }
944
+ function clackLogRows(message) {
945
+ const columns = process.stdout.columns ?? 80;
946
+ const width = stripVTControlCharacters(message).length + LOG_PREFIX_WIDTH;
947
+ return 1 + Math.max(1, Math.ceil(width / columns));
948
+ }
949
+ function eraseRows(rows) {
950
+ if (rows <= 0 || !process.stdout.isTTY || process.env.CI === "true") return;
951
+ process.stdout.write(`\x1B[${rows}A\x1B[${rows}M`);
952
+ }
953
+ function eraseRowsAbove(rows, rowsBelow) {
954
+ if (rows <= 0 || rowsBelow < 0 || !process.stdout.isTTY || process.env.CI === "true") {
955
+ return;
956
+ }
957
+ const up = rows + rowsBelow;
958
+ const restore = rowsBelow > 0 ? `\x1B[${rowsBelow}B` : "";
959
+ process.stdout.write(`\x1B[${up}A\x1B[${rows}M${restore}`);
960
+ }
961
+ function eraseClackLine(options = {}) {
962
+ if (!process.stdout.isTTY || process.env.CI === "true") return;
963
+ const below = (options.linesBelow ?? 0) * CLACK_LINE_ROWS;
964
+ const up = below + CLACK_LINE_ROWS;
965
+ const restore = below > 0 ? `\x1B[${below}B` : "";
966
+ process.stdout.write(`\x1B[${up}A\x1B[${CLACK_LINE_ROWS}M${restore}`);
967
+ }
968
+ var activeSpinners = 0;
969
+ function hasActiveSpinner() {
970
+ return activeSpinners > 0;
971
+ }
972
+ function spinner2(options) {
973
+ const inner = p3.spinner({ ...options, cancelMessage: "Setup cancelled." });
974
+ const guided = options?.withGuide !== false;
975
+ let started = false;
976
+ const setStarted = (next) => {
977
+ if (started === next) return;
978
+ started = next;
979
+ activeSpinners += next ? 1 : -1;
980
+ };
981
+ return {
982
+ start: (message = "") => {
983
+ if (started) {
984
+ inner.message(fitSpinnerMessage(message));
985
+ return;
986
+ }
987
+ setStarted(true);
988
+ inner.start(fitSpinnerMessage(message));
989
+ },
990
+ message: (message = "") => inner.message(fitSpinnerMessage(message)),
991
+ stop: (message = "") => {
992
+ setStarted(false);
993
+ inner.stop(message);
994
+ },
995
+ clear: () => {
996
+ if (!started) return;
997
+ setStarted(false);
998
+ inner.clear();
999
+ if (guided && process.stdout.isTTY && process.env.CI !== "true") {
1000
+ process.stdout.write("\x1B[1A\x1B[2K");
1001
+ }
1002
+ }
1003
+ };
1004
+ }
873
1005
 
874
1006
  // adapters/next/init/scaffolders/dependencies.ts
875
1007
  import { spawn } from "child_process";
@@ -1752,7 +1884,7 @@ async function syncProjectCliDependency(cwd, pm) {
1752
1884
  // adapters/next/utils/drizzle-push.ts
1753
1885
  import { spawn as spawn2 } from "child_process";
1754
1886
  import path9 from "path";
1755
- import * as p3 from "@clack/prompts";
1887
+ import * as p4 from "@clack/prompts";
1756
1888
  var PG_SSL_WARNING_START = "Warning: SECURITY WARNING: The SSL modes 'prefer', 'require', and 'verify-ca' are treated as aliases for 'verify-full'.";
1757
1889
  var PG_SSL_WARNING_END = "See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode definitions.";
1758
1890
  var NODE_TRACE_WARNING_HINT = "(Use `node --trace-warnings";
@@ -1928,7 +2060,7 @@ function runDrizzlePush(cwd, options = {}) {
1928
2060
  }
1929
2061
  if (stdoutTtySuppressor.sawError() || stderrTtySuppressor.sawError()) {
1930
2062
  notifyOutput();
1931
- p3.log.info("Database changes need your input \u2014 continuing in drizzle-kit.");
2063
+ p4.log.info("Database changes need your input \u2014 continuing in drizzle-kit.");
1932
2064
  const attached = spawn2(drizzleBin, ["push", "--force"], {
1933
2065
  cwd,
1934
2066
  stdio: "inherit",
@@ -1974,7 +2106,7 @@ ${stderr}`.trim();
1974
2106
  }
1975
2107
 
1976
2108
  // adapters/next/utils/next-steps.ts
1977
- import * as p4 from "@clack/prompts";
2109
+ import * as p5 from "@clack/prompts";
1978
2110
  function printNextSteps(options) {
1979
2111
  const steps = [`1. Review ${options.reviewLabel}`];
1980
2112
  if (options.needsMigration) {
@@ -1983,11 +2115,12 @@ function printNextSteps(options) {
1983
2115
  } else {
1984
2116
  steps.push(`2. Start the dev server and visit ${options.route}`);
1985
2117
  }
1986
- p4.note(steps.join("\n"), "Next steps");
2118
+ p5.note(steps.join("\n"), "Next steps");
1987
2119
  }
1988
2120
 
1989
2121
  // adapters/next/generators/post-generate.ts
1990
2122
  var BIOME_FORMAT_TIMEOUT_MS = 3e4;
2123
+ var execFileAsync = promisify(execFile);
1991
2124
  function loadEnvFile(cwd) {
1992
2125
  const envPath = path10.join(cwd, ".env.local");
1993
2126
  if (!fs8.existsSync(envPath)) return;
@@ -2011,10 +2144,10 @@ function loadEnvFile(cwd) {
2011
2144
  }
2012
2145
  }
2013
2146
  }
2014
- function runPmScript(pm, script, cwd, timeout) {
2147
+ async function runPmScript(pm, script, cwd, timeout) {
2015
2148
  const args = pm === "bun" ? ["run", script] : [script];
2016
2149
  try {
2017
- execFileSync2(pm, args, { cwd, stdio: "pipe", timeout });
2150
+ await execFileAsync(pm, args, { cwd, timeout });
2018
2151
  return true;
2019
2152
  } catch {
2020
2153
  return false;
@@ -2025,7 +2158,7 @@ function getExistingTrackedFilePaths(cwd) {
2025
2158
  (filePath) => fs8.existsSync(path10.join(cwd, ...filePath.split("/")))
2026
2159
  );
2027
2160
  }
2028
- function runBiomeCheckWrite(cwd) {
2161
+ async function runBiomeCheckWrite(cwd) {
2029
2162
  const biomeBin = path10.join(cwd, "node_modules", ".bin", "biome");
2030
2163
  if (!fs8.existsSync(biomeBin)) {
2031
2164
  return false;
@@ -2044,9 +2177,8 @@ function runBiomeCheckWrite(cwd) {
2044
2177
  ...configPath ? ["--config-path", configPath] : [],
2045
2178
  ...chunk
2046
2179
  ];
2047
- execFileSync2(biomeBin, args, {
2180
+ await execFileAsync(biomeBin, args, {
2048
2181
  cwd,
2049
- stdio: "pipe",
2050
2182
  timeout: BIOME_FORMAT_TIMEOUT_MS
2051
2183
  });
2052
2184
  }
@@ -2063,18 +2195,15 @@ function buildAddDependencyArgs(pm, dependency, dev) {
2063
2195
  return ["install", ...devFlag ? [devFlag] : [], dependency];
2064
2196
  }
2065
2197
  }
2066
- function installDependency(cwd, pm, dependency, dev = false) {
2198
+ async function installDependency(cwd, pm, dependency, dev = false) {
2067
2199
  try {
2068
- execFileSync2(pm, buildAddDependencyArgs(pm, dependency, dev), {
2069
- cwd,
2070
- stdio: "pipe"
2071
- });
2200
+ await execFileAsync(pm, buildAddDependencyArgs(pm, dependency, dev), { cwd });
2072
2201
  return true;
2073
2202
  } catch {
2074
2203
  return false;
2075
2204
  }
2076
2205
  }
2077
- function ensureDatabasePushDependencies(cwd, pm) {
2206
+ async function ensureDatabasePushDependencies(cwd, pm) {
2078
2207
  const missing = [];
2079
2208
  if (!hasPostgresRuntimeDependency(cwd)) {
2080
2209
  missing.push({ name: POSTGRES_RUNTIME_DEP, dev: false });
@@ -2085,25 +2214,25 @@ function ensureDatabasePushDependencies(cwd, pm) {
2085
2214
  if (missing.length === 0) {
2086
2215
  return true;
2087
2216
  }
2088
- p5.log.info(
2217
+ p6.log.info(
2089
2218
  `Installing database push dependencies (${missing.map((dependency) => dependency.name).join(", ")})...`
2090
2219
  );
2091
2220
  for (const dependency of missing) {
2092
- const installed2 = installDependency(cwd, pm, dependency.name, dependency.dev);
2221
+ const installed2 = await installDependency(cwd, pm, dependency.name, dependency.dev);
2093
2222
  if (!installed2) {
2094
- p5.log.warn(`Failed to install ${dependency.name}`);
2223
+ p6.log.warn(`Failed to install ${dependency.name}`);
2095
2224
  return false;
2096
2225
  }
2097
2226
  }
2098
2227
  const ready = hasPostgresRuntimeDependency(cwd) && hasDrizzleKitPostgresDriverDependency(cwd);
2099
2228
  if (ready) {
2100
- p5.log.success("Database push dependencies installed");
2229
+ p6.log.success("Database push dependencies installed");
2101
2230
  } else {
2102
2231
  const unresolved = [
2103
2232
  !hasPostgresRuntimeDependency(cwd) ? POSTGRES_RUNTIME_DEP : null,
2104
2233
  !hasDrizzleKitPostgresDriverDependency(cwd) ? DRIZZLE_KIT_POSTGRES_DRIVER_DEP : null
2105
2234
  ].filter((dependency) => Boolean(dependency));
2106
- p5.log.warn(`Installed dependencies but could not resolve ${unresolved.join(", ")}`);
2235
+ p6.log.warn(`Installed dependencies but could not resolve ${unresolved.join(", ")}`);
2107
2236
  }
2108
2237
  return ready;
2109
2238
  }
@@ -2146,8 +2275,8 @@ async function runPostGenerate(cwd, schemaName, options = {}) {
2146
2275
  "Formatting: skipped (biome refuses files with conflict markers)",
2147
2276
  ...(options.conflictPaths ?? []).map((conflictPath) => ` - ${conflictPath}`)
2148
2277
  ];
2149
- p5.log.warn(lines.join("\n"));
2150
- p5.log.message(
2278
+ p6.log.warn(lines.join("\n"));
2279
+ p6.log.message(
2151
2280
  "Resolve markers in the files above and re-run the BetterStart command that wrote them. Post-write tasks run automatically once every file is clean."
2152
2281
  );
2153
2282
  return result;
@@ -2157,62 +2286,83 @@ async function runPostGenerate(cwd, schemaName, options = {}) {
2157
2286
  const dbUrl = process.env.DATABASE_URL;
2158
2287
  if (!dbUrl) {
2159
2288
  result.dbPush = "no-db-url";
2160
- p5.log.warn(
2289
+ p6.log.warn(
2161
2290
  [
2162
2291
  "Database: skipped (no DATABASE_URL configured)",
2163
2292
  " To sync later: run db:push after setting DATABASE_URL"
2164
2293
  ].join("\n")
2165
2294
  );
2166
- } else if (!ensureDatabasePushDependencies(cwd, pm)) {
2295
+ } else if (!await ensureDatabasePushDependencies(cwd, pm)) {
2167
2296
  result.dbPush = "failed";
2168
- p5.log.warn(
2297
+ p6.log.warn(
2169
2298
  "Database push failed (install database dependencies and run drizzle-kit push manually)"
2170
2299
  );
2171
2300
  } else if (hasPkgScript(cwd, "db:push")) {
2172
- p5.log.info("Running db:push...");
2173
- const ok = runPmScript(pm, "db:push", cwd);
2301
+ const s = spinner2();
2302
+ s.start("Pushing database schema");
2303
+ const ok = await runPmScript(pm, "db:push", cwd);
2174
2304
  result.dbPush = ok ? "success" : "failed";
2175
2305
  if (ok) {
2176
- p5.log.success("Database schema synced");
2306
+ s.stop("Database schema synced");
2177
2307
  } else {
2178
- p5.log.warn("Database push failed (run db:push manually)");
2308
+ s.clear();
2309
+ p6.log.warn("Database push failed (run db:push manually)");
2179
2310
  }
2180
2311
  } else {
2181
- p5.log.info("Running drizzle-kit push...");
2312
+ const s = spinner2();
2313
+ let spinnerVisible = true;
2314
+ const clearSpinner = () => {
2315
+ if (!spinnerVisible) return;
2316
+ spinnerVisible = false;
2317
+ s.clear();
2318
+ };
2319
+ s.start("Pushing database schema");
2182
2320
  try {
2183
- const pushResult = await runDrizzlePush(cwd, { interactive: true });
2321
+ const pushResult = await runDrizzlePush(cwd, {
2322
+ interactive: true,
2323
+ onOutput: clearSpinner
2324
+ });
2184
2325
  if (!pushResult.success) {
2185
2326
  throw new Error(pushResult.error ?? "drizzle-kit push failed");
2186
2327
  }
2187
2328
  result.dbPush = "success";
2188
- p5.log.success("Database schema synced");
2329
+ if (spinnerVisible) {
2330
+ spinnerVisible = false;
2331
+ s.stop("Database schema synced");
2332
+ } else {
2333
+ p6.log.success("Database schema synced");
2334
+ }
2189
2335
  } catch {
2190
2336
  result.dbPush = "failed";
2191
- p5.log.warn("Database push failed (run drizzle-kit push manually)");
2337
+ clearSpinner();
2338
+ p6.log.warn("Database push failed (run drizzle-kit push manually)");
2192
2339
  }
2193
2340
  }
2194
2341
  } else {
2195
- p5.log.info(`Database: skipped (${options.skipMigrationMessage ?? "--skip-migration"})`);
2342
+ p6.log.info(`Database: skipped (${options.skipMigrationMessage ?? "--skip-migration"})`);
2196
2343
  }
2344
+ const formatSpinner = spinner2();
2345
+ formatSpinner.start("Formatting generated files");
2197
2346
  if (hasPkgScript(cwd, "lint:fix")) {
2198
- p5.log.info("Running lint:fix...");
2199
- const ok = runPmScript(pm, "lint:fix", cwd, BIOME_FORMAT_TIMEOUT_MS);
2347
+ const ok = await runPmScript(pm, "lint:fix", cwd, BIOME_FORMAT_TIMEOUT_MS);
2200
2348
  result.lintFix = ok ? "success" : "failed";
2201
2349
  if (ok) {
2202
- p5.log.success("Code formatted");
2350
+ formatSpinner.stop("Code formatted");
2203
2351
  } else {
2204
- p5.log.warn("Lint fix had issues (run lint:fix manually)");
2352
+ formatSpinner.clear();
2353
+ p6.log.warn("Lint fix had issues (run lint:fix manually)");
2205
2354
  }
2206
2355
  } else {
2207
2356
  try {
2208
- if (!runBiomeCheckWrite(cwd)) {
2357
+ if (!await runBiomeCheckWrite(cwd)) {
2209
2358
  throw new Error("Biome binary not found");
2210
2359
  }
2211
2360
  result.lintFix = "success";
2212
- p5.log.success("Code formatted with Biome");
2361
+ formatSpinner.stop("Code formatted with Biome");
2213
2362
  } catch {
2214
2363
  result.lintFix = "failed";
2215
- p5.log.warn("Biome formatting had issues (run biome check --write manually)");
2364
+ formatSpinner.clear();
2365
+ p6.log.warn("Biome formatting had issues (run biome check --write manually)");
2216
2366
  }
2217
2367
  }
2218
2368
  if (options.showNextSteps !== false) {
@@ -2229,7 +2379,7 @@ async function runPostGenerate(cwd, schemaName, options = {}) {
2229
2379
  // adapters/next/integration-runtime.ts
2230
2380
  import fs14 from "fs";
2231
2381
  import path17 from "path";
2232
- import * as p6 from "@clack/prompts";
2382
+ import * as p7 from "@clack/prompts";
2233
2383
 
2234
2384
  // core-engine/schema/schema-reader.ts
2235
2385
  import fs9 from "fs";
@@ -3555,12 +3705,12 @@ async function resolveTextEnvValue(options) {
3555
3705
  if (existingValue) {
3556
3706
  return existingValue;
3557
3707
  }
3558
- const result = await p6.text({
3708
+ const result = await p7.text({
3559
3709
  message: options.message,
3560
3710
  defaultValue: options.defaultValue,
3561
3711
  validate: options.validate
3562
3712
  });
3563
- if (p6.isCancel(result)) {
3713
+ if (p7.isCancel(result)) {
3564
3714
  throw new Error(options.cancelMessage);
3565
3715
  }
3566
3716
  options.overwriteEnvKeys.add(options.key);
@@ -3571,11 +3721,11 @@ async function resolvePasswordEnvValue(options) {
3571
3721
  if (existingValue) {
3572
3722
  return existingValue;
3573
3723
  }
3574
- const result = await p6.password({
3724
+ const result = await p7.password({
3575
3725
  message: options.message,
3576
3726
  validate: options.validate
3577
3727
  });
3578
- if (p6.isCancel(result)) {
3728
+ if (p7.isCancel(result)) {
3579
3729
  throw new Error(options.cancelMessage);
3580
3730
  }
3581
3731
  options.overwriteEnvKeys.add(options.key);
@@ -17924,7 +18074,7 @@ function readPresetTemplate(presetId, relativePath) {
17924
18074
  // adapters/next/snapshots/apply.ts
17925
18075
  import fs20 from "fs";
17926
18076
  import path24 from "path";
17927
- import * as p7 from "@clack/prompts";
18077
+ import * as p8 from "@clack/prompts";
17928
18078
 
17929
18079
  // core-engine/snapshots/ast-substitution.ts
17930
18080
  import { Node, Project } from "ts-morph";
@@ -18286,7 +18436,7 @@ function hasConflictMarkers(content) {
18286
18436
  }
18287
18437
 
18288
18438
  // adapters/next/snapshots/format-for-merge.ts
18289
- import { execFileSync as execFileSync3 } from "child_process";
18439
+ import { execFileSync as execFileSync2 } from "child_process";
18290
18440
  import fs18 from "fs";
18291
18441
  import path22 from "path";
18292
18442
  var BIOME_FORMAT_TIMEOUT_MS2 = 15e3;
@@ -18336,7 +18486,7 @@ function formatContentForSnapshotMerge(cwd, filePath, content) {
18336
18486
  const configPath = findBiomeConfig(cwd);
18337
18487
  const targetPath = path22.join(cwd, ...filePath.split("/"));
18338
18488
  try {
18339
- return execFileSync3(
18489
+ return execFileSync2(
18340
18490
  biomeBin,
18341
18491
  [
18342
18492
  "check",
@@ -18591,7 +18741,7 @@ function isInteractiveSession2(interactive) {
18591
18741
  return interactive && Boolean(process.stdin.isTTY) && process.env.CI !== "true";
18592
18742
  }
18593
18743
  async function promptNoBaseDecision(filePath) {
18594
- const answer = await p7.select({
18744
+ const answer = await p8.select({
18595
18745
  message: `File already exists without snapshot base: ${filePath}`,
18596
18746
  options: [
18597
18747
  { value: "backup", label: "Backup and overwrite", hint: "recommended" },
@@ -18600,7 +18750,7 @@ async function promptNoBaseDecision(filePath) {
18600
18750
  ],
18601
18751
  initialValue: "backup"
18602
18752
  });
18603
- if (p7.isCancel(answer)) {
18753
+ if (p8.isCancel(answer)) {
18604
18754
  throw new Error("Generation cancelled.");
18605
18755
  }
18606
18756
  return answer;
@@ -19481,18 +19631,18 @@ async function runAddCommand(items, options) {
19481
19631
  const presetIds = items.filter(isPresetId);
19482
19632
  const integrationIds = items.filter(isIntegrationId);
19483
19633
  if (!installIntegrationsMode && integrationIds.length > 0) {
19484
- p8.log.error(
19634
+ p9.log.error(
19485
19635
  `Integration IDs require --integration. Run \`betterstart add --integration ${integrationIds.join(" ")}\`.`
19486
19636
  );
19487
19637
  process.exit(1);
19488
19638
  }
19489
19639
  if (installIntegrationsMode && presetIds.length > 0) {
19490
- p8.log.error(`Preset IDs cannot be installed with --integration: ${presetIds.join(", ")}`);
19640
+ p9.log.error(`Preset IDs cannot be installed with --integration: ${presetIds.join(", ")}`);
19491
19641
  process.exit(1);
19492
19642
  }
19493
19643
  const invalidItems = installIntegrationsMode ? items.filter((integrationId) => !isIntegrationId(integrationId)) : items.filter((presetId) => !isPresetId(presetId));
19494
19644
  if (invalidItems.length > 0) {
19495
- p8.log.error(
19645
+ p9.log.error(
19496
19646
  installIntegrationsMode ? formatUnknownIntegrationMessage(invalidItems) : formatUnknownPresetMessage(invalidItems)
19497
19647
  );
19498
19648
  process.exit(1);
@@ -19503,7 +19653,7 @@ async function runAddCommand(items, options) {
19503
19653
  const pm = detectPackageManager(cwd);
19504
19654
  const cliSyncResult = await syncProjectCliDependency(cwd, pm);
19505
19655
  if (cliSyncResult && !cliSyncResult.success) {
19506
- p8.log.error(cliSyncResult.error ?? "Failed to sync betterstart-cli");
19656
+ p9.log.error(cliSyncResult.error ?? "Failed to sync betterstart-cli");
19507
19657
  process.exit(1);
19508
19658
  }
19509
19659
  if (installIntegrationsMode) {
@@ -19517,10 +19667,10 @@ async function runAddCommand(items, options) {
19517
19667
  });
19518
19668
  writeConfigFile(cwd, result2.config);
19519
19669
  if (result2.warnings.length > 0) {
19520
- p8.note(result2.warnings.join("\n"), "Warnings");
19670
+ p9.note(result2.warnings.join("\n"), "Warnings");
19521
19671
  }
19522
19672
  if (result2.installed.length === 0 && result2.activated.length === 0 && result2.skipped.length > 0) {
19523
- p8.outro(`No changes made. Already installed: ${result2.skipped.join(", ")}`);
19673
+ p9.outro(`No changes made. Already installed: ${result2.skipped.join(", ")}`);
19524
19674
  return;
19525
19675
  }
19526
19676
  const conflictPaths2 = scanConflictPaths(cwd);
@@ -19542,12 +19692,12 @@ async function runAddCommand(items, options) {
19542
19692
  result2.installed.length > 0 ? `Installed integration${result2.installed.length === 1 ? "" : "s"}: ${result2.installed.join(", ")}` : null,
19543
19693
  result2.activated.length > 0 ? `Activated integration${result2.activated.length === 1 ? "" : "s"}: ${result2.activated.join(", ")}` : null
19544
19694
  ].filter(Boolean);
19545
- p8.outro(messages.join("\n"));
19695
+ p9.outro(messages.join("\n"));
19546
19696
  return;
19547
19697
  }
19548
19698
  const invalidPresets = items.filter((presetId) => !isPresetId(presetId));
19549
19699
  if (invalidPresets.length > 0) {
19550
- p8.log.error(formatUnknownPresetMessage(invalidPresets));
19700
+ p9.log.error(formatUnknownPresetMessage(invalidPresets));
19551
19701
  process.exit(1);
19552
19702
  }
19553
19703
  const result = await installPresets({
@@ -19560,11 +19710,11 @@ async function runAddCommand(items, options) {
19560
19710
  });
19561
19711
  writeConfigFile(cwd, result.config);
19562
19712
  if (result.installed.length === 0 && result.skipped.length > 0) {
19563
- p8.outro(`No changes made. Already installed: ${result.skipped.join(", ")}`);
19713
+ p9.outro(`No changes made. Already installed: ${result.skipped.join(", ")}`);
19564
19714
  return;
19565
19715
  }
19566
19716
  if (result.warnings.length > 0) {
19567
- p8.note(result.warnings.join("\n"), "Warnings");
19717
+ p9.note(result.warnings.join("\n"), "Warnings");
19568
19718
  }
19569
19719
  const installedSchemaNames = result.installed.flatMap(
19570
19720
  (presetId) => getPresetDefinition(presetId).schemaFiles.map((schemaFile) => schemaFile.replace(/\.json$/, ""))
@@ -19581,7 +19731,7 @@ async function runAddCommand(items, options) {
19581
19731
  if (conflictPaths.length === 0) {
19582
19732
  printAddNextSteps("preset", hasSchemaChanges, postGenerateResult, adminRoutePath);
19583
19733
  }
19584
- p8.outro(
19734
+ p9.outro(
19585
19735
  `Installed preset${result.installed.length === 1 ? "" : "s"}: ${result.installed.join(", ")}`
19586
19736
  );
19587
19737
  }
@@ -20332,88 +20482,6 @@ function diffSchemas(before, after) {
20332
20482
  };
20333
20483
  }
20334
20484
 
20335
- // core-engine/utils/spinner.ts
20336
- import { stripVTControlCharacters } from "util";
20337
- import * as p9 from "@clack/prompts";
20338
- var RENDER_OVERHEAD = 7;
20339
- var MIN_MESSAGE_WIDTH = 8;
20340
- function fitSpinnerMessage(message) {
20341
- const normalized = message.replace(/\t/g, " ");
20342
- const columns = process.stdout.columns ?? 80;
20343
- const max = Math.max(columns - RENDER_OVERHEAD, MIN_MESSAGE_WIDTH);
20344
- const visible = stripVTControlCharacters(normalized);
20345
- if (visible.length <= max) return normalized;
20346
- return `${visible.slice(0, max - 1)}\u2026`;
20347
- }
20348
- var CLACK_LINE_ROWS = 2;
20349
- var LOG_PREFIX_WIDTH = 3;
20350
- function clackPromptRows(message, value) {
20351
- const columns = process.stdout.columns ?? 80;
20352
- const rows = (text7) => Math.max(1, Math.ceil((stripVTControlCharacters(text7).length + LOG_PREFIX_WIDTH) / columns));
20353
- return 1 + rows(message) + rows(value);
20354
- }
20355
- function clackLogRows(message) {
20356
- const columns = process.stdout.columns ?? 80;
20357
- const width = stripVTControlCharacters(message).length + LOG_PREFIX_WIDTH;
20358
- return 1 + Math.max(1, Math.ceil(width / columns));
20359
- }
20360
- function eraseRows(rows) {
20361
- if (rows <= 0 || !process.stdout.isTTY || process.env.CI === "true") return;
20362
- process.stdout.write(`\x1B[${rows}A\x1B[${rows}M`);
20363
- }
20364
- function eraseRowsAbove(rows, rowsBelow) {
20365
- if (rows <= 0 || rowsBelow < 0 || !process.stdout.isTTY || process.env.CI === "true") {
20366
- return;
20367
- }
20368
- const up = rows + rowsBelow;
20369
- const restore = rowsBelow > 0 ? `\x1B[${rowsBelow}B` : "";
20370
- process.stdout.write(`\x1B[${up}A\x1B[${rows}M${restore}`);
20371
- }
20372
- function eraseClackLine(options = {}) {
20373
- if (!process.stdout.isTTY || process.env.CI === "true") return;
20374
- const below = (options.linesBelow ?? 0) * CLACK_LINE_ROWS;
20375
- const up = below + CLACK_LINE_ROWS;
20376
- const restore = below > 0 ? `\x1B[${below}B` : "";
20377
- process.stdout.write(`\x1B[${up}A\x1B[${CLACK_LINE_ROWS}M${restore}`);
20378
- }
20379
- var activeSpinners = 0;
20380
- function hasActiveSpinner() {
20381
- return activeSpinners > 0;
20382
- }
20383
- function spinner2(options) {
20384
- const inner = p9.spinner({ ...options, cancelMessage: "Setup cancelled." });
20385
- const guided = options?.withGuide !== false;
20386
- let started = false;
20387
- const setStarted = (next) => {
20388
- if (started === next) return;
20389
- started = next;
20390
- activeSpinners += next ? 1 : -1;
20391
- };
20392
- return {
20393
- start: (message = "") => {
20394
- if (started) {
20395
- inner.message(fitSpinnerMessage(message));
20396
- return;
20397
- }
20398
- setStarted(true);
20399
- inner.start(fitSpinnerMessage(message));
20400
- },
20401
- message: (message = "") => inner.message(fitSpinnerMessage(message)),
20402
- stop: (message = "") => {
20403
- setStarted(false);
20404
- inner.stop(message);
20405
- },
20406
- clear: () => {
20407
- if (!started) return;
20408
- setStarted(false);
20409
- inner.clear();
20410
- if (guided && process.stdout.isTTY && process.env.CI !== "true") {
20411
- process.stdout.write("\x1B[1A\x1B[2K");
20412
- }
20413
- }
20414
- };
20415
- }
20416
-
20417
20485
  // adapters/next/commands/generate.ts
20418
20486
  function isInteractiveSession3() {
20419
20487
  return Boolean(process.stdin.isTTY) && process.env.CI !== "true";
@@ -22403,7 +22471,7 @@ ${validationErrors.map((error) => ` - ${error}`).join("\n")}`
22403
22471
  }
22404
22472
 
22405
22473
  // adapters/next/commands/init.ts
22406
- import { execFileSync as execFileSync5, spawn as spawn6 } from "child_process";
22474
+ import { execFileSync as execFileSync4, spawn as spawn6 } from "child_process";
22407
22475
  import fs41 from "fs";
22408
22476
  import path52 from "path";
22409
22477
  import { PassThrough } from "stream";
@@ -22468,7 +22536,7 @@ function redactSecrets(text7) {
22468
22536
  }
22469
22537
 
22470
22538
  // adapters/next/init/prompts/database.ts
22471
- import { execFileSync as execFileSync4 } from "child_process";
22539
+ import { execFileSync as execFileSync3 } from "child_process";
22472
22540
  import * as p15 from "@clack/prompts";
22473
22541
  import pc from "picocolors";
22474
22542
  var VERCEL_NEON_URL = "https://vercel.com/dashboard/integrations/checkout/neon";
@@ -22548,11 +22616,11 @@ function openBrowser(url) {
22548
22616
  try {
22549
22617
  const platform = process.platform;
22550
22618
  if (platform === "darwin") {
22551
- execFileSync4("open", [url], { stdio: "ignore" });
22619
+ execFileSync3("open", [url], { stdio: "ignore" });
22552
22620
  } else if (platform === "win32") {
22553
- execFileSync4("cmd", ["/c", "start", url], { stdio: "ignore" });
22621
+ execFileSync3("cmd", ["/c", "start", url], { stdio: "ignore" });
22554
22622
  } else {
22555
- execFileSync4("xdg-open", [url], { stdio: "ignore" });
22623
+ execFileSync3("xdg-open", [url], { stdio: "ignore" });
22556
22624
  }
22557
22625
  } catch {
22558
22626
  }
@@ -25036,8 +25104,16 @@ function scaffoldLayout({ cwd, config }) {
25036
25104
  readTemplate("pages/settings/forms/forms-settings-page-content.tsx")
25037
25105
  );
25038
25106
  write(
25039
- path43.join(settingsFormsDir, "edit-form-notifications-dialog.tsx"),
25040
- 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")
25041
25117
  );
25042
25118
  const settingsWebhooksDir = path43.join(settingsDir, "webhooks");
25043
25119
  write(
@@ -27214,10 +27290,10 @@ async function runSeedCommand(options) {
27214
27290
  }
27215
27291
  const namespace = resolveAdminNamespace(config.frameworkConfig.next.namespace);
27216
27292
  fs40.writeFileSync(seedPath, buildSeedScript(`${namespace.apiPath}/auth`), "utf-8");
27217
- const { execFile } = await import("child_process");
27293
+ const { execFile: execFile2 } = await import("child_process");
27218
27294
  const tsxBin = path51.join(cwd, "node_modules", ".bin", "tsx");
27219
27295
  const runSeed2 = (overwrite) => new Promise((resolve, reject) => {
27220
- execFile(
27296
+ execFile2(
27221
27297
  tsxBin,
27222
27298
  [seedPath],
27223
27299
  {
@@ -28192,9 +28268,9 @@ Run manually: ${pc10.cyan(betterstartExecCommand(pm, "seed"))}`,
28192
28268
  if (isFreshProject) {
28193
28269
  s.start("Creating initial git commit");
28194
28270
  try {
28195
- execFileSync5("git", ["init"], { cwd, stdio: "pipe" });
28196
- execFileSync5("git", ["add", "."], { cwd, stdio: "pipe" });
28197
- execFileSync5("git", ["commit", "-m", "Initial commit from BetterStart"], {
28271
+ execFileSync4("git", ["init"], { cwd, stdio: "pipe" });
28272
+ execFileSync4("git", ["add", "."], { cwd, stdio: "pipe" });
28273
+ execFileSync4("git", ["commit", "-m", "Initial commit from BetterStart"], {
28198
28274
  cwd,
28199
28275
  stdio: "pipe"
28200
28276
  });
@@ -28895,581 +28971,13 @@ async function runListPresetsCommand(options) {
28895
28971
  }
28896
28972
 
28897
28973
  // adapters/next/commands/menu-choices.ts
28898
- import path55 from "path";
28899
- async function listInstallableChoices(cwd) {
28900
- const config = await resolveConfigOrExit(cwd);
28901
- const installedPresets = new Set(config.presets.installed);
28902
- const installedIntegrations = new Set(config.integrations.installed);
28903
- return {
28904
- presets: listAvailablePresets().map((preset) => ({
28905
- id: preset.id,
28906
- description: preset.description,
28907
- installed: installedPresets.has(preset.id)
28908
- })),
28909
- integrations: listAvailableIntegrations().map((integration) => ({
28910
- id: integration.id,
28911
- description: integration.description,
28912
- installed: installedIntegrations.has(integration.id)
28913
- }))
28914
- };
28915
- }
28916
- async function listSchemaChoices(cwd) {
28917
- const config = await resolveConfigOrExit(cwd);
28918
- const paths = resolveProjectPaths(config);
28919
- return listSchemaNames(path55.join(cwd, ...paths.schemasDir.split("/")));
28920
- }
28921
-
28922
- // adapters/next/commands/remove.ts
28923
28974
  import path56 from "path";
28924
- import * as p29 from "@clack/prompts";
28925
- async function runRemoveCommand(items, options) {
28926
- const removeIntegrationsMode = Boolean(options.integration);
28927
- if (!removeIntegrationsMode && items.includes("core")) {
28928
- p29.log.error("The core Admin cannot be removed.");
28929
- process.exit(1);
28930
- }
28931
- const presetIds = items.filter(isPresetId);
28932
- const integrationIds = items.filter(isIntegrationId);
28933
- if (!removeIntegrationsMode && integrationIds.length > 0) {
28934
- p29.log.error(
28935
- `Integration IDs require --integration. Run \`betterstart remove --integration ${integrationIds.join(" ")}\`.`
28936
- );
28937
- process.exit(1);
28938
- }
28939
- if (removeIntegrationsMode && presetIds.length > 0) {
28940
- p29.log.error(`Preset IDs cannot be removed with --integration: ${presetIds.join(", ")}`);
28941
- process.exit(1);
28942
- }
28943
- const invalidItems = removeIntegrationsMode ? items.filter((integrationId) => !isIntegrationId(integrationId)) : items.filter((presetId) => !isPresetId(presetId));
28944
- if (invalidItems.length > 0) {
28945
- p29.log.error(
28946
- removeIntegrationsMode ? formatUnknownIntegrationMessage(invalidItems) : formatUnknownPresetMessage(invalidItems)
28947
- );
28948
- process.exit(1);
28949
- }
28950
- const cwd = options.cwd ? path56.resolve(options.cwd) : process.cwd();
28951
- const config = await resolveConfigOrExit(cwd);
28952
- const pm = detectPackageManager(cwd);
28953
- if (!options.force) {
28954
- const confirmed = await p29.confirm({
28955
- message: `Remove ${removeIntegrationsMode ? "integration" : "preset"}${items.length === 1 ? "" : "s"} ${items.join(", ")}?`,
28956
- initialValue: false
28957
- });
28958
- if (p29.isCancel(confirmed) || !confirmed) {
28959
- p29.cancel(`${removeIntegrationsMode ? "Integration" : "Preset"} removal cancelled.`);
28960
- process.exit(0);
28961
- }
28962
- }
28963
- if (removeIntegrationsMode) {
28964
- const result2 = await removeIntegrations({
28965
- cwd,
28966
- config,
28967
- pm,
28968
- integrationIds
28969
- });
28970
- writeConfigFile(cwd, result2.config);
28971
- if (result2.removed.length === 0) {
28972
- p29.outro("No integrations were removed.");
28973
- return;
28974
- }
28975
- if (result2.warnings.length > 0) {
28976
- p29.note(result2.warnings.join("\n"), "Warnings");
28977
- }
28978
- p29.outro(
28979
- `Removed integration${result2.removed.length === 1 ? "" : "s"}: ${result2.removed.join(", ")}`
28980
- );
28981
- return;
28982
- }
28983
- const result = await removePresets({
28984
- cwd,
28985
- config,
28986
- pm,
28987
- presetIds
28988
- });
28989
- writeConfigFile(cwd, result.config);
28990
- if (result.removed.length === 0) {
28991
- p29.outro("No presets were removed.");
28992
- return;
28993
- }
28994
- if (result.warnings.length > 0) {
28995
- p29.note(result.warnings.join("\n"), "Warnings");
28996
- }
28997
- p29.outro(`Removed preset${result.removed.length === 1 ? "" : "s"}: ${result.removed.join(", ")}`);
28998
- }
28999
28975
 
29000
- // adapters/next/commands/remove-schema.ts
28976
+ // adapters/next/commands/update-component.ts
28977
+ import { execFileSync as execFileSync5 } from "child_process";
29001
28978
  import fs42 from "fs";
29002
- import path57 from "path";
28979
+ import path55 from "path";
29003
28980
  import * as clack3 from "@clack/prompts";
29004
- function removePath2(cwd, filePath) {
29005
- const fullPath = path57.join(cwd, ...filePath.split("/"));
29006
- const existed = fs42.existsSync(fullPath);
29007
- fs42.rmSync(fullPath, { recursive: true, force: true });
29008
- return existed;
29009
- }
29010
- function cleanupEmptyDirs3(cwd, deletedPaths, configPaths) {
29011
- const stopRoots = /* @__PURE__ */ new Set([
29012
- path57.join(cwd, ...configPaths.adminDir.split("/")),
29013
- path57.join(cwd, ...configPaths.adminNavigationDir.split("/")),
29014
- path57.join(cwd, ...configPaths.adminWebhookEventsDir.split("/")),
29015
- path57.join(cwd, ...configPaths.pagesDir.split("/"))
29016
- ]);
29017
- for (const deletedPath of deletedPaths) {
29018
- let current = path57.dirname(path57.join(cwd, ...deletedPath.split("/")));
29019
- while (!stopRoots.has(current)) {
29020
- if (!fs42.existsSync(current)) {
29021
- current = path57.dirname(current);
29022
- continue;
29023
- }
29024
- const entries = fs42.readdirSync(current);
29025
- if (entries.length > 0) {
29026
- break;
29027
- }
29028
- fs42.rmdirSync(current);
29029
- current = path57.dirname(current);
29030
- }
29031
- }
29032
- }
29033
- function resolveSchemaOwnerForRemoval(cwd, schemaName) {
29034
- const explicitOwner = getSchemaOwner(cwd, schemaName);
29035
- if (explicitOwner) {
29036
- return explicitOwner;
29037
- }
29038
- if (schemaName === "settings") {
29039
- return "core";
29040
- }
29041
- return "user";
29042
- }
29043
- async function runRemoveSchemaCommand(schemaName, options) {
29044
- const owner = resolveSchemaOwnerForRemoval(
29045
- options.cwd ? path57.resolve(options.cwd) : process.cwd(),
29046
- schemaName
29047
- );
29048
- if (owner === "core") {
29049
- clack3.log.error(
29050
- `"${schemaName}" is a core Admin module and cannot be removed with remove-schema.`
29051
- );
29052
- process.exit(1);
29053
- }
29054
- if (owner.startsWith("preset:")) {
29055
- clack3.log.error(`"${schemaName}" is owned by ${owner}. Remove the owning preset instead.`);
29056
- process.exit(1);
29057
- }
29058
- const cwd = options.cwd ? path57.resolve(options.cwd) : process.cwd();
29059
- const config = await resolveConfigOrExit(cwd);
29060
- const paths = resolveProjectPaths(config);
29061
- const manifest = loadManifest(cwd, schemaName);
29062
- if (!snapshotRootExists(cwd)) {
29063
- clack3.log.error(UNSUPPORTED_PROJECT_STATE_MESSAGE);
29064
- process.exit(1);
29065
- }
29066
- if (!manifest) {
29067
- clack3.log.error(UNSUPPORTED_PROJECT_STATE_MESSAGE);
29068
- process.exit(1);
29069
- }
29070
- if (!options.force) {
29071
- if (!isInteractiveSession()) {
29072
- clack3.log.error(
29073
- `Removing generated files for ${schemaName} needs confirmation. Re-run with --force.`
29074
- );
29075
- process.exit(1);
29076
- }
29077
- const confirmed = await clack3.confirm({
29078
- message: `Remove generated files for ${schemaName}?`,
29079
- initialValue: false
29080
- });
29081
- if (clack3.isCancel(confirmed) || !confirmed) {
29082
- clack3.cancel("Cancelled.");
29083
- return;
29084
- }
29085
- }
29086
- const deletedPaths = [];
29087
- for (const file of [...manifest.files.map((entry) => entry.path), ...manifest.skipped]) {
29088
- if (removePath2(cwd, file)) {
29089
- deletedPaths.push(file);
29090
- }
29091
- }
29092
- const loaded = (() => {
29093
- try {
29094
- return loadSchema(path57.join(cwd, ...paths.schemasDir.split("/")), schemaName);
29095
- } catch {
29096
- return null;
29097
- }
29098
- })();
29099
- const kebabName = toKebabCase(schemaName);
29100
- if (loaded?.type === "form") {
29101
- if (removePath2(cwd, `${paths.adminActionsDir}/${schemaName}`)) {
29102
- deletedPaths.push(`${paths.adminActionsDir}/${schemaName}`);
29103
- }
29104
- if (removePath2(cwd, `${paths.pagesDir}/forms/${kebabName}`)) {
29105
- deletedPaths.push(`${paths.pagesDir}/forms/${kebabName}`);
29106
- }
29107
- } else {
29108
- if (removePath2(cwd, `${paths.adminActionsDir}/${schemaName}`)) {
29109
- deletedPaths.push(`${paths.adminActionsDir}/${schemaName}`);
29110
- }
29111
- if (removePath2(cwd, `${paths.pagesDir}/${schemaName}`)) {
29112
- deletedPaths.push(`${paths.pagesDir}/${schemaName}`);
29113
- }
29114
- }
29115
- cleanupEmptyDirs3(cwd, deletedPaths, paths);
29116
- deleteSnapshot(cwd, schemaName);
29117
- if (hasTombstone(cwd, schemaName)) {
29118
- clearTombstone(cwd, schemaName);
29119
- }
29120
- writeTombstone(cwd, schemaName);
29121
- await applyGeneratedFiles({
29122
- cwd,
29123
- config,
29124
- scope: BARREL_SCOPE,
29125
- schemaJson: { name: BARREL_SCOPE },
29126
- generatedFiles: renderBarrelFiles(cwd, config),
29127
- force: false,
29128
- interactive: false
29129
- });
29130
- clack3.log.info(
29131
- `Tombstone written: .betterstart/snapshots/_removed/${schemaName}
29132
- Schema JSON preserved.`
29133
- );
29134
- clack3.outro(`Removed generated files for ${schemaName}`);
29135
- }
29136
-
29137
- // adapters/next/commands/uninstall.ts
29138
- import fs44 from "fs";
29139
- import path58 from "path";
29140
- import * as p30 from "@clack/prompts";
29141
- import pc11 from "picocolors";
29142
-
29143
- // adapters/next/commands/uninstall-cleaners.ts
29144
- import fs43 from "fs";
29145
- function stripJsonComments2(input) {
29146
- let result = "";
29147
- let i = 0;
29148
- while (i < input.length) {
29149
- if (input[i] === '"') {
29150
- let j = i + 1;
29151
- while (j < input.length) {
29152
- if (input[j] === "\\") {
29153
- j += 2;
29154
- continue;
29155
- }
29156
- if (input[j] === '"') {
29157
- j++;
29158
- break;
29159
- }
29160
- j++;
29161
- }
29162
- result += input.slice(i, j);
29163
- i = j;
29164
- } else if (input[i] === "/" && input[i + 1] === "/") {
29165
- const nl = input.indexOf("\n", i);
29166
- i = nl === -1 ? input.length : nl;
29167
- } else if (input[i] === "/" && input[i + 1] === "*") {
29168
- const end = input.indexOf("*/", i + 2);
29169
- i = end === -1 ? input.length : end + 2;
29170
- } else {
29171
- result += input[i];
29172
- i++;
29173
- }
29174
- }
29175
- return result;
29176
- }
29177
- function cleanTsconfig(tsconfigPath, aliasRoot = "@admin") {
29178
- if (!fs43.existsSync(tsconfigPath)) return [];
29179
- const raw = fs43.readFileSync(tsconfigPath, "utf-8");
29180
- const stripped = stripJsonComments2(raw).replace(/,\s*([\]}])/g, "$1");
29181
- let tsconfig;
29182
- try {
29183
- tsconfig = JSON.parse(stripped);
29184
- } catch {
29185
- return [];
29186
- }
29187
- const compilerOptions = tsconfig.compilerOptions ?? {};
29188
- const paths = compilerOptions.paths ?? {};
29189
- const removed = [];
29190
- for (const key of Object.keys(paths)) {
29191
- if (key.startsWith("@admin/") || key === "@admin/*" || key.startsWith(`${aliasRoot}/`) || key === `${aliasRoot}/*`) {
29192
- removed.push(key);
29193
- delete paths[key];
29194
- }
29195
- }
29196
- if (removed.length === 0) return [];
29197
- if (Object.keys(paths).length === 0) {
29198
- compilerOptions.paths = void 0;
29199
- } else {
29200
- compilerOptions.paths = paths;
29201
- }
29202
- tsconfig.compilerOptions = compilerOptions;
29203
- fs43.writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
29204
- `, "utf-8");
29205
- return removed;
29206
- }
29207
- function cleanCss(cssPath, namespace = "admin") {
29208
- if (!fs43.existsSync(cssPath)) return [];
29209
- const content = fs43.readFileSync(cssPath, "utf-8");
29210
- const lines = content.split("\n");
29211
- const sourcePattern = new RegExp(`^@source\\s+"[^"]*(?:admin|${namespace})[^"]*";\\s*$`);
29212
- const removed = [];
29213
- const kept = [];
29214
- for (const line of lines) {
29215
- if (sourcePattern.test(line)) {
29216
- removed.push(line.trim());
29217
- } else {
29218
- kept.push(line);
29219
- }
29220
- }
29221
- if (removed.length === 0) return [];
29222
- const cleaned = kept.join("\n").replace(/\n{3,}/g, "\n\n");
29223
- fs43.writeFileSync(cssPath, cleaned, "utf-8");
29224
- return removed;
29225
- }
29226
- function cleanEnvFile(envPath) {
29227
- if (!fs43.existsSync(envPath)) return [];
29228
- const content = fs43.readFileSync(envPath, "utf-8");
29229
- const lines = content.split("\n");
29230
- const removed = [];
29231
- const kept = [];
29232
- const headerPattern = /^# =+$/;
29233
- const headerTextPattern = /^# BetterStart Admin$/;
29234
- for (let i = 0; i < lines.length; i++) {
29235
- const line = lines[i];
29236
- const trimmed = line.trim();
29237
- if (trimmed.match(/^BETTERSTART_\w+=/)) {
29238
- const key = trimmed.split("=")[0];
29239
- removed.push(key);
29240
- continue;
29241
- }
29242
- if (headerPattern.test(trimmed)) {
29243
- const next = lines[i + 1]?.trim();
29244
- const afterNext = lines[i + 2]?.trim();
29245
- if (next && headerTextPattern.test(next) && afterNext && headerPattern.test(afterNext)) {
29246
- i += 2;
29247
- continue;
29248
- }
29249
- }
29250
- if (trimmed.startsWith("#") && !headerPattern.test(trimmed)) {
29251
- const nextNonEmpty = findNextNonEmptyLine(lines, i + 1);
29252
- if (nextNonEmpty?.match(/^BETTERSTART_\w+=/)) {
29253
- continue;
29254
- }
29255
- }
29256
- kept.push(line);
29257
- }
29258
- if (removed.length === 0) return [];
29259
- const result = kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
29260
- if (result === "") {
29261
- fs43.unlinkSync(envPath);
29262
- } else {
29263
- fs43.writeFileSync(envPath, `${result}
29264
- `, "utf-8");
29265
- }
29266
- return removed;
29267
- }
29268
- function findNextNonEmptyLine(lines, startIndex) {
29269
- for (let i = startIndex; i < lines.length; i++) {
29270
- const trimmed = lines[i].trim();
29271
- if (trimmed !== "") return trimmed;
29272
- }
29273
- return null;
29274
- }
29275
-
29276
- // adapters/next/commands/uninstall.ts
29277
- function findMainCss2(cwd) {
29278
- const candidates = [
29279
- "src/app/globals.css",
29280
- "app/globals.css",
29281
- "src/app/global.css",
29282
- "app/global.css",
29283
- "src/app/app.css",
29284
- "app/app.css",
29285
- "src/globals.css",
29286
- "globals.css"
29287
- ];
29288
- for (const candidate of candidates) {
29289
- const filePath = path58.join(cwd, candidate);
29290
- if (fs44.existsSync(filePath)) return filePath;
29291
- }
29292
- return void 0;
29293
- }
29294
- function isCLICreatedBiome(biomePath) {
29295
- if (!fs44.existsSync(biomePath)) return false;
29296
- try {
29297
- const content = JSON.parse(fs44.readFileSync(biomePath, "utf-8"));
29298
- 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");
29299
- } catch {
29300
- return false;
29301
- }
29302
- }
29303
- function buildUninstallPlan(cwd, namespaceValue) {
29304
- const steps = [];
29305
- const namespace = resolveAdminNamespace(namespaceValue);
29306
- const hasSrc = fs44.existsSync(path58.join(cwd, "src"));
29307
- const appBase = hasSrc ? "src/app" : "app";
29308
- const dirs = [];
29309
- const adminDir = path58.join(cwd, namespace.segment);
29310
- const legacyAdminDir = path58.join(cwd, "admin");
29311
- const adminRouteGroup = path58.join(cwd, appBase, namespace.routeGroup);
29312
- const legacyAdminRouteGroup = path58.join(cwd, appBase, "(admin)");
29313
- if (fs44.existsSync(adminDir)) dirs.push(`${namespace.segment}/`);
29314
- if (namespace.segment !== "admin" && fs44.existsSync(legacyAdminDir)) dirs.push("admin/");
29315
- if (fs44.existsSync(adminRouteGroup)) dirs.push(`${appBase}/${namespace.routeGroup}/`);
29316
- if (namespace.segment !== "admin" && fs44.existsSync(legacyAdminRouteGroup))
29317
- dirs.push(`${appBase}/(admin)/`);
29318
- if (dirs.length > 0) {
29319
- steps.push({
29320
- label: "Admin directories",
29321
- items: dirs,
29322
- count: dirs.length,
29323
- unit: dirs.length === 1 ? "directory" : "directories",
29324
- execute() {
29325
- if (fs44.existsSync(adminDir)) fs44.rmSync(adminDir, { recursive: true, force: true });
29326
- if (fs44.existsSync(legacyAdminDir))
29327
- fs44.rmSync(legacyAdminDir, { recursive: true, force: true });
29328
- if (fs44.existsSync(adminRouteGroup)) {
29329
- fs44.rmSync(adminRouteGroup, { recursive: true, force: true });
29330
- }
29331
- if (fs44.existsSync(legacyAdminRouteGroup)) {
29332
- fs44.rmSync(legacyAdminRouteGroup, { recursive: true, force: true });
29333
- }
29334
- }
29335
- });
29336
- }
29337
- const configFiles = [];
29338
- const configPaths = [];
29339
- const candidates = [
29340
- [CONFIG_FILE_NAME, path58.join(cwd, CONFIG_FILE_NAME)],
29341
- ["drizzle.config.ts", path58.join(cwd, "drizzle.config.ts")],
29342
- ["ADMIN.md", path58.join(cwd, "ADMIN.md")]
29343
- ];
29344
- for (const [label, fullPath] of candidates) {
29345
- if (fs44.existsSync(fullPath)) {
29346
- configFiles.push(label);
29347
- configPaths.push(fullPath);
29348
- }
29349
- }
29350
- const biomePath = path58.join(cwd, "biome.json");
29351
- if (isCLICreatedBiome(biomePath)) {
29352
- configFiles.push("biome.json (CLI-created)");
29353
- configPaths.push(biomePath);
29354
- }
29355
- if (configFiles.length > 0) {
29356
- steps.push({
29357
- label: "Config files",
29358
- items: configFiles,
29359
- count: configFiles.length,
29360
- unit: configFiles.length === 1 ? "file" : "files",
29361
- execute() {
29362
- for (const p32 of configPaths) {
29363
- if (fs44.existsSync(p32)) fs44.unlinkSync(p32);
29364
- }
29365
- }
29366
- });
29367
- }
29368
- const tsconfigPath = path58.join(cwd, "tsconfig.json");
29369
- if (fs44.existsSync(tsconfigPath)) {
29370
- const content = fs44.readFileSync(tsconfigPath, "utf-8");
29371
- const aliasMatches = [
29372
- ...content.match(/"@admin\//g) ?? [],
29373
- ...content.match(new RegExp(`"${namespace.alias}/`, "g")) ?? []
29374
- ];
29375
- if (aliasMatches && aliasMatches.length > 0) {
29376
- const aliasCount = aliasMatches.length;
29377
- steps.push({
29378
- label: "tsconfig.json path aliases",
29379
- items: [`${namespace.alias}/* aliases in tsconfig.json`],
29380
- count: aliasCount,
29381
- unit: aliasCount === 1 ? "alias" : "aliases",
29382
- execute() {
29383
- cleanTsconfig(tsconfigPath, namespace.alias);
29384
- }
29385
- });
29386
- }
29387
- }
29388
- const cssFile = findMainCss2(cwd);
29389
- if (cssFile) {
29390
- const cssContent = fs44.readFileSync(cssFile, "utf-8");
29391
- const sourceLines = cssContent.split("\n").filter(
29392
- (l) => new RegExp(`^@source\\s+"[^"]*(?:admin|${namespace.segment})[^"]*";\\s*$`).test(l)
29393
- );
29394
- if (sourceLines.length > 0) {
29395
- const relCss = path58.relative(cwd, cssFile);
29396
- steps.push({
29397
- label: `CSS @source lines (${relCss})`,
29398
- items: [`@source lines in ${relCss}`],
29399
- count: sourceLines.length,
29400
- unit: sourceLines.length === 1 ? "line" : "lines",
29401
- execute() {
29402
- cleanCss(cssFile, namespace.segment);
29403
- }
29404
- });
29405
- }
29406
- }
29407
- const envPath = path58.join(cwd, ".env.local");
29408
- if (fs44.existsSync(envPath)) {
29409
- const envContent = fs44.readFileSync(envPath, "utf-8");
29410
- const bsVars = envContent.split("\n").filter((l) => l.trim().match(/^BETTERSTART_\w+=/)).map((l) => l.split("=")[0]);
29411
- if (bsVars.length > 0) {
29412
- steps.push({
29413
- label: ".env.local variables",
29414
- items: ["BETTERSTART_* vars in .env.local"],
29415
- count: bsVars.length,
29416
- unit: bsVars.length === 1 ? "variable" : "variables",
29417
- execute() {
29418
- cleanEnvFile(envPath);
29419
- }
29420
- });
29421
- }
29422
- }
29423
- return steps;
29424
- }
29425
- async function runUninstallCommand(options) {
29426
- const cwd = options.cwd ? path58.resolve(options.cwd) : process.cwd();
29427
- p30.intro(pc11.bgRed(pc11.white(" BetterStart Uninstall ")));
29428
- let namespace = DEFAULT_ADMIN_NAMESPACE;
29429
- try {
29430
- const config = await resolveConfig(cwd);
29431
- namespace = config.frameworkConfig.next.namespace;
29432
- } catch {
29433
- }
29434
- const steps = buildUninstallPlan(cwd, namespace);
29435
- if (steps.length === 0) {
29436
- p30.log.success(`${pc11.green("\u2713")} Nothing to remove \u2014 project is already clean.`);
29437
- p30.outro("Project already clean");
29438
- return;
29439
- }
29440
- const planLines = steps.map((step) => {
29441
- const names = step.items.join(" ");
29442
- const countLabel = pc11.dim(`${step.count} ${step.unit}`);
29443
- return `${pc11.red("\u2717")} ${names} ${countLabel}`;
29444
- });
29445
- p30.note(planLines.join("\n"), "Uninstall plan");
29446
- if (!options.force) {
29447
- const confirmed = await p30.confirm({
29448
- message: "Proceed with uninstall?",
29449
- initialValue: false
29450
- });
29451
- if (p30.isCancel(confirmed) || !confirmed) {
29452
- p30.cancel("Uninstall cancelled.");
29453
- process.exit(0);
29454
- }
29455
- }
29456
- const s = spinner2();
29457
- s.start(steps[0].label);
29458
- for (const step of steps) {
29459
- s.message(step.label);
29460
- step.execute();
29461
- }
29462
- const parts = steps.map((step) => `${step.count} ${step.unit}`);
29463
- s.stop(`Removed ${parts.join(", ")}`);
29464
- p30.note(pc11.dim("Database tables were NOT dropped \u2014 drop them manually if needed."), "Next steps");
29465
- p30.outro("Uninstall complete");
29466
- }
29467
-
29468
- // adapters/next/commands/update-component.ts
29469
- import { execFileSync as execFileSync6 } from "child_process";
29470
- import fs45 from "fs";
29471
- import path59 from "path";
29472
- import * as clack4 from "@clack/prompts";
29473
28981
  import fsExtra from "fs-extra";
29474
28982
  var STATIC_CUSTOM_DEPENDENCIES = {
29475
28983
  "content-editor": [
@@ -29719,24 +29227,24 @@ function applyNamespaceToTemplateEntry(entry, config, cwd) {
29719
29227
  };
29720
29228
  }
29721
29229
  function writeNamespacedFile(srcPath, destPath, namespace) {
29722
- fs45.writeFileSync(
29230
+ fs42.writeFileSync(
29723
29231
  destPath,
29724
- applyAdminNamespaceToContent(fs45.readFileSync(srcPath, "utf-8"), namespace),
29232
+ applyAdminNamespaceToContent(fs42.readFileSync(srcPath, "utf-8"), namespace),
29725
29233
  "utf-8"
29726
29234
  );
29727
29235
  }
29728
29236
  function copyNamespacedDirectory(srcDir, destDir, namespace) {
29729
- const entries = fs45.readdirSync(srcDir, { withFileTypes: true });
29237
+ const entries = fs42.readdirSync(srcDir, { withFileTypes: true });
29730
29238
  for (const entry of entries) {
29731
29239
  const namespacedName = applyAdminNamespaceToPath(entry.name, namespace);
29732
- const srcPath = path59.join(srcDir, entry.name);
29733
- const destPath = path59.join(destDir, namespacedName);
29240
+ const srcPath = path55.join(srcDir, entry.name);
29241
+ const destPath = path55.join(destDir, namespacedName);
29734
29242
  if (entry.isDirectory()) {
29735
29243
  fsExtra.ensureDirSync(destPath);
29736
29244
  copyNamespacedDirectory(srcPath, destPath, namespace);
29737
29245
  continue;
29738
29246
  }
29739
- fsExtra.ensureDirSync(path59.dirname(destPath));
29247
+ fsExtra.ensureDirSync(path55.dirname(destPath));
29740
29248
  writeNamespacedFile(srcPath, destPath, namespace);
29741
29249
  }
29742
29250
  }
@@ -29747,10 +29255,10 @@ function hasIntegration(config, integrationId) {
29747
29255
  return config.integrations.installed.includes(integrationId);
29748
29256
  }
29749
29257
  function readProjectPackageJson2(cwd) {
29750
- const pkgPath = path59.join(cwd, "package.json");
29751
- if (!fs45.existsSync(pkgPath)) return null;
29258
+ const pkgPath = path55.join(cwd, "package.json");
29259
+ if (!fs42.existsSync(pkgPath)) return null;
29752
29260
  try {
29753
- return JSON.parse(fs45.readFileSync(pkgPath, "utf-8"));
29261
+ return JSON.parse(fs42.readFileSync(pkgPath, "utf-8"));
29754
29262
  } catch {
29755
29263
  return null;
29756
29264
  }
@@ -30634,13 +30142,38 @@ var TEMPLATE_REGISTRY = {
30634
30142
  relPath: "app/(admin)/admin/(authenticated)/settings/forms/forms-settings-page-content.tsx",
30635
30143
  content: () => readTemplate("pages/settings/forms/forms-settings-page-content.tsx"),
30636
30144
  base: "cwd",
30637
- 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"]
30638
30163
  },
30639
- "edit-form-notifications-dialog": {
30640
- relPath: "app/(admin)/admin/(authenticated)/settings/forms/edit-form-notifications-dialog.tsx",
30641
- 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"),
30642
30167
  base: "cwd",
30643
- dependencies: ["form-settings-action"]
30168
+ dependencies: [
30169
+ "button",
30170
+ "card",
30171
+ "drawer",
30172
+ "form",
30173
+ "form-settings-action",
30174
+ "scroll-area",
30175
+ "textarea"
30176
+ ]
30644
30177
  },
30645
30178
  "webhooks-page": {
30646
30179
  relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/page.tsx",
@@ -31269,9 +30802,9 @@ function getStaticAssetComponents(assetDirectory) {
31269
30802
  }
31270
30803
  function getStaticAssetComponentEntries(assetDirectory) {
31271
30804
  const assetDir = resolveCliAssetPath("shared-assets", "react-admin", assetDirectory);
31272
- if (!fs45.existsSync(assetDir)) return [];
30805
+ if (!fs42.existsSync(assetDir)) return [];
31273
30806
  const components = [];
31274
- for (const entry of fs45.readdirSync(assetDir, { withFileTypes: true })) {
30807
+ for (const entry of fs42.readdirSync(assetDir, { withFileTypes: true })) {
31275
30808
  if (entry.isFile() && (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts"))) {
31276
30809
  components.push({
31277
30810
  name: entry.name.replace(/\.(tsx|ts)$/, ""),
@@ -31279,556 +30812,1130 @@ function getStaticAssetComponentEntries(assetDirectory) {
31279
30812
  });
31280
30813
  continue;
31281
30814
  }
31282
- if (!entry.isDirectory()) {
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;
30855
+ }
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();
30861
+ }
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();
30869
+ }
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))
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);
31134
+ continue;
31135
+ }
31136
+ if (writeStaticAssetEntry("ui", name)) {
31283
31137
  continue;
31284
31138
  }
31285
- const indexFile = ["index.tsx", "index.ts"].find(
31286
- (file) => fs45.existsSync(path59.join(assetDir, entry.name, file))
31287
- );
31288
- if (indexFile) {
31289
- components.push({
31290
- name: entry.name,
31291
- file: path59.join(entry.name, indexFile)
31292
- });
31139
+ if (writeStaticAssetEntry("custom", name)) {
31140
+ continue;
31293
31141
  }
31294
- }
31295
- return components.sort((a, b) => a.name.localeCompare(b.name));
31296
- }
31297
- function findStaticAssetFile(assetDir, componentName) {
31298
- if (!fs45.existsSync(assetDir)) return void 0;
31299
- const isNestedComponentName = /[\\/]/.test(componentName);
31300
- const nestedComponentName = componentName.split(/[\\/]/).filter(Boolean).join(path59.sep);
31301
- if (isNestedComponentName && nestedComponentName && !path59.isAbsolute(componentName) && !nestedComponentName.split(path59.sep).includes("..")) {
31302
- for (const extension of [".tsx", ".ts"]) {
31303
- const relPath = `${nestedComponentName}${extension}`;
31304
- const filePath = path59.join(assetDir, relPath);
31305
- if (fs45.existsSync(filePath) && fs45.statSync(filePath).isFile()) {
31306
- return relPath;
31142
+ if (name === "tiptap") {
31143
+ if (!writeTiptapTemplates()) {
31144
+ clack3.log.warn("tiptap templates not found");
31145
+ skipped++;
31307
31146
  }
31147
+ continue;
31308
31148
  }
31149
+ clack3.log.warn(`Unknown component: ${name}`);
31150
+ skipped++;
31309
31151
  }
31310
- if (!isNestedComponentName && !componentName.includes("..") && !path59.isAbsolute(componentName)) {
31311
- for (const extension of [".tsx", ".ts"]) {
31312
- const relPath = path59.join(componentName, `index${extension}`);
31313
- const filePath = path59.join(assetDir, relPath);
31314
- if (fs45.existsSync(filePath) && fs45.statSync(filePath).isFile()) {
31315
- 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);
31316
31176
  }
31317
31177
  }
31178
+ for (const entry of pendingWrites) {
31179
+ entry.write();
31180
+ }
31318
31181
  }
31319
- return fs45.readdirSync(assetDir, { withFileTypes: true }).find(
31320
- (entry) => entry.isFile() && (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts")) && entry.name.replace(/\.(tsx|ts)$/, "") === componentName
31321
- )?.name;
31322
- }
31323
- function getAllComponentNames() {
31324
- const staticUi = getStaticUiComponents();
31325
- const staticCustom = getStaticCustomComponents();
31326
- const templateKeys = Object.keys(TEMPLATE_REGISTRY);
31327
- 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
+ );
31328
31196
  }
31329
- function getAllComponentNamesForConfig(config) {
31330
- const staticUi = getStaticUiComponents();
31331
- const staticCustom = getStaticCustomComponents();
31332
- const templateKeys = Object.entries(TEMPLATE_REGISTRY).filter(
31333
- ([, entry]) => (!entry.requiredPreset || hasPreset(config, entry.requiredPreset)) && (!entry.requiredIntegration || hasIntegration(config, entry.requiredIntegration))
31334
- ).map(([name]) => name);
31335
- 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
+ }
31336
31201
  }
31337
- async function runUpdateCommand(components, options) {
31338
- const cwd = options.cwd ? path59.resolve(options.cwd) : process.cwd();
31339
- const normalizedOnly = normalizeShadcnPresetOnly(options.only);
31340
- validateShadcnPresetOptions(components, options);
31341
- if (options.json && !options.list) {
31342
- 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.");
31343
31205
  process.exit(1);
31344
31206
  }
31207
+ if (!options.shadcnPreset) {
31208
+ return;
31209
+ }
31345
31210
  if (options.list) {
31346
- const uiComponents = getStaticUiComponentEntries();
31347
- const customComponents = getStaticCustomComponentEntries();
31348
- const templateKeys = Object.keys(TEMPLATE_REGISTRY).sort();
31349
- const templatePath = (name) => {
31350
- const entry = TEMPLATE_REGISTRY[name];
31351
- return entry.displayPath ?? (typeof entry.relPath === "string" ? entry.relPath : "");
31352
- };
31353
- if (options.json) {
31354
- const items = [
31355
- ...uiComponents.map((component) => ({
31356
- name: component.name,
31357
- path: `components/ui/${component.file}`,
31358
- kind: "ui"
31359
- })),
31360
- ...customComponents.map((component) => ({
31361
- name: component.name,
31362
- path: `components/custom/${component.file}`,
31363
- kind: "custom"
31364
- })),
31365
- ...templateKeys.map((name) => ({ name, path: templatePath(name), kind: "template" })),
31366
- { name: "tiptap", path: "components/custom/content-editor/tiptap-*/", kind: "special" }
31367
- ];
31368
- console.log(JSON.stringify(items, null, 2));
31369
- return;
31370
- }
31371
- const all = getAllComponentNames();
31372
- clack4.intro("Available components");
31373
- clack4.note(
31374
- renderTableRows(
31375
- uiComponents.map((component) => [component.name, `components/ui/${component.file}`])
31376
- ).join("\n"),
31377
- `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."
31378
31231
  );
31379
- clack4.note(
31380
- renderTableRows(
31381
- customComponents.map((component) => [component.name, `components/custom/${component.file}`])
31382
- ).join("\n"),
31383
- `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.`
31384
31259
  );
31385
- clack4.note(
31386
- renderTableRows(templateKeys.map((name) => [name, templatePath(name)])).join("\n"),
31387
- `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"
31388
31276
  );
31389
- clack4.note(
31390
- renderTableRows([["tiptap", "components/custom/content-editor/tiptap-*/ (all files)"]]).join(
31391
- "\n"
31392
- ),
31393
- "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.`
31394
31331
  );
31395
- 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");
31396
31360
  return;
31397
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) {
31398
31369
  const config = await resolveConfigOrExit(cwd);
31399
- const admin = path59.resolve(cwd, config.paths.admin);
31400
- if (!fs45.existsSync(admin)) {
31401
- clack4.cancel(
31402
- `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(" ")}\`.`
31403
31409
  );
31404
31410
  process.exit(1);
31405
31411
  }
31406
- if (options.shadcnPreset) {
31407
- runShadcnPresetUpdate({
31408
- cwd,
31409
- config,
31410
- preset: options.shadcnPreset.trim(),
31411
- only: normalizedOnly
31412
- });
31413
- return;
31412
+ if (removeIntegrationsMode && presetIds.length > 0) {
31413
+ p29.log.error(`Preset IDs cannot be removed with --integration: ${presetIds.join(", ")}`);
31414
+ process.exit(1);
31414
31415
  }
31415
- if (!options.all && components.length === 0) {
31416
- clack4.log.error(
31417
- "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)
31418
31420
  );
31419
31421
  process.exit(1);
31420
31422
  }
31421
- clack4.intro("BetterStart Update Components");
31422
- const toUpdate = options.all ? getAllComponentNamesForConfig(config) : components;
31423
- const uiDir = resolveCliAssetPath("shared-assets", "react-admin", "ui");
31424
- const customDir = resolveCliAssetPath("shared-assets", "react-admin", "custom");
31425
- let updated = 0;
31426
- let skipped = 0;
31427
- const updatedTemplateNames = /* @__PURE__ */ new Set();
31428
- const updatedStaticNames = /* @__PURE__ */ new Set();
31429
- const requiredPackageDependencies = /* @__PURE__ */ new Set();
31430
- const pendingWrites = [];
31431
- function trackPackageDependencies(name) {
31432
- for (const dependency of COMPONENT_PACKAGE_DEPENDENCIES[name] ?? []) {
31433
- requiredPackageDependencies.add(dependency);
31434
- }
31435
- }
31436
- function writeTemplateEntry(name, entry) {
31437
- if (updatedTemplateNames.has(name)) {
31438
- return false;
31439
- }
31440
- if (entry.requiredPreset && !hasPreset(config, entry.requiredPreset)) {
31441
- clack4.log.warn(`${name} requires the ${entry.requiredPreset} preset and was skipped.`);
31442
- skipped++;
31443
- return false;
31444
- }
31445
- if (entry.requiredIntegration && !hasIntegration(config, entry.requiredIntegration)) {
31446
- clack4.log.warn(
31447
- `${name} requires the ${entry.requiredIntegration} integration and was skipped.`
31448
- );
31449
- skipped++;
31450
- return false;
31451
- }
31452
- const { relPath, content } = applyNamespaceToTemplateEntry(entry, config, cwd);
31453
- const baseDir = entry.base === "cwd" ? cwd : admin;
31454
- const destPath = path59.join(baseDir, relPath);
31455
- if (entry.preserveExisting && fs45.existsSync(destPath)) {
31456
- clack4.log.info(`Preserved ${relPath}`);
31457
- updatedTemplateNames.add(name);
31458
- skipped++;
31459
- return false;
31460
- }
31461
- pendingWrites.push({
31462
- displayPath: relPath,
31463
- write: () => {
31464
- fsExtra.ensureDirSync(path59.dirname(destPath));
31465
- fs45.writeFileSync(destPath, content, "utf-8");
31466
- clack4.log.success(`Updated ${relPath}`);
31467
- }
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
31468
31430
  });
31469
- updatedTemplateNames.add(name);
31470
- trackPackageDependencies(name);
31471
- updated++;
31472
- return true;
31473
- }
31474
- function writeTemplateEntryWithDependencies(name, entry) {
31475
- const wroteEntry = writeTemplateEntry(name, entry);
31476
- if (!wroteEntry) {
31477
- return false;
31478
- }
31479
- for (const dependencyName of entry.dependencies ?? []) {
31480
- writeNamedDependency(dependencyName);
31431
+ if (p29.isCancel(confirmed) || !confirmed) {
31432
+ p29.cancel(`${removeIntegrationsMode ? "Integration" : "Preset"} removal cancelled.`);
31433
+ process.exit(0);
31481
31434
  }
31482
- return true;
31483
31435
  }
31484
- function writeStaticAssetEntry(assetDirectory, name) {
31485
- const key = `${assetDirectory}:${name}`;
31486
- if (updatedStaticNames.has(key)) {
31487
- return false;
31488
- }
31489
- const assetDir = assetDirectory === "ui" ? uiDir : customDir;
31490
- const assetFile = findStaticAssetFile(assetDir, name);
31491
- if (!assetFile) {
31492
- return false;
31493
- }
31494
- const namespace = config.frameworkConfig.next.namespace;
31495
- const namespacedAssetFile = applyAdminNamespaceToPath(assetFile, namespace);
31496
- const destPath = path59.join(admin, "components", assetDirectory, namespacedAssetFile);
31497
- pendingWrites.push({
31498
- displayPath: `components/${assetDirectory}/${namespacedAssetFile}`,
31499
- write: () => {
31500
- fsExtra.ensureDirSync(path59.dirname(destPath));
31501
- writeNamespacedFile(path59.join(assetDir, assetFile), destPath, namespace);
31502
- clack4.log.success(`Updated components/${assetDirectory}/${namespacedAssetFile}`);
31503
- }
31436
+ if (removeIntegrationsMode) {
31437
+ const result2 = await removeIntegrations({
31438
+ cwd,
31439
+ config,
31440
+ pm,
31441
+ integrationIds
31504
31442
  });
31505
- if (assetDirectory === "custom") {
31506
- const assetSubdir = path59.join(assetDir, name);
31507
- if (fs45.existsSync(assetSubdir) && fs45.statSync(assetSubdir).isDirectory()) {
31508
- const namespacedName = applyAdminNamespaceToPath(name, namespace);
31509
- const destSubdir = path59.join(admin, "components", assetDirectory, namespacedName);
31510
- pendingWrites.push({
31511
- displayPath: `components/${assetDirectory}/${namespacedName}/ (template files)`,
31512
- write: () => {
31513
- fsExtra.ensureDirSync(destSubdir);
31514
- copyNamespacedDirectory(assetSubdir, destSubdir, namespace);
31515
- clack4.log.success(
31516
- `Updated components/${assetDirectory}/${namespacedName}/ (template files)`
31517
- );
31518
- }
31519
- });
31520
- }
31521
- }
31522
- updatedStaticNames.add(key);
31523
- trackPackageDependencies(name);
31524
- updated++;
31525
- if (assetDirectory === "custom") {
31526
- for (const dependencyName of STATIC_CUSTOM_DEPENDENCIES[name] ?? []) {
31527
- writeNamedDependency(dependencyName);
31528
- }
31443
+ writeConfigFile(cwd, result2.config);
31444
+ if (result2.removed.length === 0) {
31445
+ p29.outro("No integrations were removed.");
31446
+ return;
31529
31447
  }
31530
- return true;
31531
- }
31532
- function writeTiptapTemplates() {
31533
- const key = "custom:tiptap";
31534
- if (updatedStaticNames.has(key)) {
31535
- return true;
31448
+ if (result2.warnings.length > 0) {
31449
+ p29.note(result2.warnings.join("\n"), "Warnings");
31536
31450
  }
31537
- const srcBaseDir = resolveCliAssetPath(
31538
- "shared-assets",
31539
- "react-admin",
31540
- "custom",
31541
- "content-editor"
31451
+ p29.outro(
31452
+ `Removed integration${result2.removed.length === 1 ? "" : "s"}: ${result2.removed.join(", ")}`
31542
31453
  );
31543
- const namespace = config.frameworkConfig.next.namespace;
31544
- const destBaseDir = path59.join(admin, "components", "custom", "content-editor");
31545
- if (!fs45.existsSync(srcBaseDir)) {
31546
- return false;
31547
- }
31548
- const dirsToCopy = [];
31549
- for (const directory of TIPTAP_CONTENT_EDITOR_DIRECTORIES) {
31550
- const srcDir = path59.join(srcBaseDir, directory);
31551
- 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);
31552
31495
  continue;
31553
31496
  }
31554
- dirsToCopy.push({
31555
- srcDir,
31556
- destDir: path59.join(destBaseDir, applyAdminNamespaceToPath(directory, namespace))
31557
- });
31558
- }
31559
- if (dirsToCopy.length === 0) {
31560
- return false;
31561
- }
31562
- pendingWrites.push({
31563
- displayPath: "components/custom/content-editor/tiptap-*/ (template files)",
31564
- write: () => {
31565
- for (const { srcDir, destDir } of dirsToCopy) {
31566
- fsExtra.ensureDirSync(destDir);
31567
- copyNamespacedDirectory(srcDir, destDir, namespace);
31568
- }
31569
- clack4.log.success("Updated components/custom/content-editor/tiptap-*/ (template files)");
31570
- removeLegacyDirectory(path59.join(admin, "components", "custom", "tiptap"));
31571
- }
31572
- });
31573
- updatedStaticNames.add(key);
31574
- trackPackageDependencies("tiptap");
31575
- updated++;
31576
- for (const dependencyName of TIPTAP_TEMPLATE_DEPENDENCIES) {
31577
- writeNamedDependency(dependencyName);
31578
- }
31579
- return true;
31580
- }
31581
- function writeNamedDependency(name) {
31582
- const entry = TEMPLATE_REGISTRY[name];
31583
- if (entry) {
31584
- writeTemplateEntryWithDependencies(name, entry);
31585
- return;
31497
+ const entries = fs43.readdirSync(current);
31498
+ if (entries.length > 0) {
31499
+ break;
31500
+ }
31501
+ fs43.rmdirSync(current);
31502
+ current = path58.dirname(current);
31586
31503
  }
31587
- if (writeStaticAssetEntry("ui", name)) {
31588
- 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);
31589
31549
  }
31590
- 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.");
31591
31556
  return;
31592
31557
  }
31593
- if (name === "tiptap") {
31594
- writeTiptapTemplates();
31595
- }
31596
31558
  }
31597
- for (const name of toUpdate) {
31598
- if (TEMPLATE_REGISTRY[name]) {
31599
- const entry = TEMPLATE_REGISTRY[name];
31600
- writeTemplateEntryWithDependencies(name, entry);
31601
- continue;
31602
- }
31603
- if (writeStaticAssetEntry("ui", name)) {
31604
- 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);
31605
31563
  }
31606
- if (writeStaticAssetEntry("custom", name)) {
31607
- continue;
31564
+ }
31565
+ const loaded = (() => {
31566
+ try {
31567
+ return loadSchema(path58.join(cwd, ...paths.schemasDir.split("/")), schemaName);
31568
+ } catch {
31569
+ return null;
31608
31570
  }
31609
- if (name === "tiptap") {
31610
- if (!writeTiptapTemplates()) {
31611
- clack4.log.warn("tiptap templates not found");
31612
- skipped++;
31613
- }
31614
- 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}`);
31615
31576
  }
31616
- clack4.log.warn(`Unknown component: ${name}`);
31617
- skipped++;
31618
- }
31619
- if (pendingWrites.length > 0) {
31620
- const displayPaths = pendingWrites.map((entry) => entry.displayPath);
31621
- const preview = displayPaths.slice(0, MAX_OVERWRITE_PREVIEW_ROWS);
31622
- if (displayPaths.length > preview.length) {
31623
- preview.push(`\u2026 and ${displayPaths.length - preview.length} more`);
31577
+ if (removePath2(cwd, `${paths.pagesDir}/forms/${kebabName}`)) {
31578
+ deletedPaths.push(`${paths.pagesDir}/forms/${kebabName}`);
31624
31579
  }
31625
- clack4.note(
31626
- preview.join("\n"),
31627
- `Overwriting ${displayPaths.length} file${displayPaths.length === 1 ? "" : "s"}`
31628
- );
31629
- if (!options.yes) {
31630
- if (!isInteractiveSession()) {
31631
- clack4.log.error(
31632
- `Overwriting ${displayPaths.length} file${displayPaths.length === 1 ? "" : "s"} needs confirmation. Re-run with --yes.`
31633
- );
31634
- process.exit(1);
31635
- }
31636
- const proceed = await clack4.confirm({
31637
- message: "Overwrite these files with the latest templates?",
31638
- initialValue: true
31639
- });
31640
- if (clack4.isCancel(proceed) || !proceed) {
31641
- clack4.cancel("Update cancelled.");
31642
- process.exit(0);
31643
- }
31580
+ } else {
31581
+ if (removePath2(cwd, `${paths.adminActionsDir}/${schemaName}`)) {
31582
+ deletedPaths.push(`${paths.adminActionsDir}/${schemaName}`);
31644
31583
  }
31645
- for (const entry of pendingWrites) {
31646
- entry.write();
31584
+ if (removePath2(cwd, `${paths.pagesDir}/${schemaName}`)) {
31585
+ deletedPaths.push(`${paths.pagesDir}/${schemaName}`);
31647
31586
  }
31648
31587
  }
31649
- syncInstalledPresetManifests(cwd, config);
31650
- syncInstalledIntegrationManifests(cwd, config);
31651
- const projectPackageJson = readProjectPackageJson2(cwd);
31652
- const missingPackageDependencies = Array.from(requiredPackageDependencies).filter(
31653
- (dependency) => !hasDeclaredPackage2(projectPackageJson, dependency)
31654
- );
31655
- if (missingPackageDependencies.length > 0) {
31656
- clack4.log.warn(
31657
- `Updated components require package dependencies that are not declared: ${missingPackageDependencies.join(", ")}. Run 'pnpm betterstart update-deps' before building.`
31658
- );
31588
+ cleanupEmptyDirs3(cwd, deletedPaths, paths);
31589
+ deleteSnapshot(cwd, schemaName);
31590
+ if (hasTombstone(cwd, schemaName)) {
31591
+ clearTombstone(cwd, schemaName);
31659
31592
  }
31660
- clack4.outro(
31661
- `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.`
31662
31606
  );
31607
+ clack4.outro(`Removed generated files for ${schemaName}`);
31663
31608
  }
31664
- function removeLegacyDirectory(dirPath) {
31665
- if (fs45.existsSync(dirPath)) {
31666
- 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
+ }
31667
31647
  }
31648
+ return result;
31668
31649
  }
31669
- function validateShadcnPresetOptions(components, options) {
31670
- if (options.only && !options.shadcnPreset) {
31671
- clack4.cancel("--only can only be used with --shadcn-preset.");
31672
- 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 [];
31673
31659
  }
31674
- if (!options.shadcnPreset) {
31675
- 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
+ }
31676
31668
  }
31677
- if (options.list) {
31678
- clack4.cancel("--list cannot be combined with --shadcn-preset.");
31679
- 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;
31680
31674
  }
31681
- if (options.all) {
31682
- clack4.cancel("--all cannot be combined with --shadcn-preset.");
31683
- 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
+ }
31684
31693
  }
31685
- if (components.length > 0) {
31686
- clack4.cancel("Component names cannot be combined with --shadcn-preset.");
31687
- 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);
31688
31730
  }
31689
- }
31690
- function normalizeShadcnPresetOnly(value) {
31691
- if (!value) {
31692
- return void 0;
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");
31693
31738
  }
31694
- const parts = value.split(",").map((part) => part.trim().toLowerCase()).filter(Boolean);
31695
- if (parts.length !== 1 || parts[0] !== "theme") {
31696
- clack4.cancel(
31697
- "Admin-only shadcn preset updates currently support --only theme. Omit --only to apply the full preset."
31698
- );
31699
- process.exit(1);
31739
+ return removed;
31740
+ }
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;
31700
31745
  }
31701
- return "theme";
31746
+ return null;
31702
31747
  }
31703
- function runShadcnPresetUpdate({
31704
- cwd,
31705
- config,
31706
- preset,
31707
- only
31708
- }) {
31709
- const namespace = resolveAdminNamespace(config.frameworkConfig.next.namespace);
31710
- const adminGlobalsPath = path59.join(cwd, config.paths.admin, namespace.globalsFile);
31711
- const componentsJsonPath = path59.join(cwd, "components.json");
31712
- const shadcnBackupPath = `${componentsJsonPath}.bak`;
31713
- const restoreAfterApplyPaths = [
31714
- componentsJsonPath,
31715
- shadcnBackupPath,
31716
- path59.join(cwd, config.paths.admin, "lib", "utils.ts"),
31717
- ...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"
31718
31760
  ];
31719
- if (!preset) {
31720
- clack4.cancel("--shadcn-preset requires a preset code, preset name, or preset URL.");
31721
- process.exit(1);
31722
- }
31723
- if (!fs45.existsSync(adminGlobalsPath)) {
31724
- clack4.cancel(
31725
- `Admin globals file not found at ${path59.relative(cwd, adminGlobalsPath)}. Run 'betterstart update admin-globals' first.`
31726
- );
31727
- process.exit(1);
31761
+ for (const candidate of candidates) {
31762
+ const filePath = path59.join(cwd, candidate);
31763
+ if (fs45.existsSync(filePath)) return filePath;
31728
31764
  }
31729
- const shadcnBin = resolveLocalShadcnBin(cwd);
31730
- const restoreSnapshots = restoreAfterApplyPaths.map((filePath) => ({
31731
- filePath,
31732
- snapshot: snapshotFile(filePath)
31733
- }));
31734
- clack4.intro("BetterStart Shadcn Preset");
31735
- clack4.log.info(`Applying preset to ${path59.join(config.paths.admin, "components/ui")}`);
31736
- let failed = false;
31765
+ return void 0;
31766
+ }
31767
+ function isCLICreatedBiome(biomePath) {
31768
+ if (!fs45.existsSync(biomePath)) return false;
31737
31769
  try {
31738
- fs45.writeFileSync(
31739
- componentsJsonPath,
31740
- `${JSON.stringify(createAdminShadcnComponentsJson(config), null, 2)}
31741
- `,
31742
- "utf-8"
31743
- );
31744
- const args = ["apply", "--preset", preset, "--cwd", cwd, "--yes"];
31745
- if (only) {
31746
- args.push("--only", only);
31747
- }
31748
- execFileSync6(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");
31749
31772
  } catch {
31750
- failed = true;
31751
- } finally {
31752
- for (const { filePath, snapshot } of restoreSnapshots.reverse()) {
31753
- 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);
31754
31821
  }
31755
31822
  }
31756
- if (failed) {
31757
- clack4.cancel("shadcn preset application failed.");
31758
- 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);
31759
31827
  }
31760
- clack4.outro(
31761
- only ? `Applied shadcn preset parts (${only}) to the Admin.` : "Applied shadcn preset to Admin UI components and styles."
31762
- );
31763
- }
31764
- function createAdminShadcnComponentsJson(config) {
31765
- const adminPath = toPosixPath(config.paths.admin);
31766
- const namespace = resolveAdminNamespace(config.frameworkConfig.next.namespace);
31767
- return {
31768
- $schema: "https://ui.shadcn.com/schema.json",
31769
- style: "new-york",
31770
- rsc: true,
31771
- tsx: true,
31772
- tailwind: {
31773
- config: "",
31774
- css: `${adminPath}/${namespace.globalsFile}`,
31775
- baseColor: "neutral",
31776
- cssVariables: true,
31777
- prefix: ""
31778
- },
31779
- iconLibrary: "lucide",
31780
- aliases: {
31781
- components: `${namespace.alias}/components`,
31782
- ui: `${namespace.alias}/components/ui`,
31783
- hooks: `${namespace.alias}/hooks`,
31784
- lib: `${namespace.alias}/lib`,
31785
- 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
+ });
31786
31859
  }
31787
- };
31788
- }
31789
- function toPosixPath(value) {
31790
- return value.replace(/\\/g, "/");
31791
- }
31792
- function resolveLocalShadcnBin(cwd) {
31793
- const binName = process.platform === "win32" ? "shadcn.cmd" : "shadcn";
31794
- const shadcnBin = path59.join(cwd, "node_modules", ".bin", binName);
31795
- if (!fs45.existsSync(shadcnBin)) {
31796
- clack4.cancel(
31797
- `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)
31798
31866
  );
31799
- 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
+ }
31800
31879
  }
31801
- return shadcnBin;
31802
- }
31803
- function getHostProjectFilesToRestore(cwd) {
31804
- const hostRelativePaths = [
31805
- "app/layout.tsx",
31806
- "app/layout.ts",
31807
- "app/layout.jsx",
31808
- "app/layout.js",
31809
- "src/app/layout.tsx",
31810
- "src/app/layout.ts",
31811
- "src/app/layout.jsx",
31812
- "src/app/layout.js",
31813
- "app/globals.css",
31814
- "src/app/globals.css"
31815
- ];
31816
- return hostRelativePaths.map((relativePath) => path59.join(cwd, relativePath));
31817
- }
31818
- function snapshotFile(filePath) {
31819
- if (!fs45.existsSync(filePath)) {
31820
- 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
+ }
31821
31895
  }
31822
- return { existed: true, content: fs45.readFileSync(filePath, "utf-8") };
31896
+ return steps;
31823
31897
  }
31824
- function restoreFile(filePath, snapshot) {
31825
- if (snapshot.existed) {
31826
- 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");
31827
31911
  return;
31828
31912
  }
31829
- if (fs45.existsSync(filePath)) {
31830
- 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();
31831
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");
31832
31939
  }
31833
31940
 
31834
31941
  // adapters/next/commands/update-deps.ts
@@ -31894,6 +32001,7 @@ async function runUpdateStylesCommand(options) {
31894
32001
 
31895
32002
  // adapters/next/commands-runtime.ts
31896
32003
  var nextCommandRuntime = {
32004
+ listComponentChoices,
31897
32005
  listInstallableChoices,
31898
32006
  listSchemaChoices,
31899
32007
  runAdd: runAddCommand,