spfn 0.2.0-beta.60 → 0.2.0-beta.63

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/index.js CHANGED
@@ -87,6 +87,44 @@ var init_messages = __esm({
87
87
  }
88
88
  });
89
89
 
90
+ // src/commands/init/mode.ts
91
+ import prompts from "prompts";
92
+ async function selectScaffoldMode(options) {
93
+ if (options.mode) {
94
+ return options.mode;
95
+ }
96
+ if (options.yes) {
97
+ return "bare";
98
+ }
99
+ const { selectedMode } = await prompts({
100
+ type: "select",
101
+ name: "selectedMode",
102
+ message: "Which SPFN scaffold do you want?",
103
+ choices: [
104
+ {
105
+ title: "full (recommended)",
106
+ description: "Core, auth, i18n, and MCP \u2014 ready for Prototype to Production",
107
+ value: "full"
108
+ },
109
+ {
110
+ title: "bare",
111
+ description: "Core-only full-stack skeleton",
112
+ value: "bare"
113
+ }
114
+ ],
115
+ initial: 0
116
+ });
117
+ if (!selectedMode) {
118
+ process.exit(0);
119
+ }
120
+ return selectedMode;
121
+ }
122
+ var init_mode = __esm({
123
+ "src/commands/init/mode.ts"() {
124
+ "use strict";
125
+ }
126
+ });
127
+
90
128
  // src/commands/setup.ts
91
129
  var setup_exports = {};
