@biffo/cli 0.56.1 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core.version +1 -1
- package/dist/index.js +304 -214
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -337,7 +337,7 @@ async function runCoreStatus(options) {
|
|
|
337
337
|
|
|
338
338
|
// src/commands/core-upgrade.ts
|
|
339
339
|
import { execSync as execSync2 } from "child_process";
|
|
340
|
-
import { join as
|
|
340
|
+
import { join as join11, resolve as resolve3 } from "path";
|
|
341
341
|
import chalk4 from "chalk";
|
|
342
342
|
import { execa as execa3 } from "execa";
|
|
343
343
|
import { Command as Command3 } from "commander";
|
|
@@ -1628,6 +1628,45 @@ function materializeTemplateAtTag(repo, version, git = defaultGit) {
|
|
|
1628
1628
|
return { dir, cleanup: () => rmSync3(dir, { recursive: true, force: true }) };
|
|
1629
1629
|
}
|
|
1630
1630
|
|
|
1631
|
+
// src/lib/breaking-changes.ts
|
|
1632
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
1633
|
+
import { join as join8 } from "path";
|
|
1634
|
+
var UPGRADE_GUIDE_PATH = "docs/guides/core-upgrade.md";
|
|
1635
|
+
var SECTION_HEADING = "## Breaking changes by version";
|
|
1636
|
+
var ENTRY_HEADING = /^###\s+(\d+\.\d+\.\d+)\s*[—-]\s*(.+?)\s*$/;
|
|
1637
|
+
function parseBreakingChanges(guide) {
|
|
1638
|
+
const lines = guide.split("\n");
|
|
1639
|
+
const start = lines.findIndex((l) => l.trim() === SECTION_HEADING);
|
|
1640
|
+
if (start === -1) return [];
|
|
1641
|
+
const entries = [];
|
|
1642
|
+
let current = null;
|
|
1643
|
+
for (const line of lines.slice(start + 1)) {
|
|
1644
|
+
if (/^##\s/.test(line) && !/^###/.test(line)) break;
|
|
1645
|
+
const match = ENTRY_HEADING.exec(line);
|
|
1646
|
+
if (match?.[1] && match[2]) {
|
|
1647
|
+
if (current) entries.push({ ...current, body: current.body.trim() });
|
|
1648
|
+
current = { version: match[1], title: match[2], body: "" };
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
if (current) current.body += `${line}
|
|
1652
|
+
`;
|
|
1653
|
+
}
|
|
1654
|
+
if (current) entries.push({ ...current, body: current.body.trim() });
|
|
1655
|
+
return entries;
|
|
1656
|
+
}
|
|
1657
|
+
function readBreakingChanges(templateRoot) {
|
|
1658
|
+
const path = join8(templateRoot, UPGRADE_GUIDE_PATH);
|
|
1659
|
+
if (!existsSync6(path)) return [];
|
|
1660
|
+
return parseBreakingChanges(readFileSync5(path, "utf8"));
|
|
1661
|
+
}
|
|
1662
|
+
function breakingChangesBetween(from, to, entries) {
|
|
1663
|
+
parseCoreVersion(from);
|
|
1664
|
+
parseCoreVersion(to);
|
|
1665
|
+
return entries.filter(
|
|
1666
|
+
(e) => compareCoreVersions(e.version, from) > 0 && compareCoreVersions(e.version, to) <= 0
|
|
1667
|
+
).sort((a, b) => compareCoreVersions(a.version, b.version));
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1631
1670
|
// src/lib/global-workflows.ts
|
|
1632
1671
|
var GLOBAL_DISPATCH_REF = "main";
|
|
1633
1672
|
var GLOBAL_DISPATCH_WORKFLOW_PATHS = [
|
|
@@ -1635,8 +1674,8 @@ var GLOBAL_DISPATCH_WORKFLOW_PATHS = [
|
|
|
1635
1674
|
];
|
|
1636
1675
|
|
|
1637
1676
|
// src/lib/plugin-terraform-wiring.ts
|
|
1638
|
-
import { existsSync as
|
|
1639
|
-
import { join as
|
|
1677
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync as readdirSync3, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
1678
|
+
import { join as join9 } from "path";
|
|
1640
1679
|
var TEMPLATE_MODULE_DIR = "_template";
|
|
1641
1680
|
var DEFAULT_PLUGIN_HANDLER = "src.lambda.main.handler";
|
|
1642
1681
|
var GENERATED_TF_FILE = "plugins.generated.tf";
|
|
@@ -1654,7 +1693,7 @@ function standardArguments(pluginName, handler) {
|
|
|
1654
1693
|
];
|
|
1655
1694
|
}
|
|
1656
1695
|
function listPluginModules(cwd) {
|
|
1657
|
-
const dir =
|
|
1696
|
+
const dir = join9(cwd, "modules", "plugins");
|
|
1658
1697
|
let entries;
|
|
1659
1698
|
try {
|
|
1660
1699
|
entries = readdirSync3(dir, { withFileTypes: true });
|
|
@@ -1666,7 +1705,7 @@ function listPluginModules(cwd) {
|
|
|
1666
1705
|
var FIRST_PARTY_TERRAFORM = (name) => `../../../services/_plugins/${name}/terraform`;
|
|
1667
1706
|
var THIRD_PARTY_TERRAFORM = (name) => `../../../modules/plugins/${name}`;
|
|
1668
1707
|
function isFirstPartyPlugin(cwd, name) {
|
|
1669
|
-
return
|
|
1708
|
+
return existsSync7(join9(cwd, "services", "_plugins", name, "terraform", "main.tf"));
|
|
1670
1709
|
}
|
|
1671
1710
|
function pluginModuleSource(cwd, name) {
|
|
1672
1711
|
return isFirstPartyPlugin(cwd, name) ? FIRST_PARTY_TERRAFORM(name) : THIRD_PARTY_TERRAFORM(name);
|
|
@@ -1677,7 +1716,7 @@ function listWireablePlugins(cwd) {
|
|
|
1677
1716
|
return [.../* @__PURE__ */ new Set([...copied, ...firstParty])].sort();
|
|
1678
1717
|
}
|
|
1679
1718
|
function firstPartyPluginNames(cwd) {
|
|
1680
|
-
const dir =
|
|
1719
|
+
const dir = join9(cwd, "services", "_plugins");
|
|
1681
1720
|
let entries;
|
|
1682
1721
|
try {
|
|
1683
1722
|
entries = readdirSync3(dir, { withFileTypes: true });
|
|
@@ -1691,7 +1730,7 @@ function staleFirstPartyCopies(cwd) {
|
|
|
1691
1730
|
return firstPartyPluginNames(cwd).filter((name) => copied.has(name));
|
|
1692
1731
|
}
|
|
1693
1732
|
function listEnvironments(cwd) {
|
|
1694
|
-
const dir =
|
|
1733
|
+
const dir = join9(cwd, "infra", "environments");
|
|
1695
1734
|
let entries;
|
|
1696
1735
|
try {
|
|
1697
1736
|
entries = readdirSync3(dir, { withFileTypes: true });
|
|
@@ -1699,12 +1738,12 @@ function listEnvironments(cwd) {
|
|
|
1699
1738
|
return [];
|
|
1700
1739
|
}
|
|
1701
1740
|
return entries.filter((e) => {
|
|
1702
|
-
if (!e.isDirectory() || !
|
|
1703
|
-
return declaredVariables(
|
|
1741
|
+
if (!e.isDirectory() || !existsSync7(join9(dir, e.name, "main.tf"))) return false;
|
|
1742
|
+
return declaredVariables(join9(dir, e.name)).has("enabled_plugins");
|
|
1704
1743
|
}).map((e) => e.name).sort();
|
|
1705
1744
|
}
|
|
1706
1745
|
function listUnwirableEnvironments(cwd) {
|
|
1707
|
-
const dir =
|
|
1746
|
+
const dir = join9(cwd, "infra", "environments");
|
|
1708
1747
|
let entries;
|
|
1709
1748
|
try {
|
|
1710
1749
|
entries = readdirSync3(dir, { withFileTypes: true });
|
|
@@ -1712,7 +1751,7 @@ function listUnwirableEnvironments(cwd) {
|
|
|
1712
1751
|
return [];
|
|
1713
1752
|
}
|
|
1714
1753
|
return entries.filter(
|
|
1715
|
-
(e) => e.isDirectory() &&
|
|
1754
|
+
(e) => e.isDirectory() && existsSync7(join9(dir, e.name, "main.tf")) && !declaredVariables(join9(dir, e.name)).has("enabled_plugins")
|
|
1716
1755
|
).map((e) => e.name).sort();
|
|
1717
1756
|
}
|
|
1718
1757
|
function declaredVariables(moduleDir) {
|
|
@@ -1727,7 +1766,7 @@ function declaredVariables(moduleDir) {
|
|
|
1727
1766
|
if (!entry.isFile() || !entry.name.endsWith(".tf")) continue;
|
|
1728
1767
|
let contents;
|
|
1729
1768
|
try {
|
|
1730
|
-
contents =
|
|
1769
|
+
contents = readFileSync6(join9(moduleDir, entry.name), "utf8");
|
|
1731
1770
|
} catch {
|
|
1732
1771
|
continue;
|
|
1733
1772
|
}
|
|
@@ -1804,7 +1843,7 @@ function syncPluginTerraform(cwd) {
|
|
|
1804
1843
|
const changedPaths = [];
|
|
1805
1844
|
const rendered = plugins.map((name) => {
|
|
1806
1845
|
const firstParty = isFirstPartyPlugin(cwd, name);
|
|
1807
|
-
const moduleDir = firstParty ?
|
|
1846
|
+
const moduleDir = firstParty ? join9(cwd, "services", "_plugins", name, "terraform") : join9(cwd, "modules", "plugins", name);
|
|
1808
1847
|
return {
|
|
1809
1848
|
name,
|
|
1810
1849
|
declaredVariables: declaredVariables(moduleDir),
|
|
@@ -1812,16 +1851,16 @@ function syncPluginTerraform(cwd) {
|
|
|
1812
1851
|
};
|
|
1813
1852
|
});
|
|
1814
1853
|
for (const env of environments) {
|
|
1815
|
-
const envDir =
|
|
1816
|
-
const tfPath =
|
|
1817
|
-
const tfvarsPath =
|
|
1854
|
+
const envDir = join9(cwd, "infra", "environments", env);
|
|
1855
|
+
const tfPath = join9(envDir, GENERATED_TF_FILE);
|
|
1856
|
+
const tfvarsPath = join9(envDir, GENERATED_TFVARS_FILE);
|
|
1818
1857
|
const relBase = `infra/environments/${env}`;
|
|
1819
1858
|
if (plugins.length === 0) {
|
|
1820
1859
|
for (const [abs, rel] of [
|
|
1821
1860
|
[tfPath, `${relBase}/${GENERATED_TF_FILE}`],
|
|
1822
1861
|
[tfvarsPath, `${relBase}/${GENERATED_TFVARS_FILE}`]
|
|
1823
1862
|
]) {
|
|
1824
|
-
if (
|
|
1863
|
+
if (existsSync7(abs)) {
|
|
1825
1864
|
rmSync4(abs);
|
|
1826
1865
|
changedPaths.push(rel);
|
|
1827
1866
|
}
|
|
@@ -1837,8 +1876,8 @@ function syncPluginTerraform(cwd) {
|
|
|
1837
1876
|
}
|
|
1838
1877
|
|
|
1839
1878
|
// src/lib/lockfile-refresh.ts
|
|
1840
|
-
import { existsSync as
|
|
1841
|
-
import { join as
|
|
1879
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1880
|
+
import { join as join10 } from "path";
|
|
1842
1881
|
var LOCKFILE_TRIGGERS = [
|
|
1843
1882
|
{
|
|
1844
1883
|
manifest: "package.json",
|
|
@@ -1861,7 +1900,7 @@ function lockfilesNeedingRefresh(changedPaths, instanceDir, triggers = LOCKFILE_
|
|
|
1861
1900
|
const locked = changedPaths.filter((p) => !isForeignManifest(p));
|
|
1862
1901
|
return triggers.filter((t) => {
|
|
1863
1902
|
const touched = locked.some((p) => p === t.manifest || p.endsWith(`/${t.manifest}`));
|
|
1864
|
-
return touched &&
|
|
1903
|
+
return touched && existsSync8(join10(instanceDir, t.lockfile));
|
|
1865
1904
|
});
|
|
1866
1905
|
}
|
|
1867
1906
|
async function refreshLockfiles(instanceDir, triggers, run) {
|
|
@@ -1891,13 +1930,17 @@ var coreUpgradeCommand = new Command3("upgrade").description("Three-way-merge te
|
|
|
1891
1930
|
).option(
|
|
1892
1931
|
"--to-template <path>",
|
|
1893
1932
|
"Override: path to a template checkout at the TARGET version. Normally auto-resolved."
|
|
1894
|
-
).option("--apply", "Apply the plan on a new branch and open a PR (default: dry run)").option("--allow-conflicts", "With --apply, open the PR even if some files conflict").option(
|
|
1933
|
+
).option("--apply", "Apply the plan on a new branch and open a PR (default: dry run)").option("--allow-conflicts", "With --apply, open the PR even if some files conflict").option(
|
|
1934
|
+
"--acknowledge-breaking",
|
|
1935
|
+
"With --apply, proceed even though this upgrade crosses a documented breaking change"
|
|
1936
|
+
).option("--base <branch>", "Base branch for the PR (defaults to the current branch)").option("--remote <name>", "Git remote to push to and open the PR on (default: origin)").action(
|
|
1895
1937
|
async (options) => {
|
|
1896
1938
|
const cwd = options.cwd ? resolve3(options.cwd) : process.cwd();
|
|
1897
1939
|
const runOptions = {
|
|
1898
1940
|
cwd,
|
|
1899
1941
|
apply: options.apply ?? false,
|
|
1900
|
-
allowConflicts: options.allowConflicts ?? false
|
|
1942
|
+
allowConflicts: options.allowConflicts ?? false,
|
|
1943
|
+
acknowledgeBreaking: options.acknowledgeBreaking ?? false
|
|
1901
1944
|
};
|
|
1902
1945
|
if (options.templateRepo) runOptions.templateRepo = resolve3(options.templateRepo);
|
|
1903
1946
|
if (options.to) runOptions.toVersion = options.to;
|
|
@@ -1950,9 +1993,9 @@ async function runCoreUpgradeResolved(options, deps, cleanups) {
|
|
|
1950
1993
|
let toVersion;
|
|
1951
1994
|
if (options.theirsDir) {
|
|
1952
1995
|
theirsDir = options.theirsDir;
|
|
1953
|
-
toVersion = readCoreVersionFile(
|
|
1996
|
+
toVersion = readCoreVersionFile(join11(theirsDir, "core.version"));
|
|
1954
1997
|
} else {
|
|
1955
|
-
const workingVersion = readCoreVersionFile(
|
|
1998
|
+
const workingVersion = readCoreVersionFile(join11(templateRepo, "core.version"));
|
|
1956
1999
|
toVersion = options.toVersion ?? workingVersion;
|
|
1957
2000
|
if (toVersion === workingVersion) {
|
|
1958
2001
|
theirsDir = templateRepo;
|
|
@@ -1966,7 +2009,7 @@ async function runCoreUpgradeResolved(options, deps, cleanups) {
|
|
|
1966
2009
|
let fromVersion;
|
|
1967
2010
|
if (options.baseDir) {
|
|
1968
2011
|
baseDir = options.baseDir;
|
|
1969
|
-
fromVersion = readCoreVersionFile(
|
|
2012
|
+
fromVersion = readCoreVersionFile(join11(baseDir, "core.version"));
|
|
1970
2013
|
} else {
|
|
1971
2014
|
if (instanceVersion === null) {
|
|
1972
2015
|
throw new Error(
|
|
@@ -1998,6 +2041,14 @@ async function runCoreUpgradeResolved(options, deps, cleanups) {
|
|
|
1998
2041
|
log.success("Nothing to upgrade \u2014 the instance already matches the target for all core files.");
|
|
1999
2042
|
return;
|
|
2000
2043
|
}
|
|
2044
|
+
const breaking = breakingChangesBetween(
|
|
2045
|
+
fromVersion,
|
|
2046
|
+
toVersion,
|
|
2047
|
+
// From the TEMPLATE at the target version, never the instance's own copy —
|
|
2048
|
+
// docs/ is user-owned, so an instance's guide may be stale or edited.
|
|
2049
|
+
readBreakingChanges(theirsDir)
|
|
2050
|
+
);
|
|
2051
|
+
printBreakingChanges(breaking, options.apply === true);
|
|
2001
2052
|
printPlan(plan);
|
|
2002
2053
|
printMigrationCarry(migrations);
|
|
2003
2054
|
warnStaleFirstPartyCopies(options.cwd);
|
|
@@ -2014,9 +2065,14 @@ async function runCoreUpgradeResolved(options, deps, cleanups) {
|
|
|
2014
2065
|
);
|
|
2015
2066
|
return;
|
|
2016
2067
|
}
|
|
2017
|
-
await applyAndOpenPr(options, deps, plan, migrations, fromVersion, toVersion);
|
|
2068
|
+
await applyAndOpenPr(options, deps, plan, migrations, fromVersion, toVersion, breaking);
|
|
2018
2069
|
}
|
|
2019
|
-
async function applyAndOpenPr(options, deps, plan, migrations, fromVersion, toVersion) {
|
|
2070
|
+
async function applyAndOpenPr(options, deps, plan, migrations, fromVersion, toVersion, breaking) {
|
|
2071
|
+
if (breaking.length > 0 && !options.acknowledgeBreaking) {
|
|
2072
|
+
throw new Error(
|
|
2073
|
+
`This upgrade crosses ${breaking.length} documented breaking change(s): ${breaking.map((b) => b.version).join(", ")}. They are printed above and in ${UPGRADE_GUIDE_PATH}. Read what each one requires \u2014 some destroy data or need manual work after the deploy \u2014 then re-run with --acknowledge-breaking.`
|
|
2074
|
+
);
|
|
2075
|
+
}
|
|
2020
2076
|
if (plan.conflicts.length > 0 && !options.allowConflicts) {
|
|
2021
2077
|
throw new Error(
|
|
2022
2078
|
`${plan.conflicts.length} file(s) conflict. Resolve them upstream, or re-run with --allow-conflicts to open a PR that includes the conflict markers for manual resolution.`
|
|
@@ -2058,7 +2114,7 @@ async function applyAndOpenPr(options, deps, plan, migrations, fromVersion, toVe
|
|
|
2058
2114
|
head: branch,
|
|
2059
2115
|
base,
|
|
2060
2116
|
title: `Upgrade Biffo core ${fromVersion} \u2192 ${toVersion}`,
|
|
2061
|
-
body: buildPrBody(fromVersion, toVersion, plan, migrations, base, lockfiles)
|
|
2117
|
+
body: buildPrBody(fromVersion, toVersion, plan, migrations, base, lockfiles, breaking)
|
|
2062
2118
|
});
|
|
2063
2119
|
if (plan.conflicts.length > 0) {
|
|
2064
2120
|
log.warn(`PR opened with ${plan.conflicts.length} conflict(s) to resolve: ${pr.url}`);
|
|
@@ -2066,8 +2122,20 @@ async function applyAndOpenPr(options, deps, plan, migrations, fromVersion, toVe
|
|
|
2066
2122
|
log.success(`Opened PR #${pr.number}: ${pr.url}`);
|
|
2067
2123
|
}
|
|
2068
2124
|
}
|
|
2069
|
-
function buildPrBody(from, to, plan, migrations, base = GLOBAL_DISPATCH_REF, lockfiles = []) {
|
|
2070
|
-
const lines = [
|
|
2125
|
+
function buildPrBody(from, to, plan, migrations, base = GLOBAL_DISPATCH_REF, lockfiles = [], breaking = []) {
|
|
2126
|
+
const lines = [];
|
|
2127
|
+
if (breaking.length > 0) {
|
|
2128
|
+
lines.push(
|
|
2129
|
+
`## \u26A0 This upgrade crosses ${breaking.length} breaking change(s)`,
|
|
2130
|
+
"",
|
|
2131
|
+
"Read these before merging \u2014 some destroy data or require manual work afterwards.",
|
|
2132
|
+
"",
|
|
2133
|
+
...breaking.flatMap((b) => [`### ${b.version} \u2014 ${b.title}`, "", b.body, ""]),
|
|
2134
|
+
"---",
|
|
2135
|
+
""
|
|
2136
|
+
);
|
|
2137
|
+
}
|
|
2138
|
+
lines.push(
|
|
2071
2139
|
"Automated core upgrade generated by `biffo core upgrade` (ADR-0006).",
|
|
2072
2140
|
"",
|
|
2073
2141
|
`Bumps the Biffo template core from **${from}** to **${to}** and updates \`biffo.core.json\`. Only template-owned paths (see \`core-manifest.json\`) were touched \u2014 product, plugin, and infra files were left untouched.`,
|
|
@@ -2078,7 +2146,7 @@ function buildPrBody(from, to, plan, migrations, base = GLOBAL_DISPATCH_REF, loc
|
|
|
2078
2146
|
`- take-theirs: ${plan.summary["take-theirs"]}`,
|
|
2079
2147
|
`- added: ${plan.summary.added}`,
|
|
2080
2148
|
`- removed: ${plan.summary.removed}`
|
|
2081
|
-
|
|
2149
|
+
);
|
|
2082
2150
|
if (migrations.entries.length > 0) {
|
|
2083
2151
|
lines.push(
|
|
2084
2152
|
"",
|
|
@@ -2203,6 +2271,28 @@ function warnStaleFirstPartyCopies(cwd) {
|
|
|
2203
2271
|
`${stale.length} first-party plugin(s) still have a copy under modules/plugins/: ${stale.join(", ")}. An upgrade never updates those copies, so if infra/environments/*/ still sources them, this plugin's infrastructure changes are NOT deployed. Point the module source at services/_plugins/<name>/terraform and delete the copy \u2014 see docs/guides/core-upgrade.md.`
|
|
2204
2272
|
);
|
|
2205
2273
|
}
|
|
2274
|
+
function printBreakingChanges(breaking, applying) {
|
|
2275
|
+
if (breaking.length === 0) return;
|
|
2276
|
+
console.log(
|
|
2277
|
+
chalk4.red.bold(
|
|
2278
|
+
`
|
|
2279
|
+
\u26A0 This upgrade crosses ${breaking.length} documented breaking change(s):
|
|
2280
|
+
`
|
|
2281
|
+
)
|
|
2282
|
+
);
|
|
2283
|
+
for (const b of breaking) {
|
|
2284
|
+
console.log(` ${chalk4.bold(b.version)} \u2014 ${b.title}`);
|
|
2285
|
+
for (const line of b.body.split("\n").slice(0, 6)) console.log(chalk4.dim(` ${line}`));
|
|
2286
|
+
console.log();
|
|
2287
|
+
}
|
|
2288
|
+
console.log(chalk4.dim(` Full detail: ${UPGRADE_GUIDE_PATH}
|
|
2289
|
+
`));
|
|
2290
|
+
if (!applying) {
|
|
2291
|
+
console.log(
|
|
2292
|
+
chalk4.dim(" Re-run with --apply --acknowledge-breaking once you have read them.\n")
|
|
2293
|
+
);
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2206
2296
|
|
|
2207
2297
|
// src/commands/core.ts
|
|
2208
2298
|
var coreCommand = new Command4("core").description(
|
|
@@ -2216,7 +2306,7 @@ coreCommand.addCommand(coreUpgradeCommand);
|
|
|
2216
2306
|
import { Command as Command8 } from "commander";
|
|
2217
2307
|
|
|
2218
2308
|
// src/commands/data-apply.ts
|
|
2219
|
-
import { existsSync as
|
|
2309
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
2220
2310
|
import { resolve as resolve4 } from "path";
|
|
2221
2311
|
import chalk5 from "chalk";
|
|
2222
2312
|
import { Command as Command5 } from "commander";
|
|
@@ -2667,16 +2757,16 @@ function isTemplatePlaceholderConfig(raw) {
|
|
|
2667
2757
|
|
|
2668
2758
|
// src/lib/session.ts
|
|
2669
2759
|
import {
|
|
2670
|
-
existsSync as
|
|
2760
|
+
existsSync as existsSync9,
|
|
2671
2761
|
mkdirSync as mkdirSync4,
|
|
2672
2762
|
readdirSync as readdirSync4,
|
|
2673
|
-
readFileSync as
|
|
2763
|
+
readFileSync as readFileSync7,
|
|
2674
2764
|
rmSync as rmSync5,
|
|
2675
2765
|
statSync,
|
|
2676
2766
|
writeFileSync as writeFileSync5
|
|
2677
2767
|
} from "fs";
|
|
2678
2768
|
import { homedir } from "os";
|
|
2679
|
-
import { join as
|
|
2769
|
+
import { join as join12 } from "path";
|
|
2680
2770
|
var LEGACY_STEP_ALIASES = {
|
|
2681
2771
|
github_config: ["github_branches", "github_instance_files", "github_settings"]
|
|
2682
2772
|
};
|
|
@@ -2685,39 +2775,39 @@ function hasCompleted(session, step) {
|
|
|
2685
2775
|
return session.completedSteps.some((done) => LEGACY_STEP_ALIASES[done]?.includes(step) ?? false);
|
|
2686
2776
|
}
|
|
2687
2777
|
function sessionsDir() {
|
|
2688
|
-
return process.env["BIFFO_SESSIONS_DIR"] ??
|
|
2778
|
+
return process.env["BIFFO_SESSIONS_DIR"] ?? join12(homedir(), ".biffo", "sessions");
|
|
2689
2779
|
}
|
|
2690
2780
|
function sessionPath(projectName) {
|
|
2691
|
-
return
|
|
2781
|
+
return join12(sessionsDir(), `${projectName}.json`);
|
|
2692
2782
|
}
|
|
2693
2783
|
function loadSession(projectName) {
|
|
2694
2784
|
const path = sessionPath(projectName);
|
|
2695
|
-
if (!
|
|
2785
|
+
if (!existsSync9(path)) return null;
|
|
2696
2786
|
try {
|
|
2697
|
-
return JSON.parse(
|
|
2787
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
2698
2788
|
} catch {
|
|
2699
2789
|
return null;
|
|
2700
2790
|
}
|
|
2701
2791
|
}
|
|
2702
2792
|
function findLatestSession() {
|
|
2703
2793
|
const dir = sessionsDir();
|
|
2704
|
-
if (!
|
|
2794
|
+
if (!existsSync9(dir)) return null;
|
|
2705
2795
|
const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
|
|
2706
2796
|
if (files.length === 0) return null;
|
|
2707
2797
|
const sorted = files.map((f) => {
|
|
2708
|
-
const fullPath =
|
|
2709
|
-
const mtime =
|
|
2798
|
+
const fullPath = join12(dir, f);
|
|
2799
|
+
const mtime = existsSync9(fullPath) ? statSync(fullPath).mtimeMs : -1;
|
|
2710
2800
|
return { f, mtime };
|
|
2711
2801
|
}).sort((a, b) => b.mtime - a.mtime);
|
|
2712
2802
|
try {
|
|
2713
|
-
return JSON.parse(
|
|
2803
|
+
return JSON.parse(readFileSync7(join12(dir, sorted[0].f), "utf8"));
|
|
2714
2804
|
} catch {
|
|
2715
2805
|
return null;
|
|
2716
2806
|
}
|
|
2717
2807
|
}
|
|
2718
2808
|
function saveSession(session) {
|
|
2719
2809
|
const dir = sessionsDir();
|
|
2720
|
-
if (!
|
|
2810
|
+
if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
|
|
2721
2811
|
const name = session.config.project?.name ?? "unknown";
|
|
2722
2812
|
const prior = loadSession(name);
|
|
2723
2813
|
if (prior) {
|
|
@@ -2739,36 +2829,36 @@ function markStepComplete(session, step) {
|
|
|
2739
2829
|
}
|
|
2740
2830
|
function deleteSession(projectName) {
|
|
2741
2831
|
const path = sessionPath(projectName);
|
|
2742
|
-
if (
|
|
2832
|
+
if (existsSync9(path)) rmSync5(path);
|
|
2743
2833
|
}
|
|
2744
2834
|
function projectsDir() {
|
|
2745
|
-
return process.env["BIFFO_PROJECTS_DIR"] ??
|
|
2835
|
+
return process.env["BIFFO_PROJECTS_DIR"] ?? join12(homedir(), ".biffo", "projects");
|
|
2746
2836
|
}
|
|
2747
2837
|
function saveProjectConfig(config) {
|
|
2748
2838
|
const dir = projectsDir();
|
|
2749
|
-
if (!
|
|
2750
|
-
writeFileSync5(
|
|
2839
|
+
if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
|
|
2840
|
+
writeFileSync5(join12(dir, `${config.project.name}.json`), JSON.stringify(config, null, 2));
|
|
2751
2841
|
}
|
|
2752
2842
|
function loadProjectConfig(name) {
|
|
2753
|
-
const path =
|
|
2754
|
-
if (!
|
|
2843
|
+
const path = join12(projectsDir(), `${name}.json`);
|
|
2844
|
+
if (!existsSync9(path)) return null;
|
|
2755
2845
|
try {
|
|
2756
|
-
const result = BiffoConfigSchema.safeParse(JSON.parse(
|
|
2846
|
+
const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync7(path, "utf8")));
|
|
2757
2847
|
return result.success ? result.data : null;
|
|
2758
2848
|
} catch {
|
|
2759
2849
|
return null;
|
|
2760
2850
|
}
|
|
2761
2851
|
}
|
|
2762
2852
|
function deleteProjectConfig(name) {
|
|
2763
|
-
const path =
|
|
2764
|
-
if (
|
|
2853
|
+
const path = join12(projectsDir(), `${name}.json`);
|
|
2854
|
+
if (existsSync9(path)) rmSync5(path);
|
|
2765
2855
|
}
|
|
2766
2856
|
function listProjectConfigs() {
|
|
2767
2857
|
const dir = projectsDir();
|
|
2768
|
-
if (!
|
|
2858
|
+
if (!existsSync9(dir)) return [];
|
|
2769
2859
|
return readdirSync4(dir).filter((f) => f.endsWith(".json")).flatMap((f) => {
|
|
2770
2860
|
try {
|
|
2771
|
-
const result = BiffoConfigSchema.safeParse(JSON.parse(
|
|
2861
|
+
const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync7(join12(dir, f), "utf8")));
|
|
2772
2862
|
return result.success ? [result.data] : [];
|
|
2773
2863
|
} catch {
|
|
2774
2864
|
return [];
|
|
@@ -2837,7 +2927,7 @@ async function runDataApply(name, environment, config, aws) {
|
|
|
2837
2927
|
}
|
|
2838
2928
|
async function resolveConfig(options) {
|
|
2839
2929
|
if (options.config) {
|
|
2840
|
-
const raw = JSON.parse(
|
|
2930
|
+
const raw = JSON.parse(readFileSync8(resolve4(options.config), "utf8"));
|
|
2841
2931
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
2842
2932
|
if (!result.success) {
|
|
2843
2933
|
log.error(`Invalid config at ${options.config}:`);
|
|
@@ -2857,8 +2947,8 @@ async function resolveConfig(options) {
|
|
|
2857
2947
|
return cfg;
|
|
2858
2948
|
}
|
|
2859
2949
|
const localConfigPath = resolve4(process.cwd(), "biffo.config.json");
|
|
2860
|
-
if (
|
|
2861
|
-
const raw = JSON.parse(
|
|
2950
|
+
if (existsSync10(localConfigPath)) {
|
|
2951
|
+
const raw = JSON.parse(readFileSync8(localConfigPath, "utf8"));
|
|
2862
2952
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
2863
2953
|
if (result.success) return result.data;
|
|
2864
2954
|
if (isTemplatePlaceholderConfig(raw)) {
|
|
@@ -2904,8 +2994,8 @@ async function resolveConfig(options) {
|
|
|
2904
2994
|
|
|
2905
2995
|
// src/commands/data-import.ts
|
|
2906
2996
|
import { execSync as execSync3 } from "child_process";
|
|
2907
|
-
import { cpSync, existsSync as
|
|
2908
|
-
import { join as
|
|
2997
|
+
import { cpSync, existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
|
|
2998
|
+
import { join as join13, resolve as resolve5 } from "path";
|
|
2909
2999
|
import chalk6 from "chalk";
|
|
2910
3000
|
import { Command as Command6 } from "commander";
|
|
2911
3001
|
import inquirer2 from "inquirer";
|
|
@@ -2945,23 +3035,23 @@ async function runDataImport(name, options, deps) {
|
|
|
2945
3035
|
`Invalid import name '${name}'. Use lowercase letters, numbers, and hyphens, starting with a letter.`
|
|
2946
3036
|
);
|
|
2947
3037
|
}
|
|
2948
|
-
const servicesDir =
|
|
2949
|
-
if (!
|
|
3038
|
+
const servicesDir = join13(options.cwd, "services");
|
|
3039
|
+
if (!existsSync11(servicesDir)) {
|
|
2950
3040
|
throw new Error(
|
|
2951
3041
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
2952
3042
|
);
|
|
2953
3043
|
}
|
|
2954
|
-
const targetDir =
|
|
2955
|
-
if (
|
|
3044
|
+
const targetDir = join13(options.cwd, "db", "imports", name);
|
|
3045
|
+
if (existsSync11(targetDir)) {
|
|
2956
3046
|
throw new Error(
|
|
2957
3047
|
`DDL import '${name}' is already present at db/imports/${name}/. Remove it first to re-import.`
|
|
2958
3048
|
);
|
|
2959
3049
|
}
|
|
2960
|
-
const isLocalDir =
|
|
3050
|
+
const isLocalDir = existsSync11(options.source) && statSync2(options.source).isDirectory();
|
|
2961
3051
|
let sourceDir;
|
|
2962
3052
|
let cleanupClone = null;
|
|
2963
3053
|
if (isLocalDir) {
|
|
2964
|
-
sourceDir = options.path ?
|
|
3054
|
+
sourceDir = options.path ? join13(options.source, options.path) : options.source;
|
|
2965
3055
|
} else {
|
|
2966
3056
|
const token = options.token ?? await resolveDdlImportToken();
|
|
2967
3057
|
log.info(`Cloning ${options.source}...`);
|
|
@@ -2969,10 +3059,10 @@ async function runDataImport(name, options, deps) {
|
|
|
2969
3059
|
cleanupClone = () => {
|
|
2970
3060
|
deps.git.cleanup(tmpDir);
|
|
2971
3061
|
};
|
|
2972
|
-
sourceDir = options.path ?
|
|
3062
|
+
sourceDir = options.path ? join13(tmpDir, options.path) : tmpDir;
|
|
2973
3063
|
}
|
|
2974
3064
|
try {
|
|
2975
|
-
if (!
|
|
3065
|
+
if (!existsSync11(sourceDir)) {
|
|
2976
3066
|
throw new Error(`Source directory does not exist: ${sourceDir}`);
|
|
2977
3067
|
}
|
|
2978
3068
|
const sqlFiles = readdirSync5(sourceDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".sql")).map((entry) => entry.name).sort();
|
|
@@ -2997,7 +3087,7 @@ async function runDataImport(name, options, deps) {
|
|
|
2997
3087
|
}
|
|
2998
3088
|
mkdirSync5(targetDir, { recursive: true });
|
|
2999
3089
|
for (const file of sqlFiles) {
|
|
3000
|
-
cpSync(
|
|
3090
|
+
cpSync(join13(sourceDir, file), join13(targetDir, file));
|
|
3001
3091
|
}
|
|
3002
3092
|
log.success(`Imported ${String(sqlFiles.length)} .sql file(s) to db/imports/${name}/`);
|
|
3003
3093
|
const commitMessage = `feat(data): import ${name} (${String(sqlFiles.length)} SQL file(s))`;
|
|
@@ -3049,8 +3139,8 @@ function printDryRun(name, sqlFiles) {
|
|
|
3049
3139
|
}
|
|
3050
3140
|
|
|
3051
3141
|
// src/commands/data-list.ts
|
|
3052
|
-
import { existsSync as
|
|
3053
|
-
import { join as
|
|
3142
|
+
import { existsSync as existsSync12, readdirSync as readdirSync6 } from "fs";
|
|
3143
|
+
import { join as join14, resolve as resolve6 } from "path";
|
|
3054
3144
|
import chalk7 from "chalk";
|
|
3055
3145
|
import { Command as Command7 } from "commander";
|
|
3056
3146
|
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) => {
|
|
@@ -3063,15 +3153,15 @@ var dataListCommand = new Command7("list").description("List DDL imports vendore
|
|
|
3063
3153
|
}
|
|
3064
3154
|
});
|
|
3065
3155
|
async function runDataList(options) {
|
|
3066
|
-
const importsDir =
|
|
3067
|
-
if (!
|
|
3156
|
+
const importsDir = join14(options.cwd, "db", "imports");
|
|
3157
|
+
if (!existsSync12(importsDir)) {
|
|
3068
3158
|
console.log(chalk7.dim("\n No DDL imports in this checkout.\n"));
|
|
3069
3159
|
return;
|
|
3070
3160
|
}
|
|
3071
3161
|
const candidates = readdirSync6(importsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
3072
3162
|
const imports = [];
|
|
3073
3163
|
for (const name of candidates) {
|
|
3074
|
-
const fileCount = readdirSync6(
|
|
3164
|
+
const fileCount = readdirSync6(join14(importsDir, name)).filter((f) => f.endsWith(".sql")).length;
|
|
3075
3165
|
if (fileCount > 0) imports.push({ name, fileCount });
|
|
3076
3166
|
}
|
|
3077
3167
|
if (imports.length === 0) {
|
|
@@ -3101,7 +3191,7 @@ dataCommand.addCommand(dataListCommand);
|
|
|
3101
3191
|
|
|
3102
3192
|
// src/commands/deploy.ts
|
|
3103
3193
|
import { execSync as execSync4 } from "child_process";
|
|
3104
|
-
import { existsSync as
|
|
3194
|
+
import { existsSync as existsSync13, readFileSync as readFileSync9 } from "fs";
|
|
3105
3195
|
import { resolve as resolve7 } from "path";
|
|
3106
3196
|
import chalk8 from "chalk";
|
|
3107
3197
|
import { Command as Command9 } from "commander";
|
|
@@ -3455,7 +3545,7 @@ var deployCommand = new Command9("deploy").description("Deploy infrastructure an
|
|
|
3455
3545
|
);
|
|
3456
3546
|
async function resolveConfig2(options) {
|
|
3457
3547
|
if (options.config) {
|
|
3458
|
-
const raw = JSON.parse(
|
|
3548
|
+
const raw = JSON.parse(readFileSync9(resolve7(options.config), "utf8"));
|
|
3459
3549
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
3460
3550
|
if (!result.success) {
|
|
3461
3551
|
log.error(`Invalid config at ${options.config}:`);
|
|
@@ -3475,8 +3565,8 @@ async function resolveConfig2(options) {
|
|
|
3475
3565
|
return cfg;
|
|
3476
3566
|
}
|
|
3477
3567
|
const localConfigPath = resolve7(process.cwd(), "biffo.config.json");
|
|
3478
|
-
if (
|
|
3479
|
-
const raw = JSON.parse(
|
|
3568
|
+
if (existsSync13(localConfigPath)) {
|
|
3569
|
+
const raw = JSON.parse(readFileSync9(localConfigPath, "utf8"));
|
|
3480
3570
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
3481
3571
|
if (result.success) return result.data;
|
|
3482
3572
|
if (isTemplatePlaceholderConfig(raw)) {
|
|
@@ -3860,7 +3950,7 @@ function resolveGithubToken() {
|
|
|
3860
3950
|
|
|
3861
3951
|
// src/commands/destroy.ts
|
|
3862
3952
|
import { execSync as execSync5 } from "child_process";
|
|
3863
|
-
import { readFileSync as
|
|
3953
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
3864
3954
|
import { resolve as resolve8 } from "path";
|
|
3865
3955
|
import chalk9 from "chalk";
|
|
3866
3956
|
import { Command as Command10 } from "commander";
|
|
@@ -3950,7 +4040,7 @@ var destroyCommand = new Command10("destroy").description("Destroy infrastructur
|
|
|
3950
4040
|
});
|
|
3951
4041
|
async function resolveConfig3(options) {
|
|
3952
4042
|
if (options.config) {
|
|
3953
|
-
const raw = JSON.parse(
|
|
4043
|
+
const raw = JSON.parse(readFileSync10(resolve8(options.config), "utf8"));
|
|
3954
4044
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
3955
4045
|
if (!result.success) {
|
|
3956
4046
|
log.error(`Invalid config at ${options.config}:`);
|
|
@@ -3970,7 +4060,7 @@ async function resolveConfig3(options) {
|
|
|
3970
4060
|
return cfg;
|
|
3971
4061
|
}
|
|
3972
4062
|
try {
|
|
3973
|
-
const raw = JSON.parse(
|
|
4063
|
+
const raw = JSON.parse(readFileSync10(resolve8(process.cwd(), "biffo.config.json"), "utf8"));
|
|
3974
4064
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
3975
4065
|
if (result.success) return result.data;
|
|
3976
4066
|
} catch {
|
|
@@ -4020,15 +4110,15 @@ function resolveGithubToken2() {
|
|
|
4020
4110
|
}
|
|
4021
4111
|
|
|
4022
4112
|
// src/commands/init.ts
|
|
4023
|
-
import { readFileSync as
|
|
4113
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
4024
4114
|
import { resolve as resolve10 } from "path";
|
|
4025
4115
|
import chalk12 from "chalk";
|
|
4026
4116
|
import { Command as Command12 } from "commander";
|
|
4027
4117
|
import inquirer5 from "inquirer";
|
|
4028
4118
|
|
|
4029
4119
|
// src/lib/build-freshness.ts
|
|
4030
|
-
import { existsSync as
|
|
4031
|
-
import { dirname as dirname5, join as
|
|
4120
|
+
import { existsSync as existsSync14, readdirSync as readdirSync7, statSync as statSync3 } from "fs";
|
|
4121
|
+
import { dirname as dirname5, join as join15, relative as relative2, sep as sep2 } from "path";
|
|
4032
4122
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4033
4123
|
var SKIP_ENV_VAR = "BIFFO_SKIP_BUILD_FRESHNESS_CHECK";
|
|
4034
4124
|
function checkBuildFreshness(options = {}) {
|
|
@@ -4042,7 +4132,7 @@ function checkBuildFreshness(options = {}) {
|
|
|
4042
4132
|
if (!packageRoot) {
|
|
4043
4133
|
return { status: "skipped", reason: `no package.json above ${moduleDir}`, newerSources: [] };
|
|
4044
4134
|
}
|
|
4045
|
-
const distDir =
|
|
4135
|
+
const distDir = join15(packageRoot, "dist");
|
|
4046
4136
|
if (!isInside(distDir, moduleDir)) {
|
|
4047
4137
|
return {
|
|
4048
4138
|
status: "skipped",
|
|
@@ -4050,16 +4140,16 @@ function checkBuildFreshness(options = {}) {
|
|
|
4050
4140
|
newerSources: []
|
|
4051
4141
|
};
|
|
4052
4142
|
}
|
|
4053
|
-
const srcDir =
|
|
4054
|
-
if (!
|
|
4143
|
+
const srcDir = join15(packageRoot, "src");
|
|
4144
|
+
if (!existsSync14(srcDir)) {
|
|
4055
4145
|
return {
|
|
4056
4146
|
status: "skipped",
|
|
4057
4147
|
reason: "no src/ alongside dist/ \u2014 this is a shipped package",
|
|
4058
4148
|
newerSources: []
|
|
4059
4149
|
};
|
|
4060
4150
|
}
|
|
4061
|
-
const entry =
|
|
4062
|
-
if (!
|
|
4151
|
+
const entry = join15(distDir, "index.js");
|
|
4152
|
+
if (!existsSync14(entry)) {
|
|
4063
4153
|
return { status: "skipped", reason: `${entry} not found`, newerSources: [] };
|
|
4064
4154
|
}
|
|
4065
4155
|
const builtAt = statSync3(entry).mtimeMs;
|
|
@@ -4103,7 +4193,7 @@ function collectSourceFiles(srcDir) {
|
|
|
4103
4193
|
const found = [];
|
|
4104
4194
|
const walk = (dir) => {
|
|
4105
4195
|
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
4106
|
-
const full =
|
|
4196
|
+
const full = join15(dir, entry.name);
|
|
4107
4197
|
if (entry.isDirectory()) {
|
|
4108
4198
|
if (entry.name === "node_modules") continue;
|
|
4109
4199
|
walk(full);
|
|
@@ -4122,7 +4212,7 @@ function collectSourceFiles(srcDir) {
|
|
|
4122
4212
|
function findPackageRoot(from) {
|
|
4123
4213
|
let dir = from;
|
|
4124
4214
|
for (; ; ) {
|
|
4125
|
-
if (
|
|
4215
|
+
if (existsSync14(join15(dir, "package.json"))) return dir;
|
|
4126
4216
|
const parent = dirname5(dir);
|
|
4127
4217
|
if (parent === dir) return null;
|
|
4128
4218
|
dir = parent;
|
|
@@ -4136,9 +4226,9 @@ function isInside(parent, child) {
|
|
|
4136
4226
|
|
|
4137
4227
|
// src/lib/credentials.ts
|
|
4138
4228
|
import { execSync as execSync6 } from "child_process";
|
|
4139
|
-
import { existsSync as
|
|
4229
|
+
import { existsSync as existsSync15, readFileSync as readFileSync11 } from "fs";
|
|
4140
4230
|
import { homedir as homedir2 } from "os";
|
|
4141
|
-
import { join as
|
|
4231
|
+
import { join as join16 } from "path";
|
|
4142
4232
|
import { GetCallerIdentityCommand as GetCallerIdentityCommand2, STSClient as STSClient2 } from "@aws-sdk/client-sts";
|
|
4143
4233
|
import chalk10 from "chalk";
|
|
4144
4234
|
import inquirer4 from "inquirer";
|
|
@@ -4317,11 +4407,11 @@ async function verifySelectedAwsCredentials(profile, region) {
|
|
|
4317
4407
|
return sts.send(new GetCallerIdentityCommand2({}));
|
|
4318
4408
|
}
|
|
4319
4409
|
function discoverAwsProfiles() {
|
|
4320
|
-
const files = [
|
|
4410
|
+
const files = [join16(homedir2(), ".aws", "credentials"), join16(homedir2(), ".aws", "config")];
|
|
4321
4411
|
const profiles = /* @__PURE__ */ new Set();
|
|
4322
4412
|
for (const file of files) {
|
|
4323
|
-
if (!
|
|
4324
|
-
const content =
|
|
4413
|
+
if (!existsSync15(file)) continue;
|
|
4414
|
+
const content = readFileSync11(file, "utf8");
|
|
4325
4415
|
for (const match of content.matchAll(/^\s*\[([^\]]+)\]\s*$/gm)) {
|
|
4326
4416
|
const section = match[1]?.trim();
|
|
4327
4417
|
if (!section) continue;
|
|
@@ -4411,34 +4501,34 @@ var SiblingConfigSchema = z4.object({
|
|
|
4411
4501
|
|
|
4412
4502
|
// src/lib/sibling-session.ts
|
|
4413
4503
|
import {
|
|
4414
|
-
existsSync as
|
|
4504
|
+
existsSync as existsSync16,
|
|
4415
4505
|
mkdirSync as mkdirSync6,
|
|
4416
4506
|
readdirSync as readdirSync8,
|
|
4417
|
-
readFileSync as
|
|
4507
|
+
readFileSync as readFileSync12,
|
|
4418
4508
|
rmSync as rmSync6,
|
|
4419
4509
|
statSync as statSync4,
|
|
4420
4510
|
writeFileSync as writeFileSync6
|
|
4421
4511
|
} from "fs";
|
|
4422
4512
|
import { homedir as homedir3 } from "os";
|
|
4423
|
-
import { join as
|
|
4513
|
+
import { join as join17 } from "path";
|
|
4424
4514
|
function sessionsDir2() {
|
|
4425
|
-
return process.env["BIFFO_SIBLING_SESSIONS_DIR"] ??
|
|
4515
|
+
return process.env["BIFFO_SIBLING_SESSIONS_DIR"] ?? join17(homedir3(), ".biffo", "sibling-sessions");
|
|
4426
4516
|
}
|
|
4427
4517
|
function sessionPath2(projectName) {
|
|
4428
|
-
return
|
|
4518
|
+
return join17(sessionsDir2(), `${projectName}.json`);
|
|
4429
4519
|
}
|
|
4430
4520
|
function loadSiblingSession(projectName) {
|
|
4431
4521
|
const path = sessionPath2(projectName);
|
|
4432
|
-
if (!
|
|
4522
|
+
if (!existsSync16(path)) return null;
|
|
4433
4523
|
try {
|
|
4434
|
-
return JSON.parse(
|
|
4524
|
+
return JSON.parse(readFileSync12(path, "utf8"));
|
|
4435
4525
|
} catch {
|
|
4436
4526
|
return null;
|
|
4437
4527
|
}
|
|
4438
4528
|
}
|
|
4439
4529
|
function saveSiblingSession(session) {
|
|
4440
4530
|
const dir = sessionsDir2();
|
|
4441
|
-
if (!
|
|
4531
|
+
if (!existsSync16(dir)) mkdirSync6(dir, { recursive: true });
|
|
4442
4532
|
const name = session.config.project?.name ?? "unknown";
|
|
4443
4533
|
const prior = loadSiblingSession(name);
|
|
4444
4534
|
if (prior) {
|
|
@@ -4460,30 +4550,30 @@ function markSiblingStepComplete(session, step) {
|
|
|
4460
4550
|
}
|
|
4461
4551
|
function deleteSiblingSession(projectName) {
|
|
4462
4552
|
const path = sessionPath2(projectName);
|
|
4463
|
-
if (
|
|
4553
|
+
if (existsSync16(path)) rmSync6(path);
|
|
4464
4554
|
}
|
|
4465
4555
|
|
|
4466
4556
|
// src/commands/sibling-create.ts
|
|
4467
|
-
import { cpSync as cpSync2, existsSync as
|
|
4557
|
+
import { cpSync as cpSync2, existsSync as existsSync17, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "fs";
|
|
4468
4558
|
import { tmpdir as tmpdir4 } from "os";
|
|
4469
|
-
import { dirname as dirname6, join as
|
|
4559
|
+
import { dirname as dirname6, join as join19, resolve as resolve9 } from "path";
|
|
4470
4560
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4471
4561
|
import chalk11 from "chalk";
|
|
4472
4562
|
import { Command as Command11 } from "commander";
|
|
4473
4563
|
|
|
4474
4564
|
// src/lib/skeleton-dotfiles.ts
|
|
4475
4565
|
import { readdirSync as readdirSync9, renameSync } from "fs";
|
|
4476
|
-
import { join as
|
|
4566
|
+
import { join as join18 } from "path";
|
|
4477
4567
|
var PACKAGED_GITIGNORE = "_gitignore";
|
|
4478
4568
|
var REAL_GITIGNORE = ".gitignore";
|
|
4479
4569
|
function restorePackagedDotfiles(dir) {
|
|
4480
4570
|
const restored = [];
|
|
4481
4571
|
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
4482
|
-
const full =
|
|
4572
|
+
const full = join18(dir, entry.name);
|
|
4483
4573
|
if (entry.isDirectory()) {
|
|
4484
4574
|
restored.push(...restorePackagedDotfiles(full));
|
|
4485
4575
|
} else if (entry.name === PACKAGED_GITIGNORE) {
|
|
4486
|
-
const target =
|
|
4576
|
+
const target = join18(dir, REAL_GITIGNORE);
|
|
4487
4577
|
renameSync(full, target);
|
|
4488
4578
|
restored.push(target);
|
|
4489
4579
|
}
|
|
@@ -4528,7 +4618,7 @@ async function runSiblingCreateCommand(name, options) {
|
|
|
4528
4618
|
printDryRun2(config, coreConfig, options.templateRoot);
|
|
4529
4619
|
return;
|
|
4530
4620
|
}
|
|
4531
|
-
if (!
|
|
4621
|
+
if (!existsSync17(options.templateRoot)) {
|
|
4532
4622
|
throw new Error(`Sibling template not found at ${options.templateRoot}`);
|
|
4533
4623
|
}
|
|
4534
4624
|
let session = null;
|
|
@@ -4713,7 +4803,7 @@ function assertPathPrefixIsAllowed(pathPrefix) {
|
|
|
4713
4803
|
}
|
|
4714
4804
|
}
|
|
4715
4805
|
function readSiblingConfig(path, root = false) {
|
|
4716
|
-
const raw = JSON.parse(
|
|
4806
|
+
const raw = JSON.parse(readFileSync13(path, "utf8"));
|
|
4717
4807
|
const withDefaults = raw && typeof raw === "object" && "project" in raw && "core" in raw ? {
|
|
4718
4808
|
...raw,
|
|
4719
4809
|
core: {
|
|
@@ -4747,7 +4837,7 @@ function resolveCoreConfig(config, configPath) {
|
|
|
4747
4837
|
throw new Error("Either core.project_name or core.config_path is required.");
|
|
4748
4838
|
}
|
|
4749
4839
|
function parseCoreConfig(path) {
|
|
4750
|
-
const result = BiffoConfigSchema.safeParse(JSON.parse(
|
|
4840
|
+
const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync13(path, "utf8")));
|
|
4751
4841
|
if (!result.success) {
|
|
4752
4842
|
throw new Error(
|
|
4753
4843
|
`Invalid core configuration at ${path}:
|
|
@@ -4786,7 +4876,7 @@ async function resolveCoreIdentity(coreAws, coreConfig, environments) {
|
|
|
4786
4876
|
return coreIdentity;
|
|
4787
4877
|
}
|
|
4788
4878
|
async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, githubToken) {
|
|
4789
|
-
const workDir = mkdtempSync4(
|
|
4879
|
+
const workDir = mkdtempSync4(join19(tmpdir4(), `biffo-sibling-${config.project.name}-`));
|
|
4790
4880
|
try {
|
|
4791
4881
|
writeSiblingTemplate(skeletonRoot, workDir, config, {
|
|
4792
4882
|
coreProjectName: coreConfig.project.name,
|
|
@@ -4802,13 +4892,13 @@ async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, git
|
|
|
4802
4892
|
}
|
|
4803
4893
|
}
|
|
4804
4894
|
function writeSiblingTemplate(templateRoot, targetDir, config, context) {
|
|
4805
|
-
if (!
|
|
4895
|
+
if (!existsSync17(templateRoot)) {
|
|
4806
4896
|
throw new Error(`Sibling template not found at ${templateRoot}`);
|
|
4807
4897
|
}
|
|
4808
4898
|
cpSync2(templateRoot, targetDir, { recursive: true });
|
|
4809
4899
|
restorePackagedDotfiles(targetDir);
|
|
4810
4900
|
writeFileSync7(
|
|
4811
|
-
|
|
4901
|
+
join19(targetDir, "biffo.sibling.json"),
|
|
4812
4902
|
JSON.stringify(
|
|
4813
4903
|
{
|
|
4814
4904
|
name: config.project.name,
|
|
@@ -4824,10 +4914,10 @@ function writeSiblingTemplate(templateRoot, targetDir, config, context) {
|
|
|
4824
4914
|
2
|
|
4825
4915
|
) + "\n"
|
|
4826
4916
|
);
|
|
4827
|
-
const envPath =
|
|
4917
|
+
const envPath = join19(targetDir, "apps", "frontend", ".env.example");
|
|
4828
4918
|
try {
|
|
4829
4919
|
const path = basePathFor(context.pathPrefix);
|
|
4830
|
-
const content =
|
|
4920
|
+
const content = readFileSync13(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}`);
|
|
4831
4921
|
writeFileSync7(envPath, content);
|
|
4832
4922
|
} catch (err) {
|
|
4833
4923
|
if (err.code !== "ENOENT") throw err;
|
|
@@ -4873,17 +4963,17 @@ async function configureSiblingGithub(github, config, coreConfig, session, coreI
|
|
|
4873
4963
|
}
|
|
4874
4964
|
function readExistingSiblingOrigins(filePath) {
|
|
4875
4965
|
try {
|
|
4876
|
-
return JSON.parse(
|
|
4966
|
+
return JSON.parse(readFileSync13(filePath, "utf8"));
|
|
4877
4967
|
} catch (err) {
|
|
4878
4968
|
if (err.code === "ENOENT") return {};
|
|
4879
4969
|
throw err;
|
|
4880
4970
|
}
|
|
4881
4971
|
}
|
|
4882
4972
|
function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x") {
|
|
4883
|
-
const cdnVarsPath =
|
|
4973
|
+
const cdnVarsPath = join19(cloneDir, "modules", "cloud", "aws", "cdn", "variables.tf");
|
|
4884
4974
|
let declaresSiblingOrigins = false;
|
|
4885
4975
|
try {
|
|
4886
|
-
declaresSiblingOrigins = /variable\s+"sibling_origins"/.test(
|
|
4976
|
+
declaresSiblingOrigins = /variable\s+"sibling_origins"/.test(readFileSync13(cdnVarsPath, "utf8"));
|
|
4887
4977
|
} catch {
|
|
4888
4978
|
declaresSiblingOrigins = false;
|
|
4889
4979
|
}
|
|
@@ -4893,10 +4983,10 @@ function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x")
|
|
|
4893
4983
|
);
|
|
4894
4984
|
}
|
|
4895
4985
|
if (!isRootPathPrefix(pathPrefix)) return;
|
|
4896
|
-
const cdnMainPath =
|
|
4986
|
+
const cdnMainPath = join19(cloneDir, "modules", "cloud", "aws", "cdn", "main.tf");
|
|
4897
4987
|
let supportsRoot = false;
|
|
4898
4988
|
try {
|
|
4899
|
-
supportsRoot = /root_sibling_registered/.test(
|
|
4989
|
+
supportsRoot = /root_sibling_registered/.test(readFileSync13(cdnMainPath, "utf8"));
|
|
4900
4990
|
} catch {
|
|
4901
4991
|
supportsRoot = false;
|
|
4902
4992
|
}
|
|
@@ -4926,8 +5016,8 @@ async function registerWithCore(git, github, config, coreConfig, pathPrefix, git
|
|
|
4926
5016
|
for (const env of config.environments) {
|
|
4927
5017
|
const bucketName = siteBucketName(config.project.name, env, siblingAccountId);
|
|
4928
5018
|
const domain = bucketRegionalDomain(bucketName, coreAwsRegion);
|
|
4929
|
-
const relativePath =
|
|
4930
|
-
const filePath =
|
|
5019
|
+
const relativePath = join19("infra", "environments", env, "siblings.auto.tfvars.json");
|
|
5020
|
+
const filePath = join19(cloneDir, relativePath);
|
|
4931
5021
|
const existing = readExistingSiblingOrigins(filePath);
|
|
4932
5022
|
const siblings = upsertSiblingOrigin(existing.sibling_origins ?? [], {
|
|
4933
5023
|
name,
|
|
@@ -5007,8 +5097,8 @@ function defaultSiblingTemplateRoot() {
|
|
|
5007
5097
|
const start = dirname6(fileURLToPath4(import.meta.url));
|
|
5008
5098
|
let dir = start;
|
|
5009
5099
|
for (; ; ) {
|
|
5010
|
-
const candidate =
|
|
5011
|
-
if (
|
|
5100
|
+
const candidate = join19(dir, "_skeletons", "sibling-template");
|
|
5101
|
+
if (existsSync17(candidate)) return candidate;
|
|
5012
5102
|
const parent = dirname6(dir);
|
|
5013
5103
|
if (parent === dir) break;
|
|
5014
5104
|
dir = parent;
|
|
@@ -5032,7 +5122,7 @@ var initCommand = new Command12("init").description("Scaffold a new project from
|
|
|
5032
5122
|
let config;
|
|
5033
5123
|
let githubToken;
|
|
5034
5124
|
if (options.config) {
|
|
5035
|
-
const rawConfig = JSON.parse(
|
|
5125
|
+
const rawConfig = JSON.parse(readFileSync14(resolve10(options.config), "utf8"));
|
|
5036
5126
|
config = parseConfig(rawConfig);
|
|
5037
5127
|
const { account_id: accountId, region } = config.cloud.config;
|
|
5038
5128
|
session = resolveConfigFileSession(config, accountId, region, options.fresh === true);
|
|
@@ -5465,28 +5555,28 @@ async function promptForConfig(awsAccountId, awsRegion, awsProfile) {
|
|
|
5465
5555
|
import { Command as Command20 } from "commander";
|
|
5466
5556
|
|
|
5467
5557
|
// src/commands/plugin-create.ts
|
|
5468
|
-
import { existsSync as
|
|
5469
|
-
import { dirname as dirname8, join as
|
|
5558
|
+
import { existsSync as existsSync20, readFileSync as readFileSync16 } from "fs";
|
|
5559
|
+
import { dirname as dirname8, join as join22, resolve as resolve11 } from "path";
|
|
5470
5560
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
5471
5561
|
import chalk13 from "chalk";
|
|
5472
5562
|
import { Command as Command13 } from "commander";
|
|
5473
5563
|
|
|
5474
5564
|
// src/lib/plugin-locations.ts
|
|
5475
|
-
import { existsSync as
|
|
5476
|
-
import { join as
|
|
5565
|
+
import { existsSync as existsSync18, readdirSync as readdirSync10 } from "fs";
|
|
5566
|
+
import { join as join20 } from "path";
|
|
5477
5567
|
var FIRST_PARTY_PLUGINS_DIR = "_plugins";
|
|
5478
5568
|
var PLUGIN_MANIFEST_FILE = "biffo.plugin.json";
|
|
5479
5569
|
function pluginDir(name, channel) {
|
|
5480
5570
|
return channel === "first-party" ? `services/${FIRST_PARTY_PLUGINS_DIR}/${name}` : `services/${name}`;
|
|
5481
5571
|
}
|
|
5482
5572
|
function scanDir(absDir, relDir, channel) {
|
|
5483
|
-
if (!
|
|
5573
|
+
if (!existsSync18(absDir)) return [];
|
|
5484
5574
|
const found = [];
|
|
5485
5575
|
for (const entry of readdirSync10(absDir, { withFileTypes: true })) {
|
|
5486
5576
|
if (!entry.isDirectory()) continue;
|
|
5487
5577
|
if (channel === "third-party" && entry.name === FIRST_PARTY_PLUGINS_DIR) continue;
|
|
5488
|
-
const manifestPath =
|
|
5489
|
-
if (!
|
|
5578
|
+
const manifestPath = join20(absDir, entry.name, PLUGIN_MANIFEST_FILE);
|
|
5579
|
+
if (!existsSync18(manifestPath)) continue;
|
|
5490
5580
|
found.push({
|
|
5491
5581
|
dirName: entry.name,
|
|
5492
5582
|
relDir: `${relDir}/${entry.name}`,
|
|
@@ -5497,11 +5587,11 @@ function scanDir(absDir, relDir, channel) {
|
|
|
5497
5587
|
return found;
|
|
5498
5588
|
}
|
|
5499
5589
|
function findInstalledPlugins(cwd) {
|
|
5500
|
-
const servicesDir =
|
|
5590
|
+
const servicesDir = join20(cwd, "services");
|
|
5501
5591
|
return [
|
|
5502
5592
|
...scanDir(servicesDir, "services", "third-party"),
|
|
5503
5593
|
...scanDir(
|
|
5504
|
-
|
|
5594
|
+
join20(servicesDir, FIRST_PARTY_PLUGINS_DIR),
|
|
5505
5595
|
`services/${FIRST_PARTY_PLUGINS_DIR}`,
|
|
5506
5596
|
"first-party"
|
|
5507
5597
|
)
|
|
@@ -5661,13 +5751,13 @@ function validateManifest(raw) {
|
|
|
5661
5751
|
// src/lib/plugin-scaffold.ts
|
|
5662
5752
|
import {
|
|
5663
5753
|
copyFileSync,
|
|
5664
|
-
existsSync as
|
|
5754
|
+
existsSync as existsSync19,
|
|
5665
5755
|
mkdirSync as mkdirSync8,
|
|
5666
|
-
readFileSync as
|
|
5756
|
+
readFileSync as readFileSync15,
|
|
5667
5757
|
readdirSync as readdirSync11,
|
|
5668
5758
|
writeFileSync as writeFileSync8
|
|
5669
5759
|
} from "fs";
|
|
5670
|
-
import { dirname as dirname7, join as
|
|
5760
|
+
import { dirname as dirname7, join as join21 } from "path";
|
|
5671
5761
|
var STANDALONE_ONLY_ENTRIES = {
|
|
5672
5762
|
".github": "standalone-repo CI/release workflows \u2014 the host monorepo already runs lint/type/test/security over services/",
|
|
5673
5763
|
"registry-schema.json": "the plugin-registry publishing schema, used when submitting a *published* plugin to the registry repo, not by an in-tree plugin"
|
|
@@ -5720,10 +5810,10 @@ function applySubstitutions(text, names) {
|
|
|
5720
5810
|
}
|
|
5721
5811
|
var BINARY_EXTENSIONS = /\.(png|jpe?g|gif|ico|woff2?|ttf|zip|gz)$/i;
|
|
5722
5812
|
function scaffoldPlugin(skeletonRoot, destDir, names) {
|
|
5723
|
-
if (!
|
|
5813
|
+
if (!existsSync19(skeletonRoot)) {
|
|
5724
5814
|
throw new Error(`Plugin skeleton not found at ${skeletonRoot}`);
|
|
5725
5815
|
}
|
|
5726
|
-
if (!
|
|
5816
|
+
if (!existsSync19(join21(skeletonRoot, "terraform"))) {
|
|
5727
5817
|
throw new Error(
|
|
5728
5818
|
`Plugin skeleton at ${skeletonRoot} has no terraform/ directory. Refusing to scaffold a plugin that cannot receive events (issue #194) \u2014 the skeleton is broken.`
|
|
5729
5819
|
);
|
|
@@ -5731,7 +5821,7 @@ function scaffoldPlugin(skeletonRoot, destDir, names) {
|
|
|
5731
5821
|
const skipped = [];
|
|
5732
5822
|
const files = [];
|
|
5733
5823
|
const walk = (relDir) => {
|
|
5734
|
-
const absDir =
|
|
5824
|
+
const absDir = join21(skeletonRoot, relDir);
|
|
5735
5825
|
for (const entry of readdirSync11(absDir, { withFileTypes: true }).sort(
|
|
5736
5826
|
(a, b) => a.name.localeCompare(b.name)
|
|
5737
5827
|
)) {
|
|
@@ -5746,14 +5836,14 @@ function scaffoldPlugin(skeletonRoot, destDir, names) {
|
|
|
5746
5836
|
continue;
|
|
5747
5837
|
}
|
|
5748
5838
|
const destRel = applySubstitutions(relPath, names);
|
|
5749
|
-
const destPath =
|
|
5839
|
+
const destPath = join21(destDir, destRel);
|
|
5750
5840
|
mkdirSync8(dirname7(destPath), { recursive: true });
|
|
5751
5841
|
if (BINARY_EXTENSIONS.test(entry.name)) {
|
|
5752
|
-
copyFileSync(
|
|
5842
|
+
copyFileSync(join21(skeletonRoot, relPath), destPath);
|
|
5753
5843
|
} else {
|
|
5754
5844
|
writeFileSync8(
|
|
5755
5845
|
destPath,
|
|
5756
|
-
applySubstitutions(
|
|
5846
|
+
applySubstitutions(readFileSync15(join21(skeletonRoot, relPath), "utf8"), names)
|
|
5757
5847
|
);
|
|
5758
5848
|
}
|
|
5759
5849
|
files.push(destRel);
|
|
@@ -5770,8 +5860,8 @@ function scaffoldPlugin(skeletonRoot, destDir, names) {
|
|
|
5770
5860
|
function findSkeletonRoot(startDir, skeleton) {
|
|
5771
5861
|
let dir = startDir;
|
|
5772
5862
|
for (; ; ) {
|
|
5773
|
-
const candidate =
|
|
5774
|
-
if (
|
|
5863
|
+
const candidate = join21(dir, "_skeletons", skeleton);
|
|
5864
|
+
if (existsSync19(candidate)) return candidate;
|
|
5775
5865
|
const parent = dirname7(dir);
|
|
5776
5866
|
if (parent === dir) return null;
|
|
5777
5867
|
dir = parent;
|
|
@@ -5808,7 +5898,7 @@ var pluginCreateCommand = new Command13("create").description("Scaffold a new pl
|
|
|
5808
5898
|
);
|
|
5809
5899
|
async function runPluginCreate(name, options, deps) {
|
|
5810
5900
|
const names = deriveNames(name);
|
|
5811
|
-
const isInstance =
|
|
5901
|
+
const isInstance = existsSync20(join22(options.cwd, INSTANCE_CORE_FILE));
|
|
5812
5902
|
if (options.firstParty && isInstance) {
|
|
5813
5903
|
throw new Error(
|
|
5814
5904
|
`--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.`
|
|
@@ -5816,19 +5906,19 @@ async function runPluginCreate(name, options, deps) {
|
|
|
5816
5906
|
}
|
|
5817
5907
|
const channel = options.firstParty ? "first-party" : "third-party";
|
|
5818
5908
|
const relDir = pluginDir(names.slug, channel);
|
|
5819
|
-
const destDir =
|
|
5820
|
-
const servicesDir =
|
|
5821
|
-
if (!
|
|
5909
|
+
const destDir = join22(options.cwd, relDir);
|
|
5910
|
+
const servicesDir = join22(options.cwd, "services");
|
|
5911
|
+
if (!existsSync20(servicesDir)) {
|
|
5822
5912
|
throw new Error(
|
|
5823
5913
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
5824
5914
|
);
|
|
5825
5915
|
}
|
|
5826
|
-
if (
|
|
5916
|
+
if (existsSync20(destDir)) {
|
|
5827
5917
|
throw new Error(`${relDir}/ already exists. Choose a different name, or remove it first.`);
|
|
5828
5918
|
}
|
|
5829
5919
|
const here = dirname8(fileURLToPath5(import.meta.url));
|
|
5830
|
-
const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ??
|
|
5831
|
-
if (!
|
|
5920
|
+
const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join22(options.cwd, "_skeletons", "plugin-template");
|
|
5921
|
+
if (!existsSync20(skeletonRoot)) {
|
|
5832
5922
|
throw new Error(
|
|
5833
5923
|
`Could not find the plugin skeleton (_skeletons/plugin-template/). Pass --skeleton <path> to point at it explicitly.`
|
|
5834
5924
|
);
|
|
@@ -5843,8 +5933,8 @@ async function runPluginCreate(name, options, deps) {
|
|
|
5843
5933
|
for (const { entry, reason } of skipped) {
|
|
5844
5934
|
log.info(`Skipped ${entry} \u2014 ${reason}`);
|
|
5845
5935
|
}
|
|
5846
|
-
const manifestPath =
|
|
5847
|
-
const manifest = validateManifest(JSON.parse(
|
|
5936
|
+
const manifestPath = join22(destDir, "biffo.plugin.json");
|
|
5937
|
+
const manifest = validateManifest(JSON.parse(readFileSync16(manifestPath, "utf8")));
|
|
5848
5938
|
if (manifest.name !== names.slug) {
|
|
5849
5939
|
throw new Error(
|
|
5850
5940
|
`Scaffolded manifest declares name '${manifest.name}', expected '${names.slug}'. The skeleton's manifest name may have diverged from 'example-plugin'.`
|
|
@@ -6034,14 +6124,14 @@ function printEntry(entry) {
|
|
|
6034
6124
|
}
|
|
6035
6125
|
|
|
6036
6126
|
// src/commands/plugin-install.ts
|
|
6037
|
-
import { cpSync as cpSync3, existsSync as
|
|
6038
|
-
import { basename, join as
|
|
6127
|
+
import { cpSync as cpSync3, existsSync as existsSync21, mkdirSync as mkdirSync9, readFileSync as readFileSync17, statSync as statSync5 } from "fs";
|
|
6128
|
+
import { basename, join as join24, relative as relative3, resolve as resolve12 } from "path";
|
|
6039
6129
|
import chalk15 from "chalk";
|
|
6040
6130
|
import { Command as Command15 } from "commander";
|
|
6041
6131
|
|
|
6042
6132
|
// src/adapters/plugin-migrations/index.ts
|
|
6043
6133
|
import { execa as execa4 } from "execa";
|
|
6044
|
-
import { join as
|
|
6134
|
+
import { join as join23 } from "path";
|
|
6045
6135
|
var PluginMigrationsAdapter = class {
|
|
6046
6136
|
/**
|
|
6047
6137
|
* Generates migration file(s) for `pluginNames` (every discovered
|
|
@@ -6050,22 +6140,22 @@ var PluginMigrationsAdapter = class {
|
|
|
6050
6140
|
* or declared no tables.
|
|
6051
6141
|
*/
|
|
6052
6142
|
async generate(cwd, pluginNames) {
|
|
6053
|
-
const scriptPath =
|
|
6143
|
+
const scriptPath = join23(cwd, "services", "api", "scripts", "generate_plugin_migrations.py");
|
|
6054
6144
|
const args = [
|
|
6055
6145
|
"run",
|
|
6056
6146
|
"python",
|
|
6057
6147
|
scriptPath,
|
|
6058
6148
|
"--services-root",
|
|
6059
|
-
|
|
6149
|
+
join23(cwd, "services"),
|
|
6060
6150
|
"--versions-dir",
|
|
6061
|
-
|
|
6151
|
+
join23(cwd, "services", "api", "migrations", "versions")
|
|
6062
6152
|
];
|
|
6063
6153
|
for (const name of pluginNames ?? []) {
|
|
6064
6154
|
args.push("--plugin", name);
|
|
6065
6155
|
}
|
|
6066
6156
|
let result;
|
|
6067
6157
|
try {
|
|
6068
|
-
result = await execa4("uv", args, { cwd:
|
|
6158
|
+
result = await execa4("uv", args, { cwd: join23(cwd, "services", "api") });
|
|
6069
6159
|
} catch (err) {
|
|
6070
6160
|
const cause = err;
|
|
6071
6161
|
if (cause.code === "ENOENT") {
|
|
@@ -6123,14 +6213,14 @@ var LOCAL_COPY_EXCLUDES = /* @__PURE__ */ new Set([
|
|
|
6123
6213
|
".terraform"
|
|
6124
6214
|
]);
|
|
6125
6215
|
function resolveLocalPlugin(localPath) {
|
|
6126
|
-
if (!
|
|
6216
|
+
if (!existsSync21(localPath)) {
|
|
6127
6217
|
throw new Error(`--local path does not exist: ${localPath}`);
|
|
6128
6218
|
}
|
|
6129
6219
|
if (!statSync5(localPath).isDirectory()) {
|
|
6130
6220
|
throw new Error(`--local path is not a directory: ${localPath}`);
|
|
6131
6221
|
}
|
|
6132
|
-
const manifestPath =
|
|
6133
|
-
if (!
|
|
6222
|
+
const manifestPath = join24(localPath, "biffo.plugin.json");
|
|
6223
|
+
if (!existsSync21(manifestPath)) {
|
|
6134
6224
|
throw new Error(
|
|
6135
6225
|
`${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>\`.)`
|
|
6136
6226
|
);
|
|
@@ -6156,8 +6246,8 @@ function parsePluginTarget(target) {
|
|
|
6156
6246
|
async function cloneAndValidatePlugin(entry, git) {
|
|
6157
6247
|
const tmpDir = await git.cloneToTemp(entry.repo, `biffo-plugin-${entry.name}`);
|
|
6158
6248
|
try {
|
|
6159
|
-
const manifestPath =
|
|
6160
|
-
if (!
|
|
6249
|
+
const manifestPath = join24(tmpDir, "biffo.plugin.json");
|
|
6250
|
+
if (!existsSync21(manifestPath)) {
|
|
6161
6251
|
throw new Error(
|
|
6162
6252
|
`Plugin repo ${entry.repo} does not contain a biffo.plugin.json manifest at its root.`
|
|
6163
6253
|
);
|
|
@@ -6185,8 +6275,8 @@ async function runPluginInstall(target, options, deps) {
|
|
|
6185
6275
|
`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\`).`
|
|
6186
6276
|
);
|
|
6187
6277
|
}
|
|
6188
|
-
const servicesDir =
|
|
6189
|
-
if (!
|
|
6278
|
+
const servicesDir = join24(options.cwd, "services");
|
|
6279
|
+
if (!existsSync21(servicesDir)) {
|
|
6190
6280
|
throw new Error(
|
|
6191
6281
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
6192
6282
|
);
|
|
@@ -6204,10 +6294,10 @@ async function runPluginInstall(target, options, deps) {
|
|
|
6204
6294
|
}
|
|
6205
6295
|
const pluginName = entry ? entry.name : source.name;
|
|
6206
6296
|
const relTargetDir = pluginDir(pluginName, "third-party");
|
|
6207
|
-
const targetDir =
|
|
6208
|
-
const modulesDir =
|
|
6297
|
+
const targetDir = join24(options.cwd, relTargetDir);
|
|
6298
|
+
const modulesDir = join24(options.cwd, "modules", "plugins", pluginName);
|
|
6209
6299
|
const inTreeSource = options.local !== void 0 && resolve12(options.local) === resolve12(targetDir);
|
|
6210
|
-
if (
|
|
6300
|
+
if (existsSync21(targetDir) && !inTreeSource) {
|
|
6211
6301
|
throw new Error(
|
|
6212
6302
|
`Plugin '${pluginName}' is already installed at ${relTargetDir}/. Remove it first, or wait for a future 'biffo plugin upgrade' command.`
|
|
6213
6303
|
);
|
|
@@ -6250,8 +6340,8 @@ async function runPluginInstall(target, options, deps) {
|
|
|
6250
6340
|
log.success(`Installed plugin source at ${relTargetDir}/`);
|
|
6251
6341
|
}
|
|
6252
6342
|
const stagePaths = [relTargetDir];
|
|
6253
|
-
const tfSourceDir =
|
|
6254
|
-
if (
|
|
6343
|
+
const tfSourceDir = join24(targetDir, "terraform");
|
|
6344
|
+
if (existsSync21(tfSourceDir)) {
|
|
6255
6345
|
mkdirSync9(modulesDir, { recursive: true });
|
|
6256
6346
|
cpSync3(tfSourceDir, modulesDir, { recursive: true });
|
|
6257
6347
|
stagePaths.push(`modules/plugins/${pluginName}`);
|
|
@@ -6308,7 +6398,7 @@ async function runPluginInstall(target, options, deps) {
|
|
|
6308
6398
|
}
|
|
6309
6399
|
function parseManifestFile(path) {
|
|
6310
6400
|
try {
|
|
6311
|
-
return JSON.parse(
|
|
6401
|
+
return JSON.parse(readFileSync17(path, "utf8"));
|
|
6312
6402
|
} catch (err) {
|
|
6313
6403
|
throw new Error(`Could not parse ${path} as JSON: ${err.message}`);
|
|
6314
6404
|
}
|
|
@@ -6345,8 +6435,8 @@ function printDryRun4(entry, source, relTargetDir, inTreeSource) {
|
|
|
6345
6435
|
}
|
|
6346
6436
|
|
|
6347
6437
|
// src/commands/plugin-list.ts
|
|
6348
|
-
import { existsSync as
|
|
6349
|
-
import { join as
|
|
6438
|
+
import { existsSync as existsSync22, readFileSync as readFileSync18 } from "fs";
|
|
6439
|
+
import { join as join25, resolve as resolve13 } from "path";
|
|
6350
6440
|
import chalk16 from "chalk";
|
|
6351
6441
|
import { Command as Command16 } from "commander";
|
|
6352
6442
|
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) => {
|
|
@@ -6359,8 +6449,8 @@ var pluginListCommand = new Command16("list").description("List plugins installe
|
|
|
6359
6449
|
}
|
|
6360
6450
|
});
|
|
6361
6451
|
async function runPluginList(options) {
|
|
6362
|
-
const servicesDir =
|
|
6363
|
-
if (!
|
|
6452
|
+
const servicesDir = join25(options.cwd, "services");
|
|
6453
|
+
if (!existsSync22(servicesDir)) {
|
|
6364
6454
|
throw new Error(
|
|
6365
6455
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
6366
6456
|
);
|
|
@@ -6368,7 +6458,7 @@ async function runPluginList(options) {
|
|
|
6368
6458
|
const plugins = [];
|
|
6369
6459
|
for (const location of findInstalledPlugins(options.cwd)) {
|
|
6370
6460
|
try {
|
|
6371
|
-
const manifest = validateManifest(JSON.parse(
|
|
6461
|
+
const manifest = validateManifest(JSON.parse(readFileSync18(location.manifestPath, "utf8")));
|
|
6372
6462
|
plugins.push({
|
|
6373
6463
|
name: manifest.name,
|
|
6374
6464
|
version: manifest.version,
|
|
@@ -6405,8 +6495,8 @@ async function runPluginList(options) {
|
|
|
6405
6495
|
}
|
|
6406
6496
|
|
|
6407
6497
|
// src/commands/plugin-sync-migrations.ts
|
|
6408
|
-
import { existsSync as
|
|
6409
|
-
import { join as
|
|
6498
|
+
import { existsSync as existsSync23 } from "fs";
|
|
6499
|
+
import { join as join26, relative as relative4, resolve as resolve14 } from "path";
|
|
6410
6500
|
import chalk17 from "chalk";
|
|
6411
6501
|
import { Command as Command17 } from "commander";
|
|
6412
6502
|
var pluginSyncMigrationsCommand = new Command17("sync-migrations").description(
|
|
@@ -6427,11 +6517,11 @@ var pluginSyncMigrationsCommand = new Command17("sync-migrations").description(
|
|
|
6427
6517
|
}
|
|
6428
6518
|
);
|
|
6429
6519
|
async function runPluginSyncMigrations(name, options, deps) {
|
|
6430
|
-
const servicesDir =
|
|
6431
|
-
if (!
|
|
6520
|
+
const servicesDir = join26(options.cwd, "services");
|
|
6521
|
+
if (!existsSync23(servicesDir)) {
|
|
6432
6522
|
throw new Error(`${servicesDir} does not exist \u2014 is ${options.cwd} a Biffo project checkout?`);
|
|
6433
6523
|
}
|
|
6434
|
-
if (name && !
|
|
6524
|
+
if (name && !existsSync23(join26(servicesDir, name, "biffo.plugin.json"))) {
|
|
6435
6525
|
throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
|
|
6436
6526
|
}
|
|
6437
6527
|
if (options.dryRun) {
|
|
@@ -6467,8 +6557,8 @@ async function runPluginSyncMigrations(name, options, deps) {
|
|
|
6467
6557
|
}
|
|
6468
6558
|
|
|
6469
6559
|
// src/commands/plugin-uninstall.ts
|
|
6470
|
-
import { existsSync as
|
|
6471
|
-
import { join as
|
|
6560
|
+
import { existsSync as existsSync24, readFileSync as readFileSync19, rmSync as rmSync7 } from "fs";
|
|
6561
|
+
import { join as join27, resolve as resolve15 } from "path";
|
|
6472
6562
|
import chalk18 from "chalk";
|
|
6473
6563
|
import { Command as Command18 } from "commander";
|
|
6474
6564
|
import inquirer6 from "inquirer";
|
|
@@ -6500,16 +6590,16 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
6500
6590
|
if (!NAME_PATTERN2.test(name)) {
|
|
6501
6591
|
throw new Error(`Invalid plugin name '${name}'. Expected a lowercase kebab-case slug.`);
|
|
6502
6592
|
}
|
|
6503
|
-
const servicesDir =
|
|
6504
|
-
if (!
|
|
6593
|
+
const servicesDir = join27(options.cwd, "services");
|
|
6594
|
+
if (!existsSync24(servicesDir)) {
|
|
6505
6595
|
throw new Error(
|
|
6506
6596
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
6507
6597
|
);
|
|
6508
6598
|
}
|
|
6509
|
-
const targetDir =
|
|
6510
|
-
if (!
|
|
6511
|
-
const firstParty =
|
|
6512
|
-
if (
|
|
6599
|
+
const targetDir = join27(servicesDir, name);
|
|
6600
|
+
if (!existsSync24(targetDir)) {
|
|
6601
|
+
const firstParty = join27(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
|
|
6602
|
+
if (existsSync24(firstParty)) {
|
|
6513
6603
|
throw new Error(
|
|
6514
6604
|
`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.`
|
|
6515
6605
|
);
|
|
@@ -6517,9 +6607,9 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
6517
6607
|
throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
|
|
6518
6608
|
}
|
|
6519
6609
|
const version = readInstalledVersion(targetDir);
|
|
6520
|
-
const modulesDir =
|
|
6610
|
+
const modulesDir = join27(options.cwd, "modules", "plugins", name);
|
|
6521
6611
|
const stagePaths = [`services/${name}`];
|
|
6522
|
-
if (
|
|
6612
|
+
if (existsSync24(modulesDir)) {
|
|
6523
6613
|
stagePaths.push(`modules/plugins/${name}`);
|
|
6524
6614
|
}
|
|
6525
6615
|
if (options.dryRun) {
|
|
@@ -6541,7 +6631,7 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
6541
6631
|
}
|
|
6542
6632
|
rmSync7(targetDir, { recursive: true, force: true });
|
|
6543
6633
|
log.success(`Removed services/${name}/`);
|
|
6544
|
-
if (
|
|
6634
|
+
if (existsSync24(modulesDir)) {
|
|
6545
6635
|
rmSync7(modulesDir, { recursive: true, force: true });
|
|
6546
6636
|
log.success(`Removed modules/plugins/${name}/`);
|
|
6547
6637
|
const wiring = syncPluginTerraform(options.cwd);
|
|
@@ -6578,10 +6668,10 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
6578
6668
|
}
|
|
6579
6669
|
}
|
|
6580
6670
|
function readInstalledVersion(targetDir) {
|
|
6581
|
-
const manifestPath =
|
|
6582
|
-
if (!
|
|
6671
|
+
const manifestPath = join27(targetDir, "biffo.plugin.json");
|
|
6672
|
+
if (!existsSync24(manifestPath)) return void 0;
|
|
6583
6673
|
try {
|
|
6584
|
-
return validateManifest(JSON.parse(
|
|
6674
|
+
return validateManifest(JSON.parse(readFileSync19(manifestPath, "utf8"))).version;
|
|
6585
6675
|
} catch {
|
|
6586
6676
|
return void 0;
|
|
6587
6677
|
}
|
|
@@ -6614,8 +6704,8 @@ function printDryRun5(name, version, stagePaths, keepData) {
|
|
|
6614
6704
|
}
|
|
6615
6705
|
|
|
6616
6706
|
// src/commands/plugin-upgrade.ts
|
|
6617
|
-
import { cpSync as cpSync4, existsSync as
|
|
6618
|
-
import { join as
|
|
6707
|
+
import { cpSync as cpSync4, existsSync as existsSync25, mkdirSync as mkdirSync10, readFileSync as readFileSync20, rmSync as rmSync8 } from "fs";
|
|
6708
|
+
import { join as join28, relative as relative5, resolve as resolve16 } from "path";
|
|
6619
6709
|
import chalk19 from "chalk";
|
|
6620
6710
|
import { Command as Command19 } from "commander";
|
|
6621
6711
|
import inquirer7 from "inquirer";
|
|
@@ -6640,14 +6730,14 @@ var pluginUpgradeCommand = new Command19("upgrade").description(
|
|
|
6640
6730
|
});
|
|
6641
6731
|
async function runPluginUpgrade(target, options, deps) {
|
|
6642
6732
|
const { name, minor } = parsePluginTarget(target);
|
|
6643
|
-
const servicesDir =
|
|
6644
|
-
if (!
|
|
6733
|
+
const servicesDir = join28(options.cwd, "services");
|
|
6734
|
+
if (!existsSync25(servicesDir)) {
|
|
6645
6735
|
throw new Error(
|
|
6646
6736
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
6647
6737
|
);
|
|
6648
6738
|
}
|
|
6649
|
-
const targetDir =
|
|
6650
|
-
if (!
|
|
6739
|
+
const targetDir = join28(servicesDir, name);
|
|
6740
|
+
if (!existsSync25(targetDir)) {
|
|
6651
6741
|
throw new Error(
|
|
6652
6742
|
`Plugin '${name}' is not installed at services/${name}/. Use 'biffo plugin install ${name}@${minor}' instead.`
|
|
6653
6743
|
);
|
|
@@ -6661,7 +6751,7 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
6661
6751
|
`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.`
|
|
6662
6752
|
);
|
|
6663
6753
|
}
|
|
6664
|
-
const modulesDir =
|
|
6754
|
+
const modulesDir = join28(options.cwd, "modules", "plugins", entry.name);
|
|
6665
6755
|
if (options.dryRun) {
|
|
6666
6756
|
printDryRun6(entry, currentVersion);
|
|
6667
6757
|
return;
|
|
@@ -6694,11 +6784,11 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
6694
6784
|
cpSync4(tmpDir, targetDir, { recursive: true });
|
|
6695
6785
|
log.success(`Upgraded plugin source at services/${entry.name}/`);
|
|
6696
6786
|
const stagePaths = [`services/${entry.name}`];
|
|
6697
|
-
if (
|
|
6787
|
+
if (existsSync25(modulesDir)) {
|
|
6698
6788
|
rmSync8(modulesDir, { recursive: true, force: true });
|
|
6699
6789
|
}
|
|
6700
|
-
const tfSourceDir =
|
|
6701
|
-
if (
|
|
6790
|
+
const tfSourceDir = join28(targetDir, "terraform");
|
|
6791
|
+
if (existsSync25(tfSourceDir)) {
|
|
6702
6792
|
mkdirSync10(modulesDir, { recursive: true });
|
|
6703
6793
|
cpSync4(tfSourceDir, modulesDir, { recursive: true });
|
|
6704
6794
|
stagePaths.push(`modules/plugins/${entry.name}`);
|
|
@@ -6732,10 +6822,10 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
6732
6822
|
}
|
|
6733
6823
|
}
|
|
6734
6824
|
function readInstalledVersion2(targetDir) {
|
|
6735
|
-
const manifestPath =
|
|
6736
|
-
if (!
|
|
6825
|
+
const manifestPath = join28(targetDir, "biffo.plugin.json");
|
|
6826
|
+
if (!existsSync25(manifestPath)) return void 0;
|
|
6737
6827
|
try {
|
|
6738
|
-
return validateManifest(JSON.parse(
|
|
6828
|
+
return validateManifest(JSON.parse(readFileSync20(manifestPath, "utf8"))).version;
|
|
6739
6829
|
} catch {
|
|
6740
6830
|
return void 0;
|
|
6741
6831
|
}
|