spfn 0.2.0-beta.1 → 0.2.0-beta.2
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 +247 -37
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1183,7 +1183,7 @@ import { existsSync as existsSync11, readFileSync as readFileSync3, writeFileSyn
|
|
|
1183
1183
|
import { join as join11 } from "path";
|
|
1184
1184
|
import { execa as execa4 } from "execa";
|
|
1185
1185
|
import chokidar from "chokidar";
|
|
1186
|
-
var devCommand = new Command4("dev").description("Start SPFN development server (detects and runs Next.js + Hono)").option("-p, --port <port>", "Server port"
|
|
1186
|
+
var devCommand = new Command4("dev").description("Start SPFN development server (detects and runs Next.js + Hono)").option("-p, --port <port>", "Server port").option("-H, --host <host>", "Server host").option("--routes <path>", "Routes directory path").option("--server-only", "Run only Hono server (skip Next.js)").option("--watch", "Enable hot reload (watch mode)").action(async (options) => {
|
|
1187
1187
|
process.setMaxListeners(20);
|
|
1188
1188
|
if (!process.env.NODE_ENV) {
|
|
1189
1189
|
process.env.NODE_ENV = "development";
|
|
@@ -1205,6 +1205,11 @@ var devCommand = new Command4("dev").description("Start SPFN development server
|
|
|
1205
1205
|
const serverEntry = join11(tempDir, "server.mjs");
|
|
1206
1206
|
const watcherEntry = join11(tempDir, "watcher.mjs");
|
|
1207
1207
|
mkdirSync(tempDir, { recursive: true });
|
|
1208
|
+
const configParts = [];
|
|
1209
|
+
if (options.port) configParts.push(`port: ${options.port}`);
|
|
1210
|
+
if (options.host) configParts.push(`host: '${options.host}'`);
|
|
1211
|
+
if (options.routes) configParts.push(`routesPath: '${options.routes}'`);
|
|
1212
|
+
configParts.push("debug: true");
|
|
1208
1213
|
writeFileSync6(serverEntry, `
|
|
1209
1214
|
// Load environment variables FIRST (before any imports that depend on them)
|
|
1210
1215
|
// Use centralized environment loader for standard dotenv priority
|
|
@@ -1214,9 +1219,7 @@ await import('@spfn/core/config');
|
|
|
1214
1219
|
const { startServer } = await import('@spfn/core/server');
|
|
1215
1220
|
|
|
1216
1221
|
await startServer({
|
|
1217
|
-
|
|
1218
|
-
host: '${options.host}',
|
|
1219
|
-
${options.routes ? `routesPath: '${options.routes}',` : ""}debug: true
|
|
1222
|
+
${configParts.join(",\n ")}
|
|
1220
1223
|
});
|
|
1221
1224
|
`);
|
|
1222
1225
|
writeFileSync6(watcherEntry, `
|
|
@@ -1312,7 +1315,7 @@ catch (error)
|
|
|
1312
1315
|
serverProcess2.kill("SIGTERM");
|
|
1313
1316
|
await serverProcess2.catch(() => {
|
|
1314
1317
|
});
|
|
1315
|
-
await new Promise((
|
|
1318
|
+
await new Promise((resolve2) => setTimeout(resolve2, 500));
|
|
1316
1319
|
} catch (error) {
|
|
1317
1320
|
}
|
|
1318
1321
|
}
|
|
@@ -1356,12 +1359,12 @@ catch (error)
|
|
|
1356
1359
|
process.on("SIGTERM", cleanup2);
|
|
1357
1360
|
startWatcher2();
|
|
1358
1361
|
startServer2();
|
|
1359
|
-
await new Promise((
|
|
1362
|
+
await new Promise((resolve2) => {
|
|
1360
1363
|
const keepAlive = setInterval(() => {
|
|
1361
1364
|
}, 1e6);
|
|
1362
1365
|
process.once("beforeExit", () => {
|
|
1363
1366
|
clearInterval(keepAlive);
|
|
1364
|
-
|
|
1367
|
+
resolve2();
|
|
1365
1368
|
});
|
|
1366
1369
|
});
|
|
1367
1370
|
return;
|
|
@@ -1421,7 +1424,7 @@ catch (error)
|
|
|
1421
1424
|
serverProcess.kill("SIGTERM");
|
|
1422
1425
|
await serverProcess.catch(() => {
|
|
1423
1426
|
});
|
|
1424
|
-
await new Promise((
|
|
1427
|
+
await new Promise((resolve2) => setTimeout(resolve2, 500));
|
|
1425
1428
|
} catch (error) {
|
|
1426
1429
|
}
|
|
1427
1430
|
}
|
|
@@ -1468,14 +1471,14 @@ catch (error)
|
|
|
1468
1471
|
process.on("SIGTERM", cleanup);
|
|
1469
1472
|
startWatcher();
|
|
1470
1473
|
startServer();
|
|
1471
|
-
await new Promise((
|
|
1474
|
+
await new Promise((resolve2) => setTimeout(resolve2, 2e3));
|
|
1472
1475
|
startNext();
|
|
1473
|
-
await new Promise((
|
|
1476
|
+
await new Promise((resolve2) => {
|
|
1474
1477
|
const keepAlive = setInterval(() => {
|
|
1475
1478
|
}, 1e6);
|
|
1476
1479
|
process.once("beforeExit", () => {
|
|
1477
1480
|
clearInterval(keepAlive);
|
|
1478
|
-
|
|
1481
|
+
resolve2();
|
|
1479
1482
|
});
|
|
1480
1483
|
});
|
|
1481
1484
|
});
|
|
@@ -2024,7 +2027,7 @@ async function runDrizzleCommand(command) {
|
|
|
2024
2027
|
}
|
|
2025
2028
|
const args = command.split(" ");
|
|
2026
2029
|
args.push(`--config=${configPath}`);
|
|
2027
|
-
return new Promise((
|
|
2030
|
+
return new Promise((resolve2, reject) => {
|
|
2028
2031
|
const drizzleProcess = spawn("drizzle-kit", args, {
|
|
2029
2032
|
stdio: "inherit",
|
|
2030
2033
|
// Allow interactive input
|
|
@@ -2038,7 +2041,7 @@ async function runDrizzleCommand(command) {
|
|
|
2038
2041
|
drizzleProcess.on("close", (code) => {
|
|
2039
2042
|
cleanup();
|
|
2040
2043
|
if (code === 0) {
|
|
2041
|
-
|
|
2044
|
+
resolve2();
|
|
2042
2045
|
} else {
|
|
2043
2046
|
reject(new Error(`drizzle-kit ${command} exited with code ${code}`));
|
|
2044
2047
|
}
|
|
@@ -2130,15 +2133,15 @@ function parseDatabaseUrl(dbUrl) {
|
|
|
2130
2133
|
}
|
|
2131
2134
|
}
|
|
2132
2135
|
async function isPortAvailable(port) {
|
|
2133
|
-
return new Promise((
|
|
2136
|
+
return new Promise((resolve2) => {
|
|
2134
2137
|
const server = net.createServer();
|
|
2135
2138
|
server.once("error", () => {
|
|
2136
2139
|
server.close();
|
|
2137
|
-
|
|
2140
|
+
resolve2(false);
|
|
2138
2141
|
});
|
|
2139
2142
|
server.once("listening", () => {
|
|
2140
2143
|
server.close();
|
|
2141
|
-
|
|
2144
|
+
resolve2(true);
|
|
2142
2145
|
});
|
|
2143
2146
|
server.listen(port, "127.0.0.1");
|
|
2144
2147
|
});
|
|
@@ -2392,7 +2395,7 @@ async function dbBackup(options) {
|
|
|
2392
2395
|
pgDump.stderr?.on("data", (data) => {
|
|
2393
2396
|
errorOutput += data.toString();
|
|
2394
2397
|
});
|
|
2395
|
-
await new Promise((
|
|
2398
|
+
await new Promise((resolve2, reject) => {
|
|
2396
2399
|
pgDump.on("close", async (code) => {
|
|
2397
2400
|
if (code === 0) {
|
|
2398
2401
|
try {
|
|
@@ -2429,7 +2432,7 @@ async function dbBackup(options) {
|
|
|
2429
2432
|
tags: tags.length > 0 ? tags : void 0
|
|
2430
2433
|
};
|
|
2431
2434
|
await saveBackupMetadata(metadata, filename);
|
|
2432
|
-
|
|
2435
|
+
resolve2();
|
|
2433
2436
|
} catch (error) {
|
|
2434
2437
|
reject(error);
|
|
2435
2438
|
}
|
|
@@ -2761,12 +2764,12 @@ async function dbRestore(backupFile, options = {}) {
|
|
|
2761
2764
|
restoreProcess.stderr?.on("data", (data) => {
|
|
2762
2765
|
errorOutput += data.toString();
|
|
2763
2766
|
});
|
|
2764
|
-
await new Promise((
|
|
2767
|
+
await new Promise((resolve2, reject) => {
|
|
2765
2768
|
restoreProcess.on("close", (code) => {
|
|
2766
2769
|
if (code === 0) {
|
|
2767
2770
|
spinner.succeed("Restore completed");
|
|
2768
2771
|
console.log(chalk20.green("\n\u2705 Database restored successfully"));
|
|
2769
|
-
|
|
2772
|
+
resolve2();
|
|
2770
2773
|
} else {
|
|
2771
2774
|
spinner.fail("Restore failed");
|
|
2772
2775
|
reject(new Error(errorOutput || "Restore failed"));
|
|
@@ -4285,6 +4288,20 @@ generateCommand.command("fn").description("Generate a new SPFN function module")
|
|
|
4285
4288
|
// src/commands/env.ts
|
|
4286
4289
|
import { Command as Command12 } from "commander";
|
|
4287
4290
|
import chalk26 from "chalk";
|
|
4291
|
+
import { existsSync as existsSync23, readFileSync as readFileSync8, writeFileSync as writeFileSync18 } from "fs";
|
|
4292
|
+
import { resolve } from "path";
|
|
4293
|
+
import { parse } from "dotenv";
|
|
4294
|
+
var ENV_FILES = {
|
|
4295
|
+
nextjs: [".env", ".env.local"],
|
|
4296
|
+
server: [".env.server", ".env.server.local"]
|
|
4297
|
+
};
|
|
4298
|
+
function getTargetFile(schema) {
|
|
4299
|
+
const isNextjs = schema.nextjs ?? schema.key?.startsWith("NEXT_PUBLIC_");
|
|
4300
|
+
if (isNextjs) {
|
|
4301
|
+
return schema.sensitive ? ".env.local" : ".env";
|
|
4302
|
+
}
|
|
4303
|
+
return schema.sensitive ? ".env.server.local" : ".env.server";
|
|
4304
|
+
}
|
|
4288
4305
|
async function loadEnvSchema(packageName) {
|
|
4289
4306
|
try {
|
|
4290
4307
|
const schemaPath = `${packageName}/config`;
|
|
@@ -4329,26 +4346,34 @@ async function listEnvVars(options) {
|
|
|
4329
4346
|
const packageName = options.package || "@spfn/core";
|
|
4330
4347
|
try {
|
|
4331
4348
|
const envSchema = await loadEnvSchema(packageName);
|
|
4332
|
-
console.log(chalk26.blue.bold(`
|
|
4333
|
-
\u{1F4CB} Environment Variables (${packageName})
|
|
4334
|
-
`));
|
|
4335
4349
|
const allVars = Object.entries(envSchema);
|
|
4336
|
-
|
|
4337
|
-
const
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4350
|
+
if (options.group) {
|
|
4351
|
+
const grouped = allVars.reduce((acc, [key, schema]) => {
|
|
4352
|
+
const target = getTargetFile(schema);
|
|
4353
|
+
if (!acc[target]) acc[target] = [];
|
|
4354
|
+
acc[target].push([key, schema]);
|
|
4355
|
+
return acc;
|
|
4356
|
+
}, {});
|
|
4357
|
+
console.log(chalk26.blue.bold(`
|
|
4358
|
+
\u{1F4CB} Environment Variables by File (${packageName})
|
|
4359
|
+
`));
|
|
4360
|
+
for (const [file, vars] of Object.entries(grouped)) {
|
|
4361
|
+
console.log(chalk26.bold.magenta(`
|
|
4362
|
+
${file}`));
|
|
4363
|
+
console.log(chalk26.dim("\u2500".repeat(50)));
|
|
4364
|
+
for (const [key, schema] of vars) {
|
|
4365
|
+
printEnvVar(key, schema);
|
|
4366
|
+
}
|
|
4344
4367
|
}
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4368
|
+
} else {
|
|
4369
|
+
console.log(chalk26.blue.bold(`
|
|
4370
|
+
\u{1F4CB} Environment Variables (${packageName})
|
|
4371
|
+
`));
|
|
4372
|
+
for (const [key, schema] of allVars) {
|
|
4373
|
+
printEnvVar(key, schema, true);
|
|
4348
4374
|
}
|
|
4349
|
-
console.log();
|
|
4350
4375
|
}
|
|
4351
|
-
console.log(chalk26.dim("\n\u{1F4A1} Tip: Use
|
|
4376
|
+
console.log(chalk26.dim("\n\u{1F4A1} Tip: Use `spfn env init` to generate .env template files\n"));
|
|
4352
4377
|
} catch (error) {
|
|
4353
4378
|
console.error(chalk26.red(`
|
|
4354
4379
|
\u274C ${error instanceof Error ? error.message : "Unknown error"}
|
|
@@ -4356,6 +4381,22 @@ async function listEnvVars(options) {
|
|
|
4356
4381
|
process.exit(1);
|
|
4357
4382
|
}
|
|
4358
4383
|
}
|
|
4384
|
+
function printEnvVar(key, schema, showFile = false) {
|
|
4385
|
+
const typeStr = formatType(schema.type);
|
|
4386
|
+
const requiredStr = schema.required || schema.default !== void 0 ? chalk26.red("[required]") : chalk26.dim("[optional]");
|
|
4387
|
+
const sensitiveStr = schema.sensitive ? chalk26.yellow(" [sensitive]") : "";
|
|
4388
|
+
const fileStr = showFile ? chalk26.dim(` \u2192 ${getTargetFile(schema)}`) : "";
|
|
4389
|
+
console.log(`${chalk26.bold.cyan(key)} ${chalk26.dim("(")}${typeStr}${chalk26.dim(")")} ${requiredStr}${sensitiveStr}${fileStr}`);
|
|
4390
|
+
console.log(` ${chalk26.dim(schema.description)}`);
|
|
4391
|
+
if (schema.default !== void 0) {
|
|
4392
|
+
console.log(` ${chalk26.dim("Default:")} ${formatDefault(schema.default, schema.type)}`);
|
|
4393
|
+
}
|
|
4394
|
+
if (schema.examples && schema.examples.length > 0) {
|
|
4395
|
+
const exampleStr = schema.examples.map((ex) => formatDefault(ex, schema.type)).join(", ");
|
|
4396
|
+
console.log(` ${chalk26.dim("Examples:")} ${exampleStr}`);
|
|
4397
|
+
}
|
|
4398
|
+
console.log();
|
|
4399
|
+
}
|
|
4359
4400
|
async function showEnvStats(options) {
|
|
4360
4401
|
const packageName = options.package || "@spfn/core";
|
|
4361
4402
|
try {
|
|
@@ -4367,14 +4408,32 @@ async function showEnvStats(options) {
|
|
|
4367
4408
|
const required = allVars.filter(([_, schema]) => schema.required || schema.default !== void 0);
|
|
4368
4409
|
const optional = allVars.filter(([_, schema]) => !schema.required && schema.default === void 0);
|
|
4369
4410
|
const sensitive = allVars.filter(([_, schema]) => schema.sensitive);
|
|
4411
|
+
const nextjsVars = allVars.filter(
|
|
4412
|
+
([_, schema]) => schema.nextjs ?? schema.key?.startsWith("NEXT_PUBLIC_")
|
|
4413
|
+
);
|
|
4414
|
+
const serverOnlyVars = allVars.filter(
|
|
4415
|
+
([_, schema]) => !(schema.nextjs ?? schema.key?.startsWith("NEXT_PUBLIC_"))
|
|
4416
|
+
);
|
|
4370
4417
|
const typeCount = allVars.reduce((acc, [_, schema]) => {
|
|
4371
4418
|
acc[schema.type] = (acc[schema.type] || 0) + 1;
|
|
4372
4419
|
return acc;
|
|
4373
4420
|
}, {});
|
|
4421
|
+
const fileCount = allVars.reduce((acc, [_, schema]) => {
|
|
4422
|
+
const file = getTargetFile(schema);
|
|
4423
|
+
acc[file] = (acc[file] || 0) + 1;
|
|
4424
|
+
return acc;
|
|
4425
|
+
}, {});
|
|
4374
4426
|
console.log(`${chalk26.bold("Total variables:")} ${chalk26.cyan(allVars.length)}`);
|
|
4375
4427
|
console.log(`${chalk26.bold("Required:")} ${chalk26.red(required.length)}`);
|
|
4376
4428
|
console.log(`${chalk26.bold("Optional:")} ${chalk26.dim(optional.length)}`);
|
|
4377
4429
|
console.log(`${chalk26.bold("Sensitive:")} ${chalk26.yellow(sensitive.length)}`);
|
|
4430
|
+
console.log(chalk26.bold("\nBy Target:"));
|
|
4431
|
+
console.log(` ${chalk26.blue("Next.js accessible:")} ${chalk26.cyan(nextjsVars.length)}`);
|
|
4432
|
+
console.log(` ${chalk26.magenta("SPFN server only:")} ${chalk26.cyan(serverOnlyVars.length)}`);
|
|
4433
|
+
console.log(chalk26.bold("\nBy File:"));
|
|
4434
|
+
for (const [file, count] of Object.entries(fileCount)) {
|
|
4435
|
+
console.log(` ${chalk26.dim(file)}: ${chalk26.cyan(count)}`);
|
|
4436
|
+
}
|
|
4378
4437
|
console.log(chalk26.bold("\nBy Type:"));
|
|
4379
4438
|
for (const [type, count] of Object.entries(typeCount)) {
|
|
4380
4439
|
console.log(` ${formatType(type)}: ${chalk26.cyan(count)}`);
|
|
@@ -4427,9 +4486,160 @@ async function searchEnvVars(query, options) {
|
|
|
4427
4486
|
}
|
|
4428
4487
|
}
|
|
4429
4488
|
var envCommand = new Command12("env").description("Manage environment variables");
|
|
4430
|
-
envCommand.command("list").description("List all environment variables from schema").option("-p, --package <package>", "Package name to read env schema from", "@spfn/core").action(listEnvVars);
|
|
4489
|
+
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);
|
|
4431
4490
|
envCommand.command("stats").description("Show environment variable statistics").option("-p, --package <package>", "Package name to read env schema from", "@spfn/core").action(showEnvStats);
|
|
4432
4491
|
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);
|
|
4492
|
+
async function initEnvFiles(options) {
|
|
4493
|
+
const packageName = options.package || "@spfn/core";
|
|
4494
|
+
const cwd = process.cwd();
|
|
4495
|
+
try {
|
|
4496
|
+
const envSchema = await loadEnvSchema(packageName);
|
|
4497
|
+
const allVars = Object.entries(envSchema);
|
|
4498
|
+
const grouped = allVars.reduce((acc, [key, schema]) => {
|
|
4499
|
+
const target = getTargetFile(schema);
|
|
4500
|
+
const exampleFile = target + ".example";
|
|
4501
|
+
if (!acc[exampleFile]) acc[exampleFile] = [];
|
|
4502
|
+
acc[exampleFile].push([key, schema]);
|
|
4503
|
+
return acc;
|
|
4504
|
+
}, {});
|
|
4505
|
+
console.log(chalk26.blue.bold(`
|
|
4506
|
+
\u{1F680} Generating .env template files
|
|
4507
|
+
`));
|
|
4508
|
+
for (const [file, vars] of Object.entries(grouped)) {
|
|
4509
|
+
const filePath = resolve(cwd, file);
|
|
4510
|
+
if (existsSync23(filePath) && !options.force) {
|
|
4511
|
+
console.log(chalk26.yellow(` \u23ED\uFE0F ${file} already exists (use --force to overwrite)`));
|
|
4512
|
+
continue;
|
|
4513
|
+
}
|
|
4514
|
+
const content = generateEnvFileContent(vars);
|
|
4515
|
+
writeFileSync18(filePath, content, "utf-8");
|
|
4516
|
+
console.log(chalk26.green(` \u2705 ${file} (${vars.length} variables)`));
|
|
4517
|
+
}
|
|
4518
|
+
console.log(chalk26.dim("\n\u{1F4A1} Copy .example files to create your actual .env files:"));
|
|
4519
|
+
console.log(chalk26.dim(" cp .env.example .env"));
|
|
4520
|
+
console.log(chalk26.dim(" cp .env.local.example .env.local"));
|
|
4521
|
+
console.log(chalk26.dim(" cp .env.server.example .env.server"));
|
|
4522
|
+
console.log(chalk26.dim(" cp .env.server.local.example .env.server.local\n"));
|
|
4523
|
+
} catch (error) {
|
|
4524
|
+
console.error(chalk26.red(`
|
|
4525
|
+
\u274C ${error instanceof Error ? error.message : "Unknown error"}
|
|
4526
|
+
`));
|
|
4527
|
+
process.exit(1);
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
function generateEnvFileContent(vars) {
|
|
4531
|
+
const lines = [
|
|
4532
|
+
"# Auto-generated by spfn env init",
|
|
4533
|
+
"# Copy this file and fill in the values",
|
|
4534
|
+
""
|
|
4535
|
+
];
|
|
4536
|
+
for (const [key, schema] of vars) {
|
|
4537
|
+
lines.push(`# ${schema.description}`);
|
|
4538
|
+
if (schema.required) {
|
|
4539
|
+
lines.push(`# [required]`);
|
|
4540
|
+
}
|
|
4541
|
+
if (schema.sensitive) {
|
|
4542
|
+
lines.push(`# [sensitive] - Do not commit this value!`);
|
|
4543
|
+
}
|
|
4544
|
+
let value = "";
|
|
4545
|
+
if (schema.default !== void 0) {
|
|
4546
|
+
value = String(schema.default);
|
|
4547
|
+
} else if (schema.examples && schema.examples.length > 0) {
|
|
4548
|
+
value = String(schema.examples[0]);
|
|
4549
|
+
}
|
|
4550
|
+
lines.push(`${key}=${value}`);
|
|
4551
|
+
lines.push("");
|
|
4552
|
+
}
|
|
4553
|
+
return lines.join("\n");
|
|
4554
|
+
}
|
|
4555
|
+
async function checkEnvFiles(options) {
|
|
4556
|
+
const packageName = options.package || "@spfn/core";
|
|
4557
|
+
const cwd = process.cwd();
|
|
4558
|
+
try {
|
|
4559
|
+
const envSchema = await loadEnvSchema(packageName);
|
|
4560
|
+
const allVars = Object.entries(envSchema);
|
|
4561
|
+
console.log(chalk26.blue.bold(`
|
|
4562
|
+
\u{1F50D} Checking .env files against schema
|
|
4563
|
+
`));
|
|
4564
|
+
const allFiles = [...ENV_FILES.nextjs, ...ENV_FILES.server];
|
|
4565
|
+
const loadedEnv = {};
|
|
4566
|
+
const issues = [];
|
|
4567
|
+
const warnings = [];
|
|
4568
|
+
for (const file of allFiles) {
|
|
4569
|
+
const filePath = resolve(cwd, file);
|
|
4570
|
+
if (!existsSync23(filePath)) {
|
|
4571
|
+
continue;
|
|
4572
|
+
}
|
|
4573
|
+
const content = readFileSync8(filePath, "utf-8");
|
|
4574
|
+
const parsed = parse(content);
|
|
4575
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
4576
|
+
loadedEnv[key] = { value: value || "", file };
|
|
4577
|
+
}
|
|
4578
|
+
console.log(chalk26.dim(` \u{1F4C4} ${file} loaded`));
|
|
4579
|
+
}
|
|
4580
|
+
console.log("");
|
|
4581
|
+
for (const [key, schema] of allVars) {
|
|
4582
|
+
const expectedFile = getTargetFile(schema);
|
|
4583
|
+
const found = loadedEnv[key];
|
|
4584
|
+
if (!found) {
|
|
4585
|
+
if (schema.required && schema.default === void 0) {
|
|
4586
|
+
issues.push(`${chalk26.red("\u2717")} ${chalk26.cyan(key)} is required but not found in any .env file`);
|
|
4587
|
+
}
|
|
4588
|
+
continue;
|
|
4589
|
+
}
|
|
4590
|
+
const isNextjsFile = ENV_FILES.nextjs.includes(found.file);
|
|
4591
|
+
const isServerFile = ENV_FILES.server.includes(found.file);
|
|
4592
|
+
const shouldBeNextjs = schema.nextjs ?? key.startsWith("NEXT_PUBLIC_");
|
|
4593
|
+
if (!shouldBeNextjs && isNextjsFile && !isServerFile) {
|
|
4594
|
+
if (schema.sensitive) {
|
|
4595
|
+
issues.push(
|
|
4596
|
+
`${chalk26.red("\u2717")} ${chalk26.cyan(key)} is sensitive and should be in ${chalk26.magenta(expectedFile)}, but found in ${chalk26.yellow(found.file)} (security risk!)`
|
|
4597
|
+
);
|
|
4598
|
+
} else {
|
|
4599
|
+
warnings.push(
|
|
4600
|
+
`${chalk26.yellow("\u26A0")} ${chalk26.cyan(key)} should be in ${chalk26.magenta(expectedFile)}, but found in ${chalk26.dim(found.file)}`
|
|
4601
|
+
);
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
}
|
|
4605
|
+
for (const [key, { file }] of Object.entries(loadedEnv)) {
|
|
4606
|
+
const inSchema = allVars.some(([k]) => k === key);
|
|
4607
|
+
if (!inSchema) {
|
|
4608
|
+
warnings.push(`${chalk26.yellow("\u26A0")} ${chalk26.cyan(key)} in ${chalk26.dim(file)} is not in schema`);
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
if (issues.length > 0) {
|
|
4612
|
+
console.log(chalk26.red.bold("Issues:"));
|
|
4613
|
+
for (const issue of issues) {
|
|
4614
|
+
console.log(` ${issue}`);
|
|
4615
|
+
}
|
|
4616
|
+
console.log("");
|
|
4617
|
+
}
|
|
4618
|
+
if (warnings.length > 0) {
|
|
4619
|
+
console.log(chalk26.yellow.bold("Warnings:"));
|
|
4620
|
+
for (const warning of warnings) {
|
|
4621
|
+
console.log(` ${warning}`);
|
|
4622
|
+
}
|
|
4623
|
+
console.log("");
|
|
4624
|
+
}
|
|
4625
|
+
if (issues.length === 0 && warnings.length === 0) {
|
|
4626
|
+
console.log(chalk26.green("\u2705 All environment variables are correctly configured!\n"));
|
|
4627
|
+
} else {
|
|
4628
|
+
console.log(chalk26.dim(`Found ${issues.length} issue(s) and ${warnings.length} warning(s)
|
|
4629
|
+
`));
|
|
4630
|
+
if (issues.length > 0) {
|
|
4631
|
+
process.exit(1);
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
} catch (error) {
|
|
4635
|
+
console.error(chalk26.red(`
|
|
4636
|
+
\u274C ${error instanceof Error ? error.message : "Unknown error"}
|
|
4637
|
+
`));
|
|
4638
|
+
process.exit(1);
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
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);
|
|
4642
|
+
envCommand.command("check").description("Check .env files against schema").option("-p, --package <package>", "Package name to read env schema from", "@spfn/core").action(checkEnvFiles);
|
|
4433
4643
|
|
|
4434
4644
|
// src/index.ts
|
|
4435
4645
|
var program = new Command13();
|
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.2",
|
|
4
4
|
"description": "Superfunction CLI - Add SPFN to your Next.js project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"chalk": "^5.6.2",
|
|
59
59
|
"chokidar": "^4.0.3",
|
|
60
60
|
"commander": "^11.1.0",
|
|
61
|
+
"dotenv": "^17.2.3",
|
|
61
62
|
"drizzle-orm": "^0.44.7",
|
|
62
63
|
"execa": "^8.0.1",
|
|
63
64
|
"fs-extra": "^11.2.0",
|
|
@@ -66,7 +67,7 @@
|
|
|
66
67
|
"postgres": "^3.4.0",
|
|
67
68
|
"prompts": "^2.4.2",
|
|
68
69
|
"tsup": "^8.5.0",
|
|
69
|
-
"@spfn/core": "0.2.0-beta.
|
|
70
|
+
"@spfn/core": "0.2.0-beta.4"
|
|
70
71
|
},
|
|
71
72
|
"devDependencies": {
|
|
72
73
|
"@types/fs-extra": "^11.0.4",
|
|
@@ -74,7 +75,6 @@
|
|
|
74
75
|
"@types/pg": "^8.10.9",
|
|
75
76
|
"@types/prompts": "^2.4.9",
|
|
76
77
|
"concurrently": "^9.2.1",
|
|
77
|
-
"dotenv": "^17.2.3",
|
|
78
78
|
"drizzle-kit": "^0.31.6",
|
|
79
79
|
"tsx": "^4.20.6",
|
|
80
80
|
"typescript": "^5.3.3"
|