92
130
  __export(setup_exports, {
@@ -387,7 +425,7 @@ var init_setup = __esm({
387
425
  // src/commands/init/steps/validate.ts
388
426
  import { existsSync as existsSync3 } from "fs";
389
427
  import { join as join3 } from "path";
390
- import prompts from "prompts";
428
+ import prompts2 from "prompts";
391
429
  async function validateProject(cwd, skipPrompts) {
392
430
  const packageJsonPath = join3(cwd, "package.json");
393
431
  if (!existsSync3(packageJsonPath)) {
@@ -401,7 +439,7 @@ async function validateProject(cwd, skipPrompts) {
401
439
  if (!hasNext) {
402
440
  logger.warn("Next.js not detected in dependencies.");
403
441
  if (!skipPrompts) {
404
- const { proceed } = await prompts(
442
+ const { proceed } = await prompts2(
405
443
  {
406
444
  type: "confirm",
407
445
  name: "proceed",
@@ -418,7 +456,7 @@ async function validateProject(cwd, skipPrompts) {
418
456
  if (existsSync3(join3(cwd, "src", "server"))) {
419
457
  logger.warn("src/server directory already exists.");
420
458
  if (!skipPrompts) {
421
- const { overwrite } = await prompts(
459
+ const { overwrite } = await prompts2(
422
460
  {
423
461
  type: "confirm",
424
462
  name: "overwrite",
@@ -432,19 +470,7 @@ async function validateProject(cwd, skipPrompts) {
432
470
  }
433
471
  }
434
472
  }
435
- let includeAuth = false;
436
- if (!skipPrompts) {
437
- const { auth } = await prompts(
438
- {
439
- type: "confirm",
440
- name: "auth",
441
- message: "Include authentication (@spfn/auth)?",
442
- initial: true
443
- }
444
- );
445
- includeAuth = auth;
446
- }
447
- return { packageJson, packageJsonPath, includeAuth };
473
+ return { packageJson, packageJsonPath };
448
474
  }
449
475
  var init_validate = __esm({
450
476
  "src/commands/init/steps/validate.ts"() {
@@ -470,6 +496,10 @@ function findTemplatesPath() {
470
496
  if (existsSync4(devPath)) {
471
497
  return devPath;
472
498
  }
499
+ const sourcePath = join4(__dirname, "..", "..", "..", "..", "templates");
500
+ if (existsSync4(sourcePath)) {
501
+ return sourcePath;
502
+ }
473
503
  throw new Error("Templates directory not found. Please rebuild the package.");
474
504
  }
475
505
  var __dirname;
@@ -482,10 +512,10 @@ var init_templates = __esm({
482
512
 
483
513
  // src/commands/init/steps/server-structure.ts
484
514
  import { existsSync as existsSync5 } from "fs";
485
- import { join as join5 } from "path";
515
+ import { join as join5, sep } from "path";
486
516
  import ora2 from "ora";
487
517
  import fse2 from "fs-extra";
488
- async function setupServerStructure(cwd) {
518
+ async function setupServerStructure(cwd, mode) {
489
519
  const spinner = ora2("Setting up server structure...").start();
490
520
  try {
491
521
  const templatesDir = findTemplatesPath();
@@ -511,6 +541,27 @@ async function setupServerStructure(cwd) {
511
541
  copySync(envConfigTemplate, envConfigTarget);
512
542
  logger.success("Created src/server/config/env.config.ts (environment management)");
513
543
  }
544
+ if (mode === "full") {
545
+ const fullTemplateDir = join5(templatesDir, "modes", "full");
546
+ if (!existsSync5(fullTemplateDir)) {
547
+ throw new Error(`Full scaffold templates not found at: ${fullTemplateDir}`);
548
+ }
549
+ const fullAppTemplateDir = join5(fullTemplateDir, "src", "app");
550
+ const fullI18nTemplateDir = join5(fullTemplateDir, "src", "i18n");
551
+ const mergeOnlyDirs = [fullAppTemplateDir, fullI18nTemplateDir];
552
+ copySync(fullTemplateDir, cwd, {
553
+ filter: (source) => !mergeOnlyDirs.some((directory) => source === directory || source.startsWith(`${directory}${sep}`))
554
+ });
555
+ const appTargetDir = existsSync5(join5(cwd, "src", "app")) ? join5(cwd, "src", "app") : join5(cwd, "app");
556
+ copySync(fullAppTemplateDir, appTargetDir, {
557
+ overwrite: false,
558
+ errorOnExist: false
559
+ });
560
+ copySync(fullI18nTemplateDir, join5(cwd, "src", "i18n"), {
561
+ overwrite: false,
562
+ errorOnExist: false
563
+ });
564
+ }
514
565
  spinner.succeed("Server structure created");
515
566
  } catch (error) {
516
567
  spinner.fail("Failed to create server structure");
@@ -551,8 +602,10 @@ async function setupApiProxy(cwd, includeAuth) {
551
602
  return;
552
603
  }
553
604
  ensureDirSync3(rpcDir);
554
- const authImport = includeAuth ? `import '@spfn/auth/nextjs/api';
605
+ const authImports = includeAuth ? `import '@spfn/auth/nextjs/api';
606
+ import { authRouteMap } from '@spfn/auth';
555
607
  ` : "";
608
+ const proxyRouteMap = includeAuth ? "{ ...routeMap, ...authRouteMap }" : "routeMap";
556
609
  const routeContent = `/**
557
610
  * SPFN RPC Proxy
558
611
  *
@@ -566,10 +619,10 @@ async function setupApiProxy(cwd, includeAuth) {
566
619
  * Run \`spfn codegen run\` if route-map.ts is missing.
567
620
  */
568
621
 
569
- ${authImport}import { routeMap } from '@/generated/route-map';
622
+ ${authImports}import { routeMap } from '@/generated/route-map';
570
623
  import { createRpcProxy } from '@spfn/core/nextjs/server';
571
624
 
572
- export const { GET, POST } = createRpcProxy({ routeMap });
625
+ export const { GET, POST } = createRpcProxy({ routeMap: ${proxyRouteMap} });
573
626
  `;
574
627
  writeFileSync2(rpcRoutePath, routeContent);
575
628
  const relativePath = rpcRoutePath.replace(cwd + "/", "");
@@ -869,7 +922,7 @@ var init_deployment_config = __esm({
869
922
 
870
923
  // src/utils/version.ts
871
924
  function getCliVersion() {
872
- return "0.2.0-beta.60";
925
+ return "0.2.0-beta.63";
873
926
  }
874
927
  function getTagFromVersion(version) {
875
928
  const match = version.match(/-([a-z]+)\./i);
@@ -888,7 +941,7 @@ var init_version = __esm({
888
941
  import ora3 from "ora";
889
942
  import { execa as execa2 } from "execa";
890
943
  import fse7 from "fs-extra";
891
- async function setupPackageJson(cwd, packageJsonPath, packageJson, packageManager, includeAuth) {
944
+ async function setupPackageJson(cwd, packageJsonPath, packageJson, packageManager, mode) {
892
945
  const spinner = ora3("Updating package.json...").start();
893
946
  packageJson.dependencies = packageJson.dependencies || {};
894
947
  packageJson.devDependencies = packageJson.devDependencies || {};
@@ -902,8 +955,19 @@ async function setupPackageJson(cwd, packageJsonPath, packageJson, packageManage
902
955
  packageJson.dependencies["pg"] = "^8.16.3";
903
956
  packageJson.dependencies["spfn"] = spfnTag;
904
957
  packageJson.dependencies["concurrently"] = "^9.2.1";
905
- if (includeAuth) {
958
+ if (mode === "full") {
906
959
  packageJson.dependencies["@spfn/auth"] = spfnTag;
960
+ packageJson.dependencies["@spfn/i18n"] = spfnTag;
961
+ packageJson.dependencies["@spfn/mcp"] = spfnTag;
962
+ packageJson.dependencies["@spfn/notification"] = spfnTag;
963
+ packageJson.engines = packageJson.engines || {};
964
+ const existingNodeRange = packageJson.engines.node;
965
+ if (!existingNodeRange || !requiresNode20OrNewer(existingNodeRange)) {
966
+ packageJson.engines.node = ">=20.0.0";
967
+ if (existingNodeRange) {
968
+ logger.warn(`Updated engines.node from "${existingNodeRange}" to ">=20.0.0" because full mode includes @spfn/mcp`);
969
+ }
970
+ }
907
971
  }
908
972
  packageJson.devDependencies["@types/node"] = "^20.11.0";
909
973
  packageJson.devDependencies["tsx"] = "^4.20.6";
@@ -935,6 +999,16 @@ async function setupPackageJson(cwd, packageJsonPath, packageJson, packageManage
935
999
  process.exit(1);
936
1000
  }
937
1001
  }
1002
+ function requiresNode20OrNewer(range) {
1003
+ return range.split("||").map((alternative) => alternative.trim()).every((alternative) => {
1004
+ const lowerBound = alternative.match(/^(?:>=|>|\^|~|=)?\s*v?(\d+)/);
1005
+ if (!lowerBound) {
1006
+ return false;
1007
+ }
1008
+ const major = Number(lowerBound[1]);
1009
+ return major >= 20;
1010
+ });
1011
+ }
938
1012
  var writeFileSync6;
939
1013
  var init_package = __esm({
940
1014
  "src/commands/init/steps/package.ts"() {
@@ -1041,14 +1115,70 @@ var init_env_file = __esm({
1041
1115
 
1042
1116
  // src/commands/init/steps/config-files.ts
1043
1117
  import { existsSync as existsSync11, readFileSync as readFileSync5 } from "fs";
1118
+ import { randomBytes } from "crypto";
1119
+ import { spawnSync } from "child_process";
1044
1120
  import { join as join11 } from "path";
1045
1121
  import fse8 from "fs-extra";
1122
+ function envExampleTemplate(mode) {
1123
+ return withPlaceholderCreds(`# Example environment \u2014 committed reference for the variables SPFN uses.
1124
+ # Real values live in .env.local (Next.js) and .env.server (backend secrets),
1125
+ # both gitignored. This file documents the keys; it is not loaded by anything.
1126
+
1127
+ ${ENV_LOCAL_TEMPLATE}${mode === "full" ? FULL_ENV_LOCAL_EXAMPLE : ""}
1128
+ ${ENV_SERVER_TEMPLATE}${mode === "full" ? FULL_ENV_SERVER_EXAMPLE : ""}`);
1129
+ }
1130
+ function envServerExampleTemplate(mode) {
1131
+ return withPlaceholderCreds(
1132
+ `${ENV_SERVER_TEMPLATE}${mode === "full" ? FULL_ENV_SERVER_EXAMPLE : ""}`
1133
+ );
1134
+ }
1135
+ function envLocalTemplate(mode) {
1136
+ if (mode === "bare") {
1137
+ return ENV_LOCAL_TEMPLATE;
1138
+ }
1139
+ return `${ENV_LOCAL_TEMPLATE}
1140
+ # Full scaffold: auth session encryption (Next.js server only; never NEXT_PUBLIC_*)
1141
+ SPFN_AUTH_SESSION_SECRET=${randomSecret("base64url")}
1142
+ SPFN_AUTH_SESSION_TTL=7d
1143
+ NEXT_PUBLIC_SPFN_APP_URL=http://localhost:3790
1144
+ `;
1145
+ }
1146
+ function envLocalExampleTemplate(mode) {
1147
+ return `${ENV_LOCAL_TEMPLATE}${mode === "full" ? FULL_ENV_LOCAL_EXAMPLE : ""}`;
1148
+ }
1149
+ function envServerTemplate(mode) {
1150
+ if (mode === "bare") {
1151
+ return ENV_SERVER_TEMPLATE;
1152
+ }
1153
+ return `${ENV_SERVER_TEMPLATE}
1154
+ # Full scaffold: authentication
1155
+ SPFN_AUTH_VERIFICATION_TOKEN_SECRET=${randomSecret("base64url")}
1156
+ SPFN_AUTH_TOKEN_ENCRYPTION_KEYS=v1:${randomSecret("base64")}
1157
+
1158
+ # Enable the social providers you need.
1159
+ # SPFN_AUTH_GOOGLE_CLIENT_ID=your-google-client-id
1160
+ # SPFN_AUTH_GOOGLE_CLIENT_SECRET=your-google-client-secret
1161
+ # SPFN_AUTH_GITHUB_CLIENT_ID=your-github-client-id
1162
+ # SPFN_AUTH_GITHUB_CLIENT_SECRET=your-github-client-secret
1163
+ # SPFN_AUTH_KAKAO_CLIENT_ID=your-kakao-rest-api-key
1164
+ # SPFN_AUTH_KAKAO_CLIENT_SECRET=your-kakao-client-secret
1165
+ # SPFN_AUTH_NAVER_CLIENT_ID=your-naver-client-id
1166
+ # SPFN_AUTH_NAVER_CLIENT_SECRET=your-naver-client-secret
1167
+
1168
+ # Full scaffold: MCP operator endpoint
1169
+ SPFN_MCP_URL=http://localhost:8790
1170
+ SPFN_MCP_API_KEY=${randomSecret("base64url")}
1171
+ `;
1172
+ }
1173
+ function randomSecret(encoding) {
1174
+ return randomBytes(32).toString(encoding);
1175
+ }
1046
1176
  function withPlaceholderCreds(text) {
1047
1177
  return text.replace(/(postgresql:\/\/)[^@\s/]+@/g, "$1user:password@");
1048
1178
  }
1049
- async function setupConfigFiles(cwd) {
1179
+ async function setupConfigFiles(cwd, mode) {
1050
1180
  updateGitignore(cwd);
1051
- generateEnvFiles(cwd);
1181
+ generateEnvFiles(cwd, mode);
1052
1182
  const spfnrcPath = join11(cwd, ".spfnrc.ts");
1053
1183
  if (!existsSync11(spfnrcPath)) {
1054
1184
  const spfnrcContent = `import { defineConfig, defineGenerator } from '@spfn/core/codegen';
@@ -1076,20 +1206,42 @@ export default defineConfig({
1076
1206
  }
1077
1207
  updateTsconfig(cwd);
1078
1208
  }
1079
- function generateEnvFiles(cwd) {
1080
- writeEnvFile(cwd, ".env.local", ENV_LOCAL_TEMPLATE);
1081
- writeServerEnv(cwd);
1082
- writeExampleEnv(cwd, ENV_EXAMPLE_TEMPLATE);
1209
+ function generateEnvFiles(cwd, mode) {
1210
+ writeLocalEnv(cwd, envLocalTemplate(mode), envLocalExampleTemplate(mode));
1211
+ writeServerEnv(cwd, envServerTemplate(mode), envServerExampleTemplate(mode));
1212
+ writeExampleEnv(cwd, envExampleTemplate(mode));
1213
+ }
1214
+ function writeLocalEnv(cwd, template, exampleTemplate) {
1215
+ const filename = ".env.local";
1216
+ const filePath = join11(cwd, filename);
1217
+ if (isGitTracked(cwd, filename)) {
1218
+ const referenceName = ".env.local.spfn.example";
1219
+ const referencePath = join11(cwd, referenceName);
1220
+ if (!existsSync11(referencePath)) {
1221
+ writeFileSync8(referencePath, exampleTemplate);
1222
+ }
1223
+ logger.warn(`${filename} is tracked by Git \u2014 left it untouched; wrote ${referenceName}. Untrack ${filename}, then add the missing keys with freshly generated values`);
1224
+ return;
1225
+ }
1226
+ writeEnvFile(cwd, filename, template);
1227
+ restrictEnvFilePerms(filePath);
1228
+ }
1229
+ function isGitTracked(cwd, filename) {
1230
+ const result = spawnSync("git", ["ls-files", "--error-unmatch", "--", filename], {
1231
+ cwd,
1232
+ stdio: "ignore"
1233
+ });
1234
+ return result.status === 0;
1083
1235
  }
1084
- function writeServerEnv(cwd) {
1236
+ function writeServerEnv(cwd, template, exampleTemplate) {
1085
1237
  const filePath = join11(cwd, ".env.server");
1086
1238
  if (!existsSync11(filePath)) {
1087
- writeFileSync8(filePath, ENV_SERVER_TEMPLATE);
1239
+ writeFileSync8(filePath, template);
1088
1240
  restrictEnvFilePerms(filePath);
1089
1241
  logger.success("Created .env.server");
1090
1242
  return;
1091
1243
  }
1092
- writeFileSync8(join11(cwd, ".env.server.example"), withPlaceholderCreds(ENV_SERVER_TEMPLATE));
1244
+ writeFileSync8(join11(cwd, ".env.server.example"), exampleTemplate);
1093
1245
  logger.warn(".env.server already exists \u2014 left it untouched; wrote SPFN's reference to .env.server.example, add any missing keys manually");
1094
1246
  }
1095
1247
  function writeExampleEnv(cwd, content) {
@@ -1181,7 +1333,7 @@ function updateTsconfig(cwd) {
1181
1333
  logger.warn('Could not update tsconfig.json (you can add "src/server" to exclude manually)');
1182
1334
  }
1183
1335
  }
1184
- var writeFileSync8, ENV_LOCAL_TEMPLATE, ENV_SERVER_TEMPLATE, ENV_EXAMPLE_TEMPLATE;
1336
+ var writeFileSync8, ENV_LOCAL_TEMPLATE, FULL_ENV_LOCAL_EXAMPLE, ENV_SERVER_TEMPLATE, FULL_ENV_SERVER_EXAMPLE;
1185
1337
  var init_config_files = __esm({
1186
1338
  "src/commands/init/steps/config-files.ts"() {
1187
1339
  "use strict";
@@ -1189,8 +1341,8 @@ var init_config_files = __esm({
1189
1341
  init_env_file();
1190
1342
  ({ writeFileSync: writeFileSync8 } = fse8);
1191
1343
  ENV_LOCAL_TEMPLATE = `# Next.js environment
1192
- # Loaded by Next.js (and the SPFN backend). Only non-secret, Next.js-facing
1193
- # values belong here \u2014 server secrets go in .env.server.
1344
+ # Loaded by Next.js (and the SPFN backend). Never prefix secrets with
1345
+ # NEXT_PUBLIC_. Backend-only secrets belong in .env.server.
1194
1346
 
1195
1347
  # SPFN API endpoint \u2014 browser + Next.js SSR/proxy target
1196
1348
  SPFN_API_URL=http://localhost:8790
@@ -1198,6 +1350,12 @@ NEXT_PUBLIC_SPFN_API_URL=http://localhost:8790
1198
1350
 
1199
1351
  # Next.js app URL (used by the SPFN server for CORS/redirects)
1200
1352
  SPFN_APP_URL=http://localhost:3790
1353
+ `;
1354
+ FULL_ENV_LOCAL_EXAMPLE = `
1355
+ # Full scaffold: auth session encryption (Next.js server only; never NEXT_PUBLIC_*)
1356
+ SPFN_AUTH_SESSION_SECRET=replace-with-a-random-secret-at-least-32-characters
1357
+ SPFN_AUTH_SESSION_TTL=7d
1358
+ NEXT_PUBLIC_SPFN_APP_URL=http://localhost:3790
1201
1359
  `;
1202
1360
  ENV_SERVER_TEMPLATE = `# SPFN backend environment
1203
1361
  # Loaded ONLY by the SPFN server, never by Next.js. Keep all server-only config
@@ -1224,12 +1382,25 @@ DB_POOL_IDLE_TIMEOUT=30
1224
1382
  # DATABASE_READ_URL=postgresql://user:password@replica:5432/dbname
1225
1383
  # CACHE_PASSWORD=your-redis-password
1226
1384
  `;
1227
- ENV_EXAMPLE_TEMPLATE = withPlaceholderCreds(`# Example environment \u2014 committed reference for the variables SPFN uses.
1228
- # Real values live in .env.local (Next.js) and .env.server (backend secrets),
1229
- # both gitignored. This file documents the keys; it is not loaded by anything.
1230
-
1231
- ${ENV_LOCAL_TEMPLATE}
1232
- ${ENV_SERVER_TEMPLATE}`);
1385
+ FULL_ENV_SERVER_EXAMPLE = `
1386
+ # Full scaffold: authentication
1387
+ SPFN_AUTH_VERIFICATION_TOKEN_SECRET=replace-with-a-random-secret-at-least-32-characters
1388
+ SPFN_AUTH_TOKEN_ENCRYPTION_KEYS=v1:replace-with-a-base64-encoded-32-byte-key
1389
+
1390
+ # Enable the social providers you need.
1391
+ # SPFN_AUTH_GOOGLE_CLIENT_ID=your-google-client-id
1392
+ # SPFN_AUTH_GOOGLE_CLIENT_SECRET=your-google-client-secret
1393
+ # SPFN_AUTH_GITHUB_CLIENT_ID=your-github-client-id
1394
+ # SPFN_AUTH_GITHUB_CLIENT_SECRET=your-github-client-secret
1395
+ # SPFN_AUTH_KAKAO_CLIENT_ID=your-kakao-rest-api-key
1396
+ # SPFN_AUTH_KAKAO_CLIENT_SECRET=your-kakao-client-secret
1397
+ # SPFN_AUTH_NAVER_CLIENT_ID=your-naver-client-id
1398
+ # SPFN_AUTH_NAVER_CLIENT_SECRET=your-naver-client-secret
1399
+
1400
+ # Full scaffold: MCP operator endpoint
1401
+ SPFN_MCP_URL=http://localhost:8790
1402
+ SPFN_MCP_API_KEY=replace-with-a-random-operator-key
1403
+ `;
1233
1404
  }
1234
1405
  });
1235
1406
 
@@ -1254,15 +1425,16 @@ async function setupReadme(cwd, ctx) {
1254
1425
  const content = renderReadme(template, {
1255
1426
  projectName: ctx.packageJson.name || basename(cwd) || "my-app",
1256
1427
  pm: ctx.pm,
1428
+ pmExec: ctx.pm === "npm" ? "npx" : ctx.pm,
1257
1429
  pmRun: getRunCommand(ctx.pm),
1258
- includeAuth: ctx.includeAuth
1430
+ mode: ctx.mode
1259
1431
  });
1260
1432
  writeFileSync9(readmePath, content);
1261
1433
  logger.success("Created README.md");
1262
1434
  }
1263
1435
  function renderReadme(template, vars) {
1264
1436
  const authBlock = /\r?\n?<!-- \{\{#auth\}\} -->\r?\n([\s\S]*?)\r?\n<!-- \{\{\/auth\}\} -->\r?\n/;
1265
- return template.replace(authBlock, vars.includeAuth ? "\n$1\n" : "\n").replaceAll("{{projectName}}", () => vars.projectName).replaceAll("{{pmRun}}", () => vars.pmRun).replaceAll("{{pm}}", () => vars.pm);
1437
+ return template.replace(authBlock, vars.mode === "full" ? "\n$1\n" : "\n").replaceAll("{{mode}}", () => vars.mode).replaceAll("{{projectName}}", () => vars.projectName).replaceAll("{{pmRun}}", () => vars.pmRun).replaceAll("{{pmExec}}", () => vars.pmExec).replaceAll("{{pm}}", () => vars.pm);
1266
1438
  }
1267
1439
  var writeFileSync9;
1268
1440
  var init_readme = __esm({
@@ -1281,31 +1453,40 @@ __export(init_exports, {
1281
1453
  initCommand: () => initCommand,
1282
1454
  initializeSpfn: () => initializeSpfn
1283
1455
  });
1284
- import { Command as Command2 } from "commander";
1456
+ import { Command as Command2, Option } from "commander";
1285
1457
  import chalk4 from "chalk";
1286
1458
  async function initializeSpfn(options = {}) {
1287
1459
  const cwd = process.cwd();
1288
- const { packageJson, packageJsonPath, includeAuth } = await validateProject(cwd, options.yes || false);
1460
+ const { packageJson, packageJsonPath } = await validateProject(cwd, options.yes || false);
1461
+ const mode = await selectScaffoldMode(options);
1462
+ const includeAuth = mode === "full";
1289
1463
  const pm = detectPackageManager(cwd);
1290
1464
  logger.step(`Detected package manager: ${pm}`);
1291
- await setupServerStructure(cwd);
1465
+ await setupServerStructure(cwd, mode);
1292
1466
  await setupApiProxy(cwd, includeAuth);
1293
1467
  await setupNextConfig(cwd, includeAuth);
1294
1468
  await setupDockerFiles(cwd, pm);
1295
1469
  await setupDeploymentConfig(cwd, packageJson, pm);
1296
- await setupPackageJson(cwd, packageJsonPath, packageJson, pm, includeAuth);
1297
- await setupConfigFiles(cwd);
1298
- await setupReadme(cwd, { pm, packageJson, includeAuth, overwrite: options.overwriteReadme ?? false });
1470
+ await setupPackageJson(cwd, packageJsonPath, packageJson, pm, mode);
1471
+ await setupConfigFiles(cwd, mode);
1472
+ await setupReadme(cwd, { pm, packageJson, mode, overwrite: options.overwriteReadme ?? false });
1299
1473
  console.log("\n" + chalk4.green.bold("\u2713 SPFN initialized successfully!\n"));
1300
1474
  console.log("Next steps:");
1301
1475
  console.log(" 1. Start PostgreSQL & Redis (if not installed locally):");
1302
1476
  console.log(" " + chalk4.cyan("docker compose up -d"));
1303
1477
  console.log(" 2. Review the generated env files (.env.local, .env.server)");
1304
1478
  console.log(" " + chalk4.dim(ENV_FILES_HINT));
1305
- console.log(" 3. Run: " + chalk4.cyan(`${getRunCommand(pm)} spfn:dev`));
1306
- console.log(" 4. Visit:");
1479
+ if (mode === "full") {
1480
+ const spfnCommand = pm === "npm" ? "npx spfn" : `${pm} spfn`;
1481
+ console.log(" 3. Apply the auth migrations: " + chalk4.cyan(`${spfnCommand} db migrate`));
1482
+ }
1483
+ console.log(` ${mode === "full" ? "4" : "3"}. Run: ` + chalk4.cyan(`${getRunCommand(pm)} spfn:dev`));
1484
+ console.log(` ${mode === "full" ? "5" : "4"}. Visit:`);
1307
1485
  console.log(" - Next.js: " + chalk4.cyan("http://localhost:3790"));
1308
1486
  console.log(" - API: " + chalk4.cyan("http://localhost:8790/health"));
1487
+ if (mode === "full") {
1488
+ console.log(" - MCP: " + chalk4.cyan("http://localhost:8790/mcp"));
1489
+ }
1309
1490
  console.log("\nAvailable commands:");
1310
1491
  console.log(" \u2022 " + chalk4.cyan(`${getRunCommand(pm)} spfn:dev`) + " - Start SPFN + Next.js");
1311
1492
  console.log(" \u2022 " + chalk4.cyan("spfn env validate") + " - Validate environment variables");
@@ -1329,7 +1510,8 @@ var init_init = __esm({
1329
1510
  init_package();
1330
1511
  init_config_files();
1331
1512
  init_readme();
1332
- initCommand = new Command2("init").description("Initialize SPFN in your Next.js project").option("-y, --yes", "Skip prompts and use defaults").action(initializeSpfn);
1513
+ init_mode();
1514
+ initCommand = new Command2("init").description("Initialize SPFN in your Next.js project").addOption(new Option("--mode <mode>", "Scaffold mode: bare (core) or full (core, auth, i18n, MCP)").choices(["bare", "full"])).option("-y, --yes", "Skip prompts and use defaults").action(initializeSpfn);
1333
1515
  }
1334
1516
  });
1335
1517
 
@@ -1342,39 +1524,39 @@ __export(function_migrations_exports, {
1342
1524
  loadFunctionMigrationPlans: () => loadFunctionMigrationPlans,
1343
1525
  readMigrationEntries: () => readMigrationEntries
1344
1526
  });
1345
- import chalk13 from "chalk";
1527
+ import chalk6 from "chalk";
1346
1528
  import { createHash } from "crypto";
1347
- import { join as join19 } from "path";
1348
- import { env as env2 } from "@spfn/core/config";
1349
- import { loadEnv as loadEnv2 } from "@spfn/core/server";
1350
- import { existsSync as existsSync19, readdirSync as readdirSync2, readFileSync as readFileSync9 } from "fs";
1529
+ import { join as join15 } from "path";
1530
+ import { env } from "@spfn/core/config";
1531
+ import { loadEnv } from "@spfn/core/server";
1532
+ import { existsSync as existsSync14, readdirSync, readFileSync as readFileSync7 } from "fs";
1351
1533
  function discoverFunctionMigrations(cwd = process.cwd()) {
1352
- const nodeModulesPath = join19(cwd, "node_modules");
1353
- if (!existsSync19(nodeModulesPath)) {
1534
+ const nodeModulesPath = join15(cwd, "node_modules");
1535
+ if (!existsSync14(nodeModulesPath)) {
1354
1536
  return [];
1355
1537
  }
1356
1538
  const functions = [];
1357
- const spfnDir = join19(nodeModulesPath, "@spfn");
1358
- if (!existsSync19(spfnDir)) {
1539
+ const spfnDir = join15(nodeModulesPath, "@spfn");
1540
+ if (!existsSync14(spfnDir)) {
1359
1541
  return [];
1360
1542
  }
1361
- const packages = readdirSync2(spfnDir);
1543
+ const packages = readdirSync(spfnDir);
1362
1544
  for (const pkg of packages) {
1363
- const packagePath = join19(spfnDir, pkg);
1364
- const packageJsonPath = join19(packagePath, "package.json");
1365
- if (!existsSync19(packageJsonPath)) {
1545
+ const packagePath = join15(spfnDir, pkg);
1546
+ const packageJsonPath = join15(packagePath, "package.json");
1547
+ if (!existsSync14(packageJsonPath)) {
1366
1548
  continue;
1367
1549
  }
1368
1550
  try {
1369
- const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
1551
+ const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
1370
1552
  const spfnConfig = packageJson.spfn;
1371
1553
  if (!spfnConfig?.migrations) {
1372
1554
  continue;
1373
1555
  }
1374
- const migrationsDir = join19(packagePath, spfnConfig.migrations.dir);
1375
- if (!existsSync19(migrationsDir)) {
1556
+ const migrationsDir = join15(packagePath, spfnConfig.migrations.dir);
1557
+ if (!existsSync14(migrationsDir)) {
1376
1558
  console.warn(
1377
- chalk13.yellow(`\u26A0\uFE0F Package @spfn/${pkg} specifies migrations but directory not found: ${migrationsDir}`)
1559
+ chalk6.yellow(`\u26A0\uFE0F Package @spfn/${pkg} specifies migrations but directory not found: ${migrationsDir}`)
1378
1560
  );
1379
1561
  continue;
1380
1562
  }
@@ -1384,14 +1566,14 @@ function discoverFunctionMigrations(cwd = process.cwd()) {
1384
1566
  packagePath
1385
1567
  });
1386
1568
  } catch (error) {
1387
- console.warn(chalk13.yellow(`\u26A0\uFE0F Failed to parse package.json for @spfn/${pkg}`));
1569
+ console.warn(chalk6.yellow(`\u26A0\uFE0F Failed to parse package.json for @spfn/${pkg}`));
1388
1570
  }
1389
1571
  }
1390
1572
  return functions;
1391
1573
  }
1392
1574
  function readMigrationEntries(migrationsDir, packageName) {
1393
- const journalPath = join19(migrationsDir, "meta", "_journal.json");
1394
- return existsSync19(journalPath) ? readJournalEntries(migrationsDir, journalPath, packageName) : readFolderEntries(migrationsDir, packageName);
1575
+ const journalPath = join15(migrationsDir, "meta", "_journal.json");
1576
+ return existsSync14(journalPath) ? readJournalEntries(migrationsDir, journalPath, packageName) : readFolderEntries(migrationsDir, packageName);
1395
1577
  }
1396
1578
  function loadFunctionMigrationPlans(functionMigrations) {
1397
1579
  return functionMigrations.map((func) => ({
@@ -1402,7 +1584,7 @@ function loadFunctionMigrationPlans(functionMigrations) {
1402
1584
  function readJournalEntries(migrationsDir, journalPath, packageName) {
1403
1585
  let journal;
1404
1586
  try {
1405
- journal = JSON.parse(readFileSync9(journalPath, "utf-8"));
1587
+ journal = JSON.parse(readFileSync7(journalPath, "utf-8"));
1406
1588
  } catch {
1407
1589
  journal = {};
1408
1590
  }
@@ -1415,18 +1597,18 @@ function readJournalEntries(migrationsDir, journalPath, packageName) {
1415
1597
  if (typeof entry?.tag !== "string" || typeof entry?.when !== "number") {
1416
1598
  throw new Error(`${packageName}: invalid journal entry in ${journalPath}`);
1417
1599
  }
1418
- const sqlPath = join19(migrationsDir, `${entry.tag}.sql`);
1419
- if (!existsSync19(sqlPath)) {
1600
+ const sqlPath = join15(migrationsDir, `${entry.tag}.sql`);
1601
+ if (!existsSync14(sqlPath)) {
1420
1602
  throw new Error(`${packageName}: migration file not found: ${entry.tag}.sql`);
1421
1603
  }
1422
- return toEntry(entry.tag, readFileSync9(sqlPath, "utf-8"), entry.when);
1604
+ return toEntry(entry.tag, readFileSync7(sqlPath, "utf-8"), entry.when);
1423
1605
  });
1424
1606
  }
1425
1607
  function readFolderEntries(migrationsDir, packageName) {
1426
- const folders = readdirSync2(migrationsDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name).filter((name) => existsSync19(join19(migrationsDir, name, "migration.sql"))).sort((a, b) => a.localeCompare(b));
1608
+ const folders = readdirSync(migrationsDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name).filter((name) => existsSync14(join15(migrationsDir, name, "migration.sql"))).sort((a, b) => a.localeCompare(b));
1427
1609
  return folders.map((name) => toEntry(
1428
1610
  name,
1429
- readFileSync9(join19(migrationsDir, name, "migration.sql"), "utf-8"),
1611
+ readFileSync7(join15(migrationsDir, name, "migration.sql"), "utf-8"),
1430
1612
  folderTimestampMillis(name, packageName)
1431
1613
  ));
1432
1614
  }
@@ -1466,7 +1648,7 @@ async function migrateLegacyTable(db, plans) {
1466
1648
  if (!legacyCheck[0]?.exists) {
1467
1649
  return;
1468
1650
  }
1469
- console.log(chalk13.dim("\n Migrating legacy shared migration table to per-package tables..."));
1651
+ console.log(chalk6.dim("\n Migrating legacy shared migration table to per-package tables..."));
1470
1652
  const legacyRows = await db.query(
1471
1653
  `SELECT hash, created_at FROM drizzle."__spfn_fn_migrations" ORDER BY id`
1472
1654
  );
@@ -1496,11 +1678,11 @@ async function migrateLegacyTable(db, plans) {
1496
1678
  copied++;
1497
1679
  }
1498
1680
  if (copied > 0) {
1499
- console.log(chalk13.dim(` \u2713 ${plan.packageName}: copied ${copied} migration record(s)`));
1681
+ console.log(chalk6.dim(` \u2713 ${plan.packageName}: copied ${copied} migration record(s)`));
1500
1682
  }
1501
1683
  }
1502
1684
  await db.query(`DROP TABLE drizzle."__spfn_fn_migrations"`);
1503
- console.log(chalk13.dim(" \u2713 Legacy migration table removed\n"));
1685
+ console.log(chalk6.dim(" \u2713 Legacy migration table removed\n"));
1504
1686
  }
1505
1687
  async function ensureMigrationsTable(db, tableName) {
1506
1688
  await db.query(`CREATE SCHEMA IF NOT EXISTS drizzle`);
@@ -1544,20 +1726,20 @@ function createPostgresJsMigrationDb(client) {
1544
1726
  }
1545
1727
  async function executeFunctionMigrations(plans) {
1546
1728
  const postgres = await import("postgres");
1547
- loadEnv2();
1548
- if (!env2.DATABASE_URL) {
1729
+ loadEnv();
1730
+ if (!env.DATABASE_URL) {
1549
1731
  throw new Error("DATABASE_URL not found in environment");
1550
1732
  }
1551
- const connection = postgres.default(env2.DATABASE_URL, { max: 1 });
1733
+ const connection = postgres.default(env.DATABASE_URL, { max: 1 });
1552
1734
  const db = createPostgresJsMigrationDb(connection);
1553
1735
  let appliedCount = 0;
1554
1736
  try {
1555
1737
  await migrateLegacyTable(db, plans);
1556
1738
  for (const plan of plans) {
1557
- console.log(chalk13.blue(`
1739
+ console.log(chalk6.blue(`
1558
1740
  \u{1F4E6} Running ${plan.packageName} migrations...`));
1559
1741
  const applied = await applyFunctionMigrationPlan(db, plan);
1560
- console.log(applied > 0 ? chalk13.green(` \u2713 ${plan.packageName}: ${applied} migration(s) applied`) : chalk13.dim(` \u2013 ${plan.packageName}: up to date`));
1742
+ console.log(applied > 0 ? chalk6.green(` \u2713 ${plan.packageName}: ${applied} migration(s) applied`) : chalk6.dim(` \u2013 ${plan.packageName}: up to date`));
1561
1743
  appliedCount += applied;
1562
1744
  }
1563
1745
  } finally {
@@ -1571,6 +1753,112 @@ var init_function_migrations = __esm({
1571
1753
  }
1572
1754
  });
1573
1755
 
1756
+ // src/utils/migration-status.ts
1757
+ var migration_status_exports = {};
1758
+ __export(migration_status_exports, {
1759
+ filterPendingEntries: () => filterPendingEntries,
1760
+ functionMigrationsTable: () => functionMigrationsTable2,
1761
+ getMigrationStatus: () => getMigrationStatus,
1762
+ warnPendingMigrations: () => warnPendingMigrations
1763
+ });
1764
+ import { join as join16 } from "path";
1765
+ import { existsSync as existsSync15 } from "fs";
1766
+ import chalk7 from "chalk";
1767
+ import { env as env2 } from "@spfn/core/config";
1768
+ import { loadEnv as loadEnv2 } from "@spfn/core/server";
1769
+ function functionMigrationsTable2(packageName) {
1770
+ return `__spfn_fn_${packageName.replace("@spfn/", "")}_migrations`;
1771
+ }
1772
+ function filterPendingEntries(entries, lastAppliedMillis, appliedNames) {
1773
+ return entries.filter((entry) => entry.millis > lastAppliedMillis && !appliedNames.has(entry.name));
1774
+ }
1775
+ async function readAppliedNames(sql2, tableName) {
1776
+ const columnCheck = await sql2`
1777
+ SELECT EXISTS (
1778
+ SELECT 1 FROM information_schema.columns
1779
+ WHERE table_schema = 'drizzle' AND table_name = ${tableName} AND column_name = 'name'
1780
+ ) AS "exists"`;
1781
+ if (!columnCheck[0]?.exists) {
1782
+ return /* @__PURE__ */ new Set();
1783
+ }
1784
+ const rows = await sql2`
1785
+ SELECT name FROM drizzle.${sql2(tableName)} WHERE name IS NOT NULL`;
1786
+ return new Set(rows.map((row) => row.name));
1787
+ }
1788
+ async function collectTargetStatus(sql2, name, migrationsDir, tableName) {
1789
+ const entries = readMigrationEntries(migrationsDir, name);
1790
+ const tableCheck = await sql2`
1791
+ SELECT EXISTS (
1792
+ SELECT 1 FROM information_schema.tables
1793
+ WHERE table_schema = 'drizzle' AND table_name = ${tableName}
1794
+ ) AS "exists"`;
1795
+ let lastApplied = 0;
1796
+ let appliedNames = /* @__PURE__ */ new Set();
1797
+ if (tableCheck[0]?.exists) {
1798
+ const rows = await sql2`
1799
+ SELECT created_at FROM drizzle.${sql2(tableName)}
1800
+ ORDER BY created_at DESC LIMIT 1`;
1801
+ lastApplied = rows[0]?.created_at ? Number(rows[0].created_at) : 0;
1802
+ appliedNames = await readAppliedNames(sql2, tableName);
1803
+ }
1804
+ const pendingEntries = filterPendingEntries(entries, lastApplied, appliedNames);
1805
+ return {
1806
+ name,
1807
+ total: entries.length,
1808
+ applied: entries.length - pendingEntries.length,
1809
+ pending: pendingEntries.length,
1810
+ pendingTags: pendingEntries.map((e) => e.name)
1811
+ };
1812
+ }
1813
+ async function getMigrationStatus(cwd = process.cwd(), databaseUrl) {
1814
+ loadEnv2();
1815
+ const url = databaseUrl ?? env2.DATABASE_URL;
1816
+ if (!url) {
1817
+ throw new Error("DATABASE_URL not found in environment");
1818
+ }
1819
+ const postgres = await import("postgres");
1820
+ const sql2 = postgres.default(url, { max: 1, connect_timeout: 5 });
1821
+ try {
1822
+ const packages = [];
1823
+ for (const func of discoverFunctionMigrations(cwd)) {
1824
+ packages.push(await collectTargetStatus(
1825
+ sql2,
1826
+ func.packageName,
1827
+ func.migrationsDir,
1828
+ functionMigrationsTable2(func.packageName)
1829
+ ));
1830
+ }
1831
+ const projectDir = join16(cwd, "src", "server", "drizzle");
1832
+ const project = existsSync15(projectDir) ? await collectTargetStatus(sql2, "project (src/server/drizzle)", projectDir, "__drizzle_migrations") : null;
1833
+ return { packages, project: project && project.total > 0 ? project : null };
1834
+ } finally {
1835
+ await sql2.end();
1836
+ }
1837
+ }
1838
+ async function warnPendingMigrations(cwd, databaseUrl) {
1839
+ let status;
1840
+ try {
1841
+ status = await getMigrationStatus(cwd, databaseUrl);
1842
+ } catch {
1843
+ return;
1844
+ }
1845
+ const targets = [...status.packages, ...status.project ? [status.project] : []].filter((t) => t.pending > 0);
1846
+ if (targets.length === 0) {
1847
+ return;
1848
+ }
1849
+ console.warn("");
1850
+ for (const target of targets) {
1851
+ console.warn(chalk7.yellow(`\u26A0\uFE0F ${target.name}: ${target.pending} pending migration(s)`));
1852
+ }
1853
+ console.warn(chalk7.yellow(" Missing tables will fail at request time \u2014 run: pnpm spfn db migrate\n"));
1854
+ }
1855
+ var init_migration_status = __esm({
1856
+ "src/utils/migration-status.ts"() {
1857
+ "use strict";
1858
+ init_function_migrations();
1859
+ }
1860
+ });
1861
+
1574
1862
  // src/index.ts
1575
1863
  import { Command as Command13 } from "commander";
1576
1864
 
@@ -1578,10 +1866,11 @@ import { Command as Command13 } from "commander";
1578
1866
  init_logger();
1579
1867
  init_package_manager();
1580
1868
  init_messages();
1581
- import { Command as Command3 } from "commander";
1869
+ init_mode();
1870
+ import { Command as Command3, Option as Option2 } from "commander";
1582
1871
  import { existsSync as existsSync13 } from "fs";
1583
1872
  import { join as join13, resolve, dirname as dirname2 } from "path";
1584
- import prompts2 from "prompts";
1873
+ import prompts3 from "prompts";
1585
1874
  import ora4 from "ora";
1586
1875
  import { execa as execa3 } from "execa";
1587
1876
  import chalk5 from "chalk";
@@ -1607,7 +1896,7 @@ async function createProject(projectName, options) {
1607
1896
  console.log(chalk5.blue.bold("\n\u{1F680} Creating Next.js project with SPFN...\n"));
1608
1897
  let pm = options.pm || detectPackageManager(cwd);
1609
1898
  if (!options.yes && !options.pm) {
1610
- const { selectedPm } = await prompts2({
1899
+ const { selectedPm } = await prompts3({
1611
1900
  type: "select",
1612
1901
  name: "selectedPm",
1613
1902
  message: "Which package manager do you want to use?",
@@ -1625,6 +1914,8 @@ async function createProject(projectName, options) {
1625
1914
  pm = selectedPm;
1626
1915
  }
1627
1916
  logger.step(`Using package manager: ${pm}`);
1917
+ const mode = await selectScaffoldMode(options);
1918
+ logger.step(`Using scaffold mode: ${mode}`);
1628
1919
  const workspaceRoot = pm === "pnpm" ? findPnpmWorkspaceRoot(cwd) : null;
1629
1920
  const isInWorkspace = workspaceRoot !== null;
1630
1921
  if (isInWorkspace) {
@@ -1723,7 +2014,7 @@ async function createProject(projectName, options) {
1723
2014
  const initSpinner = ora4("Initializing SPFN...").start();
1724
2015
  try {
1725
2016
  const { initializeSpfn: initializeSpfn2 } = await Promise.resolve().then(() => (init_init(), init_exports));
1726
- await initializeSpfn2({ yes: true, overwriteReadme: true });
2017
+ await initializeSpfn2({ yes: true, overwriteReadme: true, mode });
1727
2018
  initSpinner.succeed("SPFN initialized");
1728
2019
  } catch (error) {
1729
2020
  initSpinner.fail("Failed to initialize SPFN");
@@ -1735,6 +2026,10 @@ async function createProject(projectName, options) {
1735
2026
  console.log(` ${chalk5.cyan("cd")} ${projectName}`);
1736
2027
  console.log(` ${chalk5.cyan("docker compose up -d")} ${chalk5.gray("# Start PostgreSQL & Redis")}`);
1737
2028
  console.log(` ${chalk5.gray(`# .env.local & .env.server are generated \u2014 ${ENV_FILES_HINT}`)}`);
2029
+ if (mode === "full") {
2030
+ const spfnCommand = pm === "npm" ? "npx spfn" : `${pm} spfn`;
2031
+ console.log(` ${chalk5.cyan(`${spfnCommand} db migrate`)} ${chalk5.gray("# Apply auth migrations")}`);
2032
+ }
1738
2033
  console.log(` ${chalk5.cyan(`${getRunCommand(pm)} spfn:dev`)} ${chalk5.gray("# Start dev server")}
1739
2034
  `);
1740
2035
  console.log(chalk5.bold("Your app will be available at:\n"));
@@ -1751,7 +2046,7 @@ async function createProject(projectName, options) {
1751
2046
  `);
1752
2047
  console.log(chalk5.dim(" \u{1F310} Documentation: https://github.com/spfn/spfn\n"));
1753
2048
  }
1754
- var createCommand = new Command3("create").description("Create a new Next.js project with SPFN").argument("<project-name>", "Name of the project directory").option("--skip-install", "Skip installing dependencies").option("--skip-git", "Skip initializing a git repository").option("--pm <manager>", "Package manager to use (npm, pnpm, yarn, bun)").option("--shadcn", "Setup shadcn/ui (component library)").option("-y, --yes", "Skip prompts and use defaults").action(async (projectName, options) => {
2049
+ var createCommand = new Command3("create").description("Create a new Next.js project with SPFN").argument("<project-name>", "Name of the project directory").option("--skip-install", "Skip installing dependencies").option("--skip-git", "Skip initializing a git repository").option("--pm <manager>", "Package manager to use (npm, pnpm, yarn, bun)").option("--shadcn", "Setup shadcn/ui (component library)").addOption(new Option2("--mode <mode>", "Scaffold mode: bare (core) or full (core, auth, i18n, MCP)").choices(["bare", "full"])).option("-y, --yes", "Skip prompts and use defaults").action(async (projectName, options) => {
1755
2050
  await createProject(projectName, options);
1756
2051
  });
1757
2052
 
@@ -1762,8 +2057,8 @@ init_init();
1762
2057
  init_logger();
1763
2058
  init_package_manager();
1764
2059
  import { Command as Command4 } from "commander";
1765
- import { existsSync as existsSync14, readFileSync as readFileSync7, writeFileSync as writeFileSync10, mkdirSync, unlinkSync, rmSync, watch } from "fs";
1766
- import { join as join15, relative, sep } from "path";
2060
+ import { existsSync as existsSync16, readFileSync as readFileSync8, writeFileSync as writeFileSync10, mkdirSync, unlinkSync, rmSync, watch } from "fs";
2061
+ import { join as join17, relative, sep as sep2 } from "path";
1767
2062
  import { execa as execa6 } from "execa";
1768
2063
  import chokidar from "chokidar";
1769
2064
 
@@ -1939,10 +2234,10 @@ async function resolveKeychainEnv(cwd) {
1939
2234
  return { env: {}, missing: [] };
1940
2235
  }
1941
2236
  const store = detectStore();
1942
- const env8 = {};
2237
+ const env9 = {};
1943
2238
  const missing = [];
1944
2239
  if (!await store.isAvailable()) {
1945
- return { env: env8, missing: refs.map(([key]) => key) };
2240
+ return { env: env9, missing: refs.map(([key]) => key) };
1946
2241
  }
1947
2242
  for (const [key, value] of refs) {
1948
2243
  const name = value.slice(KEYCHAIN_REF_PREFIX.length);
@@ -1950,32 +2245,32 @@ async function resolveKeychainEnv(cwd) {
1950
2245
  if (resolved === null) {
1951
2246
  missing.push(key);
1952
2247
  } else {
1953
- env8[key] = resolved;
2248
+ env9[key] = resolved;
1954
2249
  }
1955
2250
  }
1956
- return { env: env8, missing };
2251
+ return { env: env9, missing };
1957
2252
  }
1958
2253
 
1959
2254
  // src/commands/dev.ts
1960
2255
  function ignoreDotfilesUnder(root) {
1961
- return (watchedPath) => relative(root, watchedPath).split(sep).some((segment) => segment.startsWith(".") && segment !== "." && segment !== "..");
2256
+ return (watchedPath) => relative(root, watchedPath).split(sep2).some((segment) => segment.startsWith(".") && segment !== "." && segment !== "..");
1962
2257
  }
1963
2258
  function waitForReadyFile(filePath, timeoutMs = 3e4) {
1964
2259
  return new Promise((resolve3, reject) => {
1965
- if (existsSync14(filePath)) {
2260
+ if (existsSync16(filePath)) {
1966
2261
  unlinkSync(filePath);
1967
2262
  }
1968
2263
  const timer = setTimeout(() => {
1969
2264
  watcher.close();
1970
2265
  reject(new Error(`Server did not become ready within ${timeoutMs / 1e3}s`));
1971
2266
  }, timeoutMs);
1972
- const dir = join15(filePath, "..");
2267
+ const dir = join17(filePath, "..");
1973
2268
  const fileName = filePath.split("/").pop();
1974
2269
  const watcher = watch(dir, (_event, name) => {
1975
- if (name === fileName && existsSync14(filePath)) {
2270
+ if (name === fileName && existsSync16(filePath)) {
1976
2271
  watcher.close();
1977
2272
  clearTimeout(timer);
1978
- resolve3(readFileSync7(filePath, "utf-8").trim());
2273
+ resolve3(readFileSync8(filePath, "utf-8").trim());
1979
2274
  }
1980
2275
  });
1981
2276
  });
@@ -1986,8 +2281,8 @@ var devCommand = new Command4("dev").description("Start SPFN development server
1986
2281
  process.env.NODE_ENV = "development";
1987
2282
  }
1988
2283
  const cwd = process.cwd();
1989
- const serverDir = join15(cwd, "src", "server");
1990
- if (!existsSync14(serverDir)) {
2284
+ const serverDir = join17(cwd, "src", "server");
2285
+ if (!existsSync16(serverDir)) {
1991
2286
  logger.error("src/server directory not found.");
1992
2287
  logger.info('Run "spfn init" first to initialize SPFN in your project.');
1993
2288
  process.exit(1);
@@ -2001,23 +2296,25 @@ var devCommand = new Command4("dev").description("Start SPFN development server
2001
2296
  logger.warn(`[SPFN] Could not resolve keychain secret(s): ${keychainMissing.join(", ")} \u2014 run \`spfn secret set <KEY>\``);
2002
2297
  }
2003
2298
  const serverEnv = { ...process.env, ...keychainEnv };
2004
- const packageJsonPath = join15(cwd, "package.json");
2299
+ const { warnPendingMigrations: warnPendingMigrations2 } = await Promise.resolve().then(() => (init_migration_status(), migration_status_exports));
2300
+ await warnPendingMigrations2(cwd, serverEnv.DATABASE_URL);
2301
+ const packageJsonPath = join17(cwd, "package.json");
2005
2302
  let hasNext = false;
2006
- if (existsSync14(packageJsonPath)) {
2007
- const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
2303
+ if (existsSync16(packageJsonPath)) {
2304
+ const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
2008
2305
  hasNext = !!(packageJson.dependencies?.next || packageJson.devDependencies?.next);
2009
2306
  }
2010
- const tempDir = join15(cwd, ".spfn");
2011
- const serverEntry = join15(tempDir, "server.mjs");
2012
- const watcherEntry = join15(tempDir, "watcher.mjs");
2307
+ const tempDir = join17(cwd, ".spfn");
2308
+ const serverEntry = join17(tempDir, "server.mjs");
2309
+ const watcherEntry = join17(tempDir, "watcher.mjs");
2013
2310
  mkdirSync(tempDir, { recursive: true });
2014
- const serverBuildDir = join15(tempDir, "server");
2015
- if (existsSync14(serverBuildDir)) {
2311
+ const serverBuildDir = join17(tempDir, "server");
2312
+ if (existsSync16(serverBuildDir)) {
2016
2313
  rmSync(serverBuildDir, { recursive: true, force: true });
2017
2314
  logger.info("[SPFN] Cleaned stale build cache (.spfn/server)");
2018
2315
  }
2019
- const readySignal = join15(tempDir, "server-ready");
2020
- if (existsSync14(readySignal)) {
2316
+ const readySignal = join17(tempDir, "server-ready");
2317
+ if (existsSync16(readySignal)) {
2021
2318
  unlinkSync(readySignal);
2022
2319
  }
2023
2320
  const configParts = [];
@@ -2025,7 +2322,7 @@ var devCommand = new Command4("dev").description("Start SPFN development server
2025
2322
  if (options.host) configParts.push(`host: '${options.host}'`);
2026
2323
  if (options.routes) configParts.push(`routesPath: '${options.routes}'`);
2027
2324
  configParts.push("debug: true");
2028
- const readyFile = join15(tempDir, "server-ready");
2325
+ const readyFile = join17(tempDir, "server-ready");
2029
2326
  writeFileSync10(serverEntry, `
2030
2327
  import { writeFileSync } from 'fs';
2031
2328
 
@@ -2315,11 +2612,11 @@ catch (error)
2315
2612
  init_logger();
2316
2613
  init_package_manager();
2317
2614
  import { Command as Command5 } from "commander";
2318
- import { existsSync as existsSync15, writeFileSync as writeFileSync11, mkdirSync as mkdirSync2, readdirSync } from "fs";
2319
- import { join as join16 } from "path";
2615
+ import { existsSync as existsSync17, writeFileSync as writeFileSync11, mkdirSync as mkdirSync2, readdirSync as readdirSync2 } from "fs";
2616
+ import { join as join18 } from "path";
2320
2617
  import { execa as execa7 } from "execa";
2321
2618
  import ora5 from "ora";
2322
- import chalk6 from "chalk";
2619
+ import chalk8 from "chalk";
2323
2620
  import { build } from "tsup";
2324
2621
  async function buildProject(options) {
2325
2622
  if (!process.env.NODE_ENV) {
@@ -2330,17 +2627,17 @@ async function buildProject(options) {
2330
2627
  }
2331
2628
  const cwd = process.cwd();
2332
2629
  const pm = detectPackageManager(cwd);
2333
- const packageJsonPath = join16(cwd, "package.json");
2630
+ const packageJsonPath = join18(cwd, "package.json");
2334
2631
  let hasNext = false;
2335
- if (existsSync15(packageJsonPath)) {
2632
+ if (existsSync17(packageJsonPath)) {
2336
2633
  const packageJson = JSON.parse(await import("fs").then(
2337
2634
  (fs5) => fs5.promises.readFile(packageJsonPath, "utf-8")
2338
2635
  ));
2339
2636
  hasNext = !!(packageJson.dependencies?.next || packageJson.devDependencies?.next);
2340
2637
  }
2341
- const serverDir = join16(cwd, "src", "server");
2342
- const hasServer = existsSync15(serverDir);
2343
- console.log(chalk6.blue.bold("\n\u{1F3D7}\uFE0F Building SPFN project for production...\n"));
2638
+ const serverDir = join18(cwd, "src", "server");
2639
+ const hasServer = existsSync17(serverDir);
2640
+ console.log(chalk8.blue.bold("\n\u{1F3D7}\uFE0F Building SPFN project for production...\n"));
2344
2641
  if (hasServer) {
2345
2642
  const spinner = ora5("Generating API client...").start();
2346
2643
  try {
@@ -2376,10 +2673,10 @@ async function buildProject(options) {
2376
2673
  if (hasServer && !options.nextOnly) {
2377
2674
  const spinner = ora5("Building SPFN server...").start();
2378
2675
  try {
2379
- const outputDir = join16(cwd, ".spfn", "server");
2676
+ const outputDir = join18(cwd, ".spfn", "server");
2380
2677
  mkdirSync2(outputDir, { recursive: true });
2381
- const serverDir2 = join16(cwd, "src", "server");
2382
- if (!existsSync15(serverDir2)) {
2678
+ const serverDir2 = join18(cwd, "src", "server");
2679
+ if (!existsSync17(serverDir2)) {
2383
2680
  spinner.fail("SPFN server build failed");
2384
2681
  logger.error("src/server/ directory not found");
2385
2682
  logger.error('Please run "spfn init" to initialize the project.');
@@ -2407,7 +2704,7 @@ async function buildProject(options) {
2407
2704
  onSuccess: async () => {
2408
2705
  }
2409
2706
  });
2410
- const prodServerPath = join16(cwd, ".spfn", "prod-server.mjs");
2707
+ const prodServerPath = join18(cwd, ".spfn", "prod-server.mjs");
2411
2708
  const prodServerContent = `// Load environment variables FIRST (before any imports that depend on them)
2412
2709
  // Use centralized environment loader for standard dotenv priority
2413
2710
  const { env } = await import('@spfn/core/config');
@@ -2433,25 +2730,25 @@ await startServer({
2433
2730
  `;
2434
2731
  writeFileSync11(prodServerPath, prodServerContent);
2435
2732
  spinner.succeed(`SPFN server build completed \u2192 .spfn/server`);
2436
- const routesDir = join16(cwd, ".spfn", "server", "routes");
2437
- if (existsSync15(routesDir)) {
2733
+ const routesDir = join18(cwd, ".spfn", "server", "routes");
2734
+ if (existsSync17(routesDir)) {
2438
2735
  console.log();
2439
- console.log(chalk6.bold("Route (api)"));
2736
+ console.log(chalk8.bold("Route (api)"));
2440
2737
  try {
2441
- const routes = readdirSync(routesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name).sort();
2738
+ const routes = readdirSync2(routesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name).sort();
2442
2739
  if (routes.length > 0) {
2443
2740
  routes.forEach((route, index) => {
2444
2741
  const isLast = index === routes.length - 1;
2445
2742
  const prefix = isLast ? "\u2514" : "\u251C";
2446
2743
  const routePath = `/api/${route}`;
2447
2744
  if (route === "health") {
2448
- console.log(`${prefix} ${chalk6.green("GET")} ${routePath}`);
2745
+ console.log(`${prefix} ${chalk8.green("GET")} ${routePath}`);
2449
2746
  } else {
2450
- console.log(`${prefix} ${chalk6.cyan("*")} ${routePath}`);
2747
+ console.log(`${prefix} ${chalk8.cyan("*")} ${routePath}`);
2451
2748
  }
2452
2749
  });
2453
2750
  console.log();
2454
- console.log(chalk6.cyan("*") + " (Hono) multiple methods supported");
2751
+ console.log(chalk8.cyan("*") + " (Hono) multiple methods supported");
2455
2752
  }
2456
2753
  } catch (error) {
2457
2754
  }
@@ -2459,9 +2756,9 @@ await startServer({
2459
2756
  } catch (error) {
2460
2757
  spinner.fail("SPFN server build failed");
2461
2758
  if (error instanceof Error) {
2462
- console.error("\n" + chalk6.red(error.message));
2759
+ console.error("\n" + chalk8.red(error.message));
2463
2760
  if (error.stack) {
2464
- console.error(chalk6.dim("\n" + error.stack));
2761
+ console.error(chalk8.dim("\n" + error.stack));
2465
2762
  }
2466
2763
  } else {
2467
2764
  logger.error(String(error));
@@ -2473,13 +2770,13 @@ await startServer({
2473
2770
  logger.error("No Next.js or SPFN server found in this project.");
2474
2771
  process.exit(1);
2475
2772
  }
2476
- console.log("\n" + chalk6.green.bold("\u2713 Build completed successfully!\n"));
2477
- console.log(chalk6.bold("Next steps:\n"));
2478
- console.log(" " + chalk6.cyan("Start production server:"));
2479
- console.log(` ${chalk6.cyan(getRunCommand(pm))} spfn:start ${chalk6.gray("# Start SPFN + Next.js")}
2773
+ console.log("\n" + chalk8.green.bold("\u2713 Build completed successfully!\n"));
2774
+ console.log(chalk8.bold("Next steps:\n"));
2775
+ console.log(" " + chalk8.cyan("Start production server:"));
2776
+ console.log(` ${chalk8.cyan(getRunCommand(pm))} spfn:start ${chalk8.gray("# Start SPFN + Next.js")}
2480
2777
  `);
2481
- console.log(" " + chalk6.cyan("Or deploy with Docker:"));
2482
- console.log(` ${chalk6.cyan("docker compose -f docker-compose.production.yml up --build -d")}
2778
+ console.log(" " + chalk8.cyan("Or deploy with Docker:"));
2779
+ console.log(` ${chalk8.cyan("docker compose -f docker-compose.production.yml up --build -d")}
2483
2780
  `);
2484
2781
  }
2485
2782
  var buildCommand = new Command5("build").description("Build SPFN project for production (Next.js + Server)").option("--server-only", "Build only SPFN server (skip Next.js)").option("--next-only", "Build only Next.js (skip SPFN server)").option("--turbo", "Use Turbopack for Next.js build").action(async (options) => {
@@ -2490,25 +2787,25 @@ var buildCommand = new Command5("build").description("Build SPFN project for pro
2490
2787
  init_logger();
2491
2788
  init_package_manager();
2492
2789
  import { Command as Command6 } from "commander";
2493
- import { existsSync as existsSync16, readFileSync as readFileSync8 } from "fs";
2494
- import { join as join17 } from "path";
2790
+ import { existsSync as existsSync18, readFileSync as readFileSync9 } from "fs";
2791
+ import { join as join19 } from "path";
2495
2792
  import { execa as execa8 } from "execa";
2496
- import chalk7 from "chalk";
2793
+ import chalk9 from "chalk";
2497
2794
  var startCommand = new Command6("start").description("Start SPFN production server (Next.js + Hono)").option("--server-only", "Run only SPFN server (skip Next.js)").option("--next-only", "Run only Next.js (skip SPFN server)").option("-p, --port <port>", "Server port", "8790").option("-h, --host <host>", "Server host", "0.0.0.0").action(async (options) => {
2498
2795
  if (!process.env.NODE_ENV) {
2499
2796
  process.env.NODE_ENV = "production";
2500
2797
  }
2501
2798
  const cwd = process.cwd();
2502
- const packageJsonPath = join17(cwd, "package.json");
2799
+ const packageJsonPath = join19(cwd, "package.json");
2503
2800
  let hasNext = false;
2504
- if (existsSync16(packageJsonPath)) {
2505
- const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
2801
+ if (existsSync18(packageJsonPath)) {
2802
+ const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
2506
2803
  hasNext = !!(packageJson.dependencies?.next || packageJson.devDependencies?.next);
2507
2804
  }
2508
- const builtServerDir = join17(cwd, ".spfn", "server");
2509
- const hasBuiltServer = existsSync16(builtServerDir);
2510
- const nextBuildDir = join17(cwd, ".next");
2511
- const hasNextBuild = existsSync16(nextBuildDir);
2805
+ const builtServerDir = join19(cwd, ".spfn", "server");
2806
+ const hasBuiltServer = existsSync18(builtServerDir);
2807
+ const nextBuildDir = join19(cwd, ".next");
2808
+ const hasNextBuild = existsSync18(nextBuildDir);
2512
2809
  if (!options.nextOnly && !hasBuiltServer) {
2513
2810
  logger.error('.spfn/server directory not found. Please run "spfn build" first.');
2514
2811
  process.exit(1);
@@ -2518,8 +2815,8 @@ var startCommand = new Command6("start").description("Start SPFN production serv
2518
2815
  process.exit(1);
2519
2816
  }
2520
2817
  const pm = detectPackageManager(cwd);
2521
- const serverEntry = join17(cwd, ".spfn", "prod-server.mjs");
2522
- if (!existsSync16(serverEntry)) {
2818
+ const serverEntry = join19(cwd, ".spfn", "prod-server.mjs");
2819
+ if (!existsSync18(serverEntry)) {
2523
2820
  logger.error('.spfn/prod-server.mjs not found. Please run "spfn build" first.');
2524
2821
  process.exit(1);
2525
2822
  }
@@ -2555,7 +2852,7 @@ var startCommand = new Command6("start").description("Start SPFN production serv
2555
2852
  }
2556
2853
  const nextCmd = "next start -H 0.0.0.0 -p 3790";
2557
2854
  const serverCmd = `node "${serverEntry}"`;
2558
- console.log(chalk7.blue.bold("\n\u{1F680} Starting SPFN production server...\n"));
2855
+ console.log(chalk9.blue.bold("\n\u{1F680} Starting SPFN production server...\n"));
2559
2856
  logger.info("Next.js: http://0.0.0.0:3790");
2560
2857
  logger.info(`SPFN API: http://${options.host}:${options.port}
2561
2858
  `);
@@ -2582,13 +2879,13 @@ var startCommand = new Command6("start").description("Start SPFN production serv
2582
2879
  // src/commands/codegen.ts
2583
2880
  init_logger();
2584
2881
  import { Command as Command7 } from "commander";
2585
- import { existsSync as existsSync17, writeFileSync as writeFileSync12 } from "fs";
2586
- import { join as join18 } from "path";
2587
- import chalk8 from "chalk";
2882
+ import { existsSync as existsSync19, writeFileSync as writeFileSync12 } from "fs";
2883
+ import { join as join20 } from "path";
2884
+ import chalk10 from "chalk";
2588
2885
  async function initCodegen(options) {
2589
2886
  const cwd = process.cwd();
2590
- const rcPath = join18(cwd, ".spfnrc.ts");
2591
- if (existsSync17(rcPath)) {
2887
+ const rcPath = join20(cwd, ".spfnrc.ts");
2888
+ if (existsSync19(rcPath)) {
2592
2889
  logger.warn(".spfnrc.ts already exists");
2593
2890
  logger.info("Edit manually to add custom generators");
2594
2891
  process.exit(0);
@@ -2614,13 +2911,13 @@ export default defineConfig({
2614
2911
  });
2615
2912
  `;
2616
2913
  writeFileSync12(rcPath, content);
2617
- console.log("\n" + chalk8.green.bold("\u2713 Created .spfnrc.ts\n"));
2618
- console.log(chalk8.gray("Configured the @spfn/core:route-map generator."));
2914
+ console.log("\n" + chalk10.green.bold("\u2713 Created .spfnrc.ts\n"));
2915
+ console.log(chalk10.gray("Configured the @spfn/core:route-map generator."));
2619
2916
  if (options.withExample) {
2620
- console.log("\n" + chalk8.yellow("\u{1F4DD} To add custom generators:"));
2917
+ console.log("\n" + chalk10.yellow("\u{1F4DD} To add custom generators:"));
2621
2918
  console.log(" 1. Create your generator file (e.g., src/generators/my-generator.ts)");
2622
2919
  console.log(" 2. Add to .spfnrc.ts:");
2623
- console.log(chalk8.gray(`
2920
+ console.log(chalk10.gray(`
2624
2921
  {
2625
2922
  "codegen": {
2626
2923
  "generators": [
@@ -2630,11 +2927,11 @@ export default defineConfig({
2630
2927
  }
2631
2928
  }
2632
2929
  `));
2633
- console.log(" 3. Run: " + chalk8.cyan("spfn dev") + " (generators run automatically)");
2930
+ console.log(" 3. Run: " + chalk10.cyan("spfn dev") + " (generators run automatically)");
2634
2931
  } else {
2635
- console.log("\n" + chalk8.yellow("\u{1F4DD} Next steps:"));
2932
+ console.log("\n" + chalk10.yellow("\u{1F4DD} Next steps:"));
2636
2933
  console.log(" \u2022 Add custom generators to .spfnrc.ts");
2637
- console.log(" \u2022 Run: " + chalk8.cyan("spfn dev") + " to start development with code generation");
2934
+ console.log(" \u2022 Run: " + chalk10.cyan("spfn dev") + " to start development with code generation");
2638
2935
  }
2639
2936
  }
2640
2937
  async function listGenerators() {
@@ -2647,10 +2944,10 @@ async function listGenerators() {
2647
2944
  logger.info('Run "spfn codegen init" to initialize configuration');
2648
2945
  return;
2649
2946
  }
2650
- console.log("\n" + chalk8.bold("Registered Generators:"));
2947
+ console.log("\n" + chalk10.bold("Registered Generators:"));
2651
2948
  generators.forEach((gen, index) => {
2652
- console.log(` ${index + 1}. ${chalk8.cyan(gen.name)}`);
2653
- console.log(` Patterns: ${chalk8.gray(gen.watchPatterns.join(", "))}`);
2949
+ console.log(` ${index + 1}. ${chalk10.cyan(gen.name)}`);
2950
+ console.log(` Patterns: ${chalk10.gray(gen.watchPatterns.join(", "))}`);
2654
2951
  });
2655
2952
  console.log("");
2656
2953
  }
@@ -2671,7 +2968,7 @@ async function runGenerators() {
2671
2968
  debug: true
2672
2969
  });
2673
2970
  await orchestrator.generateAll();
2674
- console.log("\n" + chalk8.green.bold("\u2713 Code generation completed"));
2971
+ console.log("\n" + chalk10.green.bold("\u2713 Code generation completed"));
2675
2972
  }
2676
2973
  var codegenCommand = new Command7("codegen").description("Code generation management");
2677
2974
  codegenCommand.command("init").description("Initialize .spfnrc.ts with codegen configuration").option("--with-example", "Show example custom generator usage").action(initCodegen);
@@ -2682,19 +2979,19 @@ codegenCommand.command("run").description("Run code generators once (no watch mo
2682
2979
  init_logger();
2683
2980
  import { Command as Command8 } from "commander";
2684
2981
  import { execSync } from "child_process";
2685
- import chalk9 from "chalk";
2982
+ import chalk11 from "chalk";
2686
2983
 
2687
2984
  // src/utils/secret-gen.ts
2688
- import { randomBytes, randomUUID } from "crypto";
2985
+ import { randomBytes as randomBytes2, randomUUID } from "crypto";
2689
2986
  function randomBase64Url(bytes) {
2690
- return randomBytes(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
2987
+ return randomBytes2(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
2691
2988
  }
2692
2989
  function generateSecretValue(spec) {
2693
2990
  switch (spec) {
2694
2991
  case "hex32":
2695
- return randomBytes(32).toString("hex");
2992
+ return randomBytes2(32).toString("hex");
2696
2993
  case "hex64":
2697
- return randomBytes(64).toString("hex");
2994
+ return randomBytes2(64).toString("hex");
2698
2995
  case "base64url32":
2699
2996
  return randomBase64Url(32);
2700
2997
  case "uuid":
@@ -2757,41 +3054,41 @@ function copyToClipboard(text) {
2757
3054
  function generateSecret(bytes, preset, envVarName, copy) {
2758
3055
  const key = randomBase64Url(bytes);
2759
3056
  const config = preset ? PRESETS[preset] : null;
2760
- console.log("\n" + chalk9.green.bold("\u2713 Generated secret key:"));
3057
+ console.log("\n" + chalk11.green.bold("\u2713 Generated secret key:"));
2761
3058
  if (config) {
2762
- console.log(chalk9.dim(` ${config.description} (${bytes * 8} bits)`));
3059
+ console.log(chalk11.dim(` ${config.description} (${bytes * 8} bits)`));
2763
3060
  } else {
2764
- console.log(chalk9.dim(` ${bytes * 8}-bit secret`));
3061
+ console.log(chalk11.dim(` ${bytes * 8}-bit secret`));
2765
3062
  }
2766
- console.log("\n" + chalk9.cyan(key) + "\n");
3063
+ console.log("\n" + chalk11.cyan(key) + "\n");
2767
3064
  const varName = envVarName || config?.envVar || "SECRET_KEY";
2768
- console.log(chalk9.dim("Add to your .env file:"));
2769
- console.log(chalk9.yellow(`${varName}=${key}
3065
+ console.log(chalk11.dim("Add to your .env file:"));
3066
+ console.log(chalk11.yellow(`${varName}=${key}
2770
3067
  `));
2771
3068
  if (config?.usage) {
2772
- console.log(chalk9.dim("Usage:"));
2773
- console.log(chalk9.gray(` ${config.usage}
3069
+ console.log(chalk11.dim("Usage:"));
3070
+ console.log(chalk11.gray(` ${config.usage}
2774
3071
  `));
2775
3072
  }
2776
3073
  if (copy) {
2777
3074
  if (copyToClipboard(key)) {
2778
- console.log(chalk9.green("\u2713 Copied to clipboard!\n"));
3075
+ console.log(chalk11.green("\u2713 Copied to clipboard!\n"));
2779
3076
  } else {
2780
3077
  logger.warn("Could not copy to clipboard");
2781
3078
  }
2782
3079
  }
2783
3080
  }
2784
3081
  function listPresets() {
2785
- console.log("\n" + chalk9.bold("Available presets:"));
3082
+ console.log("\n" + chalk11.bold("Available presets:"));
2786
3083
  console.log();
2787
3084
  Object.entries(PRESETS).forEach(([name, config]) => {
2788
- console.log(` ${chalk9.cyan(name.padEnd(20))} ${chalk9.dim(config.description)}`);
2789
- console.log(` ${" ".repeat(20)} ${chalk9.gray(`\u2192 ${config.envVar} (${config.bytes * 8} bits)`)}`);
3085
+ console.log(` ${chalk11.cyan(name.padEnd(20))} ${chalk11.dim(config.description)}`);
3086
+ console.log(` ${" ".repeat(20)} ${chalk11.gray(`\u2192 ${config.envVar} (${config.bytes * 8} bits)`)}`);
2790
3087
  console.log();
2791
3088
  });
2792
- console.log(chalk9.dim("Usage:"));
2793
- console.log(chalk9.gray(" spfn key <preset>"));
2794
- console.log(chalk9.gray(" spfn key auth-encryption --copy"));
3089
+ console.log(chalk11.dim("Usage:"));
3090
+ console.log(chalk11.gray(" spfn key <preset>"));
3091
+ console.log(chalk11.gray(" spfn key auth-encryption --copy"));
2795
3092
  console.log();
2796
3093
  }
2797
3094
  var generateValueCommand = new Command8("generate").alias("gen").description("Generate random value (simple output, no metadata)").option("-b, --bytes <number>", "Number of random bytes", "32").option("-c, --copy", "Copy to clipboard").action((options) => {
@@ -2804,9 +3101,9 @@ var generateValueCommand = new Command8("generate").alias("gen").description("Ge
2804
3101
  console.log(value);
2805
3102
  if (options.copy) {
2806
3103
  if (copyToClipboard(value)) {
2807
- console.error(chalk9.green("\u2713 Copied to clipboard"));
3104
+ console.error(chalk11.green("\u2713 Copied to clipboard"));
2808
3105
  } else {
2809
- console.error(chalk9.yellow("\u26A0 Could not copy to clipboard"));
3106
+ console.error(chalk11.yellow("\u26A0 Could not copy to clipboard"));
2810
3107
  }
2811
3108
  }
2812
3109
  });
@@ -2824,9 +3121,9 @@ var keyCommand = new Command8("key").alias("k").description("Generate secure ran
2824
3121
  logger.error(`Unknown preset: ${preset}`);
2825
3122
  console.log("\nAvailable presets:");
2826
3123
  Object.entries(PRESETS).forEach(([name, config]) => {
2827
- console.log(` ${chalk9.cyan(name)}: ${config.description}`);
3124
+ console.log(` ${chalk11.cyan(name)}: ${config.description}`);
2828
3125
  });
2829
- console.log("\nUse " + chalk9.cyan("--list") + " to see detailed information");
3126
+ console.log("\nUse " + chalk11.cyan("--list") + " to see detailed information");
2830
3127
  process.exit(1);
2831
3128
  }
2832
3129
  generateSecret(
@@ -2845,16 +3142,16 @@ init_setup();
2845
3142
  import { Command as Command9 } from "commander";
2846
3143
 
2847
3144
  // src/commands/db/generate.ts
2848
- import chalk11 from "chalk";
3145
+ import chalk13 from "chalk";
2849
3146
 
2850
3147
  // src/commands/db/utils/drizzle.ts
2851
- import { existsSync as existsSync18, writeFileSync as writeFileSync13, unlinkSync as unlinkSync2 } from "fs";
3148
+ import { existsSync as existsSync20, writeFileSync as writeFileSync13, unlinkSync as unlinkSync2 } from "fs";
2852
3149
  import { spawn } from "child_process";
2853
3150
  import { pathToFileURL } from "url";
2854
- import chalk10 from "chalk";
3151
+ import chalk12 from "chalk";
2855
3152
  import ora6 from "ora";
2856
- import { env } from "@spfn/core/config";
2857
- import { loadEnv } from "@spfn/core/server";
3153
+ import { env as env3 } from "@spfn/core/config";
3154
+ import { loadEnv as loadEnv3 } from "@spfn/core/server";
2858
3155
  var TLS_SSL_MODES = /* @__PURE__ */ new Set(["no-verify", "prefer", "require", "verify-ca", "verify-full"]);
2859
3156
  function parseDatabaseUrl(databaseUrl) {
2860
3157
  if (!databaseUrl) {
@@ -2907,22 +3204,22 @@ function resolvePushConnectionConfig(databaseUrl) {
2907
3204
  return { connectionString: parsedUrl.toString() };
2908
3205
  }
2909
3206
  function validateDatabasePrerequisites() {
2910
- loadEnv();
2911
- if (!env.DATABASE_URL) {
2912
- console.error(chalk10.red("\u274C DATABASE_URL not found in environment"));
2913
- console.log(chalk10.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
3207
+ loadEnv3();
3208
+ if (!env3.DATABASE_URL) {
3209
+ console.error(chalk12.red("\u274C DATABASE_URL not found in environment"));
3210
+ console.log(chalk12.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
2914
3211
  throw new Error("DATABASE_URL is required for database operations");
2915
3212
  }
2916
3213
  }
2917
3214
  async function runDrizzleCommand(command) {
2918
- const hasUserConfig = existsSync18("./drizzle.config.ts");
3215
+ const hasUserConfig = existsSync20("./drizzle.config.ts");
2919
3216
  const tempConfigPath = `./drizzle.config.${process.pid}.${Date.now()}.temp.ts`;
2920
3217
  const configPath = hasUserConfig ? "./drizzle.config.ts" : tempConfigPath;
2921
3218
  if (!hasUserConfig) {
2922
- loadEnv();
2923
- if (!env.DATABASE_URL) {
2924
- console.error(chalk10.red("\u274C DATABASE_URL not found in environment"));
2925
- console.log(chalk10.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
3219
+ loadEnv3();
3220
+ if (!env3.DATABASE_URL) {
3221
+ console.error(chalk12.red("\u274C DATABASE_URL not found in environment"));
3222
+ console.log(chalk12.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
2926
3223
  process.exit(1);
2927
3224
  }
2928
3225
  const { generateDrizzleConfigFile } = await import("@spfn/core/db");
@@ -2935,7 +3232,7 @@ async function runDrizzleCommand(command) {
2935
3232
  autoDetectSchemas: true
2936
3233
  });
2937
3234
  writeFileSync13(tempConfigPath, configContent);
2938
- console.log(chalk10.dim("Using auto-generated Drizzle config\n"));
3235
+ console.log(chalk12.dim("Using auto-generated Drizzle config\n"));
2939
3236
  }
2940
3237
  const args = command.split(" ");
2941
3238
  args.push(`--config=${configPath}`);
@@ -2944,10 +3241,10 @@ async function runDrizzleCommand(command) {
2944
3241
  stdio: "inherit",
2945
3242
  // Allow interactive input
2946
3243
  shell: true,
2947
- env: shouldRelaxDbTls(env.DATABASE_URL) ? { ...process.env, NODE_TLS_REJECT_UNAUTHORIZED: "0" } : { ...process.env }
3244
+ env: shouldRelaxDbTls(env3.DATABASE_URL) ? { ...process.env, NODE_TLS_REJECT_UNAUTHORIZED: "0" } : { ...process.env }
2948
3245
  });
2949
3246
  const cleanup = () => {
2950
- if (!hasUserConfig && existsSync18(tempConfigPath)) {
3247
+ if (!hasUserConfig && existsSync20(tempConfigPath)) {
2951
3248
  unlinkSync2(tempConfigPath);
2952
3249
  }
2953
3250
  };
@@ -2970,10 +3267,10 @@ async function runWithSpinner(spinnerText, command, successMessage, failMessage)
2970
3267
  try {
2971
3268
  spinner.stop();
2972
3269
  await runDrizzleCommand(command);
2973
- console.log(chalk10.green(`\u2705 ${successMessage}`));
3270
+ console.log(chalk12.green(`\u2705 ${successMessage}`));
2974
3271
  } catch (error) {
2975
3272
  spinner.fail(failMessage);
2976
- console.error(chalk10.red(error instanceof Error ? error.message : "Unknown error"));
3273
+ console.error(chalk12.red(error instanceof Error ? error.message : "Unknown error"));
2977
3274
  process.exit(1);
2978
3275
  }
2979
3276
  }
@@ -2997,13 +3294,13 @@ async function loadSchemaImports(schemaFiles) {
2997
3294
  return imports;
2998
3295
  }
2999
3296
  async function createPushConnection() {
3000
- loadEnv();
3001
- if (!env.DATABASE_URL) {
3297
+ loadEnv3();
3298
+ if (!env3.DATABASE_URL) {
3002
3299
  throw new Error("DATABASE_URL is required");
3003
3300
  }
3004
3301
  const pg = await import("pg");
3005
3302
  const { drizzle } = await import("drizzle-orm/node-postgres");
3006
- const connectionConfig = resolvePushConnectionConfig(env.DATABASE_URL);
3303
+ const connectionConfig = resolvePushConnectionConfig(env3.DATABASE_URL);
3007
3304
  const pool = new pg.default.Pool({
3008
3305
  ...connectionConfig,
3009
3306
  max: 1
@@ -3019,19 +3316,19 @@ async function createPushConnection() {
3019
3316
  async function dbGenerate() {
3020
3317
  try {
3021
3318
  await runDrizzleCommand("generate");
3022
- console.log(chalk11.green("\n\u2705 Migrations generated successfully"));
3319
+ console.log(chalk13.green("\n\u2705 Migrations generated successfully"));
3023
3320
  } catch (error) {
3024
- console.error(chalk11.red("\n\u274C Failed to generate migrations"));
3025
- console.error(chalk11.red(error instanceof Error ? error.message : "Unknown error"));
3321
+ console.error(chalk13.red("\n\u274C Failed to generate migrations"));
3322
+ console.error(chalk13.red(error instanceof Error ? error.message : "Unknown error"));
3026
3323
  process.exit(1);
3027
3324
  }
3028
3325
  }
3029
3326
 
3030
3327
  // src/commands/db/push.ts
3031
- import chalk14 from "chalk";
3032
- import prompts3 from "prompts";
3328
+ import chalk15 from "chalk";
3329
+ import prompts4 from "prompts";
3033
3330
  import "@spfn/core/config";
3034
- import { loadEnv as loadEnv3 } from "@spfn/core/server";
3331
+ import { loadEnv as loadEnv4 } from "@spfn/core/server";
3035
3332
  import { sql } from "drizzle-orm";
3036
3333
  import { getTableConfig } from "drizzle-orm/pg-core";
3037
3334
 
@@ -3075,7 +3372,7 @@ function classifyStatements(statements) {
3075
3372
  }
3076
3373
 
3077
3374
  // src/commands/db/utils/push-display.ts
3078
- import chalk12 from "chalk";
3375
+ import chalk14 from "chalk";
3079
3376
  function formatSql(sql2) {
3080
3377
  return sql2.replace(/\s+/g, " ").trim();
3081
3378
  }
@@ -3083,30 +3380,30 @@ function printStatements(statements, color) {
3083
3380
  for (const stmt of statements) {
3084
3381
  console.log(color(` ${formatSql(stmt.sql)}`));
3085
3382
  if (stmt.reason) {
3086
- console.log(chalk12.dim(` \u2192 ${stmt.reason}`));
3383
+ console.log(chalk14.dim(` \u2192 ${stmt.reason}`));
3087
3384
  }
3088
3385
  }
3089
3386
  }
3090
3387
  function displayClassifiedStatements(result) {
3091
3388
  if (result.safe.length > 0) {
3092
- console.log(chalk12.green(`
3389
+ console.log(chalk14.green(`
3093
3390
  \u2705 Safe changes (${result.safe.length}):`));
3094
- printStatements(result.safe, chalk12.green);
3391
+ printStatements(result.safe, chalk14.green);
3095
3392
  }
3096
3393
  if (result.warning.length > 0) {
3097
- console.log(chalk12.yellow(`
3394
+ console.log(chalk14.yellow(`
3098
3395
  \u26A0\uFE0F Warnings (${result.warning.length}):`));
3099
- printStatements(result.warning, chalk12.yellow);
3396
+ printStatements(result.warning, chalk14.yellow);
3100
3397
  }
3101
3398
  if (result.destructive.length > 0) {
3102
- console.log(chalk12.red(`
3399
+ console.log(chalk14.red(`
3103
3400
  \u274C Destructive changes (${result.destructive.length}):`));
3104
- printStatements(result.destructive, chalk12.red);
3401
+ printStatements(result.destructive, chalk14.red);
3105
3402
  }
3106
3403
  }
3107
3404
  function displayDryRunSummary(result) {
3108
3405
  const total = result.safe.length + result.warning.length + result.destructive.length;
3109
- console.log(chalk12.cyan(`
3406
+ console.log(chalk14.cyan(`
3110
3407
  \u{1F50D} Dry-run: ${total} statement(s) detected
3111
3408
  `));
3112
3409
  displayClassifiedStatements(result);
@@ -3114,11 +3411,11 @@ function displayDryRunSummary(result) {
3114
3411
  }
3115
3412
  function displayApplySummary(applied, skipped) {
3116
3413
  if (applied > 0) {
3117
- console.log(chalk12.green(`
3414
+ console.log(chalk14.green(`
3118
3415
  \u2705 Applied ${applied} statement(s)`));
3119
3416
  }
3120
3417
  if (skipped > 0) {
3121
- console.log(chalk12.yellow(`\u23ED\uFE0F Skipped ${skipped} destructive statement(s)`));
3418
+ console.log(chalk14.yellow(`\u23ED\uFE0F Skipped ${skipped} destructive statement(s)`));
3122
3419
  }
3123
3420
  console.log("");
3124
3421
  }
@@ -3153,7 +3450,7 @@ async function applyStatements(db, statements) {
3153
3450
  }
3154
3451
  async function dbPush(options = {}) {
3155
3452
  validateDatabasePrerequisites();
3156
- loadEnv3();
3453
+ loadEnv4();
3157
3454
  const { getDrizzleConfig } = await import("@spfn/core/db");
3158
3455
  const config = getDrizzleConfig({
3159
3456
  cwd: process.cwd(),
@@ -3163,10 +3460,10 @@ async function dbPush(options = {}) {
3163
3460
  });
3164
3461
  const schemaFiles = Array.isArray(config.schema) ? config.schema : [config.schema];
3165
3462
  if (schemaFiles.length === 0) {
3166
- console.log(chalk14.yellow("No schema files found."));
3463
+ console.log(chalk15.yellow("No schema files found."));
3167
3464
  return;
3168
3465
  }
3169
- console.log(chalk14.dim(`Found ${schemaFiles.length} schema file(s)
3466
+ console.log(chalk15.dim(`Found ${schemaFiles.length} schema file(s)
3170
3467
  `));
3171
3468
  const imports = await loadSchemaImports(schemaFiles);
3172
3469
  const detectedSchemas = new Set(config.schemaFilter ?? ["public"]);
@@ -3185,10 +3482,10 @@ async function dbPush(options = {}) {
3185
3482
  try {
3186
3483
  const { statements, hints } = await resolvePushPlan(imports, db, schemaFilter);
3187
3484
  for (const hint of hints) {
3188
- console.log(chalk14.yellow(`\u26A0\uFE0F ${hint.hint}`));
3485
+ console.log(chalk15.yellow(`\u26A0\uFE0F ${hint.hint}`));
3189
3486
  }
3190
3487
  if (statements.length === 0) {
3191
- console.log(chalk14.green("\u2705 No changes detected \u2014 database is up to date\n"));
3488
+ console.log(chalk15.green("\u2705 No changes detected \u2014 database is up to date\n"));
3192
3489
  await applyFunctionMigrations(functionPlans);
3193
3490
  return;
3194
3491
  }
@@ -3199,7 +3496,7 @@ async function dbPush(options = {}) {
3199
3496
  }
3200
3497
  displayClassifiedStatements(result);
3201
3498
  if (options.force) {
3202
- console.log(chalk14.dim("\n--force: applying all changes..."));
3499
+ console.log(chalk15.dim("\n--force: applying all changes..."));
3203
3500
  await applyStatements(db, statements);
3204
3501
  displayApplySummary(statements.length, 0);
3205
3502
  } else if (result.destructive.length === 0) {
@@ -3208,13 +3505,13 @@ async function dbPush(options = {}) {
3208
3505
  } else {
3209
3506
  const destructiveSet = new Set(result.destructive.map((s) => s.sql));
3210
3507
  const nonDestructive = statements.filter((s) => !destructiveSet.has(s));
3211
- console.log(chalk14.red(`
3508
+ console.log(chalk15.red(`
3212
3509
  \u274C ${result.destructive.length} destructive change(s) require confirmation:`));
3213
3510
  for (const stmt of result.destructive) {
3214
- console.log(chalk14.red(` ${stmt.sql.replace(/\s+/g, " ").trim()}`));
3215
- console.log(chalk14.dim(` \u2192 ${stmt.reason}`));
3511
+ console.log(chalk15.red(` ${stmt.sql.replace(/\s+/g, " ").trim()}`));
3512
+ console.log(chalk15.dim(` \u2192 ${stmt.reason}`));
3216
3513
  }
3217
- const { confirm } = await prompts3({
3514
+ const { confirm } = await prompts4({
3218
3515
  type: "confirm",
3219
3516
  name: "confirm",
3220
3517
  message: "Apply destructive changes?",
@@ -3228,7 +3525,7 @@ async function dbPush(options = {}) {
3228
3525
  await applyStatements(db, nonDestructive);
3229
3526
  }
3230
3527
  displayApplySummary(nonDestructive.length, result.destructive.length);
3231
- console.log(chalk14.dim("Tip: Use --force to apply all changes without prompting.\n"));
3528
+ console.log(chalk15.dim("Tip: Use --force to apply all changes without prompting.\n"));
3232
3529
  }
3233
3530
  }
3234
3531
  await applyFunctionMigrations(functionPlans);
@@ -3241,8 +3538,8 @@ function loadFunctionPlansOrExit() {
3241
3538
  try {
3242
3539
  return loadFunctionMigrationPlans(functions);
3243
3540
  } catch (error) {
3244
- console.error(chalk14.red("\n\u274C Invalid function package migrations \u2014 nothing was applied"));
3245
- console.error(chalk14.red(error instanceof Error ? error.message : "Unknown error"));
3541
+ console.error(chalk15.red("\n\u274C Invalid function package migrations \u2014 nothing was applied"));
3542
+ console.error(chalk15.red(error instanceof Error ? error.message : "Unknown error"));
3246
3543
  process.exit(1);
3247
3544
  }
3248
3545
  }
@@ -3250,37 +3547,37 @@ async function applyFunctionMigrations(plans) {
3250
3547
  if (plans.length === 0) {
3251
3548
  return;
3252
3549
  }
3253
- console.log(chalk14.blue("\n\u{1F4E6} Applying function package migrations:"));
3550
+ console.log(chalk15.blue("\n\u{1F4E6} Applying function package migrations:"));
3254
3551
  plans.forEach((plan) => {
3255
- console.log(chalk14.dim(` - ${plan.packageName}`));
3552
+ console.log(chalk15.dim(` - ${plan.packageName}`));
3256
3553
  });
3257
3554
  try {
3258
3555
  await executeFunctionMigrations(plans);
3259
- console.log(chalk14.green("\n\u2705 All function migrations applied\n"));
3556
+ console.log(chalk15.green("\n\u2705 All function migrations applied\n"));
3260
3557
  } catch (error) {
3261
- console.error(chalk14.red("\n\u274C Failed to apply function package migrations"));
3262
- console.error(chalk14.red(error instanceof Error ? error.message : "Unknown error"));
3263
- console.error(chalk14.yellow("Project schema changes (if any) were already applied \u2014 only function package migrations failed."));
3558
+ console.error(chalk15.red("\n\u274C Failed to apply function package migrations"));
3559
+ console.error(chalk15.red(error instanceof Error ? error.message : "Unknown error"));
3560
+ console.error(chalk15.yellow("Project schema changes (if any) were already applied \u2014 only function package migrations failed."));
3264
3561
  process.exit(1);
3265
3562
  }
3266
3563
  }
3267
3564
 
3268
3565
  // src/commands/db/migrate.ts
3269
- import chalk19 from "chalk";
3270
- import { join as join20 } from "path";
3271
- import { existsSync as existsSync21 } from "fs";
3566
+ import chalk20 from "chalk";
3567
+ import { join as join21 } from "path";
3568
+ import { existsSync as existsSync22 } from "fs";
3272
3569
 
3273
3570
  // src/commands/db/backup.ts
3274
3571
  import { promises as fs3 } from "fs";
3275
3572
  import path3 from "path";
3276
3573
  import { spawn as spawn2 } from "child_process";
3277
- import chalk18 from "chalk";
3574
+ import chalk19 from "chalk";
3278
3575
  import ora7 from "ora";
3279
3576
 
3280
3577
  // src/commands/db/utils/database.ts
3281
3578
  import net from "net";
3282
- import chalk15 from "chalk";
3283
- import prompts4 from "prompts";
3579
+ import chalk16 from "chalk";
3580
+ import prompts5 from "prompts";
3284
3581
  function parseDatabaseUrl2(dbUrl) {
3285
3582
  try {
3286
3583
  const url = new URL(dbUrl);
@@ -3304,13 +3601,13 @@ async function confirmDangerousTarget(dbInfo) {
3304
3601
  if (!isRemoteHost(dbInfo.host) && process.env.NODE_ENV !== "production") {
3305
3602
  return;
3306
3603
  }
3307
- const { typed } = await prompts4({
3604
+ const { typed } = await prompts5({
3308
3605
  type: "text",
3309
3606
  name: "typed",
3310
3607
  message: `Remote/production database detected. Type the database name "${dbInfo.database}" to continue:`
3311
3608
  });
3312
3609
  if (typed !== dbInfo.database) {
3313
- console.log(chalk15.gray("Cancelled (database name mismatch)."));
3610
+ console.log(chalk16.gray("Cancelled (database name mismatch)."));
3314
3611
  process.exit(0);
3315
3612
  }
3316
3613
  }
@@ -3361,16 +3658,16 @@ function formatTimestamp() {
3361
3658
 
3362
3659
  // src/commands/db/utils/backup-files.ts
3363
3660
  import { promises as fs2 } from "fs";
3364
- import { existsSync as existsSync20 } from "fs";
3661
+ import { existsSync as existsSync21 } from "fs";
3365
3662
  import path2 from "path";
3366
- import chalk17 from "chalk";
3663
+ import chalk18 from "chalk";
3367
3664
 
3368
3665
  // src/commands/db/utils/metadata.ts
3369
3666
  import { promises as fs } from "fs";
3370
3667
  import path from "path";
3371
3668
  import { promisify } from "util";
3372
3669
  import { exec } from "child_process";
3373
- import chalk16 from "chalk";
3670
+ import chalk17 from "chalk";
3374
3671
  var execAsync = promisify(exec);
3375
3672
  async function collectGitInfo() {
3376
3673
  try {
@@ -3433,7 +3730,7 @@ async function collectMigrationInfo(dbUrl) {
3433
3730
  await pool.end();
3434
3731
  }
3435
3732
  } catch (error) {
3436
- console.log(chalk16.dim("\u26A0\uFE0F Could not fetch migration info"));
3733
+ console.log(chalk17.dim("\u26A0\uFE0F Could not fetch migration info"));
3437
3734
  return void 0;
3438
3735
  }
3439
3736
  }
@@ -3441,9 +3738,9 @@ async function saveBackupMetadata(metadata, backupFilename) {
3441
3738
  const metadataPath = backupFilename.replace(/\.(sql|dump)$/, ".meta.json");
3442
3739
  try {
3443
3740
  await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
3444
- console.log(chalk16.dim(`\u2713 Metadata saved: ${path.basename(metadataPath)}`));
3741
+ console.log(chalk17.dim(`\u2713 Metadata saved: ${path.basename(metadataPath)}`));
3445
3742
  } catch (error) {
3446
- console.log(chalk16.dim("\u26A0\uFE0F Could not save metadata"));
3743
+ console.log(chalk17.dim("\u26A0\uFE0F Could not save metadata"));
3447
3744
  }
3448
3745
  }
3449
3746
  async function loadBackupMetadata(backupFilename) {
@@ -3461,7 +3758,7 @@ async function ensureBackupInGitignore() {
3461
3758
  const gitignorePath = path2.join(process.cwd(), ".gitignore");
3462
3759
  try {
3463
3760
  let content = "";
3464
- let exists = existsSync20(gitignorePath);
3761
+ let exists = existsSync21(gitignorePath);
3465
3762
  if (exists) {
3466
3763
  content = await fs2.readFile(gitignorePath, "utf-8");
3467
3764
  }
@@ -3472,10 +3769,10 @@ async function ensureBackupInGitignore() {
3472
3769
  if (!hasBackupsIgnore) {
3473
3770
  const entry = exists && content && !content.endsWith("\n") ? "\n\n# Database backups\nbackups/\n" : "# Database backups\nbackups/\n";
3474
3771
  await fs2.appendFile(gitignorePath, entry);
3475
- console.log(chalk17.dim("\u2713 Added backups/ to .gitignore"));
3772
+ console.log(chalk18.dim("\u2713 Added backups/ to .gitignore"));
3476
3773
  }
3477
3774
  } catch (error) {
3478
- console.log(chalk17.dim("\u26A0\uFE0F Could not update .gitignore"));
3775
+ console.log(chalk18.dim("\u26A0\uFE0F Could not update .gitignore"));
3479
3776
  }
3480
3777
  }
3481
3778
  async function ensureBackupDir() {
@@ -3483,7 +3780,7 @@ async function ensureBackupDir() {
3483
3780
  try {
3484
3781
  await fs2.mkdir(backupDir, { recursive: true });
3485
3782
  const gitignorePath = path2.join(backupDir, ".gitignore");
3486
- const gitignoreExists = existsSync20(gitignorePath);
3783
+ const gitignoreExists = existsSync21(gitignorePath);
3487
3784
  if (!gitignoreExists) {
3488
3785
  await fs2.writeFile(gitignorePath, "# Ignore all backup files\n*.sql\n*.dump\n*.meta.json\n");
3489
3786
  }
@@ -3522,15 +3819,15 @@ async function listBackupFiles() {
3522
3819
  }
3523
3820
 
3524
3821
  // src/commands/db/backup.ts
3525
- import { env as env3 } from "@spfn/core/config";
3526
- import { loadEnv as loadEnv4 } from "@spfn/core/server";
3822
+ import { env as env4 } from "@spfn/core/config";
3823
+ import { loadEnv as loadEnv5 } from "@spfn/core/server";
3527
3824
  async function dbBackup(options) {
3528
- console.log(chalk18.blue("\u{1F4BE} Creating database backup...\n"));
3529
- loadEnv4();
3530
- const dbUrl = env3.DATABASE_URL;
3825
+ console.log(chalk19.blue("\u{1F4BE} Creating database backup...\n"));
3826
+ loadEnv5();
3827
+ const dbUrl = env4.DATABASE_URL;
3531
3828
  if (!dbUrl) {
3532
- console.error(chalk18.red("\u274C DATABASE_URL not found in environment"));
3533
- console.log(chalk18.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
3829
+ console.error(chalk19.red("\u274C DATABASE_URL not found in environment"));
3830
+ console.log(chalk19.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
3534
3831
  process.exit(1);
3535
3832
  }
3536
3833
  const dbInfo = parseDatabaseUrl2(dbUrl);
@@ -3540,7 +3837,7 @@ async function dbBackup(options) {
3540
3837
  const ext = format === "sql" ? "sql" : "dump";
3541
3838
  const filename = options.output || path3.join(backupDir, `${dbInfo.database}_${timestamp}.${ext}`);
3542
3839
  if (options.dataOnly && options.schemaOnly) {
3543
- console.error(chalk18.red("\u274C Cannot use --data-only and --schema-only together"));
3840
+ console.error(chalk19.red("\u274C Cannot use --data-only and --schema-only together"));
3544
3841
  process.exit(1);
3545
3842
  }
3546
3843
  const args = [
@@ -3586,11 +3883,11 @@ async function dbBackup(options) {
3586
3883
  const stats = await fs3.stat(filename);
3587
3884
  const size = formatBytes(stats.size);
3588
3885
  spinner.succeed("Backup created");
3589
- console.log(chalk18.green(`
3886
+ console.log(chalk19.green(`
3590
3887
  \u2705 Backup created successfully`));
3591
- console.log(chalk18.gray(` File: ${filename}`));
3592
- console.log(chalk18.gray(` Size: ${size}`));
3593
- console.log(chalk18.dim("\n\u{1F4CB} Collecting metadata..."));
3888
+ console.log(chalk19.gray(` File: ${filename}`));
3889
+ console.log(chalk19.gray(` Size: ${size}`));
3890
+ console.log(chalk19.dim("\n\u{1F4CB} Collecting metadata..."));
3594
3891
  const [gitInfo, migrationInfo] = await Promise.all([
3595
3892
  collectGitInfo(),
3596
3893
  collectMigrationInfo(dbUrl)
@@ -3630,19 +3927,19 @@ async function dbBackup(options) {
3630
3927
  reject(error);
3631
3928
  });
3632
3929
  }).catch((error) => {
3633
- console.error(chalk18.red("\n\u274C Failed to create backup"));
3930
+ console.error(chalk19.red("\n\u274C Failed to create backup"));
3634
3931
  if (errorOutput.includes("pg_dump: command not found") || errorOutput.includes("not found")) {
3635
- console.error(chalk18.yellow("\n\u{1F4A1} pg_dump is not installed. Please install PostgreSQL client tools."));
3932
+ console.error(chalk19.yellow("\n\u{1F4A1} pg_dump is not installed. Please install PostgreSQL client tools."));
3636
3933
  } else {
3637
- console.error(chalk18.red(error instanceof Error ? error.message : "Unknown error"));
3934
+ console.error(chalk19.red(error instanceof Error ? error.message : "Unknown error"));
3638
3935
  }
3639
3936
  process.exit(1);
3640
3937
  });
3641
3938
  }
3642
3939
 
3643
3940
  // src/commands/db/migrate.ts
3644
- import { env as env4 } from "@spfn/core/config";
3645
- import { loadEnv as loadEnv5 } from "@spfn/core/server";
3941
+ import { env as env5 } from "@spfn/core/config";
3942
+ import { loadEnv as loadEnv6 } from "@spfn/core/server";
3646
3943
  var PROJECT_MIGRATIONS_TABLE = "__drizzle_migrations";
3647
3944
  async function dbMigrate(options = {}) {
3648
3945
  try {
@@ -3651,7 +3948,7 @@ async function dbMigrate(options = {}) {
3651
3948
  process.exit(1);
3652
3949
  }
3653
3950
  if (options.withBackup) {
3654
- console.log(chalk19.blue("\u{1F4E6} Creating pre-migration backup...\n"));
3951
+ console.log(chalk20.blue("\u{1F4E6} Creating pre-migration backup...\n"));
3655
3952
  await dbBackup({
3656
3953
  format: "custom",
3657
3954
  tag: "pre-migration",
@@ -3662,68 +3959,108 @@ async function dbMigrate(options = {}) {
3662
3959
  const { drizzle } = await import("drizzle-orm/postgres-js");
3663
3960
  const { migrate } = await import("drizzle-orm/postgres-js/migrator");
3664
3961
  const postgres = await import("postgres");
3665
- loadEnv5();
3666
- if (!env4.DATABASE_URL) {
3667
- console.error(chalk19.red("\u274C DATABASE_URL not found in environment"));
3962
+ loadEnv6();
3963
+ if (!env5.DATABASE_URL) {
3964
+ console.error(chalk20.red("\u274C DATABASE_URL not found in environment"));
3668
3965
  process.exit(1);
3669
3966
  }
3670
3967
  const { discoverFunctionMigrations: discoverFunctionMigrations2, loadFunctionMigrationPlans: loadFunctionMigrationPlans2, executeFunctionMigrations: executeFunctionMigrations2 } = await Promise.resolve().then(() => (init_function_migrations(), function_migrations_exports));
3671
3968
  const functions = discoverFunctionMigrations2(process.cwd());
3672
3969
  if (functions.length > 0) {
3673
- console.log(chalk19.blue("\u{1F4E6} Applying function package migrations:"));
3970
+ console.log(chalk20.blue("\u{1F4E6} Applying function package migrations:"));
3674
3971
  functions.forEach((func) => {
3675
- console.log(chalk19.dim(` - ${func.packageName}`));
3972
+ console.log(chalk20.dim(` - ${func.packageName}`));
3676
3973
  });
3677
3974
  await executeFunctionMigrations2(loadFunctionMigrationPlans2(functions));
3678
- console.log(chalk19.green("\u2705 Function migrations applied\n"));
3975
+ console.log(chalk20.green("\u2705 Function migrations applied\n"));
3679
3976
  }
3680
- const projectMigrationsDir = join20(process.cwd(), "src/server/drizzle");
3681
- if (existsSync21(projectMigrationsDir)) {
3682
- const projConn = postgres.default(env4.DATABASE_URL, { max: 1 });
3977
+ const projectMigrationsDir = join21(process.cwd(), "src/server/drizzle");
3978
+ if (existsSync22(projectMigrationsDir)) {
3979
+ const projConn = postgres.default(env5.DATABASE_URL, { max: 1 });
3683
3980
  const projDb = drizzle({ client: projConn });
3684
3981
  try {
3685
- console.log(chalk19.blue("\u{1F4E6} Running project migrations..."));
3982
+ console.log(chalk20.blue("\u{1F4E6} Running project migrations..."));
3686
3983
  await migrate(projDb, {
3687
3984
  migrationsFolder: projectMigrationsDir,
3688
3985
  migrationsTable: PROJECT_MIGRATIONS_TABLE
3689
3986
  });
3690
- console.log(chalk19.green("\u2705 Project migrations applied successfully"));
3987
+ console.log(chalk20.green("\u2705 Project migrations applied successfully"));
3691
3988
  } finally {
3692
3989
  await projConn.end();
3693
3990
  }
3694
3991
  } else {
3695
- console.log(chalk19.dim("No project migrations found (src/server/drizzle)"));
3992
+ console.log(chalk20.dim("No project migrations found (src/server/drizzle)"));
3993
+ }
3994
+ }
3995
+
3996
+ // src/commands/db/status.ts
3997
+ import chalk21 from "chalk";
3998
+ init_migration_status();
3999
+ async function dbStatus() {
4000
+ try {
4001
+ validateDatabasePrerequisites();
4002
+ } catch (error) {
4003
+ process.exit(1);
4004
+ }
4005
+ let status;
4006
+ try {
4007
+ status = await getMigrationStatus(process.cwd());
4008
+ } catch (error) {
4009
+ console.error(chalk21.red("\u274C Failed to read migration status"));
4010
+ console.error(chalk21.red(error instanceof Error ? error.message : "Unknown error"));
4011
+ process.exit(1);
4012
+ }
4013
+ const targets = [...status.packages, ...status.project ? [status.project] : []];
4014
+ if (targets.length === 0) {
4015
+ console.log(chalk21.dim("No migrations found (no @spfn packages with migrations, no src/server/drizzle)."));
4016
+ return;
4017
+ }
4018
+ console.log(chalk21.blue("\u{1F4E6} Migration status:\n"));
4019
+ for (const target of targets) {
4020
+ const state = target.pending === 0 ? chalk21.green(`${target.applied}/${target.total} applied`) : chalk21.yellow(`${target.applied}/${target.total} applied, ${target.pending} pending`);
4021
+ console.log(` ${target.name.padEnd(32)} ${state}`);
4022
+ for (const tag of target.pendingTags) {
4023
+ console.log(chalk21.dim(` - ${tag}`));
4024
+ }
4025
+ }
4026
+ const pendingTotal = targets.reduce((sum, t) => sum + t.pending, 0);
4027
+ if (pendingTotal === 0) {
4028
+ console.log(chalk21.green("\n\u2705 Database is up to date\n"));
4029
+ } else {
4030
+ console.log(chalk21.yellow(`
4031
+ \u26A0\uFE0F ${pendingTotal} pending migration(s) \u2014 run: pnpm spfn db migrate
4032
+ `));
3696
4033
  }
3697
4034
  }
3698
4035
 
3699
4036
  // src/commands/db/studio.ts
3700
- import chalk20 from "chalk";
3701
- import { existsSync as existsSync22, writeFileSync as writeFileSync14, unlinkSync as unlinkSync3 } from "fs";
4037
+ import chalk22 from "chalk";
4038
+ import { existsSync as existsSync23, writeFileSync as writeFileSync14, unlinkSync as unlinkSync3 } from "fs";
3702
4039
  import { spawn as spawn3 } from "child_process";
3703
- import { env as env5 } from "@spfn/core/config";
4040
+ import { env as env6 } from "@spfn/core/config";
3704
4041
  async function dbStudio(requestedPort) {
3705
- console.log(chalk20.blue("\u{1F3A8} Opening Drizzle Studio...\n"));
4042
+ console.log(chalk22.blue("\u{1F3A8} Opening Drizzle Studio...\n"));
3706
4043
  const defaultPort = 4983;
3707
4044
  const startPort = requestedPort || defaultPort;
3708
4045
  let port;
3709
4046
  try {
3710
4047
  port = await findAvailablePort(startPort);
3711
4048
  if (port !== startPort) {
3712
- console.log(chalk20.yellow(`\u26A0\uFE0F Port ${startPort} is in use, using port ${port} instead
4049
+ console.log(chalk22.yellow(`\u26A0\uFE0F Port ${startPort} is in use, using port ${port} instead
3713
4050
  `));
3714
4051
  }
3715
4052
  } catch (error) {
3716
- console.error(chalk20.red(error instanceof Error ? error.message : "Failed to find available port"));
4053
+ console.error(chalk22.red(error instanceof Error ? error.message : "Failed to find available port"));
3717
4054
  process.exit(1);
3718
4055
  }
3719
- const hasUserConfig = existsSync22("./drizzle.config.ts");
4056
+ const hasUserConfig = existsSync23("./drizzle.config.ts");
3720
4057
  const tempConfigPath = `./drizzle.config.${process.pid}.${Date.now()}.temp.ts`;
3721
4058
  try {
3722
4059
  const configPath = hasUserConfig ? "./drizzle.config.ts" : tempConfigPath;
3723
4060
  if (!hasUserConfig) {
3724
- if (!env5.DATABASE_URL) {
3725
- console.error(chalk20.red("\u274C DATABASE_URL not found in environment"));
3726
- console.log(chalk20.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
4061
+ if (!env6.DATABASE_URL) {
4062
+ console.error(chalk22.red("\u274C DATABASE_URL not found in environment"));
4063
+ console.log(chalk22.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
3727
4064
  process.exit(1);
3728
4065
  }
3729
4066
  const { generateDrizzleConfigFile } = await import("@spfn/core/db");
@@ -3734,34 +4071,34 @@ async function dbStudio(requestedPort) {
3734
4071
  // Expand glob patterns for Studio compatibility
3735
4072
  });
3736
4073
  writeFileSync14(tempConfigPath, configContent);
3737
- console.log(chalk20.dim("Using auto-generated Drizzle config\n"));
4074
+ console.log(chalk22.dim("Using auto-generated Drizzle config\n"));
3738
4075
  }
3739
4076
  const studioProcess = spawn3("drizzle-kit", ["studio", `--port=${port}`, `--config=${configPath}`], {
3740
4077
  stdio: "inherit",
3741
4078
  shell: true,
3742
- env: shouldRelaxDbTls(env5.DATABASE_URL) ? { ...process.env, NODE_TLS_REJECT_UNAUTHORIZED: "0" } : { ...process.env }
4079
+ env: shouldRelaxDbTls(env6.DATABASE_URL) ? { ...process.env, NODE_TLS_REJECT_UNAUTHORIZED: "0" } : { ...process.env }
3743
4080
  });
3744
4081
  const cleanup = () => {
3745
- if (!hasUserConfig && existsSync22(tempConfigPath)) {
4082
+ if (!hasUserConfig && existsSync23(tempConfigPath)) {
3746
4083
  unlinkSync3(tempConfigPath);
3747
4084
  }
3748
4085
  };
3749
4086
  studioProcess.on("exit", (code) => {
3750
4087
  cleanup();
3751
4088
  if (code !== 0 && code !== null) {
3752
- console.error(chalk20.red(`
4089
+ console.error(chalk22.red(`
3753
4090
  \u274C Drizzle Studio exited with code ${code}`));
3754
4091
  process.exit(code);
3755
4092
  }
3756
4093
  });
3757
4094
  studioProcess.on("error", (error) => {
3758
4095
  cleanup();
3759
- console.error(chalk20.red("\u274C Failed to start Drizzle Studio"));
3760
- console.error(chalk20.red(error.message));
4096
+ console.error(chalk22.red("\u274C Failed to start Drizzle Studio"));
4097
+ console.error(chalk22.red(error.message));
3761
4098
  process.exit(1);
3762
4099
  });
3763
4100
  process.on("SIGINT", () => {
3764
- console.log(chalk20.yellow("\n\n\u{1F44B} Shutting down Drizzle Studio..."));
4101
+ console.log(chalk22.yellow("\n\n\u{1F44B} Shutting down Drizzle Studio..."));
3765
4102
  studioProcess.kill("SIGTERM");
3766
4103
  cleanup();
3767
4104
  process.exit(0);
@@ -3772,39 +4109,39 @@ async function dbStudio(requestedPort) {
3772
4109
  process.exit(0);
3773
4110
  });
3774
4111
  } catch (error) {
3775
- if (!hasUserConfig && existsSync22(tempConfigPath)) {
4112
+ if (!hasUserConfig && existsSync23(tempConfigPath)) {
3776
4113
  unlinkSync3(tempConfigPath);
3777
4114
  }
3778
- console.error(chalk20.red("\u274C Failed to start Drizzle Studio"));
3779
- console.error(chalk20.red(error instanceof Error ? error.message : "Unknown error"));
4115
+ console.error(chalk22.red("\u274C Failed to start Drizzle Studio"));
4116
+ console.error(chalk22.red(error instanceof Error ? error.message : "Unknown error"));
3780
4117
  process.exit(1);
3781
4118
  }
3782
4119
  }
3783
4120
 
3784
4121
  // src/commands/db/drop.ts
3785
- import chalk21 from "chalk";
3786
- import prompts5 from "prompts";
3787
- import { env as env6 } from "@spfn/core/config";
3788
- import { loadEnv as loadEnv6 } from "@spfn/core/server";
4122
+ import chalk23 from "chalk";
4123
+ import prompts6 from "prompts";
4124
+ import { env as env7 } from "@spfn/core/config";
4125
+ import { loadEnv as loadEnv7 } from "@spfn/core/server";
3789
4126
  async function dbDrop() {
3790
- loadEnv6();
3791
- if (!env6.DATABASE_URL) {
3792
- console.error(chalk21.red("\u274C DATABASE_URL not found in environment"));
3793
- console.log(chalk21.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
4127
+ loadEnv7();
4128
+ if (!env7.DATABASE_URL) {
4129
+ console.error(chalk23.red("\u274C DATABASE_URL not found in environment"));
4130
+ console.log(chalk23.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
3794
4131
  process.exit(1);
3795
4132
  }
3796
- const dbInfo = parseDatabaseUrl2(env6.DATABASE_URL);
3797
- console.log(chalk21.yellow("\u26A0\uFE0F WARNING: This will drop all tables in your database!"));
3798
- console.log(chalk21.dim(` Target: ${dbInfo.database} @ ${dbInfo.host}:${dbInfo.port}
4133
+ const dbInfo = parseDatabaseUrl2(env7.DATABASE_URL);
4134
+ console.log(chalk23.yellow("\u26A0\uFE0F WARNING: This will drop all tables in your database!"));
4135
+ console.log(chalk23.dim(` Target: ${dbInfo.database} @ ${dbInfo.host}:${dbInfo.port}
3799
4136
  `));
3800
- const { confirm } = await prompts5({
4137
+ const { confirm } = await prompts6({
3801
4138
  type: "confirm",
3802
4139
  name: "confirm",
3803
4140
  message: "Are you sure you want to drop all tables?",
3804
4141
  initial: false
3805
4142
  });
3806
4143
  if (!confirm) {
3807
- console.log(chalk21.gray("Cancelled."));
4144
+ console.log(chalk23.gray("Cancelled."));
3808
4145
  process.exit(0);
3809
4146
  }
3810
4147
  await confirmDangerousTarget(dbInfo);
@@ -3817,16 +4154,16 @@ async function dbDrop() {
3817
4154
  }
3818
4155
 
3819
4156
  // src/commands/db/check.ts
3820
- import chalk22 from "chalk";
4157
+ import chalk24 from "chalk";
3821
4158
  import ora8 from "ora";
3822
4159
  async function dbCheck() {
3823
- const spinner = ora8("Checking database connection...").start();
4160
+ const spinner = ora8("Checking migration files...").start();
3824
4161
  try {
3825
4162
  await runDrizzleCommand("check");
3826
- spinner.succeed("Database connection OK");
4163
+ spinner.succeed("Migration files consistent");
3827
4164
  } catch (error) {
3828
- spinner.fail("Database connection failed");
3829
- console.error(chalk22.red(error instanceof Error ? error.message : "Unknown error"));
4165
+ spinner.fail("Migration file check failed");
4166
+ console.error(chalk24.red(error instanceof Error ? error.message : "Unknown error"));
3830
4167
  process.exit(1);
3831
4168
  }
3832
4169
  }
@@ -3834,28 +4171,28 @@ async function dbCheck() {
3834
4171
  // src/commands/db/restore.ts
3835
4172
  import path4 from "path";
3836
4173
  import { spawn as spawn4 } from "child_process";
3837
- import chalk23 from "chalk";
4174
+ import chalk25 from "chalk";
3838
4175
  import ora9 from "ora";
3839
- import prompts6 from "prompts";
3840
- import { env as env7 } from "@spfn/core/config";
3841
- import { loadEnv as loadEnv7 } from "@spfn/core/server";
4176
+ import prompts7 from "prompts";
4177
+ import { env as env8 } from "@spfn/core/config";
4178
+ import { loadEnv as loadEnv8 } from "@spfn/core/server";
3842
4179
  async function dbRestore(backupFile, options = {}) {
3843
- console.log(chalk23.blue("\u267B\uFE0F Restoring database from backup...\n"));
3844
- loadEnv7();
3845
- const dbUrl = env7.DATABASE_URL;
4180
+ console.log(chalk25.blue("\u267B\uFE0F Restoring database from backup...\n"));
4181
+ loadEnv8();
4182
+ const dbUrl = env8.DATABASE_URL;
3846
4183
  if (!dbUrl) {
3847
- console.error(chalk23.red("\u274C DATABASE_URL not found in environment"));
3848
- console.log(chalk23.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
4184
+ console.error(chalk25.red("\u274C DATABASE_URL not found in environment"));
4185
+ console.log(chalk25.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
3849
4186
  process.exit(1);
3850
4187
  }
3851
4188
  let file = backupFile;
3852
4189
  if (!file) {
3853
4190
  const backups = await listBackupFiles();
3854
4191
  if (backups.length === 0) {
3855
- console.log(chalk23.yellow("No backups found in ./backups directory"));
4192
+ console.log(chalk25.yellow("No backups found in ./backups directory"));
3856
4193
  process.exit(0);
3857
4194
  }
3858
- const { selected } = await prompts6({
4195
+ const { selected } = await prompts7({
3859
4196
  type: "select",
3860
4197
  name: "selected",
3861
4198
  message: "Select backup to restore:",
@@ -3865,31 +4202,31 @@ async function dbRestore(backupFile, options = {}) {
3865
4202
  }))
3866
4203
  });
3867
4204
  if (!selected) {
3868
- console.log(chalk23.gray("Cancelled"));
4205
+ console.log(chalk25.gray("Cancelled"));
3869
4206
  process.exit(0);
3870
4207
  }
3871
4208
  file = selected;
3872
4209
  }
3873
4210
  if (!file) {
3874
- console.error(chalk23.red("\u274C No backup file selected"));
4211
+ console.error(chalk25.red("\u274C No backup file selected"));
3875
4212
  process.exit(1);
3876
4213
  }
3877
4214
  const metadata = await loadBackupMetadata(file);
3878
4215
  if (metadata) {
3879
- console.log(chalk23.blue("\n\u{1F4CB} Backup Information:\n"));
3880
- console.log(chalk23.dim(` Database: ${metadata.database}`));
3881
- console.log(chalk23.dim(` Created: ${new Date(metadata.timestamp).toLocaleString()}`));
4216
+ console.log(chalk25.blue("\n\u{1F4CB} Backup Information:\n"));
4217
+ console.log(chalk25.dim(` Database: ${metadata.database}`));
4218
+ console.log(chalk25.dim(` Created: ${new Date(metadata.timestamp).toLocaleString()}`));
3882
4219
  if (metadata.environment) {
3883
- console.log(chalk23.dim(` Environment: ${metadata.environment}`));
4220
+ console.log(chalk25.dim(` Environment: ${metadata.environment}`));
3884
4221
  }
3885
4222
  if (metadata.tags && metadata.tags.length > 0) {
3886
- console.log(chalk23.dim(` Tags: ${metadata.tags.join(", ")}`));
4223
+ console.log(chalk25.dim(` Tags: ${metadata.tags.join(", ")}`));
3887
4224
  }
3888
4225
  if (metadata.backup.dataOnly) {
3889
- console.log(chalk23.yellow(" \u26A0\uFE0F Data-only backup (no schema)"));
4226
+ console.log(chalk25.yellow(" \u26A0\uFE0F Data-only backup (no schema)"));
3890
4227
  }
3891
4228
  if (metadata.backup.schemaOnly) {
3892
- console.log(chalk23.yellow(" \u26A0\uFE0F Schema-only backup (no data)"));
4229
+ console.log(chalk25.yellow(" \u26A0\uFE0F Schema-only backup (no data)"));
3893
4230
  }
3894
4231
  const warnings2 = [];
3895
4232
  const [currentGitInfo, currentMigrationInfo] = await Promise.all([
@@ -3912,25 +4249,25 @@ async function dbRestore(backupFile, options = {}) {
3912
4249
  }
3913
4250
  }
3914
4251
  if (warnings2.length > 0) {
3915
- console.log(chalk23.yellow("\n\u26A0\uFE0F Version Warnings:\n"));
3916
- warnings2.forEach((warning) => console.log(chalk23.yellow(` - ${warning}`)));
4252
+ console.log(chalk25.yellow("\n\u26A0\uFE0F Version Warnings:\n"));
4253
+ warnings2.forEach((warning) => console.log(chalk25.yellow(` - ${warning}`)));
3917
4254
  console.log("");
3918
4255
  }
3919
4256
  }
3920
4257
  const dbInfo = parseDatabaseUrl2(dbUrl);
3921
- const { confirm } = await prompts6({
4258
+ const { confirm } = await prompts7({
3922
4259
  type: "confirm",
3923
4260
  name: "confirm",
3924
- message: chalk23.yellow(`\u26A0\uFE0F This will replace all data in "${dbInfo.database}" @ ${dbInfo.host}:${dbInfo.port}. Continue?`),
4261
+ message: chalk25.yellow(`\u26A0\uFE0F This will replace all data in "${dbInfo.database}" @ ${dbInfo.host}:${dbInfo.port}. Continue?`),
3925
4262
  initial: false
3926
4263
  });
3927
4264
  if (!confirm) {
3928
- console.log(chalk23.gray("Cancelled"));
4265
+ console.log(chalk25.gray("Cancelled"));
3929
4266
  process.exit(0);
3930
4267
  }
3931
4268
  await confirmDangerousTarget(dbInfo);
3932
4269
  if (options.dataOnly && options.schemaOnly) {
3933
- console.error(chalk23.red("\u274C Cannot use --data-only and --schema-only together"));
4270
+ console.error(chalk25.red("\u274C Cannot use --data-only and --schema-only together"));
3934
4271
  process.exit(1);
3935
4272
  }
3936
4273
  const ext = path4.extname(file);
@@ -3958,11 +4295,11 @@ async function dbRestore(backupFile, options = {}) {
3958
4295
  args.push(file);
3959
4296
  } else {
3960
4297
  if (options.drop) {
3961
- console.log(chalk23.yellow("\u26A0\uFE0F Note: --drop only works with custom format backups (.dump) and was ignored."));
4298
+ console.log(chalk25.yellow("\u26A0\uFE0F Note: --drop only works with custom format backups (.dump) and was ignored."));
3962
4299
  }
3963
4300
  if (options.dataOnly || options.schemaOnly) {
3964
- console.log(chalk23.yellow("\u26A0\uFE0F Note: --data-only and --schema-only options only work with custom format backups (.dump)"));
3965
- console.log(chalk23.yellow(" For SQL files, the backup must have been created with the desired option.\n"));
4301
+ console.log(chalk25.yellow("\u26A0\uFE0F Note: --data-only and --schema-only options only work with custom format backups (.dump)"));
4302
+ console.log(chalk25.yellow(" For SQL files, the backup must have been created with the desired option.\n"));
3966
4303
  }
3967
4304
  args.push("-h", dbInfo.host);
3968
4305
  args.push("-p", dbInfo.port);
@@ -4005,7 +4342,7 @@ async function dbRestore(backupFile, options = {}) {
4005
4342
  }
4006
4343
  if (verbose) {
4007
4344
  spinner.stop();
4008
- console.log(chalk23.dim(` ${line.trim()}`));
4345
+ console.log(chalk25.dim(` ${line.trim()}`));
4009
4346
  spinner.start();
4010
4347
  }
4011
4348
  }
@@ -4013,7 +4350,7 @@ async function dbRestore(backupFile, options = {}) {
4013
4350
  restoreProcess.stdout?.on("data", (data) => {
4014
4351
  if (verbose) {
4015
4352
  spinner.stop();
4016
- console.log(chalk23.dim(` ${data.toString().trim()}`));
4353
+ console.log(chalk25.dim(` ${data.toString().trim()}`));
4017
4354
  spinner.start();
4018
4355
  }
4019
4356
  });
@@ -4023,31 +4360,31 @@ async function dbRestore(backupFile, options = {}) {
4023
4360
  const summary = objectCount > 0 ? ` (${objectCount} objects)` : "";
4024
4361
  spinner.succeed(`Restore completed${summary}`);
4025
4362
  if (warnings.length > 0) {
4026
- console.log(chalk23.yellow(`
4363
+ console.log(chalk25.yellow(`
4027
4364
  \u26A0\uFE0F Warnings during restore (${warnings.length}):
4028
4365
  `));
4029
4366
  for (const w of warnings) {
4030
- console.log(chalk23.yellow(` - ${w}`));
4367
+ console.log(chalk25.yellow(` - ${w}`));
4031
4368
  }
4032
4369
  }
4033
- console.log(chalk23.green("\n\u2705 Database restored successfully"));
4370
+ console.log(chalk25.green("\n\u2705 Database restored successfully"));
4034
4371
  resolve3();
4035
4372
  } else {
4036
4373
  spinner.fail("Restore failed");
4037
4374
  if (errors.length > 0) {
4038
- console.error(chalk23.red(`
4375
+ console.error(chalk25.red(`
4039
4376
  \u274C Errors (${errors.length}):
4040
4377
  `));
4041
4378
  for (const e of errors) {
4042
- console.error(chalk23.red(` - ${e}`));
4379
+ console.error(chalk25.red(` - ${e}`));
4043
4380
  }
4044
4381
  }
4045
4382
  if (warnings.length > 0) {
4046
- console.log(chalk23.yellow(`
4383
+ console.log(chalk25.yellow(`
4047
4384
  \u26A0\uFE0F Warnings (${warnings.length}):
4048
4385
  `));
4049
4386
  for (const w of warnings) {
4050
- console.log(chalk23.yellow(` - ${w}`));
4387
+ console.log(chalk25.yellow(` - ${w}`));
4051
4388
  }
4052
4389
  }
4053
4390
  const fallback = errors.length === 0 && warnings.length === 0 ? "Restore failed with no output" : "";
@@ -4061,7 +4398,7 @@ async function dbRestore(backupFile, options = {}) {
4061
4398
  }).catch((error) => {
4062
4399
  const msg = error instanceof Error ? error.message : "Unknown error";
4063
4400
  if (msg) {
4064
- console.error(chalk23.red(`
4401
+ console.error(chalk25.red(`
4065
4402
  \u274C ${msg}`));
4066
4403
  }
4067
4404
  process.exit(1);
@@ -4069,17 +4406,17 @@ async function dbRestore(backupFile, options = {}) {
4069
4406
  }
4070
4407
 
4071
4408
  // src/commands/db/list.ts
4072
- import chalk24 from "chalk";
4409
+ import chalk26 from "chalk";
4073
4410
  async function dbBackupList() {
4074
- console.log(chalk24.blue("\u{1F4CB} Database backups:\n"));
4411
+ console.log(chalk26.blue("\u{1F4CB} Database backups:\n"));
4075
4412
  const backups = await listBackupFiles();
4076
4413
  if (backups.length === 0) {
4077
- console.log(chalk24.yellow("No backups found in ./backups directory"));
4078
- console.log(chalk24.gray("\n\u{1F4A1} Create a backup with: pnpm spfn db backup\n"));
4414
+ console.log(chalk26.yellow("No backups found in ./backups directory"));
4415
+ console.log(chalk26.gray("\n\u{1F4A1} Create a backup with: pnpm spfn db backup\n"));
4079
4416
  return;
4080
4417
  }
4081
- console.log(chalk24.bold(" Date Size File"));
4082
- console.log(chalk24.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
4418
+ console.log(chalk26.bold(" Date Size File"));
4419
+ console.log(chalk26.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
4083
4420
  backups.forEach((backup) => {
4084
4421
  const date = backup.date.toLocaleString("en-US", {
4085
4422
  year: "numeric",
@@ -4090,23 +4427,23 @@ async function dbBackupList() {
4090
4427
  second: "2-digit"
4091
4428
  });
4092
4429
  const sizeStr = backup.size.padEnd(10);
4093
- console.log(chalk24.white(` ${date} ${sizeStr} ${backup.name}`));
4430
+ console.log(chalk26.white(` ${date} ${sizeStr} ${backup.name}`));
4094
4431
  });
4095
- console.log(chalk24.gray(`
4432
+ console.log(chalk26.gray(`
4096
4433
  Total: ${backups.length} backup(s)
4097
4434
  `));
4098
4435
  }
4099
4436
 
4100
4437
  // src/commands/db/clean.ts
4101
4438
  import { promises as fs4 } from "fs";
4102
- import chalk25 from "chalk";
4439
+ import chalk27 from "chalk";
4103
4440
  import ora10 from "ora";
4104
- import prompts7 from "prompts";
4441
+ import prompts8 from "prompts";
4105
4442
  async function dbBackupClean(options) {
4106
- console.log(chalk25.blue("\u{1F9F9} Cleaning old backups...\n"));
4443
+ console.log(chalk27.blue("\u{1F9F9} Cleaning old backups...\n"));
4107
4444
  const backups = await listBackupFiles();
4108
4445
  if (backups.length === 0) {
4109
- console.log(chalk25.yellow("No backups found"));
4446
+ console.log(chalk27.yellow("No backups found"));
4110
4447
  return;
4111
4448
  }
4112
4449
  let toDelete = [];
@@ -4123,22 +4460,22 @@ async function dbBackupClean(options) {
4123
4460
  toDelete = backups.slice(defaultKeep);
4124
4461
  }
4125
4462
  if (toDelete.length === 0) {
4126
- console.log(chalk25.green("\u2705 No backups to clean"));
4463
+ console.log(chalk27.green("\u2705 No backups to clean"));
4127
4464
  return;
4128
4465
  }
4129
- console.log(chalk25.yellow(`The following ${toDelete.length} backup(s) will be deleted:
4466
+ console.log(chalk27.yellow(`The following ${toDelete.length} backup(s) will be deleted:
4130
4467
  `));
4131
4468
  toDelete.forEach((backup) => {
4132
- console.log(chalk25.gray(` - ${backup.name} (${backup.size})`));
4469
+ console.log(chalk27.gray(` - ${backup.name} (${backup.size})`));
4133
4470
  });
4134
- const { confirm } = await prompts7({
4471
+ const { confirm } = await prompts8({
4135
4472
  type: "confirm",
4136
4473
  name: "confirm",
4137
4474
  message: "\nProceed with deletion?",
4138
4475
  initial: false
4139
4476
  });
4140
4477
  if (!confirm) {
4141
- console.log(chalk25.gray("Cancelled"));
4478
+ console.log(chalk27.gray("Cancelled"));
4142
4479
  return;
4143
4480
  }
4144
4481
  const spinner = ora10("Deleting backups...").start();
@@ -4149,20 +4486,20 @@ async function dbBackupClean(options) {
4149
4486
  fs4.rm(backup.path.replace(/\.(sql|dump)$/, ".meta.json"), { force: true })
4150
4487
  ]));
4151
4488
  spinner.succeed("Backups deleted");
4152
- console.log(chalk25.green(`
4489
+ console.log(chalk27.green(`
4153
4490
  \u2705 Deleted ${toDelete.length} backup(s)`));
4154
4491
  } catch (error) {
4155
4492
  spinner.fail("Failed to delete backups");
4156
- console.error(chalk25.red(error instanceof Error ? error.message : "Unknown error"));
4493
+ console.error(chalk27.red(error instanceof Error ? error.message : "Unknown error"));
4157
4494
  process.exit(1);
4158
4495
  }
4159
4496
  }
4160
4497
 
4161
4498
  // src/commands/db/reindex.ts
4162
- import { existsSync as existsSync23, readFileSync as readFileSync10, writeFileSync as writeFileSync15, renameSync, copyFileSync } from "fs";
4163
- import { join as join21 } from "path";
4164
- import chalk26 from "chalk";
4165
- import { loadEnv as loadEnv8 } from "@spfn/core/server";
4499
+ import { existsSync as existsSync24, readFileSync as readFileSync10, writeFileSync as writeFileSync15, renameSync, copyFileSync } from "fs";
4500
+ import { join as join22 } from "path";
4501
+ import chalk28 from "chalk";
4502
+ import { loadEnv as loadEnv9 } from "@spfn/core/server";
4166
4503
  function isTimestampPrefix(tag) {
4167
4504
  const prefix = tag.split("_")[0];
4168
4505
  return /^\d{5,}$/.test(prefix);
@@ -4178,19 +4515,19 @@ function parseTag(tag) {
4178
4515
  };
4179
4516
  }
4180
4517
  async function dbReindex(options = {}) {
4181
- loadEnv8();
4518
+ loadEnv9();
4182
4519
  const { getDrizzleConfig } = await import("@spfn/core/db");
4183
4520
  const config = getDrizzleConfig({ disablePackageDiscovery: true });
4184
4521
  const outDir = config.out;
4185
- const journalPath = join21(outDir, "meta", "_journal.json");
4186
- if (!existsSync23(journalPath)) {
4187
- console.error(chalk26.red("\u274C No _journal.json found at:"), journalPath);
4188
- console.log(chalk26.yellow("\u{1F4A1} Run `spfn db generate` first to create migrations"));
4522
+ const journalPath = join22(outDir, "meta", "_journal.json");
4523
+ if (!existsSync24(journalPath)) {
4524
+ console.error(chalk28.red("\u274C No _journal.json found at:"), journalPath);
4525
+ console.log(chalk28.yellow("\u{1F4A1} Run `spfn db generate` first to create migrations"));
4189
4526
  process.exit(1);
4190
4527
  }
4191
4528
  const journal = JSON.parse(readFileSync10(journalPath, "utf-8"));
4192
4529
  if (journal.entries.length === 0) {
4193
- console.log(chalk26.yellow("No migration entries found \u2014 nothing to reindex."));
4530
+ console.log(chalk28.yellow("No migration entries found \u2014 nothing to reindex."));
4194
4531
  return;
4195
4532
  }
4196
4533
  const renames = [];
@@ -4204,46 +4541,46 @@ async function dbReindex(options = {}) {
4204
4541
  const { prefix: oldPrefix, suffix } = parseTag(entry.tag);
4205
4542
  const newPrefix = String(entry.when);
4206
4543
  const newTag = suffix ? `${newPrefix}_${suffix}` : newPrefix;
4207
- const oldSql = join21(outDir, `${entry.tag}.sql`);
4208
- const newSql = join21(outDir, `${newTag}.sql`);
4209
- if (existsSync23(oldSql)) {
4544
+ const oldSql = join22(outDir, `${entry.tag}.sql`);
4545
+ const newSql = join22(outDir, `${newTag}.sql`);
4546
+ if (existsSync24(oldSql)) {
4210
4547
  renames.push({ type: "sql", from: oldSql, to: newSql });
4211
4548
  }
4212
- const oldSnapshot = join21(outDir, "meta", `${oldPrefix}_snapshot.json`);
4213
- const newSnapshot = join21(outDir, "meta", `${newPrefix}_snapshot.json`);
4214
- if (existsSync23(oldSnapshot)) {
4549
+ const oldSnapshot = join22(outDir, "meta", `${oldPrefix}_snapshot.json`);
4550
+ const newSnapshot = join22(outDir, "meta", `${newPrefix}_snapshot.json`);
4551
+ if (existsSync24(oldSnapshot)) {
4215
4552
  renames.push({ type: "snapshot", from: oldSnapshot, to: newSnapshot });
4216
4553
  }
4217
4554
  tagUpdates.push({ idx: entry.idx, oldTag: entry.tag, newTag });
4218
4555
  }
4219
4556
  if (tagUpdates.length === 0) {
4220
- console.log(chalk26.green("\u2705 All migrations already use timestamp prefix \u2014 nothing to do."));
4557
+ console.log(chalk28.green("\u2705 All migrations already use timestamp prefix \u2014 nothing to do."));
4221
4558
  if (skipped > 0) {
4222
- console.log(chalk26.dim(` (${skipped} entries already timestamp-prefixed)`));
4559
+ console.log(chalk28.dim(` (${skipped} entries already timestamp-prefixed)`));
4223
4560
  }
4224
4561
  return;
4225
4562
  }
4226
- console.log(chalk26.bold("\n\u{1F4CB} Reindex plan:\n"));
4563
+ console.log(chalk28.bold("\n\u{1F4CB} Reindex plan:\n"));
4227
4564
  for (const update of tagUpdates) {
4228
4565
  console.log(
4229
- chalk26.dim(` [${update.idx}]`),
4230
- chalk26.red(update.oldTag),
4231
- chalk26.dim("\u2192"),
4232
- chalk26.green(update.newTag)
4566
+ chalk28.dim(` [${update.idx}]`),
4567
+ chalk28.red(update.oldTag),
4568
+ chalk28.dim("\u2192"),
4569
+ chalk28.green(update.newTag)
4233
4570
  );
4234
4571
  }
4235
- console.log(chalk26.dim(`
4572
+ console.log(chalk28.dim(`
4236
4573
  ${renames.length} file(s) to rename, ${tagUpdates.length} journal tag(s) to update`));
4237
4574
  if (skipped > 0) {
4238
- console.log(chalk26.dim(` ${skipped} entry/entries already timestamp-prefixed (skipped)`));
4575
+ console.log(chalk28.dim(` ${skipped} entry/entries already timestamp-prefixed (skipped)`));
4239
4576
  }
4240
4577
  if (options.dryRun) {
4241
- console.log(chalk26.yellow("\n\u{1F50D} Dry run \u2014 no changes applied."));
4578
+ console.log(chalk28.yellow("\n\u{1F50D} Dry run \u2014 no changes applied."));
4242
4579
  return;
4243
4580
  }
4244
4581
  const backupPath = journalPath + ".bak";
4245
4582
  copyFileSync(journalPath, backupPath);
4246
- console.log(chalk26.dim(`
4583
+ console.log(chalk28.dim(`
4247
4584
  Backed up journal \u2192 ${backupPath}`));
4248
4585
  for (const rename of renames) {
4249
4586
  renameSync(rename.from, rename.to);
@@ -4255,7 +4592,7 @@ async function dbReindex(options = {}) {
4255
4592
  }
4256
4593
  }
4257
4594
  writeFileSync15(journalPath, JSON.stringify(journal, null, 2) + "\n");
4258
- console.log(chalk26.green(`
4595
+ console.log(chalk28.green(`
4259
4596
  \u2705 Reindex complete \u2014 ${tagUpdates.length} migration(s) converted to timestamp prefix.`));
4260
4597
  }
4261
4598
 
@@ -4264,9 +4601,10 @@ var dbCommand = new Command9("db").description("Database management commands (wr
4264
4601
  dbCommand.command("generate").alias("g").description("Generate database migrations from schema changes").action(dbGenerate);
4265
4602
  dbCommand.command("push").description("Push schema changes to database (safe mode by default)").option("--force", "Apply all changes including destructive ones").option("--dry-run", "Show changes without applying").action((options) => dbPush(options));
4266
4603
  dbCommand.command("migrate").alias("m").description("Run pending migrations").option("--with-backup", "Create backup before running migrations").action((options) => dbMigrate(options));
4604
+ dbCommand.command("status").description("Show applied/pending migration status (function packages + project)").action(dbStatus);
4267
4605
  dbCommand.command("studio").description("Open Drizzle Studio (database GUI)").option("-p, --port <port>", "Studio port (auto-finds if in use)").action((options) => dbStudio(options.port ? Number(options.port) : void 0));
4268
4606
  dbCommand.command("drop").description("Drop all database tables (\u26A0\uFE0F dangerous!)").action(dbDrop);
4269
- dbCommand.command("check").description("Check database connection").action(dbCheck);
4607
+ dbCommand.command("check").description("Check migration file consistency (drizzle-kit check)").action(dbCheck);
4270
4608
  dbCommand.command("backup").description("Create a database backup").option("-f, --format <format>", "Backup format (sql or custom)", "sql").option("-o, --output <path>", "Custom output path").option("-s, --schema <name>", "Backup specific schema only").option("--data-only", "Backup data only (no schema)").option("--schema-only", "Backup schema only (no data)").option("--tag <tags>", "Comma-separated tags for this backup").option("--env <environment>", "Environment label (e.g., production, staging)").action((options) => dbBackup(options));
4271
4609
  dbCommand.command("restore [file]").description("Restore database from backup").option("--drop", "Drop existing tables before restore").option("-s, --schema <name>", "Restore specific schema only").option("--data-only", "Restore data only (requires custom format .dump file)").option("--schema-only", "Restore schema only (requires custom format .dump file)").option("-v, --verbose", "Show detailed restore progress").action((file, options) => dbRestore(file, options));
4272
4610
  dbCommand.command("backup:list").description("List all database backups").action(dbBackupList);
@@ -4276,25 +4614,25 @@ dbCommand.command("reindex").description("Convert migration files from sequentia
4276
4614
  // src/commands/add.ts
4277
4615
  init_package_manager();
4278
4616
  import { Command as Command10 } from "commander";
4279
- import { existsSync as existsSync24, readFileSync as readFileSync11 } from "fs";
4280
- import { join as join22 } from "path";
4617
+ import { existsSync as existsSync25, readFileSync as readFileSync11 } from "fs";
4618
+ import { join as join23 } from "path";
4281
4619
  import { execa as execa9 } from "execa";
4282
- import chalk27 from "chalk";
4620
+ import chalk29 from "chalk";
4283
4621
  import ora11 from "ora";
4284
4622
  async function addPackage(packageName) {
4285
4623
  if (!packageName.includes("/")) {
4286
- console.error(chalk27.red("\u274C Please specify full package name"));
4287
- console.log(chalk27.yellow("\n\u{1F4A1} Examples:"));
4288
- console.log(chalk27.gray(" pnpm spfn add @spfn/cms"));
4289
- console.log(chalk27.gray(" pnpm spfn add @mycompany/spfn-analytics"));
4624
+ console.error(chalk29.red("\u274C Please specify full package name"));
4625
+ console.log(chalk29.yellow("\n\u{1F4A1} Examples:"));
4626
+ console.log(chalk29.gray(" pnpm spfn add @spfn/cms"));
4627
+ console.log(chalk29.gray(" pnpm spfn add @mycompany/spfn-analytics"));
4290
4628
  process.exit(1);
4291
4629
  }
4292
- console.log(chalk27.blue(`
4630
+ console.log(chalk29.blue(`
4293
4631
  \u{1F4E6} Setting up ${packageName}...
4294
4632
  `));
4295
- const pkgPath = join22(process.cwd(), "node_modules", ...packageName.split("/"));
4296
- const pkgJsonPath = join22(pkgPath, "package.json");
4297
- if (!existsSync24(pkgJsonPath)) {
4633
+ const pkgPath = join23(process.cwd(), "node_modules", ...packageName.split("/"));
4634
+ const pkgJsonPath = join23(pkgPath, "package.json");
4635
+ if (!existsSync25(pkgJsonPath)) {
4298
4636
  const pm = detectPackageManager(process.cwd());
4299
4637
  const installSpinner = ora11("Installing package...").start();
4300
4638
  try {
@@ -4302,26 +4640,27 @@ async function addPackage(packageName) {
4302
4640
  installSpinner.succeed("Package installed");
4303
4641
  } catch (error) {
4304
4642
  installSpinner.fail("Failed to install package");
4305
- console.error(chalk27.red(error instanceof Error ? error.message : "Unknown error"));
4643
+ console.error(chalk29.red(error instanceof Error ? error.message : "Unknown error"));
4306
4644
  process.exit(1);
4307
4645
  }
4308
4646
  } else {
4309
- console.log(chalk27.gray("\u2713 Package already installed (using local version)\n"));
4647
+ console.log(chalk29.gray("\u2713 Package already installed (using local version)\n"));
4310
4648
  }
4311
- if (!existsSync24(pkgJsonPath)) {
4312
- console.error(chalk27.red(`\u274C Package ${packageName} not found after installation`));
4649
+ if (!existsSync25(pkgJsonPath)) {
4650
+ console.error(chalk29.red(`\u274C Package ${packageName} not found after installation`));
4313
4651
  process.exit(1);
4314
4652
  }
4315
4653
  const pkgJson = JSON.parse(readFileSync11(pkgJsonPath, "utf-8"));
4316
4654
  if (pkgJson.spfn?.migrations) {
4317
- console.log(chalk27.blue(`
4655
+ console.log(chalk29.blue(`
4318
4656
  \u{1F5C4}\uFE0F Setting up database for ${packageName}...
4319
4657
  `));
4320
- const { env: env8 } = await import("@spfn/core/config");
4321
- if (!env8.DATABASE_URL) {
4322
- console.log(chalk27.yellow("\u26A0\uFE0F DATABASE_URL not found"));
4323
- console.log(chalk27.gray("Skipping database setup. Run migrations manually when ready:\n"));
4324
- console.log(chalk27.gray(" pnpm spfn db push\n"));
4658
+ const { env: env9 } = await import("@spfn/core/config");
4659
+ if (!env9.DATABASE_URL) {
4660
+ console.log(chalk29.yellow("\u26A0\uFE0F DATABASE_URL not found \u2014 skipping database setup."));
4661
+ console.log(chalk29.yellow(` ${packageName} tables are created by its bundled migrations, not by schema push.`));
4662
+ console.log(chalk29.cyan(" Once DATABASE_URL is set, run: pnpm spfn db migrate"));
4663
+ console.log(chalk29.gray(" (check state anytime with: pnpm spfn db status)\n"));
4325
4664
  } else {
4326
4665
  const { discoverFunctionMigrations: discoverFunctionMigrations2, loadFunctionMigrationPlans: loadFunctionMigrationPlans2, executeFunctionMigrations: executeFunctionMigrations2 } = await Promise.resolve().then(() => (init_function_migrations(), function_migrations_exports));
4327
4666
  const functions = discoverFunctionMigrations2(process.cwd());
@@ -4333,21 +4672,21 @@ async function addPackage(packageName) {
4333
4672
  spinner.succeed("Migrations applied");
4334
4673
  } catch (error) {
4335
4674
  spinner.fail("Failed to apply migrations");
4336
- console.error(chalk27.red(error instanceof Error ? error.message : "Unknown error"));
4675
+ console.error(chalk29.red(error instanceof Error ? error.message : "Unknown error"));
4337
4676
  process.exit(1);
4338
4677
  }
4339
4678
  } else {
4340
- console.log(chalk27.gray("\u2139\uFE0F No migrations found for this package"));
4679
+ console.log(chalk29.gray("\u2139\uFE0F No migrations found for this package"));
4341
4680
  }
4342
4681
  }
4343
4682
  } else {
4344
- console.log(chalk27.gray("\n\u2139\uFE0F No database migrations to apply"));
4683
+ console.log(chalk29.gray("\n\u2139\uFE0F No database migrations to apply"));
4345
4684
  }
4346
- console.log(chalk27.green(`
4685
+ console.log(chalk29.green(`
4347
4686
  \u2705 ${packageName} installed successfully!
4348
4687
  `));
4349
4688
  if (pkgJson.spfn?.setupMessage) {
4350
- console.log(chalk27.cyan("\u{1F4DA} Setup Guide:"));
4689
+ console.log(chalk29.cyan("\u{1F4DA} Setup Guide:"));
4351
4690
  console.log(pkgJson.spfn.setupMessage);
4352
4691
  console.log();
4353
4692
  }
@@ -4356,8 +4695,8 @@ var addCommand = new Command10("add").description("Install and set up SPFN ecosy
4356
4695
 
4357
4696
  // src/commands/env.ts
4358
4697
  import { Command as Command11 } from "commander";
4359
- import chalk28 from "chalk";
4360
- import { existsSync as existsSync25, readFileSync as readFileSync12, writeFileSync as writeFileSync16 } from "fs";
4698
+ import chalk30 from "chalk";
4699
+ import { existsSync as existsSync26, readFileSync as readFileSync12, writeFileSync as writeFileSync16 } from "fs";
4361
4700
  import { resolve as resolve2 } from "path";
4362
4701
  import { parse as parse2 } from "dotenv";
4363
4702
 
@@ -4413,26 +4752,26 @@ function getEnvFilesForEnvironment(nodeEnv) {
4413
4752
  }
4414
4753
  function formatType(type) {
4415
4754
  const typeColors = {
4416
- string: chalk28.green,
4417
- number: chalk28.blue,
4418
- boolean: chalk28.yellow,
4419
- url: chalk28.cyan,
4420
- enum: chalk28.magenta,
4421
- json: chalk28.red
4755
+ string: chalk30.green,
4756
+ number: chalk30.blue,
4757
+ boolean: chalk30.yellow,
4758
+ url: chalk30.cyan,
4759
+ enum: chalk30.magenta,
4760
+ json: chalk30.red
4422
4761
  };
4423
- return (typeColors[type] || chalk28.white)(type);
4762
+ return (typeColors[type] || chalk30.white)(type);
4424
4763
  }
4425
4764
  function formatDefault(value, type) {
4426
4765
  if (value === void 0) {
4427
- return chalk28.dim("(none)");
4766
+ return chalk30.dim("(none)");
4428
4767
  }
4429
4768
  if (type === "string" || type === "url") {
4430
- return chalk28.green(`"${value}"`);
4769
+ return chalk30.green(`"${value}"`);
4431
4770
  }
4432
4771
  if (type === "boolean") {
4433
- return value ? chalk28.green("true") : chalk28.red("false");
4772
+ return value ? chalk30.green("true") : chalk30.red("false");
4434
4773
  }
4435
- return chalk28.cyan(String(value));
4774
+ return chalk30.cyan(String(value));
4436
4775
  }
4437
4776
  async function listEnvVars(options) {
4438
4777
  const packageName = options.package || "@spfn/core";
@@ -4446,28 +4785,28 @@ async function listEnvVars(options) {
4446
4785
  acc[target].push([key, schema]);
4447
4786
  return acc;
4448
4787
  }, {});
4449
- console.log(chalk28.blue.bold(`
4788
+ console.log(chalk30.blue.bold(`
4450
4789
  \u{1F4CB} Environment Variables by File (${packageName})
4451
4790
  `));
4452
4791
  for (const [file, vars] of Object.entries(grouped)) {
4453
- console.log(chalk28.bold.magenta(`
4792
+ console.log(chalk30.bold.magenta(`
4454
4793
  ${file}`));
4455
- console.log(chalk28.dim("\u2500".repeat(50)));
4794
+ console.log(chalk30.dim("\u2500".repeat(50)));
4456
4795
  for (const [key, schema] of vars) {
4457
4796
  printEnvVar(key, schema);
4458
4797
  }
4459
4798
  }
4460
4799
  } else {
4461
- console.log(chalk28.blue.bold(`
4800
+ console.log(chalk30.blue.bold(`
4462
4801
  \u{1F4CB} Environment Variables (${packageName})
4463
4802
  `));
4464
4803
  for (const [key, schema] of allVars) {
4465
4804
  printEnvVar(key, schema, true);
4466
4805
  }
4467
4806
  }
4468
- console.log(chalk28.dim("\n\u{1F4A1} Tip: Use `spfn env init` to generate .env template files\n"));
4807
+ console.log(chalk30.dim("\n\u{1F4A1} Tip: Use `spfn env init` to generate .env template files\n"));
4469
4808
  } catch (error) {
4470
- console.error(chalk28.red(`
4809
+ console.error(chalk30.red(`
4471
4810
  \u274C ${error instanceof Error ? error.message : "Unknown error"}
4472
4811
  `));
4473
4812
  process.exit(1);
@@ -4475,17 +4814,17 @@ ${file}`));
4475
4814
  }
4476
4815
  function printEnvVar(key, schema, showFile = false) {
4477
4816
  const typeStr = formatType(schema.type);
4478
- const requiredStr = schema.required || schema.default !== void 0 ? chalk28.red("[required]") : chalk28.dim("[optional]");
4479
- const sensitiveStr = schema.sensitive ? chalk28.yellow(" [sensitive]") : "";
4480
- const fileStr = showFile ? chalk28.dim(` \u2192 ${getTargetFile(schema)}`) : "";
4481
- console.log(`${chalk28.bold.cyan(key)} ${chalk28.dim("(")}${typeStr}${chalk28.dim(")")} ${requiredStr}${sensitiveStr}${fileStr}`);
4482
- console.log(` ${chalk28.dim(schema.description)}`);
4817
+ const requiredStr = schema.required || schema.default !== void 0 ? chalk30.red("[required]") : chalk30.dim("[optional]");
4818
+ const sensitiveStr = schema.sensitive ? chalk30.yellow(" [sensitive]") : "";
4819
+ const fileStr = showFile ? chalk30.dim(` \u2192 ${getTargetFile(schema)}`) : "";
4820
+ console.log(`${chalk30.bold.cyan(key)} ${chalk30.dim("(")}${typeStr}${chalk30.dim(")")} ${requiredStr}${sensitiveStr}${fileStr}`);
4821
+ console.log(` ${chalk30.dim(schema.description)}`);
4483
4822
  if (schema.default !== void 0) {
4484
- console.log(` ${chalk28.dim("Default:")} ${formatDefault(schema.default, schema.type)}`);
4823
+ console.log(` ${chalk30.dim("Default:")} ${formatDefault(schema.default, schema.type)}`);
4485
4824
  }
4486
4825
  if (schema.examples && schema.examples.length > 0) {
4487
4826
  const exampleStr = schema.examples.map((ex) => formatDefault(ex, schema.type)).join(", ");
4488
- console.log(` ${chalk28.dim("Examples:")} ${exampleStr}`);
4827
+ console.log(` ${chalk30.dim("Examples:")} ${exampleStr}`);
4489
4828
  }
4490
4829
  console.log();
4491
4830
  }
@@ -4493,7 +4832,7 @@ async function showEnvStats(options) {
4493
4832
  const packageName = options.package || "@spfn/core";
4494
4833
  try {
4495
4834
  const envSchema = await loadEnvSchema(packageName);
4496
- console.log(chalk28.blue.bold(`
4835
+ console.log(chalk30.blue.bold(`
4497
4836
  \u{1F4CA} Environment Variable Statistics (${packageName})
4498
4837
  `));
4499
4838
  const allVars = Object.entries(envSchema);
@@ -4515,24 +4854,24 @@ async function showEnvStats(options) {
4515
4854
  acc[file] = (acc[file] || 0) + 1;
4516
4855
  return acc;
4517
4856
  }, {});
4518
- console.log(`${chalk28.bold("Total variables:")} ${chalk28.cyan(allVars.length)}`);
4519
- console.log(`${chalk28.bold("Required:")} ${chalk28.red(required.length)}`);
4520
- console.log(`${chalk28.bold("Optional:")} ${chalk28.dim(optional.length)}`);
4521
- console.log(`${chalk28.bold("Sensitive:")} ${chalk28.yellow(sensitive.length)}`);
4522
- console.log(chalk28.bold("\nBy Target:"));
4523
- console.log(` ${chalk28.blue("Next.js accessible:")} ${chalk28.cyan(nextjsVars.length)}`);
4524
- console.log(` ${chalk28.magenta("SPFN server only:")} ${chalk28.cyan(serverOnlyVars.length)}`);
4525
- console.log(chalk28.bold("\nBy File:"));
4857
+ console.log(`${chalk30.bold("Total variables:")} ${chalk30.cyan(allVars.length)}`);
4858
+ console.log(`${chalk30.bold("Required:")} ${chalk30.red(required.length)}`);
4859
+ console.log(`${chalk30.bold("Optional:")} ${chalk30.dim(optional.length)}`);
4860
+ console.log(`${chalk30.bold("Sensitive:")} ${chalk30.yellow(sensitive.length)}`);
4861
+ console.log(chalk30.bold("\nBy Target:"));
4862
+ console.log(` ${chalk30.blue("Next.js accessible:")} ${chalk30.cyan(nextjsVars.length)}`);
4863
+ console.log(` ${chalk30.magenta("SPFN server only:")} ${chalk30.cyan(serverOnlyVars.length)}`);
4864
+ console.log(chalk30.bold("\nBy File:"));
4526
4865
  for (const [file, count] of Object.entries(fileCount)) {
4527
- console.log(` ${chalk28.dim(file)}: ${chalk28.cyan(count)}`);
4866
+ console.log(` ${chalk30.dim(file)}: ${chalk30.cyan(count)}`);
4528
4867
  }
4529
- console.log(chalk28.bold("\nBy Type:"));
4868
+ console.log(chalk30.bold("\nBy Type:"));
4530
4869
  for (const [type, count] of Object.entries(typeCount)) {
4531
- console.log(` ${formatType(type)}: ${chalk28.cyan(count)}`);
4870
+ console.log(` ${formatType(type)}: ${chalk30.cyan(count)}`);
4532
4871
  }
4533
4872
  console.log();
4534
4873
  } catch (error) {
4535
- console.error(chalk28.red(`
4874
+ console.error(chalk30.red(`
4536
4875
  \u274C ${error instanceof Error ? error.message : "Unknown error"}
4537
4876
  `));
4538
4877
  process.exit(1);
@@ -4552,26 +4891,26 @@ async function searchEnvVars(query, options) {
4552
4891
  }
4553
4892
  }
4554
4893
  if (results.length === 0) {
4555
- console.log(chalk28.yellow(`
4894
+ console.log(chalk30.yellow(`
4556
4895
  \u26A0\uFE0F No environment variables found matching "${query}"
4557
4896
  `));
4558
4897
  return;
4559
4898
  }
4560
- console.log(chalk28.blue.bold(`
4899
+ console.log(chalk30.blue.bold(`
4561
4900
  \u{1F50D} Found ${results.length} environment variable(s) matching "${query}"
4562
4901
  `));
4563
4902
  for (const [key, schema] of results) {
4564
4903
  const typeStr = formatType(schema.type);
4565
- const requiredStr = schema.required || schema.default !== void 0 ? chalk28.red("[required]") : chalk28.dim("[optional]");
4566
- console.log(`${chalk28.bold.cyan(key)} ${chalk28.dim("(")}${typeStr}${chalk28.dim(")")} ${requiredStr}`);
4567
- console.log(` ${chalk28.dim(schema.description)}`);
4904
+ const requiredStr = schema.required || schema.default !== void 0 ? chalk30.red("[required]") : chalk30.dim("[optional]");
4905
+ console.log(`${chalk30.bold.cyan(key)} ${chalk30.dim("(")}${typeStr}${chalk30.dim(")")} ${requiredStr}`);
4906
+ console.log(` ${chalk30.dim(schema.description)}`);
4568
4907
  if (schema.default !== void 0) {
4569
- console.log(` ${chalk28.dim("Default:")} ${formatDefault(schema.default, schema.type)}`);
4908
+ console.log(` ${chalk30.dim("Default:")} ${formatDefault(schema.default, schema.type)}`);
4570
4909
  }
4571
4910
  console.log();
4572
4911
  }
4573
4912
  } catch (error) {
4574
- console.error(chalk28.red(`
4913
+ console.error(chalk30.red(`
4575
4914
  \u274C ${error instanceof Error ? error.message : "Unknown error"}
4576
4915
  `));
4577
4916
  process.exit(1);
@@ -4583,9 +4922,9 @@ envCommand.command("stats").description("Show environment variable statistics").
4583
4922
  envCommand.command("search").description("Search environment variables").argument("<query>", "Search query (matches key or description)").option("-p, --package <package>", "Package name to read env schema from", "@spfn/core").action(searchEnvVars);
4584
4923
  function validateEnvOption(envValue) {
4585
4924
  if (!VALID_ENVS.includes(envValue)) {
4586
- console.error(chalk28.red(`
4925
+ console.error(chalk30.red(`
4587
4926
  \u274C Invalid environment: "${envValue}"`));
4588
- console.log(chalk28.dim(` Valid values: ${VALID_ENVS.join(", ")}
4927
+ console.log(chalk30.dim(` Valid values: ${VALID_ENVS.join(", ")}
4589
4928
  `));
4590
4929
  process.exit(1);
4591
4930
  }
@@ -4606,8 +4945,8 @@ async function initEnvFiles(options) {
4606
4945
  return acc;
4607
4946
  }, {});
4608
4947
  if (targetEnv) {
4609
- console.log(chalk28.blue.bold(`
4610
- \u{1F680} Generating .env template files for ${chalk28.cyan(targetEnv)} environment
4948
+ console.log(chalk30.blue.bold(`
4949
+ \u{1F680} Generating .env template files for ${chalk30.cyan(targetEnv)} environment
4611
4950
  `));
4612
4951
  const envSpecificFiles = {};
4613
4952
  const committedVars = allVars.filter(([_, schema]) => !schema.sensitive);
@@ -4623,24 +4962,24 @@ async function initEnvFiles(options) {
4623
4962
  writeEnvTemplate(cwd, file, vars, options.force ?? false);
4624
4963
  }
4625
4964
  } else {
4626
- console.log(chalk28.blue.bold(`
4965
+ console.log(chalk30.blue.bold(`
4627
4966
  \u{1F680} Generating .env template files
4628
4967
  `));
4629
4968
  for (const [file, vars] of Object.entries(grouped)) {
4630
4969
  writeEnvTemplate(cwd, file, vars, options.force ?? false);
4631
4970
  }
4632
4971
  }
4633
- console.log(chalk28.dim("\n\u{1F4A1} Copy .example files to create your actual .env files:"));
4634
- console.log(chalk28.dim(" cp .env.example .env"));
4635
- console.log(chalk28.dim(" cp .env.local.example .env.local"));
4636
- console.log(chalk28.dim(" cp .env.server.example .env.server"));
4972
+ console.log(chalk30.dim("\n\u{1F4A1} Copy .example files to create your actual .env files:"));
4973
+ console.log(chalk30.dim(" cp .env.example .env"));
4974
+ console.log(chalk30.dim(" cp .env.local.example .env.local"));
4975
+ console.log(chalk30.dim(" cp .env.server.example .env.server"));
4637
4976
  if (targetEnv) {
4638
- console.log(chalk28.dim(` cp .env.${targetEnv}.example .env.${targetEnv}`));
4639
- console.log(chalk28.dim(` cp .env.${targetEnv}.local.example .env.${targetEnv}.local`));
4977
+ console.log(chalk30.dim(` cp .env.${targetEnv}.example .env.${targetEnv}`));
4978
+ console.log(chalk30.dim(` cp .env.${targetEnv}.local.example .env.${targetEnv}.local`));
4640
4979
  }
4641
4980
  console.log("");
4642
4981
  } catch (error) {
4643
- console.error(chalk28.red(`
4982
+ console.error(chalk30.red(`
4644
4983
  \u274C ${error instanceof Error ? error.message : "Unknown error"}
4645
4984
  `));
4646
4985
  process.exit(1);
@@ -4648,12 +4987,12 @@ async function initEnvFiles(options) {
4648
4987
  }
4649
4988
  function writeEnvTemplate(cwd, file, vars, force) {
4650
4989
  const filePath = resolve2(cwd, file);
4651
- if (existsSync25(filePath) && !force) {
4652
- console.log(chalk28.yellow(` \u23ED\uFE0F ${file} already exists (use --force to overwrite)`));
4990
+ if (existsSync26(filePath) && !force) {
4991
+ console.log(chalk30.yellow(` \u23ED\uFE0F ${file} already exists (use --force to overwrite)`));
4653
4992
  return;
4654
4993
  }
4655
4994
  writeFileSync16(filePath, generateEnvFileContent(vars), "utf-8");
4656
- console.log(chalk28.green(` \u2705 ${file} (${vars.length} variables)`));
4995
+ console.log(chalk30.green(` \u2705 ${file} (${vars.length} variables)`));
4657
4996
  }
4658
4997
  function generateEnvFileContent(vars) {
4659
4998
  const lines = [
@@ -4688,7 +5027,7 @@ async function checkEnvFiles(options) {
4688
5027
  const envSchema = await loadEnvSchema(packageName);
4689
5028
  const allVars = Object.entries(envSchema);
4690
5029
  const envLabel = targetEnv ? ` (${targetEnv})` : "";
4691
- console.log(chalk28.blue.bold(`
5030
+ console.log(chalk30.blue.bold(`
4692
5031
  \u{1F50D} Checking .env files against schema${envLabel}
4693
5032
  `));
4694
5033
  const filesToCheck = targetEnv ? getEnvFilesForEnvironment(targetEnv) : [...BASE_ENV_FILES.nextjs, ...BASE_ENV_FILES.server];
@@ -4697,7 +5036,7 @@ async function checkEnvFiles(options) {
4697
5036
  const warnings = [];
4698
5037
  for (const file of filesToCheck) {
4699
5038
  const filePath = resolve2(cwd, file);
4700
- if (!existsSync25(filePath)) {
5039
+ if (!existsSync26(filePath)) {
4701
5040
  continue;
4702
5041
  }
4703
5042
  const content = readFileSync12(filePath, "utf-8");
@@ -4705,7 +5044,7 @@ async function checkEnvFiles(options) {
4705
5044
  for (const [key, value] of Object.entries(parsed)) {
4706
5045
  loadedEnv[key] = { value: value || "", file };
4707
5046
  }
4708
- console.log(chalk28.dim(` \u{1F4C4} ${file} loaded`));
5047
+ console.log(chalk30.dim(` \u{1F4C4} ${file} loaded`));
4709
5048
  }
4710
5049
  console.log("");
4711
5050
  for (const [key, schema] of allVars) {
@@ -4713,7 +5052,7 @@ async function checkEnvFiles(options) {
4713
5052
  const found = loadedEnv[key];
4714
5053
  if (!found) {
4715
5054
  if (schema.required && schema.default === void 0) {
4716
- issues.push(`${chalk28.red("\u2717")} ${chalk28.cyan(key)} is required but not found in any .env file`);
5055
+ issues.push(`${chalk30.red("\u2717")} ${chalk30.cyan(key)} is required but not found in any .env file`);
4717
5056
  }
4718
5057
  continue;
4719
5058
  }
@@ -4723,11 +5062,11 @@ async function checkEnvFiles(options) {
4723
5062
  if (!shouldBeNextjs && isNextjsFile && !isServerFile) {
4724
5063
  if (schema.sensitive) {
4725
5064
  issues.push(
4726
- `${chalk28.red("\u2717")} ${chalk28.cyan(key)} is sensitive and should be in ${chalk28.magenta(expectedFile)}, but found in ${chalk28.yellow(found.file)} (security risk!)`
5065
+ `${chalk30.red("\u2717")} ${chalk30.cyan(key)} is sensitive and should be in ${chalk30.magenta(expectedFile)}, but found in ${chalk30.yellow(found.file)} (security risk!)`
4727
5066
  );
4728
5067
  } else {
4729
5068
  warnings.push(
4730
- `${chalk28.yellow("\u26A0")} ${chalk28.cyan(key)} should be in ${chalk28.magenta(expectedFile)}, but found in ${chalk28.dim(found.file)}`
5069
+ `${chalk30.yellow("\u26A0")} ${chalk30.cyan(key)} should be in ${chalk30.magenta(expectedFile)}, but found in ${chalk30.dim(found.file)}`
4731
5070
  );
4732
5071
  }
4733
5072
  }
@@ -4735,34 +5074,34 @@ async function checkEnvFiles(options) {
4735
5074
  for (const [key, { file }] of Object.entries(loadedEnv)) {
4736
5075
  const inSchema = allVars.some(([k]) => k === key);
4737
5076
  if (!inSchema) {
4738
- warnings.push(`${chalk28.yellow("\u26A0")} ${chalk28.cyan(key)} in ${chalk28.dim(file)} is not in schema`);
5077
+ warnings.push(`${chalk30.yellow("\u26A0")} ${chalk30.cyan(key)} in ${chalk30.dim(file)} is not in schema`);
4739
5078
  }
4740
5079
  }
4741
5080
  if (issues.length > 0) {
4742
- console.log(chalk28.red.bold("Issues:"));
5081
+ console.log(chalk30.red.bold("Issues:"));
4743
5082
  for (const issue of issues) {
4744
5083
  console.log(` ${issue}`);
4745
5084
  }
4746
5085
  console.log("");
4747
5086
  }
4748
5087
  if (warnings.length > 0) {
4749
- console.log(chalk28.yellow.bold("Warnings:"));
5088
+ console.log(chalk30.yellow.bold("Warnings:"));
4750
5089
  for (const warning of warnings) {
4751
5090
  console.log(` ${warning}`);
4752
5091
  }
4753
5092
  console.log("");
4754
5093
  }
4755
5094
  if (issues.length === 0 && warnings.length === 0) {
4756
- console.log(chalk28.green("\u2705 All environment variables are correctly configured!\n"));
5095
+ console.log(chalk30.green("\u2705 All environment variables are correctly configured!\n"));
4757
5096
  } else {
4758
- console.log(chalk28.dim(`Found ${issues.length} issue(s) and ${warnings.length} warning(s)
5097
+ console.log(chalk30.dim(`Found ${issues.length} issue(s) and ${warnings.length} warning(s)
4759
5098
  `));
4760
5099
  if (issues.length > 0) {
4761
5100
  process.exit(1);
4762
5101
  }
4763
5102
  }
4764
5103
  } catch (error) {
4765
- console.error(chalk28.red(`
5104
+ console.error(chalk30.red(`
4766
5105
  \u274C ${error instanceof Error ? error.message : "Unknown error"}
4767
5106
  `));
4768
5107
  process.exit(1);
@@ -4774,17 +5113,17 @@ async function validateEnvVars(options) {
4774
5113
  const packages = options.packages || ["@spfn/core"];
4775
5114
  const targetEnv = options.env ? validateEnvOption(options.env) : void 0;
4776
5115
  if (targetEnv) {
4777
- const { loadEnv: loadEnv9 } = await import("@spfn/core/env/loader");
4778
- const result = loadEnv9({ nodeEnv: targetEnv });
4779
- console.log(chalk28.blue.bold(`
4780
- \u{1F50D} Validating environment variables for ${chalk28.cyan(targetEnv)}
5116
+ const { loadEnv: loadEnv10 } = await import("@spfn/core/env/loader");
5117
+ const result = loadEnv10({ nodeEnv: targetEnv });
5118
+ console.log(chalk30.blue.bold(`
5119
+ \u{1F50D} Validating environment variables for ${chalk30.cyan(targetEnv)}
4781
5120
  `));
4782
5121
  if (result.loadedFiles.length > 0) {
4783
- console.log(chalk28.dim(` Loaded: ${result.loadedFiles.join(", ")}`));
5122
+ console.log(chalk30.dim(` Loaded: ${result.loadedFiles.join(", ")}`));
4784
5123
  }
4785
5124
  console.log("");
4786
5125
  } else {
4787
- console.log(chalk28.blue.bold(`
5126
+ console.log(chalk30.blue.bold(`
4788
5127
  \u{1F50D} Validating environment variables
4789
5128
  `));
4790
5129
  }
@@ -4792,7 +5131,7 @@ async function validateEnvVars(options) {
4792
5131
  const allWarnings = [];
4793
5132
  for (const packageName of packages) {
4794
5133
  try {
4795
- console.log(chalk28.dim(` \u{1F4E6} ${packageName}`));
5134
+ console.log(chalk30.dim(` \u{1F4E6} ${packageName}`));
4796
5135
  const envSchema = await loadEnvSchema(packageName);
4797
5136
  const { createEnvRegistry } = await import("@spfn/core/env");
4798
5137
  const registry = createEnvRegistry(envSchema);
@@ -4805,10 +5144,10 @@ async function validateEnvVars(options) {
4805
5144
  }
4806
5145
  } catch (error) {
4807
5146
  if (error instanceof Error && error.message.includes("does not export envSchema")) {
4808
- console.log(chalk28.dim(` \u23ED\uFE0F No envSchema exported, skipping`));
5147
+ console.log(chalk30.dim(` \u23ED\uFE0F No envSchema exported, skipping`));
4809
5148
  continue;
4810
5149
  }
4811
- console.error(chalk28.red(` \u274C Failed to load: ${error instanceof Error ? error.message : String(error)}`));
5150
+ console.error(chalk30.red(` \u274C Failed to load: ${error instanceof Error ? error.message : String(error)}`));
4812
5151
  if (options.strict) {
4813
5152
  process.exit(1);
4814
5153
  }
@@ -4816,32 +5155,32 @@ async function validateEnvVars(options) {
4816
5155
  }
4817
5156
  console.log("");
4818
5157
  if (allErrors.length > 0) {
4819
- console.log(chalk28.red.bold(`\u274C Validation Errors (${allErrors.length}):
5158
+ console.log(chalk30.red.bold(`\u274C Validation Errors (${allErrors.length}):
4820
5159
  `));
4821
5160
  for (const error of allErrors) {
4822
- console.log(` ${chalk28.red("\u2717")} ${chalk28.cyan(error.key)}`);
4823
- console.log(` ${chalk28.dim(error.message)}`);
4824
- console.log(` ${chalk28.dim(`from ${error.package}`)}`);
5161
+ console.log(` ${chalk30.red("\u2717")} ${chalk30.cyan(error.key)}`);
5162
+ console.log(` ${chalk30.dim(error.message)}`);
5163
+ console.log(` ${chalk30.dim(`from ${error.package}`)}`);
4825
5164
  console.log("");
4826
5165
  }
4827
5166
  }
4828
5167
  if (allWarnings.length > 0) {
4829
- console.log(chalk28.yellow.bold(`\u26A0\uFE0F Warnings (${allWarnings.length}):
5168
+ console.log(chalk30.yellow.bold(`\u26A0\uFE0F Warnings (${allWarnings.length}):
4830
5169
  `));
4831
5170
  for (const warning of allWarnings) {
4832
- console.log(` ${chalk28.yellow("\u26A0")} ${chalk28.cyan(warning.key)}`);
4833
- console.log(` ${chalk28.dim(warning.message)}`);
5171
+ console.log(` ${chalk30.yellow("\u26A0")} ${chalk30.cyan(warning.key)}`);
5172
+ console.log(` ${chalk30.dim(warning.message)}`);
4834
5173
  console.log("");
4835
5174
  }
4836
5175
  }
4837
5176
  if (allErrors.length === 0 && allWarnings.length === 0) {
4838
- console.log(chalk28.green.bold("\u2705 All environment variables are valid!\n"));
5177
+ console.log(chalk30.green.bold("\u2705 All environment variables are valid!\n"));
4839
5178
  } else if (allErrors.length === 0) {
4840
- console.log(chalk28.green("\u2705 No errors found."));
4841
- console.log(chalk28.yellow(`\u26A0\uFE0F ${allWarnings.length} warning(s) found.
5179
+ console.log(chalk30.green("\u2705 No errors found."));
5180
+ console.log(chalk30.yellow(`\u26A0\uFE0F ${allWarnings.length} warning(s) found.
4842
5181
  `));
4843
5182
  } else {
4844
- console.log(chalk28.red(`
5183
+ console.log(chalk30.red(`
4845
5184
  \u274C Validation failed with ${allErrors.length} error(s)
4846
5185
  `));
4847
5186
  process.exit(1);
@@ -4854,13 +5193,13 @@ import { Command as Command12 } from "commander";
4854
5193
 
4855
5194
  // src/commands/secret/set.ts
4856
5195
  init_logger();
4857
- import prompts8 from "prompts";
4858
- import chalk29 from "chalk";
5196
+ import prompts9 from "prompts";
5197
+ import chalk31 from "chalk";
4859
5198
 
4860
5199
  // src/commands/secret/options.ts
4861
5200
  init_logger();
4862
- function resolveEnv(env8) {
4863
- const value = env8 ?? "local";
5201
+ function resolveEnv(env9) {
5202
+ const value = env9 ?? "local";
4864
5203
  if (!VALID_ENVS.includes(value)) {
4865
5204
  logger.error(`Invalid environment: "${value}". Valid values: ${VALID_ENVS.join(", ")}`);
4866
5205
  process.exit(1);
@@ -4870,12 +5209,12 @@ function resolveEnv(env8) {
4870
5209
 
4871
5210
  // src/commands/secret/store-value.ts
4872
5211
  init_logger();
4873
- import { join as join24 } from "path";
5212
+ import { join as join25 } from "path";
4874
5213
  init_env_file();
4875
5214
 
4876
5215
  // src/utils/sops.ts
4877
5216
  import { execa as execa10 } from "execa";
4878
- import { existsSync as existsSync26, mkdirSync as mkdirSync3, writeFileSync as writeFileSync17 } from "fs";
5217
+ import { existsSync as existsSync27, mkdirSync as mkdirSync3, writeFileSync as writeFileSync17 } from "fs";
4879
5218
  import { dirname as dirname3 } from "path";
4880
5219
  async function ensureSopsInstalled() {
4881
5220
  try {
@@ -4885,14 +5224,14 @@ async function ensureSopsInstalled() {
4885
5224
  }
4886
5225
  }
4887
5226
  async function sopsDecrypt(absFile) {
4888
- if (!existsSync26(absFile)) {
5227
+ if (!existsSync27(absFile)) {
4889
5228
  return {};
4890
5229
  }
4891
5230
  const { stdout } = await execa10("sops", ["--decrypt", "--output-type", "json", absFile]);
4892
5231
  return JSON.parse(stdout);
4893
5232
  }
4894
5233
  async function sopsSetValue(absFile, relFile, key, value) {
4895
- if (existsSync26(absFile)) {
5234
+ if (existsSync27(absFile)) {
4896
5235
  await execa10("sops", ["set", absFile, `["${key}"]`, JSON.stringify(value)]);
4897
5236
  return;
4898
5237
  }
@@ -4918,20 +5257,20 @@ async function sopsUpdateKeys(absFile) {
4918
5257
  }
4919
5258
 
4920
5259
  // src/utils/secret-config.ts
4921
- import { existsSync as existsSync27, readdirSync as readdirSync3 } from "fs";
4922
- import { join as join23 } from "path";
5260
+ import { existsSync as existsSync28, readdirSync as readdirSync3 } from "fs";
5261
+ import { join as join24 } from "path";
4923
5262
  var SECRETS_DIR = "secrets";
4924
- function getSopsFile(cwd, env8) {
4925
- const relFile = `${SECRETS_DIR}/${env8}.enc.json`;
4926
- return { absFile: join23(cwd, relFile), relFile };
5263
+ function getSopsFile(cwd, env9) {
5264
+ const relFile = `${SECRETS_DIR}/${env9}.enc.json`;
5265
+ return { absFile: join24(cwd, relFile), relFile };
4927
5266
  }
4928
5267
  function findUp(cwd, filename, maxDepth = 6) {
4929
5268
  let dir = cwd;
4930
5269
  for (let depth = 0; depth < maxDepth; depth++) {
4931
- if (existsSync27(join23(dir, filename))) {
5270
+ if (existsSync28(join24(dir, filename))) {
4932
5271
  return dir;
4933
5272
  }
4934
- const parent = join23(dir, "..");
5273
+ const parent = join24(dir, "..");
4935
5274
  if (parent === dir) {
4936
5275
  break;
4937
5276
  }
@@ -4941,22 +5280,22 @@ function findUp(cwd, filename, maxDepth = 6) {
4941
5280
  }
4942
5281
  function findSopsConfig(cwd) {
4943
5282
  const dir = findUp(cwd, ".sops.yaml");
4944
- return dir ? join23(dir, ".sops.yaml") : null;
5283
+ return dir ? join24(dir, ".sops.yaml") : null;
4945
5284
  }
4946
5285
  function hasSopsConfig(cwd) {
4947
5286
  return findSopsConfig(cwd) !== null;
4948
5287
  }
4949
5288
  function listSopsFiles(cwd) {
4950
- const dir = join23(cwd, SECRETS_DIR);
4951
- if (!existsSync27(dir)) {
5289
+ const dir = join24(cwd, SECRETS_DIR);
5290
+ if (!existsSync28(dir)) {
4952
5291
  return [];
4953
5292
  }
4954
- return readdirSync3(dir).filter((name) => name.endsWith(".enc.json")).map((name) => join23(dir, name));
5293
+ return readdirSync3(dir).filter((name) => name.endsWith(".enc.json")).map((name) => join24(dir, name));
4955
5294
  }
4956
5295
 
4957
5296
  // src/commands/secret/store-value.ts
4958
- function isLocalEnv(env8) {
4959
- return env8 === "local";
5297
+ function isLocalEnv(env9) {
5298
+ return env9 === "local";
4960
5299
  }
4961
5300
  var KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
4962
5301
  function assertValidKey(key) {
@@ -4964,15 +5303,15 @@ function assertValidKey(key) {
4964
5303
  throw new Error(`Invalid secret key "${key}". Use an env-var name: letters, digits, and underscores, not starting with a digit.`);
4965
5304
  }
4966
5305
  }
4967
- async function describeTarget(env8) {
4968
- if (isLocalEnv(env8)) {
5306
+ async function describeTarget(env9) {
5307
+ if (isLocalEnv(env9)) {
4969
5308
  return detectStore().label;
4970
5309
  }
4971
- return `SOPS (${getSopsFile(process.cwd(), env8).relFile})`;
5310
+ return `SOPS (${getSopsFile(process.cwd(), env9).relFile})`;
4972
5311
  }
4973
- async function storeSecret(cwd, env8, key, value) {
5312
+ async function storeSecret(cwd, env9, key, value) {
4974
5313
  assertValidKey(key);
4975
- if (isLocalEnv(env8)) {
5314
+ if (isLocalEnv(env9)) {
4976
5315
  const store = detectStore();
4977
5316
  if (!await store.isAvailable()) {
4978
5317
  throw new Error(
@@ -4980,7 +5319,7 @@ async function storeSecret(cwd, env8, key, value) {
4980
5319
  );
4981
5320
  }
4982
5321
  await store.set(keychainName(key), value);
4983
- const serverEnvPath = join24(cwd, ".env.server");
5322
+ const serverEnvPath = join25(cwd, ".env.server");
4984
5323
  const result = upsertEnvVar(serverEnvPath, key, keychainRef(key));
4985
5324
  restrictEnvFilePerms(serverEnvPath);
4986
5325
  ensureGitignored(cwd, [{ pattern: ".env.server", comment: "spfn server env (secrets)" }]);
@@ -4993,14 +5332,14 @@ async function storeSecret(cwd, env8, key, value) {
4993
5332
  ".sops.yaml not found \u2014 add a backend (run `spfn secret keygen` for age, or a KMS rule) so the file can be decrypted on deploy."
4994
5333
  );
4995
5334
  }
4996
- const { absFile, relFile } = getSopsFile(cwd, env8);
5335
+ const { absFile, relFile } = getSopsFile(cwd, env9);
4997
5336
  await sopsSetValue(absFile, relFile, key, value);
4998
5337
  logger.success(`Set ${key} in ${relFile} (encrypted). Commit and deploy to apply.`);
4999
5338
  }
5000
5339
 
5001
5340
  // src/commands/secret/set.ts
5002
5341
  async function secretSet(key, options) {
5003
- const env8 = resolveEnv(options.env);
5342
+ const env9 = resolveEnv(options.env);
5004
5343
  const pkg = options.package ?? "@spfn/core";
5005
5344
  const resolvedKey = key ?? await pickSecretKey(pkg);
5006
5345
  if (!resolvedKey) {
@@ -5008,18 +5347,18 @@ async function secretSet(key, options) {
5008
5347
  process.exit(1);
5009
5348
  }
5010
5349
  await warnIfNotSecret(pkg, resolvedKey);
5011
- const { value } = await prompts8({
5350
+ const { value } = await prompts9({
5012
5351
  type: "password",
5013
5352
  name: "value",
5014
- message: `Value for ${chalk29.cyan(resolvedKey)} (${env8})`
5353
+ message: `Value for ${chalk31.cyan(resolvedKey)} (${env9})`
5015
5354
  });
5016
5355
  if (!value) {
5017
5356
  logger.warn("Cancelled \u2014 no value entered.");
5018
5357
  process.exit(0);
5019
5358
  }
5020
- logger.step(`Storing ${resolvedKey} \u2192 ${await describeTarget(env8)}`);
5359
+ logger.step(`Storing ${resolvedKey} \u2192 ${await describeTarget(env9)}`);
5021
5360
  try {
5022
- await storeSecret(process.cwd(), env8, resolvedKey, value);
5361
+ await storeSecret(process.cwd(), env9, resolvedKey, value);
5023
5362
  } catch (error) {
5024
5363
  logger.error(error instanceof Error ? error.message : String(error));
5025
5364
  process.exit(1);
@@ -5035,7 +5374,7 @@ async function pickSecretKey(pkg) {
5035
5374
  if (entries.length === 0) {
5036
5375
  return void 0;
5037
5376
  }
5038
- const { key } = await prompts8({
5377
+ const { key } = await prompts9({
5039
5378
  type: "select",
5040
5379
  name: "key",
5041
5380
  message: "Which secret?",
@@ -5060,9 +5399,9 @@ async function warnIfNotSecret(pkg, key) {
5060
5399
 
5061
5400
  // src/commands/secret/list.ts
5062
5401
  init_logger();
5063
- import chalk30 from "chalk";
5402
+ import chalk32 from "chalk";
5064
5403
  async function secretList(options) {
5065
- const env8 = resolveEnv(options.env);
5404
+ const env9 = resolveEnv(options.env);
5066
5405
  const pkg = options.package ?? "@spfn/core";
5067
5406
  let entries;
5068
5407
  try {
@@ -5075,18 +5414,18 @@ async function secretList(options) {
5075
5414
  logger.info(`No secrets declared in ${pkg}.`);
5076
5415
  return;
5077
5416
  }
5078
- const present = await loadPresence(env8, entries);
5079
- console.log(chalk30.blue.bold(`
5080
- \u{1F511} Secrets (${pkg}) \u2014 ${env8}
5417
+ const present = await loadPresence(env9, entries);
5418
+ console.log(chalk32.blue.bold(`
5419
+ \u{1F511} Secrets (${pkg}) \u2014 ${env9}
5081
5420
  `));
5082
5421
  for (const entry of entries) {
5083
5422
  const status = statusOf(entry, present.has(entry.key));
5084
- console.log(` ${badge(status)} ${chalk30.cyan(entry.key)}${entry.generate ? chalk30.dim(" (generatable)") : ""}`);
5423
+ console.log(` ${badge(status)} ${chalk32.cyan(entry.key)}${entry.generate ? chalk32.dim(" (generatable)") : ""}`);
5085
5424
  }
5086
5425
  console.log();
5087
5426
  }
5088
- async function loadPresence(env8, entries) {
5089
- if (isLocalEnv(env8)) {
5427
+ async function loadPresence(env9, entries) {
5428
+ if (isLocalEnv(env9)) {
5090
5429
  const store = detectStore();
5091
5430
  if (!await store.isAvailable()) {
5092
5431
  logger.warn(`${store.label} unavailable \u2014 status shown as missing.`);
@@ -5101,7 +5440,7 @@ async function loadPresence(env8, entries) {
5101
5440
  return present;
5102
5441
  }
5103
5442
  await ensureSopsInstalled();
5104
- const { absFile } = getSopsFile(process.cwd(), env8);
5443
+ const { absFile } = getSopsFile(process.cwd(), env9);
5105
5444
  const decrypted = await sopsDecrypt(absFile);
5106
5445
  return new Set(Object.keys(decrypted));
5107
5446
  }
@@ -5114,19 +5453,19 @@ function statusOf(entry, present) {
5114
5453
  function badge(status) {
5115
5454
  switch (status) {
5116
5455
  case "set":
5117
- return chalk30.green("\u25CF");
5456
+ return chalk32.green("\u25CF");
5118
5457
  case "missing":
5119
- return chalk30.yellow("\u25CB");
5458
+ return chalk32.yellow("\u25CB");
5120
5459
  case "awaiting-input":
5121
- return chalk30.red("\u25CB");
5460
+ return chalk32.red("\u25CB");
5122
5461
  }
5123
5462
  }
5124
5463
 
5125
5464
  // src/commands/secret/generate.ts
5126
5465
  init_logger();
5127
- import chalk31 from "chalk";
5466
+ import chalk33 from "chalk";
5128
5467
  async function secretGenerate(key, options) {
5129
- const env8 = resolveEnv(options.env);
5468
+ const env9 = resolveEnv(options.env);
5130
5469
  const pkg = options.package ?? "@spfn/core";
5131
5470
  let schema;
5132
5471
  try {
@@ -5140,11 +5479,11 @@ async function secretGenerate(key, options) {
5140
5479
  logger.warn("No generatable secrets found (none declare a `generate` strategy).");
5141
5480
  return;
5142
5481
  }
5143
- logger.step(`Generating ${targets.length} secret(s) \u2192 ${await describeTarget(env8)}`);
5482
+ logger.step(`Generating ${targets.length} secret(s) \u2192 ${await describeTarget(env9)}`);
5144
5483
  for (const entry of targets) {
5145
5484
  const value = generateSecretValue(entry.generate);
5146
5485
  try {
5147
- await storeSecret(process.cwd(), env8, entry.key, value);
5486
+ await storeSecret(process.cwd(), env9, entry.key, value);
5148
5487
  } catch (error) {
5149
5488
  logger.error(`${entry.key}: ${error instanceof Error ? error.message : String(error)}`);
5150
5489
  process.exit(1);
@@ -5162,7 +5501,7 @@ function requireGeneratable(schema, key) {
5162
5501
  process.exit(1);
5163
5502
  }
5164
5503
  if (!entry.generate) {
5165
- logger.error(`${chalk31.cyan(key)} has no generate strategy \u2014 it's an external value. Use \`spfn secret set ${key}\`.`);
5504
+ logger.error(`${chalk33.cyan(key)} has no generate strategy \u2014 it's an external value. Use \`spfn secret set ${key}\`.`);
5166
5505
  process.exit(1);
5167
5506
  }
5168
5507
  return entry;
@@ -5170,9 +5509,9 @@ function requireGeneratable(schema, key) {
5170
5509
 
5171
5510
  // src/commands/secret/rotate.ts
5172
5511
  init_logger();
5173
- import chalk32 from "chalk";
5512
+ import chalk34 from "chalk";
5174
5513
  async function secretRotate(key, options) {
5175
- const env8 = resolveEnv(options.env);
5514
+ const env9 = resolveEnv(options.env);
5176
5515
  const pkg = options.package ?? "@spfn/core";
5177
5516
  let schema;
5178
5517
  try {
@@ -5185,10 +5524,10 @@ async function secretRotate(key, options) {
5185
5524
  const generatable = targets.filter((entry) => entry.generate);
5186
5525
  const external = targets.filter((entry) => !entry.generate);
5187
5526
  if (generatable.length > 0) {
5188
- logger.step(`Rotating ${generatable.length} secret(s) \u2192 ${await describeTarget(env8)}`);
5527
+ logger.step(`Rotating ${generatable.length} secret(s) \u2192 ${await describeTarget(env9)}`);
5189
5528
  for (const entry of generatable) {
5190
5529
  try {
5191
- await storeSecret(process.cwd(), env8, entry.key, generateSecretValue(entry.generate));
5530
+ await storeSecret(process.cwd(), env9, entry.key, generateSecretValue(entry.generate));
5192
5531
  } catch (error) {
5193
5532
  logger.error(`${entry.key}: ${error instanceof Error ? error.message : String(error)}`);
5194
5533
  process.exit(1);
@@ -5196,7 +5535,7 @@ async function secretRotate(key, options) {
5196
5535
  }
5197
5536
  }
5198
5537
  for (const entry of external) {
5199
- logger.warn(`${chalk32.cyan(entry.key)} is external \u2014 reissue it at the provider, then \`spfn secret set ${entry.key} --env ${env8}\`.`);
5538
+ logger.warn(`${chalk34.cyan(entry.key)} is external \u2014 reissue it at the provider, then \`spfn secret set ${entry.key} --env ${env9}\`.`);
5200
5539
  }
5201
5540
  if (generatable.length > 0) {
5202
5541
  logger.info("Rotation updates the source only \u2014 commit and deploy to apply.");
@@ -5218,12 +5557,12 @@ function requireEntry(schema, key) {
5218
5557
  // src/commands/secret/keygen.ts
5219
5558
  init_logger();
5220
5559
  import { execa as execa11 } from "execa";
5221
- import { existsSync as existsSync28, mkdirSync as mkdirSync4 } from "fs";
5560
+ import { existsSync as existsSync29, mkdirSync as mkdirSync4 } from "fs";
5222
5561
  import { homedir } from "os";
5223
- import { dirname as dirname4, join as join25 } from "path";
5224
- import chalk33 from "chalk";
5562
+ import { dirname as dirname4, join as join26 } from "path";
5563
+ import chalk35 from "chalk";
5225
5564
  function ageKeyFile() {
5226
- return process.env.SOPS_AGE_KEY_FILE ?? join25(homedir(), ".config", "sops", "age", "keys.txt");
5565
+ return process.env.SOPS_AGE_KEY_FILE ?? join26(homedir(), ".config", "sops", "age", "keys.txt");
5227
5566
  }
5228
5567
  async function ensureAgeInstalled() {
5229
5568
  try {
@@ -5243,7 +5582,7 @@ async function secretKeygen() {
5243
5582
  process.exit(1);
5244
5583
  }
5245
5584
  const keyFile = ageKeyFile();
5246
- if (existsSync28(keyFile)) {
5585
+ if (existsSync29(keyFile)) {
5247
5586
  const { stdout } = await execa11("age-keygen", ["-y", keyFile]);
5248
5587
  logger.warn(`age key file already exists: ${keyFile}`);
5249
5588
  printPublicKeys(publicKeys(stdout));
@@ -5259,27 +5598,27 @@ function printPublicKeys(keys) {
5259
5598
  logger.warn("Could not read the public key \u2014 run `age-keygen -y <file>` manually.");
5260
5599
  return;
5261
5600
  }
5262
- console.log(chalk33.bold("\nPublic key(s):"));
5601
+ console.log(chalk35.bold("\nPublic key(s):"));
5263
5602
  for (const key of keys) {
5264
- console.log(` ${chalk33.cyan(key)}`);
5603
+ console.log(` ${chalk35.cyan(key)}`);
5265
5604
  }
5266
- console.log(chalk33.dim("\nRegister it as a recipient:"));
5267
- console.log(chalk33.dim(` spfn secret recipients add ${keys[0]}
5605
+ console.log(chalk35.dim("\nRegister it as a recipient:"));
5606
+ console.log(chalk35.dim(` spfn secret recipients add ${keys[0]}
5268
5607
  `));
5269
5608
  }
5270
5609
 
5271
5610
  // src/commands/secret/recipients.ts
5272
5611
  init_logger();
5273
- import { existsSync as existsSync29, readFileSync as readFileSync13, writeFileSync as writeFileSync18 } from "fs";
5274
- import { join as join26 } from "path";
5612
+ import { existsSync as existsSync30, readFileSync as readFileSync13, writeFileSync as writeFileSync18 } from "fs";
5613
+ import { join as join27 } from "path";
5275
5614
  import { parse as parse3, stringify } from "yaml";
5276
- import chalk34 from "chalk";
5615
+ import chalk36 from "chalk";
5277
5616
  var SOPS_CONFIG = ".sops.yaml";
5278
5617
  var DEFAULT_PATH_REGEX = "secrets/.*\\.enc\\.json$";
5279
5618
  var AGE_RECIPIENT = /^age1[0-9a-z]+$/;
5280
5619
  async function secretRecipients(action, key, _options) {
5281
5620
  const cwd = process.cwd();
5282
- const configPath = join26(cwd, SOPS_CONFIG);
5621
+ const configPath = join27(cwd, SOPS_CONFIG);
5283
5622
  if (action === "list") {
5284
5623
  listRecipients(configPath);
5285
5624
  return;
@@ -5311,25 +5650,25 @@ async function secretRecipients(action, key, _options) {
5311
5650
  await reencrypt(cwd);
5312
5651
  }
5313
5652
  function listRecipients(configPath) {
5314
- if (!existsSync29(configPath)) {
5653
+ if (!existsSync30(configPath)) {
5315
5654
  logger.info(`No ${SOPS_CONFIG} found.`);
5316
5655
  return;
5317
5656
  }
5318
5657
  const config = loadConfig(configPath);
5319
5658
  const rules = config.creation_rules ?? [];
5320
- console.log(chalk34.blue.bold(`
5659
+ console.log(chalk36.blue.bold(`
5321
5660
  \u{1F465} Recipients (${SOPS_CONFIG})
5322
5661
  `));
5323
5662
  for (const rule of rules) {
5324
- console.log(chalk34.dim(` ${rule.path_regex ?? "(any path)"}`));
5663
+ console.log(chalk36.dim(` ${rule.path_regex ?? "(any path)"}`));
5325
5664
  for (const recipient of parseRecipients(rule.age)) {
5326
- console.log(` ${chalk34.cyan(recipient)}`);
5665
+ console.log(` ${chalk36.cyan(recipient)}`);
5327
5666
  }
5328
5667
  }
5329
5668
  console.log();
5330
5669
  }
5331
5670
  function loadConfig(configPath) {
5332
- if (!existsSync29(configPath)) {
5671
+ if (!existsSync30(configPath)) {
5333
5672
  return { creation_rules: [] };
5334
5673
  }
5335
5674
  return parse3(readFileSync13(configPath, "utf-8")) ?? { creation_rules: [] };
@@ -5364,8 +5703,8 @@ async function reencrypt(cwd) {
5364
5703
 
5365
5704
  // src/commands/secret/check.ts
5366
5705
  init_logger();
5367
- import { join as join27 } from "path";
5368
- import chalk35 from "chalk";
5706
+ import { join as join28 } from "path";
5707
+ import chalk37 from "chalk";
5369
5708
  init_env_file();
5370
5709
  var COMMITTED_FILES = [".env", ".env.example"];
5371
5710
  var PLACEHOLDER = /(your-|changeme|placeholder|example|<.*>)/i;
@@ -5383,35 +5722,35 @@ async function secretCheck(options) {
5383
5722
  const issues = [];
5384
5723
  const warnings = [];
5385
5724
  for (const file of COMMITTED_FILES) {
5386
- const parsed = parseEnvFile(join27(cwd, file));
5725
+ const parsed = parseEnvFile(join28(cwd, file));
5387
5726
  for (const [key, value] of Object.entries(parsed)) {
5388
5727
  if (secretKeys.has(key) && value.length > 0 && !PLACEHOLDER.test(value)) {
5389
- issues.push(`${chalk35.cyan(key)} has a real value in committed ${chalk35.yellow(file)} \u2014 move it to the keychain/SOPS.`);
5728
+ issues.push(`${chalk37.cyan(key)} has a real value in committed ${chalk37.yellow(file)} \u2014 move it to the keychain/SOPS.`);
5390
5729
  }
5391
5730
  }
5392
5731
  }
5393
- const serverEnv = parseEnvFile(join27(cwd, ".env.server"));
5732
+ const serverEnv = parseEnvFile(join28(cwd, ".env.server"));
5394
5733
  for (const key of secretKeys) {
5395
5734
  const value = serverEnv[key];
5396
5735
  if (value && !value.startsWith(KEYCHAIN_REF_PREFIX)) {
5397
- warnings.push(`${chalk35.cyan(key)} is plaintext in .env.server \u2014 run \`spfn secret set ${key}\` to move it to the keychain.`);
5736
+ warnings.push(`${chalk37.cyan(key)} is plaintext in .env.server \u2014 run \`spfn secret set ${key}\` to move it to the keychain.`);
5398
5737
  }
5399
5738
  }
5400
5739
  report(issues, warnings, cwd);
5401
5740
  }
5402
5741
  function report(issues, warnings, cwd) {
5403
- console.log(chalk35.blue.bold("\n\u{1F50D} Secret hygiene check\n"));
5742
+ console.log(chalk37.blue.bold("\n\u{1F50D} Secret hygiene check\n"));
5404
5743
  for (const issue of issues) {
5405
- console.log(` ${chalk35.red("\u2717")} ${issue}`);
5744
+ console.log(` ${chalk37.red("\u2717")} ${issue}`);
5406
5745
  }
5407
5746
  for (const warning of warnings) {
5408
- console.log(` ${chalk35.yellow("\u26A0")} ${warning}`);
5747
+ console.log(` ${chalk37.yellow("\u26A0")} ${warning}`);
5409
5748
  }
5410
5749
  if (!hasSopsConfig(cwd)) {
5411
- console.log(` ${chalk35.dim("\u2139 no .sops.yaml \u2014 prod secrets need a backend (spfn secret keygen, or add a KMS rule)")}`);
5750
+ console.log(` ${chalk37.dim("\u2139 no .sops.yaml \u2014 prod secrets need a backend (spfn secret keygen, or add a KMS rule)")}`);
5412
5751
  }
5413
5752
  if (issues.length === 0 && warnings.length === 0) {
5414
- console.log(chalk35.green(" \u2713 No plaintext secret leaks found."));
5753
+ console.log(chalk37.green(" \u2713 No plaintext secret leaks found."));
5415
5754
  }
5416
5755
  console.log();
5417
5756
  if (issues.length > 0) {