@kungfu-tech/buildchain 2.4.2-alpha.0 → 2.4.2-alpha.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/actions/promote-buildchain-ref/README.md +9 -3
- package/bin/buildchain.mjs +5 -1
- package/docs/binary-distribution.md +6 -1
- package/docs/reusable-build-surface.md +23 -0
- package/docs/web-surface-deployments.md +37 -3
- package/package.json +1 -1
- package/scripts/build-contract-core.mjs +156 -0
- package/scripts/verify-publish-channel-ref.mjs +125 -0
|
@@ -125,6 +125,11 @@ contract:
|
|
|
125
125
|
specific package publication. Direct `alpha/*` or `release/*` channel refs are
|
|
126
126
|
not valid publish source locks when `require-publish-source-lock` is enabled,
|
|
127
127
|
and a mismatched `publish-source-sha` fails before any promotion or publish side effects begin.
|
|
128
|
+
The reusable build workflow performs the cheaper channel-ref preflight earlier:
|
|
129
|
+
after source-lock resolution and before the build matrix, it requires the target
|
|
130
|
+
channel ref such as `alpha/v22/v22.22` or `release/v22/v22.22` to already point
|
|
131
|
+
at the locked `publish-source-sha`. If not, maintainers should merge the source
|
|
132
|
+
commit through the channel PR first.
|
|
128
133
|
|
|
129
134
|
When enabled, the action creates or resumes a release transaction keyed by
|
|
130
135
|
repository, version, source SHA, and target ref. It persists that transaction to
|
|
@@ -232,9 +237,10 @@ governance semantics:
|
|
|
232
237
|
The promotion workflow should use `BUILDCHAIN_PROMOTION_TOKEN` for non-dry-run
|
|
233
238
|
promotion. The token is the buildchain equivalent of the old ABV runner release
|
|
234
239
|
authority: protected branch review and check rules guard human channel merges,
|
|
235
|
-
while
|
|
236
|
-
|
|
237
|
-
|
|
240
|
+
while the reusable build trust gate now checks the source-lock channel HEAD and
|
|
241
|
+
merged same-repository PR lineage before heavy build runners start. This action
|
|
242
|
+
still independently rechecks PR lineage, alpha/release tree equivalence, and
|
|
243
|
+
generated version-state verification before moving channel refs and tags.
|
|
238
244
|
|
|
239
245
|
The tag names intentionally follow the old ABV release semantics:
|
|
240
246
|
exact release tags are `vX.Y.Z`, exact alpha tags are `vX.Y.Z-alpha.N`, floating
|
package/bin/buildchain.mjs
CHANGED
|
@@ -97,7 +97,7 @@ function usage() {
|
|
|
97
97
|
[--path <jsonl>] -- <command> [args...]
|
|
98
98
|
buildchain web-surface ...
|
|
99
99
|
buildchain infra-contract ...
|
|
100
|
-
buildchain publish-source <lock|manifest|verify-lock|validate-anchored-release> ...
|
|
100
|
+
buildchain publish-source <lock|manifest|verify-lock|verify-channel-ref|validate-anchored-release> ...
|
|
101
101
|
buildchain build-contract ...
|
|
102
102
|
|
|
103
103
|
Examples:
|
|
@@ -823,6 +823,10 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
823
823
|
runScript("verify-publish-source-lock.mjs", publishArgs);
|
|
824
824
|
return;
|
|
825
825
|
}
|
|
826
|
+
if (mode === "verify-channel-ref") {
|
|
827
|
+
runScript("verify-publish-channel-ref.mjs", publishArgs);
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
826
830
|
if (mode === "validate-anchored-release") {
|
|
827
831
|
const report = validateAnchoredPackageRelease({
|
|
828
832
|
cwd: readFlag(publishArgs, "cwd", process.cwd()),
|
|
@@ -16,6 +16,12 @@ The release lane does not upload loose top-level `buildchain` or
|
|
|
16
16
|
inside their archives, so top-level loose assets would collide when matrix
|
|
17
17
|
artifacts are merged.
|
|
18
18
|
|
|
19
|
+
Release asset upload is gated before the matrix starts. Manual
|
|
20
|
+
`workflow_dispatch` runs are binary dry-runs only and must keep
|
|
21
|
+
`upload-release=false`; real GitHub Release uploads must come from a true
|
|
22
|
+
`v*` tag-triggered run so an invalid manual upload request cannot spend the
|
|
23
|
+
three-platform build matrix and then fail at `gh release upload`.
|
|
24
|
+
|
|
19
25
|
Each archive is accompanied by:
|
|
20
26
|
|
|
21
27
|
- a platform manifest from the standalone binary builder;
|
|
@@ -67,4 +73,3 @@ node scripts/create-release-bundle.mjs \
|
|
|
67
73
|
--output-dir .buildchain/release-passport \
|
|
68
74
|
--tag v0.0.0-local
|
|
69
75
|
```
|
|
70
|
-
|
|
@@ -308,6 +308,17 @@ that SHA in every build job, and uses the same SHA in artifact names, manifests,
|
|
|
308
308
|
and aggregate summaries. Reruns therefore rebuild the same source tree even if a
|
|
309
309
|
gate branch moves later.
|
|
310
310
|
|
|
311
|
+
Before any heavy build matrix is scheduled, the workflow also verifies that the
|
|
312
|
+
target channel ref implied by the source lock already points at
|
|
313
|
+
`publish-source-sha` and that the target channel HEAD came from the required
|
|
314
|
+
merged same-repository channel PR. `publish-gate/alpha/<line>/<version>` must
|
|
315
|
+
match `alpha/<line>` and have PR lineage `dev/<line> -> alpha/<line>`;
|
|
316
|
+
`publish-gate/release/<line>/<version>` must match `release/<line>` and have PR
|
|
317
|
+
lineage `alpha/<line> -> release/<line>`. If either check fails, the run fails
|
|
318
|
+
fast with a diagnostic telling maintainers to merge the source commit through
|
|
319
|
+
the channel PR first. This keeps verify from spending runner time on a source
|
|
320
|
+
tree that cannot legally enter the requested publish channel.
|
|
321
|
+
|
|
311
322
|
The resolved release manifest is uploaded as an artifact and emitted as
|
|
312
323
|
`release-manifest-json`. It records:
|
|
313
324
|
|
|
@@ -335,6 +346,18 @@ If the branch tip no longer matches the manifest SHA, the publish job must fail
|
|
|
335
346
|
closed. Moving a gate branch creates a new publish decision and should produce a
|
|
336
347
|
new build run.
|
|
337
348
|
|
|
349
|
+
Custom publish jobs can also repeat the channel-ref preflight:
|
|
350
|
+
|
|
351
|
+
```yaml
|
|
352
|
+
- name: Verify publish channel ref still matches
|
|
353
|
+
run: node .buildchain/runtime/scripts/verify-publish-channel-ref.mjs
|
|
354
|
+
env:
|
|
355
|
+
BUILDCHAIN_PUBLISH_SOURCE_REF: ${{ needs.build.outputs.publish-source-ref }}
|
|
356
|
+
BUILDCHAIN_PUBLISH_SOURCE_SHA: ${{ needs.build.outputs.publish-source-sha }}
|
|
357
|
+
BUILDCHAIN_SOURCE_REPOSITORY: ${{ github.repository }}
|
|
358
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
359
|
+
```
|
|
360
|
+
|
|
338
361
|
Anchored/manual package release jobs should also make the Buildchain promotion
|
|
339
362
|
action validate that publication is entering through the same
|
|
340
363
|
`publish-gate/{alpha,release,major}` source-lock contract before any package
|
|
@@ -368,6 +368,7 @@ The reusable workflow maps GitHub events to Buildchain web-surface semantics:
|
|
|
368
368
|
| `pull_request` opened / synchronized / reopened | validate, build, verify, and plan `preview` for `pr-N` |
|
|
369
369
|
| `pull_request` closed | plan apply-mode cleanup for the `pr-N` preview alias and manifest |
|
|
370
370
|
| `push` to `main` | validate, build, verify, and plan `staging` from the merged `main` SHA |
|
|
371
|
+
| `push` to `main` from a matching release PR merge | validate the associated release PR, plan `production`, and enter the configured GitHub Environment gate |
|
|
371
372
|
| `workflow_dispatch` with `production-approved = true` | plan `production` and enter the configured GitHub Environment gate |
|
|
372
373
|
|
|
373
374
|
The optional `buildchain-ref` input is empty by default. Empty keeps the
|
|
@@ -427,6 +428,7 @@ jobs:
|
|
|
427
428
|
staging-apply: true
|
|
428
429
|
staging-aws-role-arn: arn:aws:iam::123456789012:role/site-staging-github-actions
|
|
429
430
|
production-apply: false
|
|
431
|
+
production-release-on-main: false
|
|
430
432
|
production-aws-role-arn: arn:aws:iam::123456789012:role/site-production-github-actions
|
|
431
433
|
production-environment: production
|
|
432
434
|
```
|
|
@@ -439,9 +441,41 @@ When enabled, Buildchain owns the full release apply state machine:
|
|
|
439
441
|
only.
|
|
440
442
|
- Pushes to `main` run staging `deploy-apply --dry-run false` with the staging
|
|
441
443
|
role.
|
|
442
|
-
- Production runs
|
|
443
|
-
|
|
444
|
-
|
|
444
|
+
- Production runs when `production-apply` is true and either:
|
|
445
|
+
- a trusted `workflow_dispatch` passes `production-approved=true`; or
|
|
446
|
+
- `production-release-on-main=true` and the `main` push commit is associated
|
|
447
|
+
with exactly one same-repository, merged release pull request matching
|
|
448
|
+
`production-release-label` and `production-release-head-prefix`.
|
|
449
|
+
The production job is then gated by the configured GitHub Environment.
|
|
450
|
+
|
|
451
|
+
For release-PR publishing, callers opt in explicitly:
|
|
452
|
+
|
|
453
|
+
```yaml
|
|
454
|
+
jobs:
|
|
455
|
+
web-surface:
|
|
456
|
+
uses: kungfu-systems/buildchain/.github/workflows/.web-surface.yml@v2
|
|
457
|
+
with:
|
|
458
|
+
build-command: npm run build
|
|
459
|
+
verify-command: npm run check
|
|
460
|
+
artifact-path: dist
|
|
461
|
+
production-apply: ${{ github.event_name == 'push' && github.ref_name == 'main' }}
|
|
462
|
+
production-release-on-main: true
|
|
463
|
+
production-release-label: buildchain-release
|
|
464
|
+
production-release-head-prefix: feature/release-
|
|
465
|
+
production-aws-role-arn: arn:aws:iam::123456789012:role/site-production-github-actions
|
|
466
|
+
production-environment: production
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
The merge button becomes the production approval only for a PR that carries the
|
|
470
|
+
release label and comes from the configured source-branch prefix. Ordinary
|
|
471
|
+
pull requests merged into `main` keep the staging plan behavior and do not
|
|
472
|
+
publish production.
|
|
473
|
+
|
|
474
|
+
Apply-only inputs are validated before the caller build or verification command
|
|
475
|
+
runs. If the current event would run preview, staging, or production apply,
|
|
476
|
+
missing role inputs or a production apply without `production-approved=true`
|
|
477
|
+
on manual dispatch fail immediately instead of spending the build and plan jobs
|
|
478
|
+
first.
|
|
445
479
|
|
|
446
480
|
Callers must grant `id-token: write` for OIDC role assumption. Preview comments
|
|
447
481
|
also need `pull-requests: write`. The AWS roles remain caller-owned and should
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "2.4.2-alpha.
|
|
3
|
+
"version": "2.4.2-alpha.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
|
|
6
6
|
"repository": "https://github.com/kungfu-systems/buildchain",
|
|
@@ -348,6 +348,162 @@ export function verifyPublishSourceLock({
|
|
|
348
348
|
};
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
+
export function resolvePublishChannelTargetRef({
|
|
352
|
+
sourceRef = "",
|
|
353
|
+
targetRef = "",
|
|
354
|
+
} = {}) {
|
|
355
|
+
const requestedTarget = normalizeGitRefName(targetRef);
|
|
356
|
+
if (requestedTarget) {
|
|
357
|
+
return requestedTarget;
|
|
358
|
+
}
|
|
359
|
+
const parsed = parsePublishSourceRef(sourceRef);
|
|
360
|
+
if (!parsed.enabled || parsed.anchor) {
|
|
361
|
+
return "";
|
|
362
|
+
}
|
|
363
|
+
if (parsed.channel === "alpha" || parsed.channel === "release") {
|
|
364
|
+
return `${parsed.channel}/${parsed.line}`;
|
|
365
|
+
}
|
|
366
|
+
if (parsed.channel === "major") {
|
|
367
|
+
return parsed.legacyAlias ? "major-gate" : "publish-gate/major";
|
|
368
|
+
}
|
|
369
|
+
return "";
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function expectedPublishChannelHeadRef(targetRef) {
|
|
373
|
+
const ref = normalizeGitRefName(targetRef);
|
|
374
|
+
if (ref === "publish-gate/major" || ref === "major-gate") {
|
|
375
|
+
return "release/vN/vN.M";
|
|
376
|
+
}
|
|
377
|
+
const match = ref.match(/^(alpha|release)\/v(\d+)\/v(\d+)\.(\d+)$/);
|
|
378
|
+
if (!match) {
|
|
379
|
+
throw new Error(
|
|
380
|
+
`publish target channel ref must be alpha/vN/vN.M, release/vN/vN.M, publish-gate/major, or major-gate; got ${ref || "<empty>"}`,
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
const [, channel, major, lineMajor, minor] = match;
|
|
384
|
+
if (major !== lineMajor) {
|
|
385
|
+
throw new Error(`publish target channel ref major mismatch: ${ref}`);
|
|
386
|
+
}
|
|
387
|
+
return channel === "alpha"
|
|
388
|
+
? `dev/v${major}/v${major}.${minor}`
|
|
389
|
+
: `alpha/v${major}/v${major}.${minor}`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function isReleaseLineRef(ref = "") {
|
|
393
|
+
const match = String(ref || "").match(/^release\/v(\d+)\/v(\d+)\.(\d+)$/);
|
|
394
|
+
return !!match && match[1] === match[2];
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function verifyPublishChannelPrLineage({
|
|
398
|
+
sourceRef = "",
|
|
399
|
+
sourceSha = "",
|
|
400
|
+
targetRef = "",
|
|
401
|
+
repository = "",
|
|
402
|
+
pullRequests = [],
|
|
403
|
+
} = {}) {
|
|
404
|
+
const parsed = parsePublishSourceRef(sourceRef);
|
|
405
|
+
const resolvedTargetRef = resolvePublishChannelTargetRef({
|
|
406
|
+
sourceRef,
|
|
407
|
+
targetRef,
|
|
408
|
+
});
|
|
409
|
+
if (!parsed.enabled || parsed.anchor || !resolvedTargetRef) {
|
|
410
|
+
return {
|
|
411
|
+
ok: true,
|
|
412
|
+
skipped: true,
|
|
413
|
+
sourceRef: parsed.sourceRef || normalizeGitRefName(sourceRef),
|
|
414
|
+
targetRef: resolvedTargetRef,
|
|
415
|
+
reason: !parsed.enabled
|
|
416
|
+
? "publish source ref is not configured"
|
|
417
|
+
: parsed.anchor
|
|
418
|
+
? "publish-gate/anchor has no channel target ref"
|
|
419
|
+
: "publish target channel ref is not configured",
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
const sha = assertSha(sourceSha, "sourceSha");
|
|
423
|
+
const expectedHeadRef = expectedPublishChannelHeadRef(resolvedTargetRef);
|
|
424
|
+
const expectedRepository = String(repository || "").trim();
|
|
425
|
+
if (!expectedRepository) {
|
|
426
|
+
throw new Error("repository is required to verify publish channel PR lineage");
|
|
427
|
+
}
|
|
428
|
+
const matchingPullRequest = (pullRequests || []).find((pullRequest) => {
|
|
429
|
+
const baseRef = pullRequest?.base?.ref || "";
|
|
430
|
+
const headRef = pullRequest?.head?.ref || "";
|
|
431
|
+
const headRepo = pullRequest?.head?.repo?.full_name || "";
|
|
432
|
+
if (!pullRequest?.merged_at || baseRef !== resolvedTargetRef || headRepo !== expectedRepository) {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
if (resolvedTargetRef === "publish-gate/major" || resolvedTargetRef === "major-gate") {
|
|
436
|
+
return isReleaseLineRef(headRef);
|
|
437
|
+
}
|
|
438
|
+
return headRef === expectedHeadRef;
|
|
439
|
+
});
|
|
440
|
+
if (!matchingPullRequest) {
|
|
441
|
+
throw new Error(
|
|
442
|
+
`publish source-lock PR lineage mismatch: ${parsed.sourceRef} is locked at ${sha}, but target channel ref ${resolvedTargetRef} must come from a merged same-repository PR ${expectedHeadRef} -> ${resolvedTargetRef}. Merge the source commit through that channel PR before running publish verification.`,
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
return {
|
|
446
|
+
ok: true,
|
|
447
|
+
skipped: false,
|
|
448
|
+
sourceRef: parsed.sourceRef,
|
|
449
|
+
sourceSha: sha,
|
|
450
|
+
targetRef: resolvedTargetRef,
|
|
451
|
+
expectedHeadRef,
|
|
452
|
+
pullRequest: {
|
|
453
|
+
number: matchingPullRequest.number,
|
|
454
|
+
url: matchingPullRequest.html_url || matchingPullRequest.url || "",
|
|
455
|
+
headRef: matchingPullRequest.head?.ref || "",
|
|
456
|
+
baseRef: matchingPullRequest.base?.ref || "",
|
|
457
|
+
mergedAt: matchingPullRequest.merged_at || "",
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export function verifyPublishChannelRef({
|
|
463
|
+
sourceRef = "",
|
|
464
|
+
sourceSha = "",
|
|
465
|
+
targetRef = "",
|
|
466
|
+
targetSha = "",
|
|
467
|
+
} = {}) {
|
|
468
|
+
const parsed = parsePublishSourceRef(sourceRef);
|
|
469
|
+
const resolvedTargetRef = resolvePublishChannelTargetRef({
|
|
470
|
+
sourceRef,
|
|
471
|
+
targetRef,
|
|
472
|
+
});
|
|
473
|
+
if (!parsed.enabled || parsed.anchor || !resolvedTargetRef) {
|
|
474
|
+
return {
|
|
475
|
+
ok: true,
|
|
476
|
+
skipped: true,
|
|
477
|
+
sourceRef: parsed.sourceRef || normalizeGitRefName(sourceRef),
|
|
478
|
+
targetRef: resolvedTargetRef,
|
|
479
|
+
reason: !parsed.enabled
|
|
480
|
+
? "publish source ref is not configured"
|
|
481
|
+
: parsed.anchor
|
|
482
|
+
? "publish-gate/anchor has no channel target ref"
|
|
483
|
+
: "publish target channel ref is not configured",
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
const expected = assertSha(sourceSha, "sourceSha");
|
|
487
|
+
const current = assertSha(targetSha, "targetSha");
|
|
488
|
+
if (expected !== current) {
|
|
489
|
+
const channelHint =
|
|
490
|
+
parsed.channel === "alpha" || parsed.channel === "release"
|
|
491
|
+
? `Merge the source commit through the channel PR into ${resolvedTargetRef} before running publish verification.`
|
|
492
|
+
: `Move ${resolvedTargetRef} to the reviewed source commit before running publish verification.`;
|
|
493
|
+
throw new Error(
|
|
494
|
+
`publish source-lock target mismatch: ${parsed.sourceRef} is locked at ${expected}, but target channel ref ${resolvedTargetRef} points at ${current}. ${channelHint}`,
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
ok: true,
|
|
499
|
+
skipped: false,
|
|
500
|
+
sourceRef: parsed.sourceRef,
|
|
501
|
+
sourceSha: expected,
|
|
502
|
+
targetRef: resolvedTargetRef,
|
|
503
|
+
targetSha: current,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
351
507
|
function normalizePackageSet(packages) {
|
|
352
508
|
if (!Array.isArray(packages) || packages.length === 0) {
|
|
353
509
|
throw new Error("package set must include at least one package");
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import {
|
|
4
|
+
resolvePublishChannelTargetRef,
|
|
5
|
+
verifyPublishChannelPrLineage,
|
|
6
|
+
verifyPublishChannelRef,
|
|
7
|
+
} from "./build-contract-core.mjs";
|
|
8
|
+
import {
|
|
9
|
+
normalizeSourceRef,
|
|
10
|
+
resolvePublishSourceRefSha,
|
|
11
|
+
} from "./publish-source-ref-resolver.mjs";
|
|
12
|
+
|
|
13
|
+
function readEnv(env, name, fallback = "") {
|
|
14
|
+
return env[name] || fallback;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function splitRepository(repository) {
|
|
18
|
+
const value = String(repository || "").trim();
|
|
19
|
+
const match = value.match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
20
|
+
if (!match) {
|
|
21
|
+
throw new Error(`BUILDCHAIN_SOURCE_REPOSITORY must be owner/repo, got: ${value || "<empty>"}`);
|
|
22
|
+
}
|
|
23
|
+
return { owner: match[1], repo: match[2] };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function resolveAssociatedPullRequests({
|
|
27
|
+
repository,
|
|
28
|
+
sha,
|
|
29
|
+
env = process.env,
|
|
30
|
+
fetchImpl = globalThis.fetch,
|
|
31
|
+
} = {}) {
|
|
32
|
+
if (typeof fetchImpl !== "function") {
|
|
33
|
+
throw new Error("fetch is required to verify publish channel PR lineage through GitHub REST API");
|
|
34
|
+
}
|
|
35
|
+
const { owner, repo } = splitRepository(repository);
|
|
36
|
+
const apiBase = String(readEnv(env, "GITHUB_API_URL", "https://api.github.com")).replace(/\/+$/, "");
|
|
37
|
+
const url = `${apiBase}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/commits/${encodeURIComponent(sha)}/pulls`;
|
|
38
|
+
const headers = {
|
|
39
|
+
Accept: "application/vnd.github+json",
|
|
40
|
+
"User-Agent": "buildchain-publish-channel-verifier",
|
|
41
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
42
|
+
};
|
|
43
|
+
const token = readEnv(env, "GITHUB_TOKEN");
|
|
44
|
+
if (token) {
|
|
45
|
+
headers.Authorization = `Bearer ${token}`;
|
|
46
|
+
}
|
|
47
|
+
const response = await fetchImpl(url, { headers });
|
|
48
|
+
if (!response?.ok) {
|
|
49
|
+
let detail = "";
|
|
50
|
+
try {
|
|
51
|
+
const body = await response.json();
|
|
52
|
+
detail = body?.message ? `: ${body.message}` : "";
|
|
53
|
+
} catch {
|
|
54
|
+
detail = "";
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`publish channel PR lineage could not be read for ${sha} (GitHub API ${response?.status || "unknown"}${detail})`);
|
|
57
|
+
}
|
|
58
|
+
const body = await response.json();
|
|
59
|
+
if (!Array.isArray(body)) {
|
|
60
|
+
throw new Error("publish channel PR lineage response must be an array");
|
|
61
|
+
}
|
|
62
|
+
return body;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function verifyPublishChannelRefCli({
|
|
66
|
+
env = process.env,
|
|
67
|
+
fetchImpl = globalThis.fetch,
|
|
68
|
+
} = {}) {
|
|
69
|
+
const sourceRef = normalizeSourceRef(readEnv(env, "BUILDCHAIN_PUBLISH_SOURCE_REF"));
|
|
70
|
+
const sourceSha = readEnv(env, "BUILDCHAIN_PUBLISH_SOURCE_SHA");
|
|
71
|
+
const targetRef = resolvePublishChannelTargetRef({
|
|
72
|
+
sourceRef,
|
|
73
|
+
targetRef: readEnv(env, "BUILDCHAIN_PUBLISH_TARGET_REF"),
|
|
74
|
+
});
|
|
75
|
+
if (!targetRef) {
|
|
76
|
+
return verifyPublishChannelRef({ sourceRef, sourceSha, targetRef });
|
|
77
|
+
}
|
|
78
|
+
const repository = readEnv(env, "BUILDCHAIN_SOURCE_REPOSITORY", readEnv(env, "GITHUB_REPOSITORY"));
|
|
79
|
+
const targetSha = readEnv(env, "BUILDCHAIN_CURRENT_TARGET_SHA")
|
|
80
|
+
|| await resolvePublishSourceRefSha({
|
|
81
|
+
repository,
|
|
82
|
+
sourceRef: targetRef,
|
|
83
|
+
env,
|
|
84
|
+
fetchImpl,
|
|
85
|
+
});
|
|
86
|
+
const refResult = verifyPublishChannelRef({
|
|
87
|
+
sourceRef,
|
|
88
|
+
sourceSha,
|
|
89
|
+
targetRef,
|
|
90
|
+
targetSha,
|
|
91
|
+
});
|
|
92
|
+
if (refResult.skipped) {
|
|
93
|
+
return refResult;
|
|
94
|
+
}
|
|
95
|
+
const pullRequestsJson = readEnv(env, "BUILDCHAIN_CURRENT_TARGET_PULLS_JSON");
|
|
96
|
+
const pullRequests = pullRequestsJson
|
|
97
|
+
? JSON.parse(pullRequestsJson)
|
|
98
|
+
: await resolveAssociatedPullRequests({
|
|
99
|
+
repository,
|
|
100
|
+
sha: sourceSha,
|
|
101
|
+
env,
|
|
102
|
+
fetchImpl,
|
|
103
|
+
});
|
|
104
|
+
const lineageResult = verifyPublishChannelPrLineage({
|
|
105
|
+
sourceRef,
|
|
106
|
+
sourceSha,
|
|
107
|
+
targetRef,
|
|
108
|
+
repository,
|
|
109
|
+
pullRequests,
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
...refResult,
|
|
113
|
+
prLineage: lineageResult,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
118
|
+
try {
|
|
119
|
+
const result = await verifyPublishChannelRefCli();
|
|
120
|
+
console.log(JSON.stringify(result));
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error(`::error::${String(error.message || error).replace(/\r?\n/g, "%0A")}`);
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
}
|
|
125
|
+
}
|