betterstart-cli 0.0.31 → 0.0.33

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
@@ -19775,6 +19775,14 @@ function fitSpinnerMessage(message) {
19775
19775
  if (visible.length <= max) return message;
19776
19776
  return `${visible.slice(0, max - 1)}\u2026`;
19777
19777
  }
19778
+ var CLACK_LINE_ROWS = 2;
19779
+ function eraseClackLine(options = {}) {
19780
+ if (!process.stdout.isTTY || process.env.CI === "true") return;
19781
+ const below = (options.linesBelow ?? 0) * CLACK_LINE_ROWS;
19782
+ const up = below + CLACK_LINE_ROWS;
19783
+ const restore = below > 0 ? `\x1B[${below}B` : "";
19784
+ process.stdout.write(`\x1B[${up}A\x1B[${CLACK_LINE_ROWS}M${restore}`);
19785
+ }
19778
19786
  function spinner2(options) {
19779
19787
  const inner = p5.spinner(options);
19780
19788
  const guided = options?.withGuide !== false;
@@ -21900,6 +21908,155 @@ function openBrowser(url) {
21900
21908
  // adapters/next/init/prompts/plugins.ts
21901
21909
  import { styleText } from "util";
21902
21910
  import * as p11 from "@clack/prompts";
21911
+
21912
+ // adapters/next/init/scaffolders/env.ts
21913
+ import crypto2 from "crypto";
21914
+
21915
+ // adapters/next/init/scaffolders/dev-server.ts
21916
+ import fs26 from "fs";
21917
+ import path32 from "path";
21918
+ var DEFAULT_DEV_PORT = 4242;
21919
+ var DEV_PORT_FLAG_PATTERN = /(?:^|\s)(?:--port(?:=|\s+)|-p(?:=|\s+)?)(\d+)(?=\s|$)/;
21920
+ var DEV_PORT_ENV_PATTERN = /(?:^|\s)PORT=(\d+)(?=\s|$)/;
21921
+ function detectDevPort(cwd) {
21922
+ const pkg = readPackageJson2(cwd);
21923
+ if (!pkg) return DEFAULT_DEV_PORT;
21924
+ return detectDevPortFromScript(pkg.scripts?.dev) ?? DEFAULT_DEV_PORT;
21925
+ }
21926
+ function detectDevPortFromScript(devScript) {
21927
+ if (typeof devScript !== "string") return void 0;
21928
+ const trimmed = devScript.trim();
21929
+ if (trimmed.length === 0) return void 0;
21930
+ const flagMatch = trimmed.match(DEV_PORT_FLAG_PATTERN);
21931
+ const envMatch = trimmed.match(DEV_PORT_ENV_PATTERN);
21932
+ const rawPort = flagMatch?.[1] ?? envMatch?.[1];
21933
+ if (!rawPort) return void 0;
21934
+ const port = Number.parseInt(rawPort, 10);
21935
+ return isValidPort(port) ? port : void 0;
21936
+ }
21937
+ function ensureDefaultDevScriptPort(cwd) {
21938
+ const pkgPath = path32.join(cwd, "package.json");
21939
+ if (!fs26.existsSync(pkgPath)) {
21940
+ return { status: "missing-package", port: DEFAULT_DEV_PORT };
21941
+ }
21942
+ const raw = fs26.readFileSync(pkgPath, "utf-8");
21943
+ let pkg;
21944
+ try {
21945
+ pkg = JSON.parse(raw);
21946
+ } catch {
21947
+ return { status: "missing-package", port: DEFAULT_DEV_PORT };
21948
+ }
21949
+ const devScript = pkg.scripts?.dev;
21950
+ if (typeof devScript !== "string" || devScript.trim().length === 0) {
21951
+ return { status: "missing-script", port: DEFAULT_DEV_PORT };
21952
+ }
21953
+ const explicitPort = detectDevPortFromScript(devScript);
21954
+ if (explicitPort) {
21955
+ return { status: "preserved", port: explicitPort };
21956
+ }
21957
+ if (!isPlainNextDevScript(devScript)) {
21958
+ return { status: "skipped", port: DEFAULT_DEV_PORT };
21959
+ }
21960
+ const normalizedScript = `${normalizeWhitespace(devScript)} --port ${DEFAULT_DEV_PORT}`;
21961
+ pkg.scripts = {
21962
+ ...pkg.scripts,
21963
+ dev: normalizedScript
21964
+ };
21965
+ const newline = raw.includes("\r\n") ? "\r\n" : "\n";
21966
+ const indent = detectIndent(raw);
21967
+ fs26.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, indent)}${newline}`, "utf-8");
21968
+ return { status: "updated", port: DEFAULT_DEV_PORT };
21969
+ }
21970
+ function readPackageJson2(cwd) {
21971
+ const pkgPath = path32.join(cwd, "package.json");
21972
+ if (!fs26.existsSync(pkgPath)) return void 0;
21973
+ try {
21974
+ return JSON.parse(fs26.readFileSync(pkgPath, "utf-8"));
21975
+ } catch {
21976
+ return void 0;
21977
+ }
21978
+ }
21979
+ function isPlainNextDevScript(devScript) {
21980
+ const normalized = normalizeWhitespace(devScript);
21981
+ return normalized === "next dev" || normalized === "next dev --turbopack" || normalized === "next dev --turbo";
21982
+ }
21983
+ function normalizeWhitespace(value) {
21984
+ return value.trim().replace(/\s+/g, " ");
21985
+ }
21986
+ function detectIndent(raw) {
21987
+ const indentMatch = raw.match(/^[ \t]+(?=")/m);
21988
+ return indentMatch?.[0] ?? 2;
21989
+ }
21990
+ function isValidPort(port) {
21991
+ return Number.isInteger(port) && port > 0 && port <= 65535;
21992
+ }
21993
+
21994
+ // adapters/next/init/scaffolders/env.ts
21995
+ var LEGACY_DEFAULT_AUTH_URL = "http://localhost:3000";
21996
+ function getCoreEnvSections(databaseUrl, devPort, namespace) {
21997
+ const authSecret = crypto2.randomBytes(32).toString("base64");
21998
+ return [
21999
+ {
22000
+ header: "Database (PostgreSQL)",
22001
+ vars: [{ key: "DATABASE_URL", value: databaseUrl ?? "postgresql://..." }]
22002
+ },
22003
+ {
22004
+ header: "Authentication",
22005
+ vars: [
22006
+ { key: "BETTERSTART_AUTH_SECRET", value: authSecret },
22007
+ { key: "BETTERSTART_AUTH_URL", value: `http://localhost:${devPort}` },
22008
+ { key: "BETTERSTART_AUTH_BASE_PATH", value: `/api/${namespace}/auth` }
22009
+ ]
22010
+ }
22011
+ ];
22012
+ }
22013
+ function shouldOverwriteAuthUrl(cwd, devPort) {
22014
+ const existingAuthUrl = readEnvVar(cwd, "BETTERSTART_AUTH_URL");
22015
+ if (!existingAuthUrl) return false;
22016
+ return existingAuthUrl === LEGACY_DEFAULT_AUTH_URL && existingAuthUrl !== `http://localhost:${devPort}`;
22017
+ }
22018
+ function persistDatabaseUrl(cwd, databaseUrl) {
22019
+ appendEnvVars(
22020
+ cwd,
22021
+ [
22022
+ {
22023
+ header: "Database (PostgreSQL)",
22024
+ vars: [{ key: "DATABASE_URL", value: databaseUrl }]
22025
+ }
22026
+ ],
22027
+ /* @__PURE__ */ new Set(["DATABASE_URL"])
22028
+ );
22029
+ }
22030
+ function persistBlobReadWriteToken(cwd, token) {
22031
+ appendEnvVars(
22032
+ cwd,
22033
+ [
22034
+ {
22035
+ header: "Storage (Vercel Blob)",
22036
+ vars: [{ key: "BLOB_READ_WRITE_TOKEN", value: token }]
22037
+ }
22038
+ ],
22039
+ /* @__PURE__ */ new Set(["BLOB_READ_WRITE_TOKEN"])
22040
+ );
22041
+ }
22042
+ function scaffoldEnv(cwd, options) {
22043
+ const devPort = detectDevPort(cwd);
22044
+ const sections = getCoreEnvSections(options.databaseUrl, devPort, options.namespace ?? "admin");
22045
+ const authBasePath = `/api/${options.namespace ?? "admin"}/auth`;
22046
+ const overwrite = /* @__PURE__ */ new Set();
22047
+ if (options.databaseUrl) {
22048
+ overwrite.add("DATABASE_URL");
22049
+ }
22050
+ if (shouldOverwriteAuthUrl(cwd, devPort)) {
22051
+ overwrite.add("BETTERSTART_AUTH_URL");
22052
+ }
22053
+ if (readEnvVar(cwd, "BETTERSTART_AUTH_BASE_PATH") !== authBasePath) {
22054
+ overwrite.add("BETTERSTART_AUTH_BASE_PATH");
22055
+ }
22056
+ return appendEnvVars(cwd, sections, overwrite);
22057
+ }
22058
+
22059
+ // adapters/next/init/prompts/plugins.ts
21903
22060
  async function promptPlugins(cwd, options = {}) {
21904
22061
  const sections = [];
21905
22062
  const overwriteKeys = /* @__PURE__ */ new Set();
@@ -21938,6 +22095,8 @@ async function promptPlugins(cwd, options = {}) {
21938
22095
  const existingToken = readEnvVar(cwd, "BLOB_READ_WRITE_TOKEN")?.trim();
21939
22096
  const flow = !existingToken && options.provisionVercelBlob ? await options.provisionVercelBlob() : void 0;
21940
22097
  if (flow?.ok && flow.token) {
22098
+ persistBlobReadWriteToken(cwd, flow.token);
22099
+ p11.log.success("Saved BLOB_READ_WRITE_TOKEN to .env.local");
21941
22100
  sections.push({
21942
22101
  header: "Storage (Vercel Blob)",
21943
22102
  vars: [{ key: "BLOB_READ_WRITE_TOKEN", value: flow.token }]
@@ -22045,53 +22204,53 @@ async function promptProject(defaultName) {
22045
22204
  }
22046
22205
 
22047
22206
  // adapters/next/init/scaffolders/api-routes.ts
22048
- import path33 from "path";
22207
+ import path34 from "path";
22049
22208
 
22050
22209
  // core-engine/utils/fs.ts
22051
- import fs26 from "fs";
22052
- import path32 from "path";
22210
+ import fs27 from "fs";
22211
+ import path33 from "path";
22053
22212
  import fse from "fs-extra";
22054
22213
  function ensureDir(dirPath) {
22055
22214
  fse.ensureDirSync(dirPath);
22056
22215
  }
22057
22216
  function safeWriteFile(filePath, content, force = false) {
22058
- if (!force && fs26.existsSync(filePath)) {
22217
+ if (!force && fs27.existsSync(filePath)) {
22059
22218
  return false;
22060
22219
  }
22061
- ensureDir(path32.dirname(filePath));
22062
- fs26.writeFileSync(filePath, content, "utf-8");
22220
+ ensureDir(path33.dirname(filePath));
22221
+ fs27.writeFileSync(filePath, content, "utf-8");
22063
22222
  return true;
22064
22223
  }
22065
22224
 
22066
22225
  // adapters/next/init/scaffolders/api-routes.ts
22067
22226
  function scaffoldApiRoutes({ cwd, config }) {
22068
22227
  const created = [];
22069
- const apiDir = path33.resolve(cwd, config.paths.api);
22228
+ const apiDir = path34.resolve(cwd, config.paths.api);
22070
22229
  const namespace = config.frameworkConfig.next.namespace;
22071
22230
  function write(relPath, content) {
22072
- const fullPath = path33.join(apiDir, relPath);
22073
- ensureDir(path33.dirname(fullPath));
22231
+ const fullPath = path34.join(apiDir, relPath);
22232
+ ensureDir(path34.dirname(fullPath));
22074
22233
  const versionedContent = applyNextCacheApiForVersion(content, config.nextMajorVersion);
22075
22234
  if (safeWriteFile(fullPath, applyAdminNamespaceToContent(versionedContent, namespace))) {
22076
- created.push(path33.join(config.paths.api, relPath));
22235
+ created.push(path34.join(config.paths.api, relPath));
22077
22236
  }
22078
22237
  }
22079
- write(path33.join("auth", "[...all]", "route.ts"), readTemplate("api/auth-route.ts"));
22080
- write(path33.join("upload", "route.ts"), readTemplate("api/upload-route.ts"));
22238
+ write(path34.join("auth", "[...all]", "route.ts"), readTemplate("api/auth-route.ts"));
22239
+ write(path34.join("upload", "route.ts"), readTemplate("api/upload-route.ts"));
22081
22240
  return created;
22082
22241
  }
22083
22242
 
22084
22243
  // adapters/next/init/scaffolders/auth.ts
22085
- import path34 from "path";
22244
+ import path35 from "path";
22086
22245
  function scaffoldAuth({ cwd, config }) {
22087
22246
  const created = [];
22088
- const authDir = path34.resolve(cwd, config.paths.admin, "lib", "actions", "auth");
22247
+ const authDir = path35.resolve(cwd, config.paths.admin, "lib", "actions", "auth");
22089
22248
  const namespace = config.frameworkConfig.next.namespace;
22090
22249
  function write(filename, content) {
22091
22250
  const outputFile = applyAdminNamespaceToPath(filename, namespace);
22092
- const fullPath = path34.join(authDir, outputFile);
22251
+ const fullPath = path35.join(authDir, outputFile);
22093
22252
  if (safeWriteFile(fullPath, applyAdminNamespaceToContent(content, namespace))) {
22094
- created.push(path34.join(config.paths.admin, "lib", "actions", "auth", outputFile));
22253
+ created.push(path35.join(config.paths.admin, "lib", "actions", "auth", outputFile));
22095
22254
  }
22096
22255
  }
22097
22256
  write("server.ts", readTemplate("lib/actions/auth/auth.ts"));
@@ -22101,7 +22260,7 @@ function scaffoldAuth({ cwd, config }) {
22101
22260
  }
22102
22261
 
22103
22262
  // adapters/next/init/scaffolders/base.ts
22104
- import path35 from "path";
22263
+ import path36 from "path";
22105
22264
  function scaffoldBase({
22106
22265
  cwd,
22107
22266
  config,
@@ -22111,58 +22270,58 @@ function scaffoldBase({
22111
22270
  const adminDirs = [
22112
22271
  config.paths.admin,
22113
22272
  config.paths.schemas,
22114
- path35.join(config.paths.admin, "lib"),
22115
- path35.join(config.paths.admin, "lib", "db"),
22116
- path35.join(config.paths.admin, "lib", "db", "core"),
22117
- path35.join(config.paths.admin, "lib", "db", "migrations"),
22118
- path35.join(config.paths.admin, "lib", "actions"),
22119
- path35.join(config.paths.admin, "lib", "actions", "auth"),
22120
- path35.join(config.paths.admin, "lib", "actions", "email"),
22121
- path35.join(config.paths.admin, "lib", "actions", "forms"),
22122
- path35.join(config.paths.admin, "lib", "actions", "media"),
22123
- path35.join(config.paths.admin, "lib", "actions", "profile"),
22124
- path35.join(config.paths.admin, "lib", "actions", "storage"),
22125
- path35.join(config.paths.admin, "lib", "actions", "upload"),
22126
- path35.join(config.paths.admin, "lib", "actions", "users"),
22127
- path35.join(config.paths.admin, "hooks"),
22128
- path35.join(config.paths.admin, "components", "ui"),
22129
- path35.join(config.paths.admin, "components", "custom"),
22130
- path35.join(config.paths.admin, "components", "layouts"),
22131
- path35.join(config.paths.admin, "components", "shared"),
22132
- path35.join(config.paths.admin, "components", "shared", "data-table"),
22133
- path35.join(config.paths.admin, "components", "shared", "media"),
22134
- path35.join(config.paths.admin, "data"),
22135
- path35.join(config.paths.admin, "data", "navigation"),
22136
- path35.join(config.paths.admin, "types"),
22137
- path35.join(config.paths.admin, "utils"),
22138
- path35.join(config.paths.admin, "utils", "core")
22273
+ path36.join(config.paths.admin, "lib"),
22274
+ path36.join(config.paths.admin, "lib", "db"),
22275
+ path36.join(config.paths.admin, "lib", "db", "core"),
22276
+ path36.join(config.paths.admin, "lib", "db", "migrations"),
22277
+ path36.join(config.paths.admin, "lib", "actions"),
22278
+ path36.join(config.paths.admin, "lib", "actions", "auth"),
22279
+ path36.join(config.paths.admin, "lib", "actions", "email"),
22280
+ path36.join(config.paths.admin, "lib", "actions", "forms"),
22281
+ path36.join(config.paths.admin, "lib", "actions", "media"),
22282
+ path36.join(config.paths.admin, "lib", "actions", "profile"),
22283
+ path36.join(config.paths.admin, "lib", "actions", "storage"),
22284
+ path36.join(config.paths.admin, "lib", "actions", "upload"),
22285
+ path36.join(config.paths.admin, "lib", "actions", "users"),
22286
+ path36.join(config.paths.admin, "hooks"),
22287
+ path36.join(config.paths.admin, "components", "ui"),
22288
+ path36.join(config.paths.admin, "components", "custom"),
22289
+ path36.join(config.paths.admin, "components", "layouts"),
22290
+ path36.join(config.paths.admin, "components", "shared"),
22291
+ path36.join(config.paths.admin, "components", "shared", "data-table"),
22292
+ path36.join(config.paths.admin, "components", "shared", "media"),
22293
+ path36.join(config.paths.admin, "data"),
22294
+ path36.join(config.paths.admin, "data", "navigation"),
22295
+ path36.join(config.paths.admin, "types"),
22296
+ path36.join(config.paths.admin, "utils"),
22297
+ path36.join(config.paths.admin, "utils", "core")
22139
22298
  ];
22140
22299
  if (includeEmailTemplatesDir) {
22141
- adminDirs.push(path35.join(config.paths.admin, "components", "email-templates"));
22300
+ adminDirs.push(path36.join(config.paths.admin, "components", "email-templates"));
22142
22301
  }
22143
22302
  for (const dir of adminDirs) {
22144
- ensureDir(path35.resolve(cwd, dir));
22303
+ ensureDir(path36.resolve(cwd, dir));
22145
22304
  }
22146
- const adminDir = path35.dirname(config.paths.pages);
22305
+ const adminDir = path36.dirname(config.paths.pages);
22147
22306
  const appDirs = [
22148
22307
  config.paths.pages,
22149
22308
  config.paths.login,
22150
22309
  config.paths.api,
22151
- path35.join(adminDir, "(account)", "profile")
22310
+ path36.join(adminDir, "(account)", "profile")
22152
22311
  ];
22153
22312
  for (const dir of appDirs) {
22154
- ensureDir(path35.resolve(cwd, dir));
22313
+ ensureDir(path36.resolve(cwd, dir));
22155
22314
  }
22156
22315
  const configContent = serializeConfig(config);
22157
- if (safeWriteFile(path35.resolve(cwd, "admin.config.ts"), configContent)) {
22316
+ if (safeWriteFile(path36.resolve(cwd, "admin.config.ts"), configContent)) {
22158
22317
  created.push("admin.config.ts");
22159
22318
  }
22160
22319
  return created;
22161
22320
  }
22162
22321
 
22163
22322
  // adapters/next/init/scaffolders/biome.ts
22164
- import fs27 from "fs";
22165
- import path36 from "path";
22323
+ import fs28 from "fs";
22324
+ import path37 from "path";
22166
22325
  function scaffoldBiome(cwd, linter) {
22167
22326
  if (linter.type !== "none") {
22168
22327
  return {
@@ -22170,8 +22329,8 @@ function scaffoldBiome(cwd, linter) {
22170
22329
  skippedReason: `${linter.type} already configured (${linter.configFile})`
22171
22330
  };
22172
22331
  }
22173
- const configPath = path36.join(cwd, "biome.json");
22174
- if (fs27.existsSync(configPath)) {
22332
+ const configPath = path37.join(cwd, "biome.json");
22333
+ if (fs28.existsSync(configPath)) {
22175
22334
  return { installed: false, skippedReason: "biome.json already exists" };
22176
22335
  }
22177
22336
  const config = {
@@ -22234,13 +22393,13 @@ function scaffoldBiome(cwd, linter) {
22234
22393
  ]
22235
22394
  }
22236
22395
  };
22237
- fs27.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
22396
+ fs28.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
22238
22397
  `, "utf-8");
22239
22398
  return { installed: true, skippedReason: null };
22240
22399
  }
22241
22400
 
22242
22401
  // adapters/next/init/scaffolders/components.ts
22243
- import path37 from "path";
22402
+ import path38 from "path";
22244
22403
 
22245
22404
  // adapters/next/init/templates/app/admin.ts
22246
22405
  function adminDataTemplate(projectName, namespaceValue = DEFAULT_ADMIN_NAMESPACE) {
@@ -22252,17 +22411,17 @@ function adminDataTemplate(projectName, namespaceValue = DEFAULT_ADMIN_NAMESPACE
22252
22411
  }
22253
22412
 
22254
22413
  // adapters/next/init/scaffolders/components.ts
22255
- import fs28 from "fs-extra";
22414
+ import fs29 from "fs-extra";
22256
22415
  function scaffoldComponents({ cwd, config }) {
22257
- const admin = path37.resolve(cwd, config.paths.admin);
22416
+ const admin = path38.resolve(cwd, config.paths.admin);
22258
22417
  const created = [];
22259
22418
  const namespace = config.frameworkConfig.next.namespace;
22260
22419
  function write(relPath, content) {
22261
22420
  const namespacedRelPath = applyAdminNamespaceToPath(relPath, namespace);
22262
- const fullPath = path37.join(admin, namespacedRelPath);
22421
+ const fullPath = path38.join(admin, namespacedRelPath);
22263
22422
  const versionedContent = applyNextCacheApiForVersion(content, config.nextMajorVersion);
22264
22423
  if (safeWriteFile(fullPath, applyAdminNamespaceToContent(versionedContent, namespace))) {
22265
- created.push(path37.join(config.paths.admin, namespacedRelPath));
22424
+ created.push(path38.join(config.paths.admin, namespacedRelPath));
22266
22425
  }
22267
22426
  }
22268
22427
  write("admin-globals.css", readTemplate("admin-globals.css"));
@@ -22695,49 +22854,49 @@ function copyCustomTemplates(cwd, config) {
22695
22854
  }
22696
22855
  function copyStaticComponentTemplates(cwd, config, sourceDirectory, targetDirectory) {
22697
22856
  const created = [];
22698
- const destDir = path37.resolve(cwd, config.paths.admin, "components", targetDirectory);
22857
+ const destDir = path38.resolve(cwd, config.paths.admin, "components", targetDirectory);
22699
22858
  const srcDir = resolveCliAssetPath("shared-assets", "react-admin", sourceDirectory);
22700
- if (!fs28.existsSync(srcDir)) {
22859
+ if (!fs29.existsSync(srcDir)) {
22701
22860
  return created;
22702
22861
  }
22703
- fs28.ensureDirSync(destDir);
22862
+ fs29.ensureDirSync(destDir);
22704
22863
  if (sourceDirectory === "custom") {
22705
22864
  copyStaticComponentTemplatesRecursive(
22706
22865
  srcDir,
22707
22866
  destDir,
22708
22867
  "",
22709
- path37.join(config.paths.admin, "components", targetDirectory),
22868
+ path38.join(config.paths.admin, "components", targetDirectory),
22710
22869
  config.frameworkConfig.next.namespace,
22711
22870
  created
22712
22871
  );
22713
22872
  return created;
22714
22873
  }
22715
- const files = fs28.readdirSync(srcDir, { withFileTypes: true }).filter(
22874
+ const files = fs29.readdirSync(srcDir, { withFileTypes: true }).filter(
22716
22875
  (entry) => entry.isFile() && (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts"))
22717
22876
  ).map((entry) => entry.name);
22718
22877
  for (const file of files) {
22719
22878
  const namespacedFile = applyAdminNamespaceToPath(file, config.frameworkConfig.next.namespace);
22720
- const destPath = path37.join(destDir, namespacedFile);
22721
- if (!fs28.existsSync(destPath)) {
22722
- const content = fs28.readFileSync(path37.join(srcDir, file), "utf-8");
22723
- fs28.writeFileSync(
22879
+ const destPath = path38.join(destDir, namespacedFile);
22880
+ if (!fs29.existsSync(destPath)) {
22881
+ const content = fs29.readFileSync(path38.join(srcDir, file), "utf-8");
22882
+ fs29.writeFileSync(
22724
22883
  destPath,
22725
22884
  applyAdminNamespaceToContent(content, config.frameworkConfig.next.namespace),
22726
22885
  "utf-8"
22727
22886
  );
22728
- created.push(path37.join(config.paths.admin, "components", targetDirectory, namespacedFile));
22887
+ created.push(path38.join(config.paths.admin, "components", targetDirectory, namespacedFile));
22729
22888
  }
22730
22889
  }
22731
22890
  return created;
22732
22891
  }
22733
22892
  function copyStaticComponentTemplatesRecursive(srcDir, destDir, relBase, createdPrefix, namespace, created) {
22734
- const entries = fs28.readdirSync(srcDir, { withFileTypes: true });
22893
+ const entries = fs29.readdirSync(srcDir, { withFileTypes: true });
22735
22894
  for (const entry of entries) {
22736
- const rel = path37.join(relBase, entry.name);
22737
- const srcPath = path37.join(srcDir, entry.name);
22738
- const destPath = path37.join(destDir, entry.name);
22895
+ const rel = path38.join(relBase, entry.name);
22896
+ const srcPath = path38.join(srcDir, entry.name);
22897
+ const destPath = path38.join(destDir, entry.name);
22739
22898
  if (entry.isDirectory()) {
22740
- const namespacedDestPath2 = path37.join(
22899
+ const namespacedDestPath2 = path38.join(
22741
22900
  destDir,
22742
22901
  applyAdminNamespaceToPath(entry.name, namespace)
22743
22902
  );
@@ -22755,37 +22914,37 @@ function copyStaticComponentTemplatesRecursive(srcDir, destDir, relBase, created
22755
22914
  continue;
22756
22915
  }
22757
22916
  const namespacedRel = applyAdminNamespaceToPath(rel, namespace);
22758
- const namespacedDestPath = path37.join(
22759
- path37.dirname(destPath),
22917
+ const namespacedDestPath = path38.join(
22918
+ path38.dirname(destPath),
22760
22919
  applyAdminNamespaceToPath(entry.name, namespace)
22761
22920
  );
22762
- if (!fs28.existsSync(namespacedDestPath)) {
22763
- fs28.ensureDirSync(path37.dirname(namespacedDestPath));
22764
- const content = fs28.readFileSync(srcPath, "utf-8");
22765
- fs28.writeFileSync(
22921
+ if (!fs29.existsSync(namespacedDestPath)) {
22922
+ fs29.ensureDirSync(path38.dirname(namespacedDestPath));
22923
+ const content = fs29.readFileSync(srcPath, "utf-8");
22924
+ fs29.writeFileSync(
22766
22925
  namespacedDestPath,
22767
22926
  applyAdminNamespaceToContent(content, namespace),
22768
22927
  "utf-8"
22769
22928
  );
22770
- created.push(path37.join(createdPrefix, namespacedRel));
22929
+ created.push(path38.join(createdPrefix, namespacedRel));
22771
22930
  }
22772
22931
  }
22773
22932
  }
22774
22933
  function copySchemaMetaschema(cwd, config) {
22775
22934
  const created = [];
22776
22935
  const srcPath = resolveCliAssetPath("shared-assets", "react-admin", "schema.json");
22777
- const destPath = path37.resolve(cwd, config.paths.schemas, "schema.json");
22778
- if (fs28.existsSync(srcPath) && !fs28.existsSync(destPath)) {
22779
- fs28.ensureDirSync(path37.dirname(destPath));
22780
- fs28.copyFileSync(srcPath, destPath);
22781
- created.push(path37.join(config.paths.schemas, "schema.json"));
22936
+ const destPath = path38.resolve(cwd, config.paths.schemas, "schema.json");
22937
+ if (fs29.existsSync(srcPath) && !fs29.existsSync(destPath)) {
22938
+ fs29.ensureDirSync(path38.dirname(destPath));
22939
+ fs29.copyFileSync(srcPath, destPath);
22940
+ created.push(path38.join(config.paths.schemas, "schema.json"));
22782
22941
  }
22783
22942
  return created;
22784
22943
  }
22785
22944
 
22786
22945
  // adapters/next/init/scaffolders/core-schemas.ts
22787
- import fs29 from "fs";
22788
- import path38 from "path";
22946
+ import fs30 from "fs";
22947
+ import path39 from "path";
22789
22948
 
22790
22949
  // adapters/next/init/templates/core/default-settings.ts
22791
22950
  function defaultSettingsSchema() {
@@ -22865,9 +23024,9 @@ function scaffoldCoreSchemas({
22865
23024
  };
22866
23025
  const schemaContent = defaultSettingsSchema();
22867
23026
  const schemaName = "settings";
22868
- const schemaPath = path38.join(cwd, config.paths.schemas, `${schemaName}.json`);
22869
- fs29.mkdirSync(path38.dirname(schemaPath), { recursive: true });
22870
- fs29.writeFileSync(schemaPath, schemaContent, "utf-8");
23027
+ const schemaPath = path39.join(cwd, config.paths.schemas, `${schemaName}.json`);
23028
+ fs30.mkdirSync(path39.dirname(schemaPath), { recursive: true });
23029
+ fs30.writeFileSync(schemaPath, schemaContent, "utf-8");
22871
23030
  result.schemas.push(`${schemaName}.json`);
22872
23031
  ensureSnapshotGitFiles(cwd);
22873
23032
  try {
@@ -22912,21 +23071,21 @@ function scaffoldCoreSchemas({
22912
23071
  }
22913
23072
 
22914
23073
  // adapters/next/init/scaffolders/database.ts
22915
- import path39 from "path";
23074
+ import path40 from "path";
22916
23075
  function scaffoldDatabase({ cwd, config }) {
22917
23076
  const created = [];
22918
- const dbDir = path39.resolve(cwd, config.paths.admin, "lib", "db");
23077
+ const dbDir = path40.resolve(cwd, config.paths.admin, "lib", "db");
22919
23078
  const namespace = config.frameworkConfig.next.namespace;
22920
23079
  function write(filename, content) {
22921
- const fullPath = path39.join(dbDir, filename);
23080
+ const fullPath = path40.join(dbDir, filename);
22922
23081
  if (safeWriteFile(fullPath, applyAdminNamespaceToContent(content, namespace))) {
22923
- created.push(path39.join(config.paths.admin, "lib", "db", filename));
23082
+ created.push(path40.join(config.paths.admin, "lib", "db", filename));
22924
23083
  }
22925
23084
  }
22926
23085
  write("client.ts", readTemplate("lib/db/client.ts"));
22927
23086
  write("core/schema.ts", readTemplate("lib/db/core/schema.ts"));
22928
23087
  write("schema.ts", readTemplate("lib/db/schema.ts"));
22929
- const drizzleConfigPath = path39.resolve(cwd, "drizzle.config.ts");
23088
+ const drizzleConfigPath = path40.resolve(cwd, "drizzle.config.ts");
22930
23089
  if (safeWriteFile(
22931
23090
  drizzleConfigPath,
22932
23091
  applyAdminNamespaceToContent(readTemplate("drizzle.config.ts"), namespace)
@@ -22936,139 +23095,6 @@ function scaffoldDatabase({ cwd, config }) {
22936
23095
  return created;
22937
23096
  }
22938
23097
 
22939
- // adapters/next/init/scaffolders/dev-server.ts
22940
- import fs30 from "fs";
22941
- import path40 from "path";
22942
- var DEFAULT_DEV_PORT = 4242;
22943
- var DEV_PORT_FLAG_PATTERN = /(?:^|\s)(?:--port(?:=|\s+)|-p(?:=|\s+)?)(\d+)(?=\s|$)/;
22944
- var DEV_PORT_ENV_PATTERN = /(?:^|\s)PORT=(\d+)(?=\s|$)/;
22945
- function detectDevPort(cwd) {
22946
- const pkg = readPackageJson2(cwd);
22947
- if (!pkg) return DEFAULT_DEV_PORT;
22948
- return detectDevPortFromScript(pkg.scripts?.dev) ?? DEFAULT_DEV_PORT;
22949
- }
22950
- function detectDevPortFromScript(devScript) {
22951
- if (typeof devScript !== "string") return void 0;
22952
- const trimmed = devScript.trim();
22953
- if (trimmed.length === 0) return void 0;
22954
- const flagMatch = trimmed.match(DEV_PORT_FLAG_PATTERN);
22955
- const envMatch = trimmed.match(DEV_PORT_ENV_PATTERN);
22956
- const rawPort = flagMatch?.[1] ?? envMatch?.[1];
22957
- if (!rawPort) return void 0;
22958
- const port = Number.parseInt(rawPort, 10);
22959
- return isValidPort(port) ? port : void 0;
22960
- }
22961
- function ensureDefaultDevScriptPort(cwd) {
22962
- const pkgPath = path40.join(cwd, "package.json");
22963
- if (!fs30.existsSync(pkgPath)) {
22964
- return { status: "missing-package", port: DEFAULT_DEV_PORT };
22965
- }
22966
- const raw = fs30.readFileSync(pkgPath, "utf-8");
22967
- let pkg;
22968
- try {
22969
- pkg = JSON.parse(raw);
22970
- } catch {
22971
- return { status: "missing-package", port: DEFAULT_DEV_PORT };
22972
- }
22973
- const devScript = pkg.scripts?.dev;
22974
- if (typeof devScript !== "string" || devScript.trim().length === 0) {
22975
- return { status: "missing-script", port: DEFAULT_DEV_PORT };
22976
- }
22977
- const explicitPort = detectDevPortFromScript(devScript);
22978
- if (explicitPort) {
22979
- return { status: "preserved", port: explicitPort };
22980
- }
22981
- if (!isPlainNextDevScript(devScript)) {
22982
- return { status: "skipped", port: DEFAULT_DEV_PORT };
22983
- }
22984
- const normalizedScript = `${normalizeWhitespace(devScript)} --port ${DEFAULT_DEV_PORT}`;
22985
- pkg.scripts = {
22986
- ...pkg.scripts,
22987
- dev: normalizedScript
22988
- };
22989
- const newline = raw.includes("\r\n") ? "\r\n" : "\n";
22990
- const indent = detectIndent(raw);
22991
- fs30.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, indent)}${newline}`, "utf-8");
22992
- return { status: "updated", port: DEFAULT_DEV_PORT };
22993
- }
22994
- function readPackageJson2(cwd) {
22995
- const pkgPath = path40.join(cwd, "package.json");
22996
- if (!fs30.existsSync(pkgPath)) return void 0;
22997
- try {
22998
- return JSON.parse(fs30.readFileSync(pkgPath, "utf-8"));
22999
- } catch {
23000
- return void 0;
23001
- }
23002
- }
23003
- function isPlainNextDevScript(devScript) {
23004
- const normalized = normalizeWhitespace(devScript);
23005
- return normalized === "next dev" || normalized === "next dev --turbopack" || normalized === "next dev --turbo";
23006
- }
23007
- function normalizeWhitespace(value) {
23008
- return value.trim().replace(/\s+/g, " ");
23009
- }
23010
- function detectIndent(raw) {
23011
- const indentMatch = raw.match(/^[ \t]+(?=")/m);
23012
- return indentMatch?.[0] ?? 2;
23013
- }
23014
- function isValidPort(port) {
23015
- return Number.isInteger(port) && port > 0 && port <= 65535;
23016
- }
23017
-
23018
- // adapters/next/init/scaffolders/env.ts
23019
- import crypto2 from "crypto";
23020
- var LEGACY_DEFAULT_AUTH_URL = "http://localhost:3000";
23021
- function getCoreEnvSections(databaseUrl, devPort, namespace) {
23022
- const authSecret = crypto2.randomBytes(32).toString("base64");
23023
- return [
23024
- {
23025
- header: "Database (PostgreSQL)",
23026
- vars: [{ key: "DATABASE_URL", value: databaseUrl ?? "postgresql://..." }]
23027
- },
23028
- {
23029
- header: "Authentication",
23030
- vars: [
23031
- { key: "BETTERSTART_AUTH_SECRET", value: authSecret },
23032
- { key: "BETTERSTART_AUTH_URL", value: `http://localhost:${devPort}` },
23033
- { key: "BETTERSTART_AUTH_BASE_PATH", value: `/api/${namespace}/auth` }
23034
- ]
23035
- }
23036
- ];
23037
- }
23038
- function shouldOverwriteAuthUrl(cwd, devPort) {
23039
- const existingAuthUrl = readEnvVar(cwd, "BETTERSTART_AUTH_URL");
23040
- if (!existingAuthUrl) return false;
23041
- return existingAuthUrl === LEGACY_DEFAULT_AUTH_URL && existingAuthUrl !== `http://localhost:${devPort}`;
23042
- }
23043
- function persistDatabaseUrl(cwd, databaseUrl) {
23044
- appendEnvVars(
23045
- cwd,
23046
- [
23047
- {
23048
- header: "Database (PostgreSQL)",
23049
- vars: [{ key: "DATABASE_URL", value: databaseUrl }]
23050
- }
23051
- ],
23052
- /* @__PURE__ */ new Set(["DATABASE_URL"])
23053
- );
23054
- }
23055
- function scaffoldEnv(cwd, options) {
23056
- const devPort = detectDevPort(cwd);
23057
- const sections = getCoreEnvSections(options.databaseUrl, devPort, options.namespace ?? "admin");
23058
- const authBasePath = `/api/${options.namespace ?? "admin"}/auth`;
23059
- const overwrite = /* @__PURE__ */ new Set();
23060
- if (options.databaseUrl) {
23061
- overwrite.add("DATABASE_URL");
23062
- }
23063
- if (shouldOverwriteAuthUrl(cwd, devPort)) {
23064
- overwrite.add("BETTERSTART_AUTH_URL");
23065
- }
23066
- if (readEnvVar(cwd, "BETTERSTART_AUTH_BASE_PATH") !== authBasePath) {
23067
- overwrite.add("BETTERSTART_AUTH_BASE_PATH");
23068
- }
23069
- return appendEnvVars(cwd, sections, overwrite);
23070
- }
23071
-
23072
23098
  // adapters/next/init/scaffolders/layout.ts
23073
23099
  import path41 from "path";
23074
23100
  function scaffoldLayout({ cwd, config }) {
@@ -24217,10 +24243,10 @@ async function ensureVercelAuth(runner, cwd, options) {
24217
24243
  if (existing.authed) {
24218
24244
  if (options.quietIfAuthed) {
24219
24245
  checkSpinner.clear();
24220
- } else {
24221
- checkSpinner.stop(signedInMessage(existing.username));
24246
+ return existing;
24222
24247
  }
24223
- return existing;
24248
+ checkSpinner.stop(signedInMessage(existing.username));
24249
+ return { ...existing, dismissNote: eraseClackLine };
24224
24250
  }
24225
24251
  if (env.VERCEL_TOKEN) {
24226
24252
  checkSpinner.stop("Could not verify VERCEL_TOKEN with Vercel");
@@ -24245,7 +24271,7 @@ async function ensureVercelAuth(runner, cwd, options) {
24245
24271
  const after = await checkVercelAuth(runner, cwd, env);
24246
24272
  if (after.authed) {
24247
24273
  verifySpinner.stop(signedInMessage(after.username));
24248
- return after;
24274
+ return { ...after, dismissNote: eraseClackLine };
24249
24275
  }
24250
24276
  verifySpinner.stop("Could not verify your Vercel sign-in");
24251
24277
  return { authed: false, reason: "login-failed" };
@@ -24692,7 +24718,7 @@ async function provisionNeonInteractive(runner, cwd, options) {
24692
24718
  if (!add.success) {
24693
24719
  return { failure: add.timedOut ? "timeout" : "provision-failed" };
24694
24720
  }
24695
- return {};
24721
+ return { usedTerminalFallback: true };
24696
24722
  }
24697
24723
  async function provisionNeon(runner, cwd, options) {
24698
24724
  const provisionSpinner = spinner2();
@@ -24755,7 +24781,7 @@ async function runVercelNeonFlow(options) {
24755
24781
  p16.log.warn(authFailureMessage(auth.reason));
24756
24782
  return { ok: false };
24757
24783
  }
24758
- await ensureLinkedProject(runner, options.cwd, options.projectName, env);
24784
+ const linkLinePrinted = await ensureLinkedProject(runner, options.cwd, options.projectName, env);
24759
24785
  const neon = await provisionNeonForMode(runner, options);
24760
24786
  if (neon.failure) {
24761
24787
  p16.log.warn(neonFailureMessage(neon.failure));
@@ -24763,11 +24789,13 @@ async function runVercelNeonFlow(options) {
24763
24789
  return { ok: false };
24764
24790
  }
24765
24791
  const databaseUrl = await pullNeonDatabaseUrl(runner, options.cwd, env);
24792
+ const dismissSignedInNote = databaseUrl && auth.dismissNote && !neon.usedTerminalFallback ? () => auth.dismissNote?.({ linesBelow: linkLinePrinted ? 1 : 0 }) : void 0;
24766
24793
  return {
24767
24794
  ok: true,
24768
24795
  databaseUrl,
24769
24796
  dashboardUrl: neon.dashboardUrl,
24770
- resourceUrl: neon.resourceUrl
24797
+ resourceUrl: neon.resourceUrl,
24798
+ dismissSignedInNote
24771
24799
  };
24772
24800
  } catch (error) {
24773
24801
  p16.log.warn(
@@ -24881,13 +24909,14 @@ function printManualDeployHint() {
24881
24909
  p16.log.info(`You can deploy manually: ${pc6.cyan("vercel deploy --prod")}`);
24882
24910
  }
24883
24911
  async function ensureLinkedProject(runner, cwd, projectName, env) {
24884
- if (readLinkedProjectId(cwd)) return;
24912
+ if (readLinkedProjectId(cwd)) return false;
24885
24913
  const projectSpinner = spinner2();
24886
24914
  projectSpinner.start(`Creating a Vercel project ${pc6.cyan(projectName)}`);
24887
24915
  const project2 = await createVercelProject(runner, cwd, projectName, env);
24888
24916
  projectSpinner.stop(
24889
24917
  project2.linked ? `Linked Vercel project ${pc6.cyan(project2.name)}` : `Using Vercel project ${pc6.cyan(project2.name)}`
24890
24918
  );
24919
+ return true;
24891
24920
  }
24892
24921
  async function pullNeonDatabaseUrl(runner, cwd, env) {
24893
24922
  const neonSpinner = spinner2();
@@ -25523,6 +25552,7 @@ async function runInitCommand(name, options) {
25523
25552
  isFreshProject = true;
25524
25553
  }
25525
25554
  let databaseUrl;
25555
+ let dismissVercelSignedInNote;
25526
25556
  const existingDbUrl = readExistingDbUrl(cwd);
25527
25557
  if (options.yes) {
25528
25558
  if (options.databaseUrl) {
@@ -25546,10 +25576,9 @@ async function runInitCommand(name, options) {
25546
25576
  if (flow.ok && flow.databaseUrl) {
25547
25577
  databaseUrl = flow.databaseUrl;
25548
25578
  persistDatabaseUrl(cwd, databaseUrl);
25549
- p17.log.success(`Saved DATABASE_URL to ${pc7.cyan(".env.local")}`);
25550
25579
  } else if (flow.ok) {
25551
25580
  p17.log.warn("Created a Neon database, but DATABASE_URL could not be retrieved from Vercel.");
25552
- const resourceUrl = flow.resourceUrl ?? flow.dashboardUrl;
25581
+ const resourceUrl = flow.dashboardUrl ?? flow.resourceUrl;
25553
25582
  if (resourceUrl) {
25554
25583
  p17.log.info(
25555
25584
  `Open ${pc7.cyan(resourceUrl)} to copy DATABASE_URL, then rerun ${pc7.cyan("betterstart init --database-url <url> --yes")}.`
@@ -25580,12 +25609,11 @@ async function runInitCommand(name, options) {
25580
25609
  if (flow.ok && flow.databaseUrl) {
25581
25610
  databaseUrl = flow.databaseUrl;
25582
25611
  persistDatabaseUrl(cwd, databaseUrl);
25583
- p17.log.success(`Saved DATABASE_URL to ${pc7.cyan(".env.local")}`);
25612
+ dismissVercelSignedInNote = flow.dismissSignedInNote;
25584
25613
  } else if (flow.ok) {
25585
- openBrowserVercelNeonResource(flow.resourceUrl ?? flow.dashboardUrl);
25614
+ openBrowserVercelNeonResource(flow.dashboardUrl ?? flow.resourceUrl);
25586
25615
  databaseUrl = await promptConnectionString();
25587
25616
  persistDatabaseUrl(cwd, databaseUrl);
25588
- p17.log.success(`Saved DATABASE_URL to ${pc7.cyan(".env.local")}`);
25589
25617
  } else {
25590
25618
  p17.log.info("Falling back to a manual database connection string.");
25591
25619
  openBrowserVercelNeon();
@@ -25627,6 +25655,8 @@ async function runInitCommand(name, options) {
25627
25655
  env: process.env
25628
25656
  });
25629
25657
  if (flow.ok && flow.token) {
25658
+ persistBlobReadWriteToken(cwd, flow.token);
25659
+ p17.log.success(`Saved BLOB_READ_WRITE_TOKEN to ${pc7.cyan(".env.local")}`);
25630
25660
  collectedIntegrationConfig.sections.push({
25631
25661
  header: "Storage (Vercel Blob)",
25632
25662
  vars: [{ key: "BLOB_READ_WRITE_TOKEN", value: flow.token }]
@@ -25646,6 +25676,7 @@ async function runInitCommand(name, options) {
25646
25676
  } else if (options.yes) {
25647
25677
  pluginSelection = { plugins: [], integrations: [], storage: "local" };
25648
25678
  } else {
25679
+ dismissVercelSignedInNote?.();
25649
25680
  const promptResult = await promptPlugins(cwd, {
25650
25681
  provisionVercelBlob: vercelBlobAllowed ? () => runVercelBlobFlow({ cwd, projectName, interactive: true, env: process.env }) : void 0
25651
25682
  });
@@ -25828,8 +25859,9 @@ async function runInitCommand(name, options) {
25828
25859
  config: resolvedPluginInstallResult.config,
25829
25860
  pm,
25830
25861
  integrationIds: pluginSelection.integrations,
25831
- // Secrets were already collected up front and written to .env.local,
25832
- // so the install runs without prompting (rule: no mid-scaffold input).
25862
+ // Secrets were already collected up front and persisted/written to
25863
+ // .env.local, so the install runs without prompting (rule: no
25864
+ // mid-scaffold input).
25833
25865
  interactive: false,
25834
25866
  includeBiome: project2.linter.type === "none"
25835
25867
  });