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.
- package/README.md +46 -4
- package/dist/index.js +349 -455
- 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.
|
|
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
|
|
1528
|
-
import {
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
1730
|
-
if (!
|
|
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(
|
|
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(
|
|
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 ?
|
|
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
|
|
2061
|
-
import { join as
|
|
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 (
|
|
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 =
|
|
2137
|
+
const dir = join15(filePath, "..");
|
|
2268
2138
|
const fileName = filePath.split("/").pop();
|
|
2269
2139
|
const watcher = watch(dir, (_event, name) => {
|
|
2270
|
-
if (name === fileName &&
|
|
2140
|
+
if (name === fileName && existsSync14(filePath)) {
|
|
2271
2141
|
watcher.close();
|
|
2272
2142
|
clearTimeout(timer);
|
|
2273
|
-
resolve3(
|
|
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 =
|
|
2285
|
-
if (!
|
|
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 {
|
|
2300
|
-
await
|
|
2301
|
-
|
|
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 (
|
|
2304
|
-
const packageJson = JSON.parse(
|
|
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 =
|
|
2308
|
-
const serverEntry =
|
|
2309
|
-
const watcherEntry =
|
|
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 =
|
|
2312
|
-
if (
|
|
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 =
|
|
2317
|
-
if (
|
|
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 =
|
|
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
|
|
2616
|
-
import { join as
|
|
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
|
|
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 =
|
|
2510
|
+
const packageJsonPath = join16(cwd, "package.json");
|
|
2631
2511
|
let hasNext = false;
|
|
2632
|
-
if (
|
|
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 =
|
|
2639
|
-
const hasServer =
|
|
2640
|
-
console.log(
|
|
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 =
|
|
2561
|
+
const outputDir = join16(cwd, ".spfn", "server");
|
|
2682
2562
|
mkdirSync2(outputDir, { recursive: true });
|
|
2683
|
-
const serverDir2 =
|
|
2684
|
-
if (!
|
|
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 =
|
|
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 =
|
|
2739
|
-
if (
|
|
2618
|
+
const routesDir = join16(cwd, ".spfn", "server", "routes");
|
|
2619
|
+
if (existsSync15(routesDir)) {
|
|
2740
2620
|
console.log();
|
|
2741
|
-
console.log(
|
|
2621
|
+
console.log(chalk7.bold("Route (api)"));
|
|
2742
2622
|
try {
|
|
2743
|
-
const routes =
|
|
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} ${
|
|
2630
|
+
console.log(`${prefix} ${chalk7.green("GET")} ${routePath}`);
|
|
2751
2631
|
} else {
|
|
2752
|
-
console.log(`${prefix} ${
|
|
2632
|
+
console.log(`${prefix} ${chalk7.cyan("*")} ${routePath}`);
|
|
2753
2633
|
}
|
|
2754
2634
|
});
|
|
2755
2635
|
console.log();
|
|
2756
|
-
console.log(
|
|
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" +
|
|
2644
|
+
console.error("\n" + chalk7.red(error.message));
|
|
2765
2645
|
if (error.stack) {
|
|
2766
|
-
console.error(
|
|
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" +
|
|
2779
|
-
console.log(
|
|
2780
|
-
console.log(" " +
|
|
2781
|
-
console.log(` ${
|
|
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(" " +
|
|
2784
|
-
console.log(` ${
|
|
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
|
|
2796
|
-
import { join as
|
|
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
|
|
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 =
|
|
2684
|
+
const packageJsonPath = join17(cwd, "package.json");
|
|
2805
2685
|
let hasNext = false;
|
|
2806
|
-
if (
|
|
2807
|
-
const packageJson = JSON.parse(
|
|
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 =
|
|
2811
|
-
const hasBuiltServer =
|
|
2812
|
-
const nextBuildDir =
|
|
2813
|
-
const hasNextBuild =
|
|
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 =
|
|
2824
|
-
if (!
|
|
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(
|
|
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
|
|
2889
|
-
import { join as
|
|
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) =>
|
|
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 =
|
|
2921
|
-
const runnerEntry =
|
|
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
|
|
2967
|
-
import { join as
|
|
2968
|
-
import
|
|
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 =
|
|
2972
|
-
if (
|
|
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" +
|
|
2999
|
-
console.log(
|
|
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" +
|
|
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(
|
|
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: " +
|
|
2908
|
+
console.log(" 3. Run: " + chalk9.cyan("spfn dev") + " (generators run automatically)");
|
|
3015
2909
|
} else {
|
|
3016
|
-
console.log("\n" +
|
|
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: " +
|
|
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" +
|
|
2925
|
+
console.log("\n" + chalk9.bold("Registered Generators:"));
|
|
3032
2926
|
generators.forEach((gen, index) => {
|
|
3033
|
-
console.log(` ${index + 1}. ${
|
|
3034
|
-
console.log(` Patterns: ${
|
|
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" +
|
|
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
|
|
3066
|
-
import
|
|
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
|
|
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
|
|
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" +
|
|
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" +
|
|
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" +
|
|
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,
|
|
3139
|
-
console.log("\n" +
|
|
3032
|
+
const file = writeSnapshot(contractsDir, document);
|
|
3033
|
+
console.log("\n" + chalk10.green.bold(`\u2713 Released contract ${version}
|
|
3140
3034
|
`));
|
|
3141
|
-
console.log(
|
|
3142
|
-
console.log(
|
|
3143
|
-
console.log(
|
|
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" +
|
|
3053
|
+
console.log("\n" + chalk10.bold("Released contracts:"));
|
|
3160
3054
|
for (const snapshot of snapshots) {
|
|
3161
|
-
console.log(` ${
|
|
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
|
|
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" +
|
|
3147
|
+
console.log("\n" + chalk11.green.bold("\u2713 Generated secret key:"));
|
|
3254
3148
|
if (config) {
|
|
3255
|
-
console.log(
|
|
3149
|
+
console.log(chalk11.dim(` ${config.description} (${bytes * 8} bits)`));
|
|
3256
3150
|
} else {
|
|
3257
|
-
console.log(
|
|
3151
|
+
console.log(chalk11.dim(` ${bytes * 8}-bit secret`));
|
|
3258
3152
|
}
|
|
3259
|
-
console.log("\n" +
|
|
3153
|
+
console.log("\n" + chalk11.cyan(key) + "\n");
|
|
3260
3154
|
const varName = envVarName || config?.envVar || "SECRET_KEY";
|
|
3261
|
-
console.log(
|
|
3262
|
-
console.log(
|
|
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(
|
|
3266
|
-
console.log(
|
|
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(
|
|
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" +
|
|
3172
|
+
console.log("\n" + chalk11.bold("Available presets:"));
|
|
3279
3173
|
console.log();
|
|
3280
3174
|
Object.entries(PRESETS).forEach(([name, config]) => {
|
|
3281
|
-
console.log(` ${
|
|
3282
|
-
console.log(` ${" ".repeat(20)} ${
|
|
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(
|
|
3286
|
-
console.log(
|
|
3287
|
-
console.log(
|
|
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(
|
|
3194
|
+
console.error(chalk11.green("\u2713 Copied to clipboard"));
|
|
3301
3195
|
} else {
|
|
3302
|
-
console.error(
|
|
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(` ${
|
|
3214
|
+
console.log(` ${chalk11.cyan(name)}: ${config.description}`);
|
|
3321
3215
|
});
|
|
3322
|
-
console.log("\nUse " +
|
|
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
|
|
3235
|
+
import chalk13 from "chalk";
|
|
3342
3236
|
|
|
3343
3237
|
// src/commands/db/utils/drizzle.ts
|
|
3344
|
-
import { existsSync as
|
|
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
|
|
3241
|
+
import chalk12 from "chalk";
|
|
3348
3242
|
import ora6 from "ora";
|
|
3349
|
-
import { env as
|
|
3350
|
-
import { loadEnv as
|
|
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
|
-
|
|
3404
|
-
if (!
|
|
3405
|
-
console.error(
|
|
3406
|
-
console.log(
|
|
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 =
|
|
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
|
-
|
|
3416
|
-
if (!
|
|
3417
|
-
console.error(
|
|
3418
|
-
console.log(
|
|
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(
|
|
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(
|
|
3334
|
+
env: shouldRelaxDbTls(env2.DATABASE_URL) ? { ...process.env, NODE_TLS_REJECT_UNAUTHORIZED: "0" } : { ...process.env }
|
|
3441
3335
|
});
|
|
3442
3336
|
const cleanup = () => {
|
|
3443
|
-
if (!hasUserConfig &&
|
|
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(
|
|
3360
|
+
console.log(chalk12.green(`\u2705 ${successMessage}`));
|
|
3467
3361
|
} catch (error) {
|
|
3468
3362
|
spinner.fail(failMessage);
|
|
3469
|
-
console.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
|
-
|
|
3494
|
-
if (!
|
|
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(
|
|
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(
|
|
3409
|
+
console.log(chalk13.green("\n\u2705 Migrations generated successfully"));
|
|
3516
3410
|
} catch (error) {
|
|
3517
|
-
console.error(
|
|
3518
|
-
console.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
|
|
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(
|
|
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(
|
|
3479
|
+
console.log(chalk14.green(`
|
|
3586
3480
|
\u2705 Safe changes (${result.safe.length}):`));
|
|
3587
|
-
printStatements(result.safe,
|
|
3481
|
+
printStatements(result.safe, chalk14.green);
|
|
3588
3482
|
}
|
|
3589
3483
|
if (result.warning.length > 0) {
|
|
3590
|
-
console.log(
|
|
3484
|
+
console.log(chalk14.yellow(`
|
|
3591
3485
|
\u26A0\uFE0F Warnings (${result.warning.length}):`));
|
|
3592
|
-
printStatements(result.warning,
|
|
3486
|
+
printStatements(result.warning, chalk14.yellow);
|
|
3593
3487
|
}
|
|
3594
3488
|
if (result.destructive.length > 0) {
|
|
3595
|
-
console.log(
|
|
3489
|
+
console.log(chalk14.red(`
|
|
3596
3490
|
\u274C Destructive changes (${result.destructive.length}):`));
|
|
3597
|
-
printStatements(result.destructive,
|
|
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(
|
|
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(
|
|
3504
|
+
console.log(chalk14.green(`
|
|
3611
3505
|
\u2705 Applied ${applied} statement(s)`));
|
|
3612
3506
|
}
|
|
3613
3507
|
if (skipped > 0) {
|
|
3614
|
-
console.log(
|
|
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
|
|
3764
|
-
import { existsSync as
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
4174
|
-
if (
|
|
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 =
|
|
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
|
|
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 =
|
|
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 &&
|
|
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 &&
|
|
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
|
|
4696
|
-
import { join as
|
|
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 =
|
|
4719
|
-
if (!
|
|
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(
|
|
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 =
|
|
4741
|
-
const newSql =
|
|
4742
|
-
if (
|
|
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 =
|
|
4746
|
-
const newSnapshot =
|
|
4747
|
-
if (
|
|
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
|
|
4814
|
-
import { join as
|
|
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
|
|
4822
|
-
import { dirname as dirname3, join as
|
|
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:
|
|
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 =
|
|
4738
|
+
const templatesDir = join23(findTemplatesPath(), "vercel");
|
|
4845
4739
|
for (const file of FILES) {
|
|
4846
|
-
const targetPath =
|
|
4847
|
-
if (
|
|
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(
|
|
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 =
|
|
4892
|
-
const pkgJsonPath =
|
|
4893
|
-
if (!
|
|
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 (!
|
|
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(
|
|
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
|
|
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 (
|
|
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 (!
|
|
5191
|
+
if (!existsSync26(filePath)) {
|
|
5298
5192
|
continue;
|
|
5299
5193
|
}
|
|
5300
|
-
const content =
|
|
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
|
|
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
|
|
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 (!
|
|
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 (
|
|
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
|
|
5519
|
-
import { join as
|
|
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:
|
|
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 (
|
|
5422
|
+
if (existsSync28(join25(dir, filename))) {
|
|
5529
5423
|
return dir;
|
|
5530
5424
|
}
|
|
5531
|
-
const parent =
|
|
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 ?
|
|
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 =
|
|
5548
|
-
if (!
|
|
5441
|
+
const dir = join25(cwd, SECRETS_DIR);
|
|
5442
|
+
if (!existsSync28(dir)) {
|
|
5549
5443
|
return [];
|
|
5550
5444
|
}
|
|
5551
|
-
return
|
|
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 =
|
|
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
|
|
5712
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync6 } from "fs";
|
|
5819
5713
|
import { homedir } from "os";
|
|
5820
|
-
import { dirname as dirname5, join as
|
|
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 ??
|
|
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 (
|
|
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
|
|
5871
|
-
import { join as
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
5823
|
+
if (!existsSync30(configPath)) {
|
|
5930
5824
|
return { creation_rules: [] };
|
|
5931
5825
|
}
|
|
5932
|
-
return parse3(
|
|
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
|
|
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(
|
|
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(
|
|
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)) {
|