@biffo/cli 0.53.0 → 0.54.0
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/core.version +1 -1
- package/dist/index.js +377 -332
- package/package.json +1 -1
package/core.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.54.0
|
package/dist/index.js
CHANGED
|
@@ -337,7 +337,7 @@ async function runCoreStatus(options) {
|
|
|
337
337
|
|
|
338
338
|
// src/commands/core-upgrade.ts
|
|
339
339
|
import { execSync as execSync2 } from "child_process";
|
|
340
|
-
import { join as
|
|
340
|
+
import { join as join10, resolve as resolve3 } from "path";
|
|
341
341
|
import chalk4 from "chalk";
|
|
342
342
|
import { execa as execa3 } from "execa";
|
|
343
343
|
import { Command as Command3 } from "commander";
|
|
@@ -1634,9 +1634,211 @@ var GLOBAL_DISPATCH_WORKFLOW_PATHS = [
|
|
|
1634
1634
|
".github/workflows/deploy-global.yml"
|
|
1635
1635
|
];
|
|
1636
1636
|
|
|
1637
|
-
// src/lib/
|
|
1638
|
-
import { existsSync as existsSync6 } from "fs";
|
|
1637
|
+
// src/lib/plugin-terraform-wiring.ts
|
|
1638
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync as readdirSync3, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
1639
1639
|
import { join as join8 } from "path";
|
|
1640
|
+
var TEMPLATE_MODULE_DIR = "_template";
|
|
1641
|
+
var DEFAULT_PLUGIN_HANDLER = "src.lambda.main.handler";
|
|
1642
|
+
var GENERATED_TF_FILE = "plugins.generated.tf";
|
|
1643
|
+
var GENERATED_TFVARS_FILE = "plugins.auto.tfvars.json";
|
|
1644
|
+
function standardArguments(pluginName, handler) {
|
|
1645
|
+
return [
|
|
1646
|
+
["project_name", "var.project_name"],
|
|
1647
|
+
["environment", "local.environment"],
|
|
1648
|
+
["plugin_name", JSON.stringify(pluginName)],
|
|
1649
|
+
["handler", JSON.stringify(handler)],
|
|
1650
|
+
["event_bus_name", "module.events.event_bus_name"],
|
|
1651
|
+
["core_api_url", "module.api_gateway.api_endpoint"],
|
|
1652
|
+
["core_api_execution_arn", "module.api_gateway.execution_arn"],
|
|
1653
|
+
["tags", "local.tags"]
|
|
1654
|
+
];
|
|
1655
|
+
}
|
|
1656
|
+
function listPluginModules(cwd) {
|
|
1657
|
+
const dir = join8(cwd, "modules", "plugins");
|
|
1658
|
+
let entries;
|
|
1659
|
+
try {
|
|
1660
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
1661
|
+
} catch {
|
|
1662
|
+
return [];
|
|
1663
|
+
}
|
|
1664
|
+
return entries.filter((e) => e.isDirectory() && e.name !== TEMPLATE_MODULE_DIR && !e.name.startsWith(".")).map((e) => e.name).sort();
|
|
1665
|
+
}
|
|
1666
|
+
var FIRST_PARTY_TERRAFORM = (name) => `../../../services/_plugins/${name}/terraform`;
|
|
1667
|
+
var THIRD_PARTY_TERRAFORM = (name) => `../../../modules/plugins/${name}`;
|
|
1668
|
+
function isFirstPartyPlugin(cwd, name) {
|
|
1669
|
+
return existsSync6(join8(cwd, "services", "_plugins", name, "terraform", "main.tf"));
|
|
1670
|
+
}
|
|
1671
|
+
function pluginModuleSource(cwd, name) {
|
|
1672
|
+
return isFirstPartyPlugin(cwd, name) ? FIRST_PARTY_TERRAFORM(name) : THIRD_PARTY_TERRAFORM(name);
|
|
1673
|
+
}
|
|
1674
|
+
function listWireablePlugins(cwd) {
|
|
1675
|
+
const copied = listPluginModules(cwd);
|
|
1676
|
+
const firstParty = firstPartyPluginNames(cwd);
|
|
1677
|
+
return [.../* @__PURE__ */ new Set([...copied, ...firstParty])].sort();
|
|
1678
|
+
}
|
|
1679
|
+
function firstPartyPluginNames(cwd) {
|
|
1680
|
+
const dir = join8(cwd, "services", "_plugins");
|
|
1681
|
+
let entries;
|
|
1682
|
+
try {
|
|
1683
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
1684
|
+
} catch {
|
|
1685
|
+
return [];
|
|
1686
|
+
}
|
|
1687
|
+
return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && isFirstPartyPlugin(cwd, e.name)).map((e) => e.name).sort();
|
|
1688
|
+
}
|
|
1689
|
+
function staleFirstPartyCopies(cwd) {
|
|
1690
|
+
const copied = new Set(listPluginModules(cwd));
|
|
1691
|
+
return firstPartyPluginNames(cwd).filter((name) => copied.has(name));
|
|
1692
|
+
}
|
|
1693
|
+
function listEnvironments(cwd) {
|
|
1694
|
+
const dir = join8(cwd, "infra", "environments");
|
|
1695
|
+
let entries;
|
|
1696
|
+
try {
|
|
1697
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
1698
|
+
} catch {
|
|
1699
|
+
return [];
|
|
1700
|
+
}
|
|
1701
|
+
return entries.filter((e) => {
|
|
1702
|
+
if (!e.isDirectory() || !existsSync6(join8(dir, e.name, "main.tf"))) return false;
|
|
1703
|
+
return declaredVariables(join8(dir, e.name)).has("enabled_plugins");
|
|
1704
|
+
}).map((e) => e.name).sort();
|
|
1705
|
+
}
|
|
1706
|
+
function listUnwirableEnvironments(cwd) {
|
|
1707
|
+
const dir = join8(cwd, "infra", "environments");
|
|
1708
|
+
let entries;
|
|
1709
|
+
try {
|
|
1710
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
1711
|
+
} catch {
|
|
1712
|
+
return [];
|
|
1713
|
+
}
|
|
1714
|
+
return entries.filter(
|
|
1715
|
+
(e) => e.isDirectory() && existsSync6(join8(dir, e.name, "main.tf")) && !declaredVariables(join8(dir, e.name)).has("enabled_plugins")
|
|
1716
|
+
).map((e) => e.name).sort();
|
|
1717
|
+
}
|
|
1718
|
+
function declaredVariables(moduleDir) {
|
|
1719
|
+
const names = /* @__PURE__ */ new Set();
|
|
1720
|
+
let entries;
|
|
1721
|
+
try {
|
|
1722
|
+
entries = readdirSync3(moduleDir, { withFileTypes: true });
|
|
1723
|
+
} catch {
|
|
1724
|
+
return names;
|
|
1725
|
+
}
|
|
1726
|
+
for (const entry of entries) {
|
|
1727
|
+
if (!entry.isFile() || !entry.name.endsWith(".tf")) continue;
|
|
1728
|
+
let contents;
|
|
1729
|
+
try {
|
|
1730
|
+
contents = readFileSync5(join8(moduleDir, entry.name), "utf8");
|
|
1731
|
+
} catch {
|
|
1732
|
+
continue;
|
|
1733
|
+
}
|
|
1734
|
+
for (const match of contents.matchAll(/^\s*variable\s+"([^"]+)"/gm)) {
|
|
1735
|
+
names.add(match[1]);
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
return names;
|
|
1739
|
+
}
|
|
1740
|
+
function renderArguments(args, indent) {
|
|
1741
|
+
const width = Math.max(...args.map(([key]) => key.length));
|
|
1742
|
+
return args.map(([key, value]) => `${indent}${key.padEnd(width)} = ${value}`).join("\n");
|
|
1743
|
+
}
|
|
1744
|
+
function renderModuleBlock(pluginName, declared, handler, source) {
|
|
1745
|
+
const args = standardArguments(pluginName, handler).filter(([key]) => declared.has(key));
|
|
1746
|
+
const quoted = JSON.stringify(pluginName);
|
|
1747
|
+
return [
|
|
1748
|
+
`module "plugin_${pluginName}" {`,
|
|
1749
|
+
` source = "${source}"`,
|
|
1750
|
+
` for_each = contains(var.enabled_plugins, ${quoted}) ? { ${quoted} = true } : {}`,
|
|
1751
|
+
"",
|
|
1752
|
+
renderArguments(args, " "),
|
|
1753
|
+
"}",
|
|
1754
|
+
"",
|
|
1755
|
+
`output "plugin_${pluginName}_function_arn" {`,
|
|
1756
|
+
` description = "Lambda ARN of the ${pluginName} plugin, or null when it is not in enabled_plugins."`,
|
|
1757
|
+
` value = try(module.plugin_${pluginName}[${quoted}].function_arn, null)`,
|
|
1758
|
+
"}"
|
|
1759
|
+
].join("\n");
|
|
1760
|
+
}
|
|
1761
|
+
var GENERATED_HEADER = `# ---------------------------------------------------------------------------
|
|
1762
|
+
# GENERATED FILE \u2014 DO NOT EDIT BY HAND.
|
|
1763
|
+
#
|
|
1764
|
+
# Written by \`biffo plugin install\` / \`biffo plugin uninstall\` (issue #201),
|
|
1765
|
+
# regenerated in full from the contents of modules/plugins/. Any manual edit is
|
|
1766
|
+
# lost on the next plugin install or uninstall.
|
|
1767
|
+
#
|
|
1768
|
+
# Terraform loads every *.tf file in this directory, so these blocks are as
|
|
1769
|
+
# live as anything in main.tf \u2014 they simply live in a CLI-owned file so the
|
|
1770
|
+
# CLI never has to rewrite your hand-authored main.tf.
|
|
1771
|
+
#
|
|
1772
|
+
# Terraform requires a module's \`source\` to be a static string literal, so it
|
|
1773
|
+
# cannot loop over var.enabled_plugins; hence one explicit block per plugin,
|
|
1774
|
+
# each gated on membership in enabled_plugins (supplied by the generated
|
|
1775
|
+
# ${GENERATED_TFVARS_FILE} alongside this file).
|
|
1776
|
+
#
|
|
1777
|
+
# Not generated here: the Core API's BIFFO_SERVICE_PRINCIPAL_ARN_ALLOWLIST
|
|
1778
|
+
# (ADR-0009). It lives in main.tf and is derived from var.enabled_plugins as a
|
|
1779
|
+
# static role-name glob \u2014 deriving it from a plugin module's role_arn output
|
|
1780
|
+
# would create the cycle core_api -> api_gateway -> plugin -> core_api.
|
|
1781
|
+
# ---------------------------------------------------------------------------
|
|
1782
|
+
`;
|
|
1783
|
+
function renderGeneratedTerraform(plugins) {
|
|
1784
|
+
const blocks = plugins.map(
|
|
1785
|
+
(p) => renderModuleBlock(
|
|
1786
|
+
p.name,
|
|
1787
|
+
p.declaredVariables,
|
|
1788
|
+
p.handler ?? DEFAULT_PLUGIN_HANDLER,
|
|
1789
|
+
p.source ?? THIRD_PARTY_TERRAFORM(p.name)
|
|
1790
|
+
)
|
|
1791
|
+
);
|
|
1792
|
+
return `${GENERATED_HEADER}
|
|
1793
|
+
${blocks.join("\n\n")}
|
|
1794
|
+
`;
|
|
1795
|
+
}
|
|
1796
|
+
function renderGeneratedTfvars(pluginNames) {
|
|
1797
|
+
return `${JSON.stringify({ enabled_plugins: pluginNames }, null, 2)}
|
|
1798
|
+
`;
|
|
1799
|
+
}
|
|
1800
|
+
function syncPluginTerraform(cwd) {
|
|
1801
|
+
const plugins = listWireablePlugins(cwd);
|
|
1802
|
+
const environments = listEnvironments(cwd);
|
|
1803
|
+
const skippedEnvironments = listUnwirableEnvironments(cwd);
|
|
1804
|
+
const changedPaths = [];
|
|
1805
|
+
const rendered = plugins.map((name) => {
|
|
1806
|
+
const firstParty = isFirstPartyPlugin(cwd, name);
|
|
1807
|
+
const moduleDir = firstParty ? join8(cwd, "services", "_plugins", name, "terraform") : join8(cwd, "modules", "plugins", name);
|
|
1808
|
+
return {
|
|
1809
|
+
name,
|
|
1810
|
+
declaredVariables: declaredVariables(moduleDir),
|
|
1811
|
+
source: pluginModuleSource(cwd, name)
|
|
1812
|
+
};
|
|
1813
|
+
});
|
|
1814
|
+
for (const env of environments) {
|
|
1815
|
+
const envDir = join8(cwd, "infra", "environments", env);
|
|
1816
|
+
const tfPath = join8(envDir, GENERATED_TF_FILE);
|
|
1817
|
+
const tfvarsPath = join8(envDir, GENERATED_TFVARS_FILE);
|
|
1818
|
+
const relBase = `infra/environments/${env}`;
|
|
1819
|
+
if (plugins.length === 0) {
|
|
1820
|
+
for (const [abs, rel] of [
|
|
1821
|
+
[tfPath, `${relBase}/${GENERATED_TF_FILE}`],
|
|
1822
|
+
[tfvarsPath, `${relBase}/${GENERATED_TFVARS_FILE}`]
|
|
1823
|
+
]) {
|
|
1824
|
+
if (existsSync6(abs)) {
|
|
1825
|
+
rmSync4(abs);
|
|
1826
|
+
changedPaths.push(rel);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
mkdirSync3(envDir, { recursive: true });
|
|
1832
|
+
writeFileSync4(tfPath, renderGeneratedTerraform(rendered));
|
|
1833
|
+
writeFileSync4(tfvarsPath, renderGeneratedTfvars(plugins));
|
|
1834
|
+
changedPaths.push(`${relBase}/${GENERATED_TF_FILE}`, `${relBase}/${GENERATED_TFVARS_FILE}`);
|
|
1835
|
+
}
|
|
1836
|
+
return { plugins, environments, skippedEnvironments, changedPaths };
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// src/lib/lockfile-refresh.ts
|
|
1840
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1841
|
+
import { join as join9 } from "path";
|
|
1640
1842
|
var LOCKFILE_TRIGGERS = [
|
|
1641
1843
|
{
|
|
1642
1844
|
manifest: "package.json",
|
|
@@ -1654,7 +1856,7 @@ var LOCKFILE_TRIGGERS = [
|
|
|
1654
1856
|
function lockfilesNeedingRefresh(changedPaths, instanceDir, triggers = LOCKFILE_TRIGGERS) {
|
|
1655
1857
|
return triggers.filter((t) => {
|
|
1656
1858
|
const touched = changedPaths.some((p) => p === t.manifest || p.endsWith(`/${t.manifest}`));
|
|
1657
|
-
return touched &&
|
|
1859
|
+
return touched && existsSync7(join9(instanceDir, t.lockfile));
|
|
1658
1860
|
});
|
|
1659
1861
|
}
|
|
1660
1862
|
async function refreshLockfiles(instanceDir, triggers, run) {
|
|
@@ -1743,9 +1945,9 @@ async function runCoreUpgradeResolved(options, deps, cleanups) {
|
|
|
1743
1945
|
let toVersion;
|
|
1744
1946
|
if (options.theirsDir) {
|
|
1745
1947
|
theirsDir = options.theirsDir;
|
|
1746
|
-
toVersion = readCoreVersionFile(
|
|
1948
|
+
toVersion = readCoreVersionFile(join10(theirsDir, "core.version"));
|
|
1747
1949
|
} else {
|
|
1748
|
-
const workingVersion = readCoreVersionFile(
|
|
1950
|
+
const workingVersion = readCoreVersionFile(join10(templateRepo, "core.version"));
|
|
1749
1951
|
toVersion = options.toVersion ?? workingVersion;
|
|
1750
1952
|
if (toVersion === workingVersion) {
|
|
1751
1953
|
theirsDir = templateRepo;
|
|
@@ -1759,7 +1961,7 @@ async function runCoreUpgradeResolved(options, deps, cleanups) {
|
|
|
1759
1961
|
let fromVersion;
|
|
1760
1962
|
if (options.baseDir) {
|
|
1761
1963
|
baseDir = options.baseDir;
|
|
1762
|
-
fromVersion = readCoreVersionFile(
|
|
1964
|
+
fromVersion = readCoreVersionFile(join10(baseDir, "core.version"));
|
|
1763
1965
|
} else {
|
|
1764
1966
|
if (instanceVersion === null) {
|
|
1765
1967
|
throw new Error(
|
|
@@ -1793,6 +1995,7 @@ async function runCoreUpgradeResolved(options, deps, cleanups) {
|
|
|
1793
1995
|
}
|
|
1794
1996
|
printPlan(plan);
|
|
1795
1997
|
printMigrationCarry(migrations);
|
|
1998
|
+
warnStaleFirstPartyCopies(options.cwd);
|
|
1796
1999
|
console.log(
|
|
1797
2000
|
`
|
|
1798
2001
|
${chalk4.bold(String(plan.changes.length))} change(s), ${chalk4.bold(String(migrations.entries.length))} new core migration(s), ${plan.conflicts.length > 0 ? chalk4.red(`${plan.conflicts.length} conflict(s)`) : chalk4.green("0 conflicts")}.`
|
|
@@ -1991,6 +2194,13 @@ var defaultRunCommand = async (command, cwd) => {
|
|
|
1991
2194
|
return { ok: false, error: detail.split("\n")[0] ?? "failed" };
|
|
1992
2195
|
}
|
|
1993
2196
|
};
|
|
2197
|
+
function warnStaleFirstPartyCopies(cwd) {
|
|
2198
|
+
const stale = staleFirstPartyCopies(cwd);
|
|
2199
|
+
if (stale.length === 0) return;
|
|
2200
|
+
log.warn(
|
|
2201
|
+
`${stale.length} first-party plugin(s) still have a copy under modules/plugins/: ${stale.join(", ")}. An upgrade never updates those copies, so if infra/environments/*/ still sources them, this plugin's infrastructure changes are NOT deployed. Point the module source at services/_plugins/<name>/terraform and delete the copy \u2014 see docs/guides/core-upgrade.md.`
|
|
2202
|
+
);
|
|
2203
|
+
}
|
|
1994
2204
|
|
|
1995
2205
|
// src/commands/core.ts
|
|
1996
2206
|
var coreCommand = new Command4("core").description(
|
|
@@ -2004,7 +2214,7 @@ coreCommand.addCommand(coreUpgradeCommand);
|
|
|
2004
2214
|
import { Command as Command8 } from "commander";
|
|
2005
2215
|
|
|
2006
2216
|
// src/commands/data-apply.ts
|
|
2007
|
-
import { existsSync as
|
|
2217
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
2008
2218
|
import { resolve as resolve4 } from "path";
|
|
2009
2219
|
import chalk5 from "chalk";
|
|
2010
2220
|
import { Command as Command5 } from "commander";
|
|
@@ -2455,16 +2665,16 @@ function isTemplatePlaceholderConfig(raw) {
|
|
|
2455
2665
|
|
|
2456
2666
|
// src/lib/session.ts
|
|
2457
2667
|
import {
|
|
2458
|
-
existsSync as
|
|
2459
|
-
mkdirSync as
|
|
2460
|
-
readdirSync as
|
|
2461
|
-
readFileSync as
|
|
2462
|
-
rmSync as
|
|
2668
|
+
existsSync as existsSync8,
|
|
2669
|
+
mkdirSync as mkdirSync4,
|
|
2670
|
+
readdirSync as readdirSync4,
|
|
2671
|
+
readFileSync as readFileSync6,
|
|
2672
|
+
rmSync as rmSync5,
|
|
2463
2673
|
statSync,
|
|
2464
|
-
writeFileSync as
|
|
2674
|
+
writeFileSync as writeFileSync5
|
|
2465
2675
|
} from "fs";
|
|
2466
2676
|
import { homedir } from "os";
|
|
2467
|
-
import { join as
|
|
2677
|
+
import { join as join11 } from "path";
|
|
2468
2678
|
var LEGACY_STEP_ALIASES = {
|
|
2469
2679
|
github_config: ["github_branches", "github_instance_files", "github_settings"]
|
|
2470
2680
|
};
|
|
@@ -2473,39 +2683,39 @@ function hasCompleted(session, step) {
|
|
|
2473
2683
|
return session.completedSteps.some((done) => LEGACY_STEP_ALIASES[done]?.includes(step) ?? false);
|
|
2474
2684
|
}
|
|
2475
2685
|
function sessionsDir() {
|
|
2476
|
-
return process.env["BIFFO_SESSIONS_DIR"] ??
|
|
2686
|
+
return process.env["BIFFO_SESSIONS_DIR"] ?? join11(homedir(), ".biffo", "sessions");
|
|
2477
2687
|
}
|
|
2478
2688
|
function sessionPath(projectName) {
|
|
2479
|
-
return
|
|
2689
|
+
return join11(sessionsDir(), `${projectName}.json`);
|
|
2480
2690
|
}
|
|
2481
2691
|
function loadSession(projectName) {
|
|
2482
2692
|
const path = sessionPath(projectName);
|
|
2483
|
-
if (!
|
|
2693
|
+
if (!existsSync8(path)) return null;
|
|
2484
2694
|
try {
|
|
2485
|
-
return JSON.parse(
|
|
2695
|
+
return JSON.parse(readFileSync6(path, "utf8"));
|
|
2486
2696
|
} catch {
|
|
2487
2697
|
return null;
|
|
2488
2698
|
}
|
|
2489
2699
|
}
|
|
2490
2700
|
function findLatestSession() {
|
|
2491
2701
|
const dir = sessionsDir();
|
|
2492
|
-
if (!
|
|
2493
|
-
const files =
|
|
2702
|
+
if (!existsSync8(dir)) return null;
|
|
2703
|
+
const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
|
|
2494
2704
|
if (files.length === 0) return null;
|
|
2495
2705
|
const sorted = files.map((f) => {
|
|
2496
|
-
const fullPath =
|
|
2497
|
-
const mtime =
|
|
2706
|
+
const fullPath = join11(dir, f);
|
|
2707
|
+
const mtime = existsSync8(fullPath) ? statSync(fullPath).mtimeMs : -1;
|
|
2498
2708
|
return { f, mtime };
|
|
2499
2709
|
}).sort((a, b) => b.mtime - a.mtime);
|
|
2500
2710
|
try {
|
|
2501
|
-
return JSON.parse(
|
|
2711
|
+
return JSON.parse(readFileSync6(join11(dir, sorted[0].f), "utf8"));
|
|
2502
2712
|
} catch {
|
|
2503
2713
|
return null;
|
|
2504
2714
|
}
|
|
2505
2715
|
}
|
|
2506
2716
|
function saveSession(session) {
|
|
2507
2717
|
const dir = sessionsDir();
|
|
2508
|
-
if (!
|
|
2718
|
+
if (!existsSync8(dir)) mkdirSync4(dir, { recursive: true });
|
|
2509
2719
|
const name = session.config.project?.name ?? "unknown";
|
|
2510
2720
|
const prior = loadSession(name);
|
|
2511
2721
|
if (prior) {
|
|
@@ -2514,7 +2724,7 @@ function saveSession(session) {
|
|
|
2514
2724
|
}
|
|
2515
2725
|
session.outputs = { ...prior.outputs, ...definedOnly(session.outputs) };
|
|
2516
2726
|
}
|
|
2517
|
-
|
|
2727
|
+
writeFileSync5(sessionPath(name), JSON.stringify(session, null, 2));
|
|
2518
2728
|
}
|
|
2519
2729
|
function definedOnly(value) {
|
|
2520
2730
|
return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0));
|
|
@@ -2527,36 +2737,36 @@ function markStepComplete(session, step) {
|
|
|
2527
2737
|
}
|
|
2528
2738
|
function deleteSession(projectName) {
|
|
2529
2739
|
const path = sessionPath(projectName);
|
|
2530
|
-
if (
|
|
2740
|
+
if (existsSync8(path)) rmSync5(path);
|
|
2531
2741
|
}
|
|
2532
2742
|
function projectsDir() {
|
|
2533
|
-
return process.env["BIFFO_PROJECTS_DIR"] ??
|
|
2743
|
+
return process.env["BIFFO_PROJECTS_DIR"] ?? join11(homedir(), ".biffo", "projects");
|
|
2534
2744
|
}
|
|
2535
2745
|
function saveProjectConfig(config) {
|
|
2536
2746
|
const dir = projectsDir();
|
|
2537
|
-
if (!
|
|
2538
|
-
|
|
2747
|
+
if (!existsSync8(dir)) mkdirSync4(dir, { recursive: true });
|
|
2748
|
+
writeFileSync5(join11(dir, `${config.project.name}.json`), JSON.stringify(config, null, 2));
|
|
2539
2749
|
}
|
|
2540
2750
|
function loadProjectConfig(name) {
|
|
2541
|
-
const path =
|
|
2542
|
-
if (!
|
|
2751
|
+
const path = join11(projectsDir(), `${name}.json`);
|
|
2752
|
+
if (!existsSync8(path)) return null;
|
|
2543
2753
|
try {
|
|
2544
|
-
const result = BiffoConfigSchema.safeParse(JSON.parse(
|
|
2754
|
+
const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync6(path, "utf8")));
|
|
2545
2755
|
return result.success ? result.data : null;
|
|
2546
2756
|
} catch {
|
|
2547
2757
|
return null;
|
|
2548
2758
|
}
|
|
2549
2759
|
}
|
|
2550
2760
|
function deleteProjectConfig(name) {
|
|
2551
|
-
const path =
|
|
2552
|
-
if (
|
|
2761
|
+
const path = join11(projectsDir(), `${name}.json`);
|
|
2762
|
+
if (existsSync8(path)) rmSync5(path);
|
|
2553
2763
|
}
|
|
2554
2764
|
function listProjectConfigs() {
|
|
2555
2765
|
const dir = projectsDir();
|
|
2556
|
-
if (!
|
|
2557
|
-
return
|
|
2766
|
+
if (!existsSync8(dir)) return [];
|
|
2767
|
+
return readdirSync4(dir).filter((f) => f.endsWith(".json")).flatMap((f) => {
|
|
2558
2768
|
try {
|
|
2559
|
-
const result = BiffoConfigSchema.safeParse(JSON.parse(
|
|
2769
|
+
const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync6(join11(dir, f), "utf8")));
|
|
2560
2770
|
return result.success ? [result.data] : [];
|
|
2561
2771
|
} catch {
|
|
2562
2772
|
return [];
|
|
@@ -2625,7 +2835,7 @@ async function runDataApply(name, environment, config, aws) {
|
|
|
2625
2835
|
}
|
|
2626
2836
|
async function resolveConfig(options) {
|
|
2627
2837
|
if (options.config) {
|
|
2628
|
-
const raw = JSON.parse(
|
|
2838
|
+
const raw = JSON.parse(readFileSync7(resolve4(options.config), "utf8"));
|
|
2629
2839
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
2630
2840
|
if (!result.success) {
|
|
2631
2841
|
log.error(`Invalid config at ${options.config}:`);
|
|
@@ -2645,8 +2855,8 @@ async function resolveConfig(options) {
|
|
|
2645
2855
|
return cfg;
|
|
2646
2856
|
}
|
|
2647
2857
|
const localConfigPath = resolve4(process.cwd(), "biffo.config.json");
|
|
2648
|
-
if (
|
|
2649
|
-
const raw = JSON.parse(
|
|
2858
|
+
if (existsSync9(localConfigPath)) {
|
|
2859
|
+
const raw = JSON.parse(readFileSync7(localConfigPath, "utf8"));
|
|
2650
2860
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
2651
2861
|
if (result.success) return result.data;
|
|
2652
2862
|
if (isTemplatePlaceholderConfig(raw)) {
|
|
@@ -2692,8 +2902,8 @@ async function resolveConfig(options) {
|
|
|
2692
2902
|
|
|
2693
2903
|
// src/commands/data-import.ts
|
|
2694
2904
|
import { execSync as execSync3 } from "child_process";
|
|
2695
|
-
import { cpSync, existsSync as
|
|
2696
|
-
import { join as
|
|
2905
|
+
import { cpSync, existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
|
|
2906
|
+
import { join as join12, resolve as resolve5 } from "path";
|
|
2697
2907
|
import chalk6 from "chalk";
|
|
2698
2908
|
import { Command as Command6 } from "commander";
|
|
2699
2909
|
import inquirer2 from "inquirer";
|
|
@@ -2733,23 +2943,23 @@ async function runDataImport(name, options, deps) {
|
|
|
2733
2943
|
`Invalid import name '${name}'. Use lowercase letters, numbers, and hyphens, starting with a letter.`
|
|
2734
2944
|
);
|
|
2735
2945
|
}
|
|
2736
|
-
const servicesDir =
|
|
2737
|
-
if (!
|
|
2946
|
+
const servicesDir = join12(options.cwd, "services");
|
|
2947
|
+
if (!existsSync10(servicesDir)) {
|
|
2738
2948
|
throw new Error(
|
|
2739
2949
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
2740
2950
|
);
|
|
2741
2951
|
}
|
|
2742
|
-
const targetDir =
|
|
2743
|
-
if (
|
|
2952
|
+
const targetDir = join12(options.cwd, "db", "imports", name);
|
|
2953
|
+
if (existsSync10(targetDir)) {
|
|
2744
2954
|
throw new Error(
|
|
2745
2955
|
`DDL import '${name}' is already present at db/imports/${name}/. Remove it first to re-import.`
|
|
2746
2956
|
);
|
|
2747
2957
|
}
|
|
2748
|
-
const isLocalDir =
|
|
2958
|
+
const isLocalDir = existsSync10(options.source) && statSync2(options.source).isDirectory();
|
|
2749
2959
|
let sourceDir;
|
|
2750
2960
|
let cleanupClone = null;
|
|
2751
2961
|
if (isLocalDir) {
|
|
2752
|
-
sourceDir = options.path ?
|
|
2962
|
+
sourceDir = options.path ? join12(options.source, options.path) : options.source;
|
|
2753
2963
|
} else {
|
|
2754
2964
|
const token = options.token ?? await resolveDdlImportToken();
|
|
2755
2965
|
log.info(`Cloning ${options.source}...`);
|
|
@@ -2757,13 +2967,13 @@ async function runDataImport(name, options, deps) {
|
|
|
2757
2967
|
cleanupClone = () => {
|
|
2758
2968
|
deps.git.cleanup(tmpDir);
|
|
2759
2969
|
};
|
|
2760
|
-
sourceDir = options.path ?
|
|
2970
|
+
sourceDir = options.path ? join12(tmpDir, options.path) : tmpDir;
|
|
2761
2971
|
}
|
|
2762
2972
|
try {
|
|
2763
|
-
if (!
|
|
2973
|
+
if (!existsSync10(sourceDir)) {
|
|
2764
2974
|
throw new Error(`Source directory does not exist: ${sourceDir}`);
|
|
2765
2975
|
}
|
|
2766
|
-
const sqlFiles =
|
|
2976
|
+
const sqlFiles = readdirSync5(sourceDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".sql")).map((entry) => entry.name).sort();
|
|
2767
2977
|
if (sqlFiles.length === 0) {
|
|
2768
2978
|
throw new Error(`No .sql files found at ${sourceDir}.`);
|
|
2769
2979
|
}
|
|
@@ -2783,9 +2993,9 @@ async function runDataImport(name, options, deps) {
|
|
|
2783
2993
|
`${options.cwd} is not a git repository \u2014 biffo data import must be run from a Biffo project checkout.`
|
|
2784
2994
|
);
|
|
2785
2995
|
}
|
|
2786
|
-
|
|
2996
|
+
mkdirSync5(targetDir, { recursive: true });
|
|
2787
2997
|
for (const file of sqlFiles) {
|
|
2788
|
-
cpSync(
|
|
2998
|
+
cpSync(join12(sourceDir, file), join12(targetDir, file));
|
|
2789
2999
|
}
|
|
2790
3000
|
log.success(`Imported ${String(sqlFiles.length)} .sql file(s) to db/imports/${name}/`);
|
|
2791
3001
|
const commitMessage = `feat(data): import ${name} (${String(sqlFiles.length)} SQL file(s))`;
|
|
@@ -2837,8 +3047,8 @@ function printDryRun(name, sqlFiles) {
|
|
|
2837
3047
|
}
|
|
2838
3048
|
|
|
2839
3049
|
// src/commands/data-list.ts
|
|
2840
|
-
import { existsSync as
|
|
2841
|
-
import { join as
|
|
3050
|
+
import { existsSync as existsSync11, readdirSync as readdirSync6 } from "fs";
|
|
3051
|
+
import { join as join13, resolve as resolve6 } from "path";
|
|
2842
3052
|
import chalk7 from "chalk";
|
|
2843
3053
|
import { Command as Command7 } from "commander";
|
|
2844
3054
|
var dataListCommand = new Command7("list").description("List DDL imports vendored in this project checkout").option("--cwd <path>", "Project root to scan (defaults to the current directory)").action(async (options) => {
|
|
@@ -2851,15 +3061,15 @@ var dataListCommand = new Command7("list").description("List DDL imports vendore
|
|
|
2851
3061
|
}
|
|
2852
3062
|
});
|
|
2853
3063
|
async function runDataList(options) {
|
|
2854
|
-
const importsDir =
|
|
2855
|
-
if (!
|
|
3064
|
+
const importsDir = join13(options.cwd, "db", "imports");
|
|
3065
|
+
if (!existsSync11(importsDir)) {
|
|
2856
3066
|
console.log(chalk7.dim("\n No DDL imports in this checkout.\n"));
|
|
2857
3067
|
return;
|
|
2858
3068
|
}
|
|
2859
|
-
const candidates =
|
|
3069
|
+
const candidates = readdirSync6(importsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
2860
3070
|
const imports = [];
|
|
2861
3071
|
for (const name of candidates) {
|
|
2862
|
-
const fileCount =
|
|
3072
|
+
const fileCount = readdirSync6(join13(importsDir, name)).filter((f) => f.endsWith(".sql")).length;
|
|
2863
3073
|
if (fileCount > 0) imports.push({ name, fileCount });
|
|
2864
3074
|
}
|
|
2865
3075
|
if (imports.length === 0) {
|
|
@@ -2889,7 +3099,7 @@ dataCommand.addCommand(dataListCommand);
|
|
|
2889
3099
|
|
|
2890
3100
|
// src/commands/deploy.ts
|
|
2891
3101
|
import { execSync as execSync4 } from "child_process";
|
|
2892
|
-
import { existsSync as
|
|
3102
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
|
|
2893
3103
|
import { resolve as resolve7 } from "path";
|
|
2894
3104
|
import chalk8 from "chalk";
|
|
2895
3105
|
import { Command as Command9 } from "commander";
|
|
@@ -3243,7 +3453,7 @@ var deployCommand = new Command9("deploy").description("Deploy infrastructure an
|
|
|
3243
3453
|
);
|
|
3244
3454
|
async function resolveConfig2(options) {
|
|
3245
3455
|
if (options.config) {
|
|
3246
|
-
const raw = JSON.parse(
|
|
3456
|
+
const raw = JSON.parse(readFileSync8(resolve7(options.config), "utf8"));
|
|
3247
3457
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
3248
3458
|
if (!result.success) {
|
|
3249
3459
|
log.error(`Invalid config at ${options.config}:`);
|
|
@@ -3263,8 +3473,8 @@ async function resolveConfig2(options) {
|
|
|
3263
3473
|
return cfg;
|
|
3264
3474
|
}
|
|
3265
3475
|
const localConfigPath = resolve7(process.cwd(), "biffo.config.json");
|
|
3266
|
-
if (
|
|
3267
|
-
const raw = JSON.parse(
|
|
3476
|
+
if (existsSync12(localConfigPath)) {
|
|
3477
|
+
const raw = JSON.parse(readFileSync8(localConfigPath, "utf8"));
|
|
3268
3478
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
3269
3479
|
if (result.success) return result.data;
|
|
3270
3480
|
if (isTemplatePlaceholderConfig(raw)) {
|
|
@@ -3648,7 +3858,7 @@ function resolveGithubToken() {
|
|
|
3648
3858
|
|
|
3649
3859
|
// src/commands/destroy.ts
|
|
3650
3860
|
import { execSync as execSync5 } from "child_process";
|
|
3651
|
-
import { readFileSync as
|
|
3861
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
3652
3862
|
import { resolve as resolve8 } from "path";
|
|
3653
3863
|
import chalk9 from "chalk";
|
|
3654
3864
|
import { Command as Command10 } from "commander";
|
|
@@ -3738,7 +3948,7 @@ var destroyCommand = new Command10("destroy").description("Destroy infrastructur
|
|
|
3738
3948
|
});
|
|
3739
3949
|
async function resolveConfig3(options) {
|
|
3740
3950
|
if (options.config) {
|
|
3741
|
-
const raw = JSON.parse(
|
|
3951
|
+
const raw = JSON.parse(readFileSync9(resolve8(options.config), "utf8"));
|
|
3742
3952
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
3743
3953
|
if (!result.success) {
|
|
3744
3954
|
log.error(`Invalid config at ${options.config}:`);
|
|
@@ -3758,7 +3968,7 @@ async function resolveConfig3(options) {
|
|
|
3758
3968
|
return cfg;
|
|
3759
3969
|
}
|
|
3760
3970
|
try {
|
|
3761
|
-
const raw = JSON.parse(
|
|
3971
|
+
const raw = JSON.parse(readFileSync9(resolve8(process.cwd(), "biffo.config.json"), "utf8"));
|
|
3762
3972
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
3763
3973
|
if (result.success) return result.data;
|
|
3764
3974
|
} catch {
|
|
@@ -3808,15 +4018,15 @@ function resolveGithubToken2() {
|
|
|
3808
4018
|
}
|
|
3809
4019
|
|
|
3810
4020
|
// src/commands/init.ts
|
|
3811
|
-
import { readFileSync as
|
|
4021
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
3812
4022
|
import { resolve as resolve10 } from "path";
|
|
3813
4023
|
import chalk12 from "chalk";
|
|
3814
4024
|
import { Command as Command12 } from "commander";
|
|
3815
4025
|
import inquirer5 from "inquirer";
|
|
3816
4026
|
|
|
3817
4027
|
// src/lib/build-freshness.ts
|
|
3818
|
-
import { existsSync as
|
|
3819
|
-
import { dirname as dirname5, join as
|
|
4028
|
+
import { existsSync as existsSync13, readdirSync as readdirSync7, statSync as statSync3 } from "fs";
|
|
4029
|
+
import { dirname as dirname5, join as join14, relative as relative2, sep as sep2 } from "path";
|
|
3820
4030
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3821
4031
|
var SKIP_ENV_VAR = "BIFFO_SKIP_BUILD_FRESHNESS_CHECK";
|
|
3822
4032
|
function checkBuildFreshness(options = {}) {
|
|
@@ -3830,7 +4040,7 @@ function checkBuildFreshness(options = {}) {
|
|
|
3830
4040
|
if (!packageRoot) {
|
|
3831
4041
|
return { status: "skipped", reason: `no package.json above ${moduleDir}`, newerSources: [] };
|
|
3832
4042
|
}
|
|
3833
|
-
const distDir =
|
|
4043
|
+
const distDir = join14(packageRoot, "dist");
|
|
3834
4044
|
if (!isInside(distDir, moduleDir)) {
|
|
3835
4045
|
return {
|
|
3836
4046
|
status: "skipped",
|
|
@@ -3838,16 +4048,16 @@ function checkBuildFreshness(options = {}) {
|
|
|
3838
4048
|
newerSources: []
|
|
3839
4049
|
};
|
|
3840
4050
|
}
|
|
3841
|
-
const srcDir =
|
|
3842
|
-
if (!
|
|
4051
|
+
const srcDir = join14(packageRoot, "src");
|
|
4052
|
+
if (!existsSync13(srcDir)) {
|
|
3843
4053
|
return {
|
|
3844
4054
|
status: "skipped",
|
|
3845
4055
|
reason: "no src/ alongside dist/ \u2014 this is a shipped package",
|
|
3846
4056
|
newerSources: []
|
|
3847
4057
|
};
|
|
3848
4058
|
}
|
|
3849
|
-
const entry =
|
|
3850
|
-
if (!
|
|
4059
|
+
const entry = join14(distDir, "index.js");
|
|
4060
|
+
if (!existsSync13(entry)) {
|
|
3851
4061
|
return { status: "skipped", reason: `${entry} not found`, newerSources: [] };
|
|
3852
4062
|
}
|
|
3853
4063
|
const builtAt = statSync3(entry).mtimeMs;
|
|
@@ -3890,8 +4100,8 @@ function formatStaleBuildError(result) {
|
|
|
3890
4100
|
function collectSourceFiles(srcDir) {
|
|
3891
4101
|
const found = [];
|
|
3892
4102
|
const walk = (dir) => {
|
|
3893
|
-
for (const entry of
|
|
3894
|
-
const full =
|
|
4103
|
+
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
4104
|
+
const full = join14(dir, entry.name);
|
|
3895
4105
|
if (entry.isDirectory()) {
|
|
3896
4106
|
if (entry.name === "node_modules") continue;
|
|
3897
4107
|
walk(full);
|
|
@@ -3910,7 +4120,7 @@ function collectSourceFiles(srcDir) {
|
|
|
3910
4120
|
function findPackageRoot(from) {
|
|
3911
4121
|
let dir = from;
|
|
3912
4122
|
for (; ; ) {
|
|
3913
|
-
if (
|
|
4123
|
+
if (existsSync13(join14(dir, "package.json"))) return dir;
|
|
3914
4124
|
const parent = dirname5(dir);
|
|
3915
4125
|
if (parent === dir) return null;
|
|
3916
4126
|
dir = parent;
|
|
@@ -3924,9 +4134,9 @@ function isInside(parent, child) {
|
|
|
3924
4134
|
|
|
3925
4135
|
// src/lib/credentials.ts
|
|
3926
4136
|
import { execSync as execSync6 } from "child_process";
|
|
3927
|
-
import { existsSync as
|
|
4137
|
+
import { existsSync as existsSync14, readFileSync as readFileSync10 } from "fs";
|
|
3928
4138
|
import { homedir as homedir2 } from "os";
|
|
3929
|
-
import { join as
|
|
4139
|
+
import { join as join15 } from "path";
|
|
3930
4140
|
import { GetCallerIdentityCommand as GetCallerIdentityCommand2, STSClient as STSClient2 } from "@aws-sdk/client-sts";
|
|
3931
4141
|
import chalk10 from "chalk";
|
|
3932
4142
|
import inquirer4 from "inquirer";
|
|
@@ -4105,11 +4315,11 @@ async function verifySelectedAwsCredentials(profile, region) {
|
|
|
4105
4315
|
return sts.send(new GetCallerIdentityCommand2({}));
|
|
4106
4316
|
}
|
|
4107
4317
|
function discoverAwsProfiles() {
|
|
4108
|
-
const files = [
|
|
4318
|
+
const files = [join15(homedir2(), ".aws", "credentials"), join15(homedir2(), ".aws", "config")];
|
|
4109
4319
|
const profiles = /* @__PURE__ */ new Set();
|
|
4110
4320
|
for (const file of files) {
|
|
4111
|
-
if (!
|
|
4112
|
-
const content =
|
|
4321
|
+
if (!existsSync14(file)) continue;
|
|
4322
|
+
const content = readFileSync10(file, "utf8");
|
|
4113
4323
|
for (const match of content.matchAll(/^\s*\[([^\]]+)\]\s*$/gm)) {
|
|
4114
4324
|
const section = match[1]?.trim();
|
|
4115
4325
|
if (!section) continue;
|
|
@@ -4199,34 +4409,34 @@ var SiblingConfigSchema = z4.object({
|
|
|
4199
4409
|
|
|
4200
4410
|
// src/lib/sibling-session.ts
|
|
4201
4411
|
import {
|
|
4202
|
-
existsSync as
|
|
4203
|
-
mkdirSync as
|
|
4204
|
-
readdirSync as
|
|
4205
|
-
readFileSync as
|
|
4206
|
-
rmSync as
|
|
4412
|
+
existsSync as existsSync15,
|
|
4413
|
+
mkdirSync as mkdirSync6,
|
|
4414
|
+
readdirSync as readdirSync8,
|
|
4415
|
+
readFileSync as readFileSync11,
|
|
4416
|
+
rmSync as rmSync6,
|
|
4207
4417
|
statSync as statSync4,
|
|
4208
|
-
writeFileSync as
|
|
4418
|
+
writeFileSync as writeFileSync6
|
|
4209
4419
|
} from "fs";
|
|
4210
4420
|
import { homedir as homedir3 } from "os";
|
|
4211
|
-
import { join as
|
|
4421
|
+
import { join as join16 } from "path";
|
|
4212
4422
|
function sessionsDir2() {
|
|
4213
|
-
return process.env["BIFFO_SIBLING_SESSIONS_DIR"] ??
|
|
4423
|
+
return process.env["BIFFO_SIBLING_SESSIONS_DIR"] ?? join16(homedir3(), ".biffo", "sibling-sessions");
|
|
4214
4424
|
}
|
|
4215
4425
|
function sessionPath2(projectName) {
|
|
4216
|
-
return
|
|
4426
|
+
return join16(sessionsDir2(), `${projectName}.json`);
|
|
4217
4427
|
}
|
|
4218
4428
|
function loadSiblingSession(projectName) {
|
|
4219
4429
|
const path = sessionPath2(projectName);
|
|
4220
|
-
if (!
|
|
4430
|
+
if (!existsSync15(path)) return null;
|
|
4221
4431
|
try {
|
|
4222
|
-
return JSON.parse(
|
|
4432
|
+
return JSON.parse(readFileSync11(path, "utf8"));
|
|
4223
4433
|
} catch {
|
|
4224
4434
|
return null;
|
|
4225
4435
|
}
|
|
4226
4436
|
}
|
|
4227
4437
|
function saveSiblingSession(session) {
|
|
4228
4438
|
const dir = sessionsDir2();
|
|
4229
|
-
if (!
|
|
4439
|
+
if (!existsSync15(dir)) mkdirSync6(dir, { recursive: true });
|
|
4230
4440
|
const name = session.config.project?.name ?? "unknown";
|
|
4231
4441
|
const prior = loadSiblingSession(name);
|
|
4232
4442
|
if (prior) {
|
|
@@ -4235,7 +4445,7 @@ function saveSiblingSession(session) {
|
|
|
4235
4445
|
}
|
|
4236
4446
|
session.outputs = { ...prior.outputs, ...definedOnly2(session.outputs) };
|
|
4237
4447
|
}
|
|
4238
|
-
|
|
4448
|
+
writeFileSync6(sessionPath2(name), JSON.stringify(session, null, 2));
|
|
4239
4449
|
}
|
|
4240
4450
|
function definedOnly2(value) {
|
|
4241
4451
|
return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0));
|
|
@@ -4248,30 +4458,30 @@ function markSiblingStepComplete(session, step) {
|
|
|
4248
4458
|
}
|
|
4249
4459
|
function deleteSiblingSession(projectName) {
|
|
4250
4460
|
const path = sessionPath2(projectName);
|
|
4251
|
-
if (
|
|
4461
|
+
if (existsSync15(path)) rmSync6(path);
|
|
4252
4462
|
}
|
|
4253
4463
|
|
|
4254
4464
|
// src/commands/sibling-create.ts
|
|
4255
|
-
import { cpSync as cpSync2, existsSync as
|
|
4465
|
+
import { cpSync as cpSync2, existsSync as existsSync16, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
|
|
4256
4466
|
import { tmpdir as tmpdir4 } from "os";
|
|
4257
|
-
import { dirname as dirname6, join as
|
|
4467
|
+
import { dirname as dirname6, join as join18, resolve as resolve9 } from "path";
|
|
4258
4468
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4259
4469
|
import chalk11 from "chalk";
|
|
4260
4470
|
import { Command as Command11 } from "commander";
|
|
4261
4471
|
|
|
4262
4472
|
// src/lib/skeleton-dotfiles.ts
|
|
4263
|
-
import { readdirSync as
|
|
4264
|
-
import { join as
|
|
4473
|
+
import { readdirSync as readdirSync9, renameSync } from "fs";
|
|
4474
|
+
import { join as join17 } from "path";
|
|
4265
4475
|
var PACKAGED_GITIGNORE = "_gitignore";
|
|
4266
4476
|
var REAL_GITIGNORE = ".gitignore";
|
|
4267
4477
|
function restorePackagedDotfiles(dir) {
|
|
4268
4478
|
const restored = [];
|
|
4269
|
-
for (const entry of
|
|
4270
|
-
const full =
|
|
4479
|
+
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
4480
|
+
const full = join17(dir, entry.name);
|
|
4271
4481
|
if (entry.isDirectory()) {
|
|
4272
4482
|
restored.push(...restorePackagedDotfiles(full));
|
|
4273
4483
|
} else if (entry.name === PACKAGED_GITIGNORE) {
|
|
4274
|
-
const target =
|
|
4484
|
+
const target = join17(dir, REAL_GITIGNORE);
|
|
4275
4485
|
renameSync(full, target);
|
|
4276
4486
|
restored.push(target);
|
|
4277
4487
|
}
|
|
@@ -4316,7 +4526,7 @@ async function runSiblingCreateCommand(name, options) {
|
|
|
4316
4526
|
printDryRun2(config, coreConfig, options.templateRoot);
|
|
4317
4527
|
return;
|
|
4318
4528
|
}
|
|
4319
|
-
if (!
|
|
4529
|
+
if (!existsSync16(options.templateRoot)) {
|
|
4320
4530
|
throw new Error(`Sibling template not found at ${options.templateRoot}`);
|
|
4321
4531
|
}
|
|
4322
4532
|
let session = null;
|
|
@@ -4501,7 +4711,7 @@ function assertPathPrefixIsAllowed(pathPrefix) {
|
|
|
4501
4711
|
}
|
|
4502
4712
|
}
|
|
4503
4713
|
function readSiblingConfig(path, root = false) {
|
|
4504
|
-
const raw = JSON.parse(
|
|
4714
|
+
const raw = JSON.parse(readFileSync12(path, "utf8"));
|
|
4505
4715
|
const withDefaults = raw && typeof raw === "object" && "project" in raw && "core" in raw ? {
|
|
4506
4716
|
...raw,
|
|
4507
4717
|
core: {
|
|
@@ -4535,7 +4745,7 @@ function resolveCoreConfig(config, configPath) {
|
|
|
4535
4745
|
throw new Error("Either core.project_name or core.config_path is required.");
|
|
4536
4746
|
}
|
|
4537
4747
|
function parseCoreConfig(path) {
|
|
4538
|
-
const result = BiffoConfigSchema.safeParse(JSON.parse(
|
|
4748
|
+
const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync12(path, "utf8")));
|
|
4539
4749
|
if (!result.success) {
|
|
4540
4750
|
throw new Error(
|
|
4541
4751
|
`Invalid core configuration at ${path}:
|
|
@@ -4574,7 +4784,7 @@ async function resolveCoreIdentity(coreAws, coreConfig, environments) {
|
|
|
4574
4784
|
return coreIdentity;
|
|
4575
4785
|
}
|
|
4576
4786
|
async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, githubToken) {
|
|
4577
|
-
const workDir = mkdtempSync4(
|
|
4787
|
+
const workDir = mkdtempSync4(join18(tmpdir4(), `biffo-sibling-${config.project.name}-`));
|
|
4578
4788
|
try {
|
|
4579
4789
|
writeSiblingTemplate(skeletonRoot, workDir, config, {
|
|
4580
4790
|
coreProjectName: coreConfig.project.name,
|
|
@@ -4590,13 +4800,13 @@ async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, git
|
|
|
4590
4800
|
}
|
|
4591
4801
|
}
|
|
4592
4802
|
function writeSiblingTemplate(templateRoot, targetDir, config, context) {
|
|
4593
|
-
if (!
|
|
4803
|
+
if (!existsSync16(templateRoot)) {
|
|
4594
4804
|
throw new Error(`Sibling template not found at ${templateRoot}`);
|
|
4595
4805
|
}
|
|
4596
4806
|
cpSync2(templateRoot, targetDir, { recursive: true });
|
|
4597
4807
|
restorePackagedDotfiles(targetDir);
|
|
4598
|
-
|
|
4599
|
-
|
|
4808
|
+
writeFileSync7(
|
|
4809
|
+
join18(targetDir, "biffo.sibling.json"),
|
|
4600
4810
|
JSON.stringify(
|
|
4601
4811
|
{
|
|
4602
4812
|
name: config.project.name,
|
|
@@ -4612,11 +4822,11 @@ function writeSiblingTemplate(templateRoot, targetDir, config, context) {
|
|
|
4612
4822
|
2
|
|
4613
4823
|
) + "\n"
|
|
4614
4824
|
);
|
|
4615
|
-
const envPath =
|
|
4825
|
+
const envPath = join18(targetDir, "apps", "frontend", ".env.example");
|
|
4616
4826
|
try {
|
|
4617
4827
|
const path = basePathFor(context.pathPrefix);
|
|
4618
|
-
const content =
|
|
4619
|
-
|
|
4828
|
+
const content = readFileSync12(envPath, "utf8").replace(/^NEXT_PUBLIC_SIBLING_NAME=.*$/m, `NEXT_PUBLIC_SIBLING_NAME=${config.project.name}`).replace(/^NEXT_PUBLIC_SIBLING_PATH_PREFIX=.*$/m, `NEXT_PUBLIC_SIBLING_PATH_PREFIX=${path}`).replace(/^NEXT_PUBLIC_BASE_PATH=.*$/m, `NEXT_PUBLIC_BASE_PATH=${path}`);
|
|
4829
|
+
writeFileSync7(envPath, content);
|
|
4620
4830
|
} catch (err) {
|
|
4621
4831
|
if (err.code !== "ENOENT") throw err;
|
|
4622
4832
|
}
|
|
@@ -4661,17 +4871,17 @@ async function configureSiblingGithub(github, config, coreConfig, session, coreI
|
|
|
4661
4871
|
}
|
|
4662
4872
|
function readExistingSiblingOrigins(filePath) {
|
|
4663
4873
|
try {
|
|
4664
|
-
return JSON.parse(
|
|
4874
|
+
return JSON.parse(readFileSync12(filePath, "utf8"));
|
|
4665
4875
|
} catch (err) {
|
|
4666
4876
|
if (err.code === "ENOENT") return {};
|
|
4667
4877
|
throw err;
|
|
4668
4878
|
}
|
|
4669
4879
|
}
|
|
4670
4880
|
function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x") {
|
|
4671
|
-
const cdnVarsPath =
|
|
4881
|
+
const cdnVarsPath = join18(cloneDir, "modules", "cloud", "aws", "cdn", "variables.tf");
|
|
4672
4882
|
let declaresSiblingOrigins = false;
|
|
4673
4883
|
try {
|
|
4674
|
-
declaresSiblingOrigins = /variable\s+"sibling_origins"/.test(
|
|
4884
|
+
declaresSiblingOrigins = /variable\s+"sibling_origins"/.test(readFileSync12(cdnVarsPath, "utf8"));
|
|
4675
4885
|
} catch {
|
|
4676
4886
|
declaresSiblingOrigins = false;
|
|
4677
4887
|
}
|
|
@@ -4681,10 +4891,10 @@ function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x")
|
|
|
4681
4891
|
);
|
|
4682
4892
|
}
|
|
4683
4893
|
if (!isRootPathPrefix(pathPrefix)) return;
|
|
4684
|
-
const cdnMainPath =
|
|
4894
|
+
const cdnMainPath = join18(cloneDir, "modules", "cloud", "aws", "cdn", "main.tf");
|
|
4685
4895
|
let supportsRoot = false;
|
|
4686
4896
|
try {
|
|
4687
|
-
supportsRoot = /root_sibling_registered/.test(
|
|
4897
|
+
supportsRoot = /root_sibling_registered/.test(readFileSync12(cdnMainPath, "utf8"));
|
|
4688
4898
|
} catch {
|
|
4689
4899
|
supportsRoot = false;
|
|
4690
4900
|
}
|
|
@@ -4714,8 +4924,8 @@ async function registerWithCore(git, github, config, coreConfig, pathPrefix, git
|
|
|
4714
4924
|
for (const env of config.environments) {
|
|
4715
4925
|
const bucketName = siteBucketName(config.project.name, env, siblingAccountId);
|
|
4716
4926
|
const domain = bucketRegionalDomain(bucketName, coreAwsRegion);
|
|
4717
|
-
const relativePath =
|
|
4718
|
-
const filePath =
|
|
4927
|
+
const relativePath = join18("infra", "environments", env, "siblings.auto.tfvars.json");
|
|
4928
|
+
const filePath = join18(cloneDir, relativePath);
|
|
4719
4929
|
const existing = readExistingSiblingOrigins(filePath);
|
|
4720
4930
|
const siblings = upsertSiblingOrigin(existing.sibling_origins ?? [], {
|
|
4721
4931
|
name,
|
|
@@ -4723,8 +4933,8 @@ async function registerWithCore(git, github, config, coreConfig, pathPrefix, git
|
|
|
4723
4933
|
...config.project.description ? { description: config.project.description } : {},
|
|
4724
4934
|
...config.project.routes.length > 0 ? { routes: config.project.routes } : {}
|
|
4725
4935
|
});
|
|
4726
|
-
|
|
4727
|
-
|
|
4936
|
+
mkdirSync7(dirname6(filePath), { recursive: true });
|
|
4937
|
+
writeFileSync7(filePath, serializeRegistry(siblings));
|
|
4728
4938
|
touchedFiles.push(relativePath);
|
|
4729
4939
|
}
|
|
4730
4940
|
await git.add(cloneDir, touchedFiles);
|
|
@@ -4795,8 +5005,8 @@ function defaultSiblingTemplateRoot() {
|
|
|
4795
5005
|
const start = dirname6(fileURLToPath4(import.meta.url));
|
|
4796
5006
|
let dir = start;
|
|
4797
5007
|
for (; ; ) {
|
|
4798
|
-
const candidate =
|
|
4799
|
-
if (
|
|
5008
|
+
const candidate = join18(dir, "_skeletons", "sibling-template");
|
|
5009
|
+
if (existsSync16(candidate)) return candidate;
|
|
4800
5010
|
const parent = dirname6(dir);
|
|
4801
5011
|
if (parent === dir) break;
|
|
4802
5012
|
dir = parent;
|
|
@@ -4820,7 +5030,7 @@ var initCommand = new Command12("init").description("Scaffold a new project from
|
|
|
4820
5030
|
let config;
|
|
4821
5031
|
let githubToken;
|
|
4822
5032
|
if (options.config) {
|
|
4823
|
-
const rawConfig = JSON.parse(
|
|
5033
|
+
const rawConfig = JSON.parse(readFileSync13(resolve10(options.config), "utf8"));
|
|
4824
5034
|
config = parseConfig(rawConfig);
|
|
4825
5035
|
const { account_id: accountId, region } = config.cloud.config;
|
|
4826
5036
|
session = resolveConfigFileSession(config, accountId, region, options.fresh === true);
|
|
@@ -5253,28 +5463,28 @@ async function promptForConfig(awsAccountId, awsRegion, awsProfile) {
|
|
|
5253
5463
|
import { Command as Command20 } from "commander";
|
|
5254
5464
|
|
|
5255
5465
|
// src/commands/plugin-create.ts
|
|
5256
|
-
import { existsSync as
|
|
5257
|
-
import { dirname as dirname8, join as
|
|
5466
|
+
import { existsSync as existsSync19, readFileSync as readFileSync15 } from "fs";
|
|
5467
|
+
import { dirname as dirname8, join as join21, resolve as resolve11 } from "path";
|
|
5258
5468
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
5259
5469
|
import chalk13 from "chalk";
|
|
5260
5470
|
import { Command as Command13 } from "commander";
|
|
5261
5471
|
|
|
5262
5472
|
// src/lib/plugin-locations.ts
|
|
5263
|
-
import { existsSync as
|
|
5264
|
-
import { join as
|
|
5473
|
+
import { existsSync as existsSync17, readdirSync as readdirSync10 } from "fs";
|
|
5474
|
+
import { join as join19 } from "path";
|
|
5265
5475
|
var FIRST_PARTY_PLUGINS_DIR = "_plugins";
|
|
5266
5476
|
var PLUGIN_MANIFEST_FILE = "biffo.plugin.json";
|
|
5267
5477
|
function pluginDir(name, channel) {
|
|
5268
5478
|
return channel === "first-party" ? `services/${FIRST_PARTY_PLUGINS_DIR}/${name}` : `services/${name}`;
|
|
5269
5479
|
}
|
|
5270
5480
|
function scanDir(absDir, relDir, channel) {
|
|
5271
|
-
if (!
|
|
5481
|
+
if (!existsSync17(absDir)) return [];
|
|
5272
5482
|
const found = [];
|
|
5273
|
-
for (const entry of
|
|
5483
|
+
for (const entry of readdirSync10(absDir, { withFileTypes: true })) {
|
|
5274
5484
|
if (!entry.isDirectory()) continue;
|
|
5275
5485
|
if (channel === "third-party" && entry.name === FIRST_PARTY_PLUGINS_DIR) continue;
|
|
5276
|
-
const manifestPath =
|
|
5277
|
-
if (!
|
|
5486
|
+
const manifestPath = join19(absDir, entry.name, PLUGIN_MANIFEST_FILE);
|
|
5487
|
+
if (!existsSync17(manifestPath)) continue;
|
|
5278
5488
|
found.push({
|
|
5279
5489
|
dirName: entry.name,
|
|
5280
5490
|
relDir: `${relDir}/${entry.name}`,
|
|
@@ -5285,11 +5495,11 @@ function scanDir(absDir, relDir, channel) {
|
|
|
5285
5495
|
return found;
|
|
5286
5496
|
}
|
|
5287
5497
|
function findInstalledPlugins(cwd) {
|
|
5288
|
-
const servicesDir =
|
|
5498
|
+
const servicesDir = join19(cwd, "services");
|
|
5289
5499
|
return [
|
|
5290
5500
|
...scanDir(servicesDir, "services", "third-party"),
|
|
5291
5501
|
...scanDir(
|
|
5292
|
-
|
|
5502
|
+
join19(servicesDir, FIRST_PARTY_PLUGINS_DIR),
|
|
5293
5503
|
`services/${FIRST_PARTY_PLUGINS_DIR}`,
|
|
5294
5504
|
"first-party"
|
|
5295
5505
|
)
|
|
@@ -5449,13 +5659,13 @@ function validateManifest(raw) {
|
|
|
5449
5659
|
// src/lib/plugin-scaffold.ts
|
|
5450
5660
|
import {
|
|
5451
5661
|
copyFileSync,
|
|
5452
|
-
existsSync as
|
|
5453
|
-
mkdirSync as
|
|
5454
|
-
readFileSync as
|
|
5455
|
-
readdirSync as
|
|
5456
|
-
writeFileSync as
|
|
5662
|
+
existsSync as existsSync18,
|
|
5663
|
+
mkdirSync as mkdirSync8,
|
|
5664
|
+
readFileSync as readFileSync14,
|
|
5665
|
+
readdirSync as readdirSync11,
|
|
5666
|
+
writeFileSync as writeFileSync8
|
|
5457
5667
|
} from "fs";
|
|
5458
|
-
import { dirname as dirname7, join as
|
|
5668
|
+
import { dirname as dirname7, join as join20 } from "path";
|
|
5459
5669
|
var STANDALONE_ONLY_ENTRIES = {
|
|
5460
5670
|
".github": "standalone-repo CI/release workflows \u2014 the host monorepo already runs lint/type/test/security over services/",
|
|
5461
5671
|
"registry-schema.json": "the plugin-registry publishing schema, used when submitting a *published* plugin to the registry repo, not by an in-tree plugin"
|
|
@@ -5508,10 +5718,10 @@ function applySubstitutions(text, names) {
|
|
|
5508
5718
|
}
|
|
5509
5719
|
var BINARY_EXTENSIONS = /\.(png|jpe?g|gif|ico|woff2?|ttf|zip|gz)$/i;
|
|
5510
5720
|
function scaffoldPlugin(skeletonRoot, destDir, names) {
|
|
5511
|
-
if (!
|
|
5721
|
+
if (!existsSync18(skeletonRoot)) {
|
|
5512
5722
|
throw new Error(`Plugin skeleton not found at ${skeletonRoot}`);
|
|
5513
5723
|
}
|
|
5514
|
-
if (!
|
|
5724
|
+
if (!existsSync18(join20(skeletonRoot, "terraform"))) {
|
|
5515
5725
|
throw new Error(
|
|
5516
5726
|
`Plugin skeleton at ${skeletonRoot} has no terraform/ directory. Refusing to scaffold a plugin that cannot receive events (issue #194) \u2014 the skeleton is broken.`
|
|
5517
5727
|
);
|
|
@@ -5519,8 +5729,8 @@ function scaffoldPlugin(skeletonRoot, destDir, names) {
|
|
|
5519
5729
|
const skipped = [];
|
|
5520
5730
|
const files = [];
|
|
5521
5731
|
const walk = (relDir) => {
|
|
5522
|
-
const absDir =
|
|
5523
|
-
for (const entry of
|
|
5732
|
+
const absDir = join20(skeletonRoot, relDir);
|
|
5733
|
+
for (const entry of readdirSync11(absDir, { withFileTypes: true }).sort(
|
|
5524
5734
|
(a, b) => a.name.localeCompare(b.name)
|
|
5525
5735
|
)) {
|
|
5526
5736
|
if (NEVER_COPY.has(entry.name)) continue;
|
|
@@ -5534,14 +5744,14 @@ function scaffoldPlugin(skeletonRoot, destDir, names) {
|
|
|
5534
5744
|
continue;
|
|
5535
5745
|
}
|
|
5536
5746
|
const destRel = applySubstitutions(relPath, names);
|
|
5537
|
-
const destPath =
|
|
5538
|
-
|
|
5747
|
+
const destPath = join20(destDir, destRel);
|
|
5748
|
+
mkdirSync8(dirname7(destPath), { recursive: true });
|
|
5539
5749
|
if (BINARY_EXTENSIONS.test(entry.name)) {
|
|
5540
|
-
copyFileSync(
|
|
5750
|
+
copyFileSync(join20(skeletonRoot, relPath), destPath);
|
|
5541
5751
|
} else {
|
|
5542
|
-
|
|
5752
|
+
writeFileSync8(
|
|
5543
5753
|
destPath,
|
|
5544
|
-
applySubstitutions(
|
|
5754
|
+
applySubstitutions(readFileSync14(join20(skeletonRoot, relPath), "utf8"), names)
|
|
5545
5755
|
);
|
|
5546
5756
|
}
|
|
5547
5757
|
files.push(destRel);
|
|
@@ -5558,8 +5768,8 @@ function scaffoldPlugin(skeletonRoot, destDir, names) {
|
|
|
5558
5768
|
function findSkeletonRoot(startDir, skeleton) {
|
|
5559
5769
|
let dir = startDir;
|
|
5560
5770
|
for (; ; ) {
|
|
5561
|
-
const candidate =
|
|
5562
|
-
if (
|
|
5771
|
+
const candidate = join20(dir, "_skeletons", skeleton);
|
|
5772
|
+
if (existsSync18(candidate)) return candidate;
|
|
5563
5773
|
const parent = dirname7(dir);
|
|
5564
5774
|
if (parent === dir) return null;
|
|
5565
5775
|
dir = parent;
|
|
@@ -5596,7 +5806,7 @@ var pluginCreateCommand = new Command13("create").description("Scaffold a new pl
|
|
|
5596
5806
|
);
|
|
5597
5807
|
async function runPluginCreate(name, options, deps) {
|
|
5598
5808
|
const names = deriveNames(name);
|
|
5599
|
-
const isInstance =
|
|
5809
|
+
const isInstance = existsSync19(join21(options.cwd, INSTANCE_CORE_FILE));
|
|
5600
5810
|
if (options.firstParty && isInstance) {
|
|
5601
5811
|
throw new Error(
|
|
5602
5812
|
`--first-party scaffolds into services/_plugins/, which is template-owned: \`biffo core upgrade\` three-way-merges it against the template on every upgrade, and the template has no '${names.slug}'. This checkout is a Biffo instance (${INSTANCE_CORE_FILE} is present), so your plugin belongs in the user-owned ${pluginDir(names.slug, "third-party")}/ \u2014 re-run without --first-party.`
|
|
@@ -5604,19 +5814,19 @@ async function runPluginCreate(name, options, deps) {
|
|
|
5604
5814
|
}
|
|
5605
5815
|
const channel = options.firstParty ? "first-party" : "third-party";
|
|
5606
5816
|
const relDir = pluginDir(names.slug, channel);
|
|
5607
|
-
const destDir =
|
|
5608
|
-
const servicesDir =
|
|
5609
|
-
if (!
|
|
5817
|
+
const destDir = join21(options.cwd, relDir);
|
|
5818
|
+
const servicesDir = join21(options.cwd, "services");
|
|
5819
|
+
if (!existsSync19(servicesDir)) {
|
|
5610
5820
|
throw new Error(
|
|
5611
5821
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
5612
5822
|
);
|
|
5613
5823
|
}
|
|
5614
|
-
if (
|
|
5824
|
+
if (existsSync19(destDir)) {
|
|
5615
5825
|
throw new Error(`${relDir}/ already exists. Choose a different name, or remove it first.`);
|
|
5616
5826
|
}
|
|
5617
5827
|
const here = dirname8(fileURLToPath5(import.meta.url));
|
|
5618
|
-
const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ??
|
|
5619
|
-
if (!
|
|
5828
|
+
const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join21(options.cwd, "_skeletons", "plugin-template");
|
|
5829
|
+
if (!existsSync19(skeletonRoot)) {
|
|
5620
5830
|
throw new Error(
|
|
5621
5831
|
`Could not find the plugin skeleton (_skeletons/plugin-template/). Pass --skeleton <path> to point at it explicitly.`
|
|
5622
5832
|
);
|
|
@@ -5631,8 +5841,8 @@ async function runPluginCreate(name, options, deps) {
|
|
|
5631
5841
|
for (const { entry, reason } of skipped) {
|
|
5632
5842
|
log.info(`Skipped ${entry} \u2014 ${reason}`);
|
|
5633
5843
|
}
|
|
5634
|
-
const manifestPath =
|
|
5635
|
-
const manifest = validateManifest(JSON.parse(
|
|
5844
|
+
const manifestPath = join21(destDir, "biffo.plugin.json");
|
|
5845
|
+
const manifest = validateManifest(JSON.parse(readFileSync15(manifestPath, "utf8")));
|
|
5636
5846
|
if (manifest.name !== names.slug) {
|
|
5637
5847
|
throw new Error(
|
|
5638
5848
|
`Scaffolded manifest declares name '${manifest.name}', expected '${names.slug}'. The skeleton's manifest name may have diverged from 'example-plugin'.`
|
|
@@ -5829,7 +6039,7 @@ import { Command as Command15 } from "commander";
|
|
|
5829
6039
|
|
|
5830
6040
|
// src/adapters/plugin-migrations/index.ts
|
|
5831
6041
|
import { execa as execa4 } from "execa";
|
|
5832
|
-
import { join as
|
|
6042
|
+
import { join as join22 } from "path";
|
|
5833
6043
|
var PluginMigrationsAdapter = class {
|
|
5834
6044
|
/**
|
|
5835
6045
|
* Generates migration file(s) for `pluginNames` (every discovered
|
|
@@ -5838,22 +6048,22 @@ var PluginMigrationsAdapter = class {
|
|
|
5838
6048
|
* or declared no tables.
|
|
5839
6049
|
*/
|
|
5840
6050
|
async generate(cwd, pluginNames) {
|
|
5841
|
-
const scriptPath =
|
|
6051
|
+
const scriptPath = join22(cwd, "services", "api", "scripts", "generate_plugin_migrations.py");
|
|
5842
6052
|
const args = [
|
|
5843
6053
|
"run",
|
|
5844
6054
|
"python",
|
|
5845
6055
|
scriptPath,
|
|
5846
6056
|
"--services-root",
|
|
5847
|
-
|
|
6057
|
+
join22(cwd, "services"),
|
|
5848
6058
|
"--versions-dir",
|
|
5849
|
-
|
|
6059
|
+
join22(cwd, "services", "api", "migrations", "versions")
|
|
5850
6060
|
];
|
|
5851
6061
|
for (const name of pluginNames ?? []) {
|
|
5852
6062
|
args.push("--plugin", name);
|
|
5853
6063
|
}
|
|
5854
6064
|
let result;
|
|
5855
6065
|
try {
|
|
5856
|
-
result = await execa4("uv", args, { cwd:
|
|
6066
|
+
result = await execa4("uv", args, { cwd: join22(cwd, "services", "api") });
|
|
5857
6067
|
} catch (err) {
|
|
5858
6068
|
const cause = err;
|
|
5859
6069
|
if (cause.code === "ENOENT") {
|
|
@@ -5869,171 +6079,6 @@ var PluginMigrationsAdapter = class {
|
|
|
5869
6079
|
}
|
|
5870
6080
|
};
|
|
5871
6081
|
|
|
5872
|
-
// src/lib/plugin-terraform-wiring.ts
|
|
5873
|
-
import { existsSync as existsSync19, mkdirSync as mkdirSync8, readFileSync as readFileSync15, readdirSync as readdirSync11, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
5874
|
-
import { join as join22 } from "path";
|
|
5875
|
-
var TEMPLATE_MODULE_DIR = "_template";
|
|
5876
|
-
var DEFAULT_PLUGIN_HANDLER = "src.lambda.main.handler";
|
|
5877
|
-
var GENERATED_TF_FILE = "plugins.generated.tf";
|
|
5878
|
-
var GENERATED_TFVARS_FILE = "plugins.auto.tfvars.json";
|
|
5879
|
-
function standardArguments(pluginName, handler) {
|
|
5880
|
-
return [
|
|
5881
|
-
["project_name", "var.project_name"],
|
|
5882
|
-
["environment", "local.environment"],
|
|
5883
|
-
["plugin_name", JSON.stringify(pluginName)],
|
|
5884
|
-
["handler", JSON.stringify(handler)],
|
|
5885
|
-
["event_bus_name", "module.events.event_bus_name"],
|
|
5886
|
-
["core_api_url", "module.api_gateway.api_endpoint"],
|
|
5887
|
-
["core_api_execution_arn", "module.api_gateway.execution_arn"],
|
|
5888
|
-
["tags", "local.tags"]
|
|
5889
|
-
];
|
|
5890
|
-
}
|
|
5891
|
-
function listPluginModules(cwd) {
|
|
5892
|
-
const dir = join22(cwd, "modules", "plugins");
|
|
5893
|
-
let entries;
|
|
5894
|
-
try {
|
|
5895
|
-
entries = readdirSync11(dir, { withFileTypes: true });
|
|
5896
|
-
} catch {
|
|
5897
|
-
return [];
|
|
5898
|
-
}
|
|
5899
|
-
return entries.filter((e) => e.isDirectory() && e.name !== TEMPLATE_MODULE_DIR && !e.name.startsWith(".")).map((e) => e.name).sort();
|
|
5900
|
-
}
|
|
5901
|
-
function listEnvironments(cwd) {
|
|
5902
|
-
const dir = join22(cwd, "infra", "environments");
|
|
5903
|
-
let entries;
|
|
5904
|
-
try {
|
|
5905
|
-
entries = readdirSync11(dir, { withFileTypes: true });
|
|
5906
|
-
} catch {
|
|
5907
|
-
return [];
|
|
5908
|
-
}
|
|
5909
|
-
return entries.filter((e) => {
|
|
5910
|
-
if (!e.isDirectory() || !existsSync19(join22(dir, e.name, "main.tf"))) return false;
|
|
5911
|
-
return declaredVariables(join22(dir, e.name)).has("enabled_plugins");
|
|
5912
|
-
}).map((e) => e.name).sort();
|
|
5913
|
-
}
|
|
5914
|
-
function listUnwirableEnvironments(cwd) {
|
|
5915
|
-
const dir = join22(cwd, "infra", "environments");
|
|
5916
|
-
let entries;
|
|
5917
|
-
try {
|
|
5918
|
-
entries = readdirSync11(dir, { withFileTypes: true });
|
|
5919
|
-
} catch {
|
|
5920
|
-
return [];
|
|
5921
|
-
}
|
|
5922
|
-
return entries.filter(
|
|
5923
|
-
(e) => e.isDirectory() && existsSync19(join22(dir, e.name, "main.tf")) && !declaredVariables(join22(dir, e.name)).has("enabled_plugins")
|
|
5924
|
-
).map((e) => e.name).sort();
|
|
5925
|
-
}
|
|
5926
|
-
function declaredVariables(moduleDir) {
|
|
5927
|
-
const names = /* @__PURE__ */ new Set();
|
|
5928
|
-
let entries;
|
|
5929
|
-
try {
|
|
5930
|
-
entries = readdirSync11(moduleDir, { withFileTypes: true });
|
|
5931
|
-
} catch {
|
|
5932
|
-
return names;
|
|
5933
|
-
}
|
|
5934
|
-
for (const entry of entries) {
|
|
5935
|
-
if (!entry.isFile() || !entry.name.endsWith(".tf")) continue;
|
|
5936
|
-
let contents;
|
|
5937
|
-
try {
|
|
5938
|
-
contents = readFileSync15(join22(moduleDir, entry.name), "utf8");
|
|
5939
|
-
} catch {
|
|
5940
|
-
continue;
|
|
5941
|
-
}
|
|
5942
|
-
for (const match of contents.matchAll(/^\s*variable\s+"([^"]+)"/gm)) {
|
|
5943
|
-
names.add(match[1]);
|
|
5944
|
-
}
|
|
5945
|
-
}
|
|
5946
|
-
return names;
|
|
5947
|
-
}
|
|
5948
|
-
function renderArguments(args, indent) {
|
|
5949
|
-
const width = Math.max(...args.map(([key]) => key.length));
|
|
5950
|
-
return args.map(([key, value]) => `${indent}${key.padEnd(width)} = ${value}`).join("\n");
|
|
5951
|
-
}
|
|
5952
|
-
function renderModuleBlock(pluginName, declared, handler) {
|
|
5953
|
-
const args = standardArguments(pluginName, handler).filter(([key]) => declared.has(key));
|
|
5954
|
-
const quoted = JSON.stringify(pluginName);
|
|
5955
|
-
return [
|
|
5956
|
-
`module "plugin_${pluginName}" {`,
|
|
5957
|
-
` source = "../../../modules/plugins/${pluginName}"`,
|
|
5958
|
-
` for_each = contains(var.enabled_plugins, ${quoted}) ? { ${quoted} = true } : {}`,
|
|
5959
|
-
"",
|
|
5960
|
-
renderArguments(args, " "),
|
|
5961
|
-
"}",
|
|
5962
|
-
"",
|
|
5963
|
-
`output "plugin_${pluginName}_function_arn" {`,
|
|
5964
|
-
` description = "Lambda ARN of the ${pluginName} plugin, or null when it is not in enabled_plugins."`,
|
|
5965
|
-
` value = try(module.plugin_${pluginName}[${quoted}].function_arn, null)`,
|
|
5966
|
-
"}"
|
|
5967
|
-
].join("\n");
|
|
5968
|
-
}
|
|
5969
|
-
var GENERATED_HEADER = `# ---------------------------------------------------------------------------
|
|
5970
|
-
# GENERATED FILE \u2014 DO NOT EDIT BY HAND.
|
|
5971
|
-
#
|
|
5972
|
-
# Written by \`biffo plugin install\` / \`biffo plugin uninstall\` (issue #201),
|
|
5973
|
-
# regenerated in full from the contents of modules/plugins/. Any manual edit is
|
|
5974
|
-
# lost on the next plugin install or uninstall.
|
|
5975
|
-
#
|
|
5976
|
-
# Terraform loads every *.tf file in this directory, so these blocks are as
|
|
5977
|
-
# live as anything in main.tf \u2014 they simply live in a CLI-owned file so the
|
|
5978
|
-
# CLI never has to rewrite your hand-authored main.tf.
|
|
5979
|
-
#
|
|
5980
|
-
# Terraform requires a module's \`source\` to be a static string literal, so it
|
|
5981
|
-
# cannot loop over var.enabled_plugins; hence one explicit block per plugin,
|
|
5982
|
-
# each gated on membership in enabled_plugins (supplied by the generated
|
|
5983
|
-
# ${GENERATED_TFVARS_FILE} alongside this file).
|
|
5984
|
-
#
|
|
5985
|
-
# Not generated here: the Core API's BIFFO_SERVICE_PRINCIPAL_ARN_ALLOWLIST
|
|
5986
|
-
# (ADR-0009). It lives in main.tf and is derived from var.enabled_plugins as a
|
|
5987
|
-
# static role-name glob \u2014 deriving it from a plugin module's role_arn output
|
|
5988
|
-
# would create the cycle core_api -> api_gateway -> plugin -> core_api.
|
|
5989
|
-
# ---------------------------------------------------------------------------
|
|
5990
|
-
`;
|
|
5991
|
-
function renderGeneratedTerraform(plugins) {
|
|
5992
|
-
const blocks = plugins.map(
|
|
5993
|
-
(p) => renderModuleBlock(p.name, p.declaredVariables, p.handler ?? DEFAULT_PLUGIN_HANDLER)
|
|
5994
|
-
);
|
|
5995
|
-
return `${GENERATED_HEADER}
|
|
5996
|
-
${blocks.join("\n\n")}
|
|
5997
|
-
`;
|
|
5998
|
-
}
|
|
5999
|
-
function renderGeneratedTfvars(pluginNames) {
|
|
6000
|
-
return `${JSON.stringify({ enabled_plugins: pluginNames }, null, 2)}
|
|
6001
|
-
`;
|
|
6002
|
-
}
|
|
6003
|
-
function syncPluginTerraform(cwd) {
|
|
6004
|
-
const plugins = listPluginModules(cwd);
|
|
6005
|
-
const environments = listEnvironments(cwd);
|
|
6006
|
-
const skippedEnvironments = listUnwirableEnvironments(cwd);
|
|
6007
|
-
const changedPaths = [];
|
|
6008
|
-
const rendered = plugins.map((name) => ({
|
|
6009
|
-
name,
|
|
6010
|
-
declaredVariables: declaredVariables(join22(cwd, "modules", "plugins", name))
|
|
6011
|
-
}));
|
|
6012
|
-
for (const env of environments) {
|
|
6013
|
-
const envDir = join22(cwd, "infra", "environments", env);
|
|
6014
|
-
const tfPath = join22(envDir, GENERATED_TF_FILE);
|
|
6015
|
-
const tfvarsPath = join22(envDir, GENERATED_TFVARS_FILE);
|
|
6016
|
-
const relBase = `infra/environments/${env}`;
|
|
6017
|
-
if (plugins.length === 0) {
|
|
6018
|
-
for (const [abs, rel] of [
|
|
6019
|
-
[tfPath, `${relBase}/${GENERATED_TF_FILE}`],
|
|
6020
|
-
[tfvarsPath, `${relBase}/${GENERATED_TFVARS_FILE}`]
|
|
6021
|
-
]) {
|
|
6022
|
-
if (existsSync19(abs)) {
|
|
6023
|
-
rmSync6(abs);
|
|
6024
|
-
changedPaths.push(rel);
|
|
6025
|
-
}
|
|
6026
|
-
}
|
|
6027
|
-
continue;
|
|
6028
|
-
}
|
|
6029
|
-
mkdirSync8(envDir, { recursive: true });
|
|
6030
|
-
writeFileSync8(tfPath, renderGeneratedTerraform(rendered));
|
|
6031
|
-
writeFileSync8(tfvarsPath, renderGeneratedTfvars(plugins));
|
|
6032
|
-
changedPaths.push(`${relBase}/${GENERATED_TF_FILE}`, `${relBase}/${GENERATED_TFVARS_FILE}`);
|
|
6033
|
-
}
|
|
6034
|
-
return { plugins, environments, skippedEnvironments, changedPaths };
|
|
6035
|
-
}
|
|
6036
|
-
|
|
6037
6082
|
// src/commands/plugin-install.ts
|
|
6038
6083
|
var TARGET_PATTERN = /^([a-z][a-z0-9-]*)@(\d+\.\d+)$/;
|
|
6039
6084
|
var pluginInstallCommand = new Command15("install").description(
|