betterstart-cli 0.0.30 → 0.0.32

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
@@ -21900,6 +21900,155 @@ function openBrowser(url) {
21900
21900
  // adapters/next/init/prompts/plugins.ts
21901
21901
  import { styleText } from "util";
21902
21902
  import * as p11 from "@clack/prompts";
21903
+
21904
+ // adapters/next/init/scaffolders/env.ts
21905
+ import crypto2 from "crypto";
21906
+
21907
+ // adapters/next/init/scaffolders/dev-server.ts
21908
+ import fs26 from "fs";
21909
+ import path32 from "path";
21910
+ var DEFAULT_DEV_PORT = 4242;
21911
+ var DEV_PORT_FLAG_PATTERN = /(?:^|\s)(?:--port(?:=|\s+)|-p(?:=|\s+)?)(\d+)(?=\s|$)/;
21912
+ var DEV_PORT_ENV_PATTERN = /(?:^|\s)PORT=(\d+)(?=\s|$)/;
21913
+ function detectDevPort(cwd) {
21914
+ const pkg = readPackageJson2(cwd);
21915
+ if (!pkg) return DEFAULT_DEV_PORT;
21916
+ return detectDevPortFromScript(pkg.scripts?.dev) ?? DEFAULT_DEV_PORT;
21917
+ }
21918
+ function detectDevPortFromScript(devScript) {
21919
+ if (typeof devScript !== "string") return void 0;
21920
+ const trimmed = devScript.trim();
21921
+ if (trimmed.length === 0) return void 0;
21922
+ const flagMatch = trimmed.match(DEV_PORT_FLAG_PATTERN);
21923
+ const envMatch = trimmed.match(DEV_PORT_ENV_PATTERN);
21924
+ const rawPort = flagMatch?.[1] ?? envMatch?.[1];
21925
+ if (!rawPort) return void 0;
21926
+ const port = Number.parseInt(rawPort, 10);
21927
+ return isValidPort(port) ? port : void 0;
21928
+ }
21929
+ function ensureDefaultDevScriptPort(cwd) {
21930
+ const pkgPath = path32.join(cwd, "package.json");
21931
+ if (!fs26.existsSync(pkgPath)) {
21932
+ return { status: "missing-package", port: DEFAULT_DEV_PORT };
21933
+ }
21934
+ const raw = fs26.readFileSync(pkgPath, "utf-8");
21935
+ let pkg;
21936
+ try {
21937
+ pkg = JSON.parse(raw);
21938
+ } catch {
21939
+ return { status: "missing-package", port: DEFAULT_DEV_PORT };
21940
+ }
21941
+ const devScript = pkg.scripts?.dev;
21942
+ if (typeof devScript !== "string" || devScript.trim().length === 0) {
21943
+ return { status: "missing-script", port: DEFAULT_DEV_PORT };
21944
+ }
21945
+ const explicitPort = detectDevPortFromScript(devScript);
21946
+ if (explicitPort) {
21947
+ return { status: "preserved", port: explicitPort };
21948
+ }
21949
+ if (!isPlainNextDevScript(devScript)) {
21950
+ return { status: "skipped", port: DEFAULT_DEV_PORT };
21951
+ }
21952
+ const normalizedScript = `${normalizeWhitespace(devScript)} --port ${DEFAULT_DEV_PORT}`;
21953
+ pkg.scripts = {
21954
+ ...pkg.scripts,
21955
+ dev: normalizedScript
21956
+ };
21957
+ const newline = raw.includes("\r\n") ? "\r\n" : "\n";
21958
+ const indent = detectIndent(raw);
21959
+ fs26.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, indent)}${newline}`, "utf-8");
21960
+ return { status: "updated", port: DEFAULT_DEV_PORT };
21961
+ }
21962
+ function readPackageJson2(cwd) {
21963
+ const pkgPath = path32.join(cwd, "package.json");
21964
+ if (!fs26.existsSync(pkgPath)) return void 0;
21965
+ try {
21966
+ return JSON.parse(fs26.readFileSync(pkgPath, "utf-8"));
21967
+ } catch {
21968
+ return void 0;
21969
+ }
21970
+ }
21971
+ function isPlainNextDevScript(devScript) {
21972
+ const normalized = normalizeWhitespace(devScript);
21973
+ return normalized === "next dev" || normalized === "next dev --turbopack" || normalized === "next dev --turbo";
21974
+ }
21975
+ function normalizeWhitespace(value) {
21976
+ return value.trim().replace(/\s+/g, " ");
21977
+ }
21978
+ function detectIndent(raw) {
21979
+ const indentMatch = raw.match(/^[ \t]+(?=")/m);
21980
+ return indentMatch?.[0] ?? 2;
21981
+ }
21982
+ function isValidPort(port) {
21983
+ return Number.isInteger(port) && port > 0 && port <= 65535;
21984
+ }
21985
+
21986
+ // adapters/next/init/scaffolders/env.ts
21987
+ var LEGACY_DEFAULT_AUTH_URL = "http://localhost:3000";
21988
+ function getCoreEnvSections(databaseUrl, devPort, namespace) {
21989
+ const authSecret = crypto2.randomBytes(32).toString("base64");
21990
+ return [
21991
+ {
21992
+ header: "Database (PostgreSQL)",
21993
+ vars: [{ key: "DATABASE_URL", value: databaseUrl ?? "postgresql://..." }]
21994
+ },
21995
+ {
21996
+ header: "Authentication",
21997
+ vars: [
21998
+ { key: "BETTERSTART_AUTH_SECRET", value: authSecret },
21999
+ { key: "BETTERSTART_AUTH_URL", value: `http://localhost:${devPort}` },
22000
+ { key: "BETTERSTART_AUTH_BASE_PATH", value: `/api/${namespace}/auth` }
22001
+ ]
22002
+ }
22003
+ ];
22004
+ }
22005
+ function shouldOverwriteAuthUrl(cwd, devPort) {
22006
+ const existingAuthUrl = readEnvVar(cwd, "BETTERSTART_AUTH_URL");
22007
+ if (!existingAuthUrl) return false;
22008
+ return existingAuthUrl === LEGACY_DEFAULT_AUTH_URL && existingAuthUrl !== `http://localhost:${devPort}`;
22009
+ }
22010
+ function persistDatabaseUrl(cwd, databaseUrl) {
22011
+ appendEnvVars(
22012
+ cwd,
22013
+ [
22014
+ {
22015
+ header: "Database (PostgreSQL)",
22016
+ vars: [{ key: "DATABASE_URL", value: databaseUrl }]
22017
+ }
22018
+ ],
22019
+ /* @__PURE__ */ new Set(["DATABASE_URL"])
22020
+ );
22021
+ }
22022
+ function persistBlobReadWriteToken(cwd, token) {
22023
+ appendEnvVars(
22024
+ cwd,
22025
+ [
22026
+ {
22027
+ header: "Storage (Vercel Blob)",
22028
+ vars: [{ key: "BLOB_READ_WRITE_TOKEN", value: token }]
22029
+ }
22030
+ ],
22031
+ /* @__PURE__ */ new Set(["BLOB_READ_WRITE_TOKEN"])
22032
+ );
22033
+ }
22034
+ function scaffoldEnv(cwd, options) {
22035
+ const devPort = detectDevPort(cwd);
22036
+ const sections = getCoreEnvSections(options.databaseUrl, devPort, options.namespace ?? "admin");
22037
+ const authBasePath = `/api/${options.namespace ?? "admin"}/auth`;
22038
+ const overwrite = /* @__PURE__ */ new Set();
22039
+ if (options.databaseUrl) {
22040
+ overwrite.add("DATABASE_URL");
22041
+ }
22042
+ if (shouldOverwriteAuthUrl(cwd, devPort)) {
22043
+ overwrite.add("BETTERSTART_AUTH_URL");
22044
+ }
22045
+ if (readEnvVar(cwd, "BETTERSTART_AUTH_BASE_PATH") !== authBasePath) {
22046
+ overwrite.add("BETTERSTART_AUTH_BASE_PATH");
22047
+ }
22048
+ return appendEnvVars(cwd, sections, overwrite);
22049
+ }
22050
+
22051
+ // adapters/next/init/prompts/plugins.ts
21903
22052
  async function promptPlugins(cwd, options = {}) {
21904
22053
  const sections = [];
21905
22054
  const overwriteKeys = /* @__PURE__ */ new Set();
@@ -21938,6 +22087,8 @@ async function promptPlugins(cwd, options = {}) {
21938
22087
  const existingToken = readEnvVar(cwd, "BLOB_READ_WRITE_TOKEN")?.trim();
21939
22088
  const flow = !existingToken && options.provisionVercelBlob ? await options.provisionVercelBlob() : void 0;
21940
22089
  if (flow?.ok && flow.token) {
22090
+ persistBlobReadWriteToken(cwd, flow.token);
22091
+ p11.log.success("Saved BLOB_READ_WRITE_TOKEN to .env.local");
21941
22092
  sections.push({
21942
22093
  header: "Storage (Vercel Blob)",
21943
22094
  vars: [{ key: "BLOB_READ_WRITE_TOKEN", value: flow.token }]
@@ -22045,53 +22196,53 @@ async function promptProject(defaultName) {
22045
22196
  }
22046
22197
 
22047
22198
  // adapters/next/init/scaffolders/api-routes.ts
22048
- import path33 from "path";
22199
+ import path34 from "path";
22049
22200
 
22050
22201
  // core-engine/utils/fs.ts
22051
- import fs26 from "fs";
22052
- import path32 from "path";
22202
+ import fs27 from "fs";
22203
+ import path33 from "path";
22053
22204
  import fse from "fs-extra";
22054
22205
  function ensureDir(dirPath) {
22055
22206
  fse.ensureDirSync(dirPath);
22056
22207
  }
22057
22208
  function safeWriteFile(filePath, content, force = false) {
22058
- if (!force && fs26.existsSync(filePath)) {
22209
+ if (!force && fs27.existsSync(filePath)) {
22059
22210
  return false;
22060
22211
  }
22061
- ensureDir(path32.dirname(filePath));
22062
- fs26.writeFileSync(filePath, content, "utf-8");
22212
+ ensureDir(path33.dirname(filePath));
22213
+ fs27.writeFileSync(filePath, content, "utf-8");
22063
22214
  return true;
22064
22215
  }
22065
22216
 
22066
22217
  // adapters/next/init/scaffolders/api-routes.ts
22067
22218
  function scaffoldApiRoutes({ cwd, config }) {
22068
22219
  const created = [];
22069
- const apiDir = path33.resolve(cwd, config.paths.api);
22220
+ const apiDir = path34.resolve(cwd, config.paths.api);
22070
22221
  const namespace = config.frameworkConfig.next.namespace;
22071
22222
  function write(relPath, content) {
22072
- const fullPath = path33.join(apiDir, relPath);
22073
- ensureDir(path33.dirname(fullPath));
22223
+ const fullPath = path34.join(apiDir, relPath);
22224
+ ensureDir(path34.dirname(fullPath));
22074
22225
  const versionedContent = applyNextCacheApiForVersion(content, config.nextMajorVersion);
22075
22226
  if (safeWriteFile(fullPath, applyAdminNamespaceToContent(versionedContent, namespace))) {
22076
- created.push(path33.join(config.paths.api, relPath));
22227
+ created.push(path34.join(config.paths.api, relPath));
22077
22228
  }
22078
22229
  }
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"));
22230
+ write(path34.join("auth", "[...all]", "route.ts"), readTemplate("api/auth-route.ts"));
22231
+ write(path34.join("upload", "route.ts"), readTemplate("api/upload-route.ts"));
22081
22232
  return created;
22082
22233
  }
22083
22234
 
22084
22235
  // adapters/next/init/scaffolders/auth.ts
22085
- import path34 from "path";
22236
+ import path35 from "path";
22086
22237
  function scaffoldAuth({ cwd, config }) {
22087
22238
  const created = [];
22088
- const authDir = path34.resolve(cwd, config.paths.admin, "lib", "actions", "auth");
22239
+ const authDir = path35.resolve(cwd, config.paths.admin, "lib", "actions", "auth");
22089
22240
  const namespace = config.frameworkConfig.next.namespace;
22090
22241
  function write(filename, content) {
22091
22242
  const outputFile = applyAdminNamespaceToPath(filename, namespace);
22092
- const fullPath = path34.join(authDir, outputFile);
22243
+ const fullPath = path35.join(authDir, outputFile);
22093
22244
  if (safeWriteFile(fullPath, applyAdminNamespaceToContent(content, namespace))) {
22094
- created.push(path34.join(config.paths.admin, "lib", "actions", "auth", outputFile));
22245
+ created.push(path35.join(config.paths.admin, "lib", "actions", "auth", outputFile));
22095
22246
  }
22096
22247
  }
22097
22248
  write("server.ts", readTemplate("lib/actions/auth/auth.ts"));
@@ -22101,7 +22252,7 @@ function scaffoldAuth({ cwd, config }) {
22101
22252
  }
22102
22253
 
22103
22254
  // adapters/next/init/scaffolders/base.ts
22104
- import path35 from "path";
22255
+ import path36 from "path";
22105
22256
  function scaffoldBase({
22106
22257
  cwd,
22107
22258
  config,
@@ -22111,58 +22262,58 @@ function scaffoldBase({
22111
22262
  const adminDirs = [
22112
22263
  config.paths.admin,
22113
22264
  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")
22265
+ path36.join(config.paths.admin, "lib"),
22266
+ path36.join(config.paths.admin, "lib", "db"),
22267
+ path36.join(config.paths.admin, "lib", "db", "core"),
22268
+ path36.join(config.paths.admin, "lib", "db", "migrations"),
22269
+ path36.join(config.paths.admin, "lib", "actions"),
22270
+ path36.join(config.paths.admin, "lib", "actions", "auth"),
22271
+ path36.join(config.paths.admin, "lib", "actions", "email"),
22272
+ path36.join(config.paths.admin, "lib", "actions", "forms"),
22273
+ path36.join(config.paths.admin, "lib", "actions", "media"),
22274
+ path36.join(config.paths.admin, "lib", "actions", "profile"),
22275
+ path36.join(config.paths.admin, "lib", "actions", "storage"),
22276
+ path36.join(config.paths.admin, "lib", "actions", "upload"),
22277
+ path36.join(config.paths.admin, "lib", "actions", "users"),
22278
+ path36.join(config.paths.admin, "hooks"),
22279
+ path36.join(config.paths.admin, "components", "ui"),
22280
+ path36.join(config.paths.admin, "components", "custom"),
22281
+ path36.join(config.paths.admin, "components", "layouts"),
22282
+ path36.join(config.paths.admin, "components", "shared"),
22283
+ path36.join(config.paths.admin, "components", "shared", "data-table"),
22284
+ path36.join(config.paths.admin, "components", "shared", "media"),
22285
+ path36.join(config.paths.admin, "data"),
22286
+ path36.join(config.paths.admin, "data", "navigation"),
22287
+ path36.join(config.paths.admin, "types"),
22288
+ path36.join(config.paths.admin, "utils"),
22289
+ path36.join(config.paths.admin, "utils", "core")
22139
22290
  ];
22140
22291
  if (includeEmailTemplatesDir) {
22141
- adminDirs.push(path35.join(config.paths.admin, "components", "email-templates"));
22292
+ adminDirs.push(path36.join(config.paths.admin, "components", "email-templates"));
22142
22293
  }
22143
22294
  for (const dir of adminDirs) {
22144
- ensureDir(path35.resolve(cwd, dir));
22295
+ ensureDir(path36.resolve(cwd, dir));
22145
22296
  }
22146
- const adminDir = path35.dirname(config.paths.pages);
22297
+ const adminDir = path36.dirname(config.paths.pages);
22147
22298
  const appDirs = [
22148
22299
  config.paths.pages,
22149
22300
  config.paths.login,
22150
22301
  config.paths.api,
22151
- path35.join(adminDir, "(account)", "profile")
22302
+ path36.join(adminDir, "(account)", "profile")
22152
22303
  ];
22153
22304
  for (const dir of appDirs) {
22154
- ensureDir(path35.resolve(cwd, dir));
22305
+ ensureDir(path36.resolve(cwd, dir));
22155
22306
  }
22156
22307
  const configContent = serializeConfig(config);
22157
- if (safeWriteFile(path35.resolve(cwd, "admin.config.ts"), configContent)) {
22308
+ if (safeWriteFile(path36.resolve(cwd, "admin.config.ts"), configContent)) {
22158
22309
  created.push("admin.config.ts");
22159
22310
  }
22160
22311
  return created;
22161
22312
  }
22162
22313
 
22163
22314
  // adapters/next/init/scaffolders/biome.ts
22164
- import fs27 from "fs";
22165
- import path36 from "path";
22315
+ import fs28 from "fs";
22316
+ import path37 from "path";
22166
22317
  function scaffoldBiome(cwd, linter) {
22167
22318
  if (linter.type !== "none") {
22168
22319
  return {
@@ -22170,8 +22321,8 @@ function scaffoldBiome(cwd, linter) {
22170
22321
  skippedReason: `${linter.type} already configured (${linter.configFile})`
22171
22322
  };
22172
22323
  }
22173
- const configPath = path36.join(cwd, "biome.json");
22174
- if (fs27.existsSync(configPath)) {
22324
+ const configPath = path37.join(cwd, "biome.json");
22325
+ if (fs28.existsSync(configPath)) {
22175
22326
  return { installed: false, skippedReason: "biome.json already exists" };
22176
22327
  }
22177
22328
  const config = {
@@ -22234,13 +22385,13 @@ function scaffoldBiome(cwd, linter) {
22234
22385
  ]
22235
22386
  }
22236
22387
  };
22237
- fs27.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
22388
+ fs28.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
22238
22389
  `, "utf-8");
22239
22390
  return { installed: true, skippedReason: null };
22240
22391
  }
22241
22392
 
22242
22393
  // adapters/next/init/scaffolders/components.ts
22243
- import path37 from "path";
22394
+ import path38 from "path";
22244
22395
 
22245
22396
  // adapters/next/init/templates/app/admin.ts
22246
22397
  function adminDataTemplate(projectName, namespaceValue = DEFAULT_ADMIN_NAMESPACE) {
@@ -22252,17 +22403,17 @@ function adminDataTemplate(projectName, namespaceValue = DEFAULT_ADMIN_NAMESPACE
22252
22403
  }
22253
22404
 
22254
22405
  // adapters/next/init/scaffolders/components.ts
22255
- import fs28 from "fs-extra";
22406
+ import fs29 from "fs-extra";
22256
22407
  function scaffoldComponents({ cwd, config }) {
22257
- const admin = path37.resolve(cwd, config.paths.admin);
22408
+ const admin = path38.resolve(cwd, config.paths.admin);
22258
22409
  const created = [];
22259
22410
  const namespace = config.frameworkConfig.next.namespace;
22260
22411
  function write(relPath, content) {
22261
22412
  const namespacedRelPath = applyAdminNamespaceToPath(relPath, namespace);
22262
- const fullPath = path37.join(admin, namespacedRelPath);
22413
+ const fullPath = path38.join(admin, namespacedRelPath);
22263
22414
  const versionedContent = applyNextCacheApiForVersion(content, config.nextMajorVersion);
22264
22415
  if (safeWriteFile(fullPath, applyAdminNamespaceToContent(versionedContent, namespace))) {
22265
- created.push(path37.join(config.paths.admin, namespacedRelPath));
22416
+ created.push(path38.join(config.paths.admin, namespacedRelPath));
22266
22417
  }
22267
22418
  }
22268
22419
  write("admin-globals.css", readTemplate("admin-globals.css"));
@@ -22695,49 +22846,49 @@ function copyCustomTemplates(cwd, config) {
22695
22846
  }
22696
22847
  function copyStaticComponentTemplates(cwd, config, sourceDirectory, targetDirectory) {
22697
22848
  const created = [];
22698
- const destDir = path37.resolve(cwd, config.paths.admin, "components", targetDirectory);
22849
+ const destDir = path38.resolve(cwd, config.paths.admin, "components", targetDirectory);
22699
22850
  const srcDir = resolveCliAssetPath("shared-assets", "react-admin", sourceDirectory);
22700
- if (!fs28.existsSync(srcDir)) {
22851
+ if (!fs29.existsSync(srcDir)) {
22701
22852
  return created;
22702
22853
  }
22703
- fs28.ensureDirSync(destDir);
22854
+ fs29.ensureDirSync(destDir);
22704
22855
  if (sourceDirectory === "custom") {
22705
22856
  copyStaticComponentTemplatesRecursive(
22706
22857
  srcDir,
22707
22858
  destDir,
22708
22859
  "",
22709
- path37.join(config.paths.admin, "components", targetDirectory),
22860
+ path38.join(config.paths.admin, "components", targetDirectory),
22710
22861
  config.frameworkConfig.next.namespace,
22711
22862
  created
22712
22863
  );
22713
22864
  return created;
22714
22865
  }
22715
- const files = fs28.readdirSync(srcDir, { withFileTypes: true }).filter(
22866
+ const files = fs29.readdirSync(srcDir, { withFileTypes: true }).filter(
22716
22867
  (entry) => entry.isFile() && (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts"))
22717
22868
  ).map((entry) => entry.name);
22718
22869
  for (const file of files) {
22719
22870
  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(
22871
+ const destPath = path38.join(destDir, namespacedFile);
22872
+ if (!fs29.existsSync(destPath)) {
22873
+ const content = fs29.readFileSync(path38.join(srcDir, file), "utf-8");
22874
+ fs29.writeFileSync(
22724
22875
  destPath,
22725
22876
  applyAdminNamespaceToContent(content, config.frameworkConfig.next.namespace),
22726
22877
  "utf-8"
22727
22878
  );
22728
- created.push(path37.join(config.paths.admin, "components", targetDirectory, namespacedFile));
22879
+ created.push(path38.join(config.paths.admin, "components", targetDirectory, namespacedFile));
22729
22880
  }
22730
22881
  }
22731
22882
  return created;
22732
22883
  }
22733
22884
  function copyStaticComponentTemplatesRecursive(srcDir, destDir, relBase, createdPrefix, namespace, created) {
22734
- const entries = fs28.readdirSync(srcDir, { withFileTypes: true });
22885
+ const entries = fs29.readdirSync(srcDir, { withFileTypes: true });
22735
22886
  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);
22887
+ const rel = path38.join(relBase, entry.name);
22888
+ const srcPath = path38.join(srcDir, entry.name);
22889
+ const destPath = path38.join(destDir, entry.name);
22739
22890
  if (entry.isDirectory()) {
22740
- const namespacedDestPath2 = path37.join(
22891
+ const namespacedDestPath2 = path38.join(
22741
22892
  destDir,
22742
22893
  applyAdminNamespaceToPath(entry.name, namespace)
22743
22894
  );
@@ -22755,37 +22906,37 @@ function copyStaticComponentTemplatesRecursive(srcDir, destDir, relBase, created
22755
22906
  continue;
22756
22907
  }
22757
22908
  const namespacedRel = applyAdminNamespaceToPath(rel, namespace);
22758
- const namespacedDestPath = path37.join(
22759
- path37.dirname(destPath),
22909
+ const namespacedDestPath = path38.join(
22910
+ path38.dirname(destPath),
22760
22911
  applyAdminNamespaceToPath(entry.name, namespace)
22761
22912
  );
22762
- if (!fs28.existsSync(namespacedDestPath)) {
22763
- fs28.ensureDirSync(path37.dirname(namespacedDestPath));
22764
- const content = fs28.readFileSync(srcPath, "utf-8");
22765
- fs28.writeFileSync(
22913
+ if (!fs29.existsSync(namespacedDestPath)) {
22914
+ fs29.ensureDirSync(path38.dirname(namespacedDestPath));
22915
+ const content = fs29.readFileSync(srcPath, "utf-8");
22916
+ fs29.writeFileSync(
22766
22917
  namespacedDestPath,
22767
22918
  applyAdminNamespaceToContent(content, namespace),
22768
22919
  "utf-8"
22769
22920
  );
22770
- created.push(path37.join(createdPrefix, namespacedRel));
22921
+ created.push(path38.join(createdPrefix, namespacedRel));
22771
22922
  }
22772
22923
  }
22773
22924
  }
22774
22925
  function copySchemaMetaschema(cwd, config) {
22775
22926
  const created = [];
22776
22927
  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"));
22928
+ const destPath = path38.resolve(cwd, config.paths.schemas, "schema.json");
22929
+ if (fs29.existsSync(srcPath) && !fs29.existsSync(destPath)) {
22930
+ fs29.ensureDirSync(path38.dirname(destPath));
22931
+ fs29.copyFileSync(srcPath, destPath);
22932
+ created.push(path38.join(config.paths.schemas, "schema.json"));
22782
22933
  }
22783
22934
  return created;
22784
22935
  }
22785
22936
 
22786
22937
  // adapters/next/init/scaffolders/core-schemas.ts
22787
- import fs29 from "fs";
22788
- import path38 from "path";
22938
+ import fs30 from "fs";
22939
+ import path39 from "path";
22789
22940
 
22790
22941
  // adapters/next/init/templates/core/default-settings.ts
22791
22942
  function defaultSettingsSchema() {
@@ -22865,9 +23016,9 @@ function scaffoldCoreSchemas({
22865
23016
  };
22866
23017
  const schemaContent = defaultSettingsSchema();
22867
23018
  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");
23019
+ const schemaPath = path39.join(cwd, config.paths.schemas, `${schemaName}.json`);
23020
+ fs30.mkdirSync(path39.dirname(schemaPath), { recursive: true });
23021
+ fs30.writeFileSync(schemaPath, schemaContent, "utf-8");
22871
23022
  result.schemas.push(`${schemaName}.json`);
22872
23023
  ensureSnapshotGitFiles(cwd);
22873
23024
  try {
@@ -22912,21 +23063,21 @@ function scaffoldCoreSchemas({
22912
23063
  }
22913
23064
 
22914
23065
  // adapters/next/init/scaffolders/database.ts
22915
- import path39 from "path";
23066
+ import path40 from "path";
22916
23067
  function scaffoldDatabase({ cwd, config }) {
22917
23068
  const created = [];
22918
- const dbDir = path39.resolve(cwd, config.paths.admin, "lib", "db");
23069
+ const dbDir = path40.resolve(cwd, config.paths.admin, "lib", "db");
22919
23070
  const namespace = config.frameworkConfig.next.namespace;
22920
23071
  function write(filename, content) {
22921
- const fullPath = path39.join(dbDir, filename);
23072
+ const fullPath = path40.join(dbDir, filename);
22922
23073
  if (safeWriteFile(fullPath, applyAdminNamespaceToContent(content, namespace))) {
22923
- created.push(path39.join(config.paths.admin, "lib", "db", filename));
23074
+ created.push(path40.join(config.paths.admin, "lib", "db", filename));
22924
23075
  }
22925
23076
  }
22926
23077
  write("client.ts", readTemplate("lib/db/client.ts"));
22927
23078
  write("core/schema.ts", readTemplate("lib/db/core/schema.ts"));
22928
23079
  write("schema.ts", readTemplate("lib/db/schema.ts"));
22929
- const drizzleConfigPath = path39.resolve(cwd, "drizzle.config.ts");
23080
+ const drizzleConfigPath = path40.resolve(cwd, "drizzle.config.ts");
22930
23081
  if (safeWriteFile(
22931
23082
  drizzleConfigPath,
22932
23083
  applyAdminNamespaceToContent(readTemplate("drizzle.config.ts"), namespace)
@@ -22936,139 +23087,6 @@ function scaffoldDatabase({ cwd, config }) {
22936
23087
  return created;
22937
23088
  }
22938
23089
 
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
23090
  // adapters/next/init/scaffolders/layout.ts
23073
23091
  import path41 from "path";
23074
23092
  function scaffoldLayout({ cwd, config }) {
@@ -24692,15 +24710,7 @@ async function provisionNeonInteractive(runner, cwd, options) {
24692
24710
  if (!add.success) {
24693
24711
  return { failure: add.timedOut ? "timeout" : "provision-failed" };
24694
24712
  }
24695
- const pullSpinner = spinner2();
24696
- pullSpinner.start("Retrieving DATABASE_URL from Vercel");
24697
- const databaseUrl = await pullVercelEnvValue(runner, cwd, readDbUrlFromDotenv, options.env);
24698
- if (!databaseUrl) {
24699
- pullSpinner.stop(`${pc5.yellow("\u25B2")} No DATABASE_URL came back from Vercel`);
24700
- return {};
24701
- }
24702
- pullSpinner.clear();
24703
- return { databaseUrl };
24713
+ return {};
24704
24714
  }
24705
24715
  async function provisionNeon(runner, cwd, options) {
24706
24716
  const provisionSpinner = spinner2();
@@ -24723,20 +24733,6 @@ ${add.stderr}`.trim();
24723
24733
  provisionSpinner.clear();
24724
24734
  return readNeonProvisionInfo(add.stdout);
24725
24735
  }
24726
- async function connectNeonResourceToProject(runner, cwd, resourceName, env) {
24727
- const connect = await runVercel(
24728
- runner,
24729
- ["integration", "resource", "connect", resourceName, "--yes", "--format", "json"],
24730
- { cwd, mode: "capture", timeoutMs: PROVISION_TIMEOUT_MS2, env }
24731
- );
24732
- if (connect.success) return { ok: true };
24733
- const detail = `${connect.stdout}
24734
- ${connect.stderr}`.trim();
24735
- if (/already connected/i.test(detail)) {
24736
- return { ok: true, alreadyConnected: true, detail: detail || void 0 };
24737
- }
24738
- return { ok: false, detail: detail || void 0 };
24739
- }
24740
24736
  function readDbUrlFromDotenv(filePath) {
24741
24737
  const vars = parseDotenvFile(filePath);
24742
24738
  const isPg = (v) => !!v && (v.startsWith("postgres://") || v.startsWith("postgresql://"));
@@ -24747,14 +24743,13 @@ function readDbUrlFromDotenv(filePath) {
24747
24743
  function readNeonProvisionInfo(stdout) {
24748
24744
  const meta = parseVercelJson(stdout);
24749
24745
  if (!meta) return {};
24750
- return {
24751
- resourceName: meta.resource?.name,
24752
- dashboardUrl: meta.dashboardUrl,
24753
- resourceUrl: meta.ssoUrl?.resource
24754
- };
24746
+ const info = {};
24747
+ if (meta.dashboardUrl) info.dashboardUrl = meta.dashboardUrl;
24748
+ if (meta.ssoUrl?.resource) info.resourceUrl = meta.ssoUrl.resource;
24749
+ return info;
24755
24750
  }
24756
24751
  function neonAddArgs(options) {
24757
- const addArgs = ["integration", "add", "neon", "--no-connect", "--format", "json"];
24752
+ const addArgs = ["integration", "add", "neon", "--format", "json"];
24758
24753
  if (options.plan) addArgs.push("--plan", options.plan);
24759
24754
  return addArgs;
24760
24755
  }
@@ -24785,20 +24780,10 @@ async function runVercelNeonFlow(options) {
24785
24780
  if (neon.detail) p16.log.message(pc6.dim(neon.detail));
24786
24781
  return { ok: false };
24787
24782
  }
24788
- let databaseUrl = neon.databaseUrl;
24789
- if (neon.resourceName) {
24790
- const pulledDatabaseUrl = await connectNeonAndPullDatabaseUrl(
24791
- runner,
24792
- options.cwd,
24793
- neon.resourceName,
24794
- env
24795
- );
24796
- databaseUrl = pulledDatabaseUrl ?? databaseUrl;
24797
- }
24783
+ const databaseUrl = await pullNeonDatabaseUrl(runner, options.cwd, env);
24798
24784
  return {
24799
24785
  ok: true,
24800
24786
  databaseUrl,
24801
- neonResourceName: neon.resourceName,
24802
24787
  dashboardUrl: neon.dashboardUrl,
24803
24788
  resourceUrl: neon.resourceUrl
24804
24789
  };
@@ -24860,24 +24845,6 @@ async function runVercelDeployFlow(options) {
24860
24845
  return { ok: false };
24861
24846
  }
24862
24847
  await ensureLinkedProject(runner, options.cwd, options.projectName, env);
24863
- if (options.neonResourceName) {
24864
- const neonSpinner = spinner2();
24865
- neonSpinner.start("Connecting Neon database to Vercel project");
24866
- const connect = await connectNeonResourceToProject(
24867
- runner,
24868
- options.cwd,
24869
- options.neonResourceName,
24870
- env
24871
- );
24872
- if (connect.ok) {
24873
- neonSpinner.stop(
24874
- connect.alreadyConnected ? "Neon database already connected to Vercel project" : "Connected Neon database to Vercel project"
24875
- );
24876
- } else {
24877
- neonSpinner.stop(`${pc6.yellow("\u25B2")} Could not connect Neon database to Vercel project`);
24878
- if (connect.detail) p16.log.message(pc6.dim(connect.detail));
24879
- }
24880
- }
24881
24848
  const envSpinner = spinner2();
24882
24849
  envSpinner.start("Syncing environment variables to Vercel");
24883
24850
  const sync = await syncVercelProductionEnv(runner, options.cwd, env);
@@ -24940,16 +24907,9 @@ async function ensureLinkedProject(runner, cwd, projectName, env) {
24940
24907
  project2.linked ? `Linked Vercel project ${pc6.cyan(project2.name)}` : `Using Vercel project ${pc6.cyan(project2.name)}`
24941
24908
  );
24942
24909
  }
24943
- async function connectNeonAndPullDatabaseUrl(runner, cwd, resourceName, env) {
24910
+ async function pullNeonDatabaseUrl(runner, cwd, env) {
24944
24911
  const neonSpinner = spinner2();
24945
- neonSpinner.start("Connecting Neon database to Vercel project");
24946
- const connect = await connectNeonResourceToProject(runner, cwd, resourceName, env);
24947
- if (!connect.ok) {
24948
- neonSpinner.stop(`${pc6.yellow("\u25B2")} Could not connect Neon database to Vercel project`);
24949
- if (connect.detail) p16.log.message(pc6.dim(connect.detail));
24950
- return void 0;
24951
- }
24952
- neonSpinner.message("Retrieving DATABASE_URL from Vercel");
24912
+ neonSpinner.start("Retrieving DATABASE_URL from Vercel");
24953
24913
  const databaseUrl = await pullVercelEnvValue(runner, cwd, readDbUrlFromDotenv, env);
24954
24914
  if (!databaseUrl) {
24955
24915
  neonSpinner.stop(`${pc6.yellow("\u25B2")} No DATABASE_URL came back from Vercel`);
@@ -25581,7 +25541,6 @@ async function runInitCommand(name, options) {
25581
25541
  isFreshProject = true;
25582
25542
  }
25583
25543
  let databaseUrl;
25584
- let neonResourceName;
25585
25544
  const existingDbUrl = readExistingDbUrl(cwd);
25586
25545
  if (options.yes) {
25587
25546
  if (options.databaseUrl) {
@@ -25603,12 +25562,10 @@ async function runInitCommand(name, options) {
25603
25562
  env: process.env
25604
25563
  });
25605
25564
  if (flow.ok && flow.databaseUrl) {
25606
- neonResourceName = flow.neonResourceName;
25607
25565
  databaseUrl = flow.databaseUrl;
25608
25566
  persistDatabaseUrl(cwd, databaseUrl);
25609
25567
  p17.log.success(`Saved DATABASE_URL to ${pc7.cyan(".env.local")}`);
25610
25568
  } else if (flow.ok) {
25611
- neonResourceName = flow.neonResourceName;
25612
25569
  p17.log.warn("Created a Neon database, but DATABASE_URL could not be retrieved from Vercel.");
25613
25570
  const resourceUrl = flow.resourceUrl ?? flow.dashboardUrl;
25614
25571
  if (resourceUrl) {
@@ -25639,12 +25596,10 @@ async function runInitCommand(name, options) {
25639
25596
  env: process.env
25640
25597
  });
25641
25598
  if (flow.ok && flow.databaseUrl) {
25642
- neonResourceName = flow.neonResourceName;
25643
25599
  databaseUrl = flow.databaseUrl;
25644
25600
  persistDatabaseUrl(cwd, databaseUrl);
25645
25601
  p17.log.success(`Saved DATABASE_URL to ${pc7.cyan(".env.local")}`);
25646
25602
  } else if (flow.ok) {
25647
- neonResourceName = flow.neonResourceName;
25648
25603
  openBrowserVercelNeonResource(flow.resourceUrl ?? flow.dashboardUrl);
25649
25604
  databaseUrl = await promptConnectionString();
25650
25605
  persistDatabaseUrl(cwd, databaseUrl);
@@ -25690,6 +25645,8 @@ async function runInitCommand(name, options) {
25690
25645
  env: process.env
25691
25646
  });
25692
25647
  if (flow.ok && flow.token) {
25648
+ persistBlobReadWriteToken(cwd, flow.token);
25649
+ p17.log.success(`Saved BLOB_READ_WRITE_TOKEN to ${pc7.cyan(".env.local")}`);
25693
25650
  collectedIntegrationConfig.sections.push({
25694
25651
  header: "Storage (Vercel Blob)",
25695
25652
  vars: [{ key: "BLOB_READ_WRITE_TOKEN", value: flow.token }]
@@ -25891,8 +25848,9 @@ async function runInitCommand(name, options) {
25891
25848
  config: resolvedPluginInstallResult.config,
25892
25849
  pm,
25893
25850
  integrationIds: pluginSelection.integrations,
25894
- // Secrets were already collected up front and written to .env.local,
25895
- // so the install runs without prompting (rule: no mid-scaffold input).
25851
+ // Secrets were already collected up front and persisted/written to
25852
+ // .env.local, so the install runs without prompting (rule: no
25853
+ // mid-scaffold input).
25896
25854
  interactive: false,
25897
25855
  includeBiome: project2.linter.type === "none"
25898
25856
  });
@@ -26172,13 +26130,12 @@ Run manually: ${pc7.cyan(betterstartExecCommand(pm, "seed"))}`,
26172
26130
  initialValue: linkedVercelProject
26173
26131
  });
26174
26132
  if (!p17.isCancel(deployNow) && deployNow) {
26175
- await runVercelDeployFlow({ cwd, projectName, neonResourceName, env: process.env });
26133
+ await runVercelDeployFlow({ cwd, projectName, env: process.env });
26176
26134
  }
26177
26135
  } else if (linkedVercelProject && process.env.VERCEL_TOKEN) {
26178
26136
  const deployFlow = await runVercelDeployFlow({
26179
26137
  cwd,
26180
26138
  projectName,
26181
- neonResourceName,
26182
26139
  env: process.env
26183
26140
  });
26184
26141
  if (!deployFlow.ok) {