spfn 0.2.0-beta.21 → 0.2.0-beta.23
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 +659 -344
- package/package.json +3 -3
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.23";
|
|
759
759
|
}
|
|
760
760
|
function getTagFromVersion(version) {
|
|
761
761
|
const match = version.match(/-([a-z]+)\./i);
|
|
@@ -1042,7 +1042,7 @@ __export(function_migrations_exports, {
|
|
|
1042
1042
|
discoverFunctionMigrations: () => discoverFunctionMigrations,
|
|
1043
1043
|
executeFunctionMigrations: () => executeFunctionMigrations
|
|
1044
1044
|
});
|
|
1045
|
-
import
|
|
1045
|
+
import chalk12 from "chalk";
|
|
1046
1046
|
import { join as join15 } from "path";
|
|
1047
1047
|
import { env as env2 } from "@spfn/core/config";
|
|
1048
1048
|
import { loadEnv as loadEnv2 } from "@spfn/core/server";
|
|
@@ -1073,7 +1073,7 @@ function discoverFunctionMigrations(cwd = process.cwd()) {
|
|
|
1073
1073
|
const migrationsDir = join15(packagePath, spfnConfig.migrations.dir);
|
|
1074
1074
|
if (!existsSync16(migrationsDir)) {
|
|
1075
1075
|
console.warn(
|
|
1076
|
-
|
|
1076
|
+
chalk12.yellow(`\u26A0\uFE0F Package @spfn/${pkg} specifies migrations but directory not found: ${migrationsDir}`)
|
|
1077
1077
|
);
|
|
1078
1078
|
continue;
|
|
1079
1079
|
}
|
|
@@ -1083,7 +1083,7 @@ function discoverFunctionMigrations(cwd = process.cwd()) {
|
|
|
1083
1083
|
packagePath
|
|
1084
1084
|
});
|
|
1085
1085
|
} catch (error) {
|
|
1086
|
-
console.warn(
|
|
1086
|
+
console.warn(chalk12.yellow(`\u26A0\uFE0F Failed to parse package.json for @spfn/${pkg}`));
|
|
1087
1087
|
}
|
|
1088
1088
|
}
|
|
1089
1089
|
return functions;
|
|
@@ -1101,13 +1101,13 @@ async function executeFunctionMigrations(functionMigrations) {
|
|
|
1101
1101
|
const db = drizzle(connection);
|
|
1102
1102
|
try {
|
|
1103
1103
|
for (const func of functionMigrations) {
|
|
1104
|
-
console.log(
|
|
1104
|
+
console.log(chalk12.blue(`
|
|
1105
1105
|
\u{1F4E6} Running ${func.packageName} migrations...`));
|
|
1106
1106
|
await migrate(db, {
|
|
1107
1107
|
migrationsFolder: func.migrationsDir,
|
|
1108
1108
|
migrationsTable: "__spfn_fn_migrations"
|
|
1109
1109
|
});
|
|
1110
|
-
console.log(
|
|
1110
|
+
console.log(chalk12.green(` \u2713 ${func.packageName} migrations applied`));
|
|
1111
1111
|
executedCount++;
|
|
1112
1112
|
}
|
|
1113
1113
|
} finally {
|
|
@@ -2113,6 +2113,7 @@ import chalk10 from "chalk";
|
|
|
2113
2113
|
// src/commands/db/utils/drizzle.ts
|
|
2114
2114
|
import { existsSync as existsSync15, writeFileSync as writeFileSync9, unlinkSync as unlinkSync2 } from "fs";
|
|
2115
2115
|
import { spawn } from "child_process";
|
|
2116
|
+
import { pathToFileURL } from "url";
|
|
2116
2117
|
import chalk9 from "chalk";
|
|
2117
2118
|
import ora6 from "ora";
|
|
2118
2119
|
import { env } from "@spfn/core/config";
|
|
@@ -2188,6 +2189,50 @@ async function runWithSpinner(spinnerText, command, successMessage, failMessage)
|
|
|
2188
2189
|
process.exit(1);
|
|
2189
2190
|
}
|
|
2190
2191
|
}
|
|
2192
|
+
var tsxRegistered = false;
|
|
2193
|
+
async function ensureTsxLoader() {
|
|
2194
|
+
if (tsxRegistered) return;
|
|
2195
|
+
try {
|
|
2196
|
+
const tsx = await import("tsx/esm/api");
|
|
2197
|
+
tsx.register();
|
|
2198
|
+
tsxRegistered = true;
|
|
2199
|
+
} catch {
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
async function loadSchemaImports(schemaFiles) {
|
|
2203
|
+
const hasTsFiles = schemaFiles.some((f) => f.endsWith(".ts"));
|
|
2204
|
+
if (hasTsFiles) {
|
|
2205
|
+
await ensureTsxLoader();
|
|
2206
|
+
}
|
|
2207
|
+
const imports = {};
|
|
2208
|
+
for (const file of schemaFiles) {
|
|
2209
|
+
const moduleUrl = pathToFileURL(file).href;
|
|
2210
|
+
const mod = await import(moduleUrl);
|
|
2211
|
+
for (const [key, value] of Object.entries(mod)) {
|
|
2212
|
+
if (key !== "default") {
|
|
2213
|
+
imports[key] = value;
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
return imports;
|
|
2218
|
+
}
|
|
2219
|
+
async function createPushConnection() {
|
|
2220
|
+
loadEnv();
|
|
2221
|
+
if (!env.DATABASE_URL) {
|
|
2222
|
+
throw new Error("DATABASE_URL is required");
|
|
2223
|
+
}
|
|
2224
|
+
const pg = await import("pg");
|
|
2225
|
+
const { drizzle } = await import("drizzle-orm/node-postgres");
|
|
2226
|
+
const pool = new pg.default.Pool({
|
|
2227
|
+
connectionString: env.DATABASE_URL,
|
|
2228
|
+
max: 1
|
|
2229
|
+
});
|
|
2230
|
+
const db = drizzle(pool);
|
|
2231
|
+
return {
|
|
2232
|
+
db,
|
|
2233
|
+
close: () => pool.end()
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2191
2236
|
|
|
2192
2237
|
// src/commands/db/generate.ts
|
|
2193
2238
|
async function dbGenerate() {
|
|
@@ -2202,35 +2247,203 @@ async function dbGenerate() {
|
|
|
2202
2247
|
}
|
|
2203
2248
|
|
|
2204
2249
|
// src/commands/db/push.ts
|
|
2205
|
-
import
|
|
2250
|
+
import chalk13 from "chalk";
|
|
2251
|
+
import prompts3 from "prompts";
|
|
2206
2252
|
import "@spfn/core/config";
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2253
|
+
import { loadEnv as loadEnv3 } from "@spfn/core/server";
|
|
2254
|
+
import { sql } from "drizzle-orm";
|
|
2255
|
+
|
|
2256
|
+
// src/commands/db/utils/sql-classifier.ts
|
|
2257
|
+
var DESTRUCTIVE_PATTERNS = [
|
|
2258
|
+
{ pattern: /^\s*DROP\s+TABLE/i, reason: "Drops entire table" },
|
|
2259
|
+
{ pattern: /^\s*DROP\s+INDEX/i, reason: "Drops index" },
|
|
2260
|
+
{ pattern: /^\s*DROP\s+SCHEMA/i, reason: "Drops schema" },
|
|
2261
|
+
{ pattern: /^\s*DROP\s+TYPE/i, reason: "Drops custom type" },
|
|
2262
|
+
{ pattern: /ALTER\s+TABLE\s+.*\bDROP\s+COLUMN\b/i, reason: "Drops column" },
|
|
2263
|
+
{ pattern: /ALTER\s+TABLE\s+.*\bDROP\s+CONSTRAINT\b/i, reason: "Drops constraint" },
|
|
2264
|
+
{ pattern: /ALTER\s+TABLE\s+.*ALTER\s+COLUMN\s+.*\bTYPE\b/i, reason: "Changes column type (potential data loss)" },
|
|
2265
|
+
{ pattern: /ALTER\s+TYPE\s+.*\bRENAME\s+VALUE\b/i, reason: "Renames enum value" },
|
|
2266
|
+
{ pattern: /^\s*TRUNCATE\b/i, reason: "Truncates table data" },
|
|
2267
|
+
{ pattern: /^\s*DELETE\s+FROM\b/i, reason: "Deletes table data" }
|
|
2268
|
+
];
|
|
2269
|
+
var WARNING_PATTERNS = [
|
|
2270
|
+
{ pattern: /ALTER\s+TABLE\s+.*ALTER\s+COLUMN\s+.*\bSET\s+NOT\s+NULL\b/i, reason: "Adds NOT NULL (fails if NULLs exist)" },
|
|
2271
|
+
{ pattern: /ALTER\s+TABLE\s+.*\bRENAME\s+COLUMN\b/i, reason: "Renames column" }
|
|
2272
|
+
];
|
|
2273
|
+
function classifyStatement(sql2) {
|
|
2274
|
+
for (const { pattern, reason } of DESTRUCTIVE_PATTERNS) {
|
|
2275
|
+
if (pattern.test(sql2)) {
|
|
2276
|
+
return { sql: sql2, category: "destructive", reason };
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
for (const { pattern, reason } of WARNING_PATTERNS) {
|
|
2280
|
+
if (pattern.test(sql2)) {
|
|
2281
|
+
return { sql: sql2, category: "warning", reason };
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
return { sql: sql2, category: "safe", reason: "" };
|
|
2285
|
+
}
|
|
2286
|
+
function classifyStatements(statements) {
|
|
2287
|
+
const result = { safe: [], destructive: [], warning: [] };
|
|
2288
|
+
for (const sql2 of statements) {
|
|
2289
|
+
const classified = classifyStatement(sql2);
|
|
2290
|
+
result[classified.category].push(classified);
|
|
2291
|
+
}
|
|
2292
|
+
return result;
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
// src/commands/db/utils/push-display.ts
|
|
2296
|
+
import chalk11 from "chalk";
|
|
2297
|
+
function formatSql(sql2) {
|
|
2298
|
+
return sql2.replace(/\s+/g, " ").trim();
|
|
2299
|
+
}
|
|
2300
|
+
function printStatements(statements, color) {
|
|
2301
|
+
for (const stmt of statements) {
|
|
2302
|
+
console.log(color(` ${formatSql(stmt.sql)}`));
|
|
2303
|
+
if (stmt.reason) {
|
|
2304
|
+
console.log(chalk11.dim(` \u2192 ${stmt.reason}`));
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
function displayClassifiedStatements(result) {
|
|
2309
|
+
if (result.safe.length > 0) {
|
|
2310
|
+
console.log(chalk11.green(`
|
|
2311
|
+
\u2705 Safe changes (${result.safe.length}):`));
|
|
2312
|
+
printStatements(result.safe, chalk11.green);
|
|
2313
|
+
}
|
|
2314
|
+
if (result.warning.length > 0) {
|
|
2315
|
+
console.log(chalk11.yellow(`
|
|
2316
|
+
\u26A0\uFE0F Warnings (${result.warning.length}):`));
|
|
2317
|
+
printStatements(result.warning, chalk11.yellow);
|
|
2318
|
+
}
|
|
2319
|
+
if (result.destructive.length > 0) {
|
|
2320
|
+
console.log(chalk11.red(`
|
|
2321
|
+
\u274C Destructive changes (${result.destructive.length}):`));
|
|
2322
|
+
printStatements(result.destructive, chalk11.red);
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
function displayDryRunSummary(result) {
|
|
2326
|
+
const total = result.safe.length + result.warning.length + result.destructive.length;
|
|
2327
|
+
console.log(chalk11.cyan(`
|
|
2328
|
+
\u{1F50D} Dry-run: ${total} statement(s) detected
|
|
2329
|
+
`));
|
|
2330
|
+
displayClassifiedStatements(result);
|
|
2331
|
+
console.log("");
|
|
2332
|
+
}
|
|
2333
|
+
function displayApplySummary(applied, skipped) {
|
|
2334
|
+
if (applied > 0) {
|
|
2335
|
+
console.log(chalk11.green(`
|
|
2336
|
+
\u2705 Applied ${applied} statement(s)`));
|
|
2337
|
+
}
|
|
2338
|
+
if (skipped > 0) {
|
|
2339
|
+
console.log(chalk11.yellow(`\u23ED\uFE0F Skipped ${skipped} destructive statement(s)`));
|
|
2340
|
+
}
|
|
2341
|
+
console.log("");
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
// src/commands/db/push.ts
|
|
2345
|
+
async function dbPush(options = {}) {
|
|
2346
|
+
validateDatabasePrerequisites();
|
|
2347
|
+
loadEnv3();
|
|
2348
|
+
const { getDrizzleConfig } = await import("@spfn/core/db");
|
|
2349
|
+
const config = getDrizzleConfig({
|
|
2350
|
+
cwd: process.cwd(),
|
|
2351
|
+
expandGlobs: true,
|
|
2352
|
+
autoDetectSchemas: true,
|
|
2353
|
+
disablePackageDiscovery: true
|
|
2354
|
+
});
|
|
2355
|
+
const schemaFiles = Array.isArray(config.schema) ? config.schema : [config.schema];
|
|
2356
|
+
if (schemaFiles.length === 0) {
|
|
2357
|
+
console.log(chalk13.yellow("No schema files found."));
|
|
2358
|
+
return;
|
|
2359
|
+
}
|
|
2360
|
+
console.log(chalk13.dim(`Found ${schemaFiles.length} schema file(s)
|
|
2361
|
+
`));
|
|
2362
|
+
const imports = await loadSchemaImports(schemaFiles);
|
|
2363
|
+
const { db, close } = await createPushConnection();
|
|
2364
|
+
try {
|
|
2365
|
+
const { pushSchema } = await import("drizzle-kit/api");
|
|
2366
|
+
const { statementsToExecute, apply } = await pushSchema(
|
|
2367
|
+
imports,
|
|
2368
|
+
db,
|
|
2369
|
+
config.schemaFilter ?? ["public"]
|
|
2370
|
+
);
|
|
2371
|
+
if (statementsToExecute.length === 0) {
|
|
2372
|
+
console.log(chalk13.green("\u2705 No changes detected \u2014 database is up to date\n"));
|
|
2373
|
+
await applyFunctionMigrations();
|
|
2374
|
+
return;
|
|
2375
|
+
}
|
|
2376
|
+
const result = classifyStatements(statementsToExecute);
|
|
2377
|
+
if (options.dryRun) {
|
|
2378
|
+
displayDryRunSummary(result);
|
|
2379
|
+
return;
|
|
2380
|
+
}
|
|
2381
|
+
displayClassifiedStatements(result);
|
|
2382
|
+
if (options.force) {
|
|
2383
|
+
console.log(chalk13.dim("\n--force: applying all changes..."));
|
|
2384
|
+
await apply();
|
|
2385
|
+
displayApplySummary(statementsToExecute.length, 0);
|
|
2386
|
+
} else if (result.destructive.length === 0) {
|
|
2387
|
+
await apply();
|
|
2388
|
+
displayApplySummary(statementsToExecute.length, 0);
|
|
2389
|
+
} else {
|
|
2390
|
+
const safeCount = result.safe.length + result.warning.length;
|
|
2391
|
+
if (safeCount > 0) {
|
|
2392
|
+
for (const stmt of [...result.safe, ...result.warning]) {
|
|
2393
|
+
await db.execute(sql.raw(stmt.sql));
|
|
2394
|
+
}
|
|
2395
|
+
console.log(chalk13.green(`
|
|
2396
|
+
\u2705 Applied ${safeCount} safe statement(s)`));
|
|
2397
|
+
}
|
|
2398
|
+
console.log(chalk13.red(`
|
|
2399
|
+
\u274C ${result.destructive.length} destructive change(s) require confirmation:`));
|
|
2400
|
+
for (const stmt of result.destructive) {
|
|
2401
|
+
console.log(chalk13.red(` ${stmt.sql.replace(/\s+/g, " ").trim()}`));
|
|
2402
|
+
console.log(chalk13.dim(` \u2192 ${stmt.reason}`));
|
|
2403
|
+
}
|
|
2404
|
+
const { confirm } = await prompts3({
|
|
2405
|
+
type: "confirm",
|
|
2406
|
+
name: "confirm",
|
|
2407
|
+
message: "Apply destructive changes?",
|
|
2408
|
+
initial: false
|
|
2409
|
+
});
|
|
2410
|
+
if (confirm) {
|
|
2411
|
+
for (const stmt of result.destructive) {
|
|
2412
|
+
await db.execute(sql.raw(stmt.sql));
|
|
2413
|
+
}
|
|
2414
|
+
displayApplySummary(statementsToExecute.length, 0);
|
|
2415
|
+
} else {
|
|
2416
|
+
displayApplySummary(safeCount, result.destructive.length);
|
|
2417
|
+
console.log(chalk13.dim("Tip: Use --force to apply all changes without prompting.\n"));
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
await applyFunctionMigrations();
|
|
2421
|
+
} finally {
|
|
2422
|
+
await close();
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
async function applyFunctionMigrations() {
|
|
2214
2426
|
const { discoverFunctionMigrations: discoverFunctionMigrations2, executeFunctionMigrations: executeFunctionMigrations2 } = await Promise.resolve().then(() => (init_function_migrations(), function_migrations_exports));
|
|
2215
2427
|
const functions = discoverFunctionMigrations2(process.cwd());
|
|
2216
|
-
if (functions.length
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2428
|
+
if (functions.length === 0) {
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
console.log(chalk13.blue("\n\u{1F4E6} Applying function package migrations:"));
|
|
2432
|
+
functions.forEach((func) => {
|
|
2433
|
+
console.log(chalk13.dim(` - ${func.packageName}`));
|
|
2434
|
+
});
|
|
2435
|
+
try {
|
|
2436
|
+
await executeFunctionMigrations2(functions);
|
|
2437
|
+
console.log(chalk13.green("\n\u2705 All function migrations applied\n"));
|
|
2438
|
+
} catch (error) {
|
|
2439
|
+
console.error(chalk13.red("\n\u274C Failed to apply function migrations"));
|
|
2440
|
+
console.error(chalk13.red(error instanceof Error ? error.message : "Unknown error"));
|
|
2441
|
+
process.exit(1);
|
|
2229
2442
|
}
|
|
2230
2443
|
}
|
|
2231
2444
|
|
|
2232
2445
|
// src/commands/db/migrate.ts
|
|
2233
|
-
import
|
|
2446
|
+
import chalk17 from "chalk";
|
|
2234
2447
|
import { join as join16 } from "path";
|
|
2235
2448
|
import { existsSync as existsSync18 } from "fs";
|
|
2236
2449
|
|
|
@@ -2238,7 +2451,7 @@ import { existsSync as existsSync18 } from "fs";
|
|
|
2238
2451
|
import { promises as fs3 } from "fs";
|
|
2239
2452
|
import path3 from "path";
|
|
2240
2453
|
import { spawn as spawn2 } from "child_process";
|
|
2241
|
-
import
|
|
2454
|
+
import chalk16 from "chalk";
|
|
2242
2455
|
import ora7 from "ora";
|
|
2243
2456
|
|
|
2244
2457
|
// src/commands/db/utils/database.ts
|
|
@@ -2307,14 +2520,14 @@ function formatTimestamp() {
|
|
|
2307
2520
|
import { promises as fs2 } from "fs";
|
|
2308
2521
|
import { existsSync as existsSync17 } from "fs";
|
|
2309
2522
|
import path2 from "path";
|
|
2310
|
-
import
|
|
2523
|
+
import chalk15 from "chalk";
|
|
2311
2524
|
|
|
2312
2525
|
// src/commands/db/utils/metadata.ts
|
|
2313
2526
|
import { promises as fs } from "fs";
|
|
2314
2527
|
import path from "path";
|
|
2315
2528
|
import { promisify } from "util";
|
|
2316
2529
|
import { exec } from "child_process";
|
|
2317
|
-
import
|
|
2530
|
+
import chalk14 from "chalk";
|
|
2318
2531
|
var execAsync = promisify(exec);
|
|
2319
2532
|
async function collectGitInfo() {
|
|
2320
2533
|
try {
|
|
@@ -2377,7 +2590,7 @@ async function collectMigrationInfo(dbUrl) {
|
|
|
2377
2590
|
await pool.end();
|
|
2378
2591
|
}
|
|
2379
2592
|
} catch (error) {
|
|
2380
|
-
console.log(
|
|
2593
|
+
console.log(chalk14.dim("\u26A0\uFE0F Could not fetch migration info"));
|
|
2381
2594
|
return void 0;
|
|
2382
2595
|
}
|
|
2383
2596
|
}
|
|
@@ -2385,9 +2598,9 @@ async function saveBackupMetadata(metadata, backupFilename) {
|
|
|
2385
2598
|
const metadataPath = backupFilename.replace(/\.(sql|dump)$/, ".meta.json");
|
|
2386
2599
|
try {
|
|
2387
2600
|
await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
|
|
2388
|
-
console.log(
|
|
2601
|
+
console.log(chalk14.dim(`\u2713 Metadata saved: ${path.basename(metadataPath)}`));
|
|
2389
2602
|
} catch (error) {
|
|
2390
|
-
console.log(
|
|
2603
|
+
console.log(chalk14.dim("\u26A0\uFE0F Could not save metadata"));
|
|
2391
2604
|
}
|
|
2392
2605
|
}
|
|
2393
2606
|
async function loadBackupMetadata(backupFilename) {
|
|
@@ -2416,10 +2629,10 @@ async function ensureBackupInGitignore() {
|
|
|
2416
2629
|
if (!hasBackupsIgnore) {
|
|
2417
2630
|
const entry = exists && content && !content.endsWith("\n") ? "\n\n# Database backups\nbackups/\n" : "# Database backups\nbackups/\n";
|
|
2418
2631
|
await fs2.appendFile(gitignorePath, entry);
|
|
2419
|
-
console.log(
|
|
2632
|
+
console.log(chalk15.dim("\u2713 Added backups/ to .gitignore"));
|
|
2420
2633
|
}
|
|
2421
2634
|
} catch (error) {
|
|
2422
|
-
console.log(
|
|
2635
|
+
console.log(chalk15.dim("\u26A0\uFE0F Could not update .gitignore"));
|
|
2423
2636
|
}
|
|
2424
2637
|
}
|
|
2425
2638
|
async function ensureBackupDir() {
|
|
@@ -2467,14 +2680,14 @@ async function listBackupFiles() {
|
|
|
2467
2680
|
|
|
2468
2681
|
// src/commands/db/backup.ts
|
|
2469
2682
|
import { env as env3 } from "@spfn/core/config";
|
|
2470
|
-
import { loadEnv as
|
|
2683
|
+
import { loadEnv as loadEnv4 } from "@spfn/core/server";
|
|
2471
2684
|
async function dbBackup(options) {
|
|
2472
|
-
console.log(
|
|
2473
|
-
|
|
2685
|
+
console.log(chalk16.blue("\u{1F4BE} Creating database backup...\n"));
|
|
2686
|
+
loadEnv4();
|
|
2474
2687
|
const dbUrl = env3.DATABASE_URL;
|
|
2475
2688
|
if (!dbUrl) {
|
|
2476
|
-
console.error(
|
|
2477
|
-
console.log(
|
|
2689
|
+
console.error(chalk16.red("\u274C DATABASE_URL not found in environment"));
|
|
2690
|
+
console.log(chalk16.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
|
|
2478
2691
|
process.exit(1);
|
|
2479
2692
|
}
|
|
2480
2693
|
const dbInfo = parseDatabaseUrl(dbUrl);
|
|
@@ -2484,7 +2697,7 @@ async function dbBackup(options) {
|
|
|
2484
2697
|
const ext = format === "sql" ? "sql" : "dump";
|
|
2485
2698
|
const filename = options.output || path3.join(backupDir, `${dbInfo.database}_${timestamp}.${ext}`);
|
|
2486
2699
|
if (options.dataOnly && options.schemaOnly) {
|
|
2487
|
-
console.error(
|
|
2700
|
+
console.error(chalk16.red("\u274C Cannot use --data-only and --schema-only together"));
|
|
2488
2701
|
process.exit(1);
|
|
2489
2702
|
}
|
|
2490
2703
|
const args = [
|
|
@@ -2530,11 +2743,11 @@ async function dbBackup(options) {
|
|
|
2530
2743
|
const stats = await fs3.stat(filename);
|
|
2531
2744
|
const size = formatBytes(stats.size);
|
|
2532
2745
|
spinner.succeed("Backup created");
|
|
2533
|
-
console.log(
|
|
2746
|
+
console.log(chalk16.green(`
|
|
2534
2747
|
\u2705 Backup created successfully`));
|
|
2535
|
-
console.log(
|
|
2536
|
-
console.log(
|
|
2537
|
-
console.log(
|
|
2748
|
+
console.log(chalk16.gray(` File: ${filename}`));
|
|
2749
|
+
console.log(chalk16.gray(` Size: ${size}`));
|
|
2750
|
+
console.log(chalk16.dim("\n\u{1F4CB} Collecting metadata..."));
|
|
2538
2751
|
const [gitInfo, migrationInfo] = await Promise.all([
|
|
2539
2752
|
collectGitInfo(),
|
|
2540
2753
|
collectMigrationInfo(dbUrl)
|
|
@@ -2574,11 +2787,11 @@ async function dbBackup(options) {
|
|
|
2574
2787
|
reject(error);
|
|
2575
2788
|
});
|
|
2576
2789
|
}).catch((error) => {
|
|
2577
|
-
console.error(
|
|
2790
|
+
console.error(chalk16.red("\n\u274C Failed to create backup"));
|
|
2578
2791
|
if (errorOutput.includes("pg_dump: command not found") || errorOutput.includes("not found")) {
|
|
2579
|
-
console.error(
|
|
2792
|
+
console.error(chalk16.yellow("\n\u{1F4A1} pg_dump is not installed. Please install PostgreSQL client tools."));
|
|
2580
2793
|
} else {
|
|
2581
|
-
console.error(
|
|
2794
|
+
console.error(chalk16.red(error instanceof Error ? error.message : "Unknown error"));
|
|
2582
2795
|
}
|
|
2583
2796
|
process.exit(1);
|
|
2584
2797
|
});
|
|
@@ -2586,7 +2799,7 @@ async function dbBackup(options) {
|
|
|
2586
2799
|
|
|
2587
2800
|
// src/commands/db/migrate.ts
|
|
2588
2801
|
import { env as env4 } from "@spfn/core/config";
|
|
2589
|
-
import { loadEnv as
|
|
2802
|
+
import { loadEnv as loadEnv5 } from "@spfn/core/server";
|
|
2590
2803
|
var FUNCTION_MIGRATIONS_TABLE = "__spfn_fn_migrations";
|
|
2591
2804
|
var PROJECT_MIGRATIONS_TABLE = "__drizzle_migrations";
|
|
2592
2805
|
async function dbMigrate(options = {}) {
|
|
@@ -2596,7 +2809,7 @@ async function dbMigrate(options = {}) {
|
|
|
2596
2809
|
process.exit(1);
|
|
2597
2810
|
}
|
|
2598
2811
|
if (options.withBackup) {
|
|
2599
|
-
console.log(
|
|
2812
|
+
console.log(chalk17.blue("\u{1F4E6} Creating pre-migration backup...\n"));
|
|
2600
2813
|
await dbBackup({
|
|
2601
2814
|
format: "custom",
|
|
2602
2815
|
tag: "pre-migration",
|
|
@@ -2607,9 +2820,9 @@ async function dbMigrate(options = {}) {
|
|
|
2607
2820
|
const { drizzle } = await import("drizzle-orm/postgres-js");
|
|
2608
2821
|
const { migrate } = await import("drizzle-orm/postgres-js/migrator");
|
|
2609
2822
|
const postgres = await import("postgres");
|
|
2610
|
-
|
|
2823
|
+
loadEnv5();
|
|
2611
2824
|
if (!env4.DATABASE_URL) {
|
|
2612
|
-
console.error(
|
|
2825
|
+
console.error(chalk17.red("\u274C DATABASE_URL not found in environment"));
|
|
2613
2826
|
process.exit(1);
|
|
2614
2827
|
}
|
|
2615
2828
|
const { discoverFunctionMigrations: discoverFunctionMigrations2 } = await Promise.resolve().then(() => (init_function_migrations(), function_migrations_exports));
|
|
@@ -2618,20 +2831,20 @@ async function dbMigrate(options = {}) {
|
|
|
2618
2831
|
const fnConn = postgres.default(env4.DATABASE_URL, { max: 1 });
|
|
2619
2832
|
const fnDb = drizzle(fnConn);
|
|
2620
2833
|
try {
|
|
2621
|
-
console.log(
|
|
2834
|
+
console.log(chalk17.blue("\u{1F4E6} Applying function package migrations:"));
|
|
2622
2835
|
functions.forEach((func) => {
|
|
2623
|
-
console.log(
|
|
2836
|
+
console.log(chalk17.dim(` - ${func.packageName}`));
|
|
2624
2837
|
});
|
|
2625
2838
|
for (const func of functions) {
|
|
2626
|
-
console.log(
|
|
2839
|
+
console.log(chalk17.blue(`
|
|
2627
2840
|
\u{1F4E6} Running ${func.packageName} migrations...`));
|
|
2628
2841
|
await migrate(fnDb, {
|
|
2629
2842
|
migrationsFolder: func.migrationsDir,
|
|
2630
2843
|
migrationsTable: FUNCTION_MIGRATIONS_TABLE
|
|
2631
2844
|
});
|
|
2632
|
-
console.log(
|
|
2845
|
+
console.log(chalk17.green(` \u2713 ${func.packageName} migrations applied`));
|
|
2633
2846
|
}
|
|
2634
|
-
console.log(
|
|
2847
|
+
console.log(chalk17.green("\u2705 Function migrations applied\n"));
|
|
2635
2848
|
} finally {
|
|
2636
2849
|
await fnConn.end();
|
|
2637
2850
|
}
|
|
@@ -2641,39 +2854,39 @@ async function dbMigrate(options = {}) {
|
|
|
2641
2854
|
const projConn = postgres.default(env4.DATABASE_URL, { max: 1 });
|
|
2642
2855
|
const projDb = drizzle(projConn);
|
|
2643
2856
|
try {
|
|
2644
|
-
console.log(
|
|
2857
|
+
console.log(chalk17.blue("\u{1F4E6} Running project migrations..."));
|
|
2645
2858
|
await migrate(projDb, {
|
|
2646
2859
|
migrationsFolder: projectMigrationsDir,
|
|
2647
2860
|
migrationsTable: PROJECT_MIGRATIONS_TABLE
|
|
2648
2861
|
});
|
|
2649
|
-
console.log(
|
|
2862
|
+
console.log(chalk17.green("\u2705 Project migrations applied successfully"));
|
|
2650
2863
|
} finally {
|
|
2651
2864
|
await projConn.end();
|
|
2652
2865
|
}
|
|
2653
2866
|
} else {
|
|
2654
|
-
console.log(
|
|
2867
|
+
console.log(chalk17.dim("No project migrations found (src/server/drizzle)"));
|
|
2655
2868
|
}
|
|
2656
2869
|
}
|
|
2657
2870
|
|
|
2658
2871
|
// src/commands/db/studio.ts
|
|
2659
|
-
import
|
|
2872
|
+
import chalk18 from "chalk";
|
|
2660
2873
|
import { existsSync as existsSync19, writeFileSync as writeFileSync10, unlinkSync as unlinkSync3 } from "fs";
|
|
2661
2874
|
import { spawn as spawn3 } from "child_process";
|
|
2662
2875
|
import { env as env5 } from "@spfn/core/config";
|
|
2663
2876
|
import "@spfn/core/config";
|
|
2664
2877
|
async function dbStudio(requestedPort) {
|
|
2665
|
-
console.log(
|
|
2878
|
+
console.log(chalk18.blue("\u{1F3A8} Opening Drizzle Studio...\n"));
|
|
2666
2879
|
const defaultPort = 4983;
|
|
2667
2880
|
const startPort = requestedPort || defaultPort;
|
|
2668
2881
|
let port;
|
|
2669
2882
|
try {
|
|
2670
2883
|
port = await findAvailablePort(startPort);
|
|
2671
2884
|
if (port !== startPort) {
|
|
2672
|
-
console.log(
|
|
2885
|
+
console.log(chalk18.yellow(`\u26A0\uFE0F Port ${startPort} is in use, using port ${port} instead
|
|
2673
2886
|
`));
|
|
2674
2887
|
}
|
|
2675
2888
|
} catch (error) {
|
|
2676
|
-
console.error(
|
|
2889
|
+
console.error(chalk18.red(error instanceof Error ? error.message : "Failed to find available port"));
|
|
2677
2890
|
process.exit(1);
|
|
2678
2891
|
}
|
|
2679
2892
|
const hasUserConfig = existsSync19("./drizzle.config.ts");
|
|
@@ -2682,8 +2895,8 @@ async function dbStudio(requestedPort) {
|
|
|
2682
2895
|
const configPath = hasUserConfig ? "./drizzle.config.ts" : tempConfigPath;
|
|
2683
2896
|
if (!hasUserConfig) {
|
|
2684
2897
|
if (!env5.DATABASE_URL) {
|
|
2685
|
-
console.error(
|
|
2686
|
-
console.log(
|
|
2898
|
+
console.error(chalk18.red("\u274C DATABASE_URL not found in environment"));
|
|
2899
|
+
console.log(chalk18.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
|
|
2687
2900
|
process.exit(1);
|
|
2688
2901
|
}
|
|
2689
2902
|
const { generateDrizzleConfigFile } = await import("@spfn/core/db");
|
|
@@ -2694,7 +2907,7 @@ async function dbStudio(requestedPort) {
|
|
|
2694
2907
|
// Expand glob patterns for Studio compatibility
|
|
2695
2908
|
});
|
|
2696
2909
|
writeFileSync10(tempConfigPath, configContent);
|
|
2697
|
-
console.log(
|
|
2910
|
+
console.log(chalk18.dim("Using auto-generated Drizzle config\n"));
|
|
2698
2911
|
}
|
|
2699
2912
|
const studioProcess = spawn3("drizzle-kit", ["studio", `--port=${port}`, `--config=${configPath}`], {
|
|
2700
2913
|
stdio: "inherit",
|
|
@@ -2709,19 +2922,19 @@ async function dbStudio(requestedPort) {
|
|
|
2709
2922
|
studioProcess.on("exit", (code) => {
|
|
2710
2923
|
cleanup();
|
|
2711
2924
|
if (code !== 0 && code !== null) {
|
|
2712
|
-
console.error(
|
|
2925
|
+
console.error(chalk18.red(`
|
|
2713
2926
|
\u274C Drizzle Studio exited with code ${code}`));
|
|
2714
2927
|
process.exit(code);
|
|
2715
2928
|
}
|
|
2716
2929
|
});
|
|
2717
2930
|
studioProcess.on("error", (error) => {
|
|
2718
2931
|
cleanup();
|
|
2719
|
-
console.error(
|
|
2720
|
-
console.error(
|
|
2932
|
+
console.error(chalk18.red("\u274C Failed to start Drizzle Studio"));
|
|
2933
|
+
console.error(chalk18.red(error.message));
|
|
2721
2934
|
process.exit(1);
|
|
2722
2935
|
});
|
|
2723
2936
|
process.on("SIGINT", () => {
|
|
2724
|
-
console.log(
|
|
2937
|
+
console.log(chalk18.yellow("\n\n\u{1F44B} Shutting down Drizzle Studio..."));
|
|
2725
2938
|
studioProcess.kill("SIGTERM");
|
|
2726
2939
|
cleanup();
|
|
2727
2940
|
process.exit(0);
|
|
@@ -2735,25 +2948,25 @@ async function dbStudio(requestedPort) {
|
|
|
2735
2948
|
if (!hasUserConfig && existsSync19(tempConfigPath)) {
|
|
2736
2949
|
unlinkSync3(tempConfigPath);
|
|
2737
2950
|
}
|
|
2738
|
-
console.error(
|
|
2739
|
-
console.error(
|
|
2951
|
+
console.error(chalk18.red("\u274C Failed to start Drizzle Studio"));
|
|
2952
|
+
console.error(chalk18.red(error instanceof Error ? error.message : "Unknown error"));
|
|
2740
2953
|
process.exit(1);
|
|
2741
2954
|
}
|
|
2742
2955
|
}
|
|
2743
2956
|
|
|
2744
2957
|
// src/commands/db/drop.ts
|
|
2745
|
-
import
|
|
2746
|
-
import
|
|
2958
|
+
import chalk19 from "chalk";
|
|
2959
|
+
import prompts4 from "prompts";
|
|
2747
2960
|
async function dbDrop() {
|
|
2748
|
-
console.log(
|
|
2749
|
-
const { confirm } = await
|
|
2961
|
+
console.log(chalk19.yellow("\u26A0\uFE0F WARNING: This will drop all tables in your database!"));
|
|
2962
|
+
const { confirm } = await prompts4({
|
|
2750
2963
|
type: "confirm",
|
|
2751
2964
|
name: "confirm",
|
|
2752
2965
|
message: "Are you sure you want to drop all tables?",
|
|
2753
2966
|
initial: false
|
|
2754
2967
|
});
|
|
2755
2968
|
if (!confirm) {
|
|
2756
|
-
console.log(
|
|
2969
|
+
console.log(chalk19.gray("Cancelled."));
|
|
2757
2970
|
process.exit(0);
|
|
2758
2971
|
}
|
|
2759
2972
|
await runWithSpinner(
|
|
@@ -2765,7 +2978,7 @@ async function dbDrop() {
|
|
|
2765
2978
|
}
|
|
2766
2979
|
|
|
2767
2980
|
// src/commands/db/check.ts
|
|
2768
|
-
import
|
|
2981
|
+
import chalk20 from "chalk";
|
|
2769
2982
|
import ora8 from "ora";
|
|
2770
2983
|
async function dbCheck() {
|
|
2771
2984
|
const spinner = ora8("Checking database connection...").start();
|
|
@@ -2774,7 +2987,7 @@ async function dbCheck() {
|
|
|
2774
2987
|
spinner.succeed("Database connection OK");
|
|
2775
2988
|
} catch (error) {
|
|
2776
2989
|
spinner.fail("Database connection failed");
|
|
2777
|
-
console.error(
|
|
2990
|
+
console.error(chalk20.red(error instanceof Error ? error.message : "Unknown error"));
|
|
2778
2991
|
process.exit(1);
|
|
2779
2992
|
}
|
|
2780
2993
|
}
|
|
@@ -2782,28 +2995,28 @@ async function dbCheck() {
|
|
|
2782
2995
|
// src/commands/db/restore.ts
|
|
2783
2996
|
import path4 from "path";
|
|
2784
2997
|
import { spawn as spawn4 } from "child_process";
|
|
2785
|
-
import
|
|
2998
|
+
import chalk21 from "chalk";
|
|
2786
2999
|
import ora9 from "ora";
|
|
2787
|
-
import
|
|
3000
|
+
import prompts5 from "prompts";
|
|
2788
3001
|
import { env as env6 } from "@spfn/core/config";
|
|
2789
|
-
import { loadEnv as
|
|
3002
|
+
import { loadEnv as loadEnv6 } from "@spfn/core/server";
|
|
2790
3003
|
async function dbRestore(backupFile, options = {}) {
|
|
2791
|
-
console.log(
|
|
2792
|
-
|
|
3004
|
+
console.log(chalk21.blue("\u267B\uFE0F Restoring database from backup...\n"));
|
|
3005
|
+
loadEnv6();
|
|
2793
3006
|
const dbUrl = env6.DATABASE_URL;
|
|
2794
3007
|
if (!dbUrl) {
|
|
2795
|
-
console.error(
|
|
2796
|
-
console.log(
|
|
3008
|
+
console.error(chalk21.red("\u274C DATABASE_URL not found in environment"));
|
|
3009
|
+
console.log(chalk21.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
|
|
2797
3010
|
process.exit(1);
|
|
2798
3011
|
}
|
|
2799
3012
|
let file = backupFile;
|
|
2800
3013
|
if (!file) {
|
|
2801
3014
|
const backups = await listBackupFiles();
|
|
2802
3015
|
if (backups.length === 0) {
|
|
2803
|
-
console.log(
|
|
3016
|
+
console.log(chalk21.yellow("No backups found in ./backups directory"));
|
|
2804
3017
|
process.exit(0);
|
|
2805
3018
|
}
|
|
2806
|
-
const { selected } = await
|
|
3019
|
+
const { selected } = await prompts5({
|
|
2807
3020
|
type: "select",
|
|
2808
3021
|
name: "selected",
|
|
2809
3022
|
message: "Select backup to restore:",
|
|
@@ -2813,31 +3026,31 @@ async function dbRestore(backupFile, options = {}) {
|
|
|
2813
3026
|
}))
|
|
2814
3027
|
});
|
|
2815
3028
|
if (!selected) {
|
|
2816
|
-
console.log(
|
|
3029
|
+
console.log(chalk21.gray("Cancelled"));
|
|
2817
3030
|
process.exit(0);
|
|
2818
3031
|
}
|
|
2819
3032
|
file = selected;
|
|
2820
3033
|
}
|
|
2821
3034
|
if (!file) {
|
|
2822
|
-
console.error(
|
|
3035
|
+
console.error(chalk21.red("\u274C No backup file selected"));
|
|
2823
3036
|
process.exit(1);
|
|
2824
3037
|
}
|
|
2825
3038
|
const metadata = await loadBackupMetadata(file);
|
|
2826
3039
|
if (metadata) {
|
|
2827
|
-
console.log(
|
|
2828
|
-
console.log(
|
|
2829
|
-
console.log(
|
|
3040
|
+
console.log(chalk21.blue("\n\u{1F4CB} Backup Information:\n"));
|
|
3041
|
+
console.log(chalk21.dim(` Database: ${metadata.database}`));
|
|
3042
|
+
console.log(chalk21.dim(` Created: ${new Date(metadata.timestamp).toLocaleString()}`));
|
|
2830
3043
|
if (metadata.environment) {
|
|
2831
|
-
console.log(
|
|
3044
|
+
console.log(chalk21.dim(` Environment: ${metadata.environment}`));
|
|
2832
3045
|
}
|
|
2833
3046
|
if (metadata.tags && metadata.tags.length > 0) {
|
|
2834
|
-
console.log(
|
|
3047
|
+
console.log(chalk21.dim(` Tags: ${metadata.tags.join(", ")}`));
|
|
2835
3048
|
}
|
|
2836
3049
|
if (metadata.backup.dataOnly) {
|
|
2837
|
-
console.log(
|
|
3050
|
+
console.log(chalk21.yellow(" \u26A0\uFE0F Data-only backup (no schema)"));
|
|
2838
3051
|
}
|
|
2839
3052
|
if (metadata.backup.schemaOnly) {
|
|
2840
|
-
console.log(
|
|
3053
|
+
console.log(chalk21.yellow(" \u26A0\uFE0F Schema-only backup (no data)"));
|
|
2841
3054
|
}
|
|
2842
3055
|
const warnings2 = [];
|
|
2843
3056
|
const [currentGitInfo, currentMigrationInfo] = await Promise.all([
|
|
@@ -2860,23 +3073,23 @@ async function dbRestore(backupFile, options = {}) {
|
|
|
2860
3073
|
}
|
|
2861
3074
|
}
|
|
2862
3075
|
if (warnings2.length > 0) {
|
|
2863
|
-
console.log(
|
|
2864
|
-
warnings2.forEach((warning) => console.log(
|
|
3076
|
+
console.log(chalk21.yellow("\n\u26A0\uFE0F Version Warnings:\n"));
|
|
3077
|
+
warnings2.forEach((warning) => console.log(chalk21.yellow(` - ${warning}`)));
|
|
2865
3078
|
console.log("");
|
|
2866
3079
|
}
|
|
2867
3080
|
}
|
|
2868
|
-
const { confirm } = await
|
|
3081
|
+
const { confirm } = await prompts5({
|
|
2869
3082
|
type: "confirm",
|
|
2870
3083
|
name: "confirm",
|
|
2871
|
-
message:
|
|
3084
|
+
message: chalk21.yellow("\u26A0\uFE0F This will replace all data in the database. Continue?"),
|
|
2872
3085
|
initial: false
|
|
2873
3086
|
});
|
|
2874
3087
|
if (!confirm) {
|
|
2875
|
-
console.log(
|
|
3088
|
+
console.log(chalk21.gray("Cancelled"));
|
|
2876
3089
|
process.exit(0);
|
|
2877
3090
|
}
|
|
2878
3091
|
if (options.dataOnly && options.schemaOnly) {
|
|
2879
|
-
console.error(
|
|
3092
|
+
console.error(chalk21.red("\u274C Cannot use --data-only and --schema-only together"));
|
|
2880
3093
|
process.exit(1);
|
|
2881
3094
|
}
|
|
2882
3095
|
const dbInfo = parseDatabaseUrl(dbUrl);
|
|
@@ -2905,8 +3118,8 @@ async function dbRestore(backupFile, options = {}) {
|
|
|
2905
3118
|
args.push(file);
|
|
2906
3119
|
} else {
|
|
2907
3120
|
if (options.dataOnly || options.schemaOnly) {
|
|
2908
|
-
console.log(
|
|
2909
|
-
console.log(
|
|
3121
|
+
console.log(chalk21.yellow("\u26A0\uFE0F Note: --data-only and --schema-only options only work with custom format backups (.dump)"));
|
|
3122
|
+
console.log(chalk21.yellow(" For SQL files, the backup must have been created with the desired option.\n"));
|
|
2910
3123
|
}
|
|
2911
3124
|
args.push("-h", dbInfo.host);
|
|
2912
3125
|
args.push("-p", dbInfo.port);
|
|
@@ -2952,7 +3165,7 @@ async function dbRestore(backupFile, options = {}) {
|
|
|
2952
3165
|
}
|
|
2953
3166
|
if (verbose) {
|
|
2954
3167
|
spinner.stop();
|
|
2955
|
-
console.log(
|
|
3168
|
+
console.log(chalk21.dim(` ${line.trim()}`));
|
|
2956
3169
|
spinner.start();
|
|
2957
3170
|
}
|
|
2958
3171
|
}
|
|
@@ -2960,7 +3173,7 @@ async function dbRestore(backupFile, options = {}) {
|
|
|
2960
3173
|
restoreProcess.stdout?.on("data", (data) => {
|
|
2961
3174
|
if (verbose) {
|
|
2962
3175
|
spinner.stop();
|
|
2963
|
-
console.log(
|
|
3176
|
+
console.log(chalk21.dim(` ${data.toString().trim()}`));
|
|
2964
3177
|
spinner.start();
|
|
2965
3178
|
}
|
|
2966
3179
|
});
|
|
@@ -2970,31 +3183,31 @@ async function dbRestore(backupFile, options = {}) {
|
|
|
2970
3183
|
const summary = objectCount > 0 ? ` (${objectCount} objects)` : "";
|
|
2971
3184
|
spinner.succeed(`Restore completed${summary}`);
|
|
2972
3185
|
if (warnings.length > 0) {
|
|
2973
|
-
console.log(
|
|
3186
|
+
console.log(chalk21.yellow(`
|
|
2974
3187
|
\u26A0\uFE0F Warnings during restore (${warnings.length}):
|
|
2975
3188
|
`));
|
|
2976
3189
|
for (const w of warnings) {
|
|
2977
|
-
console.log(
|
|
3190
|
+
console.log(chalk21.yellow(` - ${w}`));
|
|
2978
3191
|
}
|
|
2979
3192
|
}
|
|
2980
|
-
console.log(
|
|
3193
|
+
console.log(chalk21.green("\n\u2705 Database restored successfully"));
|
|
2981
3194
|
resolve2();
|
|
2982
3195
|
} else {
|
|
2983
3196
|
spinner.fail("Restore failed");
|
|
2984
3197
|
if (errors.length > 0) {
|
|
2985
|
-
console.error(
|
|
3198
|
+
console.error(chalk21.red(`
|
|
2986
3199
|
\u274C Errors (${errors.length}):
|
|
2987
3200
|
`));
|
|
2988
3201
|
for (const e of errors) {
|
|
2989
|
-
console.error(
|
|
3202
|
+
console.error(chalk21.red(` - ${e}`));
|
|
2990
3203
|
}
|
|
2991
3204
|
}
|
|
2992
3205
|
if (warnings.length > 0) {
|
|
2993
|
-
console.log(
|
|
3206
|
+
console.log(chalk21.yellow(`
|
|
2994
3207
|
\u26A0\uFE0F Warnings (${warnings.length}):
|
|
2995
3208
|
`));
|
|
2996
3209
|
for (const w of warnings) {
|
|
2997
|
-
console.log(
|
|
3210
|
+
console.log(chalk21.yellow(` - ${w}`));
|
|
2998
3211
|
}
|
|
2999
3212
|
}
|
|
3000
3213
|
const fallback = errors.length === 0 && warnings.length === 0 ? "Restore failed with no output" : "";
|
|
@@ -3008,7 +3221,7 @@ async function dbRestore(backupFile, options = {}) {
|
|
|
3008
3221
|
}).catch((error) => {
|
|
3009
3222
|
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
3010
3223
|
if (msg) {
|
|
3011
|
-
console.error(
|
|
3224
|
+
console.error(chalk21.red(`
|
|
3012
3225
|
\u274C ${msg}`));
|
|
3013
3226
|
}
|
|
3014
3227
|
process.exit(1);
|
|
@@ -3016,17 +3229,17 @@ async function dbRestore(backupFile, options = {}) {
|
|
|
3016
3229
|
}
|
|
3017
3230
|
|
|
3018
3231
|
// src/commands/db/list.ts
|
|
3019
|
-
import
|
|
3232
|
+
import chalk22 from "chalk";
|
|
3020
3233
|
async function dbBackupList() {
|
|
3021
|
-
console.log(
|
|
3234
|
+
console.log(chalk22.blue("\u{1F4CB} Database backups:\n"));
|
|
3022
3235
|
const backups = await listBackupFiles();
|
|
3023
3236
|
if (backups.length === 0) {
|
|
3024
|
-
console.log(
|
|
3025
|
-
console.log(
|
|
3237
|
+
console.log(chalk22.yellow("No backups found in ./backups directory"));
|
|
3238
|
+
console.log(chalk22.gray("\n\u{1F4A1} Create a backup with: pnpm spfn db backup\n"));
|
|
3026
3239
|
return;
|
|
3027
3240
|
}
|
|
3028
|
-
console.log(
|
|
3029
|
-
console.log(
|
|
3241
|
+
console.log(chalk22.bold(" Date Size File"));
|
|
3242
|
+
console.log(chalk22.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
3030
3243
|
backups.forEach((backup) => {
|
|
3031
3244
|
const date = backup.date.toLocaleString("en-US", {
|
|
3032
3245
|
year: "numeric",
|
|
@@ -3037,23 +3250,23 @@ async function dbBackupList() {
|
|
|
3037
3250
|
second: "2-digit"
|
|
3038
3251
|
});
|
|
3039
3252
|
const sizeStr = backup.size.padEnd(10);
|
|
3040
|
-
console.log(
|
|
3253
|
+
console.log(chalk22.white(` ${date} ${sizeStr} ${backup.name}`));
|
|
3041
3254
|
});
|
|
3042
|
-
console.log(
|
|
3255
|
+
console.log(chalk22.gray(`
|
|
3043
3256
|
Total: ${backups.length} backup(s)
|
|
3044
3257
|
`));
|
|
3045
3258
|
}
|
|
3046
3259
|
|
|
3047
3260
|
// src/commands/db/clean.ts
|
|
3048
3261
|
import { promises as fs4 } from "fs";
|
|
3049
|
-
import
|
|
3262
|
+
import chalk23 from "chalk";
|
|
3050
3263
|
import ora10 from "ora";
|
|
3051
|
-
import
|
|
3264
|
+
import prompts6 from "prompts";
|
|
3052
3265
|
async function dbBackupClean(options) {
|
|
3053
|
-
console.log(
|
|
3266
|
+
console.log(chalk23.blue("\u{1F9F9} Cleaning old backups...\n"));
|
|
3054
3267
|
const backups = await listBackupFiles();
|
|
3055
3268
|
if (backups.length === 0) {
|
|
3056
|
-
console.log(
|
|
3269
|
+
console.log(chalk23.yellow("No backups found"));
|
|
3057
3270
|
return;
|
|
3058
3271
|
}
|
|
3059
3272
|
let toDelete = [];
|
|
@@ -3070,41 +3283,142 @@ async function dbBackupClean(options) {
|
|
|
3070
3283
|
toDelete = backups.slice(defaultKeep);
|
|
3071
3284
|
}
|
|
3072
3285
|
if (toDelete.length === 0) {
|
|
3073
|
-
console.log(
|
|
3286
|
+
console.log(chalk23.green("\u2705 No backups to clean"));
|
|
3074
3287
|
return;
|
|
3075
3288
|
}
|
|
3076
|
-
console.log(
|
|
3289
|
+
console.log(chalk23.yellow(`The following ${toDelete.length} backup(s) will be deleted:
|
|
3077
3290
|
`));
|
|
3078
3291
|
toDelete.forEach((backup) => {
|
|
3079
|
-
console.log(
|
|
3292
|
+
console.log(chalk23.gray(` - ${backup.name} (${backup.size})`));
|
|
3080
3293
|
});
|
|
3081
|
-
const { confirm } = await
|
|
3294
|
+
const { confirm } = await prompts6({
|
|
3082
3295
|
type: "confirm",
|
|
3083
3296
|
name: "confirm",
|
|
3084
3297
|
message: "\nProceed with deletion?",
|
|
3085
3298
|
initial: false
|
|
3086
3299
|
});
|
|
3087
3300
|
if (!confirm) {
|
|
3088
|
-
console.log(
|
|
3301
|
+
console.log(chalk23.gray("Cancelled"));
|
|
3089
3302
|
return;
|
|
3090
3303
|
}
|
|
3091
3304
|
const spinner = ora10("Deleting backups...").start();
|
|
3092
3305
|
try {
|
|
3093
3306
|
await Promise.all(toDelete.map((backup) => fs4.unlink(backup.path)));
|
|
3094
3307
|
spinner.succeed("Backups deleted");
|
|
3095
|
-
console.log(
|
|
3308
|
+
console.log(chalk23.green(`
|
|
3096
3309
|
\u2705 Deleted ${toDelete.length} backup(s)`));
|
|
3097
3310
|
} catch (error) {
|
|
3098
3311
|
spinner.fail("Failed to delete backups");
|
|
3099
|
-
console.error(
|
|
3312
|
+
console.error(chalk23.red(error instanceof Error ? error.message : "Unknown error"));
|
|
3100
3313
|
process.exit(1);
|
|
3101
3314
|
}
|
|
3102
3315
|
}
|
|
3103
3316
|
|
|
3317
|
+
// src/commands/db/reindex.ts
|
|
3318
|
+
import { existsSync as existsSync20, readFileSync as readFileSync6, writeFileSync as writeFileSync11, renameSync, copyFileSync } from "fs";
|
|
3319
|
+
import { join as join17 } from "path";
|
|
3320
|
+
import chalk24 from "chalk";
|
|
3321
|
+
import { loadEnv as loadEnv7 } from "@spfn/core/server";
|
|
3322
|
+
function isTimestampPrefix(tag) {
|
|
3323
|
+
const prefix = tag.split("_")[0];
|
|
3324
|
+
return /^\d{5,}$/.test(prefix);
|
|
3325
|
+
}
|
|
3326
|
+
function parseTag(tag) {
|
|
3327
|
+
const underscoreIdx = tag.indexOf("_");
|
|
3328
|
+
if (underscoreIdx === -1) {
|
|
3329
|
+
return { prefix: tag, suffix: "" };
|
|
3330
|
+
}
|
|
3331
|
+
return {
|
|
3332
|
+
prefix: tag.substring(0, underscoreIdx),
|
|
3333
|
+
suffix: tag.substring(underscoreIdx + 1)
|
|
3334
|
+
};
|
|
3335
|
+
}
|
|
3336
|
+
async function dbReindex(options = {}) {
|
|
3337
|
+
loadEnv7();
|
|
3338
|
+
const { getDrizzleConfig } = await import("@spfn/core/db");
|
|
3339
|
+
const config = getDrizzleConfig({ disablePackageDiscovery: true });
|
|
3340
|
+
const outDir = config.out;
|
|
3341
|
+
const journalPath = join17(outDir, "meta", "_journal.json");
|
|
3342
|
+
if (!existsSync20(journalPath)) {
|
|
3343
|
+
console.error(chalk24.red("\u274C No _journal.json found at:"), journalPath);
|
|
3344
|
+
console.log(chalk24.yellow("\u{1F4A1} Run `spfn db generate` first to create migrations"));
|
|
3345
|
+
process.exit(1);
|
|
3346
|
+
}
|
|
3347
|
+
const journal = JSON.parse(readFileSync6(journalPath, "utf-8"));
|
|
3348
|
+
if (journal.entries.length === 0) {
|
|
3349
|
+
console.log(chalk24.yellow("No migration entries found \u2014 nothing to reindex."));
|
|
3350
|
+
return;
|
|
3351
|
+
}
|
|
3352
|
+
const renames = [];
|
|
3353
|
+
const tagUpdates = [];
|
|
3354
|
+
let skipped = 0;
|
|
3355
|
+
for (const entry of journal.entries) {
|
|
3356
|
+
if (isTimestampPrefix(entry.tag)) {
|
|
3357
|
+
skipped++;
|
|
3358
|
+
continue;
|
|
3359
|
+
}
|
|
3360
|
+
const { prefix: oldPrefix, suffix } = parseTag(entry.tag);
|
|
3361
|
+
const newPrefix = String(entry.when);
|
|
3362
|
+
const newTag = suffix ? `${newPrefix}_${suffix}` : newPrefix;
|
|
3363
|
+
const oldSql = join17(outDir, `${entry.tag}.sql`);
|
|
3364
|
+
const newSql = join17(outDir, `${newTag}.sql`);
|
|
3365
|
+
if (existsSync20(oldSql)) {
|
|
3366
|
+
renames.push({ type: "sql", from: oldSql, to: newSql });
|
|
3367
|
+
}
|
|
3368
|
+
const oldSnapshot = join17(outDir, "meta", `${oldPrefix}_snapshot.json`);
|
|
3369
|
+
const newSnapshot = join17(outDir, "meta", `${newPrefix}_snapshot.json`);
|
|
3370
|
+
if (existsSync20(oldSnapshot)) {
|
|
3371
|
+
renames.push({ type: "snapshot", from: oldSnapshot, to: newSnapshot });
|
|
3372
|
+
}
|
|
3373
|
+
tagUpdates.push({ idx: entry.idx, oldTag: entry.tag, newTag });
|
|
3374
|
+
}
|
|
3375
|
+
if (tagUpdates.length === 0) {
|
|
3376
|
+
console.log(chalk24.green("\u2705 All migrations already use timestamp prefix \u2014 nothing to do."));
|
|
3377
|
+
if (skipped > 0) {
|
|
3378
|
+
console.log(chalk24.dim(` (${skipped} entries already timestamp-prefixed)`));
|
|
3379
|
+
}
|
|
3380
|
+
return;
|
|
3381
|
+
}
|
|
3382
|
+
console.log(chalk24.bold("\n\u{1F4CB} Reindex plan:\n"));
|
|
3383
|
+
for (const update of tagUpdates) {
|
|
3384
|
+
console.log(
|
|
3385
|
+
chalk24.dim(` [${update.idx}]`),
|
|
3386
|
+
chalk24.red(update.oldTag),
|
|
3387
|
+
chalk24.dim("\u2192"),
|
|
3388
|
+
chalk24.green(update.newTag)
|
|
3389
|
+
);
|
|
3390
|
+
}
|
|
3391
|
+
console.log(chalk24.dim(`
|
|
3392
|
+
${renames.length} file(s) to rename, ${tagUpdates.length} journal tag(s) to update`));
|
|
3393
|
+
if (skipped > 0) {
|
|
3394
|
+
console.log(chalk24.dim(` ${skipped} entry/entries already timestamp-prefixed (skipped)`));
|
|
3395
|
+
}
|
|
3396
|
+
if (options.dryRun) {
|
|
3397
|
+
console.log(chalk24.yellow("\n\u{1F50D} Dry run \u2014 no changes applied."));
|
|
3398
|
+
return;
|
|
3399
|
+
}
|
|
3400
|
+
const backupPath = journalPath + ".bak";
|
|
3401
|
+
copyFileSync(journalPath, backupPath);
|
|
3402
|
+
console.log(chalk24.dim(`
|
|
3403
|
+
Backed up journal \u2192 ${backupPath}`));
|
|
3404
|
+
for (const rename of renames) {
|
|
3405
|
+
renameSync(rename.from, rename.to);
|
|
3406
|
+
}
|
|
3407
|
+
for (const update of tagUpdates) {
|
|
3408
|
+
const entry = journal.entries.find((e) => e.idx === update.idx);
|
|
3409
|
+
if (entry) {
|
|
3410
|
+
entry.tag = update.newTag;
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
writeFileSync11(journalPath, JSON.stringify(journal, null, 2) + "\n");
|
|
3414
|
+
console.log(chalk24.green(`
|
|
3415
|
+
\u2705 Reindex complete \u2014 ${tagUpdates.length} migration(s) converted to timestamp prefix.`));
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3104
3418
|
// src/commands/db/index.ts
|
|
3105
3419
|
var dbCommand = new Command9("db").description("Database management commands (wraps Drizzle Kit)");
|
|
3106
3420
|
dbCommand.command("generate").alias("g").description("Generate database migrations from schema changes").action(dbGenerate);
|
|
3107
|
-
dbCommand.command("push").description("Push schema changes
|
|
3421
|
+
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));
|
|
3108
3422
|
dbCommand.command("migrate").alias("m").description("Run pending migrations").option("--with-backup", "Create backup before running migrations").action((options) => dbMigrate(options));
|
|
3109
3423
|
dbCommand.command("studio").description("Open Drizzle Studio (database GUI)").option("-p, --port <port>", "Studio port (auto-finds if in use)").action((options) => dbStudio(options.port ? Number(options.port) : void 0));
|
|
3110
3424
|
dbCommand.command("drop").description("Drop all database tables (\u26A0\uFE0F dangerous!)").action(dbDrop);
|
|
@@ -3113,31 +3427,32 @@ dbCommand.command("backup").description("Create a database backup").option("-f,
|
|
|
3113
3427
|
dbCommand.command("restore [file]").description("Restore database from backup").option("--drop", "Drop existing tables before restore").option("-s, --schema <name>", "Restore specific schema only").option("--data-only", "Restore data only (requires custom format .dump file)").option("--schema-only", "Restore schema only (requires custom format .dump file)").option("-v, --verbose", "Show detailed restore progress").action((file, options) => dbRestore(file, options));
|
|
3114
3428
|
dbCommand.command("backup:list").description("List all database backups").action(dbBackupList);
|
|
3115
3429
|
dbCommand.command("backup:clean").description("Clean old database backups").option("-k, --keep <number>", "Keep N most recent backups", parseInt).option("-o, --older-than <days>", "Delete backups older than N days", parseInt).action((options) => dbBackupClean(options));
|
|
3430
|
+
dbCommand.command("reindex").description("Convert migration files from sequential to timestamp prefix").option("--dry-run", "Show changes without applying").action((options) => dbReindex(options));
|
|
3116
3431
|
|
|
3117
3432
|
// src/commands/add.ts
|
|
3118
3433
|
import { Command as Command10 } from "commander";
|
|
3119
|
-
import { existsSync as
|
|
3120
|
-
import { join as
|
|
3434
|
+
import { existsSync as existsSync21, readFileSync as readFileSync7 } from "fs";
|
|
3435
|
+
import { join as join18 } from "path";
|
|
3121
3436
|
import { exec as exec2 } from "child_process";
|
|
3122
3437
|
import { promisify as promisify2 } from "util";
|
|
3123
|
-
import
|
|
3438
|
+
import chalk25 from "chalk";
|
|
3124
3439
|
import ora11 from "ora";
|
|
3125
3440
|
var execAsync2 = promisify2(exec2);
|
|
3126
3441
|
async function addPackage(packageName) {
|
|
3127
3442
|
if (!packageName.includes("/")) {
|
|
3128
|
-
console.error(
|
|
3129
|
-
console.log(
|
|
3130
|
-
console.log(
|
|
3131
|
-
console.log(
|
|
3443
|
+
console.error(chalk25.red("\u274C Please specify full package name"));
|
|
3444
|
+
console.log(chalk25.yellow("\n\u{1F4A1} Examples:"));
|
|
3445
|
+
console.log(chalk25.gray(" pnpm spfn add @spfn/cms"));
|
|
3446
|
+
console.log(chalk25.gray(" pnpm spfn add @mycompany/spfn-analytics"));
|
|
3132
3447
|
process.exit(1);
|
|
3133
3448
|
}
|
|
3134
|
-
console.log(
|
|
3449
|
+
console.log(chalk25.blue(`
|
|
3135
3450
|
\u{1F4E6} Setting up ${packageName}...
|
|
3136
3451
|
`));
|
|
3137
3452
|
try {
|
|
3138
|
-
const pkgPath =
|
|
3139
|
-
const pkgJsonPath =
|
|
3140
|
-
if (!
|
|
3453
|
+
const pkgPath = join18(process.cwd(), "node_modules", ...packageName.split("/"));
|
|
3454
|
+
const pkgJsonPath = join18(pkgPath, "package.json");
|
|
3455
|
+
if (!existsSync21(pkgJsonPath)) {
|
|
3141
3456
|
const installSpinner = ora11("Installing package...").start();
|
|
3142
3457
|
try {
|
|
3143
3458
|
await execAsync2(`pnpm add ${packageName}`);
|
|
@@ -3147,21 +3462,21 @@ async function addPackage(packageName) {
|
|
|
3147
3462
|
throw error;
|
|
3148
3463
|
}
|
|
3149
3464
|
} else {
|
|
3150
|
-
console.log(
|
|
3465
|
+
console.log(chalk25.gray("\u2713 Package already installed (using local version)\n"));
|
|
3151
3466
|
}
|
|
3152
|
-
if (!
|
|
3467
|
+
if (!existsSync21(pkgJsonPath)) {
|
|
3153
3468
|
throw new Error(`Package ${packageName} not found after installation`);
|
|
3154
3469
|
}
|
|
3155
|
-
const pkgJson = JSON.parse(
|
|
3470
|
+
const pkgJson = JSON.parse(readFileSync7(pkgJsonPath, "utf-8"));
|
|
3156
3471
|
if (pkgJson.spfn?.migrations) {
|
|
3157
|
-
console.log(
|
|
3472
|
+
console.log(chalk25.blue(`
|
|
3158
3473
|
\u{1F5C4}\uFE0F Setting up database for ${packageName}...
|
|
3159
3474
|
`));
|
|
3160
3475
|
const { env: env7 } = await import("@spfn/core/config");
|
|
3161
3476
|
if (!env7.DATABASE_URL) {
|
|
3162
|
-
console.log(
|
|
3163
|
-
console.log(
|
|
3164
|
-
console.log(
|
|
3477
|
+
console.log(chalk25.yellow("\u26A0\uFE0F DATABASE_URL not found"));
|
|
3478
|
+
console.log(chalk25.gray("Skipping database setup. Run migrations manually when ready:\n"));
|
|
3479
|
+
console.log(chalk25.gray(` pnpm spfn db push
|
|
3165
3480
|
`));
|
|
3166
3481
|
} else {
|
|
3167
3482
|
const { discoverFunctionMigrations: discoverFunctionMigrations2, executeFunctionMigrations: executeFunctionMigrations2 } = await Promise.resolve().then(() => (init_function_migrations(), function_migrations_exports));
|
|
@@ -3177,25 +3492,25 @@ async function addPackage(packageName) {
|
|
|
3177
3492
|
throw error;
|
|
3178
3493
|
}
|
|
3179
3494
|
} else {
|
|
3180
|
-
console.log(
|
|
3495
|
+
console.log(chalk25.gray("\u2139\uFE0F No migrations found for this package"));
|
|
3181
3496
|
}
|
|
3182
3497
|
}
|
|
3183
3498
|
} else {
|
|
3184
|
-
console.log(
|
|
3499
|
+
console.log(chalk25.gray("\n\u2139\uFE0F No database migrations to apply"));
|
|
3185
3500
|
}
|
|
3186
|
-
console.log(
|
|
3501
|
+
console.log(chalk25.green(`
|
|
3187
3502
|
\u2705 ${packageName} installed successfully!
|
|
3188
3503
|
`));
|
|
3189
3504
|
if (pkgJson.spfn?.setupMessage) {
|
|
3190
|
-
console.log(
|
|
3505
|
+
console.log(chalk25.cyan("\u{1F4DA} Setup Guide:"));
|
|
3191
3506
|
console.log(pkgJson.spfn.setupMessage);
|
|
3192
3507
|
console.log();
|
|
3193
3508
|
}
|
|
3194
3509
|
} catch (error) {
|
|
3195
|
-
console.error(
|
|
3510
|
+
console.error(chalk25.red(`
|
|
3196
3511
|
\u274C Failed to install ${packageName}
|
|
3197
3512
|
`));
|
|
3198
|
-
console.error(
|
|
3513
|
+
console.error(chalk25.red(error instanceof Error ? error.message : "Unknown error"));
|
|
3199
3514
|
process.exit(1);
|
|
3200
3515
|
}
|
|
3201
3516
|
}
|
|
@@ -3205,16 +3520,16 @@ var addCommand = new Command10("add").description("Install and set up SPFN ecosy
|
|
|
3205
3520
|
init_logger();
|
|
3206
3521
|
import { Command as Command11 } from "commander";
|
|
3207
3522
|
import ora12 from "ora";
|
|
3208
|
-
import { join as
|
|
3209
|
-
import { existsSync as
|
|
3210
|
-
import
|
|
3523
|
+
import { join as join27 } from "path";
|
|
3524
|
+
import { existsSync as existsSync24 } from "fs";
|
|
3525
|
+
import chalk27 from "chalk";
|
|
3211
3526
|
|
|
3212
3527
|
// src/commands/generate/prompts.ts
|
|
3213
3528
|
init_logger();
|
|
3214
|
-
import
|
|
3215
|
-
import
|
|
3529
|
+
import prompts7 from "prompts";
|
|
3530
|
+
import chalk26 from "chalk";
|
|
3216
3531
|
async function promptScope() {
|
|
3217
|
-
const response = await
|
|
3532
|
+
const response = await prompts7({
|
|
3218
3533
|
type: "text",
|
|
3219
3534
|
name: "scope",
|
|
3220
3535
|
message: "NPM scope (e.g., @mycompany, @username):",
|
|
@@ -3236,7 +3551,7 @@ async function promptScope() {
|
|
|
3236
3551
|
return response.scope;
|
|
3237
3552
|
}
|
|
3238
3553
|
async function promptFunctionName() {
|
|
3239
|
-
const response = await
|
|
3554
|
+
const response = await prompts7({
|
|
3240
3555
|
type: "text",
|
|
3241
3556
|
name: "fnName",
|
|
3242
3557
|
message: "Function name:",
|
|
@@ -3257,7 +3572,7 @@ async function promptFunctionName() {
|
|
|
3257
3572
|
return response.fnName;
|
|
3258
3573
|
}
|
|
3259
3574
|
async function promptDescription(fnName) {
|
|
3260
|
-
const response = await
|
|
3575
|
+
const response = await prompts7({
|
|
3261
3576
|
type: "text",
|
|
3262
3577
|
name: "description",
|
|
3263
3578
|
message: "Function description:",
|
|
@@ -3266,7 +3581,7 @@ async function promptDescription(fnName) {
|
|
|
3266
3581
|
return response.description || "A description of what this module does";
|
|
3267
3582
|
}
|
|
3268
3583
|
async function promptEntities() {
|
|
3269
|
-
const response = await
|
|
3584
|
+
const response = await prompts7({
|
|
3270
3585
|
type: "list",
|
|
3271
3586
|
name: "entities",
|
|
3272
3587
|
message: "Entity names (comma-separated, press enter to skip):",
|
|
@@ -3278,14 +3593,14 @@ async function promptEntities() {
|
|
|
3278
3593
|
async function confirmConfiguration(config) {
|
|
3279
3594
|
const { scope, fnName, description, entities, enableCache, enableRoutes } = config;
|
|
3280
3595
|
console.log("");
|
|
3281
|
-
logger.info(
|
|
3282
|
-
console.log(` ${
|
|
3283
|
-
console.log(` ${
|
|
3284
|
-
console.log(` ${
|
|
3285
|
-
console.log(` ${
|
|
3286
|
-
console.log(` ${
|
|
3596
|
+
logger.info(chalk26.bold("\u26A1 Function Configuration:"));
|
|
3597
|
+
console.log(` ${chalk26.gray("Package:")} ${chalk26.cyan(`${scope}/${fnName}`)}`);
|
|
3598
|
+
console.log(` ${chalk26.gray("Description:")} ${description}`);
|
|
3599
|
+
console.log(` ${chalk26.gray("Entities:")} ${entities.length > 0 ? entities.join(", ") : chalk26.gray("none")}`);
|
|
3600
|
+
console.log(` ${chalk26.gray("Cache:")} ${enableCache ? chalk26.green("yes") : chalk26.gray("no")}`);
|
|
3601
|
+
console.log(` ${chalk26.gray("Routes:")} ${enableRoutes ? chalk26.green("yes") : chalk26.gray("no")}`);
|
|
3287
3602
|
console.log("");
|
|
3288
|
-
const { confirmed } = await
|
|
3603
|
+
const { confirmed } = await prompts7({
|
|
3289
3604
|
type: "confirm",
|
|
3290
3605
|
name: "confirmed",
|
|
3291
3606
|
message: "Create function?",
|
|
@@ -3299,12 +3614,12 @@ async function confirmConfiguration(config) {
|
|
|
3299
3614
|
}
|
|
3300
3615
|
|
|
3301
3616
|
// src/commands/generate/generators/structure.ts
|
|
3302
|
-
import { join as
|
|
3303
|
-
import { mkdirSync as mkdirSync5, writeFileSync as
|
|
3617
|
+
import { join as join26 } from "path";
|
|
3618
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync18 } from "fs";
|
|
3304
3619
|
|
|
3305
3620
|
// src/commands/generate/generators/config.ts
|
|
3306
|
-
import { join as
|
|
3307
|
-
import { writeFileSync as
|
|
3621
|
+
import { join as join20 } from "path";
|
|
3622
|
+
import { writeFileSync as writeFileSync12, mkdirSync as mkdirSync3 } from "fs";
|
|
3308
3623
|
|
|
3309
3624
|
// src/commands/generate/string-utils.ts
|
|
3310
3625
|
function toPascalCase(str) {
|
|
@@ -3333,30 +3648,30 @@ function toSafeSchemaName(str) {
|
|
|
3333
3648
|
}
|
|
3334
3649
|
|
|
3335
3650
|
// src/commands/generate/template-loader.ts
|
|
3336
|
-
import { readFileSync as
|
|
3337
|
-
import { join as
|
|
3651
|
+
import { readFileSync as readFileSync8, existsSync as existsSync22 } from "fs";
|
|
3652
|
+
import { join as join19, dirname as dirname2 } from "path";
|
|
3338
3653
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3339
3654
|
function findTemplatesPath2() {
|
|
3340
3655
|
const __filename = fileURLToPath2(import.meta.url);
|
|
3341
3656
|
const __dirname2 = dirname2(__filename);
|
|
3342
|
-
const distPath =
|
|
3343
|
-
if (
|
|
3657
|
+
const distPath = join19(__dirname2, "commands", "generate", "templates");
|
|
3658
|
+
if (existsSync22(distPath)) {
|
|
3344
3659
|
return distPath;
|
|
3345
3660
|
}
|
|
3346
|
-
const sameDirPath =
|
|
3347
|
-
if (
|
|
3661
|
+
const sameDirPath = join19(__dirname2, "templates");
|
|
3662
|
+
if (existsSync22(sameDirPath)) {
|
|
3348
3663
|
return sameDirPath;
|
|
3349
3664
|
}
|
|
3350
|
-
const srcPath =
|
|
3351
|
-
if (
|
|
3665
|
+
const srcPath = join19(__dirname2, "..", "..", "src", "commands", "generate", "templates");
|
|
3666
|
+
if (existsSync22(srcPath)) {
|
|
3352
3667
|
return srcPath;
|
|
3353
3668
|
}
|
|
3354
3669
|
throw new Error(`Templates directory not found. Tried: ${distPath}, ${sameDirPath}, ${srcPath}`);
|
|
3355
3670
|
}
|
|
3356
3671
|
function loadTemplate(templateName, variables) {
|
|
3357
3672
|
const templatesDir = findTemplatesPath2();
|
|
3358
|
-
const templatePath =
|
|
3359
|
-
let content =
|
|
3673
|
+
const templatePath = join19(templatesDir, `${templateName}.template`);
|
|
3674
|
+
let content = readFileSync8(templatePath, "utf-8");
|
|
3360
3675
|
for (const [key, value] of Object.entries(variables)) {
|
|
3361
3676
|
const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
|
|
3362
3677
|
content = content.replace(regex, value);
|
|
@@ -3458,8 +3773,8 @@ function generatePackageJson(fnDir, scope, fnName, description) {
|
|
|
3458
3773
|
vitest: "^4.0.6"
|
|
3459
3774
|
}
|
|
3460
3775
|
};
|
|
3461
|
-
|
|
3462
|
-
|
|
3776
|
+
writeFileSync12(
|
|
3777
|
+
join20(fnDir, "package.json"),
|
|
3463
3778
|
JSON.stringify(content, null, 4) + "\n"
|
|
3464
3779
|
);
|
|
3465
3780
|
}
|
|
@@ -3492,8 +3807,8 @@ function generateTsConfig(fnDir) {
|
|
|
3492
3807
|
include: ["src/**/*"],
|
|
3493
3808
|
exclude: ["node_modules", "dist", "**/*.test.ts", "**/__tests__/**"]
|
|
3494
3809
|
};
|
|
3495
|
-
|
|
3496
|
-
|
|
3810
|
+
writeFileSync12(
|
|
3811
|
+
join20(fnDir, "tsconfig.json"),
|
|
3497
3812
|
JSON.stringify(content, null, 4) + "\n"
|
|
3498
3813
|
);
|
|
3499
3814
|
}
|
|
@@ -3569,7 +3884,7 @@ export default defineConfig({
|
|
|
3569
3884
|
],
|
|
3570
3885
|
});
|
|
3571
3886
|
`;
|
|
3572
|
-
|
|
3887
|
+
writeFileSync12(join20(fnDir, "tsup.config.ts"), content);
|
|
3573
3888
|
}
|
|
3574
3889
|
function generateDrizzleConfig(fnDir, scope, fnName) {
|
|
3575
3890
|
const schemaName = `spfn_${toSnakeCase(fnName)}`;
|
|
@@ -3589,7 +3904,7 @@ export default defineConfig({
|
|
|
3589
3904
|
schemaFilter: ['${schemaName}'], // Only generate for ${fnName} schema
|
|
3590
3905
|
});
|
|
3591
3906
|
`;
|
|
3592
|
-
|
|
3907
|
+
writeFileSync12(join20(fnDir, "drizzle.config.ts"), content);
|
|
3593
3908
|
}
|
|
3594
3909
|
function generateExampleGenerator(fnDir, scope, fnName) {
|
|
3595
3910
|
const pascalName = toPascalCase(fnName);
|
|
@@ -3658,10 +3973,10 @@ export const moduleName = '${fnName}';
|
|
|
3658
3973
|
};
|
|
3659
3974
|
}
|
|
3660
3975
|
`;
|
|
3661
|
-
const generatorsDir =
|
|
3976
|
+
const generatorsDir = join20(fnDir, "src/server/generators");
|
|
3662
3977
|
mkdirSync3(generatorsDir, { recursive: true });
|
|
3663
|
-
|
|
3664
|
-
|
|
3978
|
+
writeFileSync12(
|
|
3979
|
+
join20(generatorsDir, "example-generator.ts"),
|
|
3665
3980
|
content
|
|
3666
3981
|
);
|
|
3667
3982
|
const indexContent = `/**
|
|
@@ -3675,8 +3990,8 @@ export const moduleName = '${fnName}';
|
|
|
3675
3990
|
|
|
3676
3991
|
export { create${pascalName}ExampleGenerator } from './example-generator.js';
|
|
3677
3992
|
`;
|
|
3678
|
-
|
|
3679
|
-
|
|
3993
|
+
writeFileSync12(
|
|
3994
|
+
join20(generatorsDir, "index.ts"),
|
|
3680
3995
|
indexContent
|
|
3681
3996
|
);
|
|
3682
3997
|
}
|
|
@@ -4165,15 +4480,15 @@ Contributions are welcome! Please follow the development workflow above.
|
|
|
4165
4480
|
|
|
4166
4481
|
MIT
|
|
4167
4482
|
`;
|
|
4168
|
-
|
|
4483
|
+
writeFileSync12(join20(fnDir, "README.md"), content);
|
|
4169
4484
|
}
|
|
4170
4485
|
|
|
4171
4486
|
// src/commands/generate/generators/entity.ts
|
|
4172
|
-
import { join as
|
|
4173
|
-
import { writeFileSync as
|
|
4487
|
+
import { join as join21 } from "path";
|
|
4488
|
+
import { writeFileSync as writeFileSync13, existsSync as existsSync23 } from "fs";
|
|
4174
4489
|
function generateSchema(fnDir, scope, fnName) {
|
|
4175
|
-
const schemaFilePath =
|
|
4176
|
-
if (
|
|
4490
|
+
const schemaFilePath = join21(fnDir, "src/server/entities/schema.ts");
|
|
4491
|
+
if (existsSync23(schemaFilePath)) {
|
|
4177
4492
|
return;
|
|
4178
4493
|
}
|
|
4179
4494
|
const packageName = `${scope}/${fnName}`;
|
|
@@ -4184,7 +4499,7 @@ function generateSchema(fnDir, scope, fnName) {
|
|
|
4184
4499
|
PACKAGE_NAME: packageName,
|
|
4185
4500
|
SCHEMA_VAR_NAME: schemaVarName
|
|
4186
4501
|
});
|
|
4187
|
-
|
|
4502
|
+
writeFileSync13(schemaFilePath, content);
|
|
4188
4503
|
}
|
|
4189
4504
|
function generateEntity(fnDir, scope, fnName, entityName) {
|
|
4190
4505
|
const safeScope = toSafeSchemaName(scope);
|
|
@@ -4203,8 +4518,8 @@ function generateEntity(fnDir, scope, fnName, entityName) {
|
|
|
4203
4518
|
SCHEMA_VAR_NAME: schemaVarName,
|
|
4204
4519
|
SCHEMA_FILE_NAME: schemaFileName
|
|
4205
4520
|
});
|
|
4206
|
-
|
|
4207
|
-
|
|
4521
|
+
writeFileSync13(
|
|
4522
|
+
join21(fnDir, `src/server/entities/${toKebabCase(entityName)}.ts`),
|
|
4208
4523
|
content
|
|
4209
4524
|
);
|
|
4210
4525
|
}
|
|
@@ -4212,12 +4527,12 @@ function generateEntitiesIndex(fnDir, entities) {
|
|
|
4212
4527
|
const schemaExport = `export * from './schema';`;
|
|
4213
4528
|
const entityExports = entities.map((entity) => `export * from './${toKebabCase(entity)}';`).join("\n");
|
|
4214
4529
|
const content = [schemaExport, entityExports].filter(Boolean).join("\n");
|
|
4215
|
-
|
|
4530
|
+
writeFileSync13(join21(fnDir, "src/server/entities/index.ts"), content + "\n");
|
|
4216
4531
|
}
|
|
4217
4532
|
|
|
4218
4533
|
// src/commands/generate/generators/repository.ts
|
|
4219
|
-
import { join as
|
|
4220
|
-
import { writeFileSync as
|
|
4534
|
+
import { join as join22 } from "path";
|
|
4535
|
+
import { writeFileSync as writeFileSync14 } from "fs";
|
|
4221
4536
|
function generateRepository(fnDir, entityName) {
|
|
4222
4537
|
const pascalName = toPascalCase(entityName);
|
|
4223
4538
|
const repoName = `${entityName}Repository`;
|
|
@@ -4226,19 +4541,19 @@ function generateRepository(fnDir, entityName) {
|
|
|
4226
4541
|
ENTITY_NAME: entityName,
|
|
4227
4542
|
REPO_NAME: repoName
|
|
4228
4543
|
});
|
|
4229
|
-
|
|
4230
|
-
|
|
4544
|
+
writeFileSync14(
|
|
4545
|
+
join22(fnDir, `src/server/repositories/${toKebabCase(entityName)}.repository.ts`),
|
|
4231
4546
|
content
|
|
4232
4547
|
);
|
|
4233
4548
|
}
|
|
4234
4549
|
function generateRepositoriesIndex(fnDir, entities) {
|
|
4235
4550
|
const exports = entities.map((entity) => `export * from './${toKebabCase(entity)}.repository';`).join("\n");
|
|
4236
|
-
|
|
4551
|
+
writeFileSync14(join22(fnDir, "src/server/repositories/index.ts"), exports + "\n");
|
|
4237
4552
|
}
|
|
4238
4553
|
|
|
4239
4554
|
// src/commands/generate/generators/route.ts
|
|
4240
|
-
import { join as
|
|
4241
|
-
import { mkdirSync as mkdirSync4, writeFileSync as
|
|
4555
|
+
import { join as join23 } from "path";
|
|
4556
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync15 } from "fs";
|
|
4242
4557
|
function generateRoute(fnDir, entityName) {
|
|
4243
4558
|
const pascalName = toPascalCase(entityName);
|
|
4244
4559
|
const repoName = `${entityName}Repository`;
|
|
@@ -4249,29 +4564,29 @@ function generateRoute(fnDir, entityName) {
|
|
|
4249
4564
|
REPO_NAME: repoName,
|
|
4250
4565
|
KEBAB_NAME: kebabName
|
|
4251
4566
|
});
|
|
4252
|
-
const routeDir =
|
|
4567
|
+
const routeDir = join23(fnDir, `src/server/routes/${kebabName}`);
|
|
4253
4568
|
mkdirSync4(routeDir, { recursive: true });
|
|
4254
|
-
|
|
4569
|
+
writeFileSync15(join23(routeDir, "index.ts"), content);
|
|
4255
4570
|
}
|
|
4256
4571
|
|
|
4257
4572
|
// src/commands/generate/generators/contract.ts
|
|
4258
|
-
import { join as
|
|
4259
|
-
import { writeFileSync as
|
|
4573
|
+
import { join as join24 } from "path";
|
|
4574
|
+
import { writeFileSync as writeFileSync16 } from "fs";
|
|
4260
4575
|
function generateContract(fnDir, entityName) {
|
|
4261
4576
|
const pascalName = toPascalCase(entityName);
|
|
4262
4577
|
const content = loadTemplate("contract", {
|
|
4263
4578
|
PASCAL_NAME: pascalName,
|
|
4264
4579
|
ENTITY_NAME: entityName
|
|
4265
4580
|
});
|
|
4266
|
-
|
|
4267
|
-
|
|
4581
|
+
writeFileSync16(
|
|
4582
|
+
join24(fnDir, `src/lib/contracts/${toKebabCase(entityName)}.ts`),
|
|
4268
4583
|
content
|
|
4269
4584
|
);
|
|
4270
4585
|
}
|
|
4271
4586
|
|
|
4272
4587
|
// src/commands/generate/generators/index-files.ts
|
|
4273
|
-
import { join as
|
|
4274
|
-
import { writeFileSync as
|
|
4588
|
+
import { join as join25 } from "path";
|
|
4589
|
+
import { writeFileSync as writeFileSync17 } from "fs";
|
|
4275
4590
|
function generateMainIndex(fnDir, fnName) {
|
|
4276
4591
|
const content = `/**
|
|
4277
4592
|
* @spfn/${fnName}
|
|
@@ -4297,7 +4612,7 @@ export * from '@/lib/types/index';
|
|
|
4297
4612
|
|
|
4298
4613
|
export * from '@/server/entities/index';
|
|
4299
4614
|
`;
|
|
4300
|
-
|
|
4615
|
+
writeFileSync17(join25(fnDir, "src/index.ts"), content);
|
|
4301
4616
|
}
|
|
4302
4617
|
function generateServerIndex(fnDir) {
|
|
4303
4618
|
const content = `/**
|
|
@@ -4332,7 +4647,7 @@ export * from '@/server/repositories/index';
|
|
|
4332
4647
|
|
|
4333
4648
|
// TODO: Export helpers here
|
|
4334
4649
|
`;
|
|
4335
|
-
|
|
4650
|
+
writeFileSync17(join25(fnDir, "src/server.ts"), content);
|
|
4336
4651
|
}
|
|
4337
4652
|
function generateClientIndex(fnDir) {
|
|
4338
4653
|
const content = `/**
|
|
@@ -4365,7 +4680,7 @@ export * from './client/store';
|
|
|
4365
4680
|
|
|
4366
4681
|
export * from './client/components';
|
|
4367
4682
|
`;
|
|
4368
|
-
|
|
4683
|
+
writeFileSync17(join25(fnDir, "src/client.ts"), content);
|
|
4369
4684
|
}
|
|
4370
4685
|
function generateTypesFile(fnDir, fnName) {
|
|
4371
4686
|
const content = `/**
|
|
@@ -4377,7 +4692,7 @@ function generateTypesFile(fnDir, fnName) {
|
|
|
4377
4692
|
|
|
4378
4693
|
export * from '@/lib/types/index';
|
|
4379
4694
|
`;
|
|
4380
|
-
|
|
4695
|
+
writeFileSync17(join25(fnDir, "src/types.ts"), content);
|
|
4381
4696
|
}
|
|
4382
4697
|
|
|
4383
4698
|
// src/commands/generate/generators/structure.ts
|
|
@@ -4399,7 +4714,7 @@ async function generateFunctionStructure(options) {
|
|
|
4399
4714
|
"src/client/store",
|
|
4400
4715
|
"src/client/components"
|
|
4401
4716
|
];
|
|
4402
|
-
dirs.forEach((dir) => mkdirSync5(
|
|
4717
|
+
dirs.forEach((dir) => mkdirSync5(join26(fnDir, dir), { recursive: true }));
|
|
4403
4718
|
generatePackageJson(fnDir, scope, fnName, description);
|
|
4404
4719
|
generateTsConfig(fnDir);
|
|
4405
4720
|
generateTsupConfig(fnDir);
|
|
@@ -4419,15 +4734,15 @@ async function generateFunctionStructure(options) {
|
|
|
4419
4734
|
generateEntitiesIndex(fnDir, entities);
|
|
4420
4735
|
generateRepositoriesIndex(fnDir, entities);
|
|
4421
4736
|
} else {
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
}
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4737
|
+
writeFileSync18(join26(fnDir, "src/server/entities/index.ts"), "// Export your entities here\nexport {}\n");
|
|
4738
|
+
writeFileSync18(join26(fnDir, "src/server/repositories/index.ts"), "// Export your repositories here\nexport {}\n");
|
|
4739
|
+
}
|
|
4740
|
+
writeFileSync18(join26(fnDir, "src/client/hooks/index.ts"), "/**\n * Client Hooks\n */\n\n// TODO: Add hooks (e.g., useAuth, useData, etc.)\nexport {}\n");
|
|
4741
|
+
writeFileSync18(join26(fnDir, "src/client/store/index.ts"), "/**\n * Client Store\n */\n\n// TODO: Add Zustand store if needed\nexport {}\n");
|
|
4742
|
+
writeFileSync18(join26(fnDir, "src/client/components/index.ts"), "/**\n * Client Components\n */\n\n// TODO: Add React components\nexport {}\n");
|
|
4743
|
+
writeFileSync18(join26(fnDir, "src/client/index.ts"), "/**\n * Client Module Entry\n */\n\nexport * from './hooks';\nexport * from './store';\nexport * from './components';\n");
|
|
4744
|
+
writeFileSync18(join26(fnDir, "src/lib/types/index.ts"), "/**\n * Shared Type Definitions\n */\n\n// Add your shared types here\nexport {}\n");
|
|
4745
|
+
writeFileSync18(join26(fnDir, "src/lib/contracts/index.ts"), "/**\n * API Contracts\n */\n\n// Export your contracts here\nexport {}\n");
|
|
4431
4746
|
generateMainIndex(fnDir, fnName);
|
|
4432
4747
|
generateServerIndex(fnDir);
|
|
4433
4748
|
generateClientIndex(fnDir);
|
|
@@ -4451,8 +4766,8 @@ async function generateFunction(name, options) {
|
|
|
4451
4766
|
logger.error("Function name is required");
|
|
4452
4767
|
process.exit(1);
|
|
4453
4768
|
}
|
|
4454
|
-
const fnDir =
|
|
4455
|
-
if (
|
|
4769
|
+
const fnDir = join27(cwd, fnName);
|
|
4770
|
+
if (existsSync24(fnDir)) {
|
|
4456
4771
|
logger.error(`Directory ${fnName} already exists at ${fnDir}`);
|
|
4457
4772
|
process.exit(1);
|
|
4458
4773
|
}
|
|
@@ -4497,13 +4812,13 @@ async function generateFunction(name, options) {
|
|
|
4497
4812
|
});
|
|
4498
4813
|
spinner.succeed("Function structure generated");
|
|
4499
4814
|
console.log("");
|
|
4500
|
-
logger.success(`\u2728 Package ${
|
|
4815
|
+
logger.success(`\u2728 Package ${chalk27.cyan(`${scope}/${fnName}`)} created successfully!
|
|
4501
4816
|
`);
|
|
4502
|
-
logger.info(
|
|
4503
|
-
console.log(` ${
|
|
4504
|
-
console.log(` ${
|
|
4505
|
-
console.log(` ${
|
|
4506
|
-
console.log(` ${
|
|
4817
|
+
logger.info(chalk27.bold("\u{1F4DA} Next steps:"));
|
|
4818
|
+
console.log(` ${chalk27.gray("1.")} cd ${fnName}`);
|
|
4819
|
+
console.log(` ${chalk27.gray("2.")} pnpm install ${chalk27.dim("(in monorepo root)")}`);
|
|
4820
|
+
console.log(` ${chalk27.gray("3.")} pnpm build`);
|
|
4821
|
+
console.log(` ${chalk27.gray("4.")} ${chalk27.dim("Use the package in your app")}`);
|
|
4507
4822
|
console.log("");
|
|
4508
4823
|
} catch (error) {
|
|
4509
4824
|
spinner.fail("Failed to generate function");
|
|
@@ -4516,8 +4831,8 @@ generateCommand.command("fn").description("Generate a new SPFN function module")
|
|
|
4516
4831
|
|
|
4517
4832
|
// src/commands/env.ts
|
|
4518
4833
|
import { Command as Command12 } from "commander";
|
|
4519
|
-
import
|
|
4520
|
-
import { existsSync as
|
|
4834
|
+
import chalk28 from "chalk";
|
|
4835
|
+
import { existsSync as existsSync25, readFileSync as readFileSync9, writeFileSync as writeFileSync19 } from "fs";
|
|
4521
4836
|
import { resolve } from "path";
|
|
4522
4837
|
import { parse } from "dotenv";
|
|
4523
4838
|
var VALID_ENVS = ["local", "development", "staging", "production", "test"];
|
|
@@ -4565,26 +4880,26 @@ async function loadEnvSchema(packageName) {
|
|
|
4565
4880
|
}
|
|
4566
4881
|
function formatType(type) {
|
|
4567
4882
|
const typeColors = {
|
|
4568
|
-
string:
|
|
4569
|
-
number:
|
|
4570
|
-
boolean:
|
|
4571
|
-
url:
|
|
4572
|
-
enum:
|
|
4573
|
-
json:
|
|
4883
|
+
string: chalk28.green,
|
|
4884
|
+
number: chalk28.blue,
|
|
4885
|
+
boolean: chalk28.yellow,
|
|
4886
|
+
url: chalk28.cyan,
|
|
4887
|
+
enum: chalk28.magenta,
|
|
4888
|
+
json: chalk28.red
|
|
4574
4889
|
};
|
|
4575
|
-
return (typeColors[type] ||
|
|
4890
|
+
return (typeColors[type] || chalk28.white)(type);
|
|
4576
4891
|
}
|
|
4577
4892
|
function formatDefault(value, type) {
|
|
4578
4893
|
if (value === void 0) {
|
|
4579
|
-
return
|
|
4894
|
+
return chalk28.dim("(none)");
|
|
4580
4895
|
}
|
|
4581
4896
|
if (type === "string" || type === "url") {
|
|
4582
|
-
return
|
|
4897
|
+
return chalk28.green(`"${value}"`);
|
|
4583
4898
|
}
|
|
4584
4899
|
if (type === "boolean") {
|
|
4585
|
-
return value ?
|
|
4900
|
+
return value ? chalk28.green("true") : chalk28.red("false");
|
|
4586
4901
|
}
|
|
4587
|
-
return
|
|
4902
|
+
return chalk28.cyan(String(value));
|
|
4588
4903
|
}
|
|
4589
4904
|
async function listEnvVars(options) {
|
|
4590
4905
|
const packageName = options.package || "@spfn/core";
|
|
@@ -4598,28 +4913,28 @@ async function listEnvVars(options) {
|
|
|
4598
4913
|
acc[target].push([key, schema]);
|
|
4599
4914
|
return acc;
|
|
4600
4915
|
}, {});
|
|
4601
|
-
console.log(
|
|
4916
|
+
console.log(chalk28.blue.bold(`
|
|
4602
4917
|
\u{1F4CB} Environment Variables by File (${packageName})
|
|
4603
4918
|
`));
|
|
4604
4919
|
for (const [file, vars] of Object.entries(grouped)) {
|
|
4605
|
-
console.log(
|
|
4920
|
+
console.log(chalk28.bold.magenta(`
|
|
4606
4921
|
${file}`));
|
|
4607
|
-
console.log(
|
|
4922
|
+
console.log(chalk28.dim("\u2500".repeat(50)));
|
|
4608
4923
|
for (const [key, schema] of vars) {
|
|
4609
4924
|
printEnvVar(key, schema);
|
|
4610
4925
|
}
|
|
4611
4926
|
}
|
|
4612
4927
|
} else {
|
|
4613
|
-
console.log(
|
|
4928
|
+
console.log(chalk28.blue.bold(`
|
|
4614
4929
|
\u{1F4CB} Environment Variables (${packageName})
|
|
4615
4930
|
`));
|
|
4616
4931
|
for (const [key, schema] of allVars) {
|
|
4617
4932
|
printEnvVar(key, schema, true);
|
|
4618
4933
|
}
|
|
4619
4934
|
}
|
|
4620
|
-
console.log(
|
|
4935
|
+
console.log(chalk28.dim("\n\u{1F4A1} Tip: Use `spfn env init` to generate .env template files\n"));
|
|
4621
4936
|
} catch (error) {
|
|
4622
|
-
console.error(
|
|
4937
|
+
console.error(chalk28.red(`
|
|
4623
4938
|
\u274C ${error instanceof Error ? error.message : "Unknown error"}
|
|
4624
4939
|
`));
|
|
4625
4940
|
process.exit(1);
|
|
@@ -4627,17 +4942,17 @@ ${file}`));
|
|
|
4627
4942
|
}
|
|
4628
4943
|
function printEnvVar(key, schema, showFile = false) {
|
|
4629
4944
|
const typeStr = formatType(schema.type);
|
|
4630
|
-
const requiredStr = schema.required || schema.default !== void 0 ?
|
|
4631
|
-
const sensitiveStr = schema.sensitive ?
|
|
4632
|
-
const fileStr = showFile ?
|
|
4633
|
-
console.log(`${
|
|
4634
|
-
console.log(` ${
|
|
4945
|
+
const requiredStr = schema.required || schema.default !== void 0 ? chalk28.red("[required]") : chalk28.dim("[optional]");
|
|
4946
|
+
const sensitiveStr = schema.sensitive ? chalk28.yellow(" [sensitive]") : "";
|
|
4947
|
+
const fileStr = showFile ? chalk28.dim(` \u2192 ${getTargetFile(schema)}`) : "";
|
|
4948
|
+
console.log(`${chalk28.bold.cyan(key)} ${chalk28.dim("(")}${typeStr}${chalk28.dim(")")} ${requiredStr}${sensitiveStr}${fileStr}`);
|
|
4949
|
+
console.log(` ${chalk28.dim(schema.description)}`);
|
|
4635
4950
|
if (schema.default !== void 0) {
|
|
4636
|
-
console.log(` ${
|
|
4951
|
+
console.log(` ${chalk28.dim("Default:")} ${formatDefault(schema.default, schema.type)}`);
|
|
4637
4952
|
}
|
|
4638
4953
|
if (schema.examples && schema.examples.length > 0) {
|
|
4639
4954
|
const exampleStr = schema.examples.map((ex) => formatDefault(ex, schema.type)).join(", ");
|
|
4640
|
-
console.log(` ${
|
|
4955
|
+
console.log(` ${chalk28.dim("Examples:")} ${exampleStr}`);
|
|
4641
4956
|
}
|
|
4642
4957
|
console.log();
|
|
4643
4958
|
}
|
|
@@ -4645,7 +4960,7 @@ async function showEnvStats(options) {
|
|
|
4645
4960
|
const packageName = options.package || "@spfn/core";
|
|
4646
4961
|
try {
|
|
4647
4962
|
const envSchema = await loadEnvSchema(packageName);
|
|
4648
|
-
console.log(
|
|
4963
|
+
console.log(chalk28.blue.bold(`
|
|
4649
4964
|
\u{1F4CA} Environment Variable Statistics (${packageName})
|
|
4650
4965
|
`));
|
|
4651
4966
|
const allVars = Object.entries(envSchema);
|
|
@@ -4667,24 +4982,24 @@ async function showEnvStats(options) {
|
|
|
4667
4982
|
acc[file] = (acc[file] || 0) + 1;
|
|
4668
4983
|
return acc;
|
|
4669
4984
|
}, {});
|
|
4670
|
-
console.log(`${
|
|
4671
|
-
console.log(`${
|
|
4672
|
-
console.log(`${
|
|
4673
|
-
console.log(`${
|
|
4674
|
-
console.log(
|
|
4675
|
-
console.log(` ${
|
|
4676
|
-
console.log(` ${
|
|
4677
|
-
console.log(
|
|
4985
|
+
console.log(`${chalk28.bold("Total variables:")} ${chalk28.cyan(allVars.length)}`);
|
|
4986
|
+
console.log(`${chalk28.bold("Required:")} ${chalk28.red(required.length)}`);
|
|
4987
|
+
console.log(`${chalk28.bold("Optional:")} ${chalk28.dim(optional.length)}`);
|
|
4988
|
+
console.log(`${chalk28.bold("Sensitive:")} ${chalk28.yellow(sensitive.length)}`);
|
|
4989
|
+
console.log(chalk28.bold("\nBy Target:"));
|
|
4990
|
+
console.log(` ${chalk28.blue("Next.js accessible:")} ${chalk28.cyan(nextjsVars.length)}`);
|
|
4991
|
+
console.log(` ${chalk28.magenta("SPFN server only:")} ${chalk28.cyan(serverOnlyVars.length)}`);
|
|
4992
|
+
console.log(chalk28.bold("\nBy File:"));
|
|
4678
4993
|
for (const [file, count] of Object.entries(fileCount)) {
|
|
4679
|
-
console.log(` ${
|
|
4994
|
+
console.log(` ${chalk28.dim(file)}: ${chalk28.cyan(count)}`);
|
|
4680
4995
|
}
|
|
4681
|
-
console.log(
|
|
4996
|
+
console.log(chalk28.bold("\nBy Type:"));
|
|
4682
4997
|
for (const [type, count] of Object.entries(typeCount)) {
|
|
4683
|
-
console.log(` ${formatType(type)}: ${
|
|
4998
|
+
console.log(` ${formatType(type)}: ${chalk28.cyan(count)}`);
|
|
4684
4999
|
}
|
|
4685
5000
|
console.log();
|
|
4686
5001
|
} catch (error) {
|
|
4687
|
-
console.error(
|
|
5002
|
+
console.error(chalk28.red(`
|
|
4688
5003
|
\u274C ${error instanceof Error ? error.message : "Unknown error"}
|
|
4689
5004
|
`));
|
|
4690
5005
|
process.exit(1);
|
|
@@ -4704,26 +5019,26 @@ async function searchEnvVars(query, options) {
|
|
|
4704
5019
|
}
|
|
4705
5020
|
}
|
|
4706
5021
|
if (results.length === 0) {
|
|
4707
|
-
console.log(
|
|
5022
|
+
console.log(chalk28.yellow(`
|
|
4708
5023
|
\u26A0\uFE0F No environment variables found matching "${query}"
|
|
4709
5024
|
`));
|
|
4710
5025
|
return;
|
|
4711
5026
|
}
|
|
4712
|
-
console.log(
|
|
5027
|
+
console.log(chalk28.blue.bold(`
|
|
4713
5028
|
\u{1F50D} Found ${results.length} environment variable(s) matching "${query}"
|
|
4714
5029
|
`));
|
|
4715
5030
|
for (const [key, schema] of results) {
|
|
4716
5031
|
const typeStr = formatType(schema.type);
|
|
4717
|
-
const requiredStr = schema.required || schema.default !== void 0 ?
|
|
4718
|
-
console.log(`${
|
|
4719
|
-
console.log(` ${
|
|
5032
|
+
const requiredStr = schema.required || schema.default !== void 0 ? chalk28.red("[required]") : chalk28.dim("[optional]");
|
|
5033
|
+
console.log(`${chalk28.bold.cyan(key)} ${chalk28.dim("(")}${typeStr}${chalk28.dim(")")} ${requiredStr}`);
|
|
5034
|
+
console.log(` ${chalk28.dim(schema.description)}`);
|
|
4720
5035
|
if (schema.default !== void 0) {
|
|
4721
|
-
console.log(` ${
|
|
5036
|
+
console.log(` ${chalk28.dim("Default:")} ${formatDefault(schema.default, schema.type)}`);
|
|
4722
5037
|
}
|
|
4723
5038
|
console.log();
|
|
4724
5039
|
}
|
|
4725
5040
|
} catch (error) {
|
|
4726
|
-
console.error(
|
|
5041
|
+
console.error(chalk28.red(`
|
|
4727
5042
|
\u274C ${error instanceof Error ? error.message : "Unknown error"}
|
|
4728
5043
|
`));
|
|
4729
5044
|
process.exit(1);
|
|
@@ -4735,9 +5050,9 @@ envCommand.command("stats").description("Show environment variable statistics").
|
|
|
4735
5050
|
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);
|
|
4736
5051
|
function validateEnvOption(envValue) {
|
|
4737
5052
|
if (!VALID_ENVS.includes(envValue)) {
|
|
4738
|
-
console.error(
|
|
5053
|
+
console.error(chalk28.red(`
|
|
4739
5054
|
\u274C Invalid environment: "${envValue}"`));
|
|
4740
|
-
console.log(
|
|
5055
|
+
console.log(chalk28.dim(` Valid values: ${VALID_ENVS.join(", ")}
|
|
4741
5056
|
`));
|
|
4742
5057
|
process.exit(1);
|
|
4743
5058
|
}
|
|
@@ -4758,8 +5073,8 @@ async function initEnvFiles(options) {
|
|
|
4758
5073
|
return acc;
|
|
4759
5074
|
}, {});
|
|
4760
5075
|
if (targetEnv) {
|
|
4761
|
-
console.log(
|
|
4762
|
-
\u{1F680} Generating .env template files for ${
|
|
5076
|
+
console.log(chalk28.blue.bold(`
|
|
5077
|
+
\u{1F680} Generating .env template files for ${chalk28.cyan(targetEnv)} environment
|
|
4763
5078
|
`));
|
|
4764
5079
|
const envSpecificFiles = {};
|
|
4765
5080
|
const committedVars = allVars.filter(([_, schema]) => !schema.sensitive);
|
|
@@ -4775,25 +5090,25 @@ async function initEnvFiles(options) {
|
|
|
4775
5090
|
writeEnvTemplate(cwd, file, vars, options.force ?? false);
|
|
4776
5091
|
}
|
|
4777
5092
|
} else {
|
|
4778
|
-
console.log(
|
|
5093
|
+
console.log(chalk28.blue.bold(`
|
|
4779
5094
|
\u{1F680} Generating .env template files
|
|
4780
5095
|
`));
|
|
4781
5096
|
for (const [file, vars] of Object.entries(grouped)) {
|
|
4782
5097
|
writeEnvTemplate(cwd, file, vars, options.force ?? false);
|
|
4783
5098
|
}
|
|
4784
5099
|
}
|
|
4785
|
-
console.log(
|
|
4786
|
-
console.log(
|
|
4787
|
-
console.log(
|
|
4788
|
-
console.log(
|
|
4789
|
-
console.log(
|
|
5100
|
+
console.log(chalk28.dim("\n\u{1F4A1} Copy .example files to create your actual .env files:"));
|
|
5101
|
+
console.log(chalk28.dim(" cp .env.example .env"));
|
|
5102
|
+
console.log(chalk28.dim(" cp .env.local.example .env.local"));
|
|
5103
|
+
console.log(chalk28.dim(" cp .env.server.example .env.server"));
|
|
5104
|
+
console.log(chalk28.dim(" cp .env.server.local.example .env.server.local"));
|
|
4790
5105
|
if (targetEnv) {
|
|
4791
|
-
console.log(
|
|
4792
|
-
console.log(
|
|
5106
|
+
console.log(chalk28.dim(` cp .env.${targetEnv}.example .env.${targetEnv}`));
|
|
5107
|
+
console.log(chalk28.dim(` cp .env.${targetEnv}.local.example .env.${targetEnv}.local`));
|
|
4793
5108
|
}
|
|
4794
5109
|
console.log("");
|
|
4795
5110
|
} catch (error) {
|
|
4796
|
-
console.error(
|
|
5111
|
+
console.error(chalk28.red(`
|
|
4797
5112
|
\u274C ${error instanceof Error ? error.message : "Unknown error"}
|
|
4798
5113
|
`));
|
|
4799
5114
|
process.exit(1);
|
|
@@ -4801,12 +5116,12 @@ async function initEnvFiles(options) {
|
|
|
4801
5116
|
}
|
|
4802
5117
|
function writeEnvTemplate(cwd, file, vars, force) {
|
|
4803
5118
|
const filePath = resolve(cwd, file);
|
|
4804
|
-
if (
|
|
4805
|
-
console.log(
|
|
5119
|
+
if (existsSync25(filePath) && !force) {
|
|
5120
|
+
console.log(chalk28.yellow(` \u23ED\uFE0F ${file} already exists (use --force to overwrite)`));
|
|
4806
5121
|
return;
|
|
4807
5122
|
}
|
|
4808
|
-
|
|
4809
|
-
console.log(
|
|
5123
|
+
writeFileSync19(filePath, generateEnvFileContent(vars), "utf-8");
|
|
5124
|
+
console.log(chalk28.green(` \u2705 ${file} (${vars.length} variables)`));
|
|
4810
5125
|
}
|
|
4811
5126
|
function generateEnvFileContent(vars) {
|
|
4812
5127
|
const lines = [
|
|
@@ -4841,7 +5156,7 @@ async function checkEnvFiles(options) {
|
|
|
4841
5156
|
const envSchema = await loadEnvSchema(packageName);
|
|
4842
5157
|
const allVars = Object.entries(envSchema);
|
|
4843
5158
|
const envLabel = targetEnv ? ` (${targetEnv})` : "";
|
|
4844
|
-
console.log(
|
|
5159
|
+
console.log(chalk28.blue.bold(`
|
|
4845
5160
|
\u{1F50D} Checking .env files against schema${envLabel}
|
|
4846
5161
|
`));
|
|
4847
5162
|
const filesToCheck = targetEnv ? getEnvFilesForEnvironment(targetEnv) : [...BASE_ENV_FILES.nextjs, ...BASE_ENV_FILES.server];
|
|
@@ -4850,15 +5165,15 @@ async function checkEnvFiles(options) {
|
|
|
4850
5165
|
const warnings = [];
|
|
4851
5166
|
for (const file of filesToCheck) {
|
|
4852
5167
|
const filePath = resolve(cwd, file);
|
|
4853
|
-
if (!
|
|
5168
|
+
if (!existsSync25(filePath)) {
|
|
4854
5169
|
continue;
|
|
4855
5170
|
}
|
|
4856
|
-
const content =
|
|
5171
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
4857
5172
|
const parsed = parse(content);
|
|
4858
5173
|
for (const [key, value] of Object.entries(parsed)) {
|
|
4859
5174
|
loadedEnv[key] = { value: value || "", file };
|
|
4860
5175
|
}
|
|
4861
|
-
console.log(
|
|
5176
|
+
console.log(chalk28.dim(` \u{1F4C4} ${file} loaded`));
|
|
4862
5177
|
}
|
|
4863
5178
|
console.log("");
|
|
4864
5179
|
for (const [key, schema] of allVars) {
|
|
@@ -4866,7 +5181,7 @@ async function checkEnvFiles(options) {
|
|
|
4866
5181
|
const found = loadedEnv[key];
|
|
4867
5182
|
if (!found) {
|
|
4868
5183
|
if (schema.required && schema.default === void 0) {
|
|
4869
|
-
issues.push(`${
|
|
5184
|
+
issues.push(`${chalk28.red("\u2717")} ${chalk28.cyan(key)} is required but not found in any .env file`);
|
|
4870
5185
|
}
|
|
4871
5186
|
continue;
|
|
4872
5187
|
}
|
|
@@ -4876,11 +5191,11 @@ async function checkEnvFiles(options) {
|
|
|
4876
5191
|
if (!shouldBeNextjs && isNextjsFile && !isServerFile) {
|
|
4877
5192
|
if (schema.sensitive) {
|
|
4878
5193
|
issues.push(
|
|
4879
|
-
`${
|
|
5194
|
+
`${chalk28.red("\u2717")} ${chalk28.cyan(key)} is sensitive and should be in ${chalk28.magenta(expectedFile)}, but found in ${chalk28.yellow(found.file)} (security risk!)`
|
|
4880
5195
|
);
|
|
4881
5196
|
} else {
|
|
4882
5197
|
warnings.push(
|
|
4883
|
-
`${
|
|
5198
|
+
`${chalk28.yellow("\u26A0")} ${chalk28.cyan(key)} should be in ${chalk28.magenta(expectedFile)}, but found in ${chalk28.dim(found.file)}`
|
|
4884
5199
|
);
|
|
4885
5200
|
}
|
|
4886
5201
|
}
|
|
@@ -4888,34 +5203,34 @@ async function checkEnvFiles(options) {
|
|
|
4888
5203
|
for (const [key, { file }] of Object.entries(loadedEnv)) {
|
|
4889
5204
|
const inSchema = allVars.some(([k]) => k === key);
|
|
4890
5205
|
if (!inSchema) {
|
|
4891
|
-
warnings.push(`${
|
|
5206
|
+
warnings.push(`${chalk28.yellow("\u26A0")} ${chalk28.cyan(key)} in ${chalk28.dim(file)} is not in schema`);
|
|
4892
5207
|
}
|
|
4893
5208
|
}
|
|
4894
5209
|
if (issues.length > 0) {
|
|
4895
|
-
console.log(
|
|
5210
|
+
console.log(chalk28.red.bold("Issues:"));
|
|
4896
5211
|
for (const issue of issues) {
|
|
4897
5212
|
console.log(` ${issue}`);
|
|
4898
5213
|
}
|
|
4899
5214
|
console.log("");
|
|
4900
5215
|
}
|
|
4901
5216
|
if (warnings.length > 0) {
|
|
4902
|
-
console.log(
|
|
5217
|
+
console.log(chalk28.yellow.bold("Warnings:"));
|
|
4903
5218
|
for (const warning of warnings) {
|
|
4904
5219
|
console.log(` ${warning}`);
|
|
4905
5220
|
}
|
|
4906
5221
|
console.log("");
|
|
4907
5222
|
}
|
|
4908
5223
|
if (issues.length === 0 && warnings.length === 0) {
|
|
4909
|
-
console.log(
|
|
5224
|
+
console.log(chalk28.green("\u2705 All environment variables are correctly configured!\n"));
|
|
4910
5225
|
} else {
|
|
4911
|
-
console.log(
|
|
5226
|
+
console.log(chalk28.dim(`Found ${issues.length} issue(s) and ${warnings.length} warning(s)
|
|
4912
5227
|
`));
|
|
4913
5228
|
if (issues.length > 0) {
|
|
4914
5229
|
process.exit(1);
|
|
4915
5230
|
}
|
|
4916
5231
|
}
|
|
4917
5232
|
} catch (error) {
|
|
4918
|
-
console.error(
|
|
5233
|
+
console.error(chalk28.red(`
|
|
4919
5234
|
\u274C ${error instanceof Error ? error.message : "Unknown error"}
|
|
4920
5235
|
`));
|
|
4921
5236
|
process.exit(1);
|
|
@@ -4927,17 +5242,17 @@ async function validateEnvVars(options) {
|
|
|
4927
5242
|
const packages = options.packages || ["@spfn/core"];
|
|
4928
5243
|
const targetEnv = options.env ? validateEnvOption(options.env) : void 0;
|
|
4929
5244
|
if (targetEnv) {
|
|
4930
|
-
const { loadEnv:
|
|
4931
|
-
const result =
|
|
4932
|
-
console.log(
|
|
4933
|
-
\u{1F50D} Validating environment variables for ${
|
|
5245
|
+
const { loadEnv: loadEnv8 } = await import("@spfn/core/env/loader");
|
|
5246
|
+
const result = loadEnv8({ nodeEnv: targetEnv });
|
|
5247
|
+
console.log(chalk28.blue.bold(`
|
|
5248
|
+
\u{1F50D} Validating environment variables for ${chalk28.cyan(targetEnv)}
|
|
4934
5249
|
`));
|
|
4935
5250
|
if (result.loadedFiles.length > 0) {
|
|
4936
|
-
console.log(
|
|
5251
|
+
console.log(chalk28.dim(` Loaded: ${result.loadedFiles.join(", ")}`));
|
|
4937
5252
|
}
|
|
4938
5253
|
console.log("");
|
|
4939
5254
|
} else {
|
|
4940
|
-
console.log(
|
|
5255
|
+
console.log(chalk28.blue.bold(`
|
|
4941
5256
|
\u{1F50D} Validating environment variables
|
|
4942
5257
|
`));
|
|
4943
5258
|
}
|
|
@@ -4945,7 +5260,7 @@ async function validateEnvVars(options) {
|
|
|
4945
5260
|
const allWarnings = [];
|
|
4946
5261
|
for (const packageName of packages) {
|
|
4947
5262
|
try {
|
|
4948
|
-
console.log(
|
|
5263
|
+
console.log(chalk28.dim(` \u{1F4E6} ${packageName}`));
|
|
4949
5264
|
const envSchema = await loadEnvSchema(packageName);
|
|
4950
5265
|
const { createEnvRegistry } = await import("@spfn/core/env");
|
|
4951
5266
|
const registry = createEnvRegistry(envSchema);
|
|
@@ -4958,10 +5273,10 @@ async function validateEnvVars(options) {
|
|
|
4958
5273
|
}
|
|
4959
5274
|
} catch (error) {
|
|
4960
5275
|
if (error instanceof Error && error.message.includes("does not export envSchema")) {
|
|
4961
|
-
console.log(
|
|
5276
|
+
console.log(chalk28.dim(` \u23ED\uFE0F No envSchema exported, skipping`));
|
|
4962
5277
|
continue;
|
|
4963
5278
|
}
|
|
4964
|
-
console.error(
|
|
5279
|
+
console.error(chalk28.red(` \u274C Failed to load: ${error instanceof Error ? error.message : String(error)}`));
|
|
4965
5280
|
if (options.strict) {
|
|
4966
5281
|
process.exit(1);
|
|
4967
5282
|
}
|
|
@@ -4969,32 +5284,32 @@ async function validateEnvVars(options) {
|
|
|
4969
5284
|
}
|
|
4970
5285
|
console.log("");
|
|
4971
5286
|
if (allErrors.length > 0) {
|
|
4972
|
-
console.log(
|
|
5287
|
+
console.log(chalk28.red.bold(`\u274C Validation Errors (${allErrors.length}):
|
|
4973
5288
|
`));
|
|
4974
5289
|
for (const error of allErrors) {
|
|
4975
|
-
console.log(` ${
|
|
4976
|
-
console.log(` ${
|
|
4977
|
-
console.log(` ${
|
|
5290
|
+
console.log(` ${chalk28.red("\u2717")} ${chalk28.cyan(error.key)}`);
|
|
5291
|
+
console.log(` ${chalk28.dim(error.message)}`);
|
|
5292
|
+
console.log(` ${chalk28.dim(`from ${error.package}`)}`);
|
|
4978
5293
|
console.log("");
|
|
4979
5294
|
}
|
|
4980
5295
|
}
|
|
4981
5296
|
if (allWarnings.length > 0) {
|
|
4982
|
-
console.log(
|
|
5297
|
+
console.log(chalk28.yellow.bold(`\u26A0\uFE0F Warnings (${allWarnings.length}):
|
|
4983
5298
|
`));
|
|
4984
5299
|
for (const warning of allWarnings) {
|
|
4985
|
-
console.log(` ${
|
|
4986
|
-
console.log(` ${
|
|
5300
|
+
console.log(` ${chalk28.yellow("\u26A0")} ${chalk28.cyan(warning.key)}`);
|
|
5301
|
+
console.log(` ${chalk28.dim(warning.message)}`);
|
|
4987
5302
|
console.log("");
|
|
4988
5303
|
}
|
|
4989
5304
|
}
|
|
4990
5305
|
if (allErrors.length === 0 && allWarnings.length === 0) {
|
|
4991
|
-
console.log(
|
|
5306
|
+
console.log(chalk28.green.bold("\u2705 All environment variables are valid!\n"));
|
|
4992
5307
|
} else if (allErrors.length === 0) {
|
|
4993
|
-
console.log(
|
|
4994
|
-
console.log(
|
|
5308
|
+
console.log(chalk28.green("\u2705 No errors found."));
|
|
5309
|
+
console.log(chalk28.yellow(`\u26A0\uFE0F ${allWarnings.length} warning(s) found.
|
|
4995
5310
|
`));
|
|
4996
5311
|
} else {
|
|
4997
|
-
console.log(
|
|
5312
|
+
console.log(chalk28.red(`
|
|
4998
5313
|
\u274C Validation failed with ${allErrors.length} error(s)
|
|
4999
5314
|
`));
|
|
5000
5315
|
process.exit(1);
|