betterstart-cli 0.0.79 → 0.0.81

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
@@ -31,7 +31,7 @@ import * as p from "@clack/prompts";
31
31
  import fs from "fs";
32
32
  import path from "path";
33
33
  import { fileURLToPath } from "url";
34
- var CONFIG_FILE_NAME = "admin.config.ts";
34
+ var CONFIG_FILE_NAME = "betterstart.config.ts";
35
35
  function findConfigFile(cwd) {
36
36
  const filePath = path.join(cwd, CONFIG_FILE_NAME);
37
37
  return fs.existsSync(filePath) ? filePath : void 0;
@@ -159,8 +159,180 @@ import path27 from "path";
159
159
  import * as p4 from "@clack/prompts";
160
160
 
161
161
  // core-engine/config/serialize.ts
162
+ import fs3 from "fs";
163
+ import path4 from "path";
164
+
165
+ // core-engine/snapshots/store.ts
166
+ import crypto from "crypto";
162
167
  import fs2 from "fs";
163
168
  import path3 from "path";
169
+
170
+ // core-engine/utils/json-format.ts
171
+ function stringifyProjectJson(value) {
172
+ return `${compactShortStringArrays(JSON.stringify(value, null, 2))}
173
+ `;
174
+ }
175
+ function compactShortStringArrays(json) {
176
+ return json.replace(
177
+ /\[\n((?:[ \t]+"(?:[^"\\]|\\.)*",?\n)+)([ \t]*)\]/g,
178
+ (match, entries) => {
179
+ const values = entries.trim().split("\n").map((line) => line.trim());
180
+ const inline = `[${values.map((entry) => entry.replace(/,$/, "")).join(", ")}]`;
181
+ return inline.length <= 70 ? inline : match;
182
+ }
183
+ );
184
+ }
185
+
186
+ // core-engine/snapshots/store.ts
187
+ var BARREL_SCOPE = "_barrels";
188
+ var REMOVED_SCOPE = "_removed";
189
+ function normalizeLineEndings(content) {
190
+ return content.replace(/\r\n/g, "\n");
191
+ }
192
+ function hashContent(content) {
193
+ return `sha256-${crypto.createHash("sha256").update(normalizeLineEndings(content)).digest("hex")}`;
194
+ }
195
+ function snapshotRoot(cwd) {
196
+ return path3.join(cwd, ".betterstart", "snapshots");
197
+ }
198
+ function scopeDir(cwd, scope) {
199
+ return path3.join(snapshotRoot(cwd), scope);
200
+ }
201
+ function manifestPath(cwd, scope) {
202
+ return path3.join(scopeDir(cwd, scope), "manifest.json");
203
+ }
204
+ function filesDir(cwd, scope) {
205
+ return path3.join(scopeDir(cwd, scope), "files");
206
+ }
207
+ function snapshotFilePath(cwd, scope, filePath) {
208
+ return path3.join(filesDir(cwd, scope), ...filePath.split("/"));
209
+ }
210
+ function removedDir(cwd) {
211
+ return path3.join(snapshotRoot(cwd), REMOVED_SCOPE);
212
+ }
213
+ function tombstonePath(cwd, scope) {
214
+ return path3.join(removedDir(cwd), scope);
215
+ }
216
+ function loadManifest(cwd, scope) {
217
+ const filePath = manifestPath(cwd, scope);
218
+ if (!fs2.existsSync(filePath)) {
219
+ return null;
220
+ }
221
+ return JSON.parse(fs2.readFileSync(filePath, "utf-8"));
222
+ }
223
+ function saveManifest(cwd, scope, manifest) {
224
+ fs2.mkdirSync(scopeDir(cwd, scope), { recursive: true });
225
+ fs2.writeFileSync(manifestPath(cwd, scope), stringifyProjectJson(manifest), "utf-8");
226
+ }
227
+ function writeSnapshotFiles(cwd, scope, files) {
228
+ const dir = filesDir(cwd, scope);
229
+ fs2.rmSync(dir, { recursive: true, force: true });
230
+ fs2.mkdirSync(dir, { recursive: true });
231
+ return files.map((file) => {
232
+ const outputPath = snapshotFilePath(cwd, scope, file.path);
233
+ fs2.mkdirSync(path3.dirname(outputPath), { recursive: true });
234
+ fs2.writeFileSync(outputPath, file.content, "utf-8");
235
+ return {
236
+ path: file.path,
237
+ hash: hashContent(file.content)
238
+ };
239
+ });
240
+ }
241
+ function readSnapshotFiles(cwd, scope, manifest) {
242
+ return manifest.files.filter((entry) => fs2.existsSync(snapshotFilePath(cwd, scope, entry.path))).map((entry) => ({
243
+ path: entry.path,
244
+ content: fs2.readFileSync(snapshotFilePath(cwd, scope, entry.path), "utf-8")
245
+ }));
246
+ }
247
+ function deleteSnapshot(cwd, scope) {
248
+ fs2.rmSync(scopeDir(cwd, scope), { recursive: true, force: true });
249
+ }
250
+ function hasSnapshotFiles(cwd, scope) {
251
+ return fs2.existsSync(filesDir(cwd, scope));
252
+ }
253
+ function writeTombstone(cwd, scope) {
254
+ fs2.mkdirSync(removedDir(cwd), { recursive: true });
255
+ fs2.writeFileSync(tombstonePath(cwd, scope), "", "utf-8");
256
+ }
257
+ function hasTombstone(cwd, scope) {
258
+ return fs2.existsSync(tombstonePath(cwd, scope));
259
+ }
260
+ function clearTombstone(cwd, scope) {
261
+ fs2.rmSync(tombstonePath(cwd, scope), { force: true });
262
+ }
263
+ function listSnapshotScopes(cwd) {
264
+ const dir = snapshotRoot(cwd);
265
+ if (!fs2.existsSync(dir)) {
266
+ return [];
267
+ }
268
+ return fs2.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => name !== REMOVED_SCOPE).sort();
269
+ }
270
+ function createManifest(scope, schemaJson, cliVersion, files, skipped) {
271
+ return {
272
+ schemaName: scope,
273
+ schemaHash: hashContent(JSON.stringify(schemaJson)),
274
+ schemaJson,
275
+ cliVersion,
276
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
277
+ files: files.map((file) => ({
278
+ path: file.path,
279
+ hash: hashContent(file.content)
280
+ })),
281
+ skipped: [...skipped].sort()
282
+ };
283
+ }
284
+ function snapshotRootExists(cwd) {
285
+ return fs2.existsSync(snapshotRoot(cwd));
286
+ }
287
+ function manifestFilesByPath(manifest) {
288
+ return new Map(manifest.files.map((file) => [file.path, file]));
289
+ }
290
+ function loadAllManifests(cwd) {
291
+ return listSnapshotScopes(cwd).filter((scope) => scope !== BARREL_SCOPE).map((scope) => ({ scope, manifest: loadManifest(cwd, scope) })).filter(
292
+ (entry) => entry.manifest !== null
293
+ );
294
+ }
295
+ function snapshotTargetPath(cwd, filePath) {
296
+ return path3.join(cwd, ...filePath.split("/"));
297
+ }
298
+ function getCurrentCliVersion() {
299
+ let currentDir = path3.dirname(new URL(import.meta.url).pathname);
300
+ for (let depth = 0; depth < 6; depth++) {
301
+ const packagePath = path3.join(currentDir, "package.json");
302
+ if (fs2.existsSync(packagePath)) {
303
+ try {
304
+ const pkg = JSON.parse(fs2.readFileSync(packagePath, "utf-8"));
305
+ if (pkg.name === "betterstart-cli" && pkg.version) {
306
+ return pkg.version;
307
+ }
308
+ } catch {
309
+ }
310
+ }
311
+ const parentDir = path3.dirname(currentDir);
312
+ if (parentDir === currentDir) {
313
+ break;
314
+ }
315
+ currentDir = parentDir;
316
+ }
317
+ return "0.0.0";
318
+ }
319
+ function getAllTrackedFilePaths(cwd) {
320
+ const paths = /* @__PURE__ */ new Set();
321
+ for (const { manifest } of loadAllManifests(cwd)) {
322
+ for (const file of manifest.files) {
323
+ paths.add(file.path);
324
+ }
325
+ }
326
+ const barrelManifest = loadManifest(cwd, BARREL_SCOPE);
327
+ if (barrelManifest) {
328
+ for (const file of barrelManifest.files) {
329
+ paths.add(file.path);
330
+ }
331
+ }
332
+ return Array.from(paths).sort();
333
+ }
334
+
335
+ // core-engine/config/serialize.ts
164
336
  function quote(value) {
165
337
  return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
166
338
  }
@@ -176,6 +348,7 @@ function serializeConfig(config) {
176
348
 
177
349
  export default defineConfig({
178
350
  framework: 'next',
351
+ cliVersion: ${quote(getCurrentCliVersion())},
179
352
 
180
353
  frameworkConfig: {
181
354
  next: {
@@ -224,13 +397,13 @@ export default defineConfig({
224
397
  `;
225
398
  }
226
399
  function writeConfigFile(cwd, config) {
227
- fs2.writeFileSync(path3.join(cwd, "admin.config.ts"), serializeConfig(config), "utf-8");
400
+ fs3.writeFileSync(path4.join(cwd, CONFIG_FILE_NAME), serializeConfig(config), "utf-8");
228
401
  }
229
402
 
230
403
  // core-engine/utils/package-manager.ts
231
404
  import { execFileSync } from "child_process";
232
- import fs3 from "fs";
233
- import path4 from "path";
405
+ import fs4 from "fs";
406
+ import path5 from "path";
234
407
  var LOCKFILE_MAP = {
235
408
  "pnpm-lock.yaml": "pnpm",
236
409
  "package-lock.json": "npm",
@@ -239,18 +412,18 @@ var LOCKFILE_MAP = {
239
412
  "bun.lock": "bun"
240
413
  };
241
414
  function detectPackageManager(cwd) {
242
- let dir = path4.resolve(cwd);
243
- const root = path4.parse(dir).root;
415
+ let dir = path5.resolve(cwd);
416
+ const root = path5.parse(dir).root;
244
417
  while (dir !== root) {
245
418
  for (const [lockfile, pm] of Object.entries(LOCKFILE_MAP)) {
246
- if (fs3.existsSync(path4.join(dir, lockfile))) {
419
+ if (fs4.existsSync(path5.join(dir, lockfile))) {
247
420
  return pm;
248
421
  }
249
422
  }
250
- const pkgPath = path4.join(dir, "package.json");
251
- if (fs3.existsSync(pkgPath)) {
423
+ const pkgPath = path5.join(dir, "package.json");
424
+ if (fs4.existsSync(pkgPath)) {
252
425
  try {
253
- const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
426
+ const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
254
427
  if (typeof pkg.packageManager === "string") {
255
428
  const name = pkg.packageManager.split("@")[0];
256
429
  if (name === "pnpm" || name === "npm" || name === "yarn" || name === "bun") {
@@ -260,7 +433,7 @@ function detectPackageManager(cwd) {
260
433
  } catch {
261
434
  }
262
435
  }
263
- dir = path4.dirname(dir);
436
+ dir = path5.dirname(dir);
264
437
  }
265
438
  return "npm";
266
439
  }
@@ -284,25 +457,25 @@ function createNextAppCommand() {
284
457
  }
285
458
 
286
459
  // adapters/next/config.ts
287
- import fs5 from "fs";
288
- import path6 from "path";
460
+ import fs6 from "fs";
461
+ import path7 from "path";
289
462
 
290
463
  // adapters/next/config/detect.ts
291
- import fs4 from "fs";
292
- import path5 from "path";
464
+ import fs5 from "fs";
465
+ import path6 from "path";
293
466
  var NEXT_CONFIG_FILES = ["next.config.ts", "next.config.js", "next.config.mjs"];
294
467
  function detectProjectName(cwd) {
295
- const pkgPath = path5.join(cwd, "package.json");
296
- if (fs4.existsSync(pkgPath)) {
468
+ const pkgPath = path6.join(cwd, "package.json");
469
+ if (fs5.existsSync(pkgPath)) {
297
470
  try {
298
- const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
471
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
299
472
  if (typeof pkg.name === "string" && pkg.name.length > 0) {
300
473
  return formatProjectName(pkg.name);
301
474
  }
302
475
  } catch {
303
476
  }
304
477
  }
305
- return formatProjectName(path5.basename(cwd));
478
+ return formatProjectName(path6.basename(cwd));
306
479
  }
307
480
  function formatProjectName(name) {
308
481
  const base = name.includes("/") ? name.split("/").pop() : name;
@@ -310,21 +483,21 @@ function formatProjectName(name) {
310
483
  }
311
484
  function detectProject(cwd, namespaceValue = DEFAULT_ADMIN_NAMESPACE) {
312
485
  const namespace = validateAdminNamespace(namespaceValue);
313
- const isExisting = NEXT_CONFIG_FILES.some((f) => fs4.existsSync(path5.join(cwd, f)));
314
- const hasSrcDir = fs4.existsSync(path5.join(cwd, "src"));
315
- const hasTypeScript = fs4.existsSync(path5.join(cwd, "tsconfig.json")) || fs4.existsSync(path5.join(cwd, "tsconfig.app.json"));
486
+ const isExisting = NEXT_CONFIG_FILES.some((f) => fs5.existsSync(path6.join(cwd, f)));
487
+ const hasSrcDir = fs5.existsSync(path6.join(cwd, "src"));
488
+ const hasTypeScript = fs5.existsSync(path6.join(cwd, "tsconfig.json")) || fs5.existsSync(path6.join(cwd, "tsconfig.app.json"));
316
489
  const hasTailwind = detectTailwind(cwd);
317
490
  const linter = detectLinter(cwd);
318
491
  const conflicts = [];
319
492
  if (isExisting) {
320
- if (fs4.existsSync(path5.join(cwd, namespace))) {
493
+ if (fs5.existsSync(path6.join(cwd, namespace))) {
321
494
  conflicts.push(`${namespace}/ directory already exists`);
322
495
  }
323
- if (fs4.existsSync(path5.join(cwd, "admin.config.ts"))) {
324
- conflicts.push("admin.config.ts already exists");
496
+ if (fs5.existsSync(path6.join(cwd, CONFIG_FILE_NAME))) {
497
+ conflicts.push(`${CONFIG_FILE_NAME} already exists`);
325
498
  }
326
499
  const appBase = hasSrcDir ? "src/app" : "app";
327
- if (fs4.existsSync(path5.join(cwd, appBase, `(${namespace})`))) {
500
+ if (fs5.existsSync(path6.join(cwd, appBase, `(${namespace})`))) {
328
501
  conflicts.push(`${appBase}/(${namespace})/ route group already exists`);
329
502
  }
330
503
  if (hasTsconfigAdminAliases(cwd, namespace)) {
@@ -348,19 +521,19 @@ var ESLINT_CONFIG_FILES = [
348
521
  ];
349
522
  function detectLinter(cwd) {
350
523
  for (const f of BIOME_CONFIG_FILES) {
351
- if (fs4.existsSync(path5.join(cwd, f))) {
524
+ if (fs5.existsSync(path6.join(cwd, f))) {
352
525
  return { type: "biome", configFile: f };
353
526
  }
354
527
  }
355
528
  for (const f of ESLINT_CONFIG_FILES) {
356
- if (fs4.existsSync(path5.join(cwd, f))) {
529
+ if (fs5.existsSync(path6.join(cwd, f))) {
357
530
  return { type: "eslint", configFile: f };
358
531
  }
359
532
  }
360
- const pkgPath = path5.join(cwd, "package.json");
361
- if (fs4.existsSync(pkgPath)) {
533
+ const pkgPath = path6.join(cwd, "package.json");
534
+ if (fs5.existsSync(pkgPath)) {
362
535
  try {
363
- const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
536
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
364
537
  if (pkg.eslintConfig) {
365
538
  return { type: "eslint", configFile: "package.json (eslintConfig)" };
366
539
  }
@@ -371,14 +544,14 @@ function detectLinter(cwd) {
371
544
  }
372
545
  function detectTailwind(cwd) {
373
546
  const cssFiles = ["globals.css", "app.css", "index.css"].flatMap((f) => [
374
- path5.join(cwd, "src", "app", f),
375
- path5.join(cwd, "app", f),
376
- path5.join(cwd, "src", f),
377
- path5.join(cwd, f)
547
+ path6.join(cwd, "src", "app", f),
548
+ path6.join(cwd, "app", f),
549
+ path6.join(cwd, "src", f),
550
+ path6.join(cwd, f)
378
551
  ]);
379
552
  for (const cssFile of cssFiles) {
380
- if (fs4.existsSync(cssFile)) {
381
- const content = fs4.readFileSync(cssFile, "utf-8");
553
+ if (fs5.existsSync(cssFile)) {
554
+ const content = fs5.readFileSync(cssFile, "utf-8");
382
555
  if (content.includes('@import "tailwindcss"') || content.includes("@import 'tailwindcss'") || content.includes("@theme")) {
383
556
  return true;
384
557
  }
@@ -386,8 +559,8 @@ function detectTailwind(cwd) {
386
559
  }
387
560
  const postcssFiles = ["postcss.config.js", "postcss.config.mjs", "postcss.config.cjs"];
388
561
  for (const f of postcssFiles) {
389
- if (fs4.existsSync(path5.join(cwd, f))) {
390
- const content = fs4.readFileSync(path5.join(cwd, f), "utf-8");
562
+ if (fs5.existsSync(path6.join(cwd, f))) {
563
+ const content = fs5.readFileSync(path6.join(cwd, f), "utf-8");
391
564
  if (content.includes("tailwindcss") || content.includes("@tailwindcss")) {
392
565
  return true;
393
566
  }
@@ -396,20 +569,20 @@ function detectTailwind(cwd) {
396
569
  return false;
397
570
  }
398
571
  function hasTsconfigAdminAliases(cwd, namespace) {
399
- const tsconfigPath = path5.join(cwd, "tsconfig.json");
400
- if (!fs4.existsSync(tsconfigPath)) return false;
572
+ const tsconfigPath = path6.join(cwd, "tsconfig.json");
573
+ if (!fs5.existsSync(tsconfigPath)) return false;
401
574
  try {
402
- const content = fs4.readFileSync(tsconfigPath, "utf-8");
575
+ const content = fs5.readFileSync(tsconfigPath, "utf-8");
403
576
  return content.includes(`@${namespace}/`);
404
577
  } catch {
405
578
  return false;
406
579
  }
407
580
  }
408
581
  function detectNextMajorVersion(cwd) {
409
- const pkgPath = path5.join(cwd, "package.json");
410
- if (!fs4.existsSync(pkgPath)) return 16;
582
+ const pkgPath = path6.join(cwd, "package.json");
583
+ if (!fs5.existsSync(pkgPath)) return 16;
411
584
  try {
412
- const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
585
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
413
586
  const version2 = pkg.dependencies?.next ?? pkg.devDependencies?.next;
414
587
  if (typeof version2 !== "string") return 16;
415
588
  const match = version2.match(/(\d+)/);
@@ -421,7 +594,7 @@ function detectNextMajorVersion(cwd) {
421
594
  }
422
595
 
423
596
  // adapters/next/config.ts
424
- var unsupportedStateMessage = 'admin.config.ts must use the current BetterStart config shape with framework: "next" and frameworkConfig.next. Old top-level srcDir, nextMajorVersion, and paths config is no longer supported.';
597
+ var unsupportedStateMessage = 'betterstart.config.ts must use the current BetterStart config shape with framework: "next" and frameworkConfig.next. Old top-level srcDir, nextMajorVersion, and paths config is no longer supported.';
425
598
  function getDefaultConfig(srcDir, nextMajorVersion = 16) {
426
599
  const namespace = DEFAULT_ADMIN_NAMESPACE;
427
600
  const paths = deriveNamespacePaths(namespace, srcDir);
@@ -472,7 +645,7 @@ async function resolveConfig(cwd) {
472
645
  const workingDir = cwd ?? process.cwd();
473
646
  const configPath = findConfigFile(workingDir);
474
647
  const nextMajorVersion = detectNextMajorVersion(workingDir);
475
- const hasSrc = fs5.existsSync(path6.join(workingDir, "src"));
648
+ const hasSrc = fs6.existsSync(path7.join(workingDir, "src"));
476
649
  const defaultConfig = getDefaultConfig(hasSrc, nextMajorVersion);
477
650
  if (!configPath) {
478
651
  return defaultConfig;
@@ -524,176 +697,6 @@ import { execFileSync as execFileSync2 } from "child_process";
524
697
  import fs8 from "fs";
525
698
  import path10 from "path";
526
699
 
527
- // core-engine/snapshots/store.ts
528
- import crypto from "crypto";
529
- import fs6 from "fs";
530
- import path7 from "path";
531
-
532
- // core-engine/utils/json-format.ts
533
- function stringifyProjectJson(value) {
534
- return `${compactShortStringArrays(JSON.stringify(value, null, 2))}
535
- `;
536
- }
537
- function compactShortStringArrays(json) {
538
- return json.replace(
539
- /\[\n((?:[ \t]+"(?:[^"\\]|\\.)*",?\n)+)([ \t]*)\]/g,
540
- (match, entries) => {
541
- const values = entries.trim().split("\n").map((line) => line.trim());
542
- const inline = `[${values.map((entry) => entry.replace(/,$/, "")).join(", ")}]`;
543
- return inline.length <= 70 ? inline : match;
544
- }
545
- );
546
- }
547
-
548
- // core-engine/snapshots/store.ts
549
- var BARREL_SCOPE = "_barrels";
550
- var REMOVED_SCOPE = "_removed";
551
- function normalizeLineEndings(content) {
552
- return content.replace(/\r\n/g, "\n");
553
- }
554
- function hashContent(content) {
555
- return `sha256-${crypto.createHash("sha256").update(normalizeLineEndings(content)).digest("hex")}`;
556
- }
557
- function snapshotRoot(cwd) {
558
- return path7.join(cwd, ".betterstart", "snapshots");
559
- }
560
- function scopeDir(cwd, scope) {
561
- return path7.join(snapshotRoot(cwd), scope);
562
- }
563
- function manifestPath(cwd, scope) {
564
- return path7.join(scopeDir(cwd, scope), "manifest.json");
565
- }
566
- function filesDir(cwd, scope) {
567
- return path7.join(scopeDir(cwd, scope), "files");
568
- }
569
- function snapshotFilePath(cwd, scope, filePath) {
570
- return path7.join(filesDir(cwd, scope), ...filePath.split("/"));
571
- }
572
- function removedDir(cwd) {
573
- return path7.join(snapshotRoot(cwd), REMOVED_SCOPE);
574
- }
575
- function tombstonePath(cwd, scope) {
576
- return path7.join(removedDir(cwd), scope);
577
- }
578
- function loadManifest(cwd, scope) {
579
- const filePath = manifestPath(cwd, scope);
580
- if (!fs6.existsSync(filePath)) {
581
- return null;
582
- }
583
- return JSON.parse(fs6.readFileSync(filePath, "utf-8"));
584
- }
585
- function saveManifest(cwd, scope, manifest) {
586
- fs6.mkdirSync(scopeDir(cwd, scope), { recursive: true });
587
- fs6.writeFileSync(manifestPath(cwd, scope), stringifyProjectJson(manifest), "utf-8");
588
- }
589
- function writeSnapshotFiles(cwd, scope, files) {
590
- const dir = filesDir(cwd, scope);
591
- fs6.rmSync(dir, { recursive: true, force: true });
592
- fs6.mkdirSync(dir, { recursive: true });
593
- return files.map((file) => {
594
- const outputPath = snapshotFilePath(cwd, scope, file.path);
595
- fs6.mkdirSync(path7.dirname(outputPath), { recursive: true });
596
- fs6.writeFileSync(outputPath, file.content, "utf-8");
597
- return {
598
- path: file.path,
599
- hash: hashContent(file.content)
600
- };
601
- });
602
- }
603
- function readSnapshotFiles(cwd, scope, manifest) {
604
- return manifest.files.filter((entry) => fs6.existsSync(snapshotFilePath(cwd, scope, entry.path))).map((entry) => ({
605
- path: entry.path,
606
- content: fs6.readFileSync(snapshotFilePath(cwd, scope, entry.path), "utf-8")
607
- }));
608
- }
609
- function deleteSnapshot(cwd, scope) {
610
- fs6.rmSync(scopeDir(cwd, scope), { recursive: true, force: true });
611
- }
612
- function hasSnapshotFiles(cwd, scope) {
613
- return fs6.existsSync(filesDir(cwd, scope));
614
- }
615
- function writeTombstone(cwd, scope) {
616
- fs6.mkdirSync(removedDir(cwd), { recursive: true });
617
- fs6.writeFileSync(tombstonePath(cwd, scope), "", "utf-8");
618
- }
619
- function hasTombstone(cwd, scope) {
620
- return fs6.existsSync(tombstonePath(cwd, scope));
621
- }
622
- function clearTombstone(cwd, scope) {
623
- fs6.rmSync(tombstonePath(cwd, scope), { force: true });
624
- }
625
- function listSnapshotScopes(cwd) {
626
- const dir = snapshotRoot(cwd);
627
- if (!fs6.existsSync(dir)) {
628
- return [];
629
- }
630
- return fs6.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => name !== REMOVED_SCOPE).sort();
631
- }
632
- function createManifest(scope, schemaJson, cliVersion, files, skipped) {
633
- return {
634
- schemaName: scope,
635
- schemaHash: hashContent(JSON.stringify(schemaJson)),
636
- schemaJson,
637
- cliVersion,
638
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
639
- files: files.map((file) => ({
640
- path: file.path,
641
- hash: hashContent(file.content)
642
- })),
643
- skipped: [...skipped].sort()
644
- };
645
- }
646
- function snapshotRootExists(cwd) {
647
- return fs6.existsSync(snapshotRoot(cwd));
648
- }
649
- function manifestFilesByPath(manifest) {
650
- return new Map(manifest.files.map((file) => [file.path, file]));
651
- }
652
- function loadAllManifests(cwd) {
653
- return listSnapshotScopes(cwd).filter((scope) => scope !== BARREL_SCOPE).map((scope) => ({ scope, manifest: loadManifest(cwd, scope) })).filter(
654
- (entry) => entry.manifest !== null
655
- );
656
- }
657
- function snapshotTargetPath(cwd, filePath) {
658
- return path7.join(cwd, ...filePath.split("/"));
659
- }
660
- function getCurrentCliVersion() {
661
- let currentDir = path7.dirname(new URL(import.meta.url).pathname);
662
- for (let depth = 0; depth < 6; depth++) {
663
- const packagePath = path7.join(currentDir, "package.json");
664
- if (fs6.existsSync(packagePath)) {
665
- try {
666
- const pkg = JSON.parse(fs6.readFileSync(packagePath, "utf-8"));
667
- if (pkg.name === "betterstart-cli" && pkg.version) {
668
- return pkg.version;
669
- }
670
- } catch {
671
- }
672
- }
673
- const parentDir = path7.dirname(currentDir);
674
- if (parentDir === currentDir) {
675
- break;
676
- }
677
- currentDir = parentDir;
678
- }
679
- return "0.0.0";
680
- }
681
- function getAllTrackedFilePaths(cwd) {
682
- const paths = /* @__PURE__ */ new Set();
683
- for (const { manifest } of loadAllManifests(cwd)) {
684
- for (const file of manifest.files) {
685
- paths.add(file.path);
686
- }
687
- }
688
- const barrelManifest = loadManifest(cwd, BARREL_SCOPE);
689
- if (barrelManifest) {
690
- for (const file of barrelManifest.files) {
691
- paths.add(file.path);
692
- }
693
- }
694
- return Array.from(paths).sort();
695
- }
696
-
697
700
  // adapters/next/init/scaffolders/dependencies.ts
698
701
  import { spawn } from "child_process";
699
702
  import fs7 from "fs";
@@ -22584,7 +22587,7 @@ async function promptPresets(cwd, options = {}) {
22584
22587
  import * as p13 from "@clack/prompts";
22585
22588
  async function promptProject(defaultName) {
22586
22589
  const projectName = await p13.text({
22587
- message: "Enter the project name",
22590
+ message: "What is your project named?",
22588
22591
  placeholder: defaultName ?? "(Leave blank to skip directory creation)",
22589
22592
  defaultValue: defaultName ?? ".",
22590
22593
  validate: (value) => {
@@ -22696,13 +22699,13 @@ import { spawn as spawn3 } from "child_process";
22696
22699
  import fs27 from "fs";
22697
22700
  import path33 from "path";
22698
22701
  var LOCAL_PROTOCOL_PATTERN = /^(workspace|link|file|portal):/;
22699
- var ADMIN_CONFIG_IMPORT_PATTERN = /^import\s+\{[^}]*\bdefineConfig\b[^}]*\}\s+from\s+['"]betterstart-cli['"];?[^\S\n]*$/m;
22700
- var ADMIN_CONFIG_IMPORT_SHIM = "const defineConfig = <T>(config: T): T => config";
22702
+ var CONFIG_IMPORT_PATTERN = /^import\s+\{[^}]*\bdefineConfig\b[^}]*\}\s+from\s+['"]betterstart-cli['"];?[^\S\n]*$/m;
22703
+ var CONFIG_IMPORT_SHIM = "const defineConfig = <T>(config: T): T => config";
22701
22704
  var LOCKFILE_REFRESH_TIMEOUT_MS = 12e4;
22702
22705
  async function guardProjectForDeploy(cwd, refreshLockfile = runPnpmLockfileOnly) {
22703
22706
  const restores = [];
22704
22707
  const { localSpecDeps, removedCliDep } = guardPackageJson(cwd, restores);
22705
- guardAdminConfig(cwd, restores);
22708
+ guardBetterstartConfig(cwd, restores);
22706
22709
  const lockfile = removedCliDep ? await guardPnpmLockfile(cwd, restores, refreshLockfile) : "not-needed";
22707
22710
  return {
22708
22711
  localSpecDeps,
@@ -22776,17 +22779,13 @@ function runPnpmLockfileOnly(cwd) {
22776
22779
  });
22777
22780
  });
22778
22781
  }
22779
- function guardAdminConfig(cwd, restores) {
22780
- const adminConfigPath = path33.join(cwd, "admin.config.ts");
22781
- if (!fs27.existsSync(adminConfigPath)) return;
22782
- const original = fs27.readFileSync(adminConfigPath, "utf-8");
22783
- if (!ADMIN_CONFIG_IMPORT_PATTERN.test(original)) return;
22784
- fs27.writeFileSync(
22785
- adminConfigPath,
22786
- original.replace(ADMIN_CONFIG_IMPORT_PATTERN, ADMIN_CONFIG_IMPORT_SHIM),
22787
- "utf-8"
22788
- );
22789
- restores.push(() => fs27.writeFileSync(adminConfigPath, original, "utf-8"));
22782
+ function guardBetterstartConfig(cwd, restores) {
22783
+ const configPath = path33.join(cwd, CONFIG_FILE_NAME);
22784
+ if (!fs27.existsSync(configPath)) return;
22785
+ const original = fs27.readFileSync(configPath, "utf-8");
22786
+ if (!CONFIG_IMPORT_PATTERN.test(original)) return;
22787
+ fs27.writeFileSync(configPath, original.replace(CONFIG_IMPORT_PATTERN, CONFIG_IMPORT_SHIM), "utf-8");
22788
+ restores.push(() => fs27.writeFileSync(configPath, original, "utf-8"));
22790
22789
  }
22791
22790
 
22792
22791
  // adapters/next/init/railway/project.ts
@@ -23908,8 +23907,8 @@ function scaffoldBase({
23908
23907
  ensureDir(path38.resolve(cwd, dir));
23909
23908
  }
23910
23909
  const configContent = serializeConfig(config);
23911
- if (safeWriteFile(path38.resolve(cwd, "admin.config.ts"), configContent)) {
23912
- created.push("admin.config.ts");
23910
+ if (safeWriteFile(path38.resolve(cwd, CONFIG_FILE_NAME), configContent)) {
23911
+ created.push(CONFIG_FILE_NAME);
23913
23912
  }
23914
23913
  return created;
23915
23914
  }
@@ -26976,7 +26975,7 @@ function resolveNonInteractiveProject(name) {
26976
26975
  return { projectName };
26977
26976
  }
26978
26977
  async function readExistingConfigNamespace(cwd) {
26979
- const configPath = path52.resolve(cwd, "admin.config.ts");
26978
+ const configPath = path52.resolve(cwd, CONFIG_FILE_NAME);
26980
26979
  if (!fs41.existsSync(configPath)) {
26981
26980
  return;
26982
26981
  }
@@ -26993,7 +26992,7 @@ async function readExistingConfigNamespace(cwd) {
26993
26992
  }
26994
26993
  async function resolveForceInitNamespaces(cwd, targetNamespace) {
26995
26994
  const namespaces = /* @__PURE__ */ new Set([validateAdminNamespace(targetNamespace)]);
26996
- const hasConfig = fs41.existsSync(path52.resolve(cwd, "admin.config.ts"));
26995
+ const hasConfig = fs41.existsSync(path52.resolve(cwd, CONFIG_FILE_NAME));
26997
26996
  const existingNamespace = await readExistingConfigNamespace(cwd);
26998
26997
  if (existingNamespace) {
26999
26998
  namespaces.add(existingNamespace);
@@ -27015,7 +27014,7 @@ function removeExistingAdminPaths(cwd, namespaces) {
27015
27014
  ...segments.flatMap((segment) => [segment, `app/(${segment})`, `src/app/(${segment})`]),
27016
27015
  ".betterstart"
27017
27016
  ];
27018
- const nukeFiles = ["admin.config.ts", "ADMIN.md", "drizzle.config.ts"];
27017
+ const nukeFiles = [CONFIG_FILE_NAME, "ADMIN.md", "drizzle.config.ts"];
27019
27018
  let removed = 0;
27020
27019
  for (const dir of nukeDirs) {
27021
27020
  const fullPath = path52.resolve(cwd, dir);
@@ -27065,6 +27064,15 @@ async function runInitCommand(name, options) {
27065
27064
  process.exit(1);
27066
27065
  }
27067
27066
  }
27067
+ let projectPrompt;
27068
+ if (!detectProject(cwd, namespace).isExisting) {
27069
+ try {
27070
+ projectPrompt = options.yes ? resolveNonInteractiveProject(name) : await promptProject(name);
27071
+ } catch (error) {
27072
+ p22.log.error(error instanceof Error ? error.message : String(error));
27073
+ process.exit(1);
27074
+ }
27075
+ }
27068
27076
  if (!options.yes && !options.namespace) {
27069
27077
  const defaultDashboardPath = resolveAdminNamespace(DEFAULT_ADMIN_NAMESPACE).routePath;
27070
27078
  const namespaceInput = await p22.text({
@@ -27134,13 +27142,7 @@ async function runInitCommand(name, options) {
27134
27142
  }
27135
27143
  }
27136
27144
  } else {
27137
- let projectPrompt;
27138
- try {
27139
- projectPrompt = options.yes ? resolveNonInteractiveProject(name) : await promptProject(name);
27140
- } catch (error) {
27141
- p22.log.error(error instanceof Error ? error.message : String(error));
27142
- process.exit(1);
27143
- }
27145
+ const freshProject = projectPrompt;
27144
27146
  srcDir = false;
27145
27147
  if (!options.yes) {
27146
27148
  const pmChoice = await p22.select({
@@ -27157,12 +27159,12 @@ async function runInitCommand(name, options) {
27157
27159
  }
27158
27160
  pm = pmChoice;
27159
27161
  }
27160
- const displayName = projectPrompt.projectName === "." ? path52.basename(cwd) : projectPrompt.projectName;
27162
+ const displayName = freshProject.projectName === "." ? path52.basename(cwd) : freshProject.projectName;
27161
27163
  projectName = displayName;
27162
27164
  const { bin, prefix } = createNextAppCommand();
27163
27165
  const cnaArgs = [
27164
27166
  ...prefix,
27165
- projectPrompt.projectName,
27167
+ freshProject.projectName,
27166
27168
  "--yes",
27167
27169
  "--typescript",
27168
27170
  "--tailwind",
@@ -27190,12 +27192,12 @@ async function runInitCommand(name, options) {
27190
27192
  p22.log.error(createNextAppResult.error);
27191
27193
  p22.log.info(
27192
27194
  `You can create the project manually:
27193
- ${pc9.cyan(`npx create-next-app@latest ${projectPrompt.projectName} --typescript --tailwind --app`)}
27195
+ ${pc9.cyan(`npx create-next-app@latest ${freshProject.projectName} --typescript --tailwind --app`)}
27194
27196
  Then run ${pc9.cyan("betterstart init")} inside it.`
27195
27197
  );
27196
27198
  process.exit(1);
27197
27199
  }
27198
- cwd = path52.resolve(cwd, projectPrompt.projectName);
27200
+ cwd = path52.resolve(cwd, freshProject.projectName);
27199
27201
  const hasPackageJson = fs41.existsSync(path52.join(cwd, "package.json"));
27200
27202
  const hasNextConfig = ["next.config.ts", "next.config.js", "next.config.mjs"].some(
27201
27203
  (f) => fs41.existsSync(path52.join(cwd, f))
@@ -27205,7 +27207,7 @@ async function runInitCommand(name, options) {
27205
27207
  p22.log.error(
27206
27208
  "create-next-app completed but the project was not created. This can happen with nested npx calls."
27207
27209
  );
27208
- const manualCmd = `npx create-next-app@latest ${projectPrompt.projectName} --typescript --tailwind --app`;
27210
+ const manualCmd = `npx create-next-app@latest ${freshProject.projectName} --typescript --tailwind --app`;
27209
27211
  p22.log.info(
27210
27212
  `Create the project manually:
27211
27213
  ${pc9.cyan(manualCmd)}
@@ -27772,7 +27774,7 @@ async function runInitCommand(name, options) {
27772
27774
  }
27773
27775
  }
27774
27776
  if (seedResult.success) {
27775
- const seedSuccessMessage = seedOverwriteMode === "admin" ? `Admin user replaced` : `Admin user ${pc9.green(seedEmail)} successfully created`;
27777
+ const seedSuccessMessage = seedOverwriteMode === "admin" ? `Admin user replaced` : `Admin user ${pc9.green(seedEmail)} created`;
27776
27778
  s.stop(seedSuccessMessage);
27777
27779
  rowsBelowCredentialPrompts += clackLogRows(seedSuccessMessage);
27778
27780
  eraseRowsAbove(credentialPromptRows, rowsBelowCredentialPrompts);
@@ -28837,7 +28839,7 @@ function buildUninstallPlan(cwd, namespaceValue) {
28837
28839
  const configFiles = [];
28838
28840
  const configPaths = [];
28839
28841
  const candidates = [
28840
- ["admin.config.ts", path57.join(cwd, "admin.config.ts")],
28842
+ [CONFIG_FILE_NAME, path57.join(cwd, CONFIG_FILE_NAME)],
28841
28843
  ["drizzle.config.ts", path57.join(cwd, "drizzle.config.ts")],
28842
28844
  ["ADMIN.md", path57.join(cwd, "ADMIN.md")]
28843
28845
  ];