spfn 0.2.0-beta.10 → 0.2.0-beta.11
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 +207 -79
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -755,7 +755,7 @@ var init_deployment_config = __esm({
|
|
|
755
755
|
|
|
756
756
|
// src/utils/version.ts
|
|
757
757
|
function getCliVersion() {
|
|
758
|
-
return "0.2.0-beta.
|
|
758
|
+
return "0.2.0-beta.11";
|
|
759
759
|
}
|
|
760
760
|
function getTagFromVersion(version) {
|
|
761
761
|
const match = version.match(/-([a-z]+)\./i);
|
|
@@ -833,26 +833,7 @@ import { existsSync as existsSync9, readFileSync as readFileSync2 } from "fs";
|
|
|
833
833
|
import { join as join9 } from "path";
|
|
834
834
|
import fse7 from "fs-extra";
|
|
835
835
|
async function setupConfigFiles(cwd) {
|
|
836
|
-
|
|
837
|
-
if (!existsSync9(envExamplePath)) {
|
|
838
|
-
const envExampleContent = `# Environment
|
|
839
|
-
NODE_ENV=local
|
|
840
|
-
|
|
841
|
-
# Logging
|
|
842
|
-
SPFN_LOG_LEVEL=info
|
|
843
|
-
|
|
844
|
-
# Database (matches docker-compose.yml)
|
|
845
|
-
DATABASE_URL=postgresql://spfn:spfn@localhost:5432/spfn_dev
|
|
846
|
-
|
|
847
|
-
# Cache - Redis/Valkey (optional)
|
|
848
|
-
CACHE_URL=redis://localhost:6379
|
|
849
|
-
|
|
850
|
-
# SPFN API Server URL (for API Route Proxy and SSR)
|
|
851
|
-
SPFN_API_URL=http://localhost:8790
|
|
852
|
-
`;
|
|
853
|
-
writeFileSync5(envExamplePath, envExampleContent);
|
|
854
|
-
logger.success("Created .env.local.example");
|
|
855
|
-
}
|
|
836
|
+
generateEnvExamples(cwd);
|
|
856
837
|
const spfnrcPath = join9(cwd, ".spfnrc.ts");
|
|
857
838
|
if (!existsSync9(spfnrcPath)) {
|
|
858
839
|
const spfnrcContent = `import { defineConfig, defineGenerator } from '@spfn/core/codegen';
|
|
@@ -878,38 +859,118 @@ export default defineConfig({
|
|
|
878
859
|
writeFileSync5(spfnrcPath, spfnrcContent);
|
|
879
860
|
logger.success("Created .spfnrc.ts (codegen configuration)");
|
|
880
861
|
}
|
|
862
|
+
updateGitignore(cwd);
|
|
863
|
+
updateTsconfig(cwd);
|
|
864
|
+
}
|
|
865
|
+
function generateEnvExamples(cwd) {
|
|
866
|
+
writeEnvExample(cwd, ".env.example", `# Shared defaults (committed)
|
|
867
|
+
# These values are shared across all environments.
|
|
868
|
+
|
|
869
|
+
# Environment
|
|
870
|
+
NODE_ENV=local
|
|
871
|
+
|
|
872
|
+
# Logging
|
|
873
|
+
SPFN_LOG_LEVEL=info
|
|
874
|
+
|
|
875
|
+
# Server
|
|
876
|
+
PORT=4000
|
|
877
|
+
|
|
878
|
+
# SPFN API Server URL (for API Route Proxy and SSR)
|
|
879
|
+
SPFN_API_URL=http://localhost:8790
|
|
880
|
+
NEXT_PUBLIC_SPFN_API_URL=http://localhost:8790
|
|
881
|
+
`);
|
|
882
|
+
writeEnvExample(cwd, ".env.local.example", `# Local overrides (gitignored)
|
|
883
|
+
# Developer-specific values that should NOT be committed.
|
|
884
|
+
|
|
885
|
+
# Database (matches docker-compose.yml)
|
|
886
|
+
DATABASE_URL=postgresql://spfn:spfn@localhost:5432/spfn_dev
|
|
887
|
+
|
|
888
|
+
# Cache - Redis/Valkey (optional)
|
|
889
|
+
CACHE_URL=redis://localhost:6379
|
|
890
|
+
|
|
891
|
+
# SPFN App URL (optional, for CORS and redirects)
|
|
892
|
+
# SPFN_APP_URL=http://localhost:3790
|
|
893
|
+
`);
|
|
894
|
+
writeEnvExample(cwd, ".env.server.example", `# Server-only defaults (committed)
|
|
895
|
+
# These values are only loaded by the SPFN server, not by Next.js.
|
|
896
|
+
|
|
897
|
+
# Database pool
|
|
898
|
+
DB_POOL_MAX=10
|
|
899
|
+
DB_POOL_IDLE_TIMEOUT=30
|
|
900
|
+
|
|
901
|
+
# Server timeouts
|
|
902
|
+
SERVER_TIMEOUT=120000
|
|
903
|
+
SHUTDOWN_TIMEOUT=30000
|
|
904
|
+
`);
|
|
905
|
+
writeEnvExample(cwd, ".env.server.local.example", `# Server secrets (gitignored)
|
|
906
|
+
# Server-only sensitive values. Never commit this file.
|
|
907
|
+
|
|
908
|
+
# Database write/read URLs (master-replica pattern, optional)
|
|
909
|
+
# DATABASE_WRITE_URL=postgresql://user:password@master:5432/dbname
|
|
910
|
+
# DATABASE_READ_URL=postgresql://user:password@replica:5432/dbname
|
|
911
|
+
|
|
912
|
+
# Cache password (optional)
|
|
913
|
+
# CACHE_PASSWORD=your-redis-password
|
|
914
|
+
`);
|
|
915
|
+
}
|
|
916
|
+
function writeEnvExample(cwd, filename, content) {
|
|
917
|
+
const filePath = join9(cwd, filename);
|
|
918
|
+
if (existsSync9(filePath)) {
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
writeFileSync5(filePath, content);
|
|
922
|
+
logger.success(`Created ${filename}`);
|
|
923
|
+
}
|
|
924
|
+
function updateGitignore(cwd) {
|
|
881
925
|
const gitignorePath = join9(cwd, ".gitignore");
|
|
882
|
-
if (existsSync9(gitignorePath)) {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
926
|
+
if (!existsSync9(gitignorePath)) {
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
try {
|
|
930
|
+
const content = readFileSync2(gitignorePath, "utf-8");
|
|
931
|
+
let updated = content;
|
|
932
|
+
let changed = false;
|
|
933
|
+
if (!content.includes(".spfn")) {
|
|
934
|
+
updated = updated.replace(
|
|
935
|
+
/# production\n\/build/,
|
|
936
|
+
"# production\n/build\n\n# spfn\n/.spfn/"
|
|
937
|
+
);
|
|
938
|
+
changed = true;
|
|
895
939
|
}
|
|
940
|
+
if (!content.includes(".env.local") && !content.includes(".env.*.local")) {
|
|
941
|
+
updated += `
|
|
942
|
+
# environment secrets (local overrides)
|
|
943
|
+
.env.local
|
|
944
|
+
.env.*.local
|
|
945
|
+
`;
|
|
946
|
+
changed = true;
|
|
947
|
+
}
|
|
948
|
+
if (changed) {
|
|
949
|
+
writeFileSync5(gitignorePath, updated);
|
|
950
|
+
logger.success("Updated .gitignore with .spfn directory and env patterns");
|
|
951
|
+
}
|
|
952
|
+
} catch (error) {
|
|
953
|
+
logger.warn("Could not update .gitignore (you can add patterns manually)");
|
|
896
954
|
}
|
|
955
|
+
}
|
|
956
|
+
function updateTsconfig(cwd) {
|
|
897
957
|
const tsconfigPath = join9(cwd, "tsconfig.json");
|
|
898
|
-
if (existsSync9(tsconfigPath)) {
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
logger.warn('Could not update tsconfig.json (you can add "src/server" to exclude manually)');
|
|
958
|
+
if (!existsSync9(tsconfigPath)) {
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
try {
|
|
962
|
+
const tsconfigContent = readFileSync2(tsconfigPath, "utf-8");
|
|
963
|
+
const tsconfig = JSON.parse(tsconfigContent);
|
|
964
|
+
if (!tsconfig.exclude) {
|
|
965
|
+
tsconfig.exclude = [];
|
|
966
|
+
}
|
|
967
|
+
if (!tsconfig.exclude.includes("src/server")) {
|
|
968
|
+
tsconfig.exclude.push("src/server");
|
|
969
|
+
writeFileSync5(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n");
|
|
970
|
+
logger.success("Updated tsconfig.json (excluded src/server for Vercel compatibility)");
|
|
912
971
|
}
|
|
972
|
+
} catch (error) {
|
|
973
|
+
logger.warn('Could not update tsconfig.json (you can add "src/server" to exclude manually)');
|
|
913
974
|
}
|
|
914
975
|
}
|
|
915
976
|
var writeFileSync5;
|
|
@@ -983,7 +1044,7 @@ __export(function_migrations_exports, {
|
|
|
983
1044
|
import chalk11 from "chalk";
|
|
984
1045
|
import { join as join15 } from "path";
|
|
985
1046
|
import { env as env2 } from "@spfn/core/config";
|
|
986
|
-
import {
|
|
1047
|
+
import { loadEnv as loadEnv2 } from "@spfn/core/server";
|
|
987
1048
|
import { existsSync as existsSync16, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
|
|
988
1049
|
function discoverFunctionMigrations(cwd = process.cwd()) {
|
|
989
1050
|
const nodeModulesPath = join15(cwd, "node_modules");
|
|
@@ -1031,7 +1092,7 @@ async function executeFunctionMigrations(functionMigrations) {
|
|
|
1031
1092
|
const { drizzle } = await import("drizzle-orm/postgres-js");
|
|
1032
1093
|
const { migrate } = await import("drizzle-orm/postgres-js/migrator");
|
|
1033
1094
|
const postgres = await import("postgres");
|
|
1034
|
-
|
|
1095
|
+
loadEnv2();
|
|
1035
1096
|
if (!env2.DATABASE_URL) {
|
|
1036
1097
|
throw new Error("DATABASE_URL not found in environment");
|
|
1037
1098
|
}
|
|
@@ -2020,9 +2081,9 @@ import { spawn } from "child_process";
|
|
|
2020
2081
|
import chalk9 from "chalk";
|
|
2021
2082
|
import ora6 from "ora";
|
|
2022
2083
|
import { env } from "@spfn/core/config";
|
|
2023
|
-
import {
|
|
2084
|
+
import { loadEnv } from "@spfn/core/server";
|
|
2024
2085
|
function validateDatabasePrerequisites() {
|
|
2025
|
-
|
|
2086
|
+
loadEnv();
|
|
2026
2087
|
if (!env.DATABASE_URL) {
|
|
2027
2088
|
console.error(chalk9.red("\u274C DATABASE_URL not found in environment"));
|
|
2028
2089
|
console.log(chalk9.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
|
|
@@ -2034,7 +2095,7 @@ async function runDrizzleCommand(command) {
|
|
|
2034
2095
|
const tempConfigPath = `./drizzle.config.${process.pid}.${Date.now()}.temp.ts`;
|
|
2035
2096
|
const configPath = hasUserConfig ? "./drizzle.config.ts" : tempConfigPath;
|
|
2036
2097
|
if (!hasUserConfig) {
|
|
2037
|
-
|
|
2098
|
+
loadEnv();
|
|
2038
2099
|
if (!env.DATABASE_URL) {
|
|
2039
2100
|
console.error(chalk9.red("\u274C DATABASE_URL not found in environment"));
|
|
2040
2101
|
console.log(chalk9.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
|
|
@@ -2368,10 +2429,10 @@ async function listBackupFiles() {
|
|
|
2368
2429
|
|
|
2369
2430
|
// src/commands/db/backup.ts
|
|
2370
2431
|
import { env as env3 } from "@spfn/core/config";
|
|
2371
|
-
import {
|
|
2432
|
+
import { loadEnv as loadEnv3 } from "@spfn/core/server";
|
|
2372
2433
|
async function dbBackup(options) {
|
|
2373
2434
|
console.log(chalk15.blue("\u{1F4BE} Creating database backup...\n"));
|
|
2374
|
-
|
|
2435
|
+
loadEnv3();
|
|
2375
2436
|
const dbUrl = env3.DATABASE_URL;
|
|
2376
2437
|
if (!dbUrl) {
|
|
2377
2438
|
console.error(chalk15.red("\u274C DATABASE_URL not found in environment"));
|
|
@@ -2656,10 +2717,10 @@ import chalk20 from "chalk";
|
|
|
2656
2717
|
import ora9 from "ora";
|
|
2657
2718
|
import prompts4 from "prompts";
|
|
2658
2719
|
import { env as env5 } from "@spfn/core/config";
|
|
2659
|
-
import {
|
|
2720
|
+
import { loadEnv as loadEnv4 } from "@spfn/core/server";
|
|
2660
2721
|
async function dbRestore(backupFile, options = {}) {
|
|
2661
2722
|
console.log(chalk20.blue("\u267B\uFE0F Restoring database from backup...\n"));
|
|
2662
|
-
|
|
2723
|
+
loadEnv4();
|
|
2663
2724
|
const dbUrl = env5.DATABASE_URL;
|
|
2664
2725
|
if (!dbUrl) {
|
|
2665
2726
|
console.error(chalk20.red("\u274C DATABASE_URL not found in environment"));
|
|
@@ -4390,10 +4451,26 @@ import chalk26 from "chalk";
|
|
|
4390
4451
|
import { existsSync as existsSync23, readFileSync as readFileSync8, writeFileSync as writeFileSync18 } from "fs";
|
|
4391
4452
|
import { resolve } from "path";
|
|
4392
4453
|
import { parse } from "dotenv";
|
|
4393
|
-
var
|
|
4454
|
+
var VALID_ENVS = ["local", "development", "staging", "production", "test"];
|
|
4455
|
+
var BASE_ENV_FILES = {
|
|
4394
4456
|
nextjs: [".env", ".env.local"],
|
|
4395
4457
|
server: [".env.server", ".env.server.local"]
|
|
4396
4458
|
};
|
|
4459
|
+
function getEnvFilesForEnvironment(nodeEnv) {
|
|
4460
|
+
const files = [".env"];
|
|
4461
|
+
if (nodeEnv) {
|
|
4462
|
+
files.push(`.env.${nodeEnv}`);
|
|
4463
|
+
}
|
|
4464
|
+
if (nodeEnv !== "test") {
|
|
4465
|
+
files.push(".env.local");
|
|
4466
|
+
}
|
|
4467
|
+
if (nodeEnv) {
|
|
4468
|
+
files.push(`.env.${nodeEnv}.local`);
|
|
4469
|
+
}
|
|
4470
|
+
files.push(".env.server");
|
|
4471
|
+
files.push(".env.server.local");
|
|
4472
|
+
return files;
|
|
4473
|
+
}
|
|
4397
4474
|
function getTargetFile(schema) {
|
|
4398
4475
|
const isNextjs = schema.nextjs ?? schema.key?.startsWith("NEXT_PUBLIC_");
|
|
4399
4476
|
if (isNextjs) {
|
|
@@ -4426,8 +4503,7 @@ function formatType(type) {
|
|
|
4426
4503
|
enum: chalk26.magenta,
|
|
4427
4504
|
json: chalk26.red
|
|
4428
4505
|
};
|
|
4429
|
-
|
|
4430
|
-
return colorFn(type);
|
|
4506
|
+
return (typeColors[type] || chalk26.white)(type);
|
|
4431
4507
|
}
|
|
4432
4508
|
function formatDefault(value, type) {
|
|
4433
4509
|
if (value === void 0) {
|
|
@@ -4588,8 +4664,19 @@ var envCommand = new Command12("env").description("Manage environment variables"
|
|
|
4588
4664
|
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);
|
|
4589
4665
|
envCommand.command("stats").description("Show environment variable statistics").option("-p, --package <package>", "Package name to read env schema from", "@spfn/core").action(showEnvStats);
|
|
4590
4666
|
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);
|
|
4667
|
+
function validateEnvOption(envValue) {
|
|
4668
|
+
if (!VALID_ENVS.includes(envValue)) {
|
|
4669
|
+
console.error(chalk26.red(`
|
|
4670
|
+
\u274C Invalid environment: "${envValue}"`));
|
|
4671
|
+
console.log(chalk26.dim(` Valid values: ${VALID_ENVS.join(", ")}
|
|
4672
|
+
`));
|
|
4673
|
+
process.exit(1);
|
|
4674
|
+
}
|
|
4675
|
+
return envValue;
|
|
4676
|
+
}
|
|
4591
4677
|
async function initEnvFiles(options) {
|
|
4592
4678
|
const packageName = options.package || "@spfn/core";
|
|
4679
|
+
const targetEnv = options.env ? validateEnvOption(options.env) : void 0;
|
|
4593
4680
|
const cwd = process.cwd();
|
|
4594
4681
|
try {
|
|
4595
4682
|
const envSchema = await loadEnvSchema(packageName);
|
|
@@ -4601,24 +4688,41 @@ async function initEnvFiles(options) {
|
|
|
4601
4688
|
acc[exampleFile].push([key, schema]);
|
|
4602
4689
|
return acc;
|
|
4603
4690
|
}, {});
|
|
4604
|
-
|
|
4691
|
+
if (targetEnv) {
|
|
4692
|
+
console.log(chalk26.blue.bold(`
|
|
4693
|
+
\u{1F680} Generating .env template files for ${chalk26.cyan(targetEnv)} environment
|
|
4694
|
+
`));
|
|
4695
|
+
const envSpecificFiles = {};
|
|
4696
|
+
const committedVars = allVars.filter(([_, schema]) => !schema.sensitive);
|
|
4697
|
+
if (committedVars.length > 0) {
|
|
4698
|
+
envSpecificFiles[`.env.${targetEnv}.example`] = committedVars;
|
|
4699
|
+
}
|
|
4700
|
+
const sensitiveVars = allVars.filter(([_, schema]) => schema.sensitive);
|
|
4701
|
+
if (sensitiveVars.length > 0) {
|
|
4702
|
+
envSpecificFiles[`.env.${targetEnv}.local.example`] = sensitiveVars;
|
|
4703
|
+
}
|
|
4704
|
+
const allGrouped = { ...grouped, ...envSpecificFiles };
|
|
4705
|
+
for (const [file, vars] of Object.entries(allGrouped)) {
|
|
4706
|
+
writeEnvTemplate(cwd, file, vars, options.force ?? false);
|
|
4707
|
+
}
|
|
4708
|
+
} else {
|
|
4709
|
+
console.log(chalk26.blue.bold(`
|
|
4605
4710
|
\u{1F680} Generating .env template files
|
|
4606
4711
|
`));
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
if (existsSync23(filePath) && !options.force) {
|
|
4610
|
-
console.log(chalk26.yellow(` \u23ED\uFE0F ${file} already exists (use --force to overwrite)`));
|
|
4611
|
-
continue;
|
|
4712
|
+
for (const [file, vars] of Object.entries(grouped)) {
|
|
4713
|
+
writeEnvTemplate(cwd, file, vars, options.force ?? false);
|
|
4612
4714
|
}
|
|
4613
|
-
const content = generateEnvFileContent(vars);
|
|
4614
|
-
writeFileSync18(filePath, content, "utf-8");
|
|
4615
|
-
console.log(chalk26.green(` \u2705 ${file} (${vars.length} variables)`));
|
|
4616
4715
|
}
|
|
4617
4716
|
console.log(chalk26.dim("\n\u{1F4A1} Copy .example files to create your actual .env files:"));
|
|
4618
4717
|
console.log(chalk26.dim(" cp .env.example .env"));
|
|
4619
4718
|
console.log(chalk26.dim(" cp .env.local.example .env.local"));
|
|
4620
4719
|
console.log(chalk26.dim(" cp .env.server.example .env.server"));
|
|
4621
|
-
console.log(chalk26.dim(" cp .env.server.local.example .env.server.local
|
|
4720
|
+
console.log(chalk26.dim(" cp .env.server.local.example .env.server.local"));
|
|
4721
|
+
if (targetEnv) {
|
|
4722
|
+
console.log(chalk26.dim(` cp .env.${targetEnv}.example .env.${targetEnv}`));
|
|
4723
|
+
console.log(chalk26.dim(` cp .env.${targetEnv}.local.example .env.${targetEnv}.local`));
|
|
4724
|
+
}
|
|
4725
|
+
console.log("");
|
|
4622
4726
|
} catch (error) {
|
|
4623
4727
|
console.error(chalk26.red(`
|
|
4624
4728
|
\u274C ${error instanceof Error ? error.message : "Unknown error"}
|
|
@@ -4626,6 +4730,15 @@ async function initEnvFiles(options) {
|
|
|
4626
4730
|
process.exit(1);
|
|
4627
4731
|
}
|
|
4628
4732
|
}
|
|
4733
|
+
function writeEnvTemplate(cwd, file, vars, force) {
|
|
4734
|
+
const filePath = resolve(cwd, file);
|
|
4735
|
+
if (existsSync23(filePath) && !force) {
|
|
4736
|
+
console.log(chalk26.yellow(` \u23ED\uFE0F ${file} already exists (use --force to overwrite)`));
|
|
4737
|
+
return;
|
|
4738
|
+
}
|
|
4739
|
+
writeFileSync18(filePath, generateEnvFileContent(vars), "utf-8");
|
|
4740
|
+
console.log(chalk26.green(` \u2705 ${file} (${vars.length} variables)`));
|
|
4741
|
+
}
|
|
4629
4742
|
function generateEnvFileContent(vars) {
|
|
4630
4743
|
const lines = [
|
|
4631
4744
|
"# Auto-generated by spfn env init",
|
|
@@ -4653,18 +4766,20 @@ function generateEnvFileContent(vars) {
|
|
|
4653
4766
|
}
|
|
4654
4767
|
async function checkEnvFiles(options) {
|
|
4655
4768
|
const packageName = options.package || "@spfn/core";
|
|
4769
|
+
const targetEnv = options.env ? validateEnvOption(options.env) : void 0;
|
|
4656
4770
|
const cwd = process.cwd();
|
|
4657
4771
|
try {
|
|
4658
4772
|
const envSchema = await loadEnvSchema(packageName);
|
|
4659
4773
|
const allVars = Object.entries(envSchema);
|
|
4774
|
+
const envLabel = targetEnv ? ` (${targetEnv})` : "";
|
|
4660
4775
|
console.log(chalk26.blue.bold(`
|
|
4661
|
-
\u{1F50D} Checking .env files against schema
|
|
4776
|
+
\u{1F50D} Checking .env files against schema${envLabel}
|
|
4662
4777
|
`));
|
|
4663
|
-
const
|
|
4778
|
+
const filesToCheck = targetEnv ? getEnvFilesForEnvironment(targetEnv) : [...BASE_ENV_FILES.nextjs, ...BASE_ENV_FILES.server];
|
|
4664
4779
|
const loadedEnv = {};
|
|
4665
4780
|
const issues = [];
|
|
4666
4781
|
const warnings = [];
|
|
4667
|
-
for (const file of
|
|
4782
|
+
for (const file of filesToCheck) {
|
|
4668
4783
|
const filePath = resolve(cwd, file);
|
|
4669
4784
|
if (!existsSync23(filePath)) {
|
|
4670
4785
|
continue;
|
|
@@ -4686,8 +4801,8 @@ async function checkEnvFiles(options) {
|
|
|
4686
4801
|
}
|
|
4687
4802
|
continue;
|
|
4688
4803
|
}
|
|
4689
|
-
const isNextjsFile =
|
|
4690
|
-
const isServerFile =
|
|
4804
|
+
const isNextjsFile = BASE_ENV_FILES.nextjs.includes(found.file);
|
|
4805
|
+
const isServerFile = BASE_ENV_FILES.server.includes(found.file);
|
|
4691
4806
|
const shouldBeNextjs = schema.nextjs ?? key.startsWith("NEXT_PUBLIC_");
|
|
4692
4807
|
if (!shouldBeNextjs && isNextjsFile && !isServerFile) {
|
|
4693
4808
|
if (schema.sensitive) {
|
|
@@ -4737,13 +4852,26 @@ async function checkEnvFiles(options) {
|
|
|
4737
4852
|
process.exit(1);
|
|
4738
4853
|
}
|
|
4739
4854
|
}
|
|
4740
|
-
envCommand.command("init").description("Generate .env template files from schema").option("-p, --package <package>", "Package name to read env schema from", "@spfn/core").option("-f, --force", "Overwrite existing files").action(initEnvFiles);
|
|
4741
|
-
envCommand.command("check").description("Check .env files against schema").option("-p, --package <package>", "Package name to read env schema from", "@spfn/core").action(checkEnvFiles);
|
|
4855
|
+
envCommand.command("init").description("Generate .env template files from schema").option("-p, --package <package>", "Package name to read env schema from", "@spfn/core").option("-e, --env <environment>", "Generate environment-specific templates (e.g. production, staging)").option("-f, --force", "Overwrite existing files").action(initEnvFiles);
|
|
4856
|
+
envCommand.command("check").description("Check .env files against schema").option("-p, --package <package>", "Package name to read env schema from", "@spfn/core").option("-e, --env <environment>", "Check files for a specific environment (e.g. production)").action(checkEnvFiles);
|
|
4742
4857
|
async function validateEnvVars(options) {
|
|
4743
4858
|
const packages = options.packages || ["@spfn/core"];
|
|
4744
|
-
|
|
4859
|
+
const targetEnv = options.env ? validateEnvOption(options.env) : void 0;
|
|
4860
|
+
if (targetEnv) {
|
|
4861
|
+
const { loadEnv: loadEnv5 } = await import("@spfn/core/env/loader");
|
|
4862
|
+
const result = loadEnv5({ nodeEnv: targetEnv });
|
|
4863
|
+
console.log(chalk26.blue.bold(`
|
|
4864
|
+
\u{1F50D} Validating environment variables for ${chalk26.cyan(targetEnv)}
|
|
4865
|
+
`));
|
|
4866
|
+
if (result.loadedFiles.length > 0) {
|
|
4867
|
+
console.log(chalk26.dim(` Loaded: ${result.loadedFiles.join(", ")}`));
|
|
4868
|
+
}
|
|
4869
|
+
console.log("");
|
|
4870
|
+
} else {
|
|
4871
|
+
console.log(chalk26.blue.bold(`
|
|
4745
4872
|
\u{1F50D} Validating environment variables
|
|
4746
4873
|
`));
|
|
4874
|
+
}
|
|
4747
4875
|
const allErrors = [];
|
|
4748
4876
|
const allWarnings = [];
|
|
4749
4877
|
for (const packageName of packages) {
|
|
@@ -4803,7 +4931,7 @@ async function validateEnvVars(options) {
|
|
|
4803
4931
|
process.exit(1);
|
|
4804
4932
|
}
|
|
4805
4933
|
}
|
|
4806
|
-
envCommand.command("validate").description("Validate environment variables against schema (for CI/CD)").option("-p, --packages <packages...>", "Packages to validate", ["@spfn/core"]).option("-s, --strict", "Exit on any error (including load failures)").action(validateEnvVars);
|
|
4934
|
+
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);
|
|
4807
4935
|
|
|
4808
4936
|
// src/index.ts
|
|
4809
4937
|
init_version();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spfn",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.11",
|
|
4
4
|
"description": "Superfunction CLI - Add SPFN to your Next.js project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"postgres": "^3.4.0",
|
|
68
68
|
"prompts": "^2.4.2",
|
|
69
69
|
"tsup": "^8.5.0",
|
|
70
|
-
"@spfn/core": "0.2.0-beta.
|
|
70
|
+
"@spfn/core": "0.2.0-beta.14"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@types/fs-extra": "^11.0.4",
|