@biffo/cli 0.41.10 → 0.41.12
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 +131 -7
- package/package.json +1 -1
package/core.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.41.
|
|
1
|
+
0.41.12
|
package/dist/index.js
CHANGED
|
@@ -835,6 +835,39 @@ var GitHubAdapter = class {
|
|
|
835
835
|
log.info(`Committed ${files.map((f) => f.path).join(", ")} to ${branch}`);
|
|
836
836
|
return commit.sha;
|
|
837
837
|
}
|
|
838
|
+
/** The commit SHA at the head of `branch`. */
|
|
839
|
+
async getBranchSha(org, repo, branch) {
|
|
840
|
+
const ref = await this.waitForRef(org, repo, `heads/${branch}`, 12e4, 3e3);
|
|
841
|
+
return ref.object.sha;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Point `branch` at `sha` via a fast-forward-only ref update (issue #329).
|
|
845
|
+
*
|
|
846
|
+
* `sha` must be an ancestor-descendant of the branch's current head:
|
|
847
|
+
* GitHub's `updateRef` rejects a non-fast-forward move unless `force` is set,
|
|
848
|
+
* and it is deliberately left unset here. The only caller is `writeInstanceFiles`,
|
|
849
|
+
* moving a freshly-created branch onto the instance commit built on that
|
|
850
|
+
* branch's own shared base — always a fast-forward. If it ever isn't, failing
|
|
851
|
+
* loudly is the correct outcome, not clobbering history.
|
|
852
|
+
*
|
|
853
|
+
* Idempotent: a no-op (no API write) when `branch` is already at `sha`, so a
|
|
854
|
+
* resumed init neither errors nor rewrites anything.
|
|
855
|
+
*/
|
|
856
|
+
async fastForwardBranch(org, repo, branch, sha) {
|
|
857
|
+
const current = await this.getBranchSha(org, repo, branch);
|
|
858
|
+
if (current === sha) {
|
|
859
|
+
log.info(`${branch} already at ${sha.slice(0, 7)} \u2014 skipping`);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
await this.octokit.git.updateRef({
|
|
863
|
+
owner: org,
|
|
864
|
+
repo,
|
|
865
|
+
ref: `heads/${branch}`,
|
|
866
|
+
sha,
|
|
867
|
+
force: false
|
|
868
|
+
});
|
|
869
|
+
log.info(`Fast-forwarded ${branch} to ${sha.slice(0, 7)}`);
|
|
870
|
+
}
|
|
838
871
|
async setDefaultBranch(org, repo, branch) {
|
|
839
872
|
await this.octokit.repos.update({ owner: org, repo, default_branch: branch });
|
|
840
873
|
log.info(`Default branch set to ${branch}`);
|
|
@@ -1509,6 +1542,12 @@ function materializeTemplateAtTag(repo, version, git = defaultGit) {
|
|
|
1509
1542
|
return { dir, cleanup: () => rmSync3(dir, { recursive: true, force: true }) };
|
|
1510
1543
|
}
|
|
1511
1544
|
|
|
1545
|
+
// src/lib/global-workflows.ts
|
|
1546
|
+
var GLOBAL_DISPATCH_REF = "main";
|
|
1547
|
+
var GLOBAL_DISPATCH_WORKFLOW_PATHS = [
|
|
1548
|
+
".github/workflows/deploy-global.yml"
|
|
1549
|
+
];
|
|
1550
|
+
|
|
1512
1551
|
// src/commands/core-upgrade.ts
|
|
1513
1552
|
var MISSING_TEMPLATE_ROOT_GUIDANCE2 = "Pass --template-repo <path> to a biffo-template git checkout, e.g. `biffo core upgrade --template-repo /path/to/biffo-template`.";
|
|
1514
1553
|
var coreUpgradeCommand = new Command3("upgrade").description("Three-way-merge template-owned files for a core upgrade; preview it or open a PR").option("--cwd <path>", "Instance repo root to upgrade (defaults to the current directory)").option(
|
|
@@ -1685,7 +1724,7 @@ async function applyAndOpenPr(options, deps, plan, migrations, fromVersion, toVe
|
|
|
1685
1724
|
head: branch,
|
|
1686
1725
|
base,
|
|
1687
1726
|
title: `Upgrade Biffo core ${fromVersion} \u2192 ${toVersion}`,
|
|
1688
|
-
body: buildPrBody(fromVersion, toVersion, plan, migrations)
|
|
1727
|
+
body: buildPrBody(fromVersion, toVersion, plan, migrations, base)
|
|
1689
1728
|
});
|
|
1690
1729
|
if (plan.conflicts.length > 0) {
|
|
1691
1730
|
log.warn(`PR opened with ${plan.conflicts.length} conflict(s) to resolve: ${pr.url}`);
|
|
@@ -1693,7 +1732,7 @@ async function applyAndOpenPr(options, deps, plan, migrations, fromVersion, toVe
|
|
|
1693
1732
|
log.success(`Opened PR #${pr.number}: ${pr.url}`);
|
|
1694
1733
|
}
|
|
1695
1734
|
}
|
|
1696
|
-
function buildPrBody(from, to, plan, migrations) {
|
|
1735
|
+
function buildPrBody(from, to, plan, migrations, base = GLOBAL_DISPATCH_REF) {
|
|
1697
1736
|
const lines = [
|
|
1698
1737
|
"Automated core upgrade generated by `biffo core upgrade` (ADR-0006).",
|
|
1699
1738
|
"",
|
|
@@ -1720,6 +1759,21 @@ function buildPrBody(from, to, plan, migrations) {
|
|
|
1720
1759
|
"Review the DDL before merging: merging this PR runs these migrations against the database on the next deploy."
|
|
1721
1760
|
);
|
|
1722
1761
|
}
|
|
1762
|
+
const globalWorkflowChanges = plan.changes.filter(
|
|
1763
|
+
(c) => GLOBAL_DISPATCH_WORKFLOW_PATHS.includes(c.path)
|
|
1764
|
+
);
|
|
1765
|
+
if (base !== GLOBAL_DISPATCH_REF && globalWorkflowChanges.length > 0) {
|
|
1766
|
+
lines.push(
|
|
1767
|
+
"",
|
|
1768
|
+
`## \u26A0 Promotion required \u2014 global workflow change (issue #328)`,
|
|
1769
|
+
"",
|
|
1770
|
+
`This upgrade changes ${globalWorkflowChanges.length} workflow(s) that \`biffo deploy\` dispatches from \`${GLOBAL_DISPATCH_REF}\`, but this PR targets \`${base}\`:`,
|
|
1771
|
+
"",
|
|
1772
|
+
...globalWorkflowChanges.map((c) => `- \`${c.path}\``),
|
|
1773
|
+
"",
|
|
1774
|
+
`Merging here lands the fix on \`${base}\` \u2014 **but the deploy runs it from \`${GLOBAL_DISPATCH_REF}\`.** Until you promote \`${base}\` \u2192 \`${GLOBAL_DISPATCH_REF}\` (open a PR from \`${base}\` into \`${GLOBAL_DISPATCH_REF}\` and merge it), the old workflow keeps running and this fix will not take effect.`
|
|
1775
|
+
);
|
|
1776
|
+
}
|
|
1723
1777
|
if (plan.conflicts.length > 0) {
|
|
1724
1778
|
lines.push(
|
|
1725
1779
|
"",
|
|
@@ -3124,6 +3178,61 @@ async function reportOidcHint(github, org, repo, runId) {
|
|
|
3124
3178
|
log.error(" Re-run `biffo init` to rewrite the trust policy with both patterns.");
|
|
3125
3179
|
log.error("");
|
|
3126
3180
|
}
|
|
3181
|
+
async function readRemoteCoreVersion(github, org, repo, ref) {
|
|
3182
|
+
const record = await github.getFileContent(org, repo, INSTANCE_CORE_FILE, ref);
|
|
3183
|
+
if (record) {
|
|
3184
|
+
try {
|
|
3185
|
+
const parsed = JSON.parse(record);
|
|
3186
|
+
if (typeof parsed.version === "string") {
|
|
3187
|
+
parseCoreVersion(parsed.version);
|
|
3188
|
+
return parsed.version;
|
|
3189
|
+
}
|
|
3190
|
+
} catch {
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
const inherited = await github.getFileContent(org, repo, CORE_VERSION_FILE, ref);
|
|
3194
|
+
if (inherited) {
|
|
3195
|
+
try {
|
|
3196
|
+
const version = inherited.trim();
|
|
3197
|
+
parseCoreVersion(version);
|
|
3198
|
+
return version;
|
|
3199
|
+
} catch {
|
|
3200
|
+
return null;
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
return null;
|
|
3204
|
+
}
|
|
3205
|
+
async function warnIfDispatchRefStale(github, org, repo, workflowFile, dispatchRef, deployBranch) {
|
|
3206
|
+
if (dispatchRef === deployBranch) return;
|
|
3207
|
+
let refVersion;
|
|
3208
|
+
let branchVersion;
|
|
3209
|
+
try {
|
|
3210
|
+
;
|
|
3211
|
+
[refVersion, branchVersion] = await Promise.all([
|
|
3212
|
+
readRemoteCoreVersion(github, org, repo, dispatchRef),
|
|
3213
|
+
readRemoteCoreVersion(github, org, repo, deployBranch)
|
|
3214
|
+
]);
|
|
3215
|
+
} catch {
|
|
3216
|
+
return;
|
|
3217
|
+
}
|
|
3218
|
+
if (!refVersion || !branchVersion) return;
|
|
3219
|
+
if (compareCoreVersions(refVersion, branchVersion) >= 0) return;
|
|
3220
|
+
log.warn("");
|
|
3221
|
+
log.warn(
|
|
3222
|
+
` ${workflowFile} is dispatched from '${dispatchRef}', but '${dispatchRef}' is on core ${refVersion} while '${deployBranch}' (deploying now) is on core ${branchVersion}.`
|
|
3223
|
+
);
|
|
3224
|
+
log.warn(
|
|
3225
|
+
` A core upgrade landed on '${deployBranch}' that has not reached '${dispatchRef}'. This deploy`
|
|
3226
|
+
);
|
|
3227
|
+
log.warn(
|
|
3228
|
+
` will run the OLDER ${workflowFile} from '${dispatchRef}', so template fixes to it will NOT`
|
|
3229
|
+
);
|
|
3230
|
+
log.warn(
|
|
3231
|
+
` take effect. Promote '${deployBranch}' \u2192 '${dispatchRef}' (open a PR from '${deployBranch}'`
|
|
3232
|
+
);
|
|
3233
|
+
log.warn(` into '${dispatchRef}' and merge it), then re-run this deploy. (issue #328)`);
|
|
3234
|
+
log.warn("");
|
|
3235
|
+
}
|
|
3127
3236
|
async function runDeploy(github, aws, config, environment, options = {}) {
|
|
3128
3237
|
const { org, repo } = config.source_control.config;
|
|
3129
3238
|
const awsConfig2 = config.cloud.config;
|
|
@@ -3165,8 +3274,16 @@ async function runDeploy(github, aws, config, environment, options = {}) {
|
|
|
3165
3274
|
if (!skipInfra && hasGlobalInfra) {
|
|
3166
3275
|
const globalLabel = dns.mode === "external" ? "Requesting SSL certificate for external DNS..." : "Deploying global infrastructure (DNS + SSL certificate)...";
|
|
3167
3276
|
log.step(2, totalSteps, globalLabel);
|
|
3277
|
+
await warnIfDispatchRefStale(
|
|
3278
|
+
github,
|
|
3279
|
+
org,
|
|
3280
|
+
repo,
|
|
3281
|
+
"deploy-global.yml",
|
|
3282
|
+
GLOBAL_DISPATCH_REF,
|
|
3283
|
+
branch
|
|
3284
|
+
);
|
|
3168
3285
|
const globalBaselineId = await github.getLatestWorkflowRunId(org, repo, "deploy-global.yml");
|
|
3169
|
-
await github.triggerWorkflow(org, repo, "deploy-global.yml", {},
|
|
3286
|
+
await github.triggerWorkflow(org, repo, "deploy-global.yml", {}, GLOBAL_DISPATCH_REF);
|
|
3170
3287
|
if (dns.mode === "external") {
|
|
3171
3288
|
log.info(" DNS will not be changed automatically.");
|
|
3172
3289
|
log.info(" The workflow summary will list ACM validation CNAMEs to add manually.");
|
|
@@ -3181,7 +3298,7 @@ async function runDeploy(github, aws, config, environment, options = {}) {
|
|
|
3181
3298
|
globalBaselineId,
|
|
3182
3299
|
36e5,
|
|
3183
3300
|
3e4,
|
|
3184
|
-
|
|
3301
|
+
GLOBAL_DISPATCH_REF
|
|
3185
3302
|
);
|
|
3186
3303
|
if (globalResult.conclusion !== "success") {
|
|
3187
3304
|
log.error(`Global infrastructure deploy ${globalResult.conclusion ?? "failed"}.`);
|
|
@@ -4814,7 +4931,12 @@ function appSiblingRegistryFiles(config) {
|
|
|
4814
4931
|
}));
|
|
4815
4932
|
}
|
|
4816
4933
|
var INSTANCE_CONFIG_FILE = "biffo.config.json";
|
|
4817
|
-
var
|
|
4934
|
+
var INSTANCE_FILE_BASE_BRANCH = "main";
|
|
4935
|
+
var INSTANCE_FILE_FOLLOWER_BRANCHES = ["dev", "staging"];
|
|
4936
|
+
var INSTANCE_FILE_BRANCHES = [
|
|
4937
|
+
INSTANCE_FILE_BASE_BRANCH,
|
|
4938
|
+
...INSTANCE_FILE_FOLLOWER_BRANCHES
|
|
4939
|
+
];
|
|
4818
4940
|
async function writeInstanceFiles(github, org, repo, config) {
|
|
4819
4941
|
const files = [
|
|
4820
4942
|
{ path: INSTANCE_CORE_FILE, content: serializeInstanceCoreVersion(getLatestCoreVersion()) },
|
|
@@ -4822,8 +4944,10 @@ async function writeInstanceFiles(github, org, repo, config) {
|
|
|
4822
4944
|
...config ? appSiblingRegistryFiles(config) : []
|
|
4823
4945
|
];
|
|
4824
4946
|
const message = config ? `chore: record core version, register the ${ROOT_SIBLING_NAME} sibling, and drop the template ${INSTANCE_CONFIG_FILE}` : `chore: record core version and drop the template ${INSTANCE_CONFIG_FILE}`;
|
|
4825
|
-
|
|
4826
|
-
|
|
4947
|
+
const committed = await github.commitFiles(org, repo, INSTANCE_FILE_BASE_BRANCH, files, message);
|
|
4948
|
+
const sharedSha = committed ?? await github.getBranchSha(org, repo, INSTANCE_FILE_BASE_BRANCH);
|
|
4949
|
+
for (const branch of INSTANCE_FILE_FOLLOWER_BRANCHES) {
|
|
4950
|
+
await github.fastForwardBranch(org, repo, branch, sharedSha);
|
|
4827
4951
|
}
|
|
4828
4952
|
}
|
|
4829
4953
|
function printPlan2(config) {
|