spfn 0.2.0-beta.67 → 0.2.0-beta.68

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.
Files changed (3) hide show
  1. package/README.md +46 -4
  2. package/dist/index.js +349 -455
  3. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -922,7 +922,7 @@ var init_deployment_config = __esm({
922
922
 
923
923
  // src/utils/version.ts
924
924
  function getCliVersion() {
925
- return "0.2.0-beta.67";
925
+ return "0.2.0-beta.68";
926
926
  }
927
927
  function getTagFromVersion(version) {
928
928
  const match = version.match(/-([a-z]+)\./i);
@@ -955,19 +955,19 @@ async function setupPackageJson(cwd, packageJsonPath, packageJson, packageManage
955
955
  packageJson.dependencies["pg"] = "^8.16.3";
956
956
  packageJson.dependencies["spfn"] = spfnTag;
957
957
  packageJson.dependencies["concurrently"] = "^9.2.1";
958
+ packageJson.engines = packageJson.engines || {};
959
+ const existingNodeRange = packageJson.engines.node;
960
+ if (!existingNodeRange || !requiresNode20OrNewer(existingNodeRange)) {
961
+ packageJson.engines.node = ">=20.0.0";
962
+ if (existingNodeRange) {
963
+ logger.warn(`Updated engines.node from "${existingNodeRange}" to ">=20.0.0" because @spfn/core requires Node 20`);
964
+ }
965
+ }
958
966
  if (mode === "full") {
959
967
  packageJson.dependencies["@spfn/auth"] = spfnTag;
960
968
  packageJson.dependencies["@spfn/i18n"] = spfnTag;
961
969
  packageJson.dependencies["@spfn/mcp"] = spfnTag;
962
970
  packageJson.dependencies["@spfn/notification"] = spfnTag;
963
- packageJson.engines = packageJson.engines || {};
964
- const existingNodeRange = packageJson.engines.node;
965
- if (!existingNodeRange || !requiresNode20OrNewer(existingNodeRange)) {
966
- packageJson.engines.node = ">=20.0.0";
967
- if (existingNodeRange) {
968
- logger.warn(`Updated engines.node from "${existingNodeRange}" to ">=20.0.0" because full mode includes @spfn/mcp`);
969
- }
970
- }
971
971
  }
972
972
  packageJson.devDependencies["@types/node"] = "^20.11.0";
973
973
  packageJson.devDependencies["tsx"] = "^4.20.6";
@@ -1515,6 +1515,82 @@ var init_init = __esm({
1515
1515
  }
1516
1516
  });
1517
1517
 
1518
+ // src/utils/migration-status.ts
1519
+ var migration_status_exports = {};
1520
+ __export(migration_status_exports, {
1521
+ checkPendingMigrationsBeforeStart: () => checkPendingMigrationsBeforeStart,
1522
+ filterPendingEntries: () => filterPendingEntries,
1523
+ functionMigrationsTable: () => functionMigrationsTable,
1524
+ getMigrationStatus: () => getMigrationStatus,
1525
+ migrationTargets: () => migrationTargets,
1526
+ pendingMigrationTargets: () => pendingMigrationTargets2
1527
+ });
1528
+ import chalk6 from "chalk";
1529
+ import {
1530
+ collectMigrationStatus,
1531
+ formatPendingMigrations,
1532
+ pendingMigrationTargets,
1533
+ pendingMigrationsSummary,
1534
+ RUN_MIGRATIONS_HINT
1535
+ } from "@spfn/core/db";
1536
+ import { env } from "@spfn/core/config";
1537
+ import { loadEnv } from "@spfn/core/server";
1538
+ import {
1539
+ filterPendingEntries,
1540
+ functionMigrationsTable,
1541
+ migrationTargets,
1542
+ pendingMigrationTargets as pendingMigrationTargets2
1543
+ } from "@spfn/core/db";
1544
+ async function getMigrationStatus(cwd = process.cwd(), databaseUrl) {
1545
+ loadEnv();
1546
+ const url = databaseUrl ?? env.DATABASE_URL;
1547
+ if (!url) {
1548
+ throw new Error("DATABASE_URL not found in environment");
1549
+ }
1550
+ const { drizzle } = await import("drizzle-orm/postgres-js");
1551
+ const postgres = await import("postgres");
1552
+ const connection = postgres.default(url, { max: 1, connect_timeout: 5 });
1553
+ try {
1554
+ return await collectMigrationStatus(drizzle({ client: connection }), cwd);
1555
+ } finally {
1556
+ await connection.end();
1557
+ }
1558
+ }
1559
+ async function checkPendingMigrationsBeforeStart(cwd, databaseUrl, flagAllowsPending) {
1560
+ loadEnv();
1561
+ const allowPending = flagAllowsPending || env.SPFN_ALLOW_PENDING_MIGRATIONS === true;
1562
+ let status;
1563
+ try {
1564
+ status = await getMigrationStatus(cwd, databaseUrl);
1565
+ } catch {
1566
+ return { block: false, allowPending };
1567
+ }
1568
+ const targets = pendingMigrationTargets(status);
1569
+ if (targets.length === 0) {
1570
+ return { block: false, allowPending };
1571
+ }
1572
+ const lines = formatPendingMigrations(targets);
1573
+ console.log("");
1574
+ if (allowPending) {
1575
+ console.warn(chalk6.yellow(`\u26A0\uFE0F Starting with pending migrations \u2014 ${pendingMigrationsSummary(targets)}`));
1576
+ lines.forEach((line) => console.warn(chalk6.yellow(` ${line}`)));
1577
+ console.warn(chalk6.yellow(` Requests hitting the missing columns will fail. ${RUN_MIGRATIONS_HINT}
1578
+ `));
1579
+ return { block: false, allowPending };
1580
+ }
1581
+ console.error(chalk6.red(`\u274C Refusing to start: ${pendingMigrationsSummary(targets)}`));
1582
+ lines.forEach((line) => console.error(chalk6.red(` ${line}`)));
1583
+ console.error(chalk6.yellow(`
1584
+ ${RUN_MIGRATIONS_HINT}`));
1585
+ console.error(chalk6.dim(" To start anyway: --allow-pending-migrations, or SPFN_ALLOW_PENDING_MIGRATIONS=true\n"));
1586
+ return { block: true, allowPending };
1587
+ }
1588
+ var init_migration_status = __esm({
1589
+ "src/utils/migration-status.ts"() {
1590
+ "use strict";
1591
+ }
1592
+ });
1593
+
1518
1594
  // src/utils/function-migrations.ts
1519
1595
  var function_migrations_exports = {};
1520
1596
  __export(function_migrations_exports, {
@@ -1524,120 +1600,20 @@ __export(function_migrations_exports, {
1524
1600
  loadFunctionMigrationPlans: () => loadFunctionMigrationPlans,
1525
1601
  readMigrationEntries: () => readMigrationEntries
1526
1602
  });
1527
- import chalk6 from "chalk";
1528
- import { createHash } from "crypto";
1529
- import { join as join15 } from "path";
1530
- import { env } from "@spfn/core/config";
1531
- import { loadEnv } from "@spfn/core/server";
1532
- import { existsSync as existsSync14, readdirSync, readFileSync as readFileSync7 } from "fs";
1533
- function discoverFunctionMigrations(cwd = process.cwd()) {
1534
- const nodeModulesPath = join15(cwd, "node_modules");
1535
- if (!existsSync14(nodeModulesPath)) {
1536
- return [];
1537
- }
1538
- const functions = [];
1539
- const spfnDir = join15(nodeModulesPath, "@spfn");
1540
- if (!existsSync14(spfnDir)) {
1541
- return [];
1542
- }
1543
- const packages = readdirSync(spfnDir);
1544
- for (const pkg of packages) {
1545
- const packagePath = join15(spfnDir, pkg);
1546
- const packageJsonPath = join15(packagePath, "package.json");
1547
- if (!existsSync14(packageJsonPath)) {
1548
- continue;
1549
- }
1550
- try {
1551
- const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
1552
- const spfnConfig = packageJson.spfn;
1553
- if (!spfnConfig?.migrations) {
1554
- continue;
1555
- }
1556
- const migrationsDir = join15(packagePath, spfnConfig.migrations.dir);
1557
- if (!existsSync14(migrationsDir)) {
1558
- console.warn(
1559
- chalk6.yellow(`\u26A0\uFE0F Package @spfn/${pkg} specifies migrations but directory not found: ${migrationsDir}`)
1560
- );
1561
- continue;
1562
- }
1563
- functions.push({
1564
- packageName: `@spfn/${pkg}`,
1565
- migrationsDir,
1566
- packagePath
1567
- });
1568
- } catch (error) {
1569
- console.warn(chalk6.yellow(`\u26A0\uFE0F Failed to parse package.json for @spfn/${pkg}`));
1570
- }
1571
- }
1572
- return functions;
1573
- }
1574
- function readMigrationEntries(migrationsDir, packageName) {
1575
- const journalPath = join15(migrationsDir, "meta", "_journal.json");
1576
- return existsSync14(journalPath) ? readJournalEntries(migrationsDir, journalPath, packageName) : readFolderEntries(migrationsDir, packageName);
1577
- }
1603
+ import chalk15 from "chalk";
1604
+ import {
1605
+ discoverFunctionMigrations,
1606
+ functionMigrationsTable as functionMigrationsTable2,
1607
+ readMigrationEntries
1608
+ } from "@spfn/core/db";
1609
+ import { env as env3 } from "@spfn/core/config";
1610
+ import { loadEnv as loadEnv3 } from "@spfn/core/server";
1578
1611
  function loadFunctionMigrationPlans(functionMigrations) {
1579
1612
  return functionMigrations.map((func) => ({
1580
1613
  ...func,
1581
1614
  entries: readMigrationEntries(func.migrationsDir, func.packageName)
1582
1615
  }));
1583
1616
  }
1584
- function readJournalEntries(migrationsDir, journalPath, packageName) {
1585
- let journal;
1586
- try {
1587
- journal = JSON.parse(readFileSync7(journalPath, "utf-8"));
1588
- } catch {
1589
- journal = {};
1590
- }
1591
- if (!Array.isArray(journal.entries)) {
1592
- throw new Error(`${packageName}: invalid migration journal at ${journalPath}`);
1593
- }
1594
- const entries = [...journal.entries];
1595
- entries.sort((a, b) => a.idx - b.idx);
1596
- return entries.map((entry) => {
1597
- if (typeof entry?.tag !== "string" || typeof entry?.when !== "number") {
1598
- throw new Error(`${packageName}: invalid journal entry in ${journalPath}`);
1599
- }
1600
- const sqlPath = join15(migrationsDir, `${entry.tag}.sql`);
1601
- if (!existsSync14(sqlPath)) {
1602
- throw new Error(`${packageName}: migration file not found: ${entry.tag}.sql`);
1603
- }
1604
- return toEntry(entry.tag, readFileSync7(sqlPath, "utf-8"), entry.when);
1605
- });
1606
- }
1607
- function readFolderEntries(migrationsDir, packageName) {
1608
- const folders = readdirSync(migrationsDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name).filter((name) => existsSync14(join15(migrationsDir, name, "migration.sql"))).sort((a, b) => a.localeCompare(b));
1609
- return folders.map((name) => toEntry(
1610
- name,
1611
- readFileSync7(join15(migrationsDir, name, "migration.sql"), "utf-8"),
1612
- folderTimestampMillis(name, packageName)
1613
- ));
1614
- }
1615
- function folderTimestampMillis(name, packageName) {
1616
- const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(name);
1617
- if (!match) {
1618
- throw new Error(`${packageName}: migration folder name must start with a YYYYMMDDHHMMSS timestamp: ${name}`);
1619
- }
1620
- const [, year, month, day, hour, minute, second] = match;
1621
- return Date.UTC(
1622
- Number(year),
1623
- Number(month) - 1,
1624
- Number(day),
1625
- Number(hour),
1626
- Number(minute),
1627
- Number(second)
1628
- );
1629
- }
1630
- function toEntry(name, content, millis) {
1631
- return {
1632
- name,
1633
- millis,
1634
- hash: createHash("sha256").update(content).digest("hex"),
1635
- statements: content.split("--> statement-breakpoint").map((statement) => statement.trim()).filter((statement) => statement.length > 0)
1636
- };
1637
- }
1638
- function functionMigrationsTable(packageName) {
1639
- return `__spfn_fn_${packageName.replace("@spfn/", "")}_migrations`;
1640
- }
1641
1617
  async function migrateLegacyTable(db, plans) {
1642
1618
  const legacyCheck = await db.query(
1643
1619
  `SELECT EXISTS (
@@ -1648,7 +1624,7 @@ async function migrateLegacyTable(db, plans) {
1648
1624
  if (!legacyCheck[0]?.exists) {
1649
1625
  return;
1650
1626
  }
1651
- console.log(chalk6.dim("\n Migrating legacy shared migration table to per-package tables..."));
1627
+ console.log(chalk15.dim("\n Migrating legacy shared migration table to per-package tables..."));
1652
1628
  const legacyRows = await db.query(
1653
1629
  `SELECT hash, created_at FROM drizzle."__spfn_fn_migrations" ORDER BY id`
1654
1630
  );
@@ -1658,7 +1634,7 @@ async function migrateLegacyTable(db, plans) {
1658
1634
  }
1659
1635
  const legacyHashes = new Set(legacyRows.map((row) => row.hash));
1660
1636
  for (const plan of plans) {
1661
- const tableName = functionMigrationsTable(plan.packageName);
1637
+ const tableName = functionMigrationsTable2(plan.packageName);
1662
1638
  await ensureMigrationsTable(db, tableName);
1663
1639
  const existing = await db.query(
1664
1640
  `SELECT COUNT(*) AS "count" FROM drizzle."${tableName}"`
@@ -1678,11 +1654,11 @@ async function migrateLegacyTable(db, plans) {
1678
1654
  copied++;
1679
1655
  }
1680
1656
  if (copied > 0) {
1681
- console.log(chalk6.dim(` \u2713 ${plan.packageName}: copied ${copied} migration record(s)`));
1657
+ console.log(chalk15.dim(` \u2713 ${plan.packageName}: copied ${copied} migration record(s)`));
1682
1658
  }
1683
1659
  }
1684
1660
  await db.query(`DROP TABLE drizzle."__spfn_fn_migrations"`);
1685
- console.log(chalk6.dim(" \u2713 Legacy migration table removed\n"));
1661
+ console.log(chalk15.dim(" \u2713 Legacy migration table removed\n"));
1686
1662
  }
1687
1663
  async function ensureMigrationsTable(db, tableName) {
1688
1664
  await db.query(`CREATE SCHEMA IF NOT EXISTS drizzle`);
@@ -1695,7 +1671,7 @@ async function ensureMigrationsTable(db, tableName) {
1695
1671
  );
1696
1672
  }
1697
1673
  async function applyFunctionMigrationPlan(db, plan) {
1698
- const tableName = functionMigrationsTable(plan.packageName);
1674
+ const tableName = functionMigrationsTable2(plan.packageName);
1699
1675
  await ensureMigrationsTable(db, tableName);
1700
1676
  const rows = await db.query(
1701
1677
  `SELECT created_at FROM drizzle."${tableName}" ORDER BY created_at DESC LIMIT 1`
@@ -1726,20 +1702,20 @@ function createPostgresJsMigrationDb(client) {
1726
1702
  }
1727
1703
  async function executeFunctionMigrations(plans) {
1728
1704
  const postgres = await import("postgres");
1729
- loadEnv();
1730
- if (!env.DATABASE_URL) {
1705
+ loadEnv3();
1706
+ if (!env3.DATABASE_URL) {
1731
1707
  throw new Error("DATABASE_URL not found in environment");
1732
1708
  }
1733
- const connection = postgres.default(env.DATABASE_URL, { max: 1 });
1709
+ const connection = postgres.default(env3.DATABASE_URL, { max: 1 });
1734
1710
  const db = createPostgresJsMigrationDb(connection);
1735
1711
  let appliedCount = 0;
1736
1712
  try {
1737
1713
  await migrateLegacyTable(db, plans);
1738
1714
  for (const plan of plans) {
1739
- console.log(chalk6.blue(`
1715
+ console.log(chalk15.blue(`
1740
1716
  \u{1F4E6} Running ${plan.packageName} migrations...`));
1741
1717
  const applied = await applyFunctionMigrationPlan(db, plan);
1742
- console.log(applied > 0 ? chalk6.green(` \u2713 ${plan.packageName}: ${applied} migration(s) applied`) : chalk6.dim(` \u2013 ${plan.packageName}: up to date`));
1718
+ console.log(applied > 0 ? chalk15.green(` \u2713 ${plan.packageName}: ${applied} migration(s) applied`) : chalk15.dim(` \u2013 ${plan.packageName}: up to date`));
1743
1719
  appliedCount += applied;
1744
1720
  }
1745
1721
  } finally {
@@ -1753,112 +1729,6 @@ var init_function_migrations = __esm({
1753
1729
  }
1754
1730
  });
1755
1731
 
1756
- // src/utils/migration-status.ts
1757
- var migration_status_exports = {};
1758
- __export(migration_status_exports, {
1759
- filterPendingEntries: () => filterPendingEntries,
1760
- functionMigrationsTable: () => functionMigrationsTable2,
1761
- getMigrationStatus: () => getMigrationStatus,
1762
- warnPendingMigrations: () => warnPendingMigrations
1763
- });
1764
- import { join as join16 } from "path";
1765
- import { existsSync as existsSync15 } from "fs";
1766
- import chalk7 from "chalk";
1767
- import { env as env2 } from "@spfn/core/config";
1768
- import { loadEnv as loadEnv2 } from "@spfn/core/server";
1769
- function functionMigrationsTable2(packageName) {
1770
- return `__spfn_fn_${packageName.replace("@spfn/", "")}_migrations`;
1771
- }
1772
- function filterPendingEntries(entries, lastAppliedMillis, appliedNames) {
1773
- return entries.filter((entry) => entry.millis > lastAppliedMillis && !appliedNames.has(entry.name));
1774
- }
1775
- async function readAppliedNames(sql2, tableName) {
1776
- const columnCheck = await sql2`
1777
- SELECT EXISTS (
1778
- SELECT 1 FROM information_schema.columns
1779
- WHERE table_schema = 'drizzle' AND table_name = ${tableName} AND column_name = 'name'
1780
- ) AS "exists"`;
1781
- if (!columnCheck[0]?.exists) {
1782
- return /* @__PURE__ */ new Set();
1783
- }
1784
- const rows = await sql2`
1785
- SELECT name FROM drizzle.${sql2(tableName)} WHERE name IS NOT NULL`;
1786
- return new Set(rows.map((row) => row.name));
1787
- }
1788
- async function collectTargetStatus(sql2, name, migrationsDir, tableName) {
1789
- const entries = readMigrationEntries(migrationsDir, name);
1790
- const tableCheck = await sql2`
1791
- SELECT EXISTS (
1792
- SELECT 1 FROM information_schema.tables
1793
- WHERE table_schema = 'drizzle' AND table_name = ${tableName}
1794
- ) AS "exists"`;
1795
- let lastApplied = 0;
1796
- let appliedNames = /* @__PURE__ */ new Set();
1797
- if (tableCheck[0]?.exists) {
1798
- const rows = await sql2`
1799
- SELECT created_at FROM drizzle.${sql2(tableName)}
1800
- ORDER BY created_at DESC LIMIT 1`;
1801
- lastApplied = rows[0]?.created_at ? Number(rows[0].created_at) : 0;
1802
- appliedNames = await readAppliedNames(sql2, tableName);
1803
- }
1804
- const pendingEntries = filterPendingEntries(entries, lastApplied, appliedNames);
1805
- return {
1806
- name,
1807
- total: entries.length,
1808
- applied: entries.length - pendingEntries.length,
1809
- pending: pendingEntries.length,
1810
- pendingTags: pendingEntries.map((e) => e.name)
1811
- };
1812
- }
1813
- async function getMigrationStatus(cwd = process.cwd(), databaseUrl) {
1814
- loadEnv2();
1815
- const url = databaseUrl ?? env2.DATABASE_URL;
1816
- if (!url) {
1817
- throw new Error("DATABASE_URL not found in environment");
1818
- }
1819
- const postgres = await import("postgres");
1820
- const sql2 = postgres.default(url, { max: 1, connect_timeout: 5 });
1821
- try {
1822
- const packages = [];
1823
- for (const func of discoverFunctionMigrations(cwd)) {
1824
- packages.push(await collectTargetStatus(
1825
- sql2,
1826
- func.packageName,
1827
- func.migrationsDir,
1828
- functionMigrationsTable2(func.packageName)
1829
- ));
1830
- }
1831
- const projectDir = join16(cwd, "src", "server", "drizzle");
1832
- const project = existsSync15(projectDir) ? await collectTargetStatus(sql2, "project (src/server/drizzle)", projectDir, "__drizzle_migrations") : null;
1833
- return { packages, project: project && project.total > 0 ? project : null };
1834
- } finally {
1835
- await sql2.end();
1836
- }
1837
- }
1838
- async function warnPendingMigrations(cwd, databaseUrl) {
1839
- let status;
1840
- try {
1841
- status = await getMigrationStatus(cwd, databaseUrl);
1842
- } catch {
1843
- return;
1844
- }
1845
- const targets = [...status.packages, ...status.project ? [status.project] : []].filter((t) => t.pending > 0);
1846
- if (targets.length === 0) {
1847
- return;
1848
- }
1849
- console.warn("");
1850
- for (const target of targets) {
1851
- console.warn(chalk7.yellow(`\u26A0\uFE0F ${target.name}: ${target.pending} pending migration(s)`));
1852
- }
1853
- console.warn(chalk7.yellow(" Missing tables will fail at request time \u2014 run: pnpm spfn db migrate\n"));
1854
- }
1855
- var init_migration_status = __esm({
1856
- "src/utils/migration-status.ts"() {
1857
- "use strict";
1858
- init_function_migrations();
1859
- }
1860
- });
1861
-
1862
1732
  // src/index.ts
1863
1733
  import { Command as Command15 } from "commander";
1864
1734
 
@@ -2057,8 +1927,8 @@ init_init();
2057
1927
  init_logger();
2058
1928
  init_package_manager();
2059
1929
  import { Command as Command4 } from "commander";
2060
- import { existsSync as existsSync16, readFileSync as readFileSync8, writeFileSync as writeFileSync10, mkdirSync, unlinkSync, rmSync, watch } from "fs";
2061
- import { join as join17, relative, sep as sep2 } from "path";
1930
+ import { existsSync as existsSync14, readFileSync as readFileSync7, writeFileSync as writeFileSync10, mkdirSync, unlinkSync, rmSync, watch } from "fs";
1931
+ import { join as join15, relative, sep as sep2 } from "path";
2062
1932
  import { execa as execa6 } from "execa";
2063
1933
  import chokidar from "chokidar";
2064
1934
 
@@ -2257,32 +2127,32 @@ function ignoreDotfilesUnder(root) {
2257
2127
  }
2258
2128
  function waitForReadyFile(filePath, timeoutMs = 3e4) {
2259
2129
  return new Promise((resolve3, reject) => {
2260
- if (existsSync16(filePath)) {
2130
+ if (existsSync14(filePath)) {
2261
2131
  unlinkSync(filePath);
2262
2132
  }
2263
2133
  const timer = setTimeout(() => {
2264
2134
  watcher.close();
2265
2135
  reject(new Error(`Server did not become ready within ${timeoutMs / 1e3}s`));
2266
2136
  }, timeoutMs);
2267
- const dir = join17(filePath, "..");
2137
+ const dir = join15(filePath, "..");
2268
2138
  const fileName = filePath.split("/").pop();
2269
2139
  const watcher = watch(dir, (_event, name) => {
2270
- if (name === fileName && existsSync16(filePath)) {
2140
+ if (name === fileName && existsSync14(filePath)) {
2271
2141
  watcher.close();
2272
2142
  clearTimeout(timer);
2273
- resolve3(readFileSync8(filePath, "utf-8").trim());
2143
+ resolve3(readFileSync7(filePath, "utf-8").trim());
2274
2144
  }
2275
2145
  });
2276
2146
  });
2277
2147
  }
2278
- var devCommand = new Command4("dev").description("Start SPFN development server (detects and runs Next.js + Hono)").option("-p, --port <port>", "Server port").option("-H, --host <host>", "Server host").option("--routes <path>", "Routes directory path").option("--server-only", "Run only Hono server (skip Next.js)").option("--watch", "Enable hot reload (watch mode)").action(async (options) => {
2148
+ var devCommand = new Command4("dev").description("Start SPFN development server (detects and runs Next.js + Hono)").option("-p, --port <port>", "Server port").option("-H, --host <host>", "Server host").option("--routes <path>", "Routes directory path").option("--server-only", "Run only Hono server (skip Next.js)").option("--watch", "Enable hot reload (watch mode)").option("--allow-pending-migrations", "Start even when migrations are pending (they are listed as a warning)").action(async (options) => {
2279
2149
  process.setMaxListeners(20);
2280
2150
  if (!process.env.NODE_ENV) {
2281
2151
  process.env.NODE_ENV = "development";
2282
2152
  }
2283
2153
  const cwd = process.cwd();
2284
- const serverDir = join17(cwd, "src", "server");
2285
- if (!existsSync16(serverDir)) {
2154
+ const serverDir = join15(cwd, "src", "server");
2155
+ if (!existsSync14(serverDir)) {
2286
2156
  logger.error("src/server directory not found.");
2287
2157
  logger.info('Run "spfn init" first to initialize SPFN in your project.');
2288
2158
  process.exit(1);
@@ -2296,25 +2166,35 @@ var devCommand = new Command4("dev").description("Start SPFN development server
2296
2166
  logger.warn(`[SPFN] Could not resolve keychain secret(s): ${keychainMissing.join(", ")} \u2014 run \`spfn secret set <KEY>\``);
2297
2167
  }
2298
2168
  const serverEnv = { ...process.env, ...keychainEnv };
2299
- const { warnPendingMigrations: warnPendingMigrations2 } = await Promise.resolve().then(() => (init_migration_status(), migration_status_exports));
2300
- await warnPendingMigrations2(cwd, serverEnv.DATABASE_URL);
2301
- const packageJsonPath = join17(cwd, "package.json");
2169
+ const { checkPendingMigrationsBeforeStart: checkPendingMigrationsBeforeStart2 } = await Promise.resolve().then(() => (init_migration_status(), migration_status_exports));
2170
+ const migrationCheck = await checkPendingMigrationsBeforeStart2(
2171
+ cwd,
2172
+ serverEnv.DATABASE_URL,
2173
+ options.allowPendingMigrations === true
2174
+ );
2175
+ if (migrationCheck.block) {
2176
+ process.exit(1);
2177
+ }
2178
+ if (migrationCheck.allowPending) {
2179
+ serverEnv.SPFN_ALLOW_PENDING_MIGRATIONS = "true";
2180
+ }
2181
+ const packageJsonPath = join15(cwd, "package.json");
2302
2182
  let hasNext = false;
2303
- if (existsSync16(packageJsonPath)) {
2304
- const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
2183
+ if (existsSync14(packageJsonPath)) {
2184
+ const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
2305
2185
  hasNext = !!(packageJson.dependencies?.next || packageJson.devDependencies?.next);
2306
2186
  }
2307
- const tempDir = join17(cwd, ".spfn");
2308
- const serverEntry = join17(tempDir, "server.mjs");
2309
- const watcherEntry = join17(tempDir, "watcher.mjs");
2187
+ const tempDir = join15(cwd, ".spfn");
2188
+ const serverEntry = join15(tempDir, "server.mjs");
2189
+ const watcherEntry = join15(tempDir, "watcher.mjs");
2310
2190
  mkdirSync(tempDir, { recursive: true });
2311
- const serverBuildDir = join17(tempDir, "server");
2312
- if (existsSync16(serverBuildDir)) {
2191
+ const serverBuildDir = join15(tempDir, "server");
2192
+ if (existsSync14(serverBuildDir)) {
2313
2193
  rmSync(serverBuildDir, { recursive: true, force: true });
2314
2194
  logger.info("[SPFN] Cleaned stale build cache (.spfn/server)");
2315
2195
  }
2316
- const readySignal = join17(tempDir, "server-ready");
2317
- if (existsSync16(readySignal)) {
2196
+ const readySignal = join15(tempDir, "server-ready");
2197
+ if (existsSync14(readySignal)) {
2318
2198
  unlinkSync(readySignal);
2319
2199
  }
2320
2200
  const configParts = [];
@@ -2322,7 +2202,7 @@ var devCommand = new Command4("dev").description("Start SPFN development server
2322
2202
  if (options.host) configParts.push(`host: '${options.host}'`);
2323
2203
  if (options.routes) configParts.push(`routesPath: '${options.routes}'`);
2324
2204
  configParts.push("debug: true");
2325
- const readyFile = join17(tempDir, "server-ready");
2205
+ const readyFile = join15(tempDir, "server-ready");
2326
2206
  writeFileSync10(serverEntry, `
2327
2207
  import { writeFileSync } from 'fs';
2328
2208
 
@@ -2612,11 +2492,11 @@ catch (error)
2612
2492
  init_logger();
2613
2493
  init_package_manager();
2614
2494
  import { Command as Command5 } from "commander";
2615
- import { existsSync as existsSync17, writeFileSync as writeFileSync11, mkdirSync as mkdirSync2, readdirSync as readdirSync2 } from "fs";
2616
- import { join as join18 } from "path";
2495
+ import { existsSync as existsSync15, writeFileSync as writeFileSync11, mkdirSync as mkdirSync2, readdirSync } from "fs";
2496
+ import { join as join16 } from "path";
2617
2497
  import { execa as execa7 } from "execa";
2618
2498
  import ora5 from "ora";
2619
- import chalk8 from "chalk";
2499
+ import chalk7 from "chalk";
2620
2500
  import { build } from "tsup";
2621
2501
  async function buildProject(options) {
2622
2502
  if (!process.env.NODE_ENV) {
@@ -2627,17 +2507,17 @@ async function buildProject(options) {
2627
2507
  }
2628
2508
  const cwd = process.cwd();
2629
2509
  const pm = detectPackageManager(cwd);
2630
- const packageJsonPath = join18(cwd, "package.json");
2510
+ const packageJsonPath = join16(cwd, "package.json");
2631
2511
  let hasNext = false;
2632
- if (existsSync17(packageJsonPath)) {
2512
+ if (existsSync15(packageJsonPath)) {
2633
2513
  const packageJson = JSON.parse(await import("fs").then(
2634
2514
  (fs5) => fs5.promises.readFile(packageJsonPath, "utf-8")
2635
2515
  ));
2636
2516
  hasNext = !!(packageJson.dependencies?.next || packageJson.devDependencies?.next);
2637
2517
  }
2638
- const serverDir = join18(cwd, "src", "server");
2639
- const hasServer = existsSync17(serverDir);
2640
- console.log(chalk8.blue.bold("\n\u{1F3D7}\uFE0F Building SPFN project for production...\n"));
2518
+ const serverDir = join16(cwd, "src", "server");
2519
+ const hasServer = existsSync15(serverDir);
2520
+ console.log(chalk7.blue.bold("\n\u{1F3D7}\uFE0F Building SPFN project for production...\n"));
2641
2521
  if (hasServer) {
2642
2522
  const spinner = ora5("Generating API client...").start();
2643
2523
  try {
@@ -2678,10 +2558,10 @@ async function buildProject(options) {
2678
2558
  if (hasServer && !options.nextOnly) {
2679
2559
  const spinner = ora5("Building SPFN server...").start();
2680
2560
  try {
2681
- const outputDir = join18(cwd, ".spfn", "server");
2561
+ const outputDir = join16(cwd, ".spfn", "server");
2682
2562
  mkdirSync2(outputDir, { recursive: true });
2683
- const serverDir2 = join18(cwd, "src", "server");
2684
- if (!existsSync17(serverDir2)) {
2563
+ const serverDir2 = join16(cwd, "src", "server");
2564
+ if (!existsSync15(serverDir2)) {
2685
2565
  spinner.fail("SPFN server build failed");
2686
2566
  logger.error("src/server/ directory not found");
2687
2567
  logger.error('Please run "spfn init" to initialize the project.');
@@ -2709,7 +2589,7 @@ async function buildProject(options) {
2709
2589
  onSuccess: async () => {
2710
2590
  }
2711
2591
  });
2712
- const prodServerPath = join18(cwd, ".spfn", "prod-server.mjs");
2592
+ const prodServerPath = join16(cwd, ".spfn", "prod-server.mjs");
2713
2593
  const prodServerContent = `// Load environment variables FIRST (before any imports that depend on them)
2714
2594
  // Use centralized environment loader for standard dotenv priority
2715
2595
  const { env } = await import('@spfn/core/config');
@@ -2735,25 +2615,25 @@ await startServer({
2735
2615
  `;
2736
2616
  writeFileSync11(prodServerPath, prodServerContent);
2737
2617
  spinner.succeed(`SPFN server build completed \u2192 .spfn/server`);
2738
- const routesDir = join18(cwd, ".spfn", "server", "routes");
2739
- if (existsSync17(routesDir)) {
2618
+ const routesDir = join16(cwd, ".spfn", "server", "routes");
2619
+ if (existsSync15(routesDir)) {
2740
2620
  console.log();
2741
- console.log(chalk8.bold("Route (api)"));
2621
+ console.log(chalk7.bold("Route (api)"));
2742
2622
  try {
2743
- const routes = readdirSync2(routesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name).sort();
2623
+ const routes = readdirSync(routesDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name).sort();
2744
2624
  if (routes.length > 0) {
2745
2625
  routes.forEach((route, index) => {
2746
2626
  const isLast = index === routes.length - 1;
2747
2627
  const prefix = isLast ? "\u2514" : "\u251C";
2748
2628
  const routePath = `/api/${route}`;
2749
2629
  if (route === "health") {
2750
- console.log(`${prefix} ${chalk8.green("GET")} ${routePath}`);
2630
+ console.log(`${prefix} ${chalk7.green("GET")} ${routePath}`);
2751
2631
  } else {
2752
- console.log(`${prefix} ${chalk8.cyan("*")} ${routePath}`);
2632
+ console.log(`${prefix} ${chalk7.cyan("*")} ${routePath}`);
2753
2633
  }
2754
2634
  });
2755
2635
  console.log();
2756
- console.log(chalk8.cyan("*") + " (Hono) multiple methods supported");
2636
+ console.log(chalk7.cyan("*") + " (Hono) multiple methods supported");
2757
2637
  }
2758
2638
  } catch (error) {
2759
2639
  }
@@ -2761,9 +2641,9 @@ await startServer({
2761
2641
  } catch (error) {
2762
2642
  spinner.fail("SPFN server build failed");
2763
2643
  if (error instanceof Error) {
2764
- console.error("\n" + chalk8.red(error.message));
2644
+ console.error("\n" + chalk7.red(error.message));
2765
2645
  if (error.stack) {
2766
- console.error(chalk8.dim("\n" + error.stack));
2646
+ console.error(chalk7.dim("\n" + error.stack));
2767
2647
  }
2768
2648
  } else {
2769
2649
  logger.error(String(error));
@@ -2775,13 +2655,13 @@ await startServer({
2775
2655
  logger.error("No Next.js or SPFN server found in this project.");
2776
2656
  process.exit(1);
2777
2657
  }
2778
- console.log("\n" + chalk8.green.bold("\u2713 Build completed successfully!\n"));
2779
- console.log(chalk8.bold("Next steps:\n"));
2780
- console.log(" " + chalk8.cyan("Start production server:"));
2781
- console.log(` ${chalk8.cyan(getRunCommand(pm))} spfn:start ${chalk8.gray("# Start SPFN + Next.js")}
2658
+ console.log("\n" + chalk7.green.bold("\u2713 Build completed successfully!\n"));
2659
+ console.log(chalk7.bold("Next steps:\n"));
2660
+ console.log(" " + chalk7.cyan("Start production server:"));
2661
+ console.log(` ${chalk7.cyan(getRunCommand(pm))} spfn:start ${chalk7.gray("# Start SPFN + Next.js")}
2782
2662
  `);
2783
- console.log(" " + chalk8.cyan("Or deploy with Docker:"));
2784
- console.log(` ${chalk8.cyan("docker compose -f docker-compose.production.yml up --build -d")}
2663
+ console.log(" " + chalk7.cyan("Or deploy with Docker:"));
2664
+ console.log(` ${chalk7.cyan("docker compose -f docker-compose.production.yml up --build -d")}
2785
2665
  `);
2786
2666
  }
2787
2667
  var buildCommand = new Command5("build").description("Build SPFN project for production (Next.js + Server)").option("--server-only", "Build only SPFN server (skip Next.js)").option("--next-only", "Build only Next.js (skip SPFN server)").option("--turbo", "Use Turbopack for Next.js build").action(async (options) => {
@@ -2792,25 +2672,25 @@ var buildCommand = new Command5("build").description("Build SPFN project for pro
2792
2672
  init_logger();
2793
2673
  init_package_manager();
2794
2674
  import { Command as Command6 } from "commander";
2795
- import { existsSync as existsSync18, readFileSync as readFileSync9 } from "fs";
2796
- import { join as join19 } from "path";
2675
+ import { existsSync as existsSync16, readFileSync as readFileSync8 } from "fs";
2676
+ import { join as join17 } from "path";
2797
2677
  import { execa as execa8 } from "execa";
2798
- import chalk9 from "chalk";
2799
- var startCommand = new Command6("start").description("Start SPFN production server (Next.js + Hono)").option("--server-only", "Run only SPFN server (skip Next.js)").option("--next-only", "Run only Next.js (skip SPFN server)").option("-p, --port <port>", "Server port", "8790").option("-h, --host <host>", "Server host", "0.0.0.0").action(async (options) => {
2678
+ import chalk8 from "chalk";
2679
+ var startCommand = new Command6("start").description("Start SPFN production server (Next.js + Hono)").option("--server-only", "Run only SPFN server (skip Next.js)").option("--next-only", "Run only Next.js (skip SPFN server)").option("-p, --port <port>", "Server port", "8790").option("-h, --host <host>", "Server host", "0.0.0.0").option("--allow-pending-migrations", "Start even when migrations are pending (they are listed as a warning)").action(async (options) => {
2800
2680
  if (!process.env.NODE_ENV) {
2801
2681
  process.env.NODE_ENV = "production";
2802
2682
  }
2803
2683
  const cwd = process.cwd();
2804
- const packageJsonPath = join19(cwd, "package.json");
2684
+ const packageJsonPath = join17(cwd, "package.json");
2805
2685
  let hasNext = false;
2806
- if (existsSync18(packageJsonPath)) {
2807
- const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
2686
+ if (existsSync16(packageJsonPath)) {
2687
+ const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
2808
2688
  hasNext = !!(packageJson.dependencies?.next || packageJson.devDependencies?.next);
2809
2689
  }
2810
- const builtServerDir = join19(cwd, ".spfn", "server");
2811
- const hasBuiltServer = existsSync18(builtServerDir);
2812
- const nextBuildDir = join19(cwd, ".next");
2813
- const hasNextBuild = existsSync18(nextBuildDir);
2690
+ const builtServerDir = join17(cwd, ".spfn", "server");
2691
+ const hasBuiltServer = existsSync16(builtServerDir);
2692
+ const nextBuildDir = join17(cwd, ".next");
2693
+ const hasNextBuild = existsSync16(nextBuildDir);
2814
2694
  if (!options.nextOnly && !hasBuiltServer) {
2815
2695
  logger.error('.spfn/server directory not found. Please run "spfn build" first.');
2816
2696
  process.exit(1);
@@ -2820,13 +2700,27 @@ var startCommand = new Command6("start").description("Start SPFN production serv
2820
2700
  process.exit(1);
2821
2701
  }
2822
2702
  const pm = detectPackageManager(cwd);
2823
- const serverEntry = join19(cwd, ".spfn", "prod-server.mjs");
2824
- if (!existsSync18(serverEntry)) {
2703
+ const serverEntry = join17(cwd, ".spfn", "prod-server.mjs");
2704
+ if (!existsSync16(serverEntry)) {
2825
2705
  logger.error('.spfn/prod-server.mjs not found. Please run "spfn build" first.');
2826
2706
  process.exit(1);
2827
2707
  }
2828
2708
  process.env.SPFN_PORT = options.port;
2829
2709
  process.env.SPFN_HOST = options.host;
2710
+ if (!options.nextOnly) {
2711
+ const { checkPendingMigrationsBeforeStart: checkPendingMigrationsBeforeStart2 } = await Promise.resolve().then(() => (init_migration_status(), migration_status_exports));
2712
+ const migrationCheck = await checkPendingMigrationsBeforeStart2(
2713
+ cwd,
2714
+ process.env.DATABASE_URL,
2715
+ options.allowPendingMigrations === true
2716
+ );
2717
+ if (migrationCheck.block) {
2718
+ process.exit(1);
2719
+ }
2720
+ if (migrationCheck.allowPending) {
2721
+ process.env.SPFN_ALLOW_PENDING_MIGRATIONS = "true";
2722
+ }
2723
+ }
2830
2724
  if (options.serverOnly || !hasNext) {
2831
2725
  logger.info(`Starting SPFN Server (production) on http://${options.host}:${options.port}
2832
2726
  `);
@@ -2857,7 +2751,7 @@ var startCommand = new Command6("start").description("Start SPFN production serv
2857
2751
  }
2858
2752
  const nextCmd = "next start -H 0.0.0.0 -p 3790";
2859
2753
  const serverCmd = `node "${serverEntry}"`;
2860
- console.log(chalk9.blue.bold("\n\u{1F680} Starting SPFN production server...\n"));
2754
+ console.log(chalk8.blue.bold("\n\u{1F680} Starting SPFN production server...\n"));
2861
2755
  logger.info("Next.js: http://0.0.0.0:3790");
2862
2756
  logger.info(`SPFN API: http://${options.host}:${options.port}
2863
2757
  `);
@@ -2885,8 +2779,8 @@ var startCommand = new Command6("start").description("Start SPFN production serv
2885
2779
  init_logger();
2886
2780
  init_package_manager();
2887
2781
  import { Command as Command7 } from "commander";
2888
- import { existsSync as existsSync19, mkdirSync as mkdirSync3, writeFileSync as writeFileSync12 } from "fs";
2889
- import { join as join20 } from "path";
2782
+ import { existsSync as existsSync17, mkdirSync as mkdirSync3, writeFileSync as writeFileSync12 } from "fs";
2783
+ import { join as join18 } from "path";
2890
2784
  import { pathToFileURL } from "url";
2891
2785
  import { execa as execa9 } from "execa";
2892
2786
  var CONFIG_FILE_PATHS = [
@@ -2896,7 +2790,7 @@ var CONFIG_FILE_PATHS = [
2896
2790
  "src/server/server.config.js"
2897
2791
  ];
2898
2792
  function findServerConfig(cwd) {
2899
- return CONFIG_FILE_PATHS.map((path5) => join20(cwd, path5)).find(existsSync19);
2793
+ return CONFIG_FILE_PATHS.map((path5) => join18(cwd, path5)).find(existsSync17);
2900
2794
  }
2901
2795
  var provisionCommand = new Command7("provision").description("Run deploy-time provisioning (seed/RBAC lifecycle hooks) \u2014 once per deploy, not per cold start").action(async () => {
2902
2796
  if (!process.env.NODE_ENV) {
@@ -2917,8 +2811,8 @@ var provisionCommand = new Command7("provision").description("Run deploy-time pr
2917
2811
  if (keychainMissing.length > 0) {
2918
2812
  logger.warn(`[SPFN] Could not resolve keychain secret(s): ${keychainMissing.join(", ")} \u2014 run \`spfn secret set <KEY>\``);
2919
2813
  }
2920
- const tempDir = join20(cwd, ".spfn");
2921
- const runnerEntry = join20(tempDir, "provision.mjs");
2814
+ const tempDir = join18(cwd, ".spfn");
2815
+ const runnerEntry = join18(tempDir, "provision.mjs");
2922
2816
  mkdirSync3(tempDir, { recursive: true });
2923
2817
  writeFileSync12(runnerEntry, `
2924
2818
  if (!process.env.NODE_ENV)
@@ -2963,13 +2857,13 @@ process.exit(0);
2963
2857
  // src/commands/codegen.ts
2964
2858
  init_logger();
2965
2859
  import { Command as Command8 } from "commander";
2966
- import { existsSync as existsSync20, writeFileSync as writeFileSync13 } from "fs";
2967
- import { join as join21 } from "path";
2968
- import chalk10 from "chalk";
2860
+ import { existsSync as existsSync18, writeFileSync as writeFileSync13 } from "fs";
2861
+ import { join as join19 } from "path";
2862
+ import chalk9 from "chalk";
2969
2863
  async function initCodegen(options) {
2970
2864
  const cwd = process.cwd();
2971
- const rcPath = join21(cwd, ".spfnrc.ts");
2972
- if (existsSync20(rcPath)) {
2865
+ const rcPath = join19(cwd, ".spfnrc.ts");
2866
+ if (existsSync18(rcPath)) {
2973
2867
  logger.warn(".spfnrc.ts already exists");
2974
2868
  logger.info("Edit manually to add custom generators");
2975
2869
  process.exit(0);
@@ -2995,13 +2889,13 @@ export default defineConfig({
2995
2889
  });
2996
2890
  `;
2997
2891
  writeFileSync13(rcPath, content);
2998
- console.log("\n" + chalk10.green.bold("\u2713 Created .spfnrc.ts\n"));
2999
- console.log(chalk10.gray("Configured the @spfn/core:route-map generator."));
2892
+ console.log("\n" + chalk9.green.bold("\u2713 Created .spfnrc.ts\n"));
2893
+ console.log(chalk9.gray("Configured the @spfn/core:route-map generator."));
3000
2894
  if (options.withExample) {
3001
- console.log("\n" + chalk10.yellow("\u{1F4DD} To add custom generators:"));
2895
+ console.log("\n" + chalk9.yellow("\u{1F4DD} To add custom generators:"));
3002
2896
  console.log(" 1. Create your generator file (e.g., src/generators/my-generator.ts)");
3003
2897
  console.log(" 2. Add to .spfnrc.ts:");
3004
- console.log(chalk10.gray(`
2898
+ console.log(chalk9.gray(`
3005
2899
  {
3006
2900
  "codegen": {
3007
2901
  "generators": [
@@ -3011,11 +2905,11 @@ export default defineConfig({
3011
2905
  }
3012
2906
  }
3013
2907
  `));
3014
- console.log(" 3. Run: " + chalk10.cyan("spfn dev") + " (generators run automatically)");
2908
+ console.log(" 3. Run: " + chalk9.cyan("spfn dev") + " (generators run automatically)");
3015
2909
  } else {
3016
- console.log("\n" + chalk10.yellow("\u{1F4DD} Next steps:"));
2910
+ console.log("\n" + chalk9.yellow("\u{1F4DD} Next steps:"));
3017
2911
  console.log(" \u2022 Add custom generators to .spfnrc.ts");
3018
- console.log(" \u2022 Run: " + chalk10.cyan("spfn dev") + " to start development with code generation");
2912
+ console.log(" \u2022 Run: " + chalk9.cyan("spfn dev") + " to start development with code generation");
3019
2913
  }
3020
2914
  }
3021
2915
  async function listGenerators() {
@@ -3028,10 +2922,10 @@ async function listGenerators() {
3028
2922
  logger.info('Run "spfn codegen init" to initialize configuration');
3029
2923
  return;
3030
2924
  }
3031
- console.log("\n" + chalk10.bold("Registered Generators:"));
2925
+ console.log("\n" + chalk9.bold("Registered Generators:"));
3032
2926
  generators.forEach((gen, index) => {
3033
- console.log(` ${index + 1}. ${chalk10.cyan(gen.name)}`);
3034
- console.log(` Patterns: ${chalk10.gray(gen.watchPatterns.join(", "))}`);
2927
+ console.log(` ${index + 1}. ${chalk9.cyan(gen.name)}`);
2928
+ console.log(` Patterns: ${chalk9.gray(gen.watchPatterns.join(", "))}`);
3035
2929
  });
3036
2930
  console.log("");
3037
2931
  }
@@ -3052,7 +2946,7 @@ async function runGenerators() {
3052
2946
  debug: true
3053
2947
  });
3054
2948
  await orchestrator.generateAll();
3055
- console.log("\n" + chalk10.green.bold("\u2713 Code generation completed"));
2949
+ console.log("\n" + chalk9.green.bold("\u2713 Code generation completed"));
3056
2950
  }
3057
2951
  var codegenCommand = new Command8("codegen").description("Code generation management");
3058
2952
  codegenCommand.command("init").description("Initialize .spfnrc.ts with codegen configuration").option("--with-example", "Show example custom generator usage").action(initCodegen);
@@ -3062,18 +2956,18 @@ codegenCommand.command("run").description("Run code generators once (no watch mo
3062
2956
  // src/commands/contract.ts
3063
2957
  init_logger();
3064
2958
  import { Command as Command9 } from "commander";
3065
- import { join as join22 } from "path";
3066
- import chalk11 from "chalk";
2959
+ import { join as join20 } from "path";
2960
+ import chalk10 from "chalk";
3067
2961
  async function resolveContractsDir(cwd, override) {
3068
2962
  if (override) {
3069
- return join22(cwd, override);
2963
+ return join20(cwd, override);
3070
2964
  }
3071
2965
  const { loadCodegenConfig } = await import("@spfn/core/codegen");
3072
2966
  const config = loadCodegenConfig(cwd);
3073
2967
  const generator = config.generators?.find(
3074
2968
  (entry) => "name" in entry && entry.name === "@spfn/core:contract"
3075
2969
  );
3076
- return join22(cwd, generator?.outputDir ?? "./contracts");
2970
+ return join20(cwd, generator?.outputDir ?? "./contracts");
3077
2971
  }
3078
2972
  async function regenerate(cwd) {
3079
2973
  const { loadCodegenConfig, createGeneratorsFromConfig, CodegenOrchestrator } = await import("@spfn/core/codegen");
@@ -3098,14 +2992,14 @@ async function checkContractCommand(options) {
3098
2992
  logger.warn(warning);
3099
2993
  }
3100
2994
  if (result.violations.length > 0) {
3101
- console.log("\n" + chalk11.red.bold(`\u2717 Breaks the contract released as ${result.baselineVersion}
2995
+ console.log("\n" + chalk10.red.bold(`\u2717 Breaks the contract released as ${result.baselineVersion}
3102
2996
  `));
3103
2997
  console.log(formatViolations(result.violations));
3104
2998
  console.log("");
3105
2999
  process.exit(1);
3106
3000
  }
3107
3001
  const against = result.baselineVersion ? `backward compatible with released ${result.baselineVersion}` : "generated (nothing released to compare against yet)";
3108
- console.log("\n" + chalk11.green.bold(`\u2713 Contract ${against}
3002
+ console.log("\n" + chalk10.green.bold(`\u2713 Contract ${against}
3109
3003
  `));
3110
3004
  } catch (error) {
3111
3005
  logger.error(error instanceof Error ? error.message : String(error));
@@ -3126,7 +3020,7 @@ async function releaseContractCommand(version, options) {
3126
3020
  const document = readCurrentDocument(contractsDir);
3127
3021
  const result = checkContract(contractsDir, document);
3128
3022
  if (result.violations.length > 0) {
3129
- console.log("\n" + chalk11.red.bold(`\u2717 Cannot release: breaks the contract released as ${result.baselineVersion}
3023
+ console.log("\n" + chalk10.red.bold(`\u2717 Cannot release: breaks the contract released as ${result.baselineVersion}
3130
3024
  `));
3131
3025
  console.log(formatViolations(result.violations));
3132
3026
  console.log("");
@@ -3135,12 +3029,12 @@ async function releaseContractCommand(version, options) {
3135
3029
  for (const warning of result.warnings) {
3136
3030
  logger.warn(warning);
3137
3031
  }
3138
- const file = writeSnapshot(contractsDir, version, document);
3139
- console.log("\n" + chalk11.green.bold(`\u2713 Released contract ${version}
3032
+ const file = writeSnapshot(contractsDir, document);
3033
+ console.log("\n" + chalk10.green.bold(`\u2713 Released contract ${version}
3140
3034
  `));
3141
- console.log(chalk11.gray(` ${file}`));
3142
- console.log(chalk11.gray(` ${document.operations.length} operation(s)`));
3143
- console.log(chalk11.yellow("\n Commit this snapshot. The gate compares every later build against it.\n"));
3035
+ console.log(chalk10.gray(` ${file}`));
3036
+ console.log(chalk10.gray(` ${document.operations.length} operation(s)`));
3037
+ console.log(chalk10.yellow("\n Commit this snapshot. The gate compares every later build against it.\n"));
3144
3038
  } catch (error) {
3145
3039
  logger.error(error instanceof Error ? error.message : String(error));
3146
3040
  process.exit(1);
@@ -3156,9 +3050,9 @@ async function listContractCommand(options) {
3156
3050
  logger.info("No released contract snapshot yet");
3157
3051
  return;
3158
3052
  }
3159
- console.log("\n" + chalk11.bold("Released contracts:"));
3053
+ console.log("\n" + chalk10.bold("Released contracts:"));
3160
3054
  for (const snapshot of snapshots) {
3161
- console.log(` ${chalk11.cyan(snapshot.version)} ${chalk11.gray(snapshot.file)}`);
3055
+ console.log(` ${chalk10.cyan(snapshot.version)} ${chalk10.gray(snapshot.file)}`);
3162
3056
  }
3163
3057
  console.log("");
3164
3058
  } catch (error) {
@@ -3175,7 +3069,7 @@ contractCommand.command("list").alias("ls").description("List released contract
3175
3069
  init_logger();
3176
3070
  import { Command as Command10 } from "commander";
3177
3071
  import { execSync } from "child_process";
3178
- import chalk12 from "chalk";
3072
+ import chalk11 from "chalk";
3179
3073
 
3180
3074
  // src/utils/secret-gen.ts
3181
3075
  import { randomBytes as randomBytes2, randomUUID } from "crypto";
@@ -3250,41 +3144,41 @@ function copyToClipboard(text) {
3250
3144
  function generateSecret(bytes, preset, envVarName, copy) {
3251
3145
  const key = randomBase64Url(bytes);
3252
3146
  const config = preset ? PRESETS[preset] : null;
3253
- console.log("\n" + chalk12.green.bold("\u2713 Generated secret key:"));
3147
+ console.log("\n" + chalk11.green.bold("\u2713 Generated secret key:"));
3254
3148
  if (config) {
3255
- console.log(chalk12.dim(` ${config.description} (${bytes * 8} bits)`));
3149
+ console.log(chalk11.dim(` ${config.description} (${bytes * 8} bits)`));
3256
3150
  } else {
3257
- console.log(chalk12.dim(` ${bytes * 8}-bit secret`));
3151
+ console.log(chalk11.dim(` ${bytes * 8}-bit secret`));
3258
3152
  }
3259
- console.log("\n" + chalk12.cyan(key) + "\n");
3153
+ console.log("\n" + chalk11.cyan(key) + "\n");
3260
3154
  const varName = envVarName || config?.envVar || "SECRET_KEY";
3261
- console.log(chalk12.dim("Add to your .env file:"));
3262
- console.log(chalk12.yellow(`${varName}=${key}
3155
+ console.log(chalk11.dim("Add to your .env file:"));
3156
+ console.log(chalk11.yellow(`${varName}=${key}
3263
3157
  `));
3264
3158
  if (config?.usage) {
3265
- console.log(chalk12.dim("Usage:"));
3266
- console.log(chalk12.gray(` ${config.usage}
3159
+ console.log(chalk11.dim("Usage:"));
3160
+ console.log(chalk11.gray(` ${config.usage}
3267
3161
  `));
3268
3162
  }
3269
3163
  if (copy) {
3270
3164
  if (copyToClipboard(key)) {
3271
- console.log(chalk12.green("\u2713 Copied to clipboard!\n"));
3165
+ console.log(chalk11.green("\u2713 Copied to clipboard!\n"));
3272
3166
  } else {
3273
3167
  logger.warn("Could not copy to clipboard");
3274
3168
  }
3275
3169
  }
3276
3170
  }
3277
3171
  function listPresets() {
3278
- console.log("\n" + chalk12.bold("Available presets:"));
3172
+ console.log("\n" + chalk11.bold("Available presets:"));
3279
3173
  console.log();
3280
3174
  Object.entries(PRESETS).forEach(([name, config]) => {
3281
- console.log(` ${chalk12.cyan(name.padEnd(20))} ${chalk12.dim(config.description)}`);
3282
- console.log(` ${" ".repeat(20)} ${chalk12.gray(`\u2192 ${config.envVar} (${config.bytes * 8} bits)`)}`);
3175
+ console.log(` ${chalk11.cyan(name.padEnd(20))} ${chalk11.dim(config.description)}`);
3176
+ console.log(` ${" ".repeat(20)} ${chalk11.gray(`\u2192 ${config.envVar} (${config.bytes * 8} bits)`)}`);
3283
3177
  console.log();
3284
3178
  });
3285
- console.log(chalk12.dim("Usage:"));
3286
- console.log(chalk12.gray(" spfn key <preset>"));
3287
- console.log(chalk12.gray(" spfn key auth-encryption --copy"));
3179
+ console.log(chalk11.dim("Usage:"));
3180
+ console.log(chalk11.gray(" spfn key <preset>"));
3181
+ console.log(chalk11.gray(" spfn key auth-encryption --copy"));
3288
3182
  console.log();
3289
3183
  }
3290
3184
  var generateValueCommand = new Command10("generate").alias("gen").description("Generate random value (simple output, no metadata)").option("-b, --bytes <number>", "Number of random bytes", "32").option("-c, --copy", "Copy to clipboard").action((options) => {
@@ -3297,9 +3191,9 @@ var generateValueCommand = new Command10("generate").alias("gen").description("G
3297
3191
  console.log(value);
3298
3192
  if (options.copy) {
3299
3193
  if (copyToClipboard(value)) {
3300
- console.error(chalk12.green("\u2713 Copied to clipboard"));
3194
+ console.error(chalk11.green("\u2713 Copied to clipboard"));
3301
3195
  } else {
3302
- console.error(chalk12.yellow("\u26A0 Could not copy to clipboard"));
3196
+ console.error(chalk11.yellow("\u26A0 Could not copy to clipboard"));
3303
3197
  }
3304
3198
  }
3305
3199
  });
@@ -3317,9 +3211,9 @@ var keyCommand = new Command10("key").alias("k").description("Generate secure ra
3317
3211
  logger.error(`Unknown preset: ${preset}`);
3318
3212
  console.log("\nAvailable presets:");
3319
3213
  Object.entries(PRESETS).forEach(([name, config]) => {
3320
- console.log(` ${chalk12.cyan(name)}: ${config.description}`);
3214
+ console.log(` ${chalk11.cyan(name)}: ${config.description}`);
3321
3215
  });
3322
- console.log("\nUse " + chalk12.cyan("--list") + " to see detailed information");
3216
+ console.log("\nUse " + chalk11.cyan("--list") + " to see detailed information");
3323
3217
  process.exit(1);
3324
3218
  }
3325
3219
  generateSecret(
@@ -3338,16 +3232,16 @@ init_setup();
3338
3232
  import { Command as Command11 } from "commander";
3339
3233
 
3340
3234
  // src/commands/db/generate.ts
3341
- import chalk14 from "chalk";
3235
+ import chalk13 from "chalk";
3342
3236
 
3343
3237
  // src/commands/db/utils/drizzle.ts
3344
- import { existsSync as existsSync21, writeFileSync as writeFileSync14, unlinkSync as unlinkSync2 } from "fs";
3238
+ import { existsSync as existsSync19, writeFileSync as writeFileSync14, unlinkSync as unlinkSync2 } from "fs";
3345
3239
  import { spawn } from "child_process";
3346
3240
  import { pathToFileURL as pathToFileURL2 } from "url";
3347
- import chalk13 from "chalk";
3241
+ import chalk12 from "chalk";
3348
3242
  import ora6 from "ora";
3349
- import { env as env3 } from "@spfn/core/config";
3350
- import { loadEnv as loadEnv3 } from "@spfn/core/server";
3243
+ import { env as env2 } from "@spfn/core/config";
3244
+ import { loadEnv as loadEnv2 } from "@spfn/core/server";
3351
3245
  var TLS_SSL_MODES = /* @__PURE__ */ new Set(["no-verify", "prefer", "require", "verify-ca", "verify-full"]);
3352
3246
  function parseDatabaseUrl(databaseUrl) {
3353
3247
  if (!databaseUrl) {
@@ -3400,22 +3294,22 @@ function resolvePushConnectionConfig(databaseUrl) {
3400
3294
  return { connectionString: parsedUrl.toString() };
3401
3295
  }
3402
3296
  function validateDatabasePrerequisites() {
3403
- loadEnv3();
3404
- if (!env3.DATABASE_URL) {
3405
- console.error(chalk13.red("\u274C DATABASE_URL not found in environment"));
3406
- console.log(chalk13.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
3297
+ loadEnv2();
3298
+ if (!env2.DATABASE_URL) {
3299
+ console.error(chalk12.red("\u274C DATABASE_URL not found in environment"));
3300
+ console.log(chalk12.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
3407
3301
  throw new Error("DATABASE_URL is required for database operations");
3408
3302
  }
3409
3303
  }
3410
3304
  async function runDrizzleCommand(command) {
3411
- const hasUserConfig = existsSync21("./drizzle.config.ts");
3305
+ const hasUserConfig = existsSync19("./drizzle.config.ts");
3412
3306
  const tempConfigPath = `./drizzle.config.${process.pid}.${Date.now()}.temp.ts`;
3413
3307
  const configPath = hasUserConfig ? "./drizzle.config.ts" : tempConfigPath;
3414
3308
  if (!hasUserConfig) {
3415
- loadEnv3();
3416
- if (!env3.DATABASE_URL) {
3417
- console.error(chalk13.red("\u274C DATABASE_URL not found in environment"));
3418
- console.log(chalk13.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
3309
+ loadEnv2();
3310
+ if (!env2.DATABASE_URL) {
3311
+ console.error(chalk12.red("\u274C DATABASE_URL not found in environment"));
3312
+ console.log(chalk12.yellow("\n\u{1F4A1} Tip: Add DATABASE_URL to your .env file"));
3419
3313
  process.exit(1);
3420
3314
  }
3421
3315
  const { generateDrizzleConfigFile } = await import("@spfn/core/db");
@@ -3428,7 +3322,7 @@ async function runDrizzleCommand(command) {
3428
3322
  autoDetectSchemas: true
3429
3323
  });
3430
3324
  writeFileSync14(tempConfigPath, configContent);
3431
- console.log(chalk13.dim("Using auto-generated Drizzle config\n"));
3325
+ console.log(chalk12.dim("Using auto-generated Drizzle config\n"));
3432
3326
  }
3433
3327
  const args = command.split(" ");
3434
3328
  args.push(`--config=${configPath}`);
@@ -3437,10 +3331,10 @@ async function runDrizzleCommand(command) {
3437
3331
  stdio: "inherit",
3438
3332
  // Allow interactive input
3439
3333
  shell: true,
3440
- env: shouldRelaxDbTls(env3.DATABASE_URL) ? { ...process.env, NODE_TLS_REJECT_UNAUTHORIZED: "0" } : { ...process.env }
3334
+ env: shouldRelaxDbTls(env2.DATABASE_URL) ? { ...process.env, NODE_TLS_REJECT_UNAUTHORIZED: "0" } : { ...process.env }
3441
3335
  });
3442
3336
  const cleanup = () => {
3443
- if (!hasUserConfig && existsSync21(tempConfigPath)) {
3337
+ if (!hasUserConfig && existsSync19(tempConfigPath)) {
3444
3338
  unlinkSync2(tempConfigPath);
3445
3339
  }
3446
3340
  };
@@ -3463,10 +3357,10 @@ async function runWithSpinner(spinnerText, command, successMessage, failMessage)
3463
3357
  try {
3464
3358
  spinner.stop();
3465
3359
  await runDrizzleCommand(command);
3466
- console.log(chalk13.green(`\u2705 ${successMessage}`));
3360
+ console.log(chalk12.green(`\u2705 ${successMessage}`));
3467
3361
  } catch (error) {
3468
3362
  spinner.fail(failMessage);
3469
- console.error(chalk13.red(error instanceof Error ? error.message : "Unknown error"));
3363
+ console.error(chalk12.red(error instanceof Error ? error.message : "Unknown error"));
3470
3364
  process.exit(1);
3471
3365
  }
3472
3366
  }
@@ -3490,13 +3384,13 @@ async function loadSchemaImports(schemaFiles) {
3490
3384
  return imports;
3491
3385
  }
3492
3386
  async function createPushConnection() {
3493
- loadEnv3();
3494
- if (!env3.DATABASE_URL) {
3387
+ loadEnv2();
3388
+ if (!env2.DATABASE_URL) {
3495
3389
  throw new Error("DATABASE_URL is required");
3496
3390
  }
3497
3391
  const pg = await import("pg");
3498
3392
  const { drizzle } = await import("drizzle-orm/node-postgres");
3499
- const connectionConfig = resolvePushConnectionConfig(env3.DATABASE_URL);
3393
+ const connectionConfig = resolvePushConnectionConfig(env2.DATABASE_URL);
3500
3394
  const pool = new pg.default.Pool({
3501
3395
  ...connectionConfig,
3502
3396
  max: 1
@@ -3512,10 +3406,10 @@ async function createPushConnection() {
3512
3406
  async function dbGenerate() {
3513
3407
  try {
3514
3408
  await runDrizzleCommand("generate");
3515
- console.log(chalk14.green("\n\u2705 Migrations generated successfully"));
3409
+ console.log(chalk13.green("\n\u2705 Migrations generated successfully"));
3516
3410
  } catch (error) {
3517
- console.error(chalk14.red("\n\u274C Failed to generate migrations"));
3518
- console.error(chalk14.red(error instanceof Error ? error.message : "Unknown error"));
3411
+ console.error(chalk13.red("\n\u274C Failed to generate migrations"));
3412
+ console.error(chalk13.red(error instanceof Error ? error.message : "Unknown error"));
3519
3413
  process.exit(1);
3520
3414
  }
3521
3415
  }
@@ -3568,7 +3462,7 @@ function classifyStatements(statements) {
3568
3462
  }
3569
3463
 
3570
3464
  // src/commands/db/utils/push-display.ts
3571
- import chalk15 from "chalk";
3465
+ import chalk14 from "chalk";
3572
3466
  function formatSql(sql2) {
3573
3467
  return sql2.replace(/\s+/g, " ").trim();
3574
3468
  }
@@ -3576,30 +3470,30 @@ function printStatements(statements, color) {
3576
3470
  for (const stmt of statements) {
3577
3471
  console.log(color(` ${formatSql(stmt.sql)}`));
3578
3472
  if (stmt.reason) {
3579
- console.log(chalk15.dim(` \u2192 ${stmt.reason}`));
3473
+ console.log(chalk14.dim(` \u2192 ${stmt.reason}`));
3580
3474
  }
3581
3475
  }
3582
3476
  }
3583
3477
  function displayClassifiedStatements(result) {
3584
3478
  if (result.safe.length > 0) {
3585
- console.log(chalk15.green(`
3479
+ console.log(chalk14.green(`
3586
3480
  \u2705 Safe changes (${result.safe.length}):`));
3587
- printStatements(result.safe, chalk15.green);
3481
+ printStatements(result.safe, chalk14.green);
3588
3482
  }
3589
3483
  if (result.warning.length > 0) {
3590
- console.log(chalk15.yellow(`
3484
+ console.log(chalk14.yellow(`
3591
3485
  \u26A0\uFE0F Warnings (${result.warning.length}):`));
3592
- printStatements(result.warning, chalk15.yellow);
3486
+ printStatements(result.warning, chalk14.yellow);
3593
3487
  }
3594
3488
  if (result.destructive.length > 0) {
3595
- console.log(chalk15.red(`
3489
+ console.log(chalk14.red(`
3596
3490
  \u274C Destructive changes (${result.destructive.length}):`));
3597
- printStatements(result.destructive, chalk15.red);
3491
+ printStatements(result.destructive, chalk14.red);
3598
3492
  }
3599
3493
  }
3600
3494
  function displayDryRunSummary(result) {
3601
3495
  const total = result.safe.length + result.warning.length + result.destructive.length;
3602
- console.log(chalk15.cyan(`
3496
+ console.log(chalk14.cyan(`
3603
3497
  \u{1F50D} Dry-run: ${total} statement(s) detected
3604
3498
  `));
3605
3499
  displayClassifiedStatements(result);
@@ -3607,11 +3501,11 @@ function displayDryRunSummary(result) {
3607
3501
  }
3608
3502
  function displayApplySummary(applied, skipped) {
3609
3503
  if (applied > 0) {
3610
- console.log(chalk15.green(`
3504
+ console.log(chalk14.green(`
3611
3505
  \u2705 Applied ${applied} statement(s)`));
3612
3506
  }
3613
3507
  if (skipped > 0) {
3614
- console.log(chalk15.yellow(`\u23ED\uFE0F Skipped ${skipped} destructive statement(s)`));
3508
+ console.log(chalk14.yellow(`\u23ED\uFE0F Skipped ${skipped} destructive statement(s)`));
3615
3509
  }
3616
3510
  console.log("");
3617
3511
  }
@@ -3760,8 +3654,8 @@ async function applyFunctionMigrations(plans) {
3760
3654
 
3761
3655
  // src/commands/db/migrate.ts
3762
3656
  import chalk21 from "chalk";
3763
- import { join as join23 } from "path";
3764
- import { existsSync as existsSync23 } from "fs";
3657
+ import { join as join21 } from "path";
3658
+ import { existsSync as existsSync21 } from "fs";
3765
3659
 
3766
3660
  // src/commands/db/backup.ts
3767
3661
  import { promises as fs3 } from "fs";
@@ -3854,7 +3748,7 @@ function formatTimestamp() {
3854
3748
 
3855
3749
  // src/commands/db/utils/backup-files.ts
3856
3750
  import { promises as fs2 } from "fs";
3857
- import { existsSync as existsSync22 } from "fs";
3751
+ import { existsSync as existsSync20 } from "fs";
3858
3752
  import path2 from "path";
3859
3753
  import chalk19 from "chalk";
3860
3754
 
@@ -3954,7 +3848,7 @@ async function ensureBackupInGitignore() {
3954
3848
  const gitignorePath = path2.join(process.cwd(), ".gitignore");
3955
3849
  try {
3956
3850
  let content = "";
3957
- let exists = existsSync22(gitignorePath);
3851
+ let exists = existsSync20(gitignorePath);
3958
3852
  if (exists) {
3959
3853
  content = await fs2.readFile(gitignorePath, "utf-8");
3960
3854
  }
@@ -3976,7 +3870,7 @@ async function ensureBackupDir() {
3976
3870
  try {
3977
3871
  await fs2.mkdir(backupDir, { recursive: true });
3978
3872
  const gitignorePath = path2.join(backupDir, ".gitignore");
3979
- const gitignoreExists = existsSync22(gitignorePath);
3873
+ const gitignoreExists = existsSync20(gitignorePath);
3980
3874
  if (!gitignoreExists) {
3981
3875
  await fs2.writeFile(gitignorePath, "# Ignore all backup files\n*.sql\n*.dump\n*.meta.json\n");
3982
3876
  }
@@ -4170,8 +4064,8 @@ async function dbMigrate(options = {}) {
4170
4064
  await executeFunctionMigrations2(loadFunctionMigrationPlans2(functions));
4171
4065
  console.log(chalk21.green("\u2705 Function migrations applied\n"));
4172
4066
  }
4173
- const projectMigrationsDir = join23(process.cwd(), "src/server/drizzle");
4174
- if (existsSync23(projectMigrationsDir)) {
4067
+ const projectMigrationsDir = join21(process.cwd(), "src/server/drizzle");
4068
+ if (existsSync21(projectMigrationsDir)) {
4175
4069
  const projConn = postgres.default(env5.DATABASE_URL, { max: 1 });
4176
4070
  const projDb = drizzle({ client: projConn });
4177
4071
  try {
@@ -4206,7 +4100,7 @@ async function dbStatus() {
4206
4100
  console.error(chalk22.red(error instanceof Error ? error.message : "Unknown error"));
4207
4101
  process.exit(1);
4208
4102
  }
4209
- const targets = [...status.packages, ...status.project ? [status.project] : []];
4103
+ const targets = migrationTargets(status);
4210
4104
  if (targets.length === 0) {
4211
4105
  console.log(chalk22.dim("No migrations found (no @spfn packages with migrations, no src/server/drizzle)."));
4212
4106
  return;
@@ -4231,7 +4125,7 @@ async function dbStatus() {
4231
4125
 
4232
4126
  // src/commands/db/studio.ts
4233
4127
  import chalk23 from "chalk";
4234
- import { existsSync as existsSync24, writeFileSync as writeFileSync15, unlinkSync as unlinkSync3 } from "fs";
4128
+ import { existsSync as existsSync22, writeFileSync as writeFileSync15, unlinkSync as unlinkSync3 } from "fs";
4235
4129
  import { spawn as spawn3 } from "child_process";
4236
4130
  import { env as env6 } from "@spfn/core/config";
4237
4131
  async function dbStudio(requestedPort) {
@@ -4249,7 +4143,7 @@ async function dbStudio(requestedPort) {
4249
4143
  console.error(chalk23.red(error instanceof Error ? error.message : "Failed to find available port"));
4250
4144
  process.exit(1);
4251
4145
  }
4252
- const hasUserConfig = existsSync24("./drizzle.config.ts");
4146
+ const hasUserConfig = existsSync22("./drizzle.config.ts");
4253
4147
  const tempConfigPath = `./drizzle.config.${process.pid}.${Date.now()}.temp.ts`;
4254
4148
  try {
4255
4149
  const configPath = hasUserConfig ? "./drizzle.config.ts" : tempConfigPath;
@@ -4275,7 +4169,7 @@ async function dbStudio(requestedPort) {
4275
4169
  env: shouldRelaxDbTls(env6.DATABASE_URL) ? { ...process.env, NODE_TLS_REJECT_UNAUTHORIZED: "0" } : { ...process.env }
4276
4170
  });
4277
4171
  const cleanup = () => {
4278
- if (!hasUserConfig && existsSync24(tempConfigPath)) {
4172
+ if (!hasUserConfig && existsSync22(tempConfigPath)) {
4279
4173
  unlinkSync3(tempConfigPath);
4280
4174
  }
4281
4175
  };
@@ -4305,7 +4199,7 @@ async function dbStudio(requestedPort) {
4305
4199
  process.exit(0);
4306
4200
  });
4307
4201
  } catch (error) {
4308
- if (!hasUserConfig && existsSync24(tempConfigPath)) {
4202
+ if (!hasUserConfig && existsSync22(tempConfigPath)) {
4309
4203
  unlinkSync3(tempConfigPath);
4310
4204
  }
4311
4205
  console.error(chalk23.red("\u274C Failed to start Drizzle Studio"));
@@ -4692,8 +4586,8 @@ async function dbBackupClean(options) {
4692
4586
  }
4693
4587
 
4694
4588
  // src/commands/db/reindex.ts
4695
- import { existsSync as existsSync25, readFileSync as readFileSync10, writeFileSync as writeFileSync16, renameSync, copyFileSync } from "fs";
4696
- import { join as join24 } from "path";
4589
+ import { existsSync as existsSync23, readFileSync as readFileSync9, writeFileSync as writeFileSync16, renameSync, copyFileSync } from "fs";
4590
+ import { join as join22 } from "path";
4697
4591
  import chalk29 from "chalk";
4698
4592
  import { loadEnv as loadEnv9 } from "@spfn/core/server";
4699
4593
  function isTimestampPrefix(tag) {
@@ -4715,13 +4609,13 @@ async function dbReindex(options = {}) {
4715
4609
  const { getDrizzleConfig } = await import("@spfn/core/db");
4716
4610
  const config = getDrizzleConfig({ disablePackageDiscovery: true });
4717
4611
  const outDir = config.out;
4718
- const journalPath = join24(outDir, "meta", "_journal.json");
4719
- if (!existsSync25(journalPath)) {
4612
+ const journalPath = join22(outDir, "meta", "_journal.json");
4613
+ if (!existsSync23(journalPath)) {
4720
4614
  console.error(chalk29.red("\u274C No _journal.json found at:"), journalPath);
4721
4615
  console.log(chalk29.yellow("\u{1F4A1} Run `spfn db generate` first to create migrations"));
4722
4616
  process.exit(1);
4723
4617
  }
4724
- const journal = JSON.parse(readFileSync10(journalPath, "utf-8"));
4618
+ const journal = JSON.parse(readFileSync9(journalPath, "utf-8"));
4725
4619
  if (journal.entries.length === 0) {
4726
4620
  console.log(chalk29.yellow("No migration entries found \u2014 nothing to reindex."));
4727
4621
  return;
@@ -4737,14 +4631,14 @@ async function dbReindex(options = {}) {
4737
4631
  const { prefix: oldPrefix, suffix } = parseTag(entry.tag);
4738
4632
  const newPrefix = String(entry.when);
4739
4633
  const newTag = suffix ? `${newPrefix}_${suffix}` : newPrefix;
4740
- const oldSql = join24(outDir, `${entry.tag}.sql`);
4741
- const newSql = join24(outDir, `${newTag}.sql`);
4742
- if (existsSync25(oldSql)) {
4634
+ const oldSql = join22(outDir, `${entry.tag}.sql`);
4635
+ const newSql = join22(outDir, `${newTag}.sql`);
4636
+ if (existsSync23(oldSql)) {
4743
4637
  renames.push({ type: "sql", from: oldSql, to: newSql });
4744
4638
  }
4745
- const oldSnapshot = join24(outDir, "meta", `${oldPrefix}_snapshot.json`);
4746
- const newSnapshot = join24(outDir, "meta", `${newPrefix}_snapshot.json`);
4747
- if (existsSync25(oldSnapshot)) {
4639
+ const oldSnapshot = join22(outDir, "meta", `${oldPrefix}_snapshot.json`);
4640
+ const newSnapshot = join22(outDir, "meta", `${newPrefix}_snapshot.json`);
4641
+ if (existsSync23(oldSnapshot)) {
4748
4642
  renames.push({ type: "snapshot", from: oldSnapshot, to: newSnapshot });
4749
4643
  }
4750
4644
  tagUpdates.push({ idx: entry.idx, oldTag: entry.tag, newTag });
@@ -4810,21 +4704,21 @@ dbCommand.command("reindex").description("Convert migration files from sequentia
4810
4704
  // src/commands/add.ts
4811
4705
  init_package_manager();
4812
4706
  import { Command as Command12 } from "commander";
4813
- import { existsSync as existsSync27, readFileSync as readFileSync11 } from "fs";
4814
- import { join as join26 } from "path";
4707
+ import { existsSync as existsSync25, readFileSync as readFileSync10 } from "fs";
4708
+ import { join as join24 } from "path";
4815
4709
  import { execa as execa10 } from "execa";
4816
4710
  import chalk31 from "chalk";
4817
4711
  import ora11 from "ora";
4818
4712
 
4819
4713
  // src/commands/add-vercel.ts
4820
4714
  init_templates();
4821
- import { copyFileSync as copyFileSync2, existsSync as existsSync26, mkdirSync as mkdirSync4 } from "fs";
4822
- import { dirname as dirname3, join as join25 } from "path";
4715
+ import { copyFileSync as copyFileSync2, existsSync as existsSync24, mkdirSync as mkdirSync4 } from "fs";
4716
+ import { dirname as dirname3, join as join23 } from "path";
4823
4717
  import chalk30 from "chalk";
4824
4718
  var FILES = [
4825
4719
  {
4826
4720
  template: "route.ts",
4827
- target: join25("src", "app", "api", "backend", "[[...route]]", "route.ts"),
4721
+ target: join23("src", "app", "api", "backend", "[[...route]]", "route.ts"),
4828
4722
  description: "hono/vercel adapter (mounts the SPFN app under /api/backend)"
4829
4723
  },
4830
4724
  {
@@ -4841,15 +4735,15 @@ var FILES = [
4841
4735
  async function addVercel() {
4842
4736
  console.log(chalk30.blue("\n\u{1F4E6} Setting up the Vercel serverless target...\n"));
4843
4737
  const cwd = process.cwd();
4844
- const templatesDir = join25(findTemplatesPath(), "vercel");
4738
+ const templatesDir = join23(findTemplatesPath(), "vercel");
4845
4739
  for (const file of FILES) {
4846
- const targetPath = join25(cwd, file.target);
4847
- if (existsSync26(targetPath)) {
4740
+ const targetPath = join23(cwd, file.target);
4741
+ if (existsSync24(targetPath)) {
4848
4742
  console.log(chalk30.yellow(`\u23ED\uFE0F ${file.target} already exists \u2014 skipped (not overwritten)`));
4849
4743
  continue;
4850
4744
  }
4851
4745
  mkdirSync4(dirname3(targetPath), { recursive: true });
4852
- copyFileSync2(join25(templatesDir, file.template), targetPath);
4746
+ copyFileSync2(join23(templatesDir, file.template), targetPath);
4853
4747
  console.log(chalk30.green(`\u2705 ${file.target}`) + chalk30.gray(` \u2014 ${file.description}`));
4854
4748
  }
4855
4749
  console.log(chalk30.green("\n\u2705 Vercel target ready!\n"));
@@ -4888,9 +4782,9 @@ async function addPackage(packageName) {
4888
4782
  console.log(chalk31.blue(`
4889
4783
  \u{1F4E6} Setting up ${packageName}...
4890
4784
  `));
4891
- const pkgPath = join26(process.cwd(), "node_modules", ...packageName.split("/"));
4892
- const pkgJsonPath = join26(pkgPath, "package.json");
4893
- if (!existsSync27(pkgJsonPath)) {
4785
+ const pkgPath = join24(process.cwd(), "node_modules", ...packageName.split("/"));
4786
+ const pkgJsonPath = join24(pkgPath, "package.json");
4787
+ if (!existsSync25(pkgJsonPath)) {
4894
4788
  const pm = detectPackageManager(process.cwd());
4895
4789
  const installSpinner = ora11("Installing package...").start();
4896
4790
  try {
@@ -4904,11 +4798,11 @@ async function addPackage(packageName) {
4904
4798
  } else {
4905
4799
  console.log(chalk31.gray("\u2713 Package already installed (using local version)\n"));
4906
4800
  }
4907
- if (!existsSync27(pkgJsonPath)) {
4801
+ if (!existsSync25(pkgJsonPath)) {
4908
4802
  console.error(chalk31.red(`\u274C Package ${packageName} not found after installation`));
4909
4803
  process.exit(1);
4910
4804
  }
4911
- const pkgJson = JSON.parse(readFileSync11(pkgJsonPath, "utf-8"));
4805
+ const pkgJson = JSON.parse(readFileSync10(pkgJsonPath, "utf-8"));
4912
4806
  if (pkgJson.spfn?.migrations) {
4913
4807
  console.log(chalk31.blue(`
4914
4808
  \u{1F5C4}\uFE0F Setting up database for ${packageName}...
@@ -4954,7 +4848,7 @@ var addCommand = new Command12("add").description("Add a capability to this proj
4954
4848
  // src/commands/env.ts
4955
4849
  import { Command as Command13 } from "commander";
4956
4850
  import chalk32 from "chalk";
4957
- import { existsSync as existsSync28, readFileSync as readFileSync12, writeFileSync as writeFileSync17 } from "fs";
4851
+ import { existsSync as existsSync26, readFileSync as readFileSync11, writeFileSync as writeFileSync17 } from "fs";
4958
4852
  import { resolve as resolve2 } from "path";
4959
4853
  import { parse as parse2 } from "dotenv";
4960
4854
 
@@ -5245,7 +5139,7 @@ async function initEnvFiles(options) {
5245
5139
  }
5246
5140
  function writeEnvTemplate(cwd, file, vars, force) {
5247
5141
  const filePath = resolve2(cwd, file);
5248
- if (existsSync28(filePath) && !force) {
5142
+ if (existsSync26(filePath) && !force) {
5249
5143
  console.log(chalk32.yellow(` \u23ED\uFE0F ${file} already exists (use --force to overwrite)`));
5250
5144
  return;
5251
5145
  }
@@ -5294,10 +5188,10 @@ async function checkEnvFiles(options) {
5294
5188
  const warnings = [];
5295
5189
  for (const file of filesToCheck) {
5296
5190
  const filePath = resolve2(cwd, file);
5297
- if (!existsSync28(filePath)) {
5191
+ if (!existsSync26(filePath)) {
5298
5192
  continue;
5299
5193
  }
5300
- const content = readFileSync12(filePath, "utf-8");
5194
+ const content = readFileSync11(filePath, "utf-8");
5301
5195
  const parsed = parse2(content);
5302
5196
  for (const [key, value] of Object.entries(parsed)) {
5303
5197
  loadedEnv[key] = { value: value || "", file };
@@ -5467,12 +5361,12 @@ function resolveEnv(env9) {
5467
5361
 
5468
5362
  // src/commands/secret/store-value.ts
5469
5363
  init_logger();
5470
- import { join as join28 } from "path";
5364
+ import { join as join26 } from "path";
5471
5365
  init_env_file();
5472
5366
 
5473
5367
  // src/utils/sops.ts
5474
5368
  import { execa as execa11 } from "execa";
5475
- import { existsSync as existsSync29, mkdirSync as mkdirSync5, writeFileSync as writeFileSync18 } from "fs";
5369
+ import { existsSync as existsSync27, mkdirSync as mkdirSync5, writeFileSync as writeFileSync18 } from "fs";
5476
5370
  import { dirname as dirname4 } from "path";
5477
5371
  async function ensureSopsInstalled() {
5478
5372
  try {
@@ -5482,14 +5376,14 @@ async function ensureSopsInstalled() {
5482
5376
  }
5483
5377
  }
5484
5378
  async function sopsDecrypt(absFile) {
5485
- if (!existsSync29(absFile)) {
5379
+ if (!existsSync27(absFile)) {
5486
5380
  return {};
5487
5381
  }
5488
5382
  const { stdout } = await execa11("sops", ["--decrypt", "--output-type", "json", absFile]);
5489
5383
  return JSON.parse(stdout);
5490
5384
  }
5491
5385
  async function sopsSetValue(absFile, relFile, key, value) {
5492
- if (existsSync29(absFile)) {
5386
+ if (existsSync27(absFile)) {
5493
5387
  await execa11("sops", ["set", absFile, `["${key}"]`, JSON.stringify(value)]);
5494
5388
  return;
5495
5389
  }
@@ -5515,20 +5409,20 @@ async function sopsUpdateKeys(absFile) {
5515
5409
  }
5516
5410
 
5517
5411
  // src/utils/secret-config.ts
5518
- import { existsSync as existsSync30, readdirSync as readdirSync3 } from "fs";
5519
- import { join as join27 } from "path";
5412
+ import { existsSync as existsSync28, readdirSync as readdirSync2 } from "fs";
5413
+ import { join as join25 } from "path";
5520
5414
  var SECRETS_DIR = "secrets";
5521
5415
  function getSopsFile(cwd, env9) {
5522
5416
  const relFile = `${SECRETS_DIR}/${env9}.enc.json`;
5523
- return { absFile: join27(cwd, relFile), relFile };
5417
+ return { absFile: join25(cwd, relFile), relFile };
5524
5418
  }
5525
5419
  function findUp(cwd, filename, maxDepth = 6) {
5526
5420
  let dir = cwd;
5527
5421
  for (let depth = 0; depth < maxDepth; depth++) {
5528
- if (existsSync30(join27(dir, filename))) {
5422
+ if (existsSync28(join25(dir, filename))) {
5529
5423
  return dir;
5530
5424
  }
5531
- const parent = join27(dir, "..");
5425
+ const parent = join25(dir, "..");
5532
5426
  if (parent === dir) {
5533
5427
  break;
5534
5428
  }
@@ -5538,17 +5432,17 @@ function findUp(cwd, filename, maxDepth = 6) {
5538
5432
  }
5539
5433
  function findSopsConfig(cwd) {
5540
5434
  const dir = findUp(cwd, ".sops.yaml");
5541
- return dir ? join27(dir, ".sops.yaml") : null;
5435
+ return dir ? join25(dir, ".sops.yaml") : null;
5542
5436
  }
5543
5437
  function hasSopsConfig(cwd) {
5544
5438
  return findSopsConfig(cwd) !== null;
5545
5439
  }
5546
5440
  function listSopsFiles(cwd) {
5547
- const dir = join27(cwd, SECRETS_DIR);
5548
- if (!existsSync30(dir)) {
5441
+ const dir = join25(cwd, SECRETS_DIR);
5442
+ if (!existsSync28(dir)) {
5549
5443
  return [];
5550
5444
  }
5551
- return readdirSync3(dir).filter((name) => name.endsWith(".enc.json")).map((name) => join27(dir, name));
5445
+ return readdirSync2(dir).filter((name) => name.endsWith(".enc.json")).map((name) => join25(dir, name));
5552
5446
  }
5553
5447
 
5554
5448
  // src/commands/secret/store-value.ts
@@ -5577,7 +5471,7 @@ async function storeSecret(cwd, env9, key, value) {
5577
5471
  );
5578
5472
  }
5579
5473
  await store.set(keychainName(key), value);
5580
- const serverEnvPath = join28(cwd, ".env.server");
5474
+ const serverEnvPath = join26(cwd, ".env.server");
5581
5475
  const result = upsertEnvVar(serverEnvPath, key, keychainRef(key));
5582
5476
  restrictEnvFilePerms(serverEnvPath);
5583
5477
  ensureGitignored(cwd, [{ pattern: ".env.server", comment: "spfn server env (secrets)" }]);
@@ -5815,12 +5709,12 @@ function requireEntry(schema, key) {
5815
5709
  // src/commands/secret/keygen.ts
5816
5710
  init_logger();
5817
5711
  import { execa as execa12 } from "execa";
5818
- import { existsSync as existsSync31, mkdirSync as mkdirSync6 } from "fs";
5712
+ import { existsSync as existsSync29, mkdirSync as mkdirSync6 } from "fs";
5819
5713
  import { homedir } from "os";
5820
- import { dirname as dirname5, join as join29 } from "path";
5714
+ import { dirname as dirname5, join as join27 } from "path";
5821
5715
  import chalk37 from "chalk";
5822
5716
  function ageKeyFile() {
5823
- return process.env.SOPS_AGE_KEY_FILE ?? join29(homedir(), ".config", "sops", "age", "keys.txt");
5717
+ return process.env.SOPS_AGE_KEY_FILE ?? join27(homedir(), ".config", "sops", "age", "keys.txt");
5824
5718
  }
5825
5719
  async function ensureAgeInstalled() {
5826
5720
  try {
@@ -5840,7 +5734,7 @@ async function secretKeygen() {
5840
5734
  process.exit(1);
5841
5735
  }
5842
5736
  const keyFile = ageKeyFile();
5843
- if (existsSync31(keyFile)) {
5737
+ if (existsSync29(keyFile)) {
5844
5738
  const { stdout } = await execa12("age-keygen", ["-y", keyFile]);
5845
5739
  logger.warn(`age key file already exists: ${keyFile}`);
5846
5740
  printPublicKeys(publicKeys(stdout));
@@ -5867,8 +5761,8 @@ function printPublicKeys(keys) {
5867
5761
 
5868
5762
  // src/commands/secret/recipients.ts
5869
5763
  init_logger();
5870
- import { existsSync as existsSync32, readFileSync as readFileSync13, writeFileSync as writeFileSync19 } from "fs";
5871
- import { join as join30 } from "path";
5764
+ import { existsSync as existsSync30, readFileSync as readFileSync12, writeFileSync as writeFileSync19 } from "fs";
5765
+ import { join as join28 } from "path";
5872
5766
  import { parse as parse3, stringify } from "yaml";
5873
5767
  import chalk38 from "chalk";
5874
5768
  var SOPS_CONFIG = ".sops.yaml";
@@ -5876,7 +5770,7 @@ var DEFAULT_PATH_REGEX = "secrets/.*\\.enc\\.json$";
5876
5770
  var AGE_RECIPIENT = /^age1[0-9a-z]+$/;
5877
5771
  async function secretRecipients(action, key, _options) {
5878
5772
  const cwd = process.cwd();
5879
- const configPath = join30(cwd, SOPS_CONFIG);
5773
+ const configPath = join28(cwd, SOPS_CONFIG);
5880
5774
  if (action === "list") {
5881
5775
  listRecipients(configPath);
5882
5776
  return;
@@ -5908,7 +5802,7 @@ async function secretRecipients(action, key, _options) {
5908
5802
  await reencrypt(cwd);
5909
5803
  }
5910
5804
  function listRecipients(configPath) {
5911
- if (!existsSync32(configPath)) {
5805
+ if (!existsSync30(configPath)) {
5912
5806
  logger.info(`No ${SOPS_CONFIG} found.`);
5913
5807
  return;
5914
5808
  }
@@ -5926,10 +5820,10 @@ function listRecipients(configPath) {
5926
5820
  console.log();
5927
5821
  }
5928
5822
  function loadConfig(configPath) {
5929
- if (!existsSync32(configPath)) {
5823
+ if (!existsSync30(configPath)) {
5930
5824
  return { creation_rules: [] };
5931
5825
  }
5932
- return parse3(readFileSync13(configPath, "utf-8")) ?? { creation_rules: [] };
5826
+ return parse3(readFileSync12(configPath, "utf-8")) ?? { creation_rules: [] };
5933
5827
  }
5934
5828
  function ensureRule(config) {
5935
5829
  if (!config.creation_rules) {
@@ -5961,7 +5855,7 @@ async function reencrypt(cwd) {
5961
5855
 
5962
5856
  // src/commands/secret/check.ts
5963
5857
  init_logger();
5964
- import { join as join31 } from "path";
5858
+ import { join as join29 } from "path";
5965
5859
  import chalk39 from "chalk";
5966
5860
  init_env_file();
5967
5861
  var COMMITTED_FILES = [".env", ".env.example"];
@@ -5980,14 +5874,14 @@ async function secretCheck(options) {
5980
5874
  const issues = [];
5981
5875
  const warnings = [];
5982
5876
  for (const file of COMMITTED_FILES) {
5983
- const parsed = parseEnvFile(join31(cwd, file));
5877
+ const parsed = parseEnvFile(join29(cwd, file));
5984
5878
  for (const [key, value] of Object.entries(parsed)) {
5985
5879
  if (secretKeys.has(key) && value.length > 0 && !PLACEHOLDER.test(value)) {
5986
5880
  issues.push(`${chalk39.cyan(key)} has a real value in committed ${chalk39.yellow(file)} \u2014 move it to the keychain/SOPS.`);
5987
5881
  }
5988
5882
  }
5989
5883
  }
5990
- const serverEnv = parseEnvFile(join31(cwd, ".env.server"));
5884
+ const serverEnv = parseEnvFile(join29(cwd, ".env.server"));
5991
5885
  for (const key of secretKeys) {
5992
5886
  const value = serverEnv[key];
5993
5887
  if (value && !value.startsWith(KEYCHAIN_REF_PREFIX)) {