spfn 0.2.0-beta.61 → 0.2.0-beta.64

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.61";
925
+ return "0.2.0-beta.64";
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
 
@@ -1678,16 +1860,17 @@ var init_migration_status = __esm({
1678
1860
  });
1679
1861
 
1680
1862
  // src/index.ts
1681
- import { Command as Command13 } from "commander";
1863
+ import { Command as Command14 } from "commander";
1682
1864
 
1683
1865
  // src/commands/create.ts
1684
1866
  init_logger();
1685
1867
  init_package_manager();
1686
1868
  init_messages();
1687
- import { Command as Command3 } from "commander";
1869
+ init_mode();
1870
+ import { Command as Command3, Option as Option2 } from "commander";
1688
1871
  import { existsSync as existsSync13 } from "fs";
1689
1872
  import { join as join13, resolve, dirname as dirname2 } from "path";
1690
- import prompts2 from "prompts";
1873
+ import prompts3 from "prompts";
1691
1874
  import ora4 from "ora";
1692
1875
  import { execa as execa3 } from "execa";
1693
1876
  import chalk5 from "chalk";
@@ -1713,7 +1896,7 @@ async function createProject(projectName, options) {
1713
1896
  console.log(chalk5.blue.bold("\n\u{1F680} Creating Next.js project with SPFN...\n"));
1714
1897
  let pm = options.pm || detectPackageManager(cwd);
1715
1898
  if (!options.yes && !options.pm) {
1716
- const { selectedPm } = await prompts2({
1899
+ const { selectedPm } = await prompts3({
1717
1900
  type: "select",
1718
1901
  name: "selectedPm",
1719
1902
  message: "Which package manager do you want to use?",
@@ -1731,6 +1914,8 @@ async function createProject(projectName, options) {
1731
1914
  pm = selectedPm;
1732
1915
  }
1733
1916
  logger.step(`Using package manager: ${pm}`);
1917
+ const mode = await selectScaffoldMode(options);
1918
+ logger.step(`Using scaffold mode: ${mode}`);
1734
1919
  const workspaceRoot = pm === "pnpm" ? findPnpmWorkspaceRoot(cwd) : null;
1735
1920
  const isInWorkspace = workspaceRoot !== null;
1736
1921
  if (isInWorkspace) {
@@ -1829,7 +2014,7 @@ async function createProject(projectName, options) {
1829
2014
  const initSpinner = ora4("Initializing SPFN...").start();
1830
2015
  try {
1831
2016
  const { initializeSpfn: initializeSpfn2 } = await Promise.resolve().then(() => (init_init(), init_exports));
1832
- await initializeSpfn2({ yes: true, overwriteReadme: true });
2017
+ await initializeSpfn2({ yes: true, overwriteReadme: true, mode });
1833
2018
  initSpinner.succeed("SPFN initialized");
1834
2019
  } catch (error) {
1835
2020
  initSpinner.fail("Failed to initialize SPFN");
@@ -1841,6 +2026,10 @@ async function createProject(projectName, options) {
1841
2026
  console.log(` ${chalk5.cyan("cd")} ${projectName}`);
1842
2027
  console.log(` ${chalk5.cyan("docker compose up -d")} ${chalk5.gray("# Start PostgreSQL & Redis")}`);
1843
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
+ }
1844
2033
  console.log(` ${chalk5.cyan(`${getRunCommand(pm)} spfn:dev`)} ${chalk5.gray("# Start dev server")}
1845
2034
  `);
1846
2035
  console.log(chalk5.bold("Your app will be available at:\n"));
@@ -1857,7 +2046,7 @@ async function createProject(projectName, options) {
1857
2046
  `);
1858
2047
  console.log(chalk5.dim(" \u{1F310} Documentation: https://github.com/spfn/spfn\n"));
1859
2048
  }
1860
- 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) => {
1861
2050
  await createProject(projectName, options);
1862
2051
  });
1863
2052
 
@@ -1869,7 +2058,7 @@ init_logger();
1869
2058
  init_package_manager();
1870
2059
  import { Command as Command4 } from "commander";
1871
2060
  import { existsSync as existsSync16, readFileSync as readFileSync8, writeFileSync as writeFileSync10, mkdirSync, unlinkSync, rmSync, watch } from "fs";
1872
- import { join as join17, relative, sep } from "path";
2061
+ import { join as join17, relative, sep as sep2 } from "path";
1873
2062
  import { execa as execa6 } from "execa";
1874
2063
  import chokidar from "chokidar";
1875
2064
 
@@ -2064,7 +2253,7 @@ async function resolveKeychainEnv(cwd) {
2064
2253
 
2065
2254
  // src/commands/dev.ts
2066
2255
  function ignoreDotfilesUnder(root) {
2067
- 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 !== "..");
2068
2257
  }
2069
2258
  function waitForReadyFile(filePath, timeoutMs = 3e4) {
2070
2259
  return new Promise((resolve3, reject) => {
@@ -2687,16 +2876,95 @@ var startCommand = new Command6("start").description("Start SPFN production serv
2687
2876
  }
2688
2877
  });
2689
2878
 
2690
- // src/commands/codegen.ts
2879
+ // src/commands/provision.ts
2691
2880
  init_logger();
2881
+ init_package_manager();
2692
2882
  import { Command as Command7 } from "commander";
2693
- import { existsSync as existsSync19, writeFileSync as writeFileSync12 } from "fs";
2883
+ import { existsSync as existsSync19, mkdirSync as mkdirSync3, writeFileSync as writeFileSync12 } from "fs";
2694
2884
  import { join as join20 } from "path";
2885
+ import { pathToFileURL } from "url";
2886
+ import { execa as execa9 } from "execa";
2887
+ var CONFIG_FILE_PATHS = [
2888
+ ".spfn/server/server.config.mjs",
2889
+ ".spfn/server/server.config.js",
2890
+ "src/server/server.config.ts",
2891
+ "src/server/server.config.js"
2892
+ ];
2893
+ function findServerConfig(cwd) {
2894
+ return CONFIG_FILE_PATHS.map((path5) => join20(cwd, path5)).find(existsSync19);
2895
+ }
2896
+ var provisionCommand = new Command7("provision").description("Run deploy-time provisioning (seed/RBAC lifecycle hooks) \u2014 once per deploy, not per cold start").action(async () => {
2897
+ if (!process.env.NODE_ENV) {
2898
+ process.env.NODE_ENV = "production";
2899
+ }
2900
+ const cwd = process.cwd();
2901
+ const configPath = findServerConfig(cwd);
2902
+ if (!configPath) {
2903
+ logger.error("server.config not found.");
2904
+ logger.info(`Looked for: ${CONFIG_FILE_PATHS.join(", ")}`);
2905
+ logger.info('Run "spfn init" first, or run this command from the app root.');
2906
+ process.exit(1);
2907
+ }
2908
+ const { env: keychainEnv, missing: keychainMissing } = await resolveKeychainEnv(cwd);
2909
+ if (Object.keys(keychainEnv).length > 0) {
2910
+ logger.info(`[SPFN] Injecting ${Object.keys(keychainEnv).length} secret(s) from the keychain`);
2911
+ }
2912
+ if (keychainMissing.length > 0) {
2913
+ logger.warn(`[SPFN] Could not resolve keychain secret(s): ${keychainMissing.join(", ")} \u2014 run \`spfn secret set <KEY>\``);
2914
+ }
2915
+ const tempDir = join20(cwd, ".spfn");
2916
+ const runnerEntry = join20(tempDir, "provision.mjs");
2917
+ mkdirSync3(tempDir, { recursive: true });
2918
+ writeFileSync12(runnerEntry, `
2919
+ if (!process.env.NODE_ENV)
2920
+ {
2921
+ process.env.NODE_ENV = 'production';
2922
+ }
2923
+
2924
+ // The Vercel Supabase integration injects POSTGRES_URL but not DATABASE_URL,
2925
+ // which is what SPFN reads. Map it so build-step provisioning connects too.
2926
+ process.env.DATABASE_URL ??= process.env.POSTGRES_URL;
2927
+
2928
+ // Load environment variables FIRST (before any imports that depend on them)
2929
+ await import('@spfn/core/config');
2930
+
2931
+ const { provisionInfrastructure } = await import('@spfn/core/server');
2932
+
2933
+ const config = (await import(${JSON.stringify(pathToFileURL(configPath).href)})).default;
2934
+
2935
+ await provisionInfrastructure(config);
2936
+
2937
+ // provisionInfrastructure leaves the DB pool open \u2014 exit explicitly.
2938
+ process.exit(0);
2939
+ `);
2940
+ logger.info(`[SPFN] Provisioning with config: ${configPath.replace(cwd + "/", "")}
2941
+ `);
2942
+ const pm = detectPackageManager(cwd);
2943
+ const runnerCmd = pm === "npm" ? "npx" : pm;
2944
+ const runnerArgs = pm === "npm" ? ["tsx", runnerEntry] : ["exec", "tsx", runnerEntry];
2945
+ const result = await execa9(runnerCmd, runnerArgs, {
2946
+ cwd,
2947
+ stdio: "inherit",
2948
+ reject: false,
2949
+ env: { ...process.env, ...keychainEnv }
2950
+ });
2951
+ if (result.exitCode !== 0) {
2952
+ logger.error(`Provisioning failed (exit code ${result.exitCode})`);
2953
+ process.exit(result.exitCode ?? 1);
2954
+ }
2955
+ logger.info("[SPFN] Provisioning complete");
2956
+ });
2957
+
2958
+ // src/commands/codegen.ts
2959
+ init_logger();
2960
+ import { Command as Command8 } from "commander";
2961
+ import { existsSync as existsSync20, writeFileSync as writeFileSync13 } from "fs";
2962
+ import { join as join21 } from "path";
2695
2963
  import chalk10 from "chalk";
2696
2964
  async function initCodegen(options) {
2697
2965
  const cwd = process.cwd();
2698
- const rcPath = join20(cwd, ".spfnrc.ts");
2699
- if (existsSync19(rcPath)) {
2966
+ const rcPath = join21(cwd, ".spfnrc.ts");
2967
+ if (existsSync20(rcPath)) {
2700
2968
  logger.warn(".spfnrc.ts already exists");
2701
2969
  logger.info("Edit manually to add custom generators");
2702
2970
  process.exit(0);
@@ -2721,7 +2989,7 @@ export default defineConfig({
2721
2989
  ],
2722
2990
  });
2723
2991
  `;
2724
- writeFileSync12(rcPath, content);
2992
+ writeFileSync13(rcPath, content);
2725
2993
  console.log("\n" + chalk10.green.bold("\u2713 Created .spfnrc.ts\n"));
2726
2994
  console.log(chalk10.gray("Configured the @spfn/core:route-map generator."));
2727
2995
  if (options.withExample) {
@@ -2781,28 +3049,28 @@ async function runGenerators() {
2781
3049
  await orchestrator.generateAll();
2782
3050
  console.log("\n" + chalk10.green.bold("\u2713 Code generation completed"));
2783
3051
  }
2784
- var codegenCommand = new Command7("codegen").description("Code generation management");
3052
+ var codegenCommand = new Command8("codegen").description("Code generation management");
2785
3053
  codegenCommand.command("init").description("Initialize .spfnrc.ts with codegen configuration").option("--with-example", "Show example custom generator usage").action(initCodegen);
2786
3054
  codegenCommand.command("list").alias("ls").description("List registered code generators").action(listGenerators);
2787
3055
  codegenCommand.command("run").description("Run code generators once (no watch mode)").action(runGenerators);
2788
3056
 
2789
3057
  // src/commands/key.ts
2790
3058
  init_logger();
2791
- import { Command as Command8 } from "commander";
3059
+ import { Command as Command9 } from "commander";
2792
3060
  import { execSync } from "child_process";
2793
3061
  import chalk11 from "chalk";
2794
3062
 
2795
3063
  // src/utils/secret-gen.ts
2796
- import { randomBytes, randomUUID } from "crypto";
3064
+ import { randomBytes as randomBytes2, randomUUID } from "crypto";
2797
3065
  function randomBase64Url(bytes) {
2798
- return randomBytes(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
3066
+ return randomBytes2(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
2799
3067
  }
2800
3068
  function generateSecretValue(spec) {
2801
3069
  switch (spec) {
2802
3070
  case "hex32":
2803
- return randomBytes(32).toString("hex");
3071
+ return randomBytes2(32).toString("hex");
2804
3072
  case "hex64":
2805
- return randomBytes(64).toString("hex");
3073
+ return randomBytes2(64).toString("hex");
2806
3074
  case "base64url32":
2807
3075
  return randomBase64Url(32);
2808
3076
  case "uuid":
@@ -2902,7 +3170,7 @@ function listPresets() {
2902
3170
  console.log(chalk11.gray(" spfn key auth-encryption --copy"));
2903
3171
  console.log();
2904
3172
  }
2905
- 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) => {
3173
+ var generateValueCommand = new Command9("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) => {
2906
3174
  const bytes = parseInt(options.bytes, 10);
2907
3175
  if (isNaN(bytes) || bytes < 1 || bytes > 128) {
2908
3176
  logger.error("Invalid bytes value. Must be between 1 and 128.");
@@ -2918,7 +3186,7 @@ var generateValueCommand = new Command8("generate").alias("gen").description("Ge
2918
3186
  }
2919
3187
  }
2920
3188
  });
2921
- var keyCommand = new Command8("key").alias("k").description("Generate secure random keys and secrets").argument("[preset]", `Preset type (use --list to see all)`).option("-l, --list", "List all available presets").option("-b, --bytes <number>", "Number of random bytes to generate", "32").option("-e, --env <name>", "Environment variable name").option("-c, --copy", "Copy to clipboard").action((preset, options) => {
3189
+ var keyCommand = new Command9("key").alias("k").description("Generate secure random keys and secrets").argument("[preset]", `Preset type (use --list to see all)`).option("-l, --list", "List all available presets").option("-b, --bytes <number>", "Number of random bytes to generate", "32").option("-e, --env <name>", "Environment variable name").option("-c, --copy", "Copy to clipboard").action((preset, options) => {
2922
3190
  if (options.list) {
2923
3191
  listPresets();
2924
3192
  return;
@@ -2950,15 +3218,15 @@ keyCommand.addCommand(generateValueCommand);
2950
3218
  init_setup();
2951
3219
 
2952
3220
  // src/commands/db/index.ts
2953
- import { Command as Command9 } from "commander";
3221
+ import { Command as Command10 } from "commander";
2954
3222
 
2955
3223
  // src/commands/db/generate.ts
2956
3224
  import chalk13 from "chalk";
2957
3225
 
2958
3226
  // src/commands/db/utils/drizzle.ts
2959
- import { existsSync as existsSync20, writeFileSync as writeFileSync13, unlinkSync as unlinkSync2 } from "fs";
3227
+ import { existsSync as existsSync21, writeFileSync as writeFileSync14, unlinkSync as unlinkSync2 } from "fs";
2960
3228
  import { spawn } from "child_process";
2961
- import { pathToFileURL } from "url";
3229
+ import { pathToFileURL as pathToFileURL2 } from "url";
2962
3230
  import chalk12 from "chalk";
2963
3231
  import ora6 from "ora";
2964
3232
  import { env as env3 } from "@spfn/core/config";
@@ -3023,7 +3291,7 @@ function validateDatabasePrerequisites() {
3023
3291
  }
3024
3292
  }
3025
3293
  async function runDrizzleCommand(command) {
3026
- const hasUserConfig = existsSync20("./drizzle.config.ts");
3294
+ const hasUserConfig = existsSync21("./drizzle.config.ts");
3027
3295
  const tempConfigPath = `./drizzle.config.${process.pid}.${Date.now()}.temp.ts`;
3028
3296
  const configPath = hasUserConfig ? "./drizzle.config.ts" : tempConfigPath;
3029
3297
  if (!hasUserConfig) {
@@ -3042,7 +3310,7 @@ async function runDrizzleCommand(command) {
3042
3310
  expandGlobs: true,
3043
3311
  autoDetectSchemas: true
3044
3312
  });
3045
- writeFileSync13(tempConfigPath, configContent);
3313
+ writeFileSync14(tempConfigPath, configContent);
3046
3314
  console.log(chalk12.dim("Using auto-generated Drizzle config\n"));
3047
3315
  }
3048
3316
  const args = command.split(" ");
@@ -3055,7 +3323,7 @@ async function runDrizzleCommand(command) {
3055
3323
  env: shouldRelaxDbTls(env3.DATABASE_URL) ? { ...process.env, NODE_TLS_REJECT_UNAUTHORIZED: "0" } : { ...process.env }
3056
3324
  });
3057
3325
  const cleanup = () => {
3058
- if (!hasUserConfig && existsSync20(tempConfigPath)) {
3326
+ if (!hasUserConfig && existsSync21(tempConfigPath)) {
3059
3327
  unlinkSync2(tempConfigPath);
3060
3328
  }
3061
3329
  };
@@ -3094,7 +3362,7 @@ async function loadSchemaImports(schemaFiles) {
3094
3362
  }
3095
3363
  const imports = {};
3096
3364
  for (const file of schemaFiles) {
3097
- const moduleUrl = pathToFileURL(file).href;
3365
+ const moduleUrl = pathToFileURL2(file).href;
3098
3366
  const mod = await import(moduleUrl);
3099
3367
  for (const [key, value] of Object.entries(mod)) {
3100
3368
  if (key !== "default") {
@@ -3137,7 +3405,7 @@ async function dbGenerate() {
3137
3405
 
3138
3406
  // src/commands/db/push.ts
3139
3407
  import chalk15 from "chalk";
3140
- import prompts3 from "prompts";
3408
+ import prompts4 from "prompts";
3141
3409
  import "@spfn/core/config";
3142
3410
  import { loadEnv as loadEnv4 } from "@spfn/core/server";
3143
3411
  import { sql } from "drizzle-orm";
@@ -3322,7 +3590,7 @@ async function dbPush(options = {}) {
3322
3590
  console.log(chalk15.red(` ${stmt.sql.replace(/\s+/g, " ").trim()}`));
3323
3591
  console.log(chalk15.dim(` \u2192 ${stmt.reason}`));
3324
3592
  }
3325
- const { confirm } = await prompts3({
3593
+ const { confirm } = await prompts4({
3326
3594
  type: "confirm",
3327
3595
  name: "confirm",
3328
3596
  message: "Apply destructive changes?",
@@ -3375,8 +3643,8 @@ async function applyFunctionMigrations(plans) {
3375
3643
 
3376
3644
  // src/commands/db/migrate.ts
3377
3645
  import chalk20 from "chalk";
3378
- import { join as join21 } from "path";
3379
- import { existsSync as existsSync22 } from "fs";
3646
+ import { join as join22 } from "path";
3647
+ import { existsSync as existsSync23 } from "fs";
3380
3648
 
3381
3649
  // src/commands/db/backup.ts
3382
3650
  import { promises as fs3 } from "fs";
@@ -3388,7 +3656,7 @@ import ora7 from "ora";
3388
3656
  // src/commands/db/utils/database.ts
3389
3657
  import net from "net";
3390
3658
  import chalk16 from "chalk";
3391
- import prompts4 from "prompts";
3659
+ import prompts5 from "prompts";
3392
3660
  function parseDatabaseUrl2(dbUrl) {
3393
3661
  try {
3394
3662
  const url = new URL(dbUrl);
@@ -3412,7 +3680,7 @@ async function confirmDangerousTarget(dbInfo) {
3412
3680
  if (!isRemoteHost(dbInfo.host) && process.env.NODE_ENV !== "production") {
3413
3681
  return;
3414
3682
  }
3415
- const { typed } = await prompts4({
3683
+ const { typed } = await prompts5({
3416
3684
  type: "text",
3417
3685
  name: "typed",
3418
3686
  message: `Remote/production database detected. Type the database name "${dbInfo.database}" to continue:`
@@ -3469,7 +3737,7 @@ function formatTimestamp() {
3469
3737
 
3470
3738
  // src/commands/db/utils/backup-files.ts
3471
3739
  import { promises as fs2 } from "fs";
3472
- import { existsSync as existsSync21 } from "fs";
3740
+ import { existsSync as existsSync22 } from "fs";
3473
3741
  import path2 from "path";
3474
3742
  import chalk18 from "chalk";
3475
3743
 
@@ -3569,7 +3837,7 @@ async function ensureBackupInGitignore() {
3569
3837
  const gitignorePath = path2.join(process.cwd(), ".gitignore");
3570
3838
  try {
3571
3839
  let content = "";
3572
- let exists = existsSync21(gitignorePath);
3840
+ let exists = existsSync22(gitignorePath);
3573
3841
  if (exists) {
3574
3842
  content = await fs2.readFile(gitignorePath, "utf-8");
3575
3843
  }
@@ -3591,7 +3859,7 @@ async function ensureBackupDir() {
3591
3859
  try {
3592
3860
  await fs2.mkdir(backupDir, { recursive: true });
3593
3861
  const gitignorePath = path2.join(backupDir, ".gitignore");
3594
- const gitignoreExists = existsSync21(gitignorePath);
3862
+ const gitignoreExists = existsSync22(gitignorePath);
3595
3863
  if (!gitignoreExists) {
3596
3864
  await fs2.writeFile(gitignorePath, "# Ignore all backup files\n*.sql\n*.dump\n*.meta.json\n");
3597
3865
  }
@@ -3785,8 +4053,8 @@ async function dbMigrate(options = {}) {
3785
4053
  await executeFunctionMigrations2(loadFunctionMigrationPlans2(functions));
3786
4054
  console.log(chalk20.green("\u2705 Function migrations applied\n"));
3787
4055
  }
3788
- const projectMigrationsDir = join21(process.cwd(), "src/server/drizzle");
3789
- if (existsSync22(projectMigrationsDir)) {
4056
+ const projectMigrationsDir = join22(process.cwd(), "src/server/drizzle");
4057
+ if (existsSync23(projectMigrationsDir)) {
3790
4058
  const projConn = postgres.default(env5.DATABASE_URL, { max: 1 });
3791
4059
  const projDb = drizzle({ client: projConn });
3792
4060
  try {
@@ -3846,7 +4114,7 @@ async function dbStatus() {
3846
4114
 
3847
4115
  // src/commands/db/studio.ts
3848
4116
  import chalk22 from "chalk";
3849
- import { existsSync as existsSync23, writeFileSync as writeFileSync14, unlinkSync as unlinkSync3 } from "fs";
4117
+ import { existsSync as existsSync24, writeFileSync as writeFileSync15, unlinkSync as unlinkSync3 } from "fs";
3850
4118
  import { spawn as spawn3 } from "child_process";
3851
4119
  import { env as env6 } from "@spfn/core/config";
3852
4120
  async function dbStudio(requestedPort) {
@@ -3864,7 +4132,7 @@ async function dbStudio(requestedPort) {
3864
4132
  console.error(chalk22.red(error instanceof Error ? error.message : "Failed to find available port"));
3865
4133
  process.exit(1);
3866
4134
  }
3867
- const hasUserConfig = existsSync23("./drizzle.config.ts");
4135
+ const hasUserConfig = existsSync24("./drizzle.config.ts");
3868
4136
  const tempConfigPath = `./drizzle.config.${process.pid}.${Date.now()}.temp.ts`;
3869
4137
  try {
3870
4138
  const configPath = hasUserConfig ? "./drizzle.config.ts" : tempConfigPath;
@@ -3881,7 +4149,7 @@ async function dbStudio(requestedPort) {
3881
4149
  expandGlobs: true
3882
4150
  // Expand glob patterns for Studio compatibility
3883
4151
  });
3884
- writeFileSync14(tempConfigPath, configContent);
4152
+ writeFileSync15(tempConfigPath, configContent);
3885
4153
  console.log(chalk22.dim("Using auto-generated Drizzle config\n"));
3886
4154
  }
3887
4155
  const studioProcess = spawn3("drizzle-kit", ["studio", `--port=${port}`, `--config=${configPath}`], {
@@ -3890,7 +4158,7 @@ async function dbStudio(requestedPort) {
3890
4158
  env: shouldRelaxDbTls(env6.DATABASE_URL) ? { ...process.env, NODE_TLS_REJECT_UNAUTHORIZED: "0" } : { ...process.env }
3891
4159
  });
3892
4160
  const cleanup = () => {
3893
- if (!hasUserConfig && existsSync23(tempConfigPath)) {
4161
+ if (!hasUserConfig && existsSync24(tempConfigPath)) {
3894
4162
  unlinkSync3(tempConfigPath);
3895
4163
  }
3896
4164
  };
@@ -3920,7 +4188,7 @@ async function dbStudio(requestedPort) {
3920
4188
  process.exit(0);
3921
4189
  });
3922
4190
  } catch (error) {
3923
- if (!hasUserConfig && existsSync23(tempConfigPath)) {
4191
+ if (!hasUserConfig && existsSync24(tempConfigPath)) {
3924
4192
  unlinkSync3(tempConfigPath);
3925
4193
  }
3926
4194
  console.error(chalk22.red("\u274C Failed to start Drizzle Studio"));
@@ -3931,7 +4199,7 @@ async function dbStudio(requestedPort) {
3931
4199
 
3932
4200
  // src/commands/db/drop.ts
3933
4201
  import chalk23 from "chalk";
3934
- import prompts5 from "prompts";
4202
+ import prompts6 from "prompts";
3935
4203
  import { env as env7 } from "@spfn/core/config";
3936
4204
  import { loadEnv as loadEnv7 } from "@spfn/core/server";
3937
4205
  async function dbDrop() {
@@ -3945,7 +4213,7 @@ async function dbDrop() {
3945
4213
  console.log(chalk23.yellow("\u26A0\uFE0F WARNING: This will drop all tables in your database!"));
3946
4214
  console.log(chalk23.dim(` Target: ${dbInfo.database} @ ${dbInfo.host}:${dbInfo.port}
3947
4215
  `));
3948
- const { confirm } = await prompts5({
4216
+ const { confirm } = await prompts6({
3949
4217
  type: "confirm",
3950
4218
  name: "confirm",
3951
4219
  message: "Are you sure you want to drop all tables?",
@@ -3984,7 +4252,7 @@ import path4 from "path";
3984
4252
  import { spawn as spawn4 } from "child_process";
3985
4253
  import chalk25 from "chalk";
3986
4254
  import ora9 from "ora";
3987
- import prompts6 from "prompts";
4255
+ import prompts7 from "prompts";
3988
4256
  import { env as env8 } from "@spfn/core/config";
3989
4257
  import { loadEnv as loadEnv8 } from "@spfn/core/server";
3990
4258
  async function dbRestore(backupFile, options = {}) {
@@ -4003,7 +4271,7 @@ async function dbRestore(backupFile, options = {}) {
4003
4271
  console.log(chalk25.yellow("No backups found in ./backups directory"));
4004
4272
  process.exit(0);
4005
4273
  }
4006
- const { selected } = await prompts6({
4274
+ const { selected } = await prompts7({
4007
4275
  type: "select",
4008
4276
  name: "selected",
4009
4277
  message: "Select backup to restore:",
@@ -4066,7 +4334,7 @@ async function dbRestore(backupFile, options = {}) {
4066
4334
  }
4067
4335
  }
4068
4336
  const dbInfo = parseDatabaseUrl2(dbUrl);
4069
- const { confirm } = await prompts6({
4337
+ const { confirm } = await prompts7({
4070
4338
  type: "confirm",
4071
4339
  name: "confirm",
4072
4340
  message: chalk25.yellow(`\u26A0\uFE0F This will replace all data in "${dbInfo.database}" @ ${dbInfo.host}:${dbInfo.port}. Continue?`),
@@ -4249,7 +4517,7 @@ async function dbBackupList() {
4249
4517
  import { promises as fs4 } from "fs";
4250
4518
  import chalk27 from "chalk";
4251
4519
  import ora10 from "ora";
4252
- import prompts7 from "prompts";
4520
+ import prompts8 from "prompts";
4253
4521
  async function dbBackupClean(options) {
4254
4522
  console.log(chalk27.blue("\u{1F9F9} Cleaning old backups...\n"));
4255
4523
  const backups = await listBackupFiles();
@@ -4279,7 +4547,7 @@ async function dbBackupClean(options) {
4279
4547
  toDelete.forEach((backup) => {
4280
4548
  console.log(chalk27.gray(` - ${backup.name} (${backup.size})`));
4281
4549
  });
4282
- const { confirm } = await prompts7({
4550
+ const { confirm } = await prompts8({
4283
4551
  type: "confirm",
4284
4552
  name: "confirm",
4285
4553
  message: "\nProceed with deletion?",
@@ -4307,8 +4575,8 @@ async function dbBackupClean(options) {
4307
4575
  }
4308
4576
 
4309
4577
  // src/commands/db/reindex.ts
4310
- import { existsSync as existsSync24, readFileSync as readFileSync10, writeFileSync as writeFileSync15, renameSync, copyFileSync } from "fs";
4311
- import { join as join22 } from "path";
4578
+ import { existsSync as existsSync25, readFileSync as readFileSync10, writeFileSync as writeFileSync16, renameSync, copyFileSync } from "fs";
4579
+ import { join as join23 } from "path";
4312
4580
  import chalk28 from "chalk";
4313
4581
  import { loadEnv as loadEnv9 } from "@spfn/core/server";
4314
4582
  function isTimestampPrefix(tag) {
@@ -4330,8 +4598,8 @@ async function dbReindex(options = {}) {
4330
4598
  const { getDrizzleConfig } = await import("@spfn/core/db");
4331
4599
  const config = getDrizzleConfig({ disablePackageDiscovery: true });
4332
4600
  const outDir = config.out;
4333
- const journalPath = join22(outDir, "meta", "_journal.json");
4334
- if (!existsSync24(journalPath)) {
4601
+ const journalPath = join23(outDir, "meta", "_journal.json");
4602
+ if (!existsSync25(journalPath)) {
4335
4603
  console.error(chalk28.red("\u274C No _journal.json found at:"), journalPath);
4336
4604
  console.log(chalk28.yellow("\u{1F4A1} Run `spfn db generate` first to create migrations"));
4337
4605
  process.exit(1);
@@ -4352,14 +4620,14 @@ async function dbReindex(options = {}) {
4352
4620
  const { prefix: oldPrefix, suffix } = parseTag(entry.tag);
4353
4621
  const newPrefix = String(entry.when);
4354
4622
  const newTag = suffix ? `${newPrefix}_${suffix}` : newPrefix;
4355
- const oldSql = join22(outDir, `${entry.tag}.sql`);
4356
- const newSql = join22(outDir, `${newTag}.sql`);
4357
- if (existsSync24(oldSql)) {
4623
+ const oldSql = join23(outDir, `${entry.tag}.sql`);
4624
+ const newSql = join23(outDir, `${newTag}.sql`);
4625
+ if (existsSync25(oldSql)) {
4358
4626
  renames.push({ type: "sql", from: oldSql, to: newSql });
4359
4627
  }
4360
- const oldSnapshot = join22(outDir, "meta", `${oldPrefix}_snapshot.json`);
4361
- const newSnapshot = join22(outDir, "meta", `${newPrefix}_snapshot.json`);
4362
- if (existsSync24(oldSnapshot)) {
4628
+ const oldSnapshot = join23(outDir, "meta", `${oldPrefix}_snapshot.json`);
4629
+ const newSnapshot = join23(outDir, "meta", `${newPrefix}_snapshot.json`);
4630
+ if (existsSync25(oldSnapshot)) {
4363
4631
  renames.push({ type: "snapshot", from: oldSnapshot, to: newSnapshot });
4364
4632
  }
4365
4633
  tagUpdates.push({ idx: entry.idx, oldTag: entry.tag, newTag });
@@ -4402,13 +4670,13 @@ async function dbReindex(options = {}) {
4402
4670
  entry.tag = update.newTag;
4403
4671
  }
4404
4672
  }
4405
- writeFileSync15(journalPath, JSON.stringify(journal, null, 2) + "\n");
4673
+ writeFileSync16(journalPath, JSON.stringify(journal, null, 2) + "\n");
4406
4674
  console.log(chalk28.green(`
4407
4675
  \u2705 Reindex complete \u2014 ${tagUpdates.length} migration(s) converted to timestamp prefix.`));
4408
4676
  }
4409
4677
 
4410
4678
  // src/commands/db/index.ts
4411
- var dbCommand = new Command9("db").description("Database management commands (wraps Drizzle Kit)");
4679
+ var dbCommand = new Command10("db").description("Database management commands (wraps Drizzle Kit)");
4412
4680
  dbCommand.command("generate").alias("g").description("Generate database migrations from schema changes").action(dbGenerate);
4413
4681
  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));
4414
4682
  dbCommand.command("migrate").alias("m").description("Run pending migrations").option("--with-backup", "Create backup before running migrations").action((options) => dbMigrate(options));
@@ -4424,54 +4692,114 @@ dbCommand.command("reindex").description("Convert migration files from sequentia
4424
4692
 
4425
4693
  // src/commands/add.ts
4426
4694
  init_package_manager();
4427
- import { Command as Command10 } from "commander";
4428
- import { existsSync as existsSync25, readFileSync as readFileSync11 } from "fs";
4429
- import { join as join23 } from "path";
4430
- import { execa as execa9 } from "execa";
4431
- import chalk29 from "chalk";
4695
+ import { Command as Command11 } from "commander";
4696
+ import { existsSync as existsSync27, readFileSync as readFileSync11 } from "fs";
4697
+ import { join as join25 } from "path";
4698
+ import { execa as execa10 } from "execa";
4699
+ import chalk30 from "chalk";
4432
4700
  import ora11 from "ora";
4701
+
4702
+ // src/commands/add-vercel.ts
4703
+ init_templates();
4704
+ import { copyFileSync as copyFileSync2, existsSync as existsSync26, mkdirSync as mkdirSync4 } from "fs";
4705
+ import { dirname as dirname3, join as join24 } from "path";
4706
+ import chalk29 from "chalk";
4707
+ var FILES = [
4708
+ {
4709
+ template: "route.ts",
4710
+ target: join24("src", "app", "api", "backend", "[[...route]]", "route.ts"),
4711
+ description: "hono/vercel adapter (mounts the SPFN app under /api/backend)"
4712
+ },
4713
+ {
4714
+ template: "vercel.json",
4715
+ target: "vercel.json",
4716
+ description: "Vercel build config (pnpm spfn:build)"
4717
+ },
4718
+ {
4719
+ template: "npmrc",
4720
+ target: ".npmrc",
4721
+ description: "@spfn registry auth (reads GITEA_NPM_TOKEN from env)"
4722
+ }
4723
+ ];
4724
+ async function addVercel() {
4725
+ console.log(chalk29.blue("\n\u{1F4E6} Setting up the Vercel serverless target...\n"));
4726
+ const cwd = process.cwd();
4727
+ const templatesDir = join24(findTemplatesPath(), "vercel");
4728
+ for (const file of FILES) {
4729
+ const targetPath = join24(cwd, file.target);
4730
+ if (existsSync26(targetPath)) {
4731
+ console.log(chalk29.yellow(`\u23ED\uFE0F ${file.target} already exists \u2014 skipped (not overwritten)`));
4732
+ continue;
4733
+ }
4734
+ mkdirSync4(dirname3(targetPath), { recursive: true });
4735
+ copyFileSync2(join24(templatesDir, file.template), targetPath);
4736
+ console.log(chalk29.green(`\u2705 ${file.target}`) + chalk29.gray(` \u2014 ${file.description}`));
4737
+ }
4738
+ console.log(chalk29.green("\n\u2705 Vercel target ready!\n"));
4739
+ console.log(chalk29.cyan("\u{1F4DA} Next steps:"));
4740
+ console.log(chalk29.gray(" 1. Make sure `hono` is a direct dependency (hono/vercel must resolve from the app):"));
4741
+ console.log(chalk29.gray(" pnpm add hono"));
4742
+ console.log(chalk29.gray(" 2. Set Vercel project env vars:"));
4743
+ console.log(chalk29.gray(" GITEA_NPM_TOKEN \u2014 @spfn registry token (install step)"));
4744
+ console.log(chalk29.gray(" SPFN_AUTH_* \u2014 auth secrets (same as local .env.server)"));
4745
+ console.log(chalk29.gray(" SPFN_API_URL \u2014 https://<your-domain>/api/backend (RPC proxy origin)"));
4746
+ console.log(chalk29.gray(" DATABASE_URL \u2014 set automatically from POSTGRES_URL if you use the"));
4747
+ console.log(chalk29.gray(" Vercel Supabase integration (mapped in route.ts)"));
4748
+ console.log(chalk29.gray(" 3. Run migrations against the DIRECT (non-pooler) connection before deploying:"));
4749
+ console.log(chalk29.gray(" DATABASE_URL=<direct-url> pnpm spfn db migrate"));
4750
+ console.log(chalk29.gray(" 4. Seed/RBAC provisioning runs once per deploy, not per cold start:"));
4751
+ console.log(chalk29.gray(" pnpm spfn provision (e.g. from the build step or a deploy hook)"));
4752
+ console.log(chalk29.gray(" 5. Jobs (config.jobs) are NOT processed on serverless \u2014 drain the queue from a"));
4753
+ console.log(chalk29.gray(" scheduled endpoint (Vercel Cron) or run workers on an always-on target.\n"));
4754
+ }
4755
+
4756
+ // src/commands/add.ts
4433
4757
  async function addPackage(packageName) {
4758
+ if (packageName === "vercel") {
4759
+ await addVercel();
4760
+ return;
4761
+ }
4434
4762
  if (!packageName.includes("/")) {
4435
- console.error(chalk29.red("\u274C Please specify full package name"));
4436
- console.log(chalk29.yellow("\n\u{1F4A1} Examples:"));
4437
- console.log(chalk29.gray(" pnpm spfn add @spfn/cms"));
4438
- console.log(chalk29.gray(" pnpm spfn add @mycompany/spfn-analytics"));
4763
+ console.error(chalk30.red("\u274C Please specify full package name"));
4764
+ console.log(chalk30.yellow("\n\u{1F4A1} Examples:"));
4765
+ console.log(chalk30.gray(" pnpm spfn add @spfn/cms"));
4766
+ console.log(chalk30.gray(" pnpm spfn add @mycompany/spfn-analytics"));
4439
4767
  process.exit(1);
4440
4768
  }
4441
- console.log(chalk29.blue(`
4769
+ console.log(chalk30.blue(`
4442
4770
  \u{1F4E6} Setting up ${packageName}...
4443
4771
  `));
4444
- const pkgPath = join23(process.cwd(), "node_modules", ...packageName.split("/"));
4445
- const pkgJsonPath = join23(pkgPath, "package.json");
4446
- if (!existsSync25(pkgJsonPath)) {
4772
+ const pkgPath = join25(process.cwd(), "node_modules", ...packageName.split("/"));
4773
+ const pkgJsonPath = join25(pkgPath, "package.json");
4774
+ if (!existsSync27(pkgJsonPath)) {
4447
4775
  const pm = detectPackageManager(process.cwd());
4448
4776
  const installSpinner = ora11("Installing package...").start();
4449
4777
  try {
4450
- await execa9(pm, ["add", packageName]);
4778
+ await execa10(pm, ["add", packageName]);
4451
4779
  installSpinner.succeed("Package installed");
4452
4780
  } catch (error) {
4453
4781
  installSpinner.fail("Failed to install package");
4454
- console.error(chalk29.red(error instanceof Error ? error.message : "Unknown error"));
4782
+ console.error(chalk30.red(error instanceof Error ? error.message : "Unknown error"));
4455
4783
  process.exit(1);
4456
4784
  }
4457
4785
  } else {
4458
- console.log(chalk29.gray("\u2713 Package already installed (using local version)\n"));
4786
+ console.log(chalk30.gray("\u2713 Package already installed (using local version)\n"));
4459
4787
  }
4460
- if (!existsSync25(pkgJsonPath)) {
4461
- console.error(chalk29.red(`\u274C Package ${packageName} not found after installation`));
4788
+ if (!existsSync27(pkgJsonPath)) {
4789
+ console.error(chalk30.red(`\u274C Package ${packageName} not found after installation`));
4462
4790
  process.exit(1);
4463
4791
  }
4464
4792
  const pkgJson = JSON.parse(readFileSync11(pkgJsonPath, "utf-8"));
4465
4793
  if (pkgJson.spfn?.migrations) {
4466
- console.log(chalk29.blue(`
4794
+ console.log(chalk30.blue(`
4467
4795
  \u{1F5C4}\uFE0F Setting up database for ${packageName}...
4468
4796
  `));
4469
4797
  const { env: env9 } = await import("@spfn/core/config");
4470
4798
  if (!env9.DATABASE_URL) {
4471
- console.log(chalk29.yellow("\u26A0\uFE0F DATABASE_URL not found \u2014 skipping database setup."));
4472
- console.log(chalk29.yellow(` ${packageName} tables are created by its bundled migrations, not by schema push.`));
4473
- console.log(chalk29.cyan(" Once DATABASE_URL is set, run: pnpm spfn db migrate"));
4474
- console.log(chalk29.gray(" (check state anytime with: pnpm spfn db status)\n"));
4799
+ console.log(chalk30.yellow("\u26A0\uFE0F DATABASE_URL not found \u2014 skipping database setup."));
4800
+ console.log(chalk30.yellow(` ${packageName} tables are created by its bundled migrations, not by schema push.`));
4801
+ console.log(chalk30.cyan(" Once DATABASE_URL is set, run: pnpm spfn db migrate"));
4802
+ console.log(chalk30.gray(" (check state anytime with: pnpm spfn db status)\n"));
4475
4803
  } else {
4476
4804
  const { discoverFunctionMigrations: discoverFunctionMigrations2, loadFunctionMigrationPlans: loadFunctionMigrationPlans2, executeFunctionMigrations: executeFunctionMigrations2 } = await Promise.resolve().then(() => (init_function_migrations(), function_migrations_exports));
4477
4805
  const functions = discoverFunctionMigrations2(process.cwd());
@@ -4483,31 +4811,31 @@ async function addPackage(packageName) {
4483
4811
  spinner.succeed("Migrations applied");
4484
4812
  } catch (error) {
4485
4813
  spinner.fail("Failed to apply migrations");
4486
- console.error(chalk29.red(error instanceof Error ? error.message : "Unknown error"));
4814
+ console.error(chalk30.red(error instanceof Error ? error.message : "Unknown error"));
4487
4815
  process.exit(1);
4488
4816
  }
4489
4817
  } else {
4490
- console.log(chalk29.gray("\u2139\uFE0F No migrations found for this package"));
4818
+ console.log(chalk30.gray("\u2139\uFE0F No migrations found for this package"));
4491
4819
  }
4492
4820
  }
4493
4821
  } else {
4494
- console.log(chalk29.gray("\n\u2139\uFE0F No database migrations to apply"));
4822
+ console.log(chalk30.gray("\n\u2139\uFE0F No database migrations to apply"));
4495
4823
  }
4496
- console.log(chalk29.green(`
4824
+ console.log(chalk30.green(`
4497
4825
  \u2705 ${packageName} installed successfully!
4498
4826
  `));
4499
4827
  if (pkgJson.spfn?.setupMessage) {
4500
- console.log(chalk29.cyan("\u{1F4DA} Setup Guide:"));
4828
+ console.log(chalk30.cyan("\u{1F4DA} Setup Guide:"));
4501
4829
  console.log(pkgJson.spfn.setupMessage);
4502
4830
  console.log();
4503
4831
  }
4504
4832
  }
4505
- var addCommand = new Command10("add").description("Install and set up SPFN ecosystem packages").argument("<package>", "Package name (e.g., @spfn/cms, @mycompany/spfn-analytics)").action(addPackage);
4833
+ var addCommand = new Command11("add").description("Install and set up SPFN ecosystem packages, or scaffold a deploy target (vercel)").argument("<package>", 'Package name (e.g., @spfn/cms, @mycompany/spfn-analytics) or "vercel"').action(addPackage);
4506
4834
 
4507
4835
  // src/commands/env.ts
4508
- import { Command as Command11 } from "commander";
4509
- import chalk30 from "chalk";
4510
- import { existsSync as existsSync26, readFileSync as readFileSync12, writeFileSync as writeFileSync16 } from "fs";
4836
+ import { Command as Command12 } from "commander";
4837
+ import chalk31 from "chalk";
4838
+ import { existsSync as existsSync28, readFileSync as readFileSync12, writeFileSync as writeFileSync17 } from "fs";
4511
4839
  import { resolve as resolve2 } from "path";
4512
4840
  import { parse as parse2 } from "dotenv";
4513
4841
 
@@ -4563,26 +4891,26 @@ function getEnvFilesForEnvironment(nodeEnv) {
4563
4891
  }
4564
4892
  function formatType(type) {
4565
4893
  const typeColors = {
4566
- string: chalk30.green,
4567
- number: chalk30.blue,
4568
- boolean: chalk30.yellow,
4569
- url: chalk30.cyan,
4570
- enum: chalk30.magenta,
4571
- json: chalk30.red
4894
+ string: chalk31.green,
4895
+ number: chalk31.blue,
4896
+ boolean: chalk31.yellow,
4897
+ url: chalk31.cyan,
4898
+ enum: chalk31.magenta,
4899
+ json: chalk31.red
4572
4900
  };
4573
- return (typeColors[type] || chalk30.white)(type);
4901
+ return (typeColors[type] || chalk31.white)(type);
4574
4902
  }
4575
4903
  function formatDefault(value, type) {
4576
4904
  if (value === void 0) {
4577
- return chalk30.dim("(none)");
4905
+ return chalk31.dim("(none)");
4578
4906
  }
4579
4907
  if (type === "string" || type === "url") {
4580
- return chalk30.green(`"${value}"`);
4908
+ return chalk31.green(`"${value}"`);
4581
4909
  }
4582
4910
  if (type === "boolean") {
4583
- return value ? chalk30.green("true") : chalk30.red("false");
4911
+ return value ? chalk31.green("true") : chalk31.red("false");
4584
4912
  }
4585
- return chalk30.cyan(String(value));
4913
+ return chalk31.cyan(String(value));
4586
4914
  }
4587
4915
  async function listEnvVars(options) {
4588
4916
  const packageName = options.package || "@spfn/core";
@@ -4596,28 +4924,28 @@ async function listEnvVars(options) {
4596
4924
  acc[target].push([key, schema]);
4597
4925
  return acc;
4598
4926
  }, {});
4599
- console.log(chalk30.blue.bold(`
4927
+ console.log(chalk31.blue.bold(`
4600
4928
  \u{1F4CB} Environment Variables by File (${packageName})
4601
4929
  `));
4602
4930
  for (const [file, vars] of Object.entries(grouped)) {
4603
- console.log(chalk30.bold.magenta(`
4931
+ console.log(chalk31.bold.magenta(`
4604
4932
  ${file}`));
4605
- console.log(chalk30.dim("\u2500".repeat(50)));
4933
+ console.log(chalk31.dim("\u2500".repeat(50)));
4606
4934
  for (const [key, schema] of vars) {
4607
4935
  printEnvVar(key, schema);
4608
4936
  }
4609
4937
  }
4610
4938
  } else {
4611
- console.log(chalk30.blue.bold(`
4939
+ console.log(chalk31.blue.bold(`
4612
4940
  \u{1F4CB} Environment Variables (${packageName})
4613
4941
  `));
4614
4942
  for (const [key, schema] of allVars) {
4615
4943
  printEnvVar(key, schema, true);
4616
4944
  }
4617
4945
  }
4618
- console.log(chalk30.dim("\n\u{1F4A1} Tip: Use `spfn env init` to generate .env template files\n"));
4946
+ console.log(chalk31.dim("\n\u{1F4A1} Tip: Use `spfn env init` to generate .env template files\n"));
4619
4947
  } catch (error) {
4620
- console.error(chalk30.red(`
4948
+ console.error(chalk31.red(`
4621
4949
  \u274C ${error instanceof Error ? error.message : "Unknown error"}
4622
4950
  `));
4623
4951
  process.exit(1);
@@ -4625,17 +4953,17 @@ ${file}`));
4625
4953
  }
4626
4954
  function printEnvVar(key, schema, showFile = false) {
4627
4955
  const typeStr = formatType(schema.type);
4628
- const requiredStr = schema.required || schema.default !== void 0 ? chalk30.red("[required]") : chalk30.dim("[optional]");
4629
- const sensitiveStr = schema.sensitive ? chalk30.yellow(" [sensitive]") : "";
4630
- const fileStr = showFile ? chalk30.dim(` \u2192 ${getTargetFile(schema)}`) : "";
4631
- console.log(`${chalk30.bold.cyan(key)} ${chalk30.dim("(")}${typeStr}${chalk30.dim(")")} ${requiredStr}${sensitiveStr}${fileStr}`);
4632
- console.log(` ${chalk30.dim(schema.description)}`);
4956
+ const requiredStr = schema.required || schema.default !== void 0 ? chalk31.red("[required]") : chalk31.dim("[optional]");
4957
+ const sensitiveStr = schema.sensitive ? chalk31.yellow(" [sensitive]") : "";
4958
+ const fileStr = showFile ? chalk31.dim(` \u2192 ${getTargetFile(schema)}`) : "";
4959
+ console.log(`${chalk31.bold.cyan(key)} ${chalk31.dim("(")}${typeStr}${chalk31.dim(")")} ${requiredStr}${sensitiveStr}${fileStr}`);
4960
+ console.log(` ${chalk31.dim(schema.description)}`);
4633
4961
  if (schema.default !== void 0) {
4634
- console.log(` ${chalk30.dim("Default:")} ${formatDefault(schema.default, schema.type)}`);
4962
+ console.log(` ${chalk31.dim("Default:")} ${formatDefault(schema.default, schema.type)}`);
4635
4963
  }
4636
4964
  if (schema.examples && schema.examples.length > 0) {
4637
4965
  const exampleStr = schema.examples.map((ex) => formatDefault(ex, schema.type)).join(", ");
4638
- console.log(` ${chalk30.dim("Examples:")} ${exampleStr}`);
4966
+ console.log(` ${chalk31.dim("Examples:")} ${exampleStr}`);
4639
4967
  }
4640
4968
  console.log();
4641
4969
  }
@@ -4643,7 +4971,7 @@ async function showEnvStats(options) {
4643
4971
  const packageName = options.package || "@spfn/core";
4644
4972
  try {
4645
4973
  const envSchema = await loadEnvSchema(packageName);
4646
- console.log(chalk30.blue.bold(`
4974
+ console.log(chalk31.blue.bold(`
4647
4975
  \u{1F4CA} Environment Variable Statistics (${packageName})
4648
4976
  `));
4649
4977
  const allVars = Object.entries(envSchema);
@@ -4665,24 +4993,24 @@ async function showEnvStats(options) {
4665
4993
  acc[file] = (acc[file] || 0) + 1;
4666
4994
  return acc;
4667
4995
  }, {});
4668
- console.log(`${chalk30.bold("Total variables:")} ${chalk30.cyan(allVars.length)}`);
4669
- console.log(`${chalk30.bold("Required:")} ${chalk30.red(required.length)}`);
4670
- console.log(`${chalk30.bold("Optional:")} ${chalk30.dim(optional.length)}`);
4671
- console.log(`${chalk30.bold("Sensitive:")} ${chalk30.yellow(sensitive.length)}`);
4672
- console.log(chalk30.bold("\nBy Target:"));
4673
- console.log(` ${chalk30.blue("Next.js accessible:")} ${chalk30.cyan(nextjsVars.length)}`);
4674
- console.log(` ${chalk30.magenta("SPFN server only:")} ${chalk30.cyan(serverOnlyVars.length)}`);
4675
- console.log(chalk30.bold("\nBy File:"));
4996
+ console.log(`${chalk31.bold("Total variables:")} ${chalk31.cyan(allVars.length)}`);
4997
+ console.log(`${chalk31.bold("Required:")} ${chalk31.red(required.length)}`);
4998
+ console.log(`${chalk31.bold("Optional:")} ${chalk31.dim(optional.length)}`);
4999
+ console.log(`${chalk31.bold("Sensitive:")} ${chalk31.yellow(sensitive.length)}`);
5000
+ console.log(chalk31.bold("\nBy Target:"));
5001
+ console.log(` ${chalk31.blue("Next.js accessible:")} ${chalk31.cyan(nextjsVars.length)}`);
5002
+ console.log(` ${chalk31.magenta("SPFN server only:")} ${chalk31.cyan(serverOnlyVars.length)}`);
5003
+ console.log(chalk31.bold("\nBy File:"));
4676
5004
  for (const [file, count] of Object.entries(fileCount)) {
4677
- console.log(` ${chalk30.dim(file)}: ${chalk30.cyan(count)}`);
5005
+ console.log(` ${chalk31.dim(file)}: ${chalk31.cyan(count)}`);
4678
5006
  }
4679
- console.log(chalk30.bold("\nBy Type:"));
5007
+ console.log(chalk31.bold("\nBy Type:"));
4680
5008
  for (const [type, count] of Object.entries(typeCount)) {
4681
- console.log(` ${formatType(type)}: ${chalk30.cyan(count)}`);
5009
+ console.log(` ${formatType(type)}: ${chalk31.cyan(count)}`);
4682
5010
  }
4683
5011
  console.log();
4684
5012
  } catch (error) {
4685
- console.error(chalk30.red(`
5013
+ console.error(chalk31.red(`
4686
5014
  \u274C ${error instanceof Error ? error.message : "Unknown error"}
4687
5015
  `));
4688
5016
  process.exit(1);
@@ -4702,40 +5030,40 @@ async function searchEnvVars(query, options) {
4702
5030
  }
4703
5031
  }
4704
5032
  if (results.length === 0) {
4705
- console.log(chalk30.yellow(`
5033
+ console.log(chalk31.yellow(`
4706
5034
  \u26A0\uFE0F No environment variables found matching "${query}"
4707
5035
  `));
4708
5036
  return;
4709
5037
  }
4710
- console.log(chalk30.blue.bold(`
5038
+ console.log(chalk31.blue.bold(`
4711
5039
  \u{1F50D} Found ${results.length} environment variable(s) matching "${query}"
4712
5040
  `));
4713
5041
  for (const [key, schema] of results) {
4714
5042
  const typeStr = formatType(schema.type);
4715
- const requiredStr = schema.required || schema.default !== void 0 ? chalk30.red("[required]") : chalk30.dim("[optional]");
4716
- console.log(`${chalk30.bold.cyan(key)} ${chalk30.dim("(")}${typeStr}${chalk30.dim(")")} ${requiredStr}`);
4717
- console.log(` ${chalk30.dim(schema.description)}`);
5043
+ const requiredStr = schema.required || schema.default !== void 0 ? chalk31.red("[required]") : chalk31.dim("[optional]");
5044
+ console.log(`${chalk31.bold.cyan(key)} ${chalk31.dim("(")}${typeStr}${chalk31.dim(")")} ${requiredStr}`);
5045
+ console.log(` ${chalk31.dim(schema.description)}`);
4718
5046
  if (schema.default !== void 0) {
4719
- console.log(` ${chalk30.dim("Default:")} ${formatDefault(schema.default, schema.type)}`);
5047
+ console.log(` ${chalk31.dim("Default:")} ${formatDefault(schema.default, schema.type)}`);
4720
5048
  }
4721
5049
  console.log();
4722
5050
  }
4723
5051
  } catch (error) {
4724
- console.error(chalk30.red(`
5052
+ console.error(chalk31.red(`
4725
5053
  \u274C ${error instanceof Error ? error.message : "Unknown error"}
4726
5054
  `));
4727
5055
  process.exit(1);
4728
5056
  }
4729
5057
  }
4730
- var envCommand = new Command11("env").description("Manage environment variables");
5058
+ var envCommand = new Command12("env").description("Manage environment variables");
4731
5059
  envCommand.command("list").description("List all environment variables from schema").option("-p, --package <package>", "Package name to read env schema from", "@spfn/core").option("-g, --group", "Group variables by target file").action(listEnvVars);
4732
5060
  envCommand.command("stats").description("Show environment variable statistics").option("-p, --package <package>", "Package name to read env schema from", "@spfn/core").action(showEnvStats);
4733
5061
  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);
4734
5062
  function validateEnvOption(envValue) {
4735
5063
  if (!VALID_ENVS.includes(envValue)) {
4736
- console.error(chalk30.red(`
5064
+ console.error(chalk31.red(`
4737
5065
  \u274C Invalid environment: "${envValue}"`));
4738
- console.log(chalk30.dim(` Valid values: ${VALID_ENVS.join(", ")}
5066
+ console.log(chalk31.dim(` Valid values: ${VALID_ENVS.join(", ")}
4739
5067
  `));
4740
5068
  process.exit(1);
4741
5069
  }
@@ -4756,8 +5084,8 @@ async function initEnvFiles(options) {
4756
5084
  return acc;
4757
5085
  }, {});
4758
5086
  if (targetEnv) {
4759
- console.log(chalk30.blue.bold(`
4760
- \u{1F680} Generating .env template files for ${chalk30.cyan(targetEnv)} environment
5087
+ console.log(chalk31.blue.bold(`
5088
+ \u{1F680} Generating .env template files for ${chalk31.cyan(targetEnv)} environment
4761
5089
  `));
4762
5090
  const envSpecificFiles = {};
4763
5091
  const committedVars = allVars.filter(([_, schema]) => !schema.sensitive);
@@ -4773,24 +5101,24 @@ async function initEnvFiles(options) {
4773
5101
  writeEnvTemplate(cwd, file, vars, options.force ?? false);
4774
5102
  }
4775
5103
  } else {
4776
- console.log(chalk30.blue.bold(`
5104
+ console.log(chalk31.blue.bold(`
4777
5105
  \u{1F680} Generating .env template files
4778
5106
  `));
4779
5107
  for (const [file, vars] of Object.entries(grouped)) {
4780
5108
  writeEnvTemplate(cwd, file, vars, options.force ?? false);
4781
5109
  }
4782
5110
  }
4783
- console.log(chalk30.dim("\n\u{1F4A1} Copy .example files to create your actual .env files:"));
4784
- console.log(chalk30.dim(" cp .env.example .env"));
4785
- console.log(chalk30.dim(" cp .env.local.example .env.local"));
4786
- console.log(chalk30.dim(" cp .env.server.example .env.server"));
5111
+ console.log(chalk31.dim("\n\u{1F4A1} Copy .example files to create your actual .env files:"));
5112
+ console.log(chalk31.dim(" cp .env.example .env"));
5113
+ console.log(chalk31.dim(" cp .env.local.example .env.local"));
5114
+ console.log(chalk31.dim(" cp .env.server.example .env.server"));
4787
5115
  if (targetEnv) {
4788
- console.log(chalk30.dim(` cp .env.${targetEnv}.example .env.${targetEnv}`));
4789
- console.log(chalk30.dim(` cp .env.${targetEnv}.local.example .env.${targetEnv}.local`));
5116
+ console.log(chalk31.dim(` cp .env.${targetEnv}.example .env.${targetEnv}`));
5117
+ console.log(chalk31.dim(` cp .env.${targetEnv}.local.example .env.${targetEnv}.local`));
4790
5118
  }
4791
5119
  console.log("");
4792
5120
  } catch (error) {
4793
- console.error(chalk30.red(`
5121
+ console.error(chalk31.red(`
4794
5122
  \u274C ${error instanceof Error ? error.message : "Unknown error"}
4795
5123
  `));
4796
5124
  process.exit(1);
@@ -4798,12 +5126,12 @@ async function initEnvFiles(options) {
4798
5126
  }
4799
5127
  function writeEnvTemplate(cwd, file, vars, force) {
4800
5128
  const filePath = resolve2(cwd, file);
4801
- if (existsSync26(filePath) && !force) {
4802
- console.log(chalk30.yellow(` \u23ED\uFE0F ${file} already exists (use --force to overwrite)`));
5129
+ if (existsSync28(filePath) && !force) {
5130
+ console.log(chalk31.yellow(` \u23ED\uFE0F ${file} already exists (use --force to overwrite)`));
4803
5131
  return;
4804
5132
  }
4805
- writeFileSync16(filePath, generateEnvFileContent(vars), "utf-8");
4806
- console.log(chalk30.green(` \u2705 ${file} (${vars.length} variables)`));
5133
+ writeFileSync17(filePath, generateEnvFileContent(vars), "utf-8");
5134
+ console.log(chalk31.green(` \u2705 ${file} (${vars.length} variables)`));
4807
5135
  }
4808
5136
  function generateEnvFileContent(vars) {
4809
5137
  const lines = [
@@ -4838,7 +5166,7 @@ async function checkEnvFiles(options) {
4838
5166
  const envSchema = await loadEnvSchema(packageName);
4839
5167
  const allVars = Object.entries(envSchema);
4840
5168
  const envLabel = targetEnv ? ` (${targetEnv})` : "";
4841
- console.log(chalk30.blue.bold(`
5169
+ console.log(chalk31.blue.bold(`
4842
5170
  \u{1F50D} Checking .env files against schema${envLabel}
4843
5171
  `));
4844
5172
  const filesToCheck = targetEnv ? getEnvFilesForEnvironment(targetEnv) : [...BASE_ENV_FILES.nextjs, ...BASE_ENV_FILES.server];
@@ -4847,7 +5175,7 @@ async function checkEnvFiles(options) {
4847
5175
  const warnings = [];
4848
5176
  for (const file of filesToCheck) {
4849
5177
  const filePath = resolve2(cwd, file);
4850
- if (!existsSync26(filePath)) {
5178
+ if (!existsSync28(filePath)) {
4851
5179
  continue;
4852
5180
  }
4853
5181
  const content = readFileSync12(filePath, "utf-8");
@@ -4855,7 +5183,7 @@ async function checkEnvFiles(options) {
4855
5183
  for (const [key, value] of Object.entries(parsed)) {
4856
5184
  loadedEnv[key] = { value: value || "", file };
4857
5185
  }
4858
- console.log(chalk30.dim(` \u{1F4C4} ${file} loaded`));
5186
+ console.log(chalk31.dim(` \u{1F4C4} ${file} loaded`));
4859
5187
  }
4860
5188
  console.log("");
4861
5189
  for (const [key, schema] of allVars) {
@@ -4863,7 +5191,7 @@ async function checkEnvFiles(options) {
4863
5191
  const found = loadedEnv[key];
4864
5192
  if (!found) {
4865
5193
  if (schema.required && schema.default === void 0) {
4866
- issues.push(`${chalk30.red("\u2717")} ${chalk30.cyan(key)} is required but not found in any .env file`);
5194
+ issues.push(`${chalk31.red("\u2717")} ${chalk31.cyan(key)} is required but not found in any .env file`);
4867
5195
  }
4868
5196
  continue;
4869
5197
  }
@@ -4873,11 +5201,11 @@ async function checkEnvFiles(options) {
4873
5201
  if (!shouldBeNextjs && isNextjsFile && !isServerFile) {
4874
5202
  if (schema.sensitive) {
4875
5203
  issues.push(
4876
- `${chalk30.red("\u2717")} ${chalk30.cyan(key)} is sensitive and should be in ${chalk30.magenta(expectedFile)}, but found in ${chalk30.yellow(found.file)} (security risk!)`
5204
+ `${chalk31.red("\u2717")} ${chalk31.cyan(key)} is sensitive and should be in ${chalk31.magenta(expectedFile)}, but found in ${chalk31.yellow(found.file)} (security risk!)`
4877
5205
  );
4878
5206
  } else {
4879
5207
  warnings.push(
4880
- `${chalk30.yellow("\u26A0")} ${chalk30.cyan(key)} should be in ${chalk30.magenta(expectedFile)}, but found in ${chalk30.dim(found.file)}`
5208
+ `${chalk31.yellow("\u26A0")} ${chalk31.cyan(key)} should be in ${chalk31.magenta(expectedFile)}, but found in ${chalk31.dim(found.file)}`
4881
5209
  );
4882
5210
  }
4883
5211
  }
@@ -4885,34 +5213,34 @@ async function checkEnvFiles(options) {
4885
5213
  for (const [key, { file }] of Object.entries(loadedEnv)) {
4886
5214
  const inSchema = allVars.some(([k]) => k === key);
4887
5215
  if (!inSchema) {
4888
- warnings.push(`${chalk30.yellow("\u26A0")} ${chalk30.cyan(key)} in ${chalk30.dim(file)} is not in schema`);
5216
+ warnings.push(`${chalk31.yellow("\u26A0")} ${chalk31.cyan(key)} in ${chalk31.dim(file)} is not in schema`);
4889
5217
  }
4890
5218
  }
4891
5219
  if (issues.length > 0) {
4892
- console.log(chalk30.red.bold("Issues:"));
5220
+ console.log(chalk31.red.bold("Issues:"));
4893
5221
  for (const issue of issues) {
4894
5222
  console.log(` ${issue}`);
4895
5223
  }
4896
5224
  console.log("");
4897
5225
  }
4898
5226
  if (warnings.length > 0) {
4899
- console.log(chalk30.yellow.bold("Warnings:"));
5227
+ console.log(chalk31.yellow.bold("Warnings:"));
4900
5228
  for (const warning of warnings) {
4901
5229
  console.log(` ${warning}`);
4902
5230
  }
4903
5231
  console.log("");
4904
5232
  }
4905
5233
  if (issues.length === 0 && warnings.length === 0) {
4906
- console.log(chalk30.green("\u2705 All environment variables are correctly configured!\n"));
5234
+ console.log(chalk31.green("\u2705 All environment variables are correctly configured!\n"));
4907
5235
  } else {
4908
- console.log(chalk30.dim(`Found ${issues.length} issue(s) and ${warnings.length} warning(s)
5236
+ console.log(chalk31.dim(`Found ${issues.length} issue(s) and ${warnings.length} warning(s)
4909
5237
  `));
4910
5238
  if (issues.length > 0) {
4911
5239
  process.exit(1);
4912
5240
  }
4913
5241
  }
4914
5242
  } catch (error) {
4915
- console.error(chalk30.red(`
5243
+ console.error(chalk31.red(`
4916
5244
  \u274C ${error instanceof Error ? error.message : "Unknown error"}
4917
5245
  `));
4918
5246
  process.exit(1);
@@ -4926,15 +5254,15 @@ async function validateEnvVars(options) {
4926
5254
  if (targetEnv) {
4927
5255
  const { loadEnv: loadEnv10 } = await import("@spfn/core/env/loader");
4928
5256
  const result = loadEnv10({ nodeEnv: targetEnv });
4929
- console.log(chalk30.blue.bold(`
4930
- \u{1F50D} Validating environment variables for ${chalk30.cyan(targetEnv)}
5257
+ console.log(chalk31.blue.bold(`
5258
+ \u{1F50D} Validating environment variables for ${chalk31.cyan(targetEnv)}
4931
5259
  `));
4932
5260
  if (result.loadedFiles.length > 0) {
4933
- console.log(chalk30.dim(` Loaded: ${result.loadedFiles.join(", ")}`));
5261
+ console.log(chalk31.dim(` Loaded: ${result.loadedFiles.join(", ")}`));
4934
5262
  }
4935
5263
  console.log("");
4936
5264
  } else {
4937
- console.log(chalk30.blue.bold(`
5265
+ console.log(chalk31.blue.bold(`
4938
5266
  \u{1F50D} Validating environment variables
4939
5267
  `));
4940
5268
  }
@@ -4942,7 +5270,7 @@ async function validateEnvVars(options) {
4942
5270
  const allWarnings = [];
4943
5271
  for (const packageName of packages) {
4944
5272
  try {
4945
- console.log(chalk30.dim(` \u{1F4E6} ${packageName}`));
5273
+ console.log(chalk31.dim(` \u{1F4E6} ${packageName}`));
4946
5274
  const envSchema = await loadEnvSchema(packageName);
4947
5275
  const { createEnvRegistry } = await import("@spfn/core/env");
4948
5276
  const registry = createEnvRegistry(envSchema);
@@ -4955,10 +5283,10 @@ async function validateEnvVars(options) {
4955
5283
  }
4956
5284
  } catch (error) {
4957
5285
  if (error instanceof Error && error.message.includes("does not export envSchema")) {
4958
- console.log(chalk30.dim(` \u23ED\uFE0F No envSchema exported, skipping`));
5286
+ console.log(chalk31.dim(` \u23ED\uFE0F No envSchema exported, skipping`));
4959
5287
  continue;
4960
5288
  }
4961
- console.error(chalk30.red(` \u274C Failed to load: ${error instanceof Error ? error.message : String(error)}`));
5289
+ console.error(chalk31.red(` \u274C Failed to load: ${error instanceof Error ? error.message : String(error)}`));
4962
5290
  if (options.strict) {
4963
5291
  process.exit(1);
4964
5292
  }
@@ -4966,32 +5294,32 @@ async function validateEnvVars(options) {
4966
5294
  }
4967
5295
  console.log("");
4968
5296
  if (allErrors.length > 0) {
4969
- console.log(chalk30.red.bold(`\u274C Validation Errors (${allErrors.length}):
5297
+ console.log(chalk31.red.bold(`\u274C Validation Errors (${allErrors.length}):
4970
5298
  `));
4971
5299
  for (const error of allErrors) {
4972
- console.log(` ${chalk30.red("\u2717")} ${chalk30.cyan(error.key)}`);
4973
- console.log(` ${chalk30.dim(error.message)}`);
4974
- console.log(` ${chalk30.dim(`from ${error.package}`)}`);
5300
+ console.log(` ${chalk31.red("\u2717")} ${chalk31.cyan(error.key)}`);
5301
+ console.log(` ${chalk31.dim(error.message)}`);
5302
+ console.log(` ${chalk31.dim(`from ${error.package}`)}`);
4975
5303
  console.log("");
4976
5304
  }
4977
5305
  }
4978
5306
  if (allWarnings.length > 0) {
4979
- console.log(chalk30.yellow.bold(`\u26A0\uFE0F Warnings (${allWarnings.length}):
5307
+ console.log(chalk31.yellow.bold(`\u26A0\uFE0F Warnings (${allWarnings.length}):
4980
5308
  `));
4981
5309
  for (const warning of allWarnings) {
4982
- console.log(` ${chalk30.yellow("\u26A0")} ${chalk30.cyan(warning.key)}`);
4983
- console.log(` ${chalk30.dim(warning.message)}`);
5310
+ console.log(` ${chalk31.yellow("\u26A0")} ${chalk31.cyan(warning.key)}`);
5311
+ console.log(` ${chalk31.dim(warning.message)}`);
4984
5312
  console.log("");
4985
5313
  }
4986
5314
  }
4987
5315
  if (allErrors.length === 0 && allWarnings.length === 0) {
4988
- console.log(chalk30.green.bold("\u2705 All environment variables are valid!\n"));
5316
+ console.log(chalk31.green.bold("\u2705 All environment variables are valid!\n"));
4989
5317
  } else if (allErrors.length === 0) {
4990
- console.log(chalk30.green("\u2705 No errors found."));
4991
- console.log(chalk30.yellow(`\u26A0\uFE0F ${allWarnings.length} warning(s) found.
5318
+ console.log(chalk31.green("\u2705 No errors found."));
5319
+ console.log(chalk31.yellow(`\u26A0\uFE0F ${allWarnings.length} warning(s) found.
4992
5320
  `));
4993
5321
  } else {
4994
- console.log(chalk30.red(`
5322
+ console.log(chalk31.red(`
4995
5323
  \u274C Validation failed with ${allErrors.length} error(s)
4996
5324
  `));
4997
5325
  process.exit(1);
@@ -5000,12 +5328,12 @@ async function validateEnvVars(options) {
5000
5328
  envCommand.command("validate").description("Validate environment variables against schema (for CI/CD)").option("-p, --packages <packages...>", "Packages to validate", ["@spfn/core"]).option("-e, --env <environment>", "Load env files for specific environment before validating").option("-s, --strict", "Exit on any error (including load failures)").action(validateEnvVars);
5001
5329
 
5002
5330
  // src/commands/secret/index.ts
5003
- import { Command as Command12 } from "commander";
5331
+ import { Command as Command13 } from "commander";
5004
5332
 
5005
5333
  // src/commands/secret/set.ts
5006
5334
  init_logger();
5007
- import prompts8 from "prompts";
5008
- import chalk31 from "chalk";
5335
+ import prompts9 from "prompts";
5336
+ import chalk32 from "chalk";
5009
5337
 
5010
5338
  // src/commands/secret/options.ts
5011
5339
  init_logger();
@@ -5020,34 +5348,34 @@ function resolveEnv(env9) {
5020
5348
 
5021
5349
  // src/commands/secret/store-value.ts
5022
5350
  init_logger();
5023
- import { join as join25 } from "path";
5351
+ import { join as join27 } from "path";
5024
5352
  init_env_file();
5025
5353
 
5026
5354
  // src/utils/sops.ts
5027
- import { execa as execa10 } from "execa";
5028
- import { existsSync as existsSync27, mkdirSync as mkdirSync3, writeFileSync as writeFileSync17 } from "fs";
5029
- import { dirname as dirname3 } from "path";
5355
+ import { execa as execa11 } from "execa";
5356
+ import { existsSync as existsSync29, mkdirSync as mkdirSync5, writeFileSync as writeFileSync18 } from "fs";
5357
+ import { dirname as dirname4 } from "path";
5030
5358
  async function ensureSopsInstalled() {
5031
5359
  try {
5032
- await execa10("sops", ["--version"]);
5360
+ await execa11("sops", ["--version"]);
5033
5361
  } catch {
5034
5362
  throw new Error("`sops` not found on PATH. Install it: https://github.com/getsops/sops");
5035
5363
  }
5036
5364
  }
5037
5365
  async function sopsDecrypt(absFile) {
5038
- if (!existsSync27(absFile)) {
5366
+ if (!existsSync29(absFile)) {
5039
5367
  return {};
5040
5368
  }
5041
- const { stdout } = await execa10("sops", ["--decrypt", "--output-type", "json", absFile]);
5369
+ const { stdout } = await execa11("sops", ["--decrypt", "--output-type", "json", absFile]);
5042
5370
  return JSON.parse(stdout);
5043
5371
  }
5044
5372
  async function sopsSetValue(absFile, relFile, key, value) {
5045
- if (existsSync27(absFile)) {
5046
- await execa10("sops", ["set", absFile, `["${key}"]`, JSON.stringify(value)]);
5373
+ if (existsSync29(absFile)) {
5374
+ await execa11("sops", ["set", absFile, `["${key}"]`, JSON.stringify(value)]);
5047
5375
  return;
5048
5376
  }
5049
- mkdirSync3(dirname3(absFile), { recursive: true });
5050
- const { stdout } = await execa10(
5377
+ mkdirSync5(dirname4(absFile), { recursive: true });
5378
+ const { stdout } = await execa11(
5051
5379
  "sops",
5052
5380
  [
5053
5381
  "--encrypt",
@@ -5061,27 +5389,27 @@ async function sopsSetValue(absFile, relFile, key, value) {
5061
5389
  ],
5062
5390
  { input: JSON.stringify({ [key]: value }) }
5063
5391
  );
5064
- writeFileSync17(absFile, stdout);
5392
+ writeFileSync18(absFile, stdout);
5065
5393
  }
5066
5394
  async function sopsUpdateKeys(absFile) {
5067
- await execa10("sops", ["updatekeys", "-y", absFile]);
5395
+ await execa11("sops", ["updatekeys", "-y", absFile]);
5068
5396
  }
5069
5397
 
5070
5398
  // src/utils/secret-config.ts
5071
- import { existsSync as existsSync28, readdirSync as readdirSync3 } from "fs";
5072
- import { join as join24 } from "path";
5399
+ import { existsSync as existsSync30, readdirSync as readdirSync3 } from "fs";
5400
+ import { join as join26 } from "path";
5073
5401
  var SECRETS_DIR = "secrets";
5074
5402
  function getSopsFile(cwd, env9) {
5075
5403
  const relFile = `${SECRETS_DIR}/${env9}.enc.json`;
5076
- return { absFile: join24(cwd, relFile), relFile };
5404
+ return { absFile: join26(cwd, relFile), relFile };
5077
5405
  }
5078
5406
  function findUp(cwd, filename, maxDepth = 6) {
5079
5407
  let dir = cwd;
5080
5408
  for (let depth = 0; depth < maxDepth; depth++) {
5081
- if (existsSync28(join24(dir, filename))) {
5409
+ if (existsSync30(join26(dir, filename))) {
5082
5410
  return dir;
5083
5411
  }
5084
- const parent = join24(dir, "..");
5412
+ const parent = join26(dir, "..");
5085
5413
  if (parent === dir) {
5086
5414
  break;
5087
5415
  }
@@ -5091,17 +5419,17 @@ function findUp(cwd, filename, maxDepth = 6) {
5091
5419
  }
5092
5420
  function findSopsConfig(cwd) {
5093
5421
  const dir = findUp(cwd, ".sops.yaml");
5094
- return dir ? join24(dir, ".sops.yaml") : null;
5422
+ return dir ? join26(dir, ".sops.yaml") : null;
5095
5423
  }
5096
5424
  function hasSopsConfig(cwd) {
5097
5425
  return findSopsConfig(cwd) !== null;
5098
5426
  }
5099
5427
  function listSopsFiles(cwd) {
5100
- const dir = join24(cwd, SECRETS_DIR);
5101
- if (!existsSync28(dir)) {
5428
+ const dir = join26(cwd, SECRETS_DIR);
5429
+ if (!existsSync30(dir)) {
5102
5430
  return [];
5103
5431
  }
5104
- return readdirSync3(dir).filter((name) => name.endsWith(".enc.json")).map((name) => join24(dir, name));
5432
+ return readdirSync3(dir).filter((name) => name.endsWith(".enc.json")).map((name) => join26(dir, name));
5105
5433
  }
5106
5434
 
5107
5435
  // src/commands/secret/store-value.ts
@@ -5130,7 +5458,7 @@ async function storeSecret(cwd, env9, key, value) {
5130
5458
  );
5131
5459
  }
5132
5460
  await store.set(keychainName(key), value);
5133
- const serverEnvPath = join25(cwd, ".env.server");
5461
+ const serverEnvPath = join27(cwd, ".env.server");
5134
5462
  const result = upsertEnvVar(serverEnvPath, key, keychainRef(key));
5135
5463
  restrictEnvFilePerms(serverEnvPath);
5136
5464
  ensureGitignored(cwd, [{ pattern: ".env.server", comment: "spfn server env (secrets)" }]);
@@ -5158,10 +5486,10 @@ async function secretSet(key, options) {
5158
5486
  process.exit(1);
5159
5487
  }
5160
5488
  await warnIfNotSecret(pkg, resolvedKey);
5161
- const { value } = await prompts8({
5489
+ const { value } = await prompts9({
5162
5490
  type: "password",
5163
5491
  name: "value",
5164
- message: `Value for ${chalk31.cyan(resolvedKey)} (${env9})`
5492
+ message: `Value for ${chalk32.cyan(resolvedKey)} (${env9})`
5165
5493
  });
5166
5494
  if (!value) {
5167
5495
  logger.warn("Cancelled \u2014 no value entered.");
@@ -5185,7 +5513,7 @@ async function pickSecretKey(pkg) {
5185
5513
  if (entries.length === 0) {
5186
5514
  return void 0;
5187
5515
  }
5188
- const { key } = await prompts8({
5516
+ const { key } = await prompts9({
5189
5517
  type: "select",
5190
5518
  name: "key",
5191
5519
  message: "Which secret?",
@@ -5210,7 +5538,7 @@ async function warnIfNotSecret(pkg, key) {
5210
5538
 
5211
5539
  // src/commands/secret/list.ts
5212
5540
  init_logger();
5213
- import chalk32 from "chalk";
5541
+ import chalk33 from "chalk";
5214
5542
  async function secretList(options) {
5215
5543
  const env9 = resolveEnv(options.env);
5216
5544
  const pkg = options.package ?? "@spfn/core";
@@ -5226,12 +5554,12 @@ async function secretList(options) {
5226
5554
  return;
5227
5555
  }
5228
5556
  const present = await loadPresence(env9, entries);
5229
- console.log(chalk32.blue.bold(`
5557
+ console.log(chalk33.blue.bold(`
5230
5558
  \u{1F511} Secrets (${pkg}) \u2014 ${env9}
5231
5559
  `));
5232
5560
  for (const entry of entries) {
5233
5561
  const status = statusOf(entry, present.has(entry.key));
5234
- console.log(` ${badge(status)} ${chalk32.cyan(entry.key)}${entry.generate ? chalk32.dim(" (generatable)") : ""}`);
5562
+ console.log(` ${badge(status)} ${chalk33.cyan(entry.key)}${entry.generate ? chalk33.dim(" (generatable)") : ""}`);
5235
5563
  }
5236
5564
  console.log();
5237
5565
  }
@@ -5264,17 +5592,17 @@ function statusOf(entry, present) {
5264
5592
  function badge(status) {
5265
5593
  switch (status) {
5266
5594
  case "set":
5267
- return chalk32.green("\u25CF");
5595
+ return chalk33.green("\u25CF");
5268
5596
  case "missing":
5269
- return chalk32.yellow("\u25CB");
5597
+ return chalk33.yellow("\u25CB");
5270
5598
  case "awaiting-input":
5271
- return chalk32.red("\u25CB");
5599
+ return chalk33.red("\u25CB");
5272
5600
  }
5273
5601
  }
5274
5602
 
5275
5603
  // src/commands/secret/generate.ts
5276
5604
  init_logger();
5277
- import chalk33 from "chalk";
5605
+ import chalk34 from "chalk";
5278
5606
  async function secretGenerate(key, options) {
5279
5607
  const env9 = resolveEnv(options.env);
5280
5608
  const pkg = options.package ?? "@spfn/core";
@@ -5312,7 +5640,7 @@ function requireGeneratable(schema, key) {
5312
5640
  process.exit(1);
5313
5641
  }
5314
5642
  if (!entry.generate) {
5315
- logger.error(`${chalk33.cyan(key)} has no generate strategy \u2014 it's an external value. Use \`spfn secret set ${key}\`.`);
5643
+ logger.error(`${chalk34.cyan(key)} has no generate strategy \u2014 it's an external value. Use \`spfn secret set ${key}\`.`);
5316
5644
  process.exit(1);
5317
5645
  }
5318
5646
  return entry;
@@ -5320,7 +5648,7 @@ function requireGeneratable(schema, key) {
5320
5648
 
5321
5649
  // src/commands/secret/rotate.ts
5322
5650
  init_logger();
5323
- import chalk34 from "chalk";
5651
+ import chalk35 from "chalk";
5324
5652
  async function secretRotate(key, options) {
5325
5653
  const env9 = resolveEnv(options.env);
5326
5654
  const pkg = options.package ?? "@spfn/core";
@@ -5346,7 +5674,7 @@ async function secretRotate(key, options) {
5346
5674
  }
5347
5675
  }
5348
5676
  for (const entry of external) {
5349
- logger.warn(`${chalk34.cyan(entry.key)} is external \u2014 reissue it at the provider, then \`spfn secret set ${entry.key} --env ${env9}\`.`);
5677
+ logger.warn(`${chalk35.cyan(entry.key)} is external \u2014 reissue it at the provider, then \`spfn secret set ${entry.key} --env ${env9}\`.`);
5350
5678
  }
5351
5679
  if (generatable.length > 0) {
5352
5680
  logger.info("Rotation updates the source only \u2014 commit and deploy to apply.");
@@ -5367,17 +5695,17 @@ function requireEntry(schema, key) {
5367
5695
 
5368
5696
  // src/commands/secret/keygen.ts
5369
5697
  init_logger();
5370
- import { execa as execa11 } from "execa";
5371
- import { existsSync as existsSync29, mkdirSync as mkdirSync4 } from "fs";
5698
+ import { execa as execa12 } from "execa";
5699
+ import { existsSync as existsSync31, mkdirSync as mkdirSync6 } from "fs";
5372
5700
  import { homedir } from "os";
5373
- import { dirname as dirname4, join as join26 } from "path";
5374
- import chalk35 from "chalk";
5701
+ import { dirname as dirname5, join as join28 } from "path";
5702
+ import chalk36 from "chalk";
5375
5703
  function ageKeyFile() {
5376
- return process.env.SOPS_AGE_KEY_FILE ?? join26(homedir(), ".config", "sops", "age", "keys.txt");
5704
+ return process.env.SOPS_AGE_KEY_FILE ?? join28(homedir(), ".config", "sops", "age", "keys.txt");
5377
5705
  }
5378
5706
  async function ensureAgeInstalled() {
5379
5707
  try {
5380
- await execa11("age-keygen", ["--version"]);
5708
+ await execa12("age-keygen", ["--version"]);
5381
5709
  } catch {
5382
5710
  throw new Error("`age-keygen` not found on PATH. Install age: https://github.com/FiloSottile/age");
5383
5711
  }
@@ -5393,14 +5721,14 @@ async function secretKeygen() {
5393
5721
  process.exit(1);
5394
5722
  }
5395
5723
  const keyFile = ageKeyFile();
5396
- if (existsSync29(keyFile)) {
5397
- const { stdout } = await execa11("age-keygen", ["-y", keyFile]);
5724
+ if (existsSync31(keyFile)) {
5725
+ const { stdout } = await execa12("age-keygen", ["-y", keyFile]);
5398
5726
  logger.warn(`age key file already exists: ${keyFile}`);
5399
5727
  printPublicKeys(publicKeys(stdout));
5400
5728
  return;
5401
5729
  }
5402
- mkdirSync4(dirname4(keyFile), { recursive: true });
5403
- const { stderr } = await execa11("age-keygen", ["-o", keyFile]);
5730
+ mkdirSync6(dirname5(keyFile), { recursive: true });
5731
+ const { stderr } = await execa12("age-keygen", ["-o", keyFile]);
5404
5732
  logger.success(`Created age key: ${keyFile}`);
5405
5733
  printPublicKeys(publicKeys(stderr));
5406
5734
  }
@@ -5409,27 +5737,27 @@ function printPublicKeys(keys) {
5409
5737
  logger.warn("Could not read the public key \u2014 run `age-keygen -y <file>` manually.");
5410
5738
  return;
5411
5739
  }
5412
- console.log(chalk35.bold("\nPublic key(s):"));
5740
+ console.log(chalk36.bold("\nPublic key(s):"));
5413
5741
  for (const key of keys) {
5414
- console.log(` ${chalk35.cyan(key)}`);
5742
+ console.log(` ${chalk36.cyan(key)}`);
5415
5743
  }
5416
- console.log(chalk35.dim("\nRegister it as a recipient:"));
5417
- console.log(chalk35.dim(` spfn secret recipients add ${keys[0]}
5744
+ console.log(chalk36.dim("\nRegister it as a recipient:"));
5745
+ console.log(chalk36.dim(` spfn secret recipients add ${keys[0]}
5418
5746
  `));
5419
5747
  }
5420
5748
 
5421
5749
  // src/commands/secret/recipients.ts
5422
5750
  init_logger();
5423
- import { existsSync as existsSync30, readFileSync as readFileSync13, writeFileSync as writeFileSync18 } from "fs";
5424
- import { join as join27 } from "path";
5751
+ import { existsSync as existsSync32, readFileSync as readFileSync13, writeFileSync as writeFileSync19 } from "fs";
5752
+ import { join as join29 } from "path";
5425
5753
  import { parse as parse3, stringify } from "yaml";
5426
- import chalk36 from "chalk";
5754
+ import chalk37 from "chalk";
5427
5755
  var SOPS_CONFIG = ".sops.yaml";
5428
5756
  var DEFAULT_PATH_REGEX = "secrets/.*\\.enc\\.json$";
5429
5757
  var AGE_RECIPIENT = /^age1[0-9a-z]+$/;
5430
5758
  async function secretRecipients(action, key, _options) {
5431
5759
  const cwd = process.cwd();
5432
- const configPath = join27(cwd, SOPS_CONFIG);
5760
+ const configPath = join29(cwd, SOPS_CONFIG);
5433
5761
  if (action === "list") {
5434
5762
  listRecipients(configPath);
5435
5763
  return;
@@ -5456,30 +5784,30 @@ async function secretRecipients(action, key, _options) {
5456
5784
  logger.warn("Removing a recipient does not revoke values they already decrypted \u2014 rotate those values.");
5457
5785
  }
5458
5786
  rule.age = [...recipients].join(",");
5459
- writeFileSync18(configPath, stringify(config));
5787
+ writeFileSync19(configPath, stringify(config));
5460
5788
  logger.success(`${action === "add" ? "Added" : "Removed"} recipient; updated ${SOPS_CONFIG}.`);
5461
5789
  await reencrypt(cwd);
5462
5790
  }
5463
5791
  function listRecipients(configPath) {
5464
- if (!existsSync30(configPath)) {
5792
+ if (!existsSync32(configPath)) {
5465
5793
  logger.info(`No ${SOPS_CONFIG} found.`);
5466
5794
  return;
5467
5795
  }
5468
5796
  const config = loadConfig(configPath);
5469
5797
  const rules = config.creation_rules ?? [];
5470
- console.log(chalk36.blue.bold(`
5798
+ console.log(chalk37.blue.bold(`
5471
5799
  \u{1F465} Recipients (${SOPS_CONFIG})
5472
5800
  `));
5473
5801
  for (const rule of rules) {
5474
- console.log(chalk36.dim(` ${rule.path_regex ?? "(any path)"}`));
5802
+ console.log(chalk37.dim(` ${rule.path_regex ?? "(any path)"}`));
5475
5803
  for (const recipient of parseRecipients(rule.age)) {
5476
- console.log(` ${chalk36.cyan(recipient)}`);
5804
+ console.log(` ${chalk37.cyan(recipient)}`);
5477
5805
  }
5478
5806
  }
5479
5807
  console.log();
5480
5808
  }
5481
5809
  function loadConfig(configPath) {
5482
- if (!existsSync30(configPath)) {
5810
+ if (!existsSync32(configPath)) {
5483
5811
  return { creation_rules: [] };
5484
5812
  }
5485
5813
  return parse3(readFileSync13(configPath, "utf-8")) ?? { creation_rules: [] };
@@ -5514,8 +5842,8 @@ async function reencrypt(cwd) {
5514
5842
 
5515
5843
  // src/commands/secret/check.ts
5516
5844
  init_logger();
5517
- import { join as join28 } from "path";
5518
- import chalk37 from "chalk";
5845
+ import { join as join30 } from "path";
5846
+ import chalk38 from "chalk";
5519
5847
  init_env_file();
5520
5848
  var COMMITTED_FILES = [".env", ".env.example"];
5521
5849
  var PLACEHOLDER = /(your-|changeme|placeholder|example|<.*>)/i;
@@ -5533,35 +5861,35 @@ async function secretCheck(options) {
5533
5861
  const issues = [];
5534
5862
  const warnings = [];
5535
5863
  for (const file of COMMITTED_FILES) {
5536
- const parsed = parseEnvFile(join28(cwd, file));
5864
+ const parsed = parseEnvFile(join30(cwd, file));
5537
5865
  for (const [key, value] of Object.entries(parsed)) {
5538
5866
  if (secretKeys.has(key) && value.length > 0 && !PLACEHOLDER.test(value)) {
5539
- issues.push(`${chalk37.cyan(key)} has a real value in committed ${chalk37.yellow(file)} \u2014 move it to the keychain/SOPS.`);
5867
+ issues.push(`${chalk38.cyan(key)} has a real value in committed ${chalk38.yellow(file)} \u2014 move it to the keychain/SOPS.`);
5540
5868
  }
5541
5869
  }
5542
5870
  }
5543
- const serverEnv = parseEnvFile(join28(cwd, ".env.server"));
5871
+ const serverEnv = parseEnvFile(join30(cwd, ".env.server"));
5544
5872
  for (const key of secretKeys) {
5545
5873
  const value = serverEnv[key];
5546
5874
  if (value && !value.startsWith(KEYCHAIN_REF_PREFIX)) {
5547
- warnings.push(`${chalk37.cyan(key)} is plaintext in .env.server \u2014 run \`spfn secret set ${key}\` to move it to the keychain.`);
5875
+ warnings.push(`${chalk38.cyan(key)} is plaintext in .env.server \u2014 run \`spfn secret set ${key}\` to move it to the keychain.`);
5548
5876
  }
5549
5877
  }
5550
5878
  report(issues, warnings, cwd);
5551
5879
  }
5552
5880
  function report(issues, warnings, cwd) {
5553
- console.log(chalk37.blue.bold("\n\u{1F50D} Secret hygiene check\n"));
5881
+ console.log(chalk38.blue.bold("\n\u{1F50D} Secret hygiene check\n"));
5554
5882
  for (const issue of issues) {
5555
- console.log(` ${chalk37.red("\u2717")} ${issue}`);
5883
+ console.log(` ${chalk38.red("\u2717")} ${issue}`);
5556
5884
  }
5557
5885
  for (const warning of warnings) {
5558
- console.log(` ${chalk37.yellow("\u26A0")} ${warning}`);
5886
+ console.log(` ${chalk38.yellow("\u26A0")} ${warning}`);
5559
5887
  }
5560
5888
  if (!hasSopsConfig(cwd)) {
5561
- console.log(` ${chalk37.dim("\u2139 no .sops.yaml \u2014 prod secrets need a backend (spfn secret keygen, or add a KMS rule)")}`);
5889
+ console.log(` ${chalk38.dim("\u2139 no .sops.yaml \u2014 prod secrets need a backend (spfn secret keygen, or add a KMS rule)")}`);
5562
5890
  }
5563
5891
  if (issues.length === 0 && warnings.length === 0) {
5564
- console.log(chalk37.green(" \u2713 No plaintext secret leaks found."));
5892
+ console.log(chalk38.green(" \u2713 No plaintext secret leaks found."));
5565
5893
  }
5566
5894
  console.log();
5567
5895
  if (issues.length > 0) {
@@ -5572,7 +5900,7 @@ function report(issues, warnings, cwd) {
5572
5900
  // src/commands/secret/index.ts
5573
5901
  var ENV_OPTION = ["-e, --env <env>", "Target environment (local | development | staging | production)", "local"];
5574
5902
  var PKG_OPTION = ["-p, --package <package>", "Package whose env schema to read", "@spfn/core"];
5575
- var secretCommand = new Command12("secret").description("Manage secrets: keychain locally, SOPS (age / GCP KMS / AWS KMS) for deployed environments");
5903
+ var secretCommand = new Command13("secret").description("Manage secrets: keychain locally, SOPS (age / GCP KMS / AWS KMS) for deployed environments");
5576
5904
  secretCommand.command("set [key]").description("Store a secret value (prompts for the value, masked)").option(...ENV_OPTION).option(...PKG_OPTION).action(secretSet);
5577
5905
  secretCommand.command("list").description("List declared secrets and their status (never prints values)").option(...ENV_OPTION).option(...PKG_OPTION).action(secretList);
5578
5906
  secretCommand.command("generate [key]").description("Generate value(s) for schema secrets that declare a generate strategy").option("-a, --all", "Generate every generatable secret").option(...ENV_OPTION).option(...PKG_OPTION).action(secretGenerate);
@@ -5583,7 +5911,7 @@ secretCommand.command("check").description("Static hygiene lint \u2014 flag plai
5583
5911
 
5584
5912
  // src/index.ts
5585
5913
  init_version();
5586
- var program = new Command13();
5914
+ var program = new Command14();
5587
5915
  program.name("spfn").description("SPFN CLI - The Missing Backend for Next.js").version(getCliVersion());
5588
5916
  program.addCommand(createCommand);
5589
5917
  program.addCommand(initCommand);
@@ -5591,6 +5919,7 @@ program.addCommand(addCommand);
5591
5919
  program.addCommand(devCommand);
5592
5920
  program.addCommand(buildCommand);
5593
5921
  program.addCommand(startCommand);
5922
+ program.addCommand(provisionCommand);
5594
5923
  program.addCommand(codegenCommand);
5595
5924
  program.addCommand(keyCommand);
5596
5925
  program.addCommand(setupCommand);