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 +253 -251
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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 = "
|
|
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
|
-
|
|
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
|
|
233
|
-
import
|
|
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 =
|
|
243
|
-
const 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 (
|
|
419
|
+
if (fs4.existsSync(path5.join(dir, lockfile))) {
|
|
247
420
|
return pm;
|
|
248
421
|
}
|
|
249
422
|
}
|
|
250
|
-
const pkgPath =
|
|
251
|
-
if (
|
|
423
|
+
const pkgPath = path5.join(dir, "package.json");
|
|
424
|
+
if (fs4.existsSync(pkgPath)) {
|
|
252
425
|
try {
|
|
253
|
-
const pkg = JSON.parse(
|
|
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 =
|
|
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
|
|
288
|
-
import
|
|
460
|
+
import fs6 from "fs";
|
|
461
|
+
import path7 from "path";
|
|
289
462
|
|
|
290
463
|
// adapters/next/config/detect.ts
|
|
291
|
-
import
|
|
292
|
-
import
|
|
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 =
|
|
296
|
-
if (
|
|
468
|
+
const pkgPath = path6.join(cwd, "package.json");
|
|
469
|
+
if (fs5.existsSync(pkgPath)) {
|
|
297
470
|
try {
|
|
298
|
-
const pkg = JSON.parse(
|
|
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(
|
|
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) =>
|
|
314
|
-
const hasSrcDir =
|
|
315
|
-
const hasTypeScript =
|
|
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 (
|
|
493
|
+
if (fs5.existsSync(path6.join(cwd, namespace))) {
|
|
321
494
|
conflicts.push(`${namespace}/ directory already exists`);
|
|
322
495
|
}
|
|
323
|
-
if (
|
|
324
|
-
conflicts.push(
|
|
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 (
|
|
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 (
|
|
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 (
|
|
529
|
+
if (fs5.existsSync(path6.join(cwd, f))) {
|
|
357
530
|
return { type: "eslint", configFile: f };
|
|
358
531
|
}
|
|
359
532
|
}
|
|
360
|
-
const pkgPath =
|
|
361
|
-
if (
|
|
533
|
+
const pkgPath = path6.join(cwd, "package.json");
|
|
534
|
+
if (fs5.existsSync(pkgPath)) {
|
|
362
535
|
try {
|
|
363
|
-
const pkg = JSON.parse(
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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 (
|
|
381
|
-
const content =
|
|
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 (
|
|
390
|
-
const content =
|
|
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 =
|
|
400
|
-
if (!
|
|
572
|
+
const tsconfigPath = path6.join(cwd, "tsconfig.json");
|
|
573
|
+
if (!fs5.existsSync(tsconfigPath)) return false;
|
|
401
574
|
try {
|
|
402
|
-
const content =
|
|
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 =
|
|
410
|
-
if (!
|
|
582
|
+
const pkgPath = path6.join(cwd, "package.json");
|
|
583
|
+
if (!fs5.existsSync(pkgPath)) return 16;
|
|
411
584
|
try {
|
|
412
|
-
const pkg = JSON.parse(
|
|
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 = '
|
|
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 =
|
|
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: "
|
|
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
|
|
22700
|
-
var
|
|
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
|
-
|
|
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
|
|
22780
|
-
const
|
|
22781
|
-
if (!fs27.existsSync(
|
|
22782
|
-
const original = fs27.readFileSync(
|
|
22783
|
-
if (!
|
|
22784
|
-
fs27.writeFileSync(
|
|
22785
|
-
|
|
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,
|
|
23912
|
-
created.push(
|
|
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,
|
|
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,
|
|
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 = [
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 ${
|
|
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,
|
|
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 ${
|
|
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)}
|
|
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
|
-
[
|
|
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
|
];
|