@kungfu-tech/buildchain 2.4.10-alpha.0 → 2.4.10-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/AGENTS.md +5 -0
- package/LICENSE-POLICY.md +16 -0
- package/README.md +13 -0
- package/SECURITY.md +7 -0
- package/docs/MAP.md +5 -0
- package/docs/release-candidate.md +12 -1
- package/docs/reusable-build-surface.md +25 -5
- package/package.json +1 -1
- package/scripts/release-candidate-resolver.mjs +237 -0
package/AGENTS.md
CHANGED
|
@@ -70,11 +70,16 @@ and rebuilds every action bundle.
|
|
|
70
70
|
- Bugs, feature requests, questions, and documentation issues go through GitHub
|
|
71
71
|
issues; security vulnerabilities use private vulnerability reporting - see
|
|
72
72
|
[`SECURITY.md`](SECURITY.md).
|
|
73
|
+
- Brand, hosted-service, and upstream-provider boundaries are documented in
|
|
74
|
+
[`TRADEMARK.md`](TRADEMARK.md), [`ACCEPTABLE_USE.md`](ACCEPTABLE_USE.md), and
|
|
75
|
+
[`PROVIDER_COMPLIANCE.md`](PROVIDER_COMPLIANCE.md).
|
|
73
76
|
|
|
74
77
|
## Ground rules
|
|
75
78
|
|
|
76
79
|
- Never include secrets, credentials, tokens, or private logs in code, commits,
|
|
77
80
|
issues, or pull requests.
|
|
81
|
+
- Do not build or document official release integrations that bypass provider
|
|
82
|
+
protections, hide credential boundaries, or forge release evidence.
|
|
78
83
|
- Keep generated action bundles in sync with source changes.
|
|
79
84
|
- Keep documentation in sync with behavior, especially release governance and
|
|
80
85
|
reusable workflow contracts.
|
package/LICENSE-POLICY.md
CHANGED
|
@@ -38,12 +38,28 @@ Apache-2.0 grants copyright and patent permissions. It does not grant trademark
|
|
|
38
38
|
rights. Names, logos, domain names, and product marks such as "Kungfu" and
|
|
39
39
|
"Buildchain" may be governed by separate brand guidelines.
|
|
40
40
|
|
|
41
|
+
See [TRADEMARK.md](TRADEMARK.md) for the official project mark and fork identity
|
|
42
|
+
boundary.
|
|
43
|
+
|
|
41
44
|
## Hosted and commercial services
|
|
42
45
|
|
|
43
46
|
The open source license covers this repository. Hosted services, team features,
|
|
44
47
|
enterprise support, managed deployments, commercial connectors, or other
|
|
45
48
|
services offered by the project maintainers may use separate terms.
|
|
46
49
|
|
|
50
|
+
See [ACCEPTABLE_USE.md](ACCEPTABLE_USE.md) for acceptable use of official hosted,
|
|
51
|
+
managed, or maintainer-operated services.
|
|
52
|
+
|
|
53
|
+
## Upstream provider integrations
|
|
54
|
+
|
|
55
|
+
Official Buildchain integrations should use documented provider APIs, workflow
|
|
56
|
+
surfaces, package-registry flows, cloud APIs, OIDC, and least-privilege
|
|
57
|
+
credentials. They should not bypass provider protections, hide credential
|
|
58
|
+
boundaries, or forge release evidence.
|
|
59
|
+
|
|
60
|
+
See [PROVIDER_COMPLIANCE.md](PROVIDER_COMPLIANCE.md) for the official provider
|
|
61
|
+
integration posture.
|
|
62
|
+
|
|
47
63
|
## Third-party software
|
|
48
64
|
|
|
49
65
|
Buildchain depends on third-party software. Source dependencies are declared in
|
package/README.md
CHANGED
|
@@ -85,6 +85,19 @@ const report = verifyBuildchainLogEvents({
|
|
|
85
85
|
The package also ships `dist/site/` as the Buildchain-owned fact source for
|
|
86
86
|
`buildchain.libkungfu.dev`.
|
|
87
87
|
|
|
88
|
+
## Project Governance
|
|
89
|
+
|
|
90
|
+
- [`LICENSE-POLICY.md`](LICENSE-POLICY.md) explains the Apache-2.0 project
|
|
91
|
+
license, DCO-based contributions, and third-party notice boundary.
|
|
92
|
+
- [`TRADEMARK.md`](TRADEMARK.md) explains official project marks and fork
|
|
93
|
+
identity boundaries.
|
|
94
|
+
- [`ACCEPTABLE_USE.md`](ACCEPTABLE_USE.md) explains acceptable use of official
|
|
95
|
+
services and maintainer-operated infrastructure.
|
|
96
|
+
- [`PROVIDER_COMPLIANCE.md`](PROVIDER_COMPLIANCE.md) explains the official
|
|
97
|
+
posture for GitHub, npm, cloud, credential, release evidence, and other
|
|
98
|
+
provider integrations.
|
|
99
|
+
- [`SECURITY.md`](SECURITY.md) explains private vulnerability reporting.
|
|
100
|
+
|
|
88
101
|
Native build consumers can import the diagnostics toolkit instead of copying
|
|
89
102
|
repository-local probes:
|
|
90
103
|
|
package/SECURITY.md
CHANGED
|
@@ -35,6 +35,13 @@ Security reports may cover:
|
|
|
35
35
|
- local file access, path traversal, or unsafe archive handling;
|
|
36
36
|
- credential, token, or private data exposure.
|
|
37
37
|
|
|
38
|
+
Service-abuse, provider-compliance, credential-handling, misleading official
|
|
39
|
+
identity, or compromised release-evidence reports may also be
|
|
40
|
+
security-sensitive. Use private vulnerability reporting when public disclosure
|
|
41
|
+
would expose credentials, provider account details, user data, or an exploitable
|
|
42
|
+
bypass. See `ACCEPTABLE_USE.md`, `PROVIDER_COMPLIANCE.md`, and `TRADEMARK.md`
|
|
43
|
+
for the related policy boundaries.
|
|
44
|
+
|
|
38
45
|
## Public disclosure
|
|
39
46
|
|
|
40
47
|
Please allow maintainers time to investigate and prepare a fix before public
|
package/docs/MAP.md
CHANGED
|
@@ -43,6 +43,7 @@ running artifact), *use* (consume / extend) - and a **status**:
|
|
|
43
43
|
| How can a consumer workflow report a Buildchain-owned failure back to Buildchain? | [`consumer-issue-reporting.md`](consumer-issue-reporting.md) + [`../actions/report-buildchain-issue/README.md`](../actions/report-buildchain-issue/README.md) | use | stable |
|
|
44
44
|
| What do the fixture repositories demonstrate? | [`../fixtures/libnode-shaped/README.md`](../fixtures/libnode-shaped/README.md), [`../fixtures/publish-transaction-shaped/README.md`](../fixtures/publish-transaction-shaped/README.md), [`../fixtures/web-surface-shaped/README.md`](../fixtures/web-surface-shaped/README.md) | verify | stable |
|
|
45
45
|
| What license and contribution terms apply? | [`../LICENSE`](../LICENSE) + [`../LICENSE-POLICY.md`](../LICENSE-POLICY.md) | use | stable |
|
|
46
|
+
| What trademark, official-service, and provider-compliance boundaries apply? | [`../TRADEMARK.md`](../TRADEMARK.md) + [`../ACCEPTABLE_USE.md`](../ACCEPTABLE_USE.md) + [`../PROVIDER_COMPLIANCE.md`](../PROVIDER_COMPLIANCE.md) | use | stable |
|
|
46
47
|
| How do I report a vulnerability? | [`../SECURITY.md`](../SECURITY.md) | use | stable |
|
|
47
48
|
|
|
48
49
|
## Also asking about
|
|
@@ -88,6 +89,10 @@ running artifact), *use* (consume / extend) - and a **status**:
|
|
|
88
89
|
[`site-bundle-contract.md`](site-bundle-contract.md).
|
|
89
90
|
- **sites / web previews / staging / production gates** ->
|
|
90
91
|
[`web-surface-deployments.md`](web-surface-deployments.md).
|
|
92
|
+
- **trademark / fork / official service / provider compliance / release
|
|
93
|
+
evidence boundary** -> [`../TRADEMARK.md`](../TRADEMARK.md),
|
|
94
|
+
[`../ACCEPTABLE_USE.md`](../ACCEPTABLE_USE.md), and
|
|
95
|
+
[`../PROVIDER_COMPLIANCE.md`](../PROVIDER_COMPLIANCE.md).
|
|
91
96
|
|
|
92
97
|
## How this map is maintained
|
|
93
98
|
|
|
@@ -64,4 +64,15 @@ Buildchain-owned promotion workflow resolves the matching same-repository
|
|
|
64
64
|
merged channel PR and downloads its PR-stage RC passport automatically before
|
|
65
65
|
promotion starts. The consumer wrapper defaults to a PR-stage workflow file
|
|
66
66
|
named `build.yml` with display name `Build`, and filters the RC passport and
|
|
67
|
-
build summary by the configured `artifact-name` before promotion.
|
|
67
|
+
build summary by the configured `artifact-name` before promotion. It also
|
|
68
|
+
downloads payload artifacts from the same PR-stage run, validates the required
|
|
69
|
+
payload count, passes downloaded platform manifests into the release passport,
|
|
70
|
+
and either forwards an explicit `publish-required-artifacts-json` value or
|
|
71
|
+
generates one before calling `promote-buildchain-ref`. The default npm path
|
|
72
|
+
generates that requirement list from the downloaded `.tgz` payloads themselves:
|
|
73
|
+
Buildchain reads `package/package.json` inside each tarball for the real scoped
|
|
74
|
+
package name and version, computes npm-style `sha512-...` integrity over the
|
|
75
|
+
tarball bytes, marks `publish-package-main` as `role: main`, and marks every
|
|
76
|
+
other package as `role: platform`. Consumer workflows therefore stay
|
|
77
|
+
declarative and do not need their own artifact download or publish-evidence
|
|
78
|
+
generation scripts.
|
|
@@ -377,12 +377,14 @@ The passport records two separate source identities:
|
|
|
377
377
|
commit used for publish authority.
|
|
378
378
|
|
|
379
379
|
The reusable promote wrapper resolves the merged PR, finds exactly one matching
|
|
380
|
-
PR-stage release-candidate artifact, downloads it
|
|
381
|
-
|
|
382
|
-
|
|
380
|
+
PR-stage release-candidate artifact, downloads it with the build summary and
|
|
381
|
+
payload artifacts from the same PR-stage run, validates the payload count,
|
|
382
|
+
compares the built tree with the promotion channel tree, locks
|
|
383
|
+
`publish-gate/{alpha,release,major}` to the promotion channel commit, and then
|
|
384
|
+
calls `actions/promote-buildchain-ref` with
|
|
383
385
|
`promote-only-release-candidate: "true"`. It does not call `.build.yml`, does
|
|
384
|
-
not create a matrix, and must fail before publish if the RC evidence
|
|
385
|
-
or ambiguous.
|
|
386
|
+
not create a matrix, and must fail before publish if the RC evidence or payload
|
|
387
|
+
set is missing or ambiguous.
|
|
386
388
|
|
|
387
389
|
```yaml
|
|
388
390
|
jobs:
|
|
@@ -400,8 +402,26 @@ jobs:
|
|
|
400
402
|
publish-target: npm
|
|
401
403
|
runner-preset: github-hosted
|
|
402
404
|
trusted-publishing: true
|
|
405
|
+
required-status-check: check
|
|
406
|
+
required-artifact-count: 3
|
|
407
|
+
publish-dist-tag: alpha
|
|
408
|
+
publish-package-set-order: platforms-first-main-last
|
|
409
|
+
publish-package-main: "@kungfu-tech/libnode"
|
|
410
|
+
release-passport-product-name: Libnode
|
|
403
411
|
```
|
|
404
412
|
|
|
413
|
+
`publish-required-artifacts-json` can still be passed explicitly for custom
|
|
414
|
+
publish targets. For the default `publish-artifact-kind: npm` path, consumers do
|
|
415
|
+
not download artifacts or run repository scripts to build publish evidence. The
|
|
416
|
+
wrapper downloads the PR-stage payload artifacts, finds the downloaded `.tgz`
|
|
417
|
+
packages, reads each tarball's `package/package.json` for the real scoped
|
|
418
|
+
package name and version, computes the npm `sha512-...` integrity from the
|
|
419
|
+
tarball bytes, marks the package matching `publish-package-main` as `role:
|
|
420
|
+
main`, marks the rest as `role: platform`, and passes the generated
|
|
421
|
+
`publish-required-artifacts-json` to `promote-buildchain-ref` before any publish
|
|
422
|
+
side effect. Downloaded platform manifests are still passed into the release
|
|
423
|
+
passport unless `release-passport-platform-manifest-paths` is set explicitly.
|
|
424
|
+
|
|
405
425
|
Custom publish jobs can also repeat the channel-ref preflight:
|
|
406
426
|
|
|
407
427
|
```yaml
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "2.4.10-alpha.
|
|
3
|
+
"version": "2.4.10-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",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import crypto from "node:crypto";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
@@ -118,6 +119,173 @@ function outputPath(filePath) {
|
|
|
118
119
|
return relative.startsWith("../") || relative === ".." ? filePath : relative;
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
function splitPatterns(value = "") {
|
|
123
|
+
return String(value || "")
|
|
124
|
+
.split(/\r?\n|,/)
|
|
125
|
+
.map((entry) => entry.trim())
|
|
126
|
+
.filter(Boolean);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function escapeRegExp(value) {
|
|
130
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function artifactPatternToRegExp(pattern) {
|
|
134
|
+
return new RegExp(`^${String(pattern).split("*").map(escapeRegExp).join(".*")}$`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function selectPayloadArtifacts({
|
|
138
|
+
artifacts = [],
|
|
139
|
+
artifactName = "",
|
|
140
|
+
sourceSha = "",
|
|
141
|
+
patterns = [],
|
|
142
|
+
} = {}) {
|
|
143
|
+
const prefix = String(artifactName || "").trim();
|
|
144
|
+
const sha = assertSha(sourceSha, "sourceSha");
|
|
145
|
+
const active = artifacts.filter((artifact) => !artifact.expired);
|
|
146
|
+
const excludedNames = new Set([
|
|
147
|
+
`${prefix}-release-candidate-${sha}`,
|
|
148
|
+
`${prefix}-summary-${sha}`,
|
|
149
|
+
`${prefix}-diagnostics-summary-${sha}`,
|
|
150
|
+
]);
|
|
151
|
+
const effectivePatterns = splitPatterns(patterns).length
|
|
152
|
+
? splitPatterns(patterns)
|
|
153
|
+
: [`${prefix}-manifest-*-${sha}`];
|
|
154
|
+
const matchers = effectivePatterns.map(artifactPatternToRegExp);
|
|
155
|
+
return active
|
|
156
|
+
.filter((artifact) => !excludedNames.has(String(artifact.name || "")))
|
|
157
|
+
.filter((artifact) => matchers.some((matcher) => matcher.test(String(artifact.name || ""))))
|
|
158
|
+
.sort((left, right) => String(left.name || "").localeCompare(String(right.name || "")));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function findDownloadedFiles(root, filename) {
|
|
162
|
+
const matches = [];
|
|
163
|
+
const stack = fs.existsSync(root) ? [root] : [];
|
|
164
|
+
while (stack.length) {
|
|
165
|
+
const current = stack.pop();
|
|
166
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
167
|
+
const fullPath = path.join(current, entry.name);
|
|
168
|
+
if (entry.isDirectory()) {
|
|
169
|
+
stack.push(fullPath);
|
|
170
|
+
} else if (entry.name === filename) {
|
|
171
|
+
matches.push(fullPath);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return matches.sort();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function findDownloadedFilesByExtension(root, extensions = []) {
|
|
179
|
+
const normalizedExtensions = extensions.map((extension) => String(extension || "").toLowerCase());
|
|
180
|
+
const matches = [];
|
|
181
|
+
const stack = fs.existsSync(root) ? [root] : [];
|
|
182
|
+
while (stack.length) {
|
|
183
|
+
const current = stack.pop();
|
|
184
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
185
|
+
const fullPath = path.join(current, entry.name);
|
|
186
|
+
if (entry.isDirectory()) {
|
|
187
|
+
stack.push(fullPath);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const lowerName = entry.name.toLowerCase();
|
|
191
|
+
if (normalizedExtensions.some((extension) => lowerName.endsWith(extension))) {
|
|
192
|
+
matches.push(fullPath);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return matches.sort();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function packageNameFromArtifactPath(filePath) {
|
|
200
|
+
const basename = path.basename(String(filePath || ""));
|
|
201
|
+
return basename
|
|
202
|
+
.replace(/\.tgz$/i, "")
|
|
203
|
+
.replace(/\.tar\.gz$/i, "")
|
|
204
|
+
.replace(/\.zip$/i, "");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function npmIntegrity(filePath) {
|
|
208
|
+
return `sha512-${crypto.createHash("sha512").update(fs.readFileSync(filePath)).digest("base64")}`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function readNpmPackageJsonFromTarball(tarballPath) {
|
|
212
|
+
const candidates = ["package/package.json", "./package/package.json"];
|
|
213
|
+
const errors = [];
|
|
214
|
+
for (const candidate of candidates) {
|
|
215
|
+
try {
|
|
216
|
+
return JSON.parse(execFileSync("tar", ["-xOf", tarballPath, candidate], { encoding: "utf8" }));
|
|
217
|
+
} catch (error) {
|
|
218
|
+
errors.push(error.stderr?.toString?.().trim() || error.message);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
throw new Error(`npm package tarball ${tarballPath} does not contain package/package.json: ${errors.filter(Boolean).join("; ")}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function readNpmPackageArtifact({
|
|
225
|
+
tarballPath,
|
|
226
|
+
mainPackage = "",
|
|
227
|
+
kind = "npm",
|
|
228
|
+
} = {}) {
|
|
229
|
+
const packageJson = readNpmPackageJsonFromTarball(tarballPath);
|
|
230
|
+
const name = String(packageJson.name || "").trim();
|
|
231
|
+
const version = String(packageJson.version || "").trim();
|
|
232
|
+
if (!name || !version) {
|
|
233
|
+
throw new Error(`npm package tarball ${tarballPath || "<empty>"} package.json must include name and version`);
|
|
234
|
+
}
|
|
235
|
+
const integrity = npmIntegrity(tarballPath);
|
|
236
|
+
return {
|
|
237
|
+
kind,
|
|
238
|
+
name,
|
|
239
|
+
ref: version,
|
|
240
|
+
digest: integrity,
|
|
241
|
+
integrity,
|
|
242
|
+
role: mainPackage && name === mainPackage ? "main" : "platform",
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function generatePublishRequiredArtifacts({
|
|
247
|
+
manifests = [],
|
|
248
|
+
version = "",
|
|
249
|
+
kind = "npm",
|
|
250
|
+
tarballPaths = [],
|
|
251
|
+
mainPackage = "",
|
|
252
|
+
} = {}) {
|
|
253
|
+
if (String(kind || "") === "npm" && tarballPaths.length > 0) {
|
|
254
|
+
const artifacts = tarballPaths
|
|
255
|
+
.map((tarballPath) => readNpmPackageArtifact({ tarballPath, mainPackage, kind }))
|
|
256
|
+
.sort((left, right) => `${left.role}:${left.name}`.localeCompare(`${right.role}:${right.name}`));
|
|
257
|
+
const seen = new Set();
|
|
258
|
+
for (const artifact of artifacts) {
|
|
259
|
+
const key = `${artifact.name}@${artifact.ref}`;
|
|
260
|
+
if (seen.has(key)) {
|
|
261
|
+
throw new Error(`duplicate npm package tarball for ${key}`);
|
|
262
|
+
}
|
|
263
|
+
seen.add(key);
|
|
264
|
+
}
|
|
265
|
+
return artifacts;
|
|
266
|
+
}
|
|
267
|
+
const ref = String(version || "").trim();
|
|
268
|
+
if (!ref) {
|
|
269
|
+
return [];
|
|
270
|
+
}
|
|
271
|
+
return manifests.flatMap((manifest) => {
|
|
272
|
+
const platform = manifest.platform?.id || manifest.platformId || "";
|
|
273
|
+
const files = Array.isArray(manifest.files) ? manifest.files : [];
|
|
274
|
+
return files
|
|
275
|
+
.filter((file) => file?.sha256)
|
|
276
|
+
.map((file) => ({
|
|
277
|
+
kind,
|
|
278
|
+
name: packageNameFromArtifactPath(file.path || file.name || manifest.artifactName || platform),
|
|
279
|
+
ref,
|
|
280
|
+
digest: String(file.sha256).startsWith("sha256:")
|
|
281
|
+
? String(file.sha256)
|
|
282
|
+
: `sha256:${file.sha256}`,
|
|
283
|
+
role: "platform",
|
|
284
|
+
platform,
|
|
285
|
+
}));
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
121
289
|
export function selectReleaseCandidateArtifacts({ artifacts = [], artifactName = "" }) {
|
|
122
290
|
const expectedPrefix = String(artifactName || "").trim();
|
|
123
291
|
const active = artifacts.filter((artifact) => !artifact.expired);
|
|
@@ -170,6 +338,10 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
170
338
|
workflowFile = DEFAULT_WORKFLOW_FILE,
|
|
171
339
|
workflowName = "Build Surface Fixture",
|
|
172
340
|
artifactName = "",
|
|
341
|
+
artifactPatterns = "",
|
|
342
|
+
requiredArtifactCount = 0,
|
|
343
|
+
publishArtifactKind = "npm",
|
|
344
|
+
publishPackageMain = "",
|
|
173
345
|
outputDir = ".buildchain/release-candidate",
|
|
174
346
|
fetchImpl = globalThis.fetch,
|
|
175
347
|
download = true,
|
|
@@ -221,6 +393,16 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
221
393
|
artifacts: Array.isArray(artifactResponse.artifacts) ? artifactResponse.artifacts : [],
|
|
222
394
|
artifactName,
|
|
223
395
|
});
|
|
396
|
+
const payloadArtifacts = selectPayloadArtifacts({
|
|
397
|
+
artifacts: Array.isArray(artifactResponse.artifacts) ? artifactResponse.artifacts : [],
|
|
398
|
+
artifactName: selected.prefix,
|
|
399
|
+
sourceSha: selected.sourceSha,
|
|
400
|
+
patterns: artifactPatterns,
|
|
401
|
+
});
|
|
402
|
+
const minimumPayloadCount = Number(requiredArtifactCount || 0);
|
|
403
|
+
if (minimumPayloadCount > 0 && payloadArtifacts.length < minimumPayloadCount) {
|
|
404
|
+
throw new Error(`expected at least ${minimumPayloadCount} PR-stage payload artifacts, found ${payloadArtifacts.length}`);
|
|
405
|
+
}
|
|
224
406
|
const result = {
|
|
225
407
|
enabled: true,
|
|
226
408
|
repository: repoInfo.fullName,
|
|
@@ -240,6 +422,7 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
240
422
|
artifacts: {
|
|
241
423
|
passport: selected.passport.name,
|
|
242
424
|
summary: selected.summary.name,
|
|
425
|
+
payloads: payloadArtifacts.map((artifact) => artifact.name),
|
|
243
426
|
artifactName: selected.prefix,
|
|
244
427
|
sourceSha: selected.sourceSha,
|
|
245
428
|
},
|
|
@@ -252,6 +435,7 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
252
435
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "buildchain-rc-"));
|
|
253
436
|
const passportDir = path.join(resolvedOutput, "passport");
|
|
254
437
|
const summaryDir = path.join(resolvedOutput, "summary");
|
|
438
|
+
const payloadDir = path.join(resolvedOutput, "payloads");
|
|
255
439
|
const passportZip = path.join(tempDir, "passport.zip");
|
|
256
440
|
const summaryZip = path.join(tempDir, "summary.zip");
|
|
257
441
|
await githubDownload({
|
|
@@ -268,6 +452,18 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
268
452
|
outputPath: summaryZip,
|
|
269
453
|
path: `/repos/${repoInfo.owner}/${repoInfo.repo}/actions/artifacts/${selected.summary.id}/zip`,
|
|
270
454
|
});
|
|
455
|
+
for (const artifact of payloadArtifacts) {
|
|
456
|
+
const safeName = String(artifact.name || `artifact-${artifact.id}`).replace(/[^A-Za-z0-9._-]/g, "_");
|
|
457
|
+
const payloadZip = path.join(tempDir, `${safeName}.zip`);
|
|
458
|
+
await githubDownload({
|
|
459
|
+
apiUrl,
|
|
460
|
+
token,
|
|
461
|
+
fetchImpl,
|
|
462
|
+
outputPath: payloadZip,
|
|
463
|
+
path: `/repos/${repoInfo.owner}/${repoInfo.repo}/actions/artifacts/${artifact.id}/zip`,
|
|
464
|
+
});
|
|
465
|
+
unzip(payloadZip, path.join(payloadDir, safeName));
|
|
466
|
+
}
|
|
271
467
|
unzip(passportZip, passportDir);
|
|
272
468
|
unzip(summaryZip, summaryDir);
|
|
273
469
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
@@ -277,14 +473,43 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
277
473
|
throw new Error("downloaded release-candidate artifacts did not contain release-candidate-passport.json and build-summary.json");
|
|
278
474
|
}
|
|
279
475
|
const passport = JSON.parse(fs.readFileSync(passportPath, "utf8"));
|
|
476
|
+
const platformManifestPaths = findDownloadedFiles(payloadDir, "manifest.json");
|
|
477
|
+
const npmTarballPaths = publishArtifactKind === "npm"
|
|
478
|
+
? findDownloadedFilesByExtension(payloadDir, [".tgz"])
|
|
479
|
+
: [];
|
|
480
|
+
const downloadedRequiredArtifactCount = publishArtifactKind === "npm"
|
|
481
|
+
? npmTarballPaths.length
|
|
482
|
+
: platformManifestPaths.length;
|
|
483
|
+
if (minimumPayloadCount > 0 && downloadedRequiredArtifactCount < minimumPayloadCount) {
|
|
484
|
+
const noun = publishArtifactKind === "npm" ? "npm package tarballs" : "platform manifests";
|
|
485
|
+
throw new Error(`expected at least ${minimumPayloadCount} downloaded ${noun}, found ${downloadedRequiredArtifactCount}`);
|
|
486
|
+
}
|
|
487
|
+
const manifests = platformManifestPaths.map((manifestPath) => JSON.parse(fs.readFileSync(manifestPath, "utf8")));
|
|
488
|
+
const generatedRequiredArtifacts = generatePublishRequiredArtifacts({
|
|
489
|
+
manifests,
|
|
490
|
+
version: passport.target?.version || "",
|
|
491
|
+
kind: publishArtifactKind,
|
|
492
|
+
tarballPaths: npmTarballPaths,
|
|
493
|
+
mainPackage: publishPackageMain,
|
|
494
|
+
});
|
|
495
|
+
const requiredArtifactsPath = path.join(resolvedOutput, "publish-required-artifacts.json");
|
|
496
|
+
fs.writeFileSync(requiredArtifactsPath, `${JSON.stringify(generatedRequiredArtifacts, null, 2)}\n`);
|
|
280
497
|
return {
|
|
281
498
|
...result,
|
|
282
499
|
paths: {
|
|
283
500
|
passport: outputPath(passportPath),
|
|
284
501
|
buildSummary: outputPath(buildSummaryPath),
|
|
502
|
+
payloads: outputPath(payloadDir),
|
|
503
|
+
platformManifests: platformManifestPaths.map(outputPath),
|
|
504
|
+
npmTarballs: npmTarballPaths.map(outputPath),
|
|
505
|
+
publishRequiredArtifacts: outputPath(requiredArtifactsPath),
|
|
285
506
|
},
|
|
286
507
|
version: passport.target?.version || "",
|
|
287
508
|
candidateHash: passport.candidateHash || "",
|
|
509
|
+
payloadCount: payloadArtifacts.length,
|
|
510
|
+
platformManifestCount: platformManifestPaths.length,
|
|
511
|
+
npmTarballCount: npmTarballPaths.length,
|
|
512
|
+
publishRequiredArtifacts: generatedRequiredArtifacts,
|
|
288
513
|
};
|
|
289
514
|
}
|
|
290
515
|
|
|
@@ -296,6 +521,10 @@ export async function resolveReleaseCandidateArtifactsCli() {
|
|
|
296
521
|
workflowFile: env("BUILDCHAIN_RC_WORKFLOW_FILE", DEFAULT_WORKFLOW_FILE),
|
|
297
522
|
workflowName: env("BUILDCHAIN_RC_WORKFLOW_NAME", ""),
|
|
298
523
|
artifactName: env("BUILDCHAIN_ARTIFACT_NAME"),
|
|
524
|
+
artifactPatterns: env("BUILDCHAIN_ARTIFACT_PATTERNS"),
|
|
525
|
+
requiredArtifactCount: env("BUILDCHAIN_REQUIRED_ARTIFACT_COUNT", "0"),
|
|
526
|
+
publishArtifactKind: env("BUILDCHAIN_PUBLISH_ARTIFACT_KIND", "npm"),
|
|
527
|
+
publishPackageMain: env("BUILDCHAIN_PUBLISH_PACKAGE_MAIN"),
|
|
299
528
|
outputDir: env("BUILDCHAIN_RC_OUTPUT_DIR", ".buildchain/release-candidate"),
|
|
300
529
|
});
|
|
301
530
|
writeGitHubOutputs({
|
|
@@ -306,6 +535,14 @@ export async function resolveReleaseCandidateArtifactsCli() {
|
|
|
306
535
|
"release-candidate-source-sha": result.artifacts?.sourceSha || "",
|
|
307
536
|
"release-candidate-artifact": result.artifacts?.passport || "",
|
|
308
537
|
"release-candidate-build-summary-artifact": result.artifacts?.summary || "",
|
|
538
|
+
"release-candidate-payload-artifacts": (result.artifacts?.payloads || []).join(","),
|
|
539
|
+
"release-candidate-payload-dir": result.paths?.payloads || "",
|
|
540
|
+
"release-candidate-platform-manifest-paths": (result.paths?.platformManifests || []).join(","),
|
|
541
|
+
"release-candidate-platform-manifest-count": String(result.platformManifestCount || 0),
|
|
542
|
+
"release-candidate-npm-tarball-paths": (result.paths?.npmTarballs || []).join(","),
|
|
543
|
+
"release-candidate-npm-tarball-count": String(result.npmTarballCount || 0),
|
|
544
|
+
"publish-required-artifacts-json": JSON.stringify(result.publishRequiredArtifacts || []),
|
|
545
|
+
"publish-required-artifacts-path": result.paths?.publishRequiredArtifacts || "",
|
|
309
546
|
"release-candidate-run-id": result.run?.id || "",
|
|
310
547
|
"release-candidate-run-url": result.run?.url || "",
|
|
311
548
|
"release-candidate-pr": result.pullRequest?.number ? String(result.pullRequest.number) : "",
|