spfn 0.2.0-beta.63 → 0.2.0-beta.65

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
@@ -922,7 +922,7 @@ var init_deployment_config = __esm({
922
922
 
923
923
  // src/utils/version.ts
924
924
  function getCliVersion() {
925
- return "0.2.0-beta.63";
925
+ return "0.2.0-beta.65";
926
926
  }
927
927
  function getTagFromVersion(version) {
928
928
  const match = version.match(/-([a-z]+)\./i);
@@ -1860,7 +1860,7 @@ var init_migration_status = __esm({
1860
1860
  });
1861
1861
 
1862
1862
  // src/index.ts
1863
- import { Command as Command13 } from "commander";
1863
+ import { Command as Command14 } from "commander";
1864
1864
 
1865
1865
  // src/commands/create.ts
1866
1866
  init_logger();
@@ -2876,16 +2876,95 @@ var startCommand = new Command6("start").description("Start SPFN production serv
2876
2876
  }
2877
2877
  });
2878
2878
 
2879
- // src/commands/codegen.ts
2879
+ // src/commands/provision.ts
2880
2880
  init_logger();
2881
+ init_package_manager();
2881
2882
  import { Command as Command7 } from "commander";
2882
- import { existsSync as existsSync19, writeFileSync as writeFileSync12 } from "fs";
2883
+ import { existsSync as existsSync19, mkdirSync as mkdirSync3, writeFileSync as writeFileSync12 } from "fs";
2883
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";
2884
2963
  import chalk10 from "chalk";
2885
2964
  async function initCodegen(options) {
2886
2965
  const cwd = process.cwd();
2887
- const rcPath = join20(cwd, ".spfnrc.ts");
2888
- if (existsSync19(rcPath)) {
2966
+ const rcPath = join21(cwd, ".spfnrc.ts");
2967
+ if (existsSync20(rcPath)) {
2889
2968
  logger.warn(".spfnrc.ts already exists");
2890
2969
  logger.info("Edit manually to add custom generators");
2891
2970
  process.exit(0);
@@ -2910,7 +2989,7 @@ export default defineConfig({
2910
2989
  ],
2911
2990
  });
2912
2991
  `;
2913
- writeFileSync12(rcPath, content);
2992
+ writeFileSync13(rcPath, content);
2914
2993
  console.log("\n" + chalk10.green.bold("\u2713 Created .spfnrc.ts\n"));
2915
2994
  console.log(chalk10.gray("Configured the @spfn/core:route-map generator."));
2916
2995
  if (options.withExample) {
@@ -2970,14 +3049,14 @@ async function runGenerators() {
2970
3049
  await orchestrator.generateAll();
2971
3050
  console.log("\n" + chalk10.green.bold("\u2713 Code generation completed"));
2972
3051
  }
2973
- var codegenCommand = new Command7("codegen").description("Code generation management");
3052
+ var codegenCommand = new Command8("codegen").description("Code generation management");
2974
3053
  codegenCommand.command("init").description("Initialize .spfnrc.ts with codegen configuration").option("--with-example", "Show example custom generator usage").action(initCodegen);
2975
3054
  codegenCommand.command("list").alias("ls").description("List registered code generators").action(listGenerators);
2976
3055
  codegenCommand.command("run").description("Run code generators once (no watch mode)").action(runGenerators);
2977
3056
 
2978
3057
  // src/commands/key.ts
2979
3058
  init_logger();
2980
- import { Command as Command8 } from "commander";
3059
+ import { Command as Command9 } from "commander";
2981
3060
  import { execSync } from "child_process";
2982
3061
  import chalk11 from "chalk";
2983
3062
 
@@ -3091,7 +3170,7 @@ function listPresets() {
3091
3170
  console.log(chalk11.gray(" spfn key auth-encryption --copy"));
3092
3171
  console.log();
3093
3172
  }
3094
- var generateValueCommand = new Command8("generate").alias("gen").description("Generate random value (simple output, no metadata)").option("-b, --bytes <number>", "Number of random bytes", "32").option("-c, --copy", "Copy to clipboard").action((options) => {
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) => {
3095
3174
  const bytes = parseInt(options.bytes, 10);
3096
3175
  if (isNaN(bytes) || bytes < 1 || bytes > 128) {
3097
3176
  logger.error("Invalid bytes value. Must be between 1 and 128.");
@@ -3107,7 +3186,7 @@ var generateValueCommand = new Command8("generate").alias("gen").description("Ge
3107
3186
  }
3108
3187
  }
3109
3188
  });
3110
- 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) => {
3111
3190
  if (options.list) {
3112
3191
  listPresets();
3113
3192
  return;
@@ -3139,15 +3218,15 @@ keyCommand.addCommand(generateValueCommand);
3139
3218
  init_setup();
3140
3219
 
3141
3220
  // src/commands/db/index.ts
3142
- import { Command as Command9 } from "commander";
3221
+ import { Command as Command10 } from "commander";
3143
3222
 
3144
3223
  // src/commands/db/generate.ts
3145
3224
  import chalk13 from "chalk";
3146
3225
 
3147
3226
  // src/commands/db/utils/drizzle.ts
3148
- 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";
3149
3228
  import { spawn } from "child_process";
3150
- import { pathToFileURL } from "url";
3229
+ import { pathToFileURL as pathToFileURL2 } from "url";
3151
3230
  import chalk12 from "chalk";
3152
3231
  import ora6 from "ora";
3153
3232
  import { env as env3 } from "@spfn/core/config";
@@ -3212,7 +3291,7 @@ function validateDatabasePrerequisites() {
3212
3291
  }
3213
3292
  }
3214
3293
  async function runDrizzleCommand(command) {
3215
- const hasUserConfig = existsSync20("./drizzle.config.ts");
3294
+ const hasUserConfig = existsSync21("./drizzle.config.ts");
3216
3295
  const tempConfigPath = `./drizzle.config.${process.pid}.${Date.now()}.temp.ts`;
3217
3296
  const configPath = hasUserConfig ? "./drizzle.config.ts" : tempConfigPath;
3218
3297
  if (!hasUserConfig) {
@@ -3231,7 +3310,7 @@ async function runDrizzleCommand(command) {
3231
3310
  expandGlobs: true,
3232
3311
  autoDetectSchemas: true
3233
3312
  });
3234
- writeFileSync13(tempConfigPath, configContent);
3313
+ writeFileSync14(tempConfigPath, configContent);
3235
3314
  console.log(chalk12.dim("Using auto-generated Drizzle config\n"));
3236
3315
  }
3237
3316
  const args = command.split(" ");
@@ -3244,7 +3323,7 @@ async function runDrizzleCommand(command) {
3244
3323
  env: shouldRelaxDbTls(env3.DATABASE_URL) ? { ...process.env, NODE_TLS_REJECT_UNAUTHORIZED: "0" } : { ...process.env }
3245
3324
  });
3246
3325
  const cleanup = () => {
3247
- if (!hasUserConfig && existsSync20(tempConfigPath)) {
3326
+ if (!hasUserConfig && existsSync21(tempConfigPath)) {
3248
3327
  unlinkSync2(tempConfigPath);
3249
3328
  }
3250
3329
  };
@@ -3283,7 +3362,7 @@ async function loadSchemaImports(schemaFiles) {
3283
3362
  }
3284
3363
  const imports = {};
3285
3364
  for (const file of schemaFiles) {
3286
- const moduleUrl = pathToFileURL(file).href;
3365
+ const moduleUrl = pathToFileURL2(file).href;
3287
3366
  const mod = await import(moduleUrl);
3288
3367
  for (const [key, value] of Object.entries(mod)) {
3289
3368
  if (key !== "default") {
@@ -3564,8 +3643,8 @@ async function applyFunctionMigrations(plans) {
3564
3643
 
3565
3644
  // src/commands/db/migrate.ts
3566
3645
  import chalk20 from "chalk";
3567
- import { join as join21 } from "path";
3568
- import { existsSync as existsSync22 } from "fs";
3646
+ import { join as join22 } from "path";
3647
+ import { existsSync as existsSync23 } from "fs";
3569
3648
 
3570
3649
  // src/commands/db/backup.ts
3571
3650
  import { promises as fs3 } from "fs";
@@ -3658,7 +3737,7 @@ function formatTimestamp() {
3658
3737
 
3659
3738
  // src/commands/db/utils/backup-files.ts
3660
3739
  import { promises as fs2 } from "fs";
3661
- import { existsSync as existsSync21 } from "fs";
3740
+ import { existsSync as existsSync22 } from "fs";
3662
3741
  import path2 from "path";
3663
3742
  import chalk18 from "chalk";
3664
3743
 
@@ -3758,7 +3837,7 @@ async function ensureBackupInGitignore() {
3758
3837
  const gitignorePath = path2.join(process.cwd(), ".gitignore");
3759
3838
  try {
3760
3839
  let content = "";
3761
- let exists = existsSync21(gitignorePath);
3840
+ let exists = existsSync22(gitignorePath);
3762
3841
  if (exists) {
3763
3842
  content = await fs2.readFile(gitignorePath, "utf-8");
3764
3843
  }
@@ -3780,7 +3859,7 @@ async function ensureBackupDir() {
3780
3859
  try {
3781
3860
  await fs2.mkdir(backupDir, { recursive: true });
3782
3861
  const gitignorePath = path2.join(backupDir, ".gitignore");
3783
- const gitignoreExists = existsSync21(gitignorePath);
3862
+ const gitignoreExists = existsSync22(gitignorePath);
3784
3863
  if (!gitignoreExists) {
3785
3864
  await fs2.writeFile(gitignorePath, "# Ignore all backup files\n*.sql\n*.dump\n*.meta.json\n");
3786
3865
  }
@@ -3974,8 +4053,8 @@ async function dbMigrate(options = {}) {
3974
4053
  await executeFunctionMigrations2(loadFunctionMigrationPlans2(functions));
3975
4054
  console.log(chalk20.green("\u2705 Function migrations applied\n"));
3976
4055
  }
3977
- const projectMigrationsDir = join21(process.cwd(), "src/server/drizzle");
3978
- if (existsSync22(projectMigrationsDir)) {
4056
+ const projectMigrationsDir = join22(process.cwd(), "src/server/drizzle");
4057
+ if (existsSync23(projectMigrationsDir)) {
3979
4058
  const projConn = postgres.default(env5.DATABASE_URL, { max: 1 });
3980
4059
  const projDb = drizzle({ client: projConn });
3981
4060
  try {
@@ -4035,7 +4114,7 @@ async function dbStatus() {
4035
4114
 
4036
4115
  // src/commands/db/studio.ts
4037
4116
  import chalk22 from "chalk";
4038
- 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";
4039
4118
  import { spawn as spawn3 } from "child_process";
4040
4119
  import { env as env6 } from "@spfn/core/config";
4041
4120
  async function dbStudio(requestedPort) {
@@ -4053,7 +4132,7 @@ async function dbStudio(requestedPort) {
4053
4132
  console.error(chalk22.red(error instanceof Error ? error.message : "Failed to find available port"));
4054
4133
  process.exit(1);
4055
4134
  }
4056
- const hasUserConfig = existsSync23("./drizzle.config.ts");
4135
+ const hasUserConfig = existsSync24("./drizzle.config.ts");
4057
4136
  const tempConfigPath = `./drizzle.config.${process.pid}.${Date.now()}.temp.ts`;
4058
4137
  try {
4059
4138
  const configPath = hasUserConfig ? "./drizzle.config.ts" : tempConfigPath;
@@ -4070,7 +4149,7 @@ async function dbStudio(requestedPort) {
4070
4149
  expandGlobs: true
4071
4150
  // Expand glob patterns for Studio compatibility
4072
4151
  });
4073
- writeFileSync14(tempConfigPath, configContent);
4152
+ writeFileSync15(tempConfigPath, configContent);
4074
4153
  console.log(chalk22.dim("Using auto-generated Drizzle config\n"));
4075
4154
  }
4076
4155
  const studioProcess = spawn3("drizzle-kit", ["studio", `--port=${port}`, `--config=${configPath}`], {
@@ -4079,7 +4158,7 @@ async function dbStudio(requestedPort) {
4079
4158
  env: shouldRelaxDbTls(env6.DATABASE_URL) ? { ...process.env, NODE_TLS_REJECT_UNAUTHORIZED: "0" } : { ...process.env }
4080
4159
  });
4081
4160
  const cleanup = () => {
4082
- if (!hasUserConfig && existsSync23(tempConfigPath)) {
4161
+ if (!hasUserConfig && existsSync24(tempConfigPath)) {
4083
4162
  unlinkSync3(tempConfigPath);
4084
4163
  }
4085
4164
  };
@@ -4109,7 +4188,7 @@ async function dbStudio(requestedPort) {
4109
4188
  process.exit(0);
4110
4189
  });
4111
4190
  } catch (error) {
4112
- if (!hasUserConfig && existsSync23(tempConfigPath)) {
4191
+ if (!hasUserConfig && existsSync24(tempConfigPath)) {
4113
4192
  unlinkSync3(tempConfigPath);
4114
4193
  }
4115
4194
  console.error(chalk22.red("\u274C Failed to start Drizzle Studio"));
@@ -4496,8 +4575,8 @@ async function dbBackupClean(options) {
4496
4575
  }
4497
4576
 
4498
4577
  // src/commands/db/reindex.ts
4499
- import { existsSync as existsSync24, readFileSync as readFileSync10, writeFileSync as writeFileSync15, renameSync, copyFileSync } from "fs";
4500
- 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";
4501
4580
  import chalk28 from "chalk";
4502
4581
  import { loadEnv as loadEnv9 } from "@spfn/core/server";
4503
4582
  function isTimestampPrefix(tag) {
@@ -4519,8 +4598,8 @@ async function dbReindex(options = {}) {
4519
4598
  const { getDrizzleConfig } = await import("@spfn/core/db");
4520
4599
  const config = getDrizzleConfig({ disablePackageDiscovery: true });
4521
4600
  const outDir = config.out;
4522
- const journalPath = join22(outDir, "meta", "_journal.json");
4523
- if (!existsSync24(journalPath)) {
4601
+ const journalPath = join23(outDir, "meta", "_journal.json");
4602
+ if (!existsSync25(journalPath)) {
4524
4603
  console.error(chalk28.red("\u274C No _journal.json found at:"), journalPath);
4525
4604
  console.log(chalk28.yellow("\u{1F4A1} Run `spfn db generate` first to create migrations"));
4526
4605
  process.exit(1);
@@ -4541,14 +4620,14 @@ async function dbReindex(options = {}) {
4541
4620
  const { prefix: oldPrefix, suffix } = parseTag(entry.tag);
4542
4621
  const newPrefix = String(entry.when);
4543
4622
  const newTag = suffix ? `${newPrefix}_${suffix}` : newPrefix;
4544
- const oldSql = join22(outDir, `${entry.tag}.sql`);
4545
- const newSql = join22(outDir, `${newTag}.sql`);
4546
- if (existsSync24(oldSql)) {
4623
+ const oldSql = join23(outDir, `${entry.tag}.sql`);
4624
+ const newSql = join23(outDir, `${newTag}.sql`);
4625
+ if (existsSync25(oldSql)) {
4547
4626
  renames.push({ type: "sql", from: oldSql, to: newSql });
4548
4627
  }
4549
- const oldSnapshot = join22(outDir, "meta", `${oldPrefix}_snapshot.json`);
4550
- const newSnapshot = join22(outDir, "meta", `${newPrefix}_snapshot.json`);
4551
- 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)) {
4552
4631
  renames.push({ type: "snapshot", from: oldSnapshot, to: newSnapshot });
4553
4632
  }
4554
4633
  tagUpdates.push({ idx: entry.idx, oldTag: entry.tag, newTag });
@@ -4591,13 +4670,13 @@ async function dbReindex(options = {}) {
4591
4670
  entry.tag = update.newTag;
4592
4671
  }
4593
4672
  }
4594
- writeFileSync15(journalPath, JSON.stringify(journal, null, 2) + "\n");
4673
+ writeFileSync16(journalPath, JSON.stringify(journal, null, 2) + "\n");
4595
4674
  console.log(chalk28.green(`
4596
4675
  \u2705 Reindex complete \u2014 ${tagUpdates.length} migration(s) converted to timestamp prefix.`));
4597
4676
  }
4598
4677
 
4599
4678
  // src/commands/db/index.ts
4600
- 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)");
4601
4680
  dbCommand.command("generate").alias("g").description("Generate database migrations from schema changes").action(dbGenerate);
4602
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));
4603
4682
  dbCommand.command("migrate").alias("m").description("Run pending migrations").option("--with-backup", "Create backup before running migrations").action((options) => dbMigrate(options));
@@ -4613,54 +4692,114 @@ dbCommand.command("reindex").description("Convert migration files from sequentia
4613
4692
 
4614
4693
  // src/commands/add.ts
4615
4694
  init_package_manager();
4616
- import { Command as Command10 } from "commander";
4617
- import { existsSync as existsSync25, readFileSync as readFileSync11 } from "fs";
4618
- import { join as join23 } from "path";
4619
- import { execa as execa9 } from "execa";
4620
- 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";
4621
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
4622
4757
  async function addPackage(packageName) {
4758
+ if (packageName === "vercel") {
4759
+ await addVercel();
4760
+ return;
4761
+ }
4623
4762
  if (!packageName.includes("/")) {
4624
- console.error(chalk29.red("\u274C Please specify full package name"));
4625
- console.log(chalk29.yellow("\n\u{1F4A1} Examples:"));
4626
- console.log(chalk29.gray(" pnpm spfn add @spfn/cms"));
4627
- console.log(chalk29.gray(" pnpm spfn add @mycompany/spfn-analytics"));
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"));
4628
4767
  process.exit(1);
4629
4768
  }
4630
- console.log(chalk29.blue(`
4769
+ console.log(chalk30.blue(`
4631
4770
  \u{1F4E6} Setting up ${packageName}...
4632
4771
  `));
4633
- const pkgPath = join23(process.cwd(), "node_modules", ...packageName.split("/"));
4634
- const pkgJsonPath = join23(pkgPath, "package.json");
4635
- if (!existsSync25(pkgJsonPath)) {
4772
+ const pkgPath = join25(process.cwd(), "node_modules", ...packageName.split("/"));
4773
+ const pkgJsonPath = join25(pkgPath, "package.json");
4774
+ if (!existsSync27(pkgJsonPath)) {
4636
4775
  const pm = detectPackageManager(process.cwd());
4637
4776
  const installSpinner = ora11("Installing package...").start();
4638
4777
  try {
4639
- await execa9(pm, ["add", packageName]);
4778
+ await execa10(pm, ["add", packageName]);
4640
4779
  installSpinner.succeed("Package installed");
4641
4780
  } catch (error) {
4642
4781
  installSpinner.fail("Failed to install package");
4643
- console.error(chalk29.red(error instanceof Error ? error.message : "Unknown error"));
4782
+ console.error(chalk30.red(error instanceof Error ? error.message : "Unknown error"));
4644
4783
  process.exit(1);
4645
4784
  }
4646
4785
  } else {
4647
- 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"));
4648
4787
  }
4649
- if (!existsSync25(pkgJsonPath)) {
4650
- 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`));
4651
4790
  process.exit(1);
4652
4791
  }
4653
4792
  const pkgJson = JSON.parse(readFileSync11(pkgJsonPath, "utf-8"));
4654
4793
  if (pkgJson.spfn?.migrations) {
4655
- console.log(chalk29.blue(`
4794
+ console.log(chalk30.blue(`
4656
4795
  \u{1F5C4}\uFE0F Setting up database for ${packageName}...
4657
4796
  `));
4658
4797
  const { env: env9 } = await import("@spfn/core/config");
4659
4798
  if (!env9.DATABASE_URL) {
4660
- console.log(chalk29.yellow("\u26A0\uFE0F DATABASE_URL not found \u2014 skipping database setup."));
4661
- console.log(chalk29.yellow(` ${packageName} tables are created by its bundled migrations, not by schema push.`));
4662
- console.log(chalk29.cyan(" Once DATABASE_URL is set, run: pnpm spfn db migrate"));
4663
- console.log(chalk29.gray(" (check state anytime with: pnpm spfn db status)\n"));
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"));
4664
4803
  } else {
4665
4804
  const { discoverFunctionMigrations: discoverFunctionMigrations2, loadFunctionMigrationPlans: loadFunctionMigrationPlans2, executeFunctionMigrations: executeFunctionMigrations2 } = await Promise.resolve().then(() => (init_function_migrations(), function_migrations_exports));
4666
4805
  const functions = discoverFunctionMigrations2(process.cwd());
@@ -4672,31 +4811,31 @@ async function addPackage(packageName) {
4672
4811
  spinner.succeed("Migrations applied");
4673
4812
  } catch (error) {
4674
4813
  spinner.fail("Failed to apply migrations");
4675
- console.error(chalk29.red(error instanceof Error ? error.message : "Unknown error"));
4814
+ console.error(chalk30.red(error instanceof Error ? error.message : "Unknown error"));
4676
4815
  process.exit(1);
4677
4816
  }
4678
4817
  } else {
4679
- 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"));
4680
4819
  }
4681
4820
  }
4682
4821
  } else {
4683
- 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"));
4684
4823
  }
4685
- console.log(chalk29.green(`
4824
+ console.log(chalk30.green(`
4686
4825
  \u2705 ${packageName} installed successfully!
4687
4826
  `));
4688
4827
  if (pkgJson.spfn?.setupMessage) {
4689
- console.log(chalk29.cyan("\u{1F4DA} Setup Guide:"));
4828
+ console.log(chalk30.cyan("\u{1F4DA} Setup Guide:"));
4690
4829
  console.log(pkgJson.spfn.setupMessage);
4691
4830
  console.log();
4692
4831
  }
4693
4832
  }
4694
- 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);
4695
4834
 
4696
4835
  // src/commands/env.ts
4697
- import { Command as Command11 } from "commander";
4698
- import chalk30 from "chalk";
4699
- 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";
4700
4839
  import { resolve as resolve2 } from "path";
4701
4840
  import { parse as parse2 } from "dotenv";
4702
4841
 
@@ -4752,26 +4891,26 @@ function getEnvFilesForEnvironment(nodeEnv) {
4752
4891
  }
4753
4892
  function formatType(type) {
4754
4893
  const typeColors = {
4755
- string: chalk30.green,
4756
- number: chalk30.blue,
4757
- boolean: chalk30.yellow,
4758
- url: chalk30.cyan,
4759
- enum: chalk30.magenta,
4760
- 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
4761
4900
  };
4762
- return (typeColors[type] || chalk30.white)(type);
4901
+ return (typeColors[type] || chalk31.white)(type);
4763
4902
  }
4764
4903
  function formatDefault(value, type) {
4765
4904
  if (value === void 0) {
4766
- return chalk30.dim("(none)");
4905
+ return chalk31.dim("(none)");
4767
4906
  }
4768
4907
  if (type === "string" || type === "url") {
4769
- return chalk30.green(`"${value}"`);
4908
+ return chalk31.green(`"${value}"`);
4770
4909
  }
4771
4910
  if (type === "boolean") {
4772
- return value ? chalk30.green("true") : chalk30.red("false");
4911
+ return value ? chalk31.green("true") : chalk31.red("false");
4773
4912
  }
4774
- return chalk30.cyan(String(value));
4913
+ return chalk31.cyan(String(value));
4775
4914
  }
4776
4915
  async function listEnvVars(options) {
4777
4916
  const packageName = options.package || "@spfn/core";
@@ -4785,28 +4924,28 @@ async function listEnvVars(options) {
4785
4924
  acc[target].push([key, schema]);
4786
4925
  return acc;
4787
4926
  }, {});
4788
- console.log(chalk30.blue.bold(`
4927
+ console.log(chalk31.blue.bold(`
4789
4928
  \u{1F4CB} Environment Variables by File (${packageName})
4790
4929
  `));
4791
4930
  for (const [file, vars] of Object.entries(grouped)) {
4792
- console.log(chalk30.bold.magenta(`
4931
+ console.log(chalk31.bold.magenta(`
4793
4932
  ${file}`));
4794
- console.log(chalk30.dim("\u2500".repeat(50)));
4933
+ console.log(chalk31.dim("\u2500".repeat(50)));
4795
4934
  for (const [key, schema] of vars) {
4796
4935
  printEnvVar(key, schema);
4797
4936
  }
4798
4937
  }
4799
4938
  } else {
4800
- console.log(chalk30.blue.bold(`
4939
+ console.log(chalk31.blue.bold(`
4801
4940
  \u{1F4CB} Environment Variables (${packageName})
4802
4941
  `));
4803
4942
  for (const [key, schema] of allVars) {
4804
4943
  printEnvVar(key, schema, true);
4805
4944
  }
4806
4945
  }
4807
- 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"));
4808
4947
  } catch (error) {
4809
- console.error(chalk30.red(`
4948
+ console.error(chalk31.red(`
4810
4949
  \u274C ${error instanceof Error ? error.message : "Unknown error"}
4811
4950
  `));
4812
4951
  process.exit(1);
@@ -4814,17 +4953,17 @@ ${file}`));
4814
4953
  }
4815
4954
  function printEnvVar(key, schema, showFile = false) {
4816
4955
  const typeStr = formatType(schema.type);
4817
- const requiredStr = schema.required || schema.default !== void 0 ? chalk30.red("[required]") : chalk30.dim("[optional]");
4818
- const sensitiveStr = schema.sensitive ? chalk30.yellow(" [sensitive]") : "";
4819
- const fileStr = showFile ? chalk30.dim(` \u2192 ${getTargetFile(schema)}`) : "";
4820
- console.log(`${chalk30.bold.cyan(key)} ${chalk30.dim("(")}${typeStr}${chalk30.dim(")")} ${requiredStr}${sensitiveStr}${fileStr}`);
4821
- console.log(` ${chalk30.dim(schema.description)}`);
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)}`);
4822
4961
  if (schema.default !== void 0) {
4823
- console.log(` ${chalk30.dim("Default:")} ${formatDefault(schema.default, schema.type)}`);
4962
+ console.log(` ${chalk31.dim("Default:")} ${formatDefault(schema.default, schema.type)}`);
4824
4963
  }
4825
4964
  if (schema.examples && schema.examples.length > 0) {
4826
4965
  const exampleStr = schema.examples.map((ex) => formatDefault(ex, schema.type)).join(", ");
4827
- console.log(` ${chalk30.dim("Examples:")} ${exampleStr}`);
4966
+ console.log(` ${chalk31.dim("Examples:")} ${exampleStr}`);
4828
4967
  }
4829
4968
  console.log();
4830
4969
  }
@@ -4832,7 +4971,7 @@ async function showEnvStats(options) {
4832
4971
  const packageName = options.package || "@spfn/core";
4833
4972
  try {
4834
4973
  const envSchema = await loadEnvSchema(packageName);
4835
- console.log(chalk30.blue.bold(`
4974
+ console.log(chalk31.blue.bold(`
4836
4975
  \u{1F4CA} Environment Variable Statistics (${packageName})
4837
4976
  `));
4838
4977
  const allVars = Object.entries(envSchema);
@@ -4854,24 +4993,24 @@ async function showEnvStats(options) {
4854
4993
  acc[file] = (acc[file] || 0) + 1;
4855
4994
  return acc;
4856
4995
  }, {});
4857
- console.log(`${chalk30.bold("Total variables:")} ${chalk30.cyan(allVars.length)}`);
4858
- console.log(`${chalk30.bold("Required:")} ${chalk30.red(required.length)}`);
4859
- console.log(`${chalk30.bold("Optional:")} ${chalk30.dim(optional.length)}`);
4860
- console.log(`${chalk30.bold("Sensitive:")} ${chalk30.yellow(sensitive.length)}`);
4861
- console.log(chalk30.bold("\nBy Target:"));
4862
- console.log(` ${chalk30.blue("Next.js accessible:")} ${chalk30.cyan(nextjsVars.length)}`);
4863
- console.log(` ${chalk30.magenta("SPFN server only:")} ${chalk30.cyan(serverOnlyVars.length)}`);
4864
- console.log(chalk30.bold("\nBy File:"));
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:"));
4865
5004
  for (const [file, count] of Object.entries(fileCount)) {
4866
- console.log(` ${chalk30.dim(file)}: ${chalk30.cyan(count)}`);
5005
+ console.log(` ${chalk31.dim(file)}: ${chalk31.cyan(count)}`);
4867
5006
  }
4868
- console.log(chalk30.bold("\nBy Type:"));
5007
+ console.log(chalk31.bold("\nBy Type:"));
4869
5008
  for (const [type, count] of Object.entries(typeCount)) {
4870
- console.log(` ${formatType(type)}: ${chalk30.cyan(count)}`);
5009
+ console.log(` ${formatType(type)}: ${chalk31.cyan(count)}`);
4871
5010
  }
4872
5011
  console.log();
4873
5012
  } catch (error) {
4874
- console.error(chalk30.red(`
5013
+ console.error(chalk31.red(`
4875
5014
  \u274C ${error instanceof Error ? error.message : "Unknown error"}
4876
5015
  `));
4877
5016
  process.exit(1);
@@ -4891,40 +5030,40 @@ async function searchEnvVars(query, options) {
4891
5030
  }
4892
5031
  }
4893
5032
  if (results.length === 0) {
4894
- console.log(chalk30.yellow(`
5033
+ console.log(chalk31.yellow(`
4895
5034
  \u26A0\uFE0F No environment variables found matching "${query}"
4896
5035
  `));
4897
5036
  return;
4898
5037
  }
4899
- console.log(chalk30.blue.bold(`
5038
+ console.log(chalk31.blue.bold(`
4900
5039
  \u{1F50D} Found ${results.length} environment variable(s) matching "${query}"
4901
5040
  `));
4902
5041
  for (const [key, schema] of results) {
4903
5042
  const typeStr = formatType(schema.type);
4904
- const requiredStr = schema.required || schema.default !== void 0 ? chalk30.red("[required]") : chalk30.dim("[optional]");
4905
- console.log(`${chalk30.bold.cyan(key)} ${chalk30.dim("(")}${typeStr}${chalk30.dim(")")} ${requiredStr}`);
4906
- console.log(` ${chalk30.dim(schema.description)}`);
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)}`);
4907
5046
  if (schema.default !== void 0) {
4908
- console.log(` ${chalk30.dim("Default:")} ${formatDefault(schema.default, schema.type)}`);
5047
+ console.log(` ${chalk31.dim("Default:")} ${formatDefault(schema.default, schema.type)}`);
4909
5048
  }
4910
5049
  console.log();
4911
5050
  }
4912
5051
  } catch (error) {
4913
- console.error(chalk30.red(`
5052
+ console.error(chalk31.red(`
4914
5053
  \u274C ${error instanceof Error ? error.message : "Unknown error"}
4915
5054
  `));
4916
5055
  process.exit(1);
4917
5056
  }
4918
5057
  }
4919
- var envCommand = new Command11("env").description("Manage environment variables");
5058
+ var envCommand = new Command12("env").description("Manage environment variables");
4920
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);
4921
5060
  envCommand.command("stats").description("Show environment variable statistics").option("-p, --package <package>", "Package name to read env schema from", "@spfn/core").action(showEnvStats);
4922
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);
4923
5062
  function validateEnvOption(envValue) {
4924
5063
  if (!VALID_ENVS.includes(envValue)) {
4925
- console.error(chalk30.red(`
5064
+ console.error(chalk31.red(`
4926
5065
  \u274C Invalid environment: "${envValue}"`));
4927
- console.log(chalk30.dim(` Valid values: ${VALID_ENVS.join(", ")}
5066
+ console.log(chalk31.dim(` Valid values: ${VALID_ENVS.join(", ")}
4928
5067
  `));
4929
5068
  process.exit(1);
4930
5069
  }
@@ -4945,8 +5084,8 @@ async function initEnvFiles(options) {
4945
5084
  return acc;
4946
5085
  }, {});
4947
5086
  if (targetEnv) {
4948
- console.log(chalk30.blue.bold(`
4949
- \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
4950
5089
  `));
4951
5090
  const envSpecificFiles = {};
4952
5091
  const committedVars = allVars.filter(([_, schema]) => !schema.sensitive);
@@ -4962,24 +5101,24 @@ async function initEnvFiles(options) {
4962
5101
  writeEnvTemplate(cwd, file, vars, options.force ?? false);
4963
5102
  }
4964
5103
  } else {
4965
- console.log(chalk30.blue.bold(`
5104
+ console.log(chalk31.blue.bold(`
4966
5105
  \u{1F680} Generating .env template files
4967
5106
  `));
4968
5107
  for (const [file, vars] of Object.entries(grouped)) {
4969
5108
  writeEnvTemplate(cwd, file, vars, options.force ?? false);
4970
5109
  }
4971
5110
  }
4972
- console.log(chalk30.dim("\n\u{1F4A1} Copy .example files to create your actual .env files:"));
4973
- console.log(chalk30.dim(" cp .env.example .env"));
4974
- console.log(chalk30.dim(" cp .env.local.example .env.local"));
4975
- console.log(chalk30.dim(" cp .env.server.example .env.server"));
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"));
4976
5115
  if (targetEnv) {
4977
- console.log(chalk30.dim(` cp .env.${targetEnv}.example .env.${targetEnv}`));
4978
- 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`));
4979
5118
  }
4980
5119
  console.log("");
4981
5120
  } catch (error) {
4982
- console.error(chalk30.red(`
5121
+ console.error(chalk31.red(`
4983
5122
  \u274C ${error instanceof Error ? error.message : "Unknown error"}
4984
5123
  `));
4985
5124
  process.exit(1);
@@ -4987,12 +5126,12 @@ async function initEnvFiles(options) {
4987
5126
  }
4988
5127
  function writeEnvTemplate(cwd, file, vars, force) {
4989
5128
  const filePath = resolve2(cwd, file);
4990
- if (existsSync26(filePath) && !force) {
4991
- 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)`));
4992
5131
  return;
4993
5132
  }
4994
- writeFileSync16(filePath, generateEnvFileContent(vars), "utf-8");
4995
- 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)`));
4996
5135
  }
4997
5136
  function generateEnvFileContent(vars) {
4998
5137
  const lines = [
@@ -5027,7 +5166,7 @@ async function checkEnvFiles(options) {
5027
5166
  const envSchema = await loadEnvSchema(packageName);
5028
5167
  const allVars = Object.entries(envSchema);
5029
5168
  const envLabel = targetEnv ? ` (${targetEnv})` : "";
5030
- console.log(chalk30.blue.bold(`
5169
+ console.log(chalk31.blue.bold(`
5031
5170
  \u{1F50D} Checking .env files against schema${envLabel}
5032
5171
  `));
5033
5172
  const filesToCheck = targetEnv ? getEnvFilesForEnvironment(targetEnv) : [...BASE_ENV_FILES.nextjs, ...BASE_ENV_FILES.server];
@@ -5036,7 +5175,7 @@ async function checkEnvFiles(options) {
5036
5175
  const warnings = [];
5037
5176
  for (const file of filesToCheck) {
5038
5177
  const filePath = resolve2(cwd, file);
5039
- if (!existsSync26(filePath)) {
5178
+ if (!existsSync28(filePath)) {
5040
5179
  continue;
5041
5180
  }
5042
5181
  const content = readFileSync12(filePath, "utf-8");
@@ -5044,7 +5183,7 @@ async function checkEnvFiles(options) {
5044
5183
  for (const [key, value] of Object.entries(parsed)) {
5045
5184
  loadedEnv[key] = { value: value || "", file };
5046
5185
  }
5047
- console.log(chalk30.dim(` \u{1F4C4} ${file} loaded`));
5186
+ console.log(chalk31.dim(` \u{1F4C4} ${file} loaded`));
5048
5187
  }
5049
5188
  console.log("");
5050
5189
  for (const [key, schema] of allVars) {
@@ -5052,7 +5191,7 @@ async function checkEnvFiles(options) {
5052
5191
  const found = loadedEnv[key];
5053
5192
  if (!found) {
5054
5193
  if (schema.required && schema.default === void 0) {
5055
- 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`);
5056
5195
  }
5057
5196
  continue;
5058
5197
  }
@@ -5062,11 +5201,11 @@ async function checkEnvFiles(options) {
5062
5201
  if (!shouldBeNextjs && isNextjsFile && !isServerFile) {
5063
5202
  if (schema.sensitive) {
5064
5203
  issues.push(
5065
- `${chalk30.red("\u2717")} ${chalk30.cyan(key)} is sensitive and should be in ${chalk30.magenta(expectedFile)}, but found in ${chalk30.yellow(found.file)} (security risk!)`
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!)`
5066
5205
  );
5067
5206
  } else {
5068
5207
  warnings.push(
5069
- `${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)}`
5070
5209
  );
5071
5210
  }
5072
5211
  }
@@ -5074,34 +5213,34 @@ async function checkEnvFiles(options) {
5074
5213
  for (const [key, { file }] of Object.entries(loadedEnv)) {
5075
5214
  const inSchema = allVars.some(([k]) => k === key);
5076
5215
  if (!inSchema) {
5077
- 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`);
5078
5217
  }
5079
5218
  }
5080
5219
  if (issues.length > 0) {
5081
- console.log(chalk30.red.bold("Issues:"));
5220
+ console.log(chalk31.red.bold("Issues:"));
5082
5221
  for (const issue of issues) {
5083
5222
  console.log(` ${issue}`);
5084
5223
  }
5085
5224
  console.log("");
5086
5225
  }
5087
5226
  if (warnings.length > 0) {
5088
- console.log(chalk30.yellow.bold("Warnings:"));
5227
+ console.log(chalk31.yellow.bold("Warnings:"));
5089
5228
  for (const warning of warnings) {
5090
5229
  console.log(` ${warning}`);
5091
5230
  }
5092
5231
  console.log("");
5093
5232
  }
5094
5233
  if (issues.length === 0 && warnings.length === 0) {
5095
- 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"));
5096
5235
  } else {
5097
- 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)
5098
5237
  `));
5099
5238
  if (issues.length > 0) {
5100
5239
  process.exit(1);
5101
5240
  }
5102
5241
  }
5103
5242
  } catch (error) {
5104
- console.error(chalk30.red(`
5243
+ console.error(chalk31.red(`
5105
5244
  \u274C ${error instanceof Error ? error.message : "Unknown error"}
5106
5245
  `));
5107
5246
  process.exit(1);
@@ -5115,15 +5254,15 @@ async function validateEnvVars(options) {
5115
5254
  if (targetEnv) {
5116
5255
  const { loadEnv: loadEnv10 } = await import("@spfn/core/env/loader");
5117
5256
  const result = loadEnv10({ nodeEnv: targetEnv });
5118
- console.log(chalk30.blue.bold(`
5119
- \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)}
5120
5259
  `));
5121
5260
  if (result.loadedFiles.length > 0) {
5122
- console.log(chalk30.dim(` Loaded: ${result.loadedFiles.join(", ")}`));
5261
+ console.log(chalk31.dim(` Loaded: ${result.loadedFiles.join(", ")}`));
5123
5262
  }
5124
5263
  console.log("");
5125
5264
  } else {
5126
- console.log(chalk30.blue.bold(`
5265
+ console.log(chalk31.blue.bold(`
5127
5266
  \u{1F50D} Validating environment variables
5128
5267
  `));
5129
5268
  }
@@ -5131,7 +5270,7 @@ async function validateEnvVars(options) {
5131
5270
  const allWarnings = [];
5132
5271
  for (const packageName of packages) {
5133
5272
  try {
5134
- console.log(chalk30.dim(` \u{1F4E6} ${packageName}`));
5273
+ console.log(chalk31.dim(` \u{1F4E6} ${packageName}`));
5135
5274
  const envSchema = await loadEnvSchema(packageName);
5136
5275
  const { createEnvRegistry } = await import("@spfn/core/env");
5137
5276
  const registry = createEnvRegistry(envSchema);
@@ -5144,10 +5283,10 @@ async function validateEnvVars(options) {
5144
5283
  }
5145
5284
  } catch (error) {
5146
5285
  if (error instanceof Error && error.message.includes("does not export envSchema")) {
5147
- console.log(chalk30.dim(` \u23ED\uFE0F No envSchema exported, skipping`));
5286
+ console.log(chalk31.dim(` \u23ED\uFE0F No envSchema exported, skipping`));
5148
5287
  continue;
5149
5288
  }
5150
- 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)}`));
5151
5290
  if (options.strict) {
5152
5291
  process.exit(1);
5153
5292
  }
@@ -5155,32 +5294,32 @@ async function validateEnvVars(options) {
5155
5294
  }
5156
5295
  console.log("");
5157
5296
  if (allErrors.length > 0) {
5158
- console.log(chalk30.red.bold(`\u274C Validation Errors (${allErrors.length}):
5297
+ console.log(chalk31.red.bold(`\u274C Validation Errors (${allErrors.length}):
5159
5298
  `));
5160
5299
  for (const error of allErrors) {
5161
- console.log(` ${chalk30.red("\u2717")} ${chalk30.cyan(error.key)}`);
5162
- console.log(` ${chalk30.dim(error.message)}`);
5163
- console.log(` ${chalk30.dim(`from ${error.package}`)}`);
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}`)}`);
5164
5303
  console.log("");
5165
5304
  }
5166
5305
  }
5167
5306
  if (allWarnings.length > 0) {
5168
- console.log(chalk30.yellow.bold(`\u26A0\uFE0F Warnings (${allWarnings.length}):
5307
+ console.log(chalk31.yellow.bold(`\u26A0\uFE0F Warnings (${allWarnings.length}):
5169
5308
  `));
5170
5309
  for (const warning of allWarnings) {
5171
- console.log(` ${chalk30.yellow("\u26A0")} ${chalk30.cyan(warning.key)}`);
5172
- console.log(` ${chalk30.dim(warning.message)}`);
5310
+ console.log(` ${chalk31.yellow("\u26A0")} ${chalk31.cyan(warning.key)}`);
5311
+ console.log(` ${chalk31.dim(warning.message)}`);
5173
5312
  console.log("");
5174
5313
  }
5175
5314
  }
5176
5315
  if (allErrors.length === 0 && allWarnings.length === 0) {
5177
- 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"));
5178
5317
  } else if (allErrors.length === 0) {
5179
- console.log(chalk30.green("\u2705 No errors found."));
5180
- 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.
5181
5320
  `));
5182
5321
  } else {
5183
- console.log(chalk30.red(`
5322
+ console.log(chalk31.red(`
5184
5323
  \u274C Validation failed with ${allErrors.length} error(s)
5185
5324
  `));
5186
5325
  process.exit(1);
@@ -5189,12 +5328,12 @@ async function validateEnvVars(options) {
5189
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);
5190
5329
 
5191
5330
  // src/commands/secret/index.ts
5192
- import { Command as Command12 } from "commander";
5331
+ import { Command as Command13 } from "commander";
5193
5332
 
5194
5333
  // src/commands/secret/set.ts
5195
5334
  init_logger();
5196
5335
  import prompts9 from "prompts";
5197
- import chalk31 from "chalk";
5336
+ import chalk32 from "chalk";
5198
5337
 
5199
5338
  // src/commands/secret/options.ts
5200
5339
  init_logger();
@@ -5209,34 +5348,34 @@ function resolveEnv(env9) {
5209
5348
 
5210
5349
  // src/commands/secret/store-value.ts
5211
5350
  init_logger();
5212
- import { join as join25 } from "path";
5351
+ import { join as join27 } from "path";
5213
5352
  init_env_file();
5214
5353
 
5215
5354
  // src/utils/sops.ts
5216
- import { execa as execa10 } from "execa";
5217
- import { existsSync as existsSync27, mkdirSync as mkdirSync3, writeFileSync as writeFileSync17 } from "fs";
5218
- 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";
5219
5358
  async function ensureSopsInstalled() {
5220
5359
  try {
5221
- await execa10("sops", ["--version"]);
5360
+ await execa11("sops", ["--version"]);
5222
5361
  } catch {
5223
5362
  throw new Error("`sops` not found on PATH. Install it: https://github.com/getsops/sops");
5224
5363
  }
5225
5364
  }
5226
5365
  async function sopsDecrypt(absFile) {
5227
- if (!existsSync27(absFile)) {
5366
+ if (!existsSync29(absFile)) {
5228
5367
  return {};
5229
5368
  }
5230
- const { stdout } = await execa10("sops", ["--decrypt", "--output-type", "json", absFile]);
5369
+ const { stdout } = await execa11("sops", ["--decrypt", "--output-type", "json", absFile]);
5231
5370
  return JSON.parse(stdout);
5232
5371
  }
5233
5372
  async function sopsSetValue(absFile, relFile, key, value) {
5234
- if (existsSync27(absFile)) {
5235
- await execa10("sops", ["set", absFile, `["${key}"]`, JSON.stringify(value)]);
5373
+ if (existsSync29(absFile)) {
5374
+ await execa11("sops", ["set", absFile, `["${key}"]`, JSON.stringify(value)]);
5236
5375
  return;
5237
5376
  }
5238
- mkdirSync3(dirname3(absFile), { recursive: true });
5239
- const { stdout } = await execa10(
5377
+ mkdirSync5(dirname4(absFile), { recursive: true });
5378
+ const { stdout } = await execa11(
5240
5379
  "sops",
5241
5380
  [
5242
5381
  "--encrypt",
@@ -5250,27 +5389,27 @@ async function sopsSetValue(absFile, relFile, key, value) {
5250
5389
  ],
5251
5390
  { input: JSON.stringify({ [key]: value }) }
5252
5391
  );
5253
- writeFileSync17(absFile, stdout);
5392
+ writeFileSync18(absFile, stdout);
5254
5393
  }
5255
5394
  async function sopsUpdateKeys(absFile) {
5256
- await execa10("sops", ["updatekeys", "-y", absFile]);
5395
+ await execa11("sops", ["updatekeys", "-y", absFile]);
5257
5396
  }
5258
5397
 
5259
5398
  // src/utils/secret-config.ts
5260
- import { existsSync as existsSync28, readdirSync as readdirSync3 } from "fs";
5261
- import { join as join24 } from "path";
5399
+ import { existsSync as existsSync30, readdirSync as readdirSync3 } from "fs";
5400
+ import { join as join26 } from "path";
5262
5401
  var SECRETS_DIR = "secrets";
5263
5402
  function getSopsFile(cwd, env9) {
5264
5403
  const relFile = `${SECRETS_DIR}/${env9}.enc.json`;
5265
- return { absFile: join24(cwd, relFile), relFile };
5404
+ return { absFile: join26(cwd, relFile), relFile };
5266
5405
  }
5267
5406
  function findUp(cwd, filename, maxDepth = 6) {
5268
5407
  let dir = cwd;
5269
5408
  for (let depth = 0; depth < maxDepth; depth++) {
5270
- if (existsSync28(join24(dir, filename))) {
5409
+ if (existsSync30(join26(dir, filename))) {
5271
5410
  return dir;
5272
5411
  }
5273
- const parent = join24(dir, "..");
5412
+ const parent = join26(dir, "..");
5274
5413
  if (parent === dir) {
5275
5414
  break;
5276
5415
  }
@@ -5280,17 +5419,17 @@ function findUp(cwd, filename, maxDepth = 6) {
5280
5419
  }
5281
5420
  function findSopsConfig(cwd) {
5282
5421
  const dir = findUp(cwd, ".sops.yaml");
5283
- return dir ? join24(dir, ".sops.yaml") : null;
5422
+ return dir ? join26(dir, ".sops.yaml") : null;
5284
5423
  }
5285
5424
  function hasSopsConfig(cwd) {
5286
5425
  return findSopsConfig(cwd) !== null;
5287
5426
  }
5288
5427
  function listSopsFiles(cwd) {
5289
- const dir = join24(cwd, SECRETS_DIR);
5290
- if (!existsSync28(dir)) {
5428
+ const dir = join26(cwd, SECRETS_DIR);
5429
+ if (!existsSync30(dir)) {
5291
5430
  return [];
5292
5431
  }
5293
- 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));
5294
5433
  }
5295
5434
 
5296
5435
  // src/commands/secret/store-value.ts
@@ -5319,7 +5458,7 @@ async function storeSecret(cwd, env9, key, value) {
5319
5458
  );
5320
5459
  }
5321
5460
  await store.set(keychainName(key), value);
5322
- const serverEnvPath = join25(cwd, ".env.server");
5461
+ const serverEnvPath = join27(cwd, ".env.server");
5323
5462
  const result = upsertEnvVar(serverEnvPath, key, keychainRef(key));
5324
5463
  restrictEnvFilePerms(serverEnvPath);
5325
5464
  ensureGitignored(cwd, [{ pattern: ".env.server", comment: "spfn server env (secrets)" }]);
@@ -5350,7 +5489,7 @@ async function secretSet(key, options) {
5350
5489
  const { value } = await prompts9({
5351
5490
  type: "password",
5352
5491
  name: "value",
5353
- message: `Value for ${chalk31.cyan(resolvedKey)} (${env9})`
5492
+ message: `Value for ${chalk32.cyan(resolvedKey)} (${env9})`
5354
5493
  });
5355
5494
  if (!value) {
5356
5495
  logger.warn("Cancelled \u2014 no value entered.");
@@ -5399,7 +5538,7 @@ async function warnIfNotSecret(pkg, key) {
5399
5538
 
5400
5539
  // src/commands/secret/list.ts
5401
5540
  init_logger();
5402
- import chalk32 from "chalk";
5541
+ import chalk33 from "chalk";
5403
5542
  async function secretList(options) {
5404
5543
  const env9 = resolveEnv(options.env);
5405
5544
  const pkg = options.package ?? "@spfn/core";
@@ -5415,12 +5554,12 @@ async function secretList(options) {
5415
5554
  return;
5416
5555
  }
5417
5556
  const present = await loadPresence(env9, entries);
5418
- console.log(chalk32.blue.bold(`
5557
+ console.log(chalk33.blue.bold(`
5419
5558
  \u{1F511} Secrets (${pkg}) \u2014 ${env9}
5420
5559
  `));
5421
5560
  for (const entry of entries) {
5422
5561
  const status = statusOf(entry, present.has(entry.key));
5423
- 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)") : ""}`);
5424
5563
  }
5425
5564
  console.log();
5426
5565
  }
@@ -5453,17 +5592,17 @@ function statusOf(entry, present) {
5453
5592
  function badge(status) {
5454
5593
  switch (status) {
5455
5594
  case "set":
5456
- return chalk32.green("\u25CF");
5595
+ return chalk33.green("\u25CF");
5457
5596
  case "missing":
5458
- return chalk32.yellow("\u25CB");
5597
+ return chalk33.yellow("\u25CB");
5459
5598
  case "awaiting-input":
5460
- return chalk32.red("\u25CB");
5599
+ return chalk33.red("\u25CB");
5461
5600
  }
5462
5601
  }
5463
5602
 
5464
5603
  // src/commands/secret/generate.ts
5465
5604
  init_logger();
5466
- import chalk33 from "chalk";
5605
+ import chalk34 from "chalk";
5467
5606
  async function secretGenerate(key, options) {
5468
5607
  const env9 = resolveEnv(options.env);
5469
5608
  const pkg = options.package ?? "@spfn/core";
@@ -5501,7 +5640,7 @@ function requireGeneratable(schema, key) {
5501
5640
  process.exit(1);
5502
5641
  }
5503
5642
  if (!entry.generate) {
5504
- 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}\`.`);
5505
5644
  process.exit(1);
5506
5645
  }
5507
5646
  return entry;
@@ -5509,7 +5648,7 @@ function requireGeneratable(schema, key) {
5509
5648
 
5510
5649
  // src/commands/secret/rotate.ts
5511
5650
  init_logger();
5512
- import chalk34 from "chalk";
5651
+ import chalk35 from "chalk";
5513
5652
  async function secretRotate(key, options) {
5514
5653
  const env9 = resolveEnv(options.env);
5515
5654
  const pkg = options.package ?? "@spfn/core";
@@ -5535,7 +5674,7 @@ async function secretRotate(key, options) {
5535
5674
  }
5536
5675
  }
5537
5676
  for (const entry of external) {
5538
- 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}\`.`);
5539
5678
  }
5540
5679
  if (generatable.length > 0) {
5541
5680
  logger.info("Rotation updates the source only \u2014 commit and deploy to apply.");
@@ -5556,17 +5695,17 @@ function requireEntry(schema, key) {
5556
5695
 
5557
5696
  // src/commands/secret/keygen.ts
5558
5697
  init_logger();
5559
- import { execa as execa11 } from "execa";
5560
- 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";
5561
5700
  import { homedir } from "os";
5562
- import { dirname as dirname4, join as join26 } from "path";
5563
- import chalk35 from "chalk";
5701
+ import { dirname as dirname5, join as join28 } from "path";
5702
+ import chalk36 from "chalk";
5564
5703
  function ageKeyFile() {
5565
- 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");
5566
5705
  }
5567
5706
  async function ensureAgeInstalled() {
5568
5707
  try {
5569
- await execa11("age-keygen", ["--version"]);
5708
+ await execa12("age-keygen", ["--version"]);
5570
5709
  } catch {
5571
5710
  throw new Error("`age-keygen` not found on PATH. Install age: https://github.com/FiloSottile/age");
5572
5711
  }
@@ -5582,14 +5721,14 @@ async function secretKeygen() {
5582
5721
  process.exit(1);
5583
5722
  }
5584
5723
  const keyFile = ageKeyFile();
5585
- if (existsSync29(keyFile)) {
5586
- const { stdout } = await execa11("age-keygen", ["-y", keyFile]);
5724
+ if (existsSync31(keyFile)) {
5725
+ const { stdout } = await execa12("age-keygen", ["-y", keyFile]);
5587
5726
  logger.warn(`age key file already exists: ${keyFile}`);
5588
5727
  printPublicKeys(publicKeys(stdout));
5589
5728
  return;
5590
5729
  }
5591
- mkdirSync4(dirname4(keyFile), { recursive: true });
5592
- const { stderr } = await execa11("age-keygen", ["-o", keyFile]);
5730
+ mkdirSync6(dirname5(keyFile), { recursive: true });
5731
+ const { stderr } = await execa12("age-keygen", ["-o", keyFile]);
5593
5732
  logger.success(`Created age key: ${keyFile}`);
5594
5733
  printPublicKeys(publicKeys(stderr));
5595
5734
  }
@@ -5598,27 +5737,27 @@ function printPublicKeys(keys) {
5598
5737
  logger.warn("Could not read the public key \u2014 run `age-keygen -y <file>` manually.");
5599
5738
  return;
5600
5739
  }
5601
- console.log(chalk35.bold("\nPublic key(s):"));
5740
+ console.log(chalk36.bold("\nPublic key(s):"));
5602
5741
  for (const key of keys) {
5603
- console.log(` ${chalk35.cyan(key)}`);
5742
+ console.log(` ${chalk36.cyan(key)}`);
5604
5743
  }
5605
- console.log(chalk35.dim("\nRegister it as a recipient:"));
5606
- 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]}
5607
5746
  `));
5608
5747
  }
5609
5748
 
5610
5749
  // src/commands/secret/recipients.ts
5611
5750
  init_logger();
5612
- import { existsSync as existsSync30, readFileSync as readFileSync13, writeFileSync as writeFileSync18 } from "fs";
5613
- 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";
5614
5753
  import { parse as parse3, stringify } from "yaml";
5615
- import chalk36 from "chalk";
5754
+ import chalk37 from "chalk";
5616
5755
  var SOPS_CONFIG = ".sops.yaml";
5617
5756
  var DEFAULT_PATH_REGEX = "secrets/.*\\.enc\\.json$";
5618
5757
  var AGE_RECIPIENT = /^age1[0-9a-z]+$/;
5619
5758
  async function secretRecipients(action, key, _options) {
5620
5759
  const cwd = process.cwd();
5621
- const configPath = join27(cwd, SOPS_CONFIG);
5760
+ const configPath = join29(cwd, SOPS_CONFIG);
5622
5761
  if (action === "list") {
5623
5762
  listRecipients(configPath);
5624
5763
  return;
@@ -5645,30 +5784,30 @@ async function secretRecipients(action, key, _options) {
5645
5784
  logger.warn("Removing a recipient does not revoke values they already decrypted \u2014 rotate those values.");
5646
5785
  }
5647
5786
  rule.age = [...recipients].join(",");
5648
- writeFileSync18(configPath, stringify(config));
5787
+ writeFileSync19(configPath, stringify(config));
5649
5788
  logger.success(`${action === "add" ? "Added" : "Removed"} recipient; updated ${SOPS_CONFIG}.`);
5650
5789
  await reencrypt(cwd);
5651
5790
  }
5652
5791
  function listRecipients(configPath) {
5653
- if (!existsSync30(configPath)) {
5792
+ if (!existsSync32(configPath)) {
5654
5793
  logger.info(`No ${SOPS_CONFIG} found.`);
5655
5794
  return;
5656
5795
  }
5657
5796
  const config = loadConfig(configPath);
5658
5797
  const rules = config.creation_rules ?? [];
5659
- console.log(chalk36.blue.bold(`
5798
+ console.log(chalk37.blue.bold(`
5660
5799
  \u{1F465} Recipients (${SOPS_CONFIG})
5661
5800
  `));
5662
5801
  for (const rule of rules) {
5663
- console.log(chalk36.dim(` ${rule.path_regex ?? "(any path)"}`));
5802
+ console.log(chalk37.dim(` ${rule.path_regex ?? "(any path)"}`));
5664
5803
  for (const recipient of parseRecipients(rule.age)) {
5665
- console.log(` ${chalk36.cyan(recipient)}`);
5804
+ console.log(` ${chalk37.cyan(recipient)}`);
5666
5805
  }
5667
5806
  }
5668
5807
  console.log();
5669
5808
  }
5670
5809
  function loadConfig(configPath) {
5671
- if (!existsSync30(configPath)) {
5810
+ if (!existsSync32(configPath)) {
5672
5811
  return { creation_rules: [] };
5673
5812
  }
5674
5813
  return parse3(readFileSync13(configPath, "utf-8")) ?? { creation_rules: [] };
@@ -5703,8 +5842,8 @@ async function reencrypt(cwd) {
5703
5842
 
5704
5843
  // src/commands/secret/check.ts
5705
5844
  init_logger();
5706
- import { join as join28 } from "path";
5707
- import chalk37 from "chalk";
5845
+ import { join as join30 } from "path";
5846
+ import chalk38 from "chalk";
5708
5847
  init_env_file();
5709
5848
  var COMMITTED_FILES = [".env", ".env.example"];
5710
5849
  var PLACEHOLDER = /(your-|changeme|placeholder|example|<.*>)/i;
@@ -5722,35 +5861,35 @@ async function secretCheck(options) {
5722
5861
  const issues = [];
5723
5862
  const warnings = [];
5724
5863
  for (const file of COMMITTED_FILES) {
5725
- const parsed = parseEnvFile(join28(cwd, file));
5864
+ const parsed = parseEnvFile(join30(cwd, file));
5726
5865
  for (const [key, value] of Object.entries(parsed)) {
5727
5866
  if (secretKeys.has(key) && value.length > 0 && !PLACEHOLDER.test(value)) {
5728
- 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.`);
5729
5868
  }
5730
5869
  }
5731
5870
  }
5732
- const serverEnv = parseEnvFile(join28(cwd, ".env.server"));
5871
+ const serverEnv = parseEnvFile(join30(cwd, ".env.server"));
5733
5872
  for (const key of secretKeys) {
5734
5873
  const value = serverEnv[key];
5735
5874
  if (value && !value.startsWith(KEYCHAIN_REF_PREFIX)) {
5736
- 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.`);
5737
5876
  }
5738
5877
  }
5739
5878
  report(issues, warnings, cwd);
5740
5879
  }
5741
5880
  function report(issues, warnings, cwd) {
5742
- 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"));
5743
5882
  for (const issue of issues) {
5744
- console.log(` ${chalk37.red("\u2717")} ${issue}`);
5883
+ console.log(` ${chalk38.red("\u2717")} ${issue}`);
5745
5884
  }
5746
5885
  for (const warning of warnings) {
5747
- console.log(` ${chalk37.yellow("\u26A0")} ${warning}`);
5886
+ console.log(` ${chalk38.yellow("\u26A0")} ${warning}`);
5748
5887
  }
5749
5888
  if (!hasSopsConfig(cwd)) {
5750
- 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)")}`);
5751
5890
  }
5752
5891
  if (issues.length === 0 && warnings.length === 0) {
5753
- console.log(chalk37.green(" \u2713 No plaintext secret leaks found."));
5892
+ console.log(chalk38.green(" \u2713 No plaintext secret leaks found."));
5754
5893
  }
5755
5894
  console.log();
5756
5895
  if (issues.length > 0) {
@@ -5761,7 +5900,7 @@ function report(issues, warnings, cwd) {
5761
5900
  // src/commands/secret/index.ts
5762
5901
  var ENV_OPTION = ["-e, --env <env>", "Target environment (local | development | staging | production)", "local"];
5763
5902
  var PKG_OPTION = ["-p, --package <package>", "Package whose env schema to read", "@spfn/core"];
5764
- 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");
5765
5904
  secretCommand.command("set [key]").description("Store a secret value (prompts for the value, masked)").option(...ENV_OPTION).option(...PKG_OPTION).action(secretSet);
5766
5905
  secretCommand.command("list").description("List declared secrets and their status (never prints values)").option(...ENV_OPTION).option(...PKG_OPTION).action(secretList);
5767
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);
@@ -5772,7 +5911,7 @@ secretCommand.command("check").description("Static hygiene lint \u2014 flag plai
5772
5911
 
5773
5912
  // src/index.ts
5774
5913
  init_version();
5775
- var program = new Command13();
5914
+ var program = new Command14();
5776
5915
  program.name("spfn").description("SPFN CLI - The Missing Backend for Next.js").version(getCliVersion());
5777
5916
  program.addCommand(createCommand);
5778
5917
  program.addCommand(initCommand);
@@ -5780,6 +5919,7 @@ program.addCommand(addCommand);
5780
5919
  program.addCommand(devCommand);
5781
5920
  program.addCommand(buildCommand);
5782
5921
  program.addCommand(startCommand);
5922
+ program.addCommand(provisionCommand);
5783
5923
  program.addCommand(codegenCommand);
5784
5924
  program.addCommand(keyCommand);
5785
5925
  program.addCommand(setupCommand);