@biffo/cli 0.73.2 → 0.74.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +467 -434
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -432,8 +432,8 @@ async function runCoreStatus(options) {
|
|
|
432
432
|
|
|
433
433
|
// src/commands/core-upgrade.ts
|
|
434
434
|
import { execSync as execSync2 } from "child_process";
|
|
435
|
-
import { existsSync as
|
|
436
|
-
import { join as
|
|
435
|
+
import { existsSync as existsSync11, rmSync as rmSync5 } from "fs";
|
|
436
|
+
import { join as join12, resolve as resolve3 } from "path";
|
|
437
437
|
import chalk4 from "chalk";
|
|
438
438
|
import { execa as execa3 } from "execa";
|
|
439
439
|
import { Command as Command3 } from "commander";
|
|
@@ -1585,23 +1585,107 @@ function applyMigrationCarry(instanceDir, plan) {
|
|
|
1585
1585
|
// src/lib/core-upgrade.ts
|
|
1586
1586
|
import {
|
|
1587
1587
|
chmodSync,
|
|
1588
|
-
existsSync as
|
|
1588
|
+
existsSync as existsSync7,
|
|
1589
1589
|
mkdirSync as mkdirSync2,
|
|
1590
1590
|
mkdtempSync as mkdtempSync2,
|
|
1591
|
-
readFileSync as
|
|
1591
|
+
readFileSync as readFileSync5,
|
|
1592
1592
|
rmSync as rmSync2,
|
|
1593
1593
|
statSync,
|
|
1594
1594
|
writeFileSync as writeFileSync3
|
|
1595
1595
|
} from "fs";
|
|
1596
1596
|
import { tmpdir as tmpdir2 } from "os";
|
|
1597
|
-
import { dirname as dirname4, join as
|
|
1597
|
+
import { dirname as dirname4, join as join7 } from "path";
|
|
1598
1598
|
import { execa as execa2 } from "execa";
|
|
1599
|
+
|
|
1600
|
+
// src/lib/core-ownership-guard.ts
|
|
1601
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
1602
|
+
import { join as join6 } from "path";
|
|
1603
|
+
import { z as z3 } from "zod";
|
|
1604
|
+
var DIVERGENCE_FILE = "biffo.divergence.json";
|
|
1605
|
+
var DivergenceEntrySchema = z3.object({
|
|
1606
|
+
prefix: z3.string().min(1),
|
|
1607
|
+
reason: z3.string().min(1),
|
|
1608
|
+
upstream: z3.string().min(1)
|
|
1609
|
+
});
|
|
1610
|
+
var DivergenceConfigSchema = z3.object({
|
|
1611
|
+
note: z3.string().optional(),
|
|
1612
|
+
warnOnly: z3.array(DivergenceEntrySchema).default([])
|
|
1613
|
+
});
|
|
1614
|
+
function readDivergenceConfig(repoRoot) {
|
|
1615
|
+
const path = join6(repoRoot, DIVERGENCE_FILE);
|
|
1616
|
+
if (!existsSync6(path)) return { warnOnly: [] };
|
|
1617
|
+
let raw;
|
|
1618
|
+
try {
|
|
1619
|
+
raw = JSON.parse(readFileSync4(path, "utf8"));
|
|
1620
|
+
} catch (err) {
|
|
1621
|
+
throw new Error(`${DIVERGENCE_FILE} is not valid JSON: ${err.message}`);
|
|
1622
|
+
}
|
|
1623
|
+
const parsed = DivergenceConfigSchema.safeParse(raw);
|
|
1624
|
+
if (!parsed.success) {
|
|
1625
|
+
const issues = parsed.error.issues.map((i) => ` ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
1626
|
+
throw new Error(`${DIVERGENCE_FILE} is invalid:
|
|
1627
|
+
${issues}`);
|
|
1628
|
+
}
|
|
1629
|
+
return parsed.data;
|
|
1630
|
+
}
|
|
1631
|
+
function parseDivergenceTrailer(commitMessage) {
|
|
1632
|
+
const body = commitMessage.split("\n").filter((line) => !line.startsWith("#")).join("\n");
|
|
1633
|
+
const match = /^Core-Divergence:[ \t]*(\S.*?)[ \t]*$/m.exec(body);
|
|
1634
|
+
return match?.[1] ?? null;
|
|
1635
|
+
}
|
|
1636
|
+
function resolveBranch(env, gitBranch) {
|
|
1637
|
+
return (env["GITHUB_HEAD_REF"] || env["GITHUB_REF_NAME"] || gitBranch).trim();
|
|
1638
|
+
}
|
|
1639
|
+
function parseNameStatus(stdout) {
|
|
1640
|
+
const changed = [];
|
|
1641
|
+
const deleted = [];
|
|
1642
|
+
for (const line of stdout.split("\n")) {
|
|
1643
|
+
const parts = line.split(" ").filter(Boolean);
|
|
1644
|
+
const status = parts[0];
|
|
1645
|
+
const path = parts[parts.length - 1];
|
|
1646
|
+
if (!status || !path || parts.length < 2) continue;
|
|
1647
|
+
changed.push(path);
|
|
1648
|
+
if (status.startsWith("D")) deleted.push(path);
|
|
1649
|
+
}
|
|
1650
|
+
return { changed, deleted };
|
|
1651
|
+
}
|
|
1652
|
+
function checkCoreOwnership({
|
|
1653
|
+
changedFiles,
|
|
1654
|
+
manifest,
|
|
1655
|
+
isInstance,
|
|
1656
|
+
branch = "",
|
|
1657
|
+
commitMessage = "",
|
|
1658
|
+
warnOnly = []
|
|
1659
|
+
}) {
|
|
1660
|
+
const empty = { blocked: [], warned: [], divergenceReason: null };
|
|
1661
|
+
if (!isInstance) return { skipped: "template", ...empty };
|
|
1662
|
+
if (branch.startsWith(UPGRADE_BRANCH_PREFIX)) return { skipped: "upgrade-branch", ...empty };
|
|
1663
|
+
const templateOwned = changedFiles.filter((f) => isTemplateOwned(f, manifest));
|
|
1664
|
+
const acknowledged = (path) => warnOnly.filter((entry) => path.startsWith(entry.prefix)).reduce(
|
|
1665
|
+
(best, entry) => !best || entry.prefix.length > best.prefix.length ? entry : best,
|
|
1666
|
+
void 0
|
|
1667
|
+
);
|
|
1668
|
+
const warned = [];
|
|
1669
|
+
const offending = [];
|
|
1670
|
+
for (const path of templateOwned) {
|
|
1671
|
+
const entry = acknowledged(path);
|
|
1672
|
+
if (entry) warned.push({ path, entry });
|
|
1673
|
+
else offending.push(path);
|
|
1674
|
+
}
|
|
1675
|
+
const divergenceReason = parseDivergenceTrailer(commitMessage);
|
|
1676
|
+
if (offending.length > 0 && divergenceReason !== null) {
|
|
1677
|
+
return { skipped: "divergence-trailer", blocked: [], warned, divergenceReason };
|
|
1678
|
+
}
|
|
1679
|
+
return { skipped: null, blocked: offending, warned, divergenceReason: null };
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
// src/lib/core-upgrade.ts
|
|
1599
1683
|
var gitMergeFile = async (base, ours, theirs) => {
|
|
1600
|
-
const dir = mkdtempSync2(
|
|
1684
|
+
const dir = mkdtempSync2(join7(tmpdir2(), "biffo-merge-"));
|
|
1601
1685
|
try {
|
|
1602
|
-
const b =
|
|
1603
|
-
const o =
|
|
1604
|
-
const t =
|
|
1686
|
+
const b = join7(dir, "base");
|
|
1687
|
+
const o = join7(dir, "ours");
|
|
1688
|
+
const t = join7(dir, "theirs");
|
|
1605
1689
|
writeFileSync3(b, base);
|
|
1606
1690
|
writeFileSync3(o, ours);
|
|
1607
1691
|
writeFileSync3(t, theirs);
|
|
@@ -1618,7 +1702,7 @@ var gitMergeFile = async (base, ours, theirs) => {
|
|
|
1618
1702
|
}
|
|
1619
1703
|
};
|
|
1620
1704
|
function read(root, rel) {
|
|
1621
|
-
return
|
|
1705
|
+
return readFileSync5(join7(root, rel), "utf8");
|
|
1622
1706
|
}
|
|
1623
1707
|
var EMPTY_SUMMARY = () => ({
|
|
1624
1708
|
unchanged: 0,
|
|
@@ -1628,6 +1712,7 @@ var EMPTY_SUMMARY = () => ({
|
|
|
1628
1712
|
conflict: 0,
|
|
1629
1713
|
added: 0,
|
|
1630
1714
|
"add-conflict": 0,
|
|
1715
|
+
restored: 0,
|
|
1631
1716
|
removed: 0,
|
|
1632
1717
|
"remove-conflict": 0
|
|
1633
1718
|
});
|
|
@@ -1636,18 +1721,32 @@ async function planCoreUpgrade(options) {
|
|
|
1636
1721
|
const base = new Set(listTemplateOwnedFiles(options.baseDir, options.manifest));
|
|
1637
1722
|
const ours = new Set(listTemplateOwnedFiles(options.oursDir, options.manifest));
|
|
1638
1723
|
const theirs = new Set(listTemplateOwnedFiles(options.theirsDir, options.manifest));
|
|
1724
|
+
const divergentPrefixes = readDivergenceConfig(options.oursDir).warnOnly.map((e) => e.prefix);
|
|
1725
|
+
const isDeclaredDivergent = (path) => divergentPrefixes.some((prefix) => path.startsWith(prefix));
|
|
1639
1726
|
const paths = [.../* @__PURE__ */ new Set([...base, ...ours, ...theirs])].sort();
|
|
1640
1727
|
const entries = [];
|
|
1728
|
+
const divergenceSkips = [];
|
|
1641
1729
|
for (const path of paths) {
|
|
1642
|
-
entries.push(
|
|
1730
|
+
entries.push(
|
|
1731
|
+
await classify(
|
|
1732
|
+
path,
|
|
1733
|
+
base,
|
|
1734
|
+
ours,
|
|
1735
|
+
theirs,
|
|
1736
|
+
options,
|
|
1737
|
+
mergeFile,
|
|
1738
|
+
isDeclaredDivergent,
|
|
1739
|
+
(p) => divergenceSkips.push(p)
|
|
1740
|
+
)
|
|
1741
|
+
);
|
|
1643
1742
|
}
|
|
1644
1743
|
const summary = EMPTY_SUMMARY();
|
|
1645
1744
|
for (const e of entries) summary[e.status]++;
|
|
1646
1745
|
const changes = entries.filter((e) => e.status !== "unchanged" && e.status !== "keep-ours");
|
|
1647
1746
|
const conflicts = entries.filter((e) => e.conflicted);
|
|
1648
|
-
return { entries, changes, conflicts, summary };
|
|
1747
|
+
return { entries, changes, conflicts, summary, divergenceSkips };
|
|
1649
1748
|
}
|
|
1650
|
-
async function classify(path, base, ours, theirs, opts, mergeFile) {
|
|
1749
|
+
async function classify(path, base, ours, theirs, opts, mergeFile, isDeclaredDivergent, noteDivergenceSkip) {
|
|
1651
1750
|
const inBase = base.has(path);
|
|
1652
1751
|
const inOurs = ours.has(path);
|
|
1653
1752
|
const inTheirs = theirs.has(path);
|
|
@@ -1671,8 +1770,11 @@ async function classify(path, base, ours, theirs, opts, mergeFile) {
|
|
|
1671
1770
|
const baseContent = read(opts.baseDir, path);
|
|
1672
1771
|
const theirsContent = read(opts.theirsDir, path);
|
|
1673
1772
|
if (!inOurs) {
|
|
1674
|
-
if (
|
|
1675
|
-
|
|
1773
|
+
if (isDeclaredDivergent(path)) {
|
|
1774
|
+
noteDivergenceSkip(path);
|
|
1775
|
+
return { path, status: "removed", conflicted: false };
|
|
1776
|
+
}
|
|
1777
|
+
return { path, status: "restored", conflicted: false, content: theirsContent };
|
|
1676
1778
|
}
|
|
1677
1779
|
const oursContent = read(opts.oursDir, path);
|
|
1678
1780
|
const oursChanged = oursContent !== baseContent;
|
|
@@ -1693,9 +1795,9 @@ function applyUpgradePlan(instanceDir, plan, theirsDir) {
|
|
|
1693
1795
|
const written = [];
|
|
1694
1796
|
const deleted = [];
|
|
1695
1797
|
for (const e of plan.entries) {
|
|
1696
|
-
const abs =
|
|
1798
|
+
const abs = join7(instanceDir, e.path);
|
|
1697
1799
|
if (e.status === "removed") {
|
|
1698
|
-
if (
|
|
1800
|
+
if (existsSync7(abs)) {
|
|
1699
1801
|
rmSync2(abs);
|
|
1700
1802
|
deleted.push(e.path);
|
|
1701
1803
|
}
|
|
@@ -1705,8 +1807,8 @@ function applyUpgradePlan(instanceDir, plan, theirsDir) {
|
|
|
1705
1807
|
mkdirSync2(dirname4(abs), { recursive: true });
|
|
1706
1808
|
writeFileSync3(abs, e.content);
|
|
1707
1809
|
if (theirsDir !== void 0) {
|
|
1708
|
-
const source =
|
|
1709
|
-
if (
|
|
1810
|
+
const source = join7(theirsDir, e.path);
|
|
1811
|
+
if (existsSync7(source) && (statSync(source).mode & 73) !== 0) {
|
|
1710
1812
|
chmodSync(abs, 493);
|
|
1711
1813
|
}
|
|
1712
1814
|
}
|
|
@@ -1731,7 +1833,7 @@ function upgradeBranchName(from, to) {
|
|
|
1731
1833
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1732
1834
|
import { mkdtempSync as mkdtempSync3, rmSync as rmSync3 } from "fs";
|
|
1733
1835
|
import { tmpdir as tmpdir3 } from "os";
|
|
1734
|
-
import { join as
|
|
1836
|
+
import { join as join8 } from "path";
|
|
1735
1837
|
function coreTag(version) {
|
|
1736
1838
|
parseCoreVersion(version);
|
|
1737
1839
|
return `core-v${version}`;
|
|
@@ -1770,8 +1872,8 @@ function materializeTemplateAtTag(repo, version, git = defaultGit) {
|
|
|
1770
1872
|
`No git tag ${tag} in ${repo}. The template tree at core version ${version} is unavailable, so it can't be used as a merge base. Ensure the tag exists (it is pushed by the Core Version Tag workflow on release), or pass an explicit --from-template / --to-template checkout.`
|
|
1771
1873
|
);
|
|
1772
1874
|
}
|
|
1773
|
-
const dir = mkdtempSync3(
|
|
1774
|
-
const tarball =
|
|
1875
|
+
const dir = mkdtempSync3(join8(tmpdir3(), `biffo-core-${version}-`));
|
|
1876
|
+
const tarball = join8(dir, ".tree.tar");
|
|
1775
1877
|
try {
|
|
1776
1878
|
git(["-C", repo, "archive", "--format=tar", "-o", tarball, tag]);
|
|
1777
1879
|
execFileSync2("tar", ["-x", "-f", tarball, "-C", dir]);
|
|
@@ -1784,8 +1886,8 @@ function materializeTemplateAtTag(repo, version, git = defaultGit) {
|
|
|
1784
1886
|
}
|
|
1785
1887
|
|
|
1786
1888
|
// src/lib/breaking-changes.ts
|
|
1787
|
-
import { existsSync as
|
|
1788
|
-
import { join as
|
|
1889
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
1890
|
+
import { join as join9 } from "path";
|
|
1789
1891
|
var UPGRADE_GUIDE_PATH = "docs/guides/core-upgrade.md";
|
|
1790
1892
|
var SECTION_HEADING = "## Breaking changes by version";
|
|
1791
1893
|
var ENTRY_HEADING = /^###\s+(\d+\.\d+\.\d+)\s*[—-]\s*(.+?)\s*$/;
|
|
@@ -1810,9 +1912,9 @@ function parseBreakingChanges(guide) {
|
|
|
1810
1912
|
return entries;
|
|
1811
1913
|
}
|
|
1812
1914
|
function readBreakingChanges(templateRoot) {
|
|
1813
|
-
const path =
|
|
1814
|
-
if (!
|
|
1815
|
-
return parseBreakingChanges(
|
|
1915
|
+
const path = join9(templateRoot, UPGRADE_GUIDE_PATH);
|
|
1916
|
+
if (!existsSync8(path)) return [];
|
|
1917
|
+
return parseBreakingChanges(readFileSync6(path, "utf8"));
|
|
1816
1918
|
}
|
|
1817
1919
|
function breakingChangesBetween(from, to, entries) {
|
|
1818
1920
|
parseCoreVersion(from);
|
|
@@ -1829,8 +1931,8 @@ var GLOBAL_DISPATCH_WORKFLOW_PATHS = [
|
|
|
1829
1931
|
];
|
|
1830
1932
|
|
|
1831
1933
|
// src/lib/plugin-terraform-wiring.ts
|
|
1832
|
-
import { existsSync as
|
|
1833
|
-
import { join as
|
|
1934
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync as readdirSync3, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
1935
|
+
import { join as join10 } from "path";
|
|
1834
1936
|
var TEMPLATE_MODULE_DIR = "_template";
|
|
1835
1937
|
var DEFAULT_PLUGIN_HANDLER = "src.lambda.main.handler";
|
|
1836
1938
|
var GENERATED_TF_FILE = "plugins.generated.tf";
|
|
@@ -1848,7 +1950,7 @@ function standardArguments(pluginName, handler) {
|
|
|
1848
1950
|
];
|
|
1849
1951
|
}
|
|
1850
1952
|
function listPluginModules(cwd) {
|
|
1851
|
-
const dir =
|
|
1953
|
+
const dir = join10(cwd, "modules", "plugins");
|
|
1852
1954
|
let entries;
|
|
1853
1955
|
try {
|
|
1854
1956
|
entries = readdirSync3(dir, { withFileTypes: true });
|
|
@@ -1860,7 +1962,7 @@ function listPluginModules(cwd) {
|
|
|
1860
1962
|
var FIRST_PARTY_TERRAFORM = (name) => `../../../services/_plugins/${name}/terraform`;
|
|
1861
1963
|
var THIRD_PARTY_TERRAFORM = (name) => `../../../modules/plugins/${name}`;
|
|
1862
1964
|
function isFirstPartyPlugin(cwd, name) {
|
|
1863
|
-
return
|
|
1965
|
+
return existsSync9(join10(cwd, "services", "_plugins", name, "terraform", "main.tf"));
|
|
1864
1966
|
}
|
|
1865
1967
|
function pluginModuleSource(cwd, name) {
|
|
1866
1968
|
return isFirstPartyPlugin(cwd, name) ? FIRST_PARTY_TERRAFORM(name) : THIRD_PARTY_TERRAFORM(name);
|
|
@@ -1871,7 +1973,7 @@ function listWireablePlugins(cwd) {
|
|
|
1871
1973
|
return [.../* @__PURE__ */ new Set([...copied, ...firstParty])].sort();
|
|
1872
1974
|
}
|
|
1873
1975
|
function firstPartyPluginNames(cwd) {
|
|
1874
|
-
const dir =
|
|
1976
|
+
const dir = join10(cwd, "services", "_plugins");
|
|
1875
1977
|
let entries;
|
|
1876
1978
|
try {
|
|
1877
1979
|
entries = readdirSync3(dir, { withFileTypes: true });
|
|
@@ -1885,7 +1987,7 @@ function staleFirstPartyCopies(cwd) {
|
|
|
1885
1987
|
return firstPartyPluginNames(cwd).filter((name) => copied.has(name));
|
|
1886
1988
|
}
|
|
1887
1989
|
function listEnvironments(cwd) {
|
|
1888
|
-
const dir =
|
|
1990
|
+
const dir = join10(cwd, "infra", "environments");
|
|
1889
1991
|
let entries;
|
|
1890
1992
|
try {
|
|
1891
1993
|
entries = readdirSync3(dir, { withFileTypes: true });
|
|
@@ -1893,12 +1995,12 @@ function listEnvironments(cwd) {
|
|
|
1893
1995
|
return [];
|
|
1894
1996
|
}
|
|
1895
1997
|
return entries.filter((e) => {
|
|
1896
|
-
if (!e.isDirectory() || !
|
|
1897
|
-
return declaredVariables(
|
|
1998
|
+
if (!e.isDirectory() || !existsSync9(join10(dir, e.name, "main.tf"))) return false;
|
|
1999
|
+
return declaredVariables(join10(dir, e.name)).has("enabled_plugins");
|
|
1898
2000
|
}).map((e) => e.name).sort();
|
|
1899
2001
|
}
|
|
1900
2002
|
function listUnwirableEnvironments(cwd) {
|
|
1901
|
-
const dir =
|
|
2003
|
+
const dir = join10(cwd, "infra", "environments");
|
|
1902
2004
|
let entries;
|
|
1903
2005
|
try {
|
|
1904
2006
|
entries = readdirSync3(dir, { withFileTypes: true });
|
|
@@ -1906,7 +2008,7 @@ function listUnwirableEnvironments(cwd) {
|
|
|
1906
2008
|
return [];
|
|
1907
2009
|
}
|
|
1908
2010
|
return entries.filter(
|
|
1909
|
-
(e) => e.isDirectory() &&
|
|
2011
|
+
(e) => e.isDirectory() && existsSync9(join10(dir, e.name, "main.tf")) && !declaredVariables(join10(dir, e.name)).has("enabled_plugins")
|
|
1910
2012
|
).map((e) => e.name).sort();
|
|
1911
2013
|
}
|
|
1912
2014
|
function declaredVariables(moduleDir) {
|
|
@@ -1921,7 +2023,7 @@ function declaredVariables(moduleDir) {
|
|
|
1921
2023
|
if (!entry.isFile() || !entry.name.endsWith(".tf")) continue;
|
|
1922
2024
|
let contents;
|
|
1923
2025
|
try {
|
|
1924
|
-
contents =
|
|
2026
|
+
contents = readFileSync7(join10(moduleDir, entry.name), "utf8");
|
|
1925
2027
|
} catch {
|
|
1926
2028
|
continue;
|
|
1927
2029
|
}
|
|
@@ -1998,7 +2100,7 @@ function syncPluginTerraform(cwd) {
|
|
|
1998
2100
|
const changedPaths = [];
|
|
1999
2101
|
const rendered = plugins.map((name) => {
|
|
2000
2102
|
const firstParty = isFirstPartyPlugin(cwd, name);
|
|
2001
|
-
const moduleDir = firstParty ?
|
|
2103
|
+
const moduleDir = firstParty ? join10(cwd, "services", "_plugins", name, "terraform") : join10(cwd, "modules", "plugins", name);
|
|
2002
2104
|
return {
|
|
2003
2105
|
name,
|
|
2004
2106
|
declaredVariables: declaredVariables(moduleDir),
|
|
@@ -2006,16 +2108,16 @@ function syncPluginTerraform(cwd) {
|
|
|
2006
2108
|
};
|
|
2007
2109
|
});
|
|
2008
2110
|
for (const env of environments) {
|
|
2009
|
-
const envDir =
|
|
2010
|
-
const tfPath =
|
|
2011
|
-
const tfvarsPath =
|
|
2111
|
+
const envDir = join10(cwd, "infra", "environments", env);
|
|
2112
|
+
const tfPath = join10(envDir, GENERATED_TF_FILE);
|
|
2113
|
+
const tfvarsPath = join10(envDir, GENERATED_TFVARS_FILE);
|
|
2012
2114
|
const relBase = `infra/environments/${env}`;
|
|
2013
2115
|
if (plugins.length === 0) {
|
|
2014
2116
|
for (const [abs, rel] of [
|
|
2015
2117
|
[tfPath, `${relBase}/${GENERATED_TF_FILE}`],
|
|
2016
2118
|
[tfvarsPath, `${relBase}/${GENERATED_TFVARS_FILE}`]
|
|
2017
2119
|
]) {
|
|
2018
|
-
if (
|
|
2120
|
+
if (existsSync9(abs)) {
|
|
2019
2121
|
rmSync4(abs);
|
|
2020
2122
|
changedPaths.push(rel);
|
|
2021
2123
|
}
|
|
@@ -2031,8 +2133,8 @@ function syncPluginTerraform(cwd) {
|
|
|
2031
2133
|
}
|
|
2032
2134
|
|
|
2033
2135
|
// src/lib/lockfile-refresh.ts
|
|
2034
|
-
import { existsSync as
|
|
2035
|
-
import { join as
|
|
2136
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2137
|
+
import { join as join11 } from "path";
|
|
2036
2138
|
var LOCKFILE_TRIGGERS = [
|
|
2037
2139
|
{
|
|
2038
2140
|
manifest: "package.json",
|
|
@@ -2055,7 +2157,7 @@ function lockfilesNeedingRefresh(changedPaths, instanceDir, triggers = LOCKFILE_
|
|
|
2055
2157
|
const locked = changedPaths.filter((p) => !isForeignManifest(p));
|
|
2056
2158
|
return triggers.filter((t) => {
|
|
2057
2159
|
const touched = locked.some((p) => p === t.manifest || p.endsWith(`/${t.manifest}`));
|
|
2058
|
-
return touched &&
|
|
2160
|
+
return touched && existsSync10(join11(instanceDir, t.lockfile));
|
|
2059
2161
|
});
|
|
2060
2162
|
}
|
|
2061
2163
|
async function refreshLockfiles(instanceDir, triggers, run) {
|
|
@@ -2242,6 +2344,13 @@ async function runCoreUpgradeResolved(options, deps, cleanups) {
|
|
|
2242
2344
|
`
|
|
2243
2345
|
${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")}.`
|
|
2244
2346
|
);
|
|
2347
|
+
if (plan.divergenceSkips && plan.divergenceSkips.length > 0) {
|
|
2348
|
+
console.log(
|
|
2349
|
+
chalk4.dim(
|
|
2350
|
+
` ${plan.divergenceSkips.length} deleted template-owned file(s) left absent \u2014 declared divergent in biffo.divergence.json.`
|
|
2351
|
+
)
|
|
2352
|
+
);
|
|
2353
|
+
}
|
|
2245
2354
|
if (!options.apply) {
|
|
2246
2355
|
if (plan.conflicts.length > 0) {
|
|
2247
2356
|
log.warn("Some core files changed on both sides and need manual resolution.");
|
|
@@ -2286,7 +2395,7 @@ async function applyAndOpenPr(options, deps, plan, migrations, fromVersion, toVe
|
|
|
2286
2395
|
const carried = applyMigrationCarry(options.cwd, migrations);
|
|
2287
2396
|
writeInstanceCoreVersion(options.cwd, toVersion);
|
|
2288
2397
|
const cleanedCoreVersion = coreVersionCleanup?.action === "delete";
|
|
2289
|
-
if (cleanedCoreVersion &&
|
|
2398
|
+
if (cleanedCoreVersion && existsSync11(coreVersionCleanup.path)) {
|
|
2290
2399
|
rmSync5(coreVersionCleanup.path);
|
|
2291
2400
|
log.info(
|
|
2292
2401
|
`Deleted orphaned ${CORE_VERSION_FILE} (inherited copy recording ${coreVersionCleanup.found}, superseded by biffo.core.json) \u2014 nothing reads it as an authority (#434).`
|
|
@@ -2355,8 +2464,15 @@ function buildPrBody(from, to, plan, migrations, base = GLOBAL_DISPATCH_REF, loc
|
|
|
2355
2464
|
`- merged: ${plan.summary.merged}`,
|
|
2356
2465
|
`- take-theirs: ${plan.summary["take-theirs"]}`,
|
|
2357
2466
|
`- added: ${plan.summary.added}`,
|
|
2467
|
+
`- restored: ${plan.summary.restored}`,
|
|
2358
2468
|
`- removed: ${plan.summary.removed}`
|
|
2359
2469
|
);
|
|
2470
|
+
if (plan.divergenceSkips && plan.divergenceSkips.length > 0) {
|
|
2471
|
+
lines.push(
|
|
2472
|
+
"",
|
|
2473
|
+
`> ${plan.divergenceSkips.length} template-owned file(s) the instance deleted were left absent because \`biffo.divergence.json\` declares the path an intentional divergence (not restored).`
|
|
2474
|
+
);
|
|
2475
|
+
}
|
|
2360
2476
|
if (migrations.entries.length > 0) {
|
|
2361
2477
|
lines.push(
|
|
2362
2478
|
"",
|
|
@@ -2433,6 +2549,7 @@ var STATUS_COLOR = {
|
|
|
2433
2549
|
merged: chalk4.yellow,
|
|
2434
2550
|
"take-theirs": chalk4.green,
|
|
2435
2551
|
added: chalk4.green,
|
|
2552
|
+
restored: chalk4.green,
|
|
2436
2553
|
removed: chalk4.red,
|
|
2437
2554
|
"keep-ours": chalk4.dim
|
|
2438
2555
|
};
|
|
@@ -2525,8 +2642,8 @@ function printBreakingChanges(breaking, applying) {
|
|
|
2525
2642
|
}
|
|
2526
2643
|
function versionOfCheckout(dir, explicit) {
|
|
2527
2644
|
if (explicit) return explicit;
|
|
2528
|
-
const file =
|
|
2529
|
-
if (
|
|
2645
|
+
const file = join12(dir, CORE_VERSION_FILE);
|
|
2646
|
+
if (existsSync11(file)) return readCoreVersionFile(file);
|
|
2530
2647
|
throw new Error(
|
|
2531
2648
|
`Cannot determine the core version of ${dir}: it has no ${CORE_VERSION_FILE}, and a checkout supplied explicitly is not resolved from a tag. Pass --to to state which version this tree is.`
|
|
2532
2649
|
);
|
|
@@ -2534,8 +2651,8 @@ function versionOfCheckout(dir, explicit) {
|
|
|
2534
2651
|
function latestCoreVersion(repo) {
|
|
2535
2652
|
const fromTags = latestCoreVersionFromTags(repo);
|
|
2536
2653
|
if (fromTags) return fromTags;
|
|
2537
|
-
const file =
|
|
2538
|
-
if (
|
|
2654
|
+
const file = join12(repo, CORE_VERSION_FILE);
|
|
2655
|
+
if (existsSync11(file)) return readCoreVersionFile(file);
|
|
2539
2656
|
throw new Error(
|
|
2540
2657
|
`Cannot determine the template's core version: ${repo} has no core-v* tags and no ${CORE_VERSION_FILE}. Fetch tags (\`git fetch --tags\`) or pass --to explicitly.`
|
|
2541
2658
|
);
|
|
@@ -2553,7 +2670,7 @@ coreCommand.addCommand(coreUpgradeCommand);
|
|
|
2553
2670
|
import { Command as Command8 } from "commander";
|
|
2554
2671
|
|
|
2555
2672
|
// src/commands/data-apply.ts
|
|
2556
|
-
import { existsSync as
|
|
2673
|
+
import { existsSync as existsSync13, readFileSync as readFileSync9 } from "fs";
|
|
2557
2674
|
import { resolve as resolve4 } from "path";
|
|
2558
2675
|
import chalk5 from "chalk";
|
|
2559
2676
|
import { Command as Command5 } from "commander";
|
|
@@ -2913,62 +3030,62 @@ var AwsAdapter = class {
|
|
|
2913
3030
|
};
|
|
2914
3031
|
|
|
2915
3032
|
// src/config/schema.ts
|
|
2916
|
-
import { z as
|
|
2917
|
-
var AwsConfigSchema =
|
|
2918
|
-
account_id:
|
|
2919
|
-
region:
|
|
2920
|
-
profile:
|
|
2921
|
-
oidc_role_arn:
|
|
2922
|
-
tf_state_bucket:
|
|
3033
|
+
import { z as z4 } from "zod";
|
|
3034
|
+
var AwsConfigSchema = z4.object({
|
|
3035
|
+
account_id: z4.string().regex(/^\d{12}$/, "AWS account ID must be 12 digits").describe("12-digit AWS account ID"),
|
|
3036
|
+
region: z4.string().default("us-east-1"),
|
|
3037
|
+
profile: z4.string().optional(),
|
|
3038
|
+
oidc_role_arn: z4.string().regex(/^arn:aws:iam::\d{12}:role\/.+/, "Must be a valid IAM role ARN").optional(),
|
|
3039
|
+
tf_state_bucket: z4.string().optional()
|
|
2923
3040
|
});
|
|
2924
|
-
var GitHubConfigSchema =
|
|
2925
|
-
org:
|
|
2926
|
-
repo:
|
|
3041
|
+
var GitHubConfigSchema = z4.object({
|
|
3042
|
+
org: z4.string().min(1).describe("GitHub organisation or username"),
|
|
3043
|
+
repo: z4.string().min(1).describe("Repository name (will be created)")
|
|
2927
3044
|
});
|
|
2928
|
-
var SourceControlConfigSchema =
|
|
2929
|
-
|
|
3045
|
+
var SourceControlConfigSchema = z4.discriminatedUnion("provider", [
|
|
3046
|
+
z4.object({ provider: z4.literal("github"), config: GitHubConfigSchema })
|
|
2930
3047
|
]);
|
|
2931
|
-
var CloudConfigSchema =
|
|
2932
|
-
|
|
3048
|
+
var CloudConfigSchema = z4.discriminatedUnion("provider", [
|
|
3049
|
+
z4.object({ provider: z4.literal("aws"), config: AwsConfigSchema })
|
|
2933
3050
|
]);
|
|
2934
|
-
var ModulesSchema =
|
|
2935
|
-
auth:
|
|
2936
|
-
events:
|
|
2937
|
-
storage:
|
|
2938
|
-
database:
|
|
2939
|
-
compute:
|
|
2940
|
-
cdn:
|
|
3051
|
+
var ModulesSchema = z4.object({
|
|
3052
|
+
auth: z4.enum(["cognito"]).default("cognito"),
|
|
3053
|
+
events: z4.enum(["eventbridge"]).default("eventbridge"),
|
|
3054
|
+
storage: z4.enum(["s3"]).default("s3"),
|
|
3055
|
+
database: z4.enum(["postgresql"]).default("postgresql"),
|
|
3056
|
+
compute: z4.enum(["lambda"]).default("lambda"),
|
|
3057
|
+
cdn: z4.enum(["cloudfront"]).default("cloudfront")
|
|
2941
3058
|
});
|
|
2942
|
-
var DnsSchema =
|
|
2943
|
-
mode:
|
|
2944
|
-
domain:
|
|
3059
|
+
var DnsSchema = z4.object({
|
|
3060
|
+
mode: z4.enum(["managed-route53", "external", "none"]).default("managed-route53"),
|
|
3061
|
+
domain: z4.string().min(1).optional()
|
|
2945
3062
|
});
|
|
2946
|
-
var BiffoConfigSchema =
|
|
2947
|
-
$schema:
|
|
2948
|
-
project:
|
|
2949
|
-
name:
|
|
2950
|
-
description:
|
|
3063
|
+
var BiffoConfigSchema = z4.object({
|
|
3064
|
+
$schema: z4.string().optional(),
|
|
3065
|
+
project: z4.object({
|
|
3066
|
+
name: z4.string().min(1).regex(/^[a-z0-9-]+$/, "Must be lowercase kebab-case"),
|
|
3067
|
+
description: z4.string().default(""),
|
|
2951
3068
|
// Backward compatibility for existing configs. New configs should use dns.domain.
|
|
2952
|
-
domain:
|
|
3069
|
+
domain: z4.string().min(1).optional().describe("Primary domain, e.g. myapp.com")
|
|
2953
3070
|
}),
|
|
2954
3071
|
dns: DnsSchema.optional(),
|
|
2955
3072
|
source_control: SourceControlConfigSchema,
|
|
2956
3073
|
cloud: CloudConfigSchema,
|
|
2957
|
-
environments:
|
|
2958
|
-
admin:
|
|
2959
|
-
email:
|
|
2960
|
-
username:
|
|
3074
|
+
environments: z4.array(z4.enum(["dev", "staging", "prod"])).min(1).default(["dev"]),
|
|
3075
|
+
admin: z4.object({
|
|
3076
|
+
email: z4.string().email(),
|
|
3077
|
+
username: z4.string().min(1)
|
|
2961
3078
|
}),
|
|
2962
|
-
database:
|
|
2963
|
-
schema_path:
|
|
2964
|
-
migrations_path:
|
|
3079
|
+
database: z4.object({
|
|
3080
|
+
schema_path: z4.string().nullable().default(null),
|
|
3081
|
+
migrations_path: z4.string().default("services/api/migrations")
|
|
2965
3082
|
}).default({}),
|
|
2966
3083
|
modules: ModulesSchema.default({})
|
|
2967
3084
|
}).superRefine((config, ctx) => {
|
|
2968
3085
|
const dns = resolveDnsConfig(config);
|
|
2969
3086
|
if (dns.mode !== "none" && !dns.domain) {
|
|
2970
3087
|
ctx.addIssue({
|
|
2971
|
-
code:
|
|
3088
|
+
code: z4.ZodIssueCode.custom,
|
|
2972
3089
|
path: ["dns", "domain"],
|
|
2973
3090
|
message: 'DNS domain is required unless dns.mode is "none"'
|
|
2974
3091
|
});
|
|
@@ -3004,16 +3121,16 @@ function isTemplatePlaceholderConfig(raw) {
|
|
|
3004
3121
|
|
|
3005
3122
|
// src/lib/session.ts
|
|
3006
3123
|
import {
|
|
3007
|
-
existsSync as
|
|
3124
|
+
existsSync as existsSync12,
|
|
3008
3125
|
mkdirSync as mkdirSync4,
|
|
3009
3126
|
readdirSync as readdirSync4,
|
|
3010
|
-
readFileSync as
|
|
3127
|
+
readFileSync as readFileSync8,
|
|
3011
3128
|
rmSync as rmSync6,
|
|
3012
3129
|
statSync as statSync2,
|
|
3013
3130
|
writeFileSync as writeFileSync5
|
|
3014
3131
|
} from "fs";
|
|
3015
3132
|
import { homedir } from "os";
|
|
3016
|
-
import { join as
|
|
3133
|
+
import { join as join13 } from "path";
|
|
3017
3134
|
var LEGACY_STEP_ALIASES = {
|
|
3018
3135
|
github_config: ["github_branches", "github_instance_files", "github_settings"]
|
|
3019
3136
|
};
|
|
@@ -3022,39 +3139,39 @@ function hasCompleted(session, step) {
|
|
|
3022
3139
|
return session.completedSteps.some((done) => LEGACY_STEP_ALIASES[done]?.includes(step) ?? false);
|
|
3023
3140
|
}
|
|
3024
3141
|
function sessionsDir() {
|
|
3025
|
-
return process.env["BIFFO_SESSIONS_DIR"] ??
|
|
3142
|
+
return process.env["BIFFO_SESSIONS_DIR"] ?? join13(homedir(), ".biffo", "sessions");
|
|
3026
3143
|
}
|
|
3027
3144
|
function sessionPath(projectName) {
|
|
3028
|
-
return
|
|
3145
|
+
return join13(sessionsDir(), `${projectName}.json`);
|
|
3029
3146
|
}
|
|
3030
3147
|
function loadSession(projectName) {
|
|
3031
3148
|
const path = sessionPath(projectName);
|
|
3032
|
-
if (!
|
|
3149
|
+
if (!existsSync12(path)) return null;
|
|
3033
3150
|
try {
|
|
3034
|
-
return JSON.parse(
|
|
3151
|
+
return JSON.parse(readFileSync8(path, "utf8"));
|
|
3035
3152
|
} catch {
|
|
3036
3153
|
return null;
|
|
3037
3154
|
}
|
|
3038
3155
|
}
|
|
3039
3156
|
function findLatestSession() {
|
|
3040
3157
|
const dir = sessionsDir();
|
|
3041
|
-
if (!
|
|
3158
|
+
if (!existsSync12(dir)) return null;
|
|
3042
3159
|
const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
|
|
3043
3160
|
if (files.length === 0) return null;
|
|
3044
3161
|
const sorted = files.map((f) => {
|
|
3045
|
-
const fullPath =
|
|
3046
|
-
const mtime =
|
|
3162
|
+
const fullPath = join13(dir, f);
|
|
3163
|
+
const mtime = existsSync12(fullPath) ? statSync2(fullPath).mtimeMs : -1;
|
|
3047
3164
|
return { f, mtime };
|
|
3048
3165
|
}).sort((a, b) => b.mtime - a.mtime);
|
|
3049
3166
|
try {
|
|
3050
|
-
return JSON.parse(
|
|
3167
|
+
return JSON.parse(readFileSync8(join13(dir, sorted[0].f), "utf8"));
|
|
3051
3168
|
} catch {
|
|
3052
3169
|
return null;
|
|
3053
3170
|
}
|
|
3054
3171
|
}
|
|
3055
3172
|
function saveSession(session) {
|
|
3056
3173
|
const dir = sessionsDir();
|
|
3057
|
-
if (!
|
|
3174
|
+
if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
|
|
3058
3175
|
const name = session.config.project?.name ?? "unknown";
|
|
3059
3176
|
const prior = loadSession(name);
|
|
3060
3177
|
if (prior) {
|
|
@@ -3076,36 +3193,36 @@ function markStepComplete(session, step) {
|
|
|
3076
3193
|
}
|
|
3077
3194
|
function deleteSession(projectName) {
|
|
3078
3195
|
const path = sessionPath(projectName);
|
|
3079
|
-
if (
|
|
3196
|
+
if (existsSync12(path)) rmSync6(path);
|
|
3080
3197
|
}
|
|
3081
3198
|
function projectsDir() {
|
|
3082
|
-
return process.env["BIFFO_PROJECTS_DIR"] ??
|
|
3199
|
+
return process.env["BIFFO_PROJECTS_DIR"] ?? join13(homedir(), ".biffo", "projects");
|
|
3083
3200
|
}
|
|
3084
3201
|
function saveProjectConfig(config) {
|
|
3085
3202
|
const dir = projectsDir();
|
|
3086
|
-
if (!
|
|
3087
|
-
writeFileSync5(
|
|
3203
|
+
if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
|
|
3204
|
+
writeFileSync5(join13(dir, `${config.project.name}.json`), JSON.stringify(config, null, 2));
|
|
3088
3205
|
}
|
|
3089
3206
|
function loadProjectConfig(name) {
|
|
3090
|
-
const path =
|
|
3091
|
-
if (!
|
|
3207
|
+
const path = join13(projectsDir(), `${name}.json`);
|
|
3208
|
+
if (!existsSync12(path)) return null;
|
|
3092
3209
|
try {
|
|
3093
|
-
const result = BiffoConfigSchema.safeParse(JSON.parse(
|
|
3210
|
+
const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync8(path, "utf8")));
|
|
3094
3211
|
return result.success ? result.data : null;
|
|
3095
3212
|
} catch {
|
|
3096
3213
|
return null;
|
|
3097
3214
|
}
|
|
3098
3215
|
}
|
|
3099
3216
|
function deleteProjectConfig(name) {
|
|
3100
|
-
const path =
|
|
3101
|
-
if (
|
|
3217
|
+
const path = join13(projectsDir(), `${name}.json`);
|
|
3218
|
+
if (existsSync12(path)) rmSync6(path);
|
|
3102
3219
|
}
|
|
3103
3220
|
function listProjectConfigs() {
|
|
3104
3221
|
const dir = projectsDir();
|
|
3105
|
-
if (!
|
|
3222
|
+
if (!existsSync12(dir)) return [];
|
|
3106
3223
|
return readdirSync4(dir).filter((f) => f.endsWith(".json")).flatMap((f) => {
|
|
3107
3224
|
try {
|
|
3108
|
-
const result = BiffoConfigSchema.safeParse(JSON.parse(
|
|
3225
|
+
const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync8(join13(dir, f), "utf8")));
|
|
3109
3226
|
return result.success ? [result.data] : [];
|
|
3110
3227
|
} catch {
|
|
3111
3228
|
return [];
|
|
@@ -3174,7 +3291,7 @@ async function runDataApply(name, environment, config, aws) {
|
|
|
3174
3291
|
}
|
|
3175
3292
|
async function resolveConfig(options) {
|
|
3176
3293
|
if (options.config) {
|
|
3177
|
-
const raw = JSON.parse(
|
|
3294
|
+
const raw = JSON.parse(readFileSync9(resolve4(options.config), "utf8"));
|
|
3178
3295
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
3179
3296
|
if (!result.success) {
|
|
3180
3297
|
log.error(`Invalid config at ${options.config}:`);
|
|
@@ -3194,8 +3311,8 @@ async function resolveConfig(options) {
|
|
|
3194
3311
|
return cfg;
|
|
3195
3312
|
}
|
|
3196
3313
|
const localConfigPath = resolve4(process.cwd(), "biffo.config.json");
|
|
3197
|
-
if (
|
|
3198
|
-
const raw = JSON.parse(
|
|
3314
|
+
if (existsSync13(localConfigPath)) {
|
|
3315
|
+
const raw = JSON.parse(readFileSync9(localConfigPath, "utf8"));
|
|
3199
3316
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
3200
3317
|
if (result.success) return result.data;
|
|
3201
3318
|
if (isTemplatePlaceholderConfig(raw)) {
|
|
@@ -3241,8 +3358,8 @@ async function resolveConfig(options) {
|
|
|
3241
3358
|
|
|
3242
3359
|
// src/commands/data-import.ts
|
|
3243
3360
|
import { execSync as execSync3 } from "child_process";
|
|
3244
|
-
import { cpSync, existsSync as
|
|
3245
|
-
import { join as
|
|
3361
|
+
import { cpSync, existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
|
|
3362
|
+
import { join as join14, resolve as resolve5 } from "path";
|
|
3246
3363
|
import chalk6 from "chalk";
|
|
3247
3364
|
import { Command as Command6 } from "commander";
|
|
3248
3365
|
import inquirer2 from "inquirer";
|
|
@@ -3282,23 +3399,23 @@ async function runDataImport(name, options, deps) {
|
|
|
3282
3399
|
`Invalid import name '${name}'. Use lowercase letters, numbers, and hyphens, starting with a letter.`
|
|
3283
3400
|
);
|
|
3284
3401
|
}
|
|
3285
|
-
const servicesDir =
|
|
3286
|
-
if (!
|
|
3402
|
+
const servicesDir = join14(options.cwd, "services");
|
|
3403
|
+
if (!existsSync14(servicesDir)) {
|
|
3287
3404
|
throw new Error(
|
|
3288
3405
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
3289
3406
|
);
|
|
3290
3407
|
}
|
|
3291
|
-
const targetDir =
|
|
3292
|
-
if (
|
|
3408
|
+
const targetDir = join14(options.cwd, "db", "imports", name);
|
|
3409
|
+
if (existsSync14(targetDir)) {
|
|
3293
3410
|
throw new Error(
|
|
3294
3411
|
`DDL import '${name}' is already present at db/imports/${name}/. Remove it first to re-import.`
|
|
3295
3412
|
);
|
|
3296
3413
|
}
|
|
3297
|
-
const isLocalDir =
|
|
3414
|
+
const isLocalDir = existsSync14(options.source) && statSync3(options.source).isDirectory();
|
|
3298
3415
|
let sourceDir;
|
|
3299
3416
|
let cleanupClone = null;
|
|
3300
3417
|
if (isLocalDir) {
|
|
3301
|
-
sourceDir = options.path ?
|
|
3418
|
+
sourceDir = options.path ? join14(options.source, options.path) : options.source;
|
|
3302
3419
|
} else {
|
|
3303
3420
|
const token = options.token ?? await resolveDdlImportToken();
|
|
3304
3421
|
log.info(`Cloning ${options.source}...`);
|
|
@@ -3306,10 +3423,10 @@ async function runDataImport(name, options, deps) {
|
|
|
3306
3423
|
cleanupClone = () => {
|
|
3307
3424
|
deps.git.cleanup(tmpDir);
|
|
3308
3425
|
};
|
|
3309
|
-
sourceDir = options.path ?
|
|
3426
|
+
sourceDir = options.path ? join14(tmpDir, options.path) : tmpDir;
|
|
3310
3427
|
}
|
|
3311
3428
|
try {
|
|
3312
|
-
if (!
|
|
3429
|
+
if (!existsSync14(sourceDir)) {
|
|
3313
3430
|
throw new Error(`Source directory does not exist: ${sourceDir}`);
|
|
3314
3431
|
}
|
|
3315
3432
|
const sqlFiles = readdirSync5(sourceDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".sql")).map((entry) => entry.name).sort();
|
|
@@ -3334,7 +3451,7 @@ async function runDataImport(name, options, deps) {
|
|
|
3334
3451
|
}
|
|
3335
3452
|
mkdirSync5(targetDir, { recursive: true });
|
|
3336
3453
|
for (const file of sqlFiles) {
|
|
3337
|
-
cpSync(
|
|
3454
|
+
cpSync(join14(sourceDir, file), join14(targetDir, file));
|
|
3338
3455
|
}
|
|
3339
3456
|
log.success(`Imported ${String(sqlFiles.length)} .sql file(s) to db/imports/${name}/`);
|
|
3340
3457
|
const commitMessage = `feat(data): import ${name} (${String(sqlFiles.length)} SQL file(s))`;
|
|
@@ -3386,8 +3503,8 @@ function printDryRun(name, sqlFiles) {
|
|
|
3386
3503
|
}
|
|
3387
3504
|
|
|
3388
3505
|
// src/commands/data-list.ts
|
|
3389
|
-
import { existsSync as
|
|
3390
|
-
import { join as
|
|
3506
|
+
import { existsSync as existsSync15, readdirSync as readdirSync6 } from "fs";
|
|
3507
|
+
import { join as join15, resolve as resolve6 } from "path";
|
|
3391
3508
|
import chalk7 from "chalk";
|
|
3392
3509
|
import { Command as Command7 } from "commander";
|
|
3393
3510
|
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) => {
|
|
@@ -3400,15 +3517,15 @@ var dataListCommand = new Command7("list").description("List DDL imports vendore
|
|
|
3400
3517
|
}
|
|
3401
3518
|
});
|
|
3402
3519
|
async function runDataList(options) {
|
|
3403
|
-
const importsDir =
|
|
3404
|
-
if (!
|
|
3520
|
+
const importsDir = join15(options.cwd, "db", "imports");
|
|
3521
|
+
if (!existsSync15(importsDir)) {
|
|
3405
3522
|
console.log(chalk7.dim("\n No DDL imports in this checkout.\n"));
|
|
3406
3523
|
return;
|
|
3407
3524
|
}
|
|
3408
3525
|
const candidates = readdirSync6(importsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
3409
3526
|
const imports = [];
|
|
3410
3527
|
for (const name of candidates) {
|
|
3411
|
-
const fileCount = readdirSync6(
|
|
3528
|
+
const fileCount = readdirSync6(join15(importsDir, name)).filter((f) => f.endsWith(".sql")).length;
|
|
3412
3529
|
if (fileCount > 0) imports.push({ name, fileCount });
|
|
3413
3530
|
}
|
|
3414
3531
|
if (imports.length === 0) {
|
|
@@ -3438,7 +3555,7 @@ dataCommand.addCommand(dataListCommand);
|
|
|
3438
3555
|
|
|
3439
3556
|
// src/commands/deploy.ts
|
|
3440
3557
|
import { execSync as execSync4 } from "child_process";
|
|
3441
|
-
import { existsSync as
|
|
3558
|
+
import { existsSync as existsSync16, readFileSync as readFileSync10 } from "fs";
|
|
3442
3559
|
import { resolve as resolve7 } from "path";
|
|
3443
3560
|
import chalk8 from "chalk";
|
|
3444
3561
|
import { Command as Command9 } from "commander";
|
|
@@ -3792,7 +3909,7 @@ var deployCommand = new Command9("deploy").description("Deploy infrastructure an
|
|
|
3792
3909
|
);
|
|
3793
3910
|
async function resolveConfig2(options) {
|
|
3794
3911
|
if (options.config) {
|
|
3795
|
-
const raw = JSON.parse(
|
|
3912
|
+
const raw = JSON.parse(readFileSync10(resolve7(options.config), "utf8"));
|
|
3796
3913
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
3797
3914
|
if (!result.success) {
|
|
3798
3915
|
log.error(`Invalid config at ${options.config}:`);
|
|
@@ -3812,8 +3929,8 @@ async function resolveConfig2(options) {
|
|
|
3812
3929
|
return cfg;
|
|
3813
3930
|
}
|
|
3814
3931
|
const localConfigPath = resolve7(process.cwd(), "biffo.config.json");
|
|
3815
|
-
if (
|
|
3816
|
-
const raw = JSON.parse(
|
|
3932
|
+
if (existsSync16(localConfigPath)) {
|
|
3933
|
+
const raw = JSON.parse(readFileSync10(localConfigPath, "utf8"));
|
|
3817
3934
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
3818
3935
|
if (result.success) return result.data;
|
|
3819
3936
|
if (isTemplatePlaceholderConfig(raw)) {
|
|
@@ -4197,7 +4314,7 @@ function resolveGithubToken() {
|
|
|
4197
4314
|
|
|
4198
4315
|
// src/commands/destroy.ts
|
|
4199
4316
|
import { execSync as execSync5 } from "child_process";
|
|
4200
|
-
import { readFileSync as
|
|
4317
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
4201
4318
|
import { resolve as resolve8 } from "path";
|
|
4202
4319
|
import chalk9 from "chalk";
|
|
4203
4320
|
import { Command as Command10 } from "commander";
|
|
@@ -4287,7 +4404,7 @@ var destroyCommand = new Command10("destroy").description("Destroy infrastructur
|
|
|
4287
4404
|
});
|
|
4288
4405
|
async function resolveConfig3(options) {
|
|
4289
4406
|
if (options.config) {
|
|
4290
|
-
const raw = JSON.parse(
|
|
4407
|
+
const raw = JSON.parse(readFileSync11(resolve8(options.config), "utf8"));
|
|
4291
4408
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
4292
4409
|
if (!result.success) {
|
|
4293
4410
|
log.error(`Invalid config at ${options.config}:`);
|
|
@@ -4307,7 +4424,7 @@ async function resolveConfig3(options) {
|
|
|
4307
4424
|
return cfg;
|
|
4308
4425
|
}
|
|
4309
4426
|
try {
|
|
4310
|
-
const raw = JSON.parse(
|
|
4427
|
+
const raw = JSON.parse(readFileSync11(resolve8(process.cwd(), "biffo.config.json"), "utf8"));
|
|
4311
4428
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
4312
4429
|
if (result.success) return result.data;
|
|
4313
4430
|
} catch {
|
|
@@ -4357,15 +4474,15 @@ function resolveGithubToken2() {
|
|
|
4357
4474
|
}
|
|
4358
4475
|
|
|
4359
4476
|
// src/commands/init.ts
|
|
4360
|
-
import { readFileSync as
|
|
4477
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
4361
4478
|
import { resolve as resolve10 } from "path";
|
|
4362
4479
|
import chalk12 from "chalk";
|
|
4363
4480
|
import { Command as Command12 } from "commander";
|
|
4364
4481
|
import inquirer5 from "inquirer";
|
|
4365
4482
|
|
|
4366
4483
|
// src/lib/build-freshness.ts
|
|
4367
|
-
import { existsSync as
|
|
4368
|
-
import { dirname as dirname5, join as
|
|
4484
|
+
import { existsSync as existsSync17, readdirSync as readdirSync7, statSync as statSync4 } from "fs";
|
|
4485
|
+
import { dirname as dirname5, join as join16, relative as relative2, sep as sep2 } from "path";
|
|
4369
4486
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4370
4487
|
var SKIP_ENV_VAR = "BIFFO_SKIP_BUILD_FRESHNESS_CHECK";
|
|
4371
4488
|
function checkBuildFreshness(options = {}) {
|
|
@@ -4379,7 +4496,7 @@ function checkBuildFreshness(options = {}) {
|
|
|
4379
4496
|
if (!packageRoot) {
|
|
4380
4497
|
return { status: "skipped", reason: `no package.json above ${moduleDir}`, newerSources: [] };
|
|
4381
4498
|
}
|
|
4382
|
-
const distDir =
|
|
4499
|
+
const distDir = join16(packageRoot, "dist");
|
|
4383
4500
|
if (!isInside(distDir, moduleDir)) {
|
|
4384
4501
|
return {
|
|
4385
4502
|
status: "skipped",
|
|
@@ -4387,16 +4504,16 @@ function checkBuildFreshness(options = {}) {
|
|
|
4387
4504
|
newerSources: []
|
|
4388
4505
|
};
|
|
4389
4506
|
}
|
|
4390
|
-
const srcDir =
|
|
4391
|
-
if (!
|
|
4507
|
+
const srcDir = join16(packageRoot, "src");
|
|
4508
|
+
if (!existsSync17(srcDir)) {
|
|
4392
4509
|
return {
|
|
4393
4510
|
status: "skipped",
|
|
4394
4511
|
reason: "no src/ alongside dist/ \u2014 this is a shipped package",
|
|
4395
4512
|
newerSources: []
|
|
4396
4513
|
};
|
|
4397
4514
|
}
|
|
4398
|
-
const entry =
|
|
4399
|
-
if (!
|
|
4515
|
+
const entry = join16(distDir, "index.js");
|
|
4516
|
+
if (!existsSync17(entry)) {
|
|
4400
4517
|
return { status: "skipped", reason: `${entry} not found`, newerSources: [] };
|
|
4401
4518
|
}
|
|
4402
4519
|
const builtAt = statSync4(entry).mtimeMs;
|
|
@@ -4440,7 +4557,7 @@ function collectSourceFiles(srcDir) {
|
|
|
4440
4557
|
const found = [];
|
|
4441
4558
|
const walk = (dir) => {
|
|
4442
4559
|
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
4443
|
-
const full =
|
|
4560
|
+
const full = join16(dir, entry.name);
|
|
4444
4561
|
if (entry.isDirectory()) {
|
|
4445
4562
|
if (entry.name === "node_modules") continue;
|
|
4446
4563
|
walk(full);
|
|
@@ -4459,7 +4576,7 @@ function collectSourceFiles(srcDir) {
|
|
|
4459
4576
|
function findPackageRoot(from) {
|
|
4460
4577
|
let dir = from;
|
|
4461
4578
|
for (; ; ) {
|
|
4462
|
-
if (
|
|
4579
|
+
if (existsSync17(join16(dir, "package.json"))) return dir;
|
|
4463
4580
|
const parent = dirname5(dir);
|
|
4464
4581
|
if (parent === dir) return null;
|
|
4465
4582
|
dir = parent;
|
|
@@ -4473,9 +4590,9 @@ function isInside(parent, child) {
|
|
|
4473
4590
|
|
|
4474
4591
|
// src/lib/credentials.ts
|
|
4475
4592
|
import { execSync as execSync6 } from "child_process";
|
|
4476
|
-
import { existsSync as
|
|
4593
|
+
import { existsSync as existsSync18, readFileSync as readFileSync12 } from "fs";
|
|
4477
4594
|
import { homedir as homedir2 } from "os";
|
|
4478
|
-
import { join as
|
|
4595
|
+
import { join as join17 } from "path";
|
|
4479
4596
|
import { GetCallerIdentityCommand as GetCallerIdentityCommand2, STSClient as STSClient2 } from "@aws-sdk/client-sts";
|
|
4480
4597
|
import chalk10 from "chalk";
|
|
4481
4598
|
import inquirer4 from "inquirer";
|
|
@@ -4654,11 +4771,11 @@ async function verifySelectedAwsCredentials(profile, region) {
|
|
|
4654
4771
|
return sts.send(new GetCallerIdentityCommand2({}));
|
|
4655
4772
|
}
|
|
4656
4773
|
function discoverAwsProfiles() {
|
|
4657
|
-
const files = [
|
|
4774
|
+
const files = [join17(homedir2(), ".aws", "credentials"), join17(homedir2(), ".aws", "config")];
|
|
4658
4775
|
const profiles = /* @__PURE__ */ new Set();
|
|
4659
4776
|
for (const file of files) {
|
|
4660
|
-
if (!
|
|
4661
|
-
const content =
|
|
4777
|
+
if (!existsSync18(file)) continue;
|
|
4778
|
+
const content = readFileSync12(file, "utf8");
|
|
4662
4779
|
for (const match of content.matchAll(/^\s*\[([^\]]+)\]\s*$/gm)) {
|
|
4663
4780
|
const section = match[1]?.trim();
|
|
4664
4781
|
if (!section) continue;
|
|
@@ -4683,15 +4800,15 @@ async function resolveRepoIds(github, config) {
|
|
|
4683
4800
|
}
|
|
4684
4801
|
|
|
4685
4802
|
// src/config/sibling-schema.ts
|
|
4686
|
-
import { z as
|
|
4687
|
-
var SiblingConfigSchema =
|
|
4688
|
-
$schema:
|
|
4689
|
-
project:
|
|
4690
|
-
name:
|
|
4803
|
+
import { z as z5 } from "zod";
|
|
4804
|
+
var SiblingConfigSchema = z5.object({
|
|
4805
|
+
$schema: z5.string().optional(),
|
|
4806
|
+
project: z5.object({
|
|
4807
|
+
name: z5.string().min(1).regex(
|
|
4691
4808
|
/^[a-z][a-z0-9-]*$/,
|
|
4692
4809
|
"Must be lowercase kebab-case, starting with a letter (it becomes a URL path segment)"
|
|
4693
4810
|
),
|
|
4694
|
-
description:
|
|
4811
|
+
description: z5.string().default(""),
|
|
4695
4812
|
// Notable routes this sibling exposes, shown as labelled links on the
|
|
4696
4813
|
// core project's Microservices tab (ADR-0007). Each `path` is relative to
|
|
4697
4814
|
// the sibling's own path_prefix (so "demo" renders as /<prefix>/demo), and
|
|
@@ -4699,26 +4816,26 @@ var SiblingConfigSchema = z4.object({
|
|
|
4699
4816
|
// routes just shows its single root link. Declare real routes here as you
|
|
4700
4817
|
// build the sibling's pages; the values flow to the core's
|
|
4701
4818
|
// siblings.auto.tfvars.json at registration and into siblings.json at deploy.
|
|
4702
|
-
routes:
|
|
4703
|
-
|
|
4704
|
-
path:
|
|
4819
|
+
routes: z5.array(
|
|
4820
|
+
z5.object({
|
|
4821
|
+
path: z5.string().min(1).regex(
|
|
4705
4822
|
/^[a-z0-9][a-z0-9/-]*$/,
|
|
4706
4823
|
'Sub-path relative to the sibling prefix, no leading slash (e.g. "demo" or "apply")'
|
|
4707
4824
|
),
|
|
4708
|
-
label:
|
|
4825
|
+
label: z5.string().min(1)
|
|
4709
4826
|
})
|
|
4710
4827
|
).default([])
|
|
4711
4828
|
}),
|
|
4712
4829
|
source_control: SourceControlConfigSchema,
|
|
4713
4830
|
cloud: CloudConfigSchema,
|
|
4714
|
-
environments:
|
|
4831
|
+
environments: z5.array(z5.enum(["dev", "staging", "prod"])).min(1).default(["dev"]),
|
|
4715
4832
|
// The core project this sibling is paired with (ADR-0007) — never
|
|
4716
4833
|
// provisions its own Cognito pool or CloudFront distribution, always
|
|
4717
4834
|
// plugs into the core project's.
|
|
4718
|
-
core:
|
|
4835
|
+
core: z5.object({
|
|
4719
4836
|
// Exactly one of these two must be set — see the superRefine below.
|
|
4720
|
-
project_name:
|
|
4721
|
-
config_path:
|
|
4837
|
+
project_name: z5.string().min(1).optional().describe("Name of a project previously scaffolded with `biffo init` on this machine"),
|
|
4838
|
+
config_path: z5.string().min(1).optional().describe(
|
|
4722
4839
|
"Path to the core project's biffo.config.json, for when it wasn't scaffolded here"
|
|
4723
4840
|
),
|
|
4724
4841
|
// Defaults to project.name at parse time by the caller (sibling-create.ts),
|
|
@@ -4731,7 +4848,7 @@ var SiblingConfigSchema = z4.object({
|
|
|
4731
4848
|
// CDN's default_cache_behavior instead of a pair of ordered behaviours.
|
|
4732
4849
|
// It still registers under a non-empty reserved name ("app") — see
|
|
4733
4850
|
// lib/root-sibling.ts for why the two must not be conflated.
|
|
4734
|
-
path_prefix:
|
|
4851
|
+
path_prefix: z5.string().regex(
|
|
4735
4852
|
/^$|^[a-z][a-z0-9-]*$/,
|
|
4736
4853
|
"Must be lowercase kebab-case, or empty for the root sibling"
|
|
4737
4854
|
).optional()
|
|
@@ -4739,7 +4856,7 @@ var SiblingConfigSchema = z4.object({
|
|
|
4739
4856
|
}).superRefine((config, ctx) => {
|
|
4740
4857
|
if (!config.core.project_name && !config.core.config_path) {
|
|
4741
4858
|
ctx.addIssue({
|
|
4742
|
-
code:
|
|
4859
|
+
code: z5.ZodIssueCode.custom,
|
|
4743
4860
|
path: ["core"],
|
|
4744
4861
|
message: "Either core.project_name or core.config_path is required"
|
|
4745
4862
|
});
|
|
@@ -4748,34 +4865,34 @@ var SiblingConfigSchema = z4.object({
|
|
|
4748
4865
|
|
|
4749
4866
|
// src/lib/sibling-session.ts
|
|
4750
4867
|
import {
|
|
4751
|
-
existsSync as
|
|
4868
|
+
existsSync as existsSync19,
|
|
4752
4869
|
mkdirSync as mkdirSync6,
|
|
4753
4870
|
readdirSync as readdirSync8,
|
|
4754
|
-
readFileSync as
|
|
4871
|
+
readFileSync as readFileSync13,
|
|
4755
4872
|
rmSync as rmSync7,
|
|
4756
4873
|
statSync as statSync5,
|
|
4757
4874
|
writeFileSync as writeFileSync6
|
|
4758
4875
|
} from "fs";
|
|
4759
4876
|
import { homedir as homedir3 } from "os";
|
|
4760
|
-
import { join as
|
|
4877
|
+
import { join as join18 } from "path";
|
|
4761
4878
|
function sessionsDir2() {
|
|
4762
|
-
return process.env["BIFFO_SIBLING_SESSIONS_DIR"] ??
|
|
4879
|
+
return process.env["BIFFO_SIBLING_SESSIONS_DIR"] ?? join18(homedir3(), ".biffo", "sibling-sessions");
|
|
4763
4880
|
}
|
|
4764
4881
|
function sessionPath2(projectName) {
|
|
4765
|
-
return
|
|
4882
|
+
return join18(sessionsDir2(), `${projectName}.json`);
|
|
4766
4883
|
}
|
|
4767
4884
|
function loadSiblingSession(projectName) {
|
|
4768
4885
|
const path = sessionPath2(projectName);
|
|
4769
|
-
if (!
|
|
4886
|
+
if (!existsSync19(path)) return null;
|
|
4770
4887
|
try {
|
|
4771
|
-
return JSON.parse(
|
|
4888
|
+
return JSON.parse(readFileSync13(path, "utf8"));
|
|
4772
4889
|
} catch {
|
|
4773
4890
|
return null;
|
|
4774
4891
|
}
|
|
4775
4892
|
}
|
|
4776
4893
|
function saveSiblingSession(session) {
|
|
4777
4894
|
const dir = sessionsDir2();
|
|
4778
|
-
if (!
|
|
4895
|
+
if (!existsSync19(dir)) mkdirSync6(dir, { recursive: true });
|
|
4779
4896
|
const name = session.config.project?.name ?? "unknown";
|
|
4780
4897
|
const prior = loadSiblingSession(name);
|
|
4781
4898
|
if (prior) {
|
|
@@ -4797,30 +4914,30 @@ function markSiblingStepComplete(session, step) {
|
|
|
4797
4914
|
}
|
|
4798
4915
|
function deleteSiblingSession(projectName) {
|
|
4799
4916
|
const path = sessionPath2(projectName);
|
|
4800
|
-
if (
|
|
4917
|
+
if (existsSync19(path)) rmSync7(path);
|
|
4801
4918
|
}
|
|
4802
4919
|
|
|
4803
4920
|
// src/commands/sibling-create.ts
|
|
4804
|
-
import { cpSync as cpSync2, existsSync as
|
|
4921
|
+
import { cpSync as cpSync2, existsSync as existsSync20, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
|
|
4805
4922
|
import { tmpdir as tmpdir4 } from "os";
|
|
4806
|
-
import { dirname as dirname6, join as
|
|
4923
|
+
import { dirname as dirname6, join as join20, resolve as resolve9 } from "path";
|
|
4807
4924
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4808
4925
|
import chalk11 from "chalk";
|
|
4809
4926
|
import { Command as Command11 } from "commander";
|
|
4810
4927
|
|
|
4811
4928
|
// src/lib/skeleton-dotfiles.ts
|
|
4812
4929
|
import { readdirSync as readdirSync9, renameSync } from "fs";
|
|
4813
|
-
import { join as
|
|
4930
|
+
import { join as join19 } from "path";
|
|
4814
4931
|
var PACKAGED_GITIGNORE = "_gitignore";
|
|
4815
4932
|
var REAL_GITIGNORE = ".gitignore";
|
|
4816
4933
|
function restorePackagedDotfiles(dir) {
|
|
4817
4934
|
const restored = [];
|
|
4818
4935
|
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
4819
|
-
const full =
|
|
4936
|
+
const full = join19(dir, entry.name);
|
|
4820
4937
|
if (entry.isDirectory()) {
|
|
4821
4938
|
restored.push(...restorePackagedDotfiles(full));
|
|
4822
4939
|
} else if (entry.name === PACKAGED_GITIGNORE) {
|
|
4823
|
-
const target =
|
|
4940
|
+
const target = join19(dir, REAL_GITIGNORE);
|
|
4824
4941
|
renameSync(full, target);
|
|
4825
4942
|
restored.push(target);
|
|
4826
4943
|
}
|
|
@@ -4865,7 +4982,7 @@ async function runSiblingCreateCommand(name, options) {
|
|
|
4865
4982
|
printDryRun2(config, coreConfig, options.templateRoot);
|
|
4866
4983
|
return;
|
|
4867
4984
|
}
|
|
4868
|
-
if (!
|
|
4985
|
+
if (!existsSync20(options.templateRoot)) {
|
|
4869
4986
|
throw new Error(`Sibling template not found at ${options.templateRoot}`);
|
|
4870
4987
|
}
|
|
4871
4988
|
let session = null;
|
|
@@ -5050,7 +5167,7 @@ function assertPathPrefixIsAllowed(pathPrefix) {
|
|
|
5050
5167
|
}
|
|
5051
5168
|
}
|
|
5052
5169
|
function readSiblingConfig(path, root = false) {
|
|
5053
|
-
const raw = JSON.parse(
|
|
5170
|
+
const raw = JSON.parse(readFileSync14(path, "utf8"));
|
|
5054
5171
|
const withDefaults = raw && typeof raw === "object" && "project" in raw && "core" in raw ? {
|
|
5055
5172
|
...raw,
|
|
5056
5173
|
core: {
|
|
@@ -5084,7 +5201,7 @@ function resolveCoreConfig(config, configPath) {
|
|
|
5084
5201
|
throw new Error("Either core.project_name or core.config_path is required.");
|
|
5085
5202
|
}
|
|
5086
5203
|
function parseCoreConfig(path) {
|
|
5087
|
-
const result = BiffoConfigSchema.safeParse(JSON.parse(
|
|
5204
|
+
const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync14(path, "utf8")));
|
|
5088
5205
|
if (!result.success) {
|
|
5089
5206
|
throw new Error(
|
|
5090
5207
|
`Invalid core configuration at ${path}:
|
|
@@ -5123,7 +5240,7 @@ async function resolveCoreIdentity(coreAws, coreConfig, environments) {
|
|
|
5123
5240
|
return coreIdentity;
|
|
5124
5241
|
}
|
|
5125
5242
|
async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, githubToken) {
|
|
5126
|
-
const workDir = mkdtempSync4(
|
|
5243
|
+
const workDir = mkdtempSync4(join20(tmpdir4(), `biffo-sibling-${config.project.name}-`));
|
|
5127
5244
|
try {
|
|
5128
5245
|
writeSiblingTemplate(skeletonRoot, workDir, config, {
|
|
5129
5246
|
coreProjectName: coreConfig.project.name,
|
|
@@ -5139,13 +5256,13 @@ async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, git
|
|
|
5139
5256
|
}
|
|
5140
5257
|
}
|
|
5141
5258
|
function writeSiblingTemplate(templateRoot, targetDir, config, context) {
|
|
5142
|
-
if (!
|
|
5259
|
+
if (!existsSync20(templateRoot)) {
|
|
5143
5260
|
throw new Error(`Sibling template not found at ${templateRoot}`);
|
|
5144
5261
|
}
|
|
5145
5262
|
cpSync2(templateRoot, targetDir, { recursive: true });
|
|
5146
5263
|
restorePackagedDotfiles(targetDir);
|
|
5147
5264
|
writeFileSync7(
|
|
5148
|
-
|
|
5265
|
+
join20(targetDir, "biffo.sibling.json"),
|
|
5149
5266
|
JSON.stringify(
|
|
5150
5267
|
{
|
|
5151
5268
|
name: config.project.name,
|
|
@@ -5161,10 +5278,10 @@ function writeSiblingTemplate(templateRoot, targetDir, config, context) {
|
|
|
5161
5278
|
2
|
|
5162
5279
|
) + "\n"
|
|
5163
5280
|
);
|
|
5164
|
-
const envPath =
|
|
5281
|
+
const envPath = join20(targetDir, "apps", "frontend", ".env.example");
|
|
5165
5282
|
try {
|
|
5166
5283
|
const path = basePathFor(context.pathPrefix);
|
|
5167
|
-
const content =
|
|
5284
|
+
const content = readFileSync14(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}`);
|
|
5168
5285
|
writeFileSync7(envPath, content);
|
|
5169
5286
|
} catch (err) {
|
|
5170
5287
|
if (err.code !== "ENOENT") throw err;
|
|
@@ -5210,17 +5327,17 @@ async function configureSiblingGithub(github, config, coreConfig, session, coreI
|
|
|
5210
5327
|
}
|
|
5211
5328
|
function readExistingSiblingOrigins(filePath) {
|
|
5212
5329
|
try {
|
|
5213
|
-
return JSON.parse(
|
|
5330
|
+
return JSON.parse(readFileSync14(filePath, "utf8"));
|
|
5214
5331
|
} catch (err) {
|
|
5215
5332
|
if (err.code === "ENOENT") return {};
|
|
5216
5333
|
throw err;
|
|
5217
5334
|
}
|
|
5218
5335
|
}
|
|
5219
5336
|
function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x") {
|
|
5220
|
-
const cdnVarsPath =
|
|
5337
|
+
const cdnVarsPath = join20(cloneDir, "modules", "cloud", "aws", "cdn", "variables.tf");
|
|
5221
5338
|
let declaresSiblingOrigins = false;
|
|
5222
5339
|
try {
|
|
5223
|
-
declaresSiblingOrigins = /variable\s+"sibling_origins"/.test(
|
|
5340
|
+
declaresSiblingOrigins = /variable\s+"sibling_origins"/.test(readFileSync14(cdnVarsPath, "utf8"));
|
|
5224
5341
|
} catch {
|
|
5225
5342
|
declaresSiblingOrigins = false;
|
|
5226
5343
|
}
|
|
@@ -5230,10 +5347,10 @@ function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x")
|
|
|
5230
5347
|
);
|
|
5231
5348
|
}
|
|
5232
5349
|
if (!isRootPathPrefix(pathPrefix)) return;
|
|
5233
|
-
const cdnMainPath =
|
|
5350
|
+
const cdnMainPath = join20(cloneDir, "modules", "cloud", "aws", "cdn", "main.tf");
|
|
5234
5351
|
let supportsRoot = false;
|
|
5235
5352
|
try {
|
|
5236
|
-
supportsRoot = /root_sibling_registered/.test(
|
|
5353
|
+
supportsRoot = /root_sibling_registered/.test(readFileSync14(cdnMainPath, "utf8"));
|
|
5237
5354
|
} catch {
|
|
5238
5355
|
supportsRoot = false;
|
|
5239
5356
|
}
|
|
@@ -5263,8 +5380,8 @@ async function registerWithCore(git, github, config, coreConfig, pathPrefix, git
|
|
|
5263
5380
|
for (const env of config.environments) {
|
|
5264
5381
|
const bucketName = siteBucketName(config.project.name, env, siblingAccountId);
|
|
5265
5382
|
const domain = bucketRegionalDomain(bucketName, coreAwsRegion);
|
|
5266
|
-
const relativePath =
|
|
5267
|
-
const filePath =
|
|
5383
|
+
const relativePath = join20("infra", "environments", env, "siblings.auto.tfvars.json");
|
|
5384
|
+
const filePath = join20(cloneDir, relativePath);
|
|
5268
5385
|
const existing = readExistingSiblingOrigins(filePath);
|
|
5269
5386
|
const siblings = upsertSiblingOrigin(existing.sibling_origins ?? [], {
|
|
5270
5387
|
name,
|
|
@@ -5344,8 +5461,8 @@ function defaultSiblingTemplateRoot() {
|
|
|
5344
5461
|
const start = dirname6(fileURLToPath4(import.meta.url));
|
|
5345
5462
|
let dir = start;
|
|
5346
5463
|
for (; ; ) {
|
|
5347
|
-
const candidate =
|
|
5348
|
-
if (
|
|
5464
|
+
const candidate = join20(dir, "_skeletons", "sibling-template");
|
|
5465
|
+
if (existsSync20(candidate)) return candidate;
|
|
5349
5466
|
const parent = dirname6(dir);
|
|
5350
5467
|
if (parent === dir) break;
|
|
5351
5468
|
dir = parent;
|
|
@@ -5369,7 +5486,7 @@ var initCommand = new Command12("init").description("Scaffold a new project from
|
|
|
5369
5486
|
let config;
|
|
5370
5487
|
let githubToken;
|
|
5371
5488
|
if (options.config) {
|
|
5372
|
-
const rawConfig = JSON.parse(
|
|
5489
|
+
const rawConfig = JSON.parse(readFileSync15(resolve10(options.config), "utf8"));
|
|
5373
5490
|
config = parseConfig(rawConfig);
|
|
5374
5491
|
const { account_id: accountId, region } = config.cloud.config;
|
|
5375
5492
|
session = resolveConfigFileSession(config, accountId, region, options.fresh === true);
|
|
@@ -5802,28 +5919,28 @@ async function promptForConfig(awsAccountId, awsRegion, awsProfile) {
|
|
|
5802
5919
|
import { Command as Command20 } from "commander";
|
|
5803
5920
|
|
|
5804
5921
|
// src/commands/plugin-create.ts
|
|
5805
|
-
import { existsSync as
|
|
5806
|
-
import { dirname as dirname8, join as
|
|
5922
|
+
import { existsSync as existsSync23, readFileSync as readFileSync17 } from "fs";
|
|
5923
|
+
import { dirname as dirname8, join as join23, resolve as resolve11 } from "path";
|
|
5807
5924
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
5808
5925
|
import chalk13 from "chalk";
|
|
5809
5926
|
import { Command as Command13 } from "commander";
|
|
5810
5927
|
|
|
5811
5928
|
// src/lib/plugin-locations.ts
|
|
5812
|
-
import { existsSync as
|
|
5813
|
-
import { join as
|
|
5929
|
+
import { existsSync as existsSync21, readdirSync as readdirSync10 } from "fs";
|
|
5930
|
+
import { join as join21 } from "path";
|
|
5814
5931
|
var FIRST_PARTY_PLUGINS_DIR = "_plugins";
|
|
5815
5932
|
var PLUGIN_MANIFEST_FILE = "biffo.plugin.json";
|
|
5816
5933
|
function pluginDir(name, channel) {
|
|
5817
5934
|
return channel === "first-party" ? `services/${FIRST_PARTY_PLUGINS_DIR}/${name}` : `services/${name}`;
|
|
5818
5935
|
}
|
|
5819
5936
|
function scanDir(absDir, relDir, channel) {
|
|
5820
|
-
if (!
|
|
5937
|
+
if (!existsSync21(absDir)) return [];
|
|
5821
5938
|
const found = [];
|
|
5822
5939
|
for (const entry of readdirSync10(absDir, { withFileTypes: true })) {
|
|
5823
5940
|
if (!entry.isDirectory()) continue;
|
|
5824
5941
|
if (channel === "third-party" && entry.name === FIRST_PARTY_PLUGINS_DIR) continue;
|
|
5825
|
-
const manifestPath =
|
|
5826
|
-
if (!
|
|
5942
|
+
const manifestPath = join21(absDir, entry.name, PLUGIN_MANIFEST_FILE);
|
|
5943
|
+
if (!existsSync21(manifestPath)) continue;
|
|
5827
5944
|
found.push({
|
|
5828
5945
|
dirName: entry.name,
|
|
5829
5946
|
relDir: `${relDir}/${entry.name}`,
|
|
@@ -5834,11 +5951,11 @@ function scanDir(absDir, relDir, channel) {
|
|
|
5834
5951
|
return found;
|
|
5835
5952
|
}
|
|
5836
5953
|
function findInstalledPlugins(cwd) {
|
|
5837
|
-
const servicesDir =
|
|
5954
|
+
const servicesDir = join21(cwd, "services");
|
|
5838
5955
|
return [
|
|
5839
5956
|
...scanDir(servicesDir, "services", "third-party"),
|
|
5840
5957
|
...scanDir(
|
|
5841
|
-
|
|
5958
|
+
join21(servicesDir, FIRST_PARTY_PLUGINS_DIR),
|
|
5842
5959
|
`services/${FIRST_PARTY_PLUGINS_DIR}`,
|
|
5843
5960
|
"first-party"
|
|
5844
5961
|
)
|
|
@@ -5846,46 +5963,46 @@ function findInstalledPlugins(cwd) {
|
|
|
5846
5963
|
}
|
|
5847
5964
|
|
|
5848
5965
|
// src/lib/plugin-manifest.ts
|
|
5849
|
-
import { z as
|
|
5966
|
+
import { z as z6 } from "zod";
|
|
5850
5967
|
var RESERVED_COLUMN_NAMES = /* @__PURE__ */ new Set(["id", "tenant_id", "created_at", "updated_at"]);
|
|
5851
5968
|
var COLUMN_TYPE_PATTERN = /^(String|Integer|Text|Boolean|Float|DateTime)(\(.*\))?$/;
|
|
5852
|
-
var ColumnDefinitionSchema =
|
|
5853
|
-
name:
|
|
5969
|
+
var ColumnDefinitionSchema = z6.object({
|
|
5970
|
+
name: z6.string().refine(
|
|
5854
5971
|
(n) => !RESERVED_COLUMN_NAMES.has(n),
|
|
5855
5972
|
(n) => ({
|
|
5856
5973
|
message: `Column '${n}' is reserved and added automatically; it must not be declared in the manifest.`
|
|
5857
5974
|
})
|
|
5858
5975
|
),
|
|
5859
|
-
type:
|
|
5976
|
+
type: z6.string().regex(
|
|
5860
5977
|
COLUMN_TYPE_PATTERN,
|
|
5861
5978
|
"must be one of String, Integer, Text, Boolean, Float, DateTime (e.g. 'String(255)')"
|
|
5862
5979
|
),
|
|
5863
|
-
primary_key:
|
|
5864
|
-
nullable:
|
|
5865
|
-
index:
|
|
5866
|
-
default:
|
|
5867
|
-
description:
|
|
5980
|
+
primary_key: z6.boolean().default(false),
|
|
5981
|
+
nullable: z6.boolean().default(false),
|
|
5982
|
+
index: z6.boolean().default(false),
|
|
5983
|
+
default: z6.string().optional(),
|
|
5984
|
+
description: z6.string().default("")
|
|
5868
5985
|
});
|
|
5869
|
-
var IndexDefinitionSchema =
|
|
5870
|
-
name:
|
|
5871
|
-
columns:
|
|
5872
|
-
unique:
|
|
5986
|
+
var IndexDefinitionSchema = z6.object({
|
|
5987
|
+
name: z6.string(),
|
|
5988
|
+
columns: z6.array(z6.string()).min(1),
|
|
5989
|
+
unique: z6.boolean().default(false)
|
|
5873
5990
|
});
|
|
5874
|
-
var PermissionRuleSchema =
|
|
5875
|
-
allowed:
|
|
5876
|
-
required_role:
|
|
5991
|
+
var PermissionRuleSchema = z6.object({
|
|
5992
|
+
allowed: z6.boolean().default(false),
|
|
5993
|
+
required_role: z6.array(z6.string()).default([])
|
|
5877
5994
|
}).strict();
|
|
5878
|
-
var TablePermissionsSchema =
|
|
5995
|
+
var TablePermissionsSchema = z6.object({
|
|
5879
5996
|
list: PermissionRuleSchema.default({}),
|
|
5880
5997
|
read: PermissionRuleSchema.default({}),
|
|
5881
5998
|
create: PermissionRuleSchema.default({}),
|
|
5882
5999
|
update: PermissionRuleSchema.default({}),
|
|
5883
6000
|
delete: PermissionRuleSchema.default({})
|
|
5884
6001
|
}).strict();
|
|
5885
|
-
var TableDefinitionSchema =
|
|
5886
|
-
name:
|
|
5887
|
-
columns:
|
|
5888
|
-
indexes:
|
|
6002
|
+
var TableDefinitionSchema = z6.object({
|
|
6003
|
+
name: z6.string().regex(/^[a-z][a-z0-9_]*$/, "table name must be snake_case, e.g. rbac_roles"),
|
|
6004
|
+
columns: z6.array(ColumnDefinitionSchema).default([]),
|
|
6005
|
+
indexes: z6.array(IndexDefinitionSchema).default([]),
|
|
5889
6006
|
permissions: TablePermissionsSchema.default({})
|
|
5890
6007
|
}).superRefine((table, ctx) => {
|
|
5891
6008
|
const colCounts = /* @__PURE__ */ new Map();
|
|
@@ -5893,7 +6010,7 @@ var TableDefinitionSchema = z5.object({
|
|
|
5893
6010
|
for (const [name, count] of colCounts) {
|
|
5894
6011
|
if (count > 1) {
|
|
5895
6012
|
ctx.addIssue({
|
|
5896
|
-
code:
|
|
6013
|
+
code: z6.ZodIssueCode.custom,
|
|
5897
6014
|
message: `Duplicate column name '${name}' in table '${table.name}'`
|
|
5898
6015
|
});
|
|
5899
6016
|
}
|
|
@@ -5903,7 +6020,7 @@ var TableDefinitionSchema = z5.object({
|
|
|
5903
6020
|
for (const [name, count] of idxCounts) {
|
|
5904
6021
|
if (count > 1) {
|
|
5905
6022
|
ctx.addIssue({
|
|
5906
|
-
code:
|
|
6023
|
+
code: z6.ZodIssueCode.custom,
|
|
5907
6024
|
message: `Duplicate index name '${name}' in table '${table.name}'`
|
|
5908
6025
|
});
|
|
5909
6026
|
}
|
|
@@ -5913,7 +6030,7 @@ var TableDefinitionSchema = z5.object({
|
|
|
5913
6030
|
for (const col of idx.columns) {
|
|
5914
6031
|
if (!validColumns.has(col)) {
|
|
5915
6032
|
ctx.addIssue({
|
|
5916
|
-
code:
|
|
6033
|
+
code: z6.ZodIssueCode.custom,
|
|
5917
6034
|
message: `Index '${idx.name}' on table '${table.name}' references unknown column '${col}'`
|
|
5918
6035
|
});
|
|
5919
6036
|
}
|
|
@@ -5928,17 +6045,17 @@ var OPERATION_METHODS = {
|
|
|
5928
6045
|
delete: /* @__PURE__ */ new Set(["DELETE"])
|
|
5929
6046
|
};
|
|
5930
6047
|
var SINGLE_ROW_OPERATIONS = /* @__PURE__ */ new Set(["read", "update", "delete"]);
|
|
5931
|
-
var RouteDefSchema =
|
|
5932
|
-
method:
|
|
5933
|
-
path:
|
|
5934
|
-
table:
|
|
5935
|
-
operation:
|
|
5936
|
-
description:
|
|
6048
|
+
var RouteDefSchema = z6.object({
|
|
6049
|
+
method: z6.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
|
|
6050
|
+
path: z6.string().startsWith("/", "path must start with '/'"),
|
|
6051
|
+
table: z6.string(),
|
|
6052
|
+
operation: z6.enum(["list", "read", "create", "update", "delete"]),
|
|
6053
|
+
description: z6.string().default("")
|
|
5937
6054
|
}).superRefine((route, ctx) => {
|
|
5938
6055
|
const allowed = OPERATION_METHODS[route.operation];
|
|
5939
6056
|
if (allowed && !allowed.has(route.method)) {
|
|
5940
6057
|
ctx.addIssue({
|
|
5941
|
-
code:
|
|
6058
|
+
code: z6.ZodIssueCode.custom,
|
|
5942
6059
|
message: `operation '${route.operation}' requires method in [${[...allowed].sort().join(", ")}], got '${route.method}'`
|
|
5943
6060
|
});
|
|
5944
6061
|
}
|
|
@@ -5946,38 +6063,38 @@ var RouteDefSchema = z5.object({
|
|
|
5946
6063
|
const needsId = SINGLE_ROW_OPERATIONS.has(route.operation);
|
|
5947
6064
|
if (needsId && !hasId) {
|
|
5948
6065
|
ctx.addIssue({
|
|
5949
|
-
code:
|
|
6066
|
+
code: z6.ZodIssueCode.custom,
|
|
5950
6067
|
message: `operation '${route.operation}' addresses a single row and requires an '{id}' path parameter: ${route.path}`
|
|
5951
6068
|
});
|
|
5952
6069
|
}
|
|
5953
6070
|
if (!needsId && hasId) {
|
|
5954
6071
|
ctx.addIssue({
|
|
5955
|
-
code:
|
|
6072
|
+
code: z6.ZodIssueCode.custom,
|
|
5956
6073
|
message: `operation '${route.operation}' is collection-level and must not have an '{id}' path parameter: ${route.path}`
|
|
5957
6074
|
});
|
|
5958
6075
|
}
|
|
5959
6076
|
});
|
|
5960
|
-
var PluginManifestSchema =
|
|
5961
|
-
name:
|
|
5962
|
-
version:
|
|
5963
|
-
description:
|
|
5964
|
-
author:
|
|
5965
|
-
tags:
|
|
5966
|
-
tables:
|
|
5967
|
-
api_routes:
|
|
6077
|
+
var PluginManifestSchema = z6.object({
|
|
6078
|
+
name: z6.string().regex(/^[a-z][a-z0-9-]*$/, "must be a lowercase kebab-case slug"),
|
|
6079
|
+
version: z6.string().regex(/^\d+\.\d+\.\d+$/, "must be a full semver, e.g. 1.2.3"),
|
|
6080
|
+
description: z6.string().default(""),
|
|
6081
|
+
author: z6.string().default("Biffo Team"),
|
|
6082
|
+
tags: z6.array(z6.string()).default([]),
|
|
6083
|
+
tables: z6.array(TableDefinitionSchema).default([]),
|
|
6084
|
+
api_routes: z6.array(RouteDefSchema).default([]),
|
|
5968
6085
|
// Events the plugin reacts to. Parsed (rather than dropped as an unknown
|
|
5969
6086
|
// key) so `biffo plugin install` can warn when a plugin declares
|
|
5970
6087
|
// subscriptions but ships no terraform/ to route them — see #194 and
|
|
5971
6088
|
// lib/plugin-terraform-guard.ts. Kept loose deliberately: the authoritative
|
|
5972
6089
|
// schema is the registry's, and this consumer only needs to count them.
|
|
5973
|
-
event_subscriptions:
|
|
5974
|
-
required_core_version:
|
|
6090
|
+
event_subscriptions: z6.array(z6.object({ source: z6.string(), detail_type: z6.string() }).passthrough()).default([]),
|
|
6091
|
+
required_core_version: z6.string().default(">=0.0.0")
|
|
5975
6092
|
}).superRefine((manifest, ctx) => {
|
|
5976
6093
|
const tableNames = new Set(manifest.tables.map((t) => t.name));
|
|
5977
6094
|
for (const route of manifest.api_routes) {
|
|
5978
6095
|
if (!tableNames.has(route.table)) {
|
|
5979
6096
|
ctx.addIssue({
|
|
5980
|
-
code:
|
|
6097
|
+
code: z6.ZodIssueCode.custom,
|
|
5981
6098
|
message: `Route ${route.method} ${route.path} references table '${route.table}', which is not declared in this manifest's 'tables' (${[...tableNames].sort().join(", ") || "none"})`
|
|
5982
6099
|
});
|
|
5983
6100
|
}
|
|
@@ -5998,13 +6115,13 @@ function validateManifest(raw) {
|
|
|
5998
6115
|
// src/lib/plugin-scaffold.ts
|
|
5999
6116
|
import {
|
|
6000
6117
|
copyFileSync,
|
|
6001
|
-
existsSync as
|
|
6118
|
+
existsSync as existsSync22,
|
|
6002
6119
|
mkdirSync as mkdirSync8,
|
|
6003
|
-
readFileSync as
|
|
6120
|
+
readFileSync as readFileSync16,
|
|
6004
6121
|
readdirSync as readdirSync11,
|
|
6005
6122
|
writeFileSync as writeFileSync8
|
|
6006
6123
|
} from "fs";
|
|
6007
|
-
import { dirname as dirname7, join as
|
|
6124
|
+
import { dirname as dirname7, join as join22 } from "path";
|
|
6008
6125
|
var STANDALONE_ONLY_ENTRIES = {
|
|
6009
6126
|
".github": "standalone-repo CI/release workflows \u2014 the host monorepo already runs lint/type/test/security over services/",
|
|
6010
6127
|
"registry-schema.json": "the plugin-registry publishing schema, used when submitting a *published* plugin to the registry repo, not by an in-tree plugin"
|
|
@@ -6057,10 +6174,10 @@ function applySubstitutions(text, names) {
|
|
|
6057
6174
|
}
|
|
6058
6175
|
var BINARY_EXTENSIONS = /\.(png|jpe?g|gif|ico|woff2?|ttf|zip|gz)$/i;
|
|
6059
6176
|
function scaffoldPlugin(skeletonRoot, destDir, names) {
|
|
6060
|
-
if (!
|
|
6177
|
+
if (!existsSync22(skeletonRoot)) {
|
|
6061
6178
|
throw new Error(`Plugin skeleton not found at ${skeletonRoot}`);
|
|
6062
6179
|
}
|
|
6063
|
-
if (!
|
|
6180
|
+
if (!existsSync22(join22(skeletonRoot, "terraform"))) {
|
|
6064
6181
|
throw new Error(
|
|
6065
6182
|
`Plugin skeleton at ${skeletonRoot} has no terraform/ directory. Refusing to scaffold a plugin that cannot receive events (issue #194) \u2014 the skeleton is broken.`
|
|
6066
6183
|
);
|
|
@@ -6068,7 +6185,7 @@ function scaffoldPlugin(skeletonRoot, destDir, names) {
|
|
|
6068
6185
|
const skipped = [];
|
|
6069
6186
|
const files = [];
|
|
6070
6187
|
const walk = (relDir) => {
|
|
6071
|
-
const absDir =
|
|
6188
|
+
const absDir = join22(skeletonRoot, relDir);
|
|
6072
6189
|
for (const entry of readdirSync11(absDir, { withFileTypes: true }).sort(
|
|
6073
6190
|
(a, b) => a.name.localeCompare(b.name)
|
|
6074
6191
|
)) {
|
|
@@ -6083,14 +6200,14 @@ function scaffoldPlugin(skeletonRoot, destDir, names) {
|
|
|
6083
6200
|
continue;
|
|
6084
6201
|
}
|
|
6085
6202
|
const destRel = applySubstitutions(relPath, names);
|
|
6086
|
-
const destPath =
|
|
6203
|
+
const destPath = join22(destDir, destRel);
|
|
6087
6204
|
mkdirSync8(dirname7(destPath), { recursive: true });
|
|
6088
6205
|
if (BINARY_EXTENSIONS.test(entry.name)) {
|
|
6089
|
-
copyFileSync(
|
|
6206
|
+
copyFileSync(join22(skeletonRoot, relPath), destPath);
|
|
6090
6207
|
} else {
|
|
6091
6208
|
writeFileSync8(
|
|
6092
6209
|
destPath,
|
|
6093
|
-
applySubstitutions(
|
|
6210
|
+
applySubstitutions(readFileSync16(join22(skeletonRoot, relPath), "utf8"), names)
|
|
6094
6211
|
);
|
|
6095
6212
|
}
|
|
6096
6213
|
files.push(destRel);
|
|
@@ -6107,8 +6224,8 @@ function scaffoldPlugin(skeletonRoot, destDir, names) {
|
|
|
6107
6224
|
function findSkeletonRoot(startDir, skeleton) {
|
|
6108
6225
|
let dir = startDir;
|
|
6109
6226
|
for (; ; ) {
|
|
6110
|
-
const candidate =
|
|
6111
|
-
if (
|
|
6227
|
+
const candidate = join22(dir, "_skeletons", skeleton);
|
|
6228
|
+
if (existsSync22(candidate)) return candidate;
|
|
6112
6229
|
const parent = dirname7(dir);
|
|
6113
6230
|
if (parent === dir) return null;
|
|
6114
6231
|
dir = parent;
|
|
@@ -6145,7 +6262,7 @@ var pluginCreateCommand = new Command13("create").description("Scaffold a new pl
|
|
|
6145
6262
|
);
|
|
6146
6263
|
async function runPluginCreate(name, options, deps) {
|
|
6147
6264
|
const names = deriveNames(name);
|
|
6148
|
-
const isInstance =
|
|
6265
|
+
const isInstance = existsSync23(join23(options.cwd, INSTANCE_CORE_FILE));
|
|
6149
6266
|
if (options.firstParty && isInstance) {
|
|
6150
6267
|
throw new Error(
|
|
6151
6268
|
`--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.`
|
|
@@ -6153,19 +6270,19 @@ async function runPluginCreate(name, options, deps) {
|
|
|
6153
6270
|
}
|
|
6154
6271
|
const channel = options.firstParty ? "first-party" : "third-party";
|
|
6155
6272
|
const relDir = pluginDir(names.slug, channel);
|
|
6156
|
-
const destDir =
|
|
6157
|
-
const servicesDir =
|
|
6158
|
-
if (!
|
|
6273
|
+
const destDir = join23(options.cwd, relDir);
|
|
6274
|
+
const servicesDir = join23(options.cwd, "services");
|
|
6275
|
+
if (!existsSync23(servicesDir)) {
|
|
6159
6276
|
throw new Error(
|
|
6160
6277
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
6161
6278
|
);
|
|
6162
6279
|
}
|
|
6163
|
-
if (
|
|
6280
|
+
if (existsSync23(destDir)) {
|
|
6164
6281
|
throw new Error(`${relDir}/ already exists. Choose a different name, or remove it first.`);
|
|
6165
6282
|
}
|
|
6166
6283
|
const here = dirname8(fileURLToPath5(import.meta.url));
|
|
6167
|
-
const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ??
|
|
6168
|
-
if (!
|
|
6284
|
+
const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join23(options.cwd, "_skeletons", "plugin-template");
|
|
6285
|
+
if (!existsSync23(skeletonRoot)) {
|
|
6169
6286
|
throw new Error(
|
|
6170
6287
|
`Could not find the plugin skeleton (_skeletons/plugin-template/). Pass --skeleton <path> to point at it explicitly.`
|
|
6171
6288
|
);
|
|
@@ -6180,8 +6297,8 @@ async function runPluginCreate(name, options, deps) {
|
|
|
6180
6297
|
for (const { entry, reason } of skipped) {
|
|
6181
6298
|
log.info(`Skipped ${entry} \u2014 ${reason}`);
|
|
6182
6299
|
}
|
|
6183
|
-
const manifestPath =
|
|
6184
|
-
const manifest = validateManifest(JSON.parse(
|
|
6300
|
+
const manifestPath = join23(destDir, "biffo.plugin.json");
|
|
6301
|
+
const manifest = validateManifest(JSON.parse(readFileSync17(manifestPath, "utf8")));
|
|
6185
6302
|
if (manifest.name !== names.slug) {
|
|
6186
6303
|
throw new Error(
|
|
6187
6304
|
`Scaffolded manifest declares name '${manifest.name}', expected '${names.slug}'. The skeleton's manifest name may have diverged from 'example-plugin'.`
|
|
@@ -6237,25 +6354,25 @@ import chalk14 from "chalk";
|
|
|
6237
6354
|
import { Command as Command14 } from "commander";
|
|
6238
6355
|
|
|
6239
6356
|
// src/adapters/registry/index.ts
|
|
6240
|
-
import { z as
|
|
6241
|
-
var RegistryPluginEntrySchema =
|
|
6242
|
-
name:
|
|
6243
|
-
version:
|
|
6244
|
-
minor_version:
|
|
6245
|
-
repo:
|
|
6246
|
-
description:
|
|
6247
|
-
author:
|
|
6248
|
-
tags:
|
|
6249
|
-
required_core_version:
|
|
6250
|
-
infra_modules:
|
|
6251
|
-
api_routes:
|
|
6252
|
-
ui_components:
|
|
6253
|
-
status:
|
|
6357
|
+
import { z as z7 } from "zod";
|
|
6358
|
+
var RegistryPluginEntrySchema = z7.object({
|
|
6359
|
+
name: z7.string().regex(/^[a-z][a-z0-9-]*$/),
|
|
6360
|
+
version: z7.string().regex(/^\d+\.\d+\.\d+$/),
|
|
6361
|
+
minor_version: z7.string().regex(/^\d+\.\d+$/),
|
|
6362
|
+
repo: z7.string().url(),
|
|
6363
|
+
description: z7.string().optional(),
|
|
6364
|
+
author: z7.string().optional(),
|
|
6365
|
+
tags: z7.array(z7.string()).optional(),
|
|
6366
|
+
required_core_version: z7.string().optional(),
|
|
6367
|
+
infra_modules: z7.array(z7.string()).optional(),
|
|
6368
|
+
api_routes: z7.array(z7.string()).optional(),
|
|
6369
|
+
ui_components: z7.array(z7.string()).optional(),
|
|
6370
|
+
status: z7.enum(["active", "disabled"])
|
|
6254
6371
|
});
|
|
6255
|
-
var PluginRegistrySchema =
|
|
6256
|
-
schema_version:
|
|
6257
|
-
last_updated:
|
|
6258
|
-
plugins:
|
|
6372
|
+
var PluginRegistrySchema = z7.object({
|
|
6373
|
+
schema_version: z7.string(),
|
|
6374
|
+
last_updated: z7.string(),
|
|
6375
|
+
plugins: z7.array(RegistryPluginEntrySchema)
|
|
6259
6376
|
});
|
|
6260
6377
|
var DEFAULT_REGISTRY_URL = "https://raw.githubusercontent.com/keiranholloway/biffo-plugins-registry/main/plugins.json";
|
|
6261
6378
|
var RegistryAdapter = class {
|
|
@@ -6371,14 +6488,14 @@ function printEntry(entry) {
|
|
|
6371
6488
|
}
|
|
6372
6489
|
|
|
6373
6490
|
// src/commands/plugin-install.ts
|
|
6374
|
-
import { cpSync as cpSync3, existsSync as
|
|
6375
|
-
import { basename, join as
|
|
6491
|
+
import { cpSync as cpSync3, existsSync as existsSync24, mkdirSync as mkdirSync9, readFileSync as readFileSync18, statSync as statSync6 } from "fs";
|
|
6492
|
+
import { basename, join as join25, relative as relative3, resolve as resolve12 } from "path";
|
|
6376
6493
|
import chalk15 from "chalk";
|
|
6377
6494
|
import { Command as Command15 } from "commander";
|
|
6378
6495
|
|
|
6379
6496
|
// src/adapters/plugin-migrations/index.ts
|
|
6380
6497
|
import { execa as execa4 } from "execa";
|
|
6381
|
-
import { join as
|
|
6498
|
+
import { join as join24 } from "path";
|
|
6382
6499
|
var PluginMigrationsAdapter = class {
|
|
6383
6500
|
/**
|
|
6384
6501
|
* Generates migration file(s) for `pluginNames` (every discovered
|
|
@@ -6387,22 +6504,22 @@ var PluginMigrationsAdapter = class {
|
|
|
6387
6504
|
* or declared no tables.
|
|
6388
6505
|
*/
|
|
6389
6506
|
async generate(cwd, pluginNames) {
|
|
6390
|
-
const scriptPath =
|
|
6507
|
+
const scriptPath = join24(cwd, "services", "api", "scripts", "generate_plugin_migrations.py");
|
|
6391
6508
|
const args = [
|
|
6392
6509
|
"run",
|
|
6393
6510
|
"python",
|
|
6394
6511
|
scriptPath,
|
|
6395
6512
|
"--services-root",
|
|
6396
|
-
|
|
6513
|
+
join24(cwd, "services"),
|
|
6397
6514
|
"--versions-dir",
|
|
6398
|
-
|
|
6515
|
+
join24(cwd, "services", "api", "migrations", "versions")
|
|
6399
6516
|
];
|
|
6400
6517
|
for (const name of pluginNames ?? []) {
|
|
6401
6518
|
args.push("--plugin", name);
|
|
6402
6519
|
}
|
|
6403
6520
|
let result;
|
|
6404
6521
|
try {
|
|
6405
|
-
result = await execa4("uv", args, { cwd:
|
|
6522
|
+
result = await execa4("uv", args, { cwd: join24(cwd, "services", "api") });
|
|
6406
6523
|
} catch (err) {
|
|
6407
6524
|
const cause = err;
|
|
6408
6525
|
if (cause.code === "ENOENT") {
|
|
@@ -6460,14 +6577,14 @@ var LOCAL_COPY_EXCLUDES = /* @__PURE__ */ new Set([
|
|
|
6460
6577
|
".terraform"
|
|
6461
6578
|
]);
|
|
6462
6579
|
function resolveLocalPlugin(localPath) {
|
|
6463
|
-
if (!
|
|
6580
|
+
if (!existsSync24(localPath)) {
|
|
6464
6581
|
throw new Error(`--local path does not exist: ${localPath}`);
|
|
6465
6582
|
}
|
|
6466
6583
|
if (!statSync6(localPath).isDirectory()) {
|
|
6467
6584
|
throw new Error(`--local path is not a directory: ${localPath}`);
|
|
6468
6585
|
}
|
|
6469
|
-
const manifestPath =
|
|
6470
|
-
if (!
|
|
6586
|
+
const manifestPath = join25(localPath, "biffo.plugin.json");
|
|
6587
|
+
if (!existsSync24(manifestPath)) {
|
|
6471
6588
|
throw new Error(
|
|
6472
6589
|
`${localPath} does not contain a biffo.plugin.json manifest at its root \u2014 is it a plugin directory? (Scaffold one with \`biffo plugin create <name>\`.)`
|
|
6473
6590
|
);
|
|
@@ -6493,8 +6610,8 @@ function parsePluginTarget(target) {
|
|
|
6493
6610
|
async function cloneAndValidatePlugin(entry, git) {
|
|
6494
6611
|
const tmpDir = await git.cloneToTemp(entry.repo, `biffo-plugin-${entry.name}`);
|
|
6495
6612
|
try {
|
|
6496
|
-
const manifestPath =
|
|
6497
|
-
if (!
|
|
6613
|
+
const manifestPath = join25(tmpDir, "biffo.plugin.json");
|
|
6614
|
+
if (!existsSync24(manifestPath)) {
|
|
6498
6615
|
throw new Error(
|
|
6499
6616
|
`Plugin repo ${entry.repo} does not contain a biffo.plugin.json manifest at its root.`
|
|
6500
6617
|
);
|
|
@@ -6522,8 +6639,8 @@ async function runPluginInstall(target, options, deps) {
|
|
|
6522
6639
|
`Nothing to install. Pass a registry target (e.g. \`biffo plugin install acme-crm@1.0\`) or a local plugin directory (\`biffo plugin install --local services/acme-crm\`).`
|
|
6523
6640
|
);
|
|
6524
6641
|
}
|
|
6525
|
-
const servicesDir =
|
|
6526
|
-
if (!
|
|
6642
|
+
const servicesDir = join25(options.cwd, "services");
|
|
6643
|
+
if (!existsSync24(servicesDir)) {
|
|
6527
6644
|
throw new Error(
|
|
6528
6645
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
6529
6646
|
);
|
|
@@ -6541,10 +6658,10 @@ async function runPluginInstall(target, options, deps) {
|
|
|
6541
6658
|
}
|
|
6542
6659
|
const pluginName = entry ? entry.name : source.name;
|
|
6543
6660
|
const relTargetDir = pluginDir(pluginName, "third-party");
|
|
6544
|
-
const targetDir =
|
|
6545
|
-
const modulesDir =
|
|
6661
|
+
const targetDir = join25(options.cwd, relTargetDir);
|
|
6662
|
+
const modulesDir = join25(options.cwd, "modules", "plugins", pluginName);
|
|
6546
6663
|
const inTreeSource = options.local !== void 0 && resolve12(options.local) === resolve12(targetDir);
|
|
6547
|
-
if (
|
|
6664
|
+
if (existsSync24(targetDir) && !inTreeSource) {
|
|
6548
6665
|
throw new Error(
|
|
6549
6666
|
`Plugin '${pluginName}' is already installed at ${relTargetDir}/. Remove it first, or wait for a future 'biffo plugin upgrade' command.`
|
|
6550
6667
|
);
|
|
@@ -6587,8 +6704,8 @@ async function runPluginInstall(target, options, deps) {
|
|
|
6587
6704
|
log.success(`Installed plugin source at ${relTargetDir}/`);
|
|
6588
6705
|
}
|
|
6589
6706
|
const stagePaths = [relTargetDir];
|
|
6590
|
-
const tfSourceDir =
|
|
6591
|
-
if (
|
|
6707
|
+
const tfSourceDir = join25(targetDir, "terraform");
|
|
6708
|
+
if (existsSync24(tfSourceDir)) {
|
|
6592
6709
|
mkdirSync9(modulesDir, { recursive: true });
|
|
6593
6710
|
cpSync3(tfSourceDir, modulesDir, { recursive: true });
|
|
6594
6711
|
stagePaths.push(`modules/plugins/${pluginName}`);
|
|
@@ -6645,7 +6762,7 @@ async function runPluginInstall(target, options, deps) {
|
|
|
6645
6762
|
}
|
|
6646
6763
|
function parseManifestFile(path) {
|
|
6647
6764
|
try {
|
|
6648
|
-
return JSON.parse(
|
|
6765
|
+
return JSON.parse(readFileSync18(path, "utf8"));
|
|
6649
6766
|
} catch (err) {
|
|
6650
6767
|
throw new Error(`Could not parse ${path} as JSON: ${err.message}`);
|
|
6651
6768
|
}
|
|
@@ -6682,8 +6799,8 @@ function printDryRun4(entry, source, relTargetDir, inTreeSource) {
|
|
|
6682
6799
|
}
|
|
6683
6800
|
|
|
6684
6801
|
// src/commands/plugin-list.ts
|
|
6685
|
-
import { existsSync as
|
|
6686
|
-
import { join as
|
|
6802
|
+
import { existsSync as existsSync25, readFileSync as readFileSync19 } from "fs";
|
|
6803
|
+
import { join as join26, resolve as resolve13 } from "path";
|
|
6687
6804
|
import chalk16 from "chalk";
|
|
6688
6805
|
import { Command as Command16 } from "commander";
|
|
6689
6806
|
var pluginListCommand = new Command16("list").description("List plugins installed in this project checkout").option("--cwd <path>", "Project root to scan (defaults to the current directory)").action(async (options) => {
|
|
@@ -6696,8 +6813,8 @@ var pluginListCommand = new Command16("list").description("List plugins installe
|
|
|
6696
6813
|
}
|
|
6697
6814
|
});
|
|
6698
6815
|
async function runPluginList(options) {
|
|
6699
|
-
const servicesDir =
|
|
6700
|
-
if (!
|
|
6816
|
+
const servicesDir = join26(options.cwd, "services");
|
|
6817
|
+
if (!existsSync25(servicesDir)) {
|
|
6701
6818
|
throw new Error(
|
|
6702
6819
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
6703
6820
|
);
|
|
@@ -6705,7 +6822,7 @@ async function runPluginList(options) {
|
|
|
6705
6822
|
const plugins = [];
|
|
6706
6823
|
for (const location of findInstalledPlugins(options.cwd)) {
|
|
6707
6824
|
try {
|
|
6708
|
-
const manifest = validateManifest(JSON.parse(
|
|
6825
|
+
const manifest = validateManifest(JSON.parse(readFileSync19(location.manifestPath, "utf8")));
|
|
6709
6826
|
plugins.push({
|
|
6710
6827
|
name: manifest.name,
|
|
6711
6828
|
version: manifest.version,
|
|
@@ -6742,8 +6859,8 @@ async function runPluginList(options) {
|
|
|
6742
6859
|
}
|
|
6743
6860
|
|
|
6744
6861
|
// src/commands/plugin-sync-migrations.ts
|
|
6745
|
-
import { existsSync as
|
|
6746
|
-
import { join as
|
|
6862
|
+
import { existsSync as existsSync26 } from "fs";
|
|
6863
|
+
import { join as join27, relative as relative4, resolve as resolve14 } from "path";
|
|
6747
6864
|
import chalk17 from "chalk";
|
|
6748
6865
|
import { Command as Command17 } from "commander";
|
|
6749
6866
|
var pluginSyncMigrationsCommand = new Command17("sync-migrations").description(
|
|
@@ -6764,11 +6881,11 @@ var pluginSyncMigrationsCommand = new Command17("sync-migrations").description(
|
|
|
6764
6881
|
}
|
|
6765
6882
|
);
|
|
6766
6883
|
async function runPluginSyncMigrations(name, options, deps) {
|
|
6767
|
-
const servicesDir =
|
|
6768
|
-
if (!
|
|
6884
|
+
const servicesDir = join27(options.cwd, "services");
|
|
6885
|
+
if (!existsSync26(servicesDir)) {
|
|
6769
6886
|
throw new Error(`${servicesDir} does not exist \u2014 is ${options.cwd} a Biffo project checkout?`);
|
|
6770
6887
|
}
|
|
6771
|
-
if (name && !
|
|
6888
|
+
if (name && !existsSync26(join27(servicesDir, name, "biffo.plugin.json"))) {
|
|
6772
6889
|
throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
|
|
6773
6890
|
}
|
|
6774
6891
|
if (options.dryRun) {
|
|
@@ -6804,8 +6921,8 @@ async function runPluginSyncMigrations(name, options, deps) {
|
|
|
6804
6921
|
}
|
|
6805
6922
|
|
|
6806
6923
|
// src/commands/plugin-uninstall.ts
|
|
6807
|
-
import { existsSync as
|
|
6808
|
-
import { join as
|
|
6924
|
+
import { existsSync as existsSync27, readFileSync as readFileSync20, rmSync as rmSync8 } from "fs";
|
|
6925
|
+
import { join as join28, resolve as resolve15 } from "path";
|
|
6809
6926
|
import chalk18 from "chalk";
|
|
6810
6927
|
import { Command as Command18 } from "commander";
|
|
6811
6928
|
import inquirer6 from "inquirer";
|
|
@@ -6837,16 +6954,16 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
6837
6954
|
if (!NAME_PATTERN2.test(name)) {
|
|
6838
6955
|
throw new Error(`Invalid plugin name '${name}'. Expected a lowercase kebab-case slug.`);
|
|
6839
6956
|
}
|
|
6840
|
-
const servicesDir =
|
|
6841
|
-
if (!
|
|
6957
|
+
const servicesDir = join28(options.cwd, "services");
|
|
6958
|
+
if (!existsSync27(servicesDir)) {
|
|
6842
6959
|
throw new Error(
|
|
6843
6960
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
6844
6961
|
);
|
|
6845
6962
|
}
|
|
6846
|
-
const targetDir =
|
|
6847
|
-
if (!
|
|
6848
|
-
const firstParty =
|
|
6849
|
-
if (
|
|
6963
|
+
const targetDir = join28(servicesDir, name);
|
|
6964
|
+
if (!existsSync27(targetDir)) {
|
|
6965
|
+
const firstParty = join28(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
|
|
6966
|
+
if (existsSync27(firstParty)) {
|
|
6850
6967
|
throw new Error(
|
|
6851
6968
|
`Plugin '${name}' is a first-party plugin at ${pluginDir(name, "first-party")}/, which is template-owned \u2014 \`biffo core upgrade\` would restore it on the next upgrade. Disable it instead by removing '${name}' from \`enabled_plugins\` in infra/environments/<env>/main.tf and re-applying.`
|
|
6852
6969
|
);
|
|
@@ -6854,9 +6971,9 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
6854
6971
|
throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
|
|
6855
6972
|
}
|
|
6856
6973
|
const version = readInstalledVersion(targetDir);
|
|
6857
|
-
const modulesDir =
|
|
6974
|
+
const modulesDir = join28(options.cwd, "modules", "plugins", name);
|
|
6858
6975
|
const stagePaths = [`services/${name}`];
|
|
6859
|
-
if (
|
|
6976
|
+
if (existsSync27(modulesDir)) {
|
|
6860
6977
|
stagePaths.push(`modules/plugins/${name}`);
|
|
6861
6978
|
}
|
|
6862
6979
|
if (options.dryRun) {
|
|
@@ -6878,7 +6995,7 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
6878
6995
|
}
|
|
6879
6996
|
rmSync8(targetDir, { recursive: true, force: true });
|
|
6880
6997
|
log.success(`Removed services/${name}/`);
|
|
6881
|
-
if (
|
|
6998
|
+
if (existsSync27(modulesDir)) {
|
|
6882
6999
|
rmSync8(modulesDir, { recursive: true, force: true });
|
|
6883
7000
|
log.success(`Removed modules/plugins/${name}/`);
|
|
6884
7001
|
const wiring = syncPluginTerraform(options.cwd);
|
|
@@ -6915,10 +7032,10 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
6915
7032
|
}
|
|
6916
7033
|
}
|
|
6917
7034
|
function readInstalledVersion(targetDir) {
|
|
6918
|
-
const manifestPath =
|
|
6919
|
-
if (!
|
|
7035
|
+
const manifestPath = join28(targetDir, "biffo.plugin.json");
|
|
7036
|
+
if (!existsSync27(manifestPath)) return void 0;
|
|
6920
7037
|
try {
|
|
6921
|
-
return validateManifest(JSON.parse(
|
|
7038
|
+
return validateManifest(JSON.parse(readFileSync20(manifestPath, "utf8"))).version;
|
|
6922
7039
|
} catch {
|
|
6923
7040
|
return void 0;
|
|
6924
7041
|
}
|
|
@@ -6951,8 +7068,8 @@ function printDryRun5(name, version, stagePaths, keepData) {
|
|
|
6951
7068
|
}
|
|
6952
7069
|
|
|
6953
7070
|
// src/commands/plugin-upgrade.ts
|
|
6954
|
-
import { cpSync as cpSync4, existsSync as
|
|
6955
|
-
import { join as
|
|
7071
|
+
import { cpSync as cpSync4, existsSync as existsSync28, mkdirSync as mkdirSync10, readFileSync as readFileSync21, rmSync as rmSync9 } from "fs";
|
|
7072
|
+
import { join as join29, relative as relative5, resolve as resolve16 } from "path";
|
|
6956
7073
|
import chalk19 from "chalk";
|
|
6957
7074
|
import { Command as Command19 } from "commander";
|
|
6958
7075
|
import inquirer7 from "inquirer";
|
|
@@ -6977,14 +7094,14 @@ var pluginUpgradeCommand = new Command19("upgrade").description(
|
|
|
6977
7094
|
});
|
|
6978
7095
|
async function runPluginUpgrade(target, options, deps) {
|
|
6979
7096
|
const { name, minor } = parsePluginTarget(target);
|
|
6980
|
-
const servicesDir =
|
|
6981
|
-
if (!
|
|
7097
|
+
const servicesDir = join29(options.cwd, "services");
|
|
7098
|
+
if (!existsSync28(servicesDir)) {
|
|
6982
7099
|
throw new Error(
|
|
6983
7100
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
6984
7101
|
);
|
|
6985
7102
|
}
|
|
6986
|
-
const targetDir =
|
|
6987
|
-
if (!
|
|
7103
|
+
const targetDir = join29(servicesDir, name);
|
|
7104
|
+
if (!existsSync28(targetDir)) {
|
|
6988
7105
|
throw new Error(
|
|
6989
7106
|
`Plugin '${name}' is not installed at services/${name}/. Use 'biffo plugin install ${name}@${minor}' instead.`
|
|
6990
7107
|
);
|
|
@@ -6998,7 +7115,7 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
6998
7115
|
`Plugin declares required_core_version '${entry.required_core_version}'. The CLI cannot verify this against your deployment \u2014 the Core API exposes no version endpoint and services/api/pyproject.toml's version is a static placeholder, not a real release marker. Confirm compatibility yourself before deploying.`
|
|
6999
7116
|
);
|
|
7000
7117
|
}
|
|
7001
|
-
const modulesDir =
|
|
7118
|
+
const modulesDir = join29(options.cwd, "modules", "plugins", entry.name);
|
|
7002
7119
|
if (options.dryRun) {
|
|
7003
7120
|
printDryRun6(entry, currentVersion);
|
|
7004
7121
|
return;
|
|
@@ -7031,11 +7148,11 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
7031
7148
|
cpSync4(tmpDir, targetDir, { recursive: true });
|
|
7032
7149
|
log.success(`Upgraded plugin source at services/${entry.name}/`);
|
|
7033
7150
|
const stagePaths = [`services/${entry.name}`];
|
|
7034
|
-
if (
|
|
7151
|
+
if (existsSync28(modulesDir)) {
|
|
7035
7152
|
rmSync9(modulesDir, { recursive: true, force: true });
|
|
7036
7153
|
}
|
|
7037
|
-
const tfSourceDir =
|
|
7038
|
-
if (
|
|
7154
|
+
const tfSourceDir = join29(targetDir, "terraform");
|
|
7155
|
+
if (existsSync28(tfSourceDir)) {
|
|
7039
7156
|
mkdirSync10(modulesDir, { recursive: true });
|
|
7040
7157
|
cpSync4(tfSourceDir, modulesDir, { recursive: true });
|
|
7041
7158
|
stagePaths.push(`modules/plugins/${entry.name}`);
|
|
@@ -7069,10 +7186,10 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
7069
7186
|
}
|
|
7070
7187
|
}
|
|
7071
7188
|
function readInstalledVersion2(targetDir) {
|
|
7072
|
-
const manifestPath =
|
|
7073
|
-
if (!
|
|
7189
|
+
const manifestPath = join29(targetDir, "biffo.plugin.json");
|
|
7190
|
+
if (!existsSync28(manifestPath)) return void 0;
|
|
7074
7191
|
try {
|
|
7075
|
-
return validateManifest(JSON.parse(
|
|
7192
|
+
return validateManifest(JSON.parse(readFileSync21(manifestPath, "utf8"))).version;
|
|
7076
7193
|
} catch {
|
|
7077
7194
|
return void 0;
|
|
7078
7195
|
}
|
|
@@ -7122,90 +7239,6 @@ import { Command as Command22 } from "commander";
|
|
|
7122
7239
|
|
|
7123
7240
|
// src/scripts/check-core-ownership.ts
|
|
7124
7241
|
import { execa as execa5 } from "execa";
|
|
7125
|
-
|
|
7126
|
-
// src/lib/core-ownership-guard.ts
|
|
7127
|
-
import { existsSync as existsSync28, readFileSync as readFileSync21 } from "fs";
|
|
7128
|
-
import { join as join29 } from "path";
|
|
7129
|
-
import { z as z7 } from "zod";
|
|
7130
|
-
var DIVERGENCE_FILE = "biffo.divergence.json";
|
|
7131
|
-
var DivergenceEntrySchema = z7.object({
|
|
7132
|
-
prefix: z7.string().min(1),
|
|
7133
|
-
reason: z7.string().min(1),
|
|
7134
|
-
upstream: z7.string().min(1)
|
|
7135
|
-
});
|
|
7136
|
-
var DivergenceConfigSchema = z7.object({
|
|
7137
|
-
note: z7.string().optional(),
|
|
7138
|
-
warnOnly: z7.array(DivergenceEntrySchema).default([])
|
|
7139
|
-
});
|
|
7140
|
-
function readDivergenceConfig(repoRoot) {
|
|
7141
|
-
const path = join29(repoRoot, DIVERGENCE_FILE);
|
|
7142
|
-
if (!existsSync28(path)) return { warnOnly: [] };
|
|
7143
|
-
let raw;
|
|
7144
|
-
try {
|
|
7145
|
-
raw = JSON.parse(readFileSync21(path, "utf8"));
|
|
7146
|
-
} catch (err) {
|
|
7147
|
-
throw new Error(`${DIVERGENCE_FILE} is not valid JSON: ${err.message}`);
|
|
7148
|
-
}
|
|
7149
|
-
const parsed = DivergenceConfigSchema.safeParse(raw);
|
|
7150
|
-
if (!parsed.success) {
|
|
7151
|
-
const issues = parsed.error.issues.map((i) => ` ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
7152
|
-
throw new Error(`${DIVERGENCE_FILE} is invalid:
|
|
7153
|
-
${issues}`);
|
|
7154
|
-
}
|
|
7155
|
-
return parsed.data;
|
|
7156
|
-
}
|
|
7157
|
-
function parseDivergenceTrailer(commitMessage) {
|
|
7158
|
-
const body = commitMessage.split("\n").filter((line) => !line.startsWith("#")).join("\n");
|
|
7159
|
-
const match = /^Core-Divergence:[ \t]*(\S.*?)[ \t]*$/m.exec(body);
|
|
7160
|
-
return match?.[1] ?? null;
|
|
7161
|
-
}
|
|
7162
|
-
function resolveBranch(env, gitBranch) {
|
|
7163
|
-
return (env["GITHUB_HEAD_REF"] || env["GITHUB_REF_NAME"] || gitBranch).trim();
|
|
7164
|
-
}
|
|
7165
|
-
function parseNameStatus(stdout) {
|
|
7166
|
-
const changed = [];
|
|
7167
|
-
const deleted = [];
|
|
7168
|
-
for (const line of stdout.split("\n")) {
|
|
7169
|
-
const parts = line.split(" ").filter(Boolean);
|
|
7170
|
-
const status = parts[0];
|
|
7171
|
-
const path = parts[parts.length - 1];
|
|
7172
|
-
if (!status || !path || parts.length < 2) continue;
|
|
7173
|
-
changed.push(path);
|
|
7174
|
-
if (status.startsWith("D")) deleted.push(path);
|
|
7175
|
-
}
|
|
7176
|
-
return { changed, deleted };
|
|
7177
|
-
}
|
|
7178
|
-
function checkCoreOwnership({
|
|
7179
|
-
changedFiles,
|
|
7180
|
-
manifest,
|
|
7181
|
-
isInstance,
|
|
7182
|
-
branch = "",
|
|
7183
|
-
commitMessage = "",
|
|
7184
|
-
warnOnly = []
|
|
7185
|
-
}) {
|
|
7186
|
-
const empty = { blocked: [], warned: [], divergenceReason: null };
|
|
7187
|
-
if (!isInstance) return { skipped: "template", ...empty };
|
|
7188
|
-
if (branch.startsWith(UPGRADE_BRANCH_PREFIX)) return { skipped: "upgrade-branch", ...empty };
|
|
7189
|
-
const templateOwned = changedFiles.filter((f) => isTemplateOwned(f, manifest));
|
|
7190
|
-
const acknowledged = (path) => warnOnly.filter((entry) => path.startsWith(entry.prefix)).reduce(
|
|
7191
|
-
(best, entry) => !best || entry.prefix.length > best.prefix.length ? entry : best,
|
|
7192
|
-
void 0
|
|
7193
|
-
);
|
|
7194
|
-
const warned = [];
|
|
7195
|
-
const offending = [];
|
|
7196
|
-
for (const path of templateOwned) {
|
|
7197
|
-
const entry = acknowledged(path);
|
|
7198
|
-
if (entry) warned.push({ path, entry });
|
|
7199
|
-
else offending.push(path);
|
|
7200
|
-
}
|
|
7201
|
-
const divergenceReason = parseDivergenceTrailer(commitMessage);
|
|
7202
|
-
if (offending.length > 0 && divergenceReason !== null) {
|
|
7203
|
-
return { skipped: "divergence-trailer", blocked: [], warned, divergenceReason };
|
|
7204
|
-
}
|
|
7205
|
-
return { skipped: null, blocked: offending, warned, divergenceReason: null };
|
|
7206
|
-
}
|
|
7207
|
-
|
|
7208
|
-
// src/scripts/check-core-ownership.ts
|
|
7209
7242
|
var BOLD = "\x1B[1m";
|
|
7210
7243
|
var DIM = "\x1B[2m";
|
|
7211
7244
|
var RED = "\x1B[31m";
|