@rightkit/git 0.2.8 → 0.2.9
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/README.md +2 -2
- package/manifest.mjs +112 -22
- package/manifest.schema.json +14 -0
- package/manifest.test.mjs +29 -0
- package/package.json +1 -1
- package/render.test.mjs +40 -0
- package/templates/profiles/rust-hybrid/release-candidate.yml +2 -1
package/README.md
CHANGED
|
@@ -38,6 +38,6 @@ Node builtins only; no runtime dependencies.
|
|
|
38
38
|
|
|
39
39
|
## Profiles
|
|
40
40
|
|
|
41
|
-
`profile` defaults to `node`. `rust-hybrid` requires `ci`, accepts optional `release-candidate`, installs pinned Node plus Rust, then runs product-owned package scripts. Candidate configuration is `{ buildScript, checkScript, artifactRoot, os, signWindows?, signMacos?, releaseChain? }
|
|
41
|
+
`profile` defaults to `node`. `rust-hybrid` requires `ci`, accepts optional `release-candidate`, installs pinned Node plus Rust, then runs product-owned package scripts. Candidate configuration is `{ buildScript, checkScript, artifactRoot, os, targets?, signWindows?, signMacos?, releaseChain? }`. Legacy `os` remains supported & derives one target per runner; explicit `targets` is `{ os, platform, architecture }[]`, independent of main CI matrix. Each target maps to native runner, Rust target triple, exact unsigned artifact, & platform signing matrix. Supported platform values are `windows`, `macos`, `linux`; architectures are `x86_64`, `arm64`. RightGit always uploads an unsigned handoff. `signWindows: true` adds Azure OIDC signing. `signMacos: true` adds Developer ID signing plus notarization from protected environment secrets.
|
|
42
42
|
|
|
43
|
-
`releaseChain` lets RightGit own full protected DAG while product scripts own artifact semantics: `{ windowsFinalizeScript, macosFinalizeScript?, installedQualificationScript, publishScript, qualificationRunnerVariable, publishSecrets? }`. Generated ordering is unsigned candidate → platform signing/finalization → installed qualification → publication. Every protected job is tag-gated, publication needs successful installed qualification plus macOS finalization when enabled, & only publish job receives declared publication secrets plus `contents: write`.
|
|
43
|
+
`releaseChain` lets RightGit own full protected DAG while product scripts own artifact semantics: `{ windowsFinalizeScript, macosFinalizeScript?, installedQualificationScript, publishScript, qualificationRunnerVariable, publishSecrets? }`. Generated ordering is unsigned candidate → platform signing/finalization → installed qualification → publication. It deliberately rejects more than one Windows target or more than one macOS target because its installed-qualification/publish handoff has one canonical signed artifact per platform. Every protected job is tag-gated, publication needs successful installed qualification plus macOS finalization when enabled, & only publish job receives declared publication secrets plus `contents: write`.
|
package/manifest.mjs
CHANGED
|
@@ -62,6 +62,23 @@ const TOP_LEVEL_KEYS = new Set([
|
|
|
62
62
|
const GATE_PATH_RE = /^(?!.*\.\.)(?!.*\s)[A-Za-z0-9][A-Za-z0-9._/-]*$/;
|
|
63
63
|
const PACKAGE_SCRIPT_RE = /^[A-Za-z0-9][A-Za-z0-9:_-]*$/;
|
|
64
64
|
const GITHUB_VARIABLE_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
65
|
+
const RELEASE_PLATFORMS = new Set(["windows", "macos", "linux"]);
|
|
66
|
+
const RELEASE_ARCHITECTURES = new Set(["x86_64", "arm64"]);
|
|
67
|
+
const NATIVE_RUNNER_TARGETS = new Map([
|
|
68
|
+
["windows-2025", ["windows", "x86_64"]],
|
|
69
|
+
["windows-11-arm", ["windows", "arm64"]],
|
|
70
|
+
["macos-15", ["macos", "arm64"]],
|
|
71
|
+
["macos-15-intel", ["macos", "x86_64"]],
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
function inferredTarget(os) {
|
|
75
|
+
const platform = /^windows-/i.test(os) ? "windows" : /^macos-/i.test(os) ? "macos" : "linux";
|
|
76
|
+
return { os, platform, architecture: platform === "macos" ? "arm64" : "x86_64" };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function candidateTargets(candidate) {
|
|
80
|
+
return Array.isArray(candidate?.targets) ? candidate.targets : (candidate?.os ?? []).map(inferredTarget);
|
|
81
|
+
}
|
|
65
82
|
|
|
66
83
|
/**
|
|
67
84
|
* Validate a parsed manifest object against the right-git manifest contract.
|
|
@@ -142,7 +159,7 @@ export function validateManifest(manifest) {
|
|
|
142
159
|
errors.push("candidate must be an object");
|
|
143
160
|
} else {
|
|
144
161
|
for (const key of Object.keys(candidate)) {
|
|
145
|
-
if (!["buildScript", "checkScript", "artifactRoot", "os", "signWindows", "signMacos", "releaseChain"].includes(key)) errors.push(`unknown candidate field: ${key}`);
|
|
162
|
+
if (!["buildScript", "checkScript", "artifactRoot", "os", "targets", "signWindows", "signMacos", "releaseChain"].includes(key)) errors.push(`unknown candidate field: ${key}`);
|
|
146
163
|
}
|
|
147
164
|
for (const key of ["buildScript", "checkScript"]) {
|
|
148
165
|
if (typeof candidate[key] !== "string" || !PACKAGE_SCRIPT_RE.test(candidate[key])) {
|
|
@@ -154,20 +171,52 @@ export function validateManifest(manifest) {
|
|
|
154
171
|
}
|
|
155
172
|
if (!Array.isArray(candidate.os) || candidate.os.length === 0 || candidate.os.some((value) => typeof value !== "string" || !SAFE_MATRIX_VALUE_RE.test(value))) {
|
|
156
173
|
errors.push("candidate.os must be a non-empty array of safe runner labels");
|
|
157
|
-
} else if (candidate.os.some((value) => !manifest.matrix?.os?.includes(value))) {
|
|
174
|
+
} else if (candidate.targets === undefined && candidate.os.some((value) => !manifest.matrix?.os?.includes(value))) {
|
|
158
175
|
errors.push("candidate.os entries must also exist in matrix.os");
|
|
159
176
|
}
|
|
177
|
+
if (candidate.targets !== undefined) {
|
|
178
|
+
if (!Array.isArray(candidate.targets) || candidate.targets.length === 0) {
|
|
179
|
+
errors.push("candidate.targets must be a non-empty array");
|
|
180
|
+
} else {
|
|
181
|
+
const targetKeys = new Set();
|
|
182
|
+
for (const target of candidate.targets) {
|
|
183
|
+
if (target === null || typeof target !== "object" || Array.isArray(target)) {
|
|
184
|
+
errors.push("candidate.targets entries must be objects");
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (Object.keys(target).some((key) => !["os", "platform", "architecture"].includes(key))) errors.push("candidate.targets entries may only contain os, platform, architecture");
|
|
188
|
+
if (typeof target.os !== "string" || !SAFE_MATRIX_VALUE_RE.test(target.os)) errors.push("candidate.targets[].os must be a safe runner label");
|
|
189
|
+
if (!RELEASE_PLATFORMS.has(target.platform)) errors.push("candidate.targets[].platform must be windows, macos, or linux");
|
|
190
|
+
if (!RELEASE_ARCHITECTURES.has(target.architecture)) errors.push("candidate.targets[].architecture must be x86_64 or arm64");
|
|
191
|
+
const nativeTarget = NATIVE_RUNNER_TARGETS.get(target.os);
|
|
192
|
+
if (nativeTarget && (target.platform !== nativeTarget[0] || target.architecture !== nativeTarget[1])) {
|
|
193
|
+
errors.push(`candidate.targets[] must declare ${target.os} as ${nativeTarget[0]}/${nativeTarget[1]}`);
|
|
194
|
+
}
|
|
195
|
+
if ((target.platform === "windows" && !/^windows-/i.test(target.os)) || (target.platform === "macos" && !/^macos-/i.test(target.os))) {
|
|
196
|
+
errors.push("candidate.targets[].os must match its Windows or macOS platform");
|
|
197
|
+
}
|
|
198
|
+
const targetKey = `${target.os}|${target.platform}|${target.architecture}`;
|
|
199
|
+
if (targetKeys.has(targetKey)) errors.push("candidate.targets entries must be unique");
|
|
200
|
+
targetKeys.add(targetKey);
|
|
201
|
+
}
|
|
202
|
+
const targetOs = new Set(candidate.targets.map((target) => target?.os));
|
|
203
|
+
if (candidate.os.some((os) => !targetOs.has(os)) || targetOs.size !== new Set(candidate.os).size || candidate.os.length !== new Set(candidate.os).size) {
|
|
204
|
+
errors.push("candidate.os must contain exactly candidate.targets runner labels");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const targets = candidateTargets(candidate);
|
|
160
209
|
if (candidate.signWindows !== undefined && typeof candidate.signWindows !== "boolean") {
|
|
161
210
|
errors.push("candidate.signWindows must be a boolean");
|
|
162
211
|
}
|
|
163
|
-
if (candidate.signWindows === true &&
|
|
164
|
-
errors.push("candidate.signWindows requires
|
|
212
|
+
if (candidate.signWindows === true && targets.filter((target) => target?.platform === "windows").length === 0) {
|
|
213
|
+
errors.push("candidate.signWindows requires at least one Windows target");
|
|
165
214
|
}
|
|
166
215
|
if (candidate.signMacos !== undefined && typeof candidate.signMacos !== "boolean") {
|
|
167
216
|
errors.push("candidate.signMacos must be a boolean");
|
|
168
217
|
}
|
|
169
|
-
if (candidate.signMacos === true &&
|
|
170
|
-
errors.push("candidate.signMacos requires
|
|
218
|
+
if (candidate.signMacos === true && targets.filter((target) => target?.platform === "macos").length === 0) {
|
|
219
|
+
errors.push("candidate.signMacos requires at least one macOS target");
|
|
171
220
|
}
|
|
172
221
|
if ((candidate.signWindows === true || candidate.signMacos === true) && manifest.matrix?.node?.length !== 1) {
|
|
173
222
|
errors.push("candidate signing requires exactly one matrix.node version");
|
|
@@ -196,6 +245,9 @@ export function validateManifest(manifest) {
|
|
|
196
245
|
errors.push("candidate.releaseChain.publishSecrets must be unique uppercase GitHub secret names");
|
|
197
246
|
}
|
|
198
247
|
if (candidate.signWindows !== true) errors.push("candidate.releaseChain requires candidate.signWindows=true");
|
|
248
|
+
if (targets.filter((target) => target?.platform === "windows").length !== 1 || targets.filter((target) => target?.platform === "macos").length > 1) {
|
|
249
|
+
errors.push("candidate.releaseChain requires at most one target per signing platform; multi-target aggregation is not supported");
|
|
250
|
+
}
|
|
199
251
|
if (candidate.signMacos === true && !chain.macosFinalizeScript) errors.push("candidate.releaseChain with candidate.signMacos=true requires macosFinalizeScript");
|
|
200
252
|
if (chain.macosFinalizeScript && candidate.signMacos !== true) errors.push("candidate.releaseChain.macosFinalizeScript requires candidate.signMacos=true");
|
|
201
253
|
}
|
|
@@ -324,10 +376,8 @@ function yamlFlowStringList(values) {
|
|
|
324
376
|
|
|
325
377
|
function candidateMatrix(manifest) {
|
|
326
378
|
const entries = [];
|
|
327
|
-
for (const
|
|
328
|
-
const
|
|
329
|
-
const architecture = platform === "macos" ? "arm64" : "x86_64";
|
|
330
|
-
for (const node of manifest.matrix.node) entries.push({ os, node, platform, architecture });
|
|
379
|
+
for (const target of candidateTargets(manifest.candidate ?? { os: manifest.matrix.os })) {
|
|
380
|
+
for (const node of manifest.matrix.node) entries.push({ ...target, node, targetTriple: targetTriple(target) });
|
|
331
381
|
}
|
|
332
382
|
return [
|
|
333
383
|
" include:",
|
|
@@ -336,6 +386,31 @@ function candidateMatrix(manifest) {
|
|
|
336
386
|
` node: ${JSON.stringify(entry.node)}`,
|
|
337
387
|
` platform: ${JSON.stringify(entry.platform)}`,
|
|
338
388
|
` architecture: ${JSON.stringify(entry.architecture)}`,
|
|
389
|
+
` targetTriple: ${JSON.stringify(entry.targetTriple)}`,
|
|
390
|
+
]),
|
|
391
|
+
].join("\n");
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function targetTriple(target) {
|
|
395
|
+
const rustArchitecture = target.architecture === "arm64" ? "aarch64" : target.architecture;
|
|
396
|
+
if (target.platform === "windows") return `${rustArchitecture}-pc-windows-msvc`;
|
|
397
|
+
if (target.platform === "macos") return `${rustArchitecture}-apple-darwin`;
|
|
398
|
+
return `${rustArchitecture}-unknown-linux-gnu`;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function signingMatrix(manifest, platform) {
|
|
402
|
+
const node = String(manifest.matrix.node[0]);
|
|
403
|
+
const entries = candidateTargets(manifest.candidate)
|
|
404
|
+
.filter((target) => target.platform === platform)
|
|
405
|
+
.map((target) => ({ ...target, node, targetTriple: targetTriple(target) }));
|
|
406
|
+
return [
|
|
407
|
+
" include:",
|
|
408
|
+
...entries.flatMap((entry) => [
|
|
409
|
+
` - os: ${JSON.stringify(entry.os)}`,
|
|
410
|
+
` platform: ${JSON.stringify(entry.platform)}`,
|
|
411
|
+
` architecture: ${JSON.stringify(entry.architecture)}`,
|
|
412
|
+
` targetTriple: ${JSON.stringify(entry.targetTriple)}`,
|
|
413
|
+
` node: ${JSON.stringify(entry.node)}`,
|
|
339
414
|
]),
|
|
340
415
|
].join("\n");
|
|
341
416
|
}
|
|
@@ -350,7 +425,6 @@ const WINDOWS_SDK_BUILD_TOOLS_SHA256 = "180DEB372659029864C10A0C04787833234D64AA
|
|
|
350
425
|
|
|
351
426
|
function windowsSigningJob(manifest, pm, artifactPrefix) {
|
|
352
427
|
if (manifest.candidate?.signWindows !== true) return "";
|
|
353
|
-
const windowsRunner = manifest.candidate.os.find((value) => /^windows-/i.test(value));
|
|
354
428
|
const nodeVersion = String(manifest.matrix.node[0]);
|
|
355
429
|
const releaseChain = manifest.candidate.releaseChain;
|
|
356
430
|
const finalizeCommand = releaseChain
|
|
@@ -370,7 +444,11 @@ function windowsSigningJob(manifest, pm, artifactPrefix) {
|
|
|
370
444
|
needs: [candidate]
|
|
371
445
|
if: \${{ startsWith(github.ref, 'refs/tags/v') }}
|
|
372
446
|
environment: release
|
|
373
|
-
|
|
447
|
+
strategy:
|
|
448
|
+
fail-fast: false
|
|
449
|
+
matrix:
|
|
450
|
+
${signingMatrix(manifest, "windows")}
|
|
451
|
+
runs-on: \${{ matrix.os }}
|
|
374
452
|
permissions:
|
|
375
453
|
contents: read
|
|
376
454
|
id-token: write
|
|
@@ -387,11 +465,12 @@ ${pm.setup}
|
|
|
387
465
|
- uses: dtolnay/rust-toolchain@f8be11a05b1d4f3fcebe6410cc16743212b999b0
|
|
388
466
|
with:
|
|
389
467
|
toolchain: 1.98.0
|
|
468
|
+
targets: \${{ matrix.targetTriple }}
|
|
390
469
|
- run: ${pm.installCommand}
|
|
391
470
|
- name: Download exact unsigned Windows candidate
|
|
392
471
|
uses: actions/download-artifact@${DOWNLOAD_ARTIFACT_SHA} # v4.3.0
|
|
393
472
|
with:
|
|
394
|
-
name: ${artifactPrefix}-unsigned
|
|
473
|
+
name: ${artifactPrefix}-unsigned-\${{ matrix.platform }}-\${{ matrix.architecture }}-${nodeVersion}
|
|
395
474
|
path: \${{ runner.temp }}/unsigned-windows-candidate
|
|
396
475
|
- name: Authenticate to Azure with GitHub OIDC
|
|
397
476
|
uses: azure/login@${AZURE_LOGIN_SHA} # v3
|
|
@@ -419,13 +498,15 @@ ${pm.setup}
|
|
|
419
498
|
run: ${finalizeCommand}
|
|
420
499
|
env:
|
|
421
500
|
${finalizeEnvironment}
|
|
501
|
+
RIGHT_GIT_RELEASE_PLATFORM: \${{ matrix.platform }}
|
|
502
|
+
RIGHT_GIT_RELEASE_ARCHITECTURE: \${{ matrix.architecture }}
|
|
422
503
|
AZURE_ARTIFACT_SIGNING_ENDPOINT: \${{ vars.AZURE_ARTIFACT_SIGNING_ENDPOINT }}
|
|
423
504
|
AZURE_ARTIFACT_SIGNING_ACCOUNT: \${{ vars.AZURE_ARTIFACT_SIGNING_ACCOUNT }}
|
|
424
505
|
AZURE_ARTIFACT_SIGNING_PROFILE: \${{ vars.AZURE_ARTIFACT_SIGNING_PROFILE }}
|
|
425
506
|
- name: Upload signed Windows candidate
|
|
426
507
|
uses: actions/upload-artifact@${UPLOAD_ARTIFACT_SHA} # v4
|
|
427
508
|
with:
|
|
428
|
-
name: ${artifactPrefix}-signed-windows-${nodeVersion}
|
|
509
|
+
name: ${artifactPrefix}-signed-windows-\${{ matrix.architecture }}-${nodeVersion}
|
|
429
510
|
path: |
|
|
430
511
|
${uploadPath}
|
|
431
512
|
if-no-files-found: error
|
|
@@ -434,7 +515,6 @@ ${uploadPath}
|
|
|
434
515
|
|
|
435
516
|
function macosSigningJob(manifest, pm, artifactPrefix) {
|
|
436
517
|
if (manifest.candidate?.signMacos !== true) return "";
|
|
437
|
-
const macosRunner = manifest.candidate.os.find((value) => /^macos-/i.test(value));
|
|
438
518
|
const nodeVersion = String(manifest.matrix.node[0]);
|
|
439
519
|
const releaseChain = manifest.candidate.releaseChain;
|
|
440
520
|
const finalizeCommand = releaseChain
|
|
@@ -454,7 +534,11 @@ function macosSigningJob(manifest, pm, artifactPrefix) {
|
|
|
454
534
|
needs: [candidate]
|
|
455
535
|
if: \${{ startsWith(github.ref, 'refs/tags/v') }}
|
|
456
536
|
environment: release
|
|
457
|
-
|
|
537
|
+
strategy:
|
|
538
|
+
fail-fast: false
|
|
539
|
+
matrix:
|
|
540
|
+
${signingMatrix(manifest, "macos")}
|
|
541
|
+
runs-on: \${{ matrix.os }}
|
|
458
542
|
permissions:
|
|
459
543
|
contents: read
|
|
460
544
|
steps:
|
|
@@ -470,11 +554,12 @@ ${pm.setup}
|
|
|
470
554
|
- uses: dtolnay/rust-toolchain@f8be11a05b1d4f3fcebe6410cc16743212b999b0
|
|
471
555
|
with:
|
|
472
556
|
toolchain: 1.98.0
|
|
557
|
+
targets: \${{ matrix.targetTriple }}
|
|
473
558
|
- run: ${pm.installCommand}
|
|
474
559
|
- name: Download exact unsigned macOS candidate
|
|
475
560
|
uses: actions/download-artifact@${DOWNLOAD_ARTIFACT_SHA} # v4.3.0
|
|
476
561
|
with:
|
|
477
|
-
name: ${artifactPrefix}-unsigned
|
|
562
|
+
name: ${artifactPrefix}-unsigned-\${{ matrix.platform }}-\${{ matrix.architecture }}-${nodeVersion}
|
|
478
563
|
path: \${{ runner.temp }}/unsigned-macos-candidate
|
|
479
564
|
- name: Install Apple signing identity in temporary keychain
|
|
480
565
|
shell: bash
|
|
@@ -501,13 +586,15 @@ ${pm.setup}
|
|
|
501
586
|
run: ${finalizeCommand}
|
|
502
587
|
env:
|
|
503
588
|
${finalizeEnvironment}
|
|
589
|
+
RIGHT_GIT_RELEASE_PLATFORM: \${{ matrix.platform }}
|
|
590
|
+
RIGHT_GIT_RELEASE_ARCHITECTURE: \${{ matrix.architecture }}
|
|
504
591
|
APPLE_DEVELOPER_ID: \${{ vars.APPLE_DEVELOPER_ID }}
|
|
505
592
|
APPLE_API_KEY: \${{ secrets.APPLE_API_KEY }}
|
|
506
593
|
APPLE_API_ISSUER: \${{ secrets.APPLE_API_ISSUER }}
|
|
507
594
|
- name: Upload signed & notarized macOS candidate
|
|
508
595
|
uses: actions/upload-artifact@${UPLOAD_ARTIFACT_SHA} # v4
|
|
509
596
|
with:
|
|
510
|
-
name: ${artifactPrefix}-signed-macos-${nodeVersion}
|
|
597
|
+
name: ${artifactPrefix}-signed-macos-\${{ matrix.architecture }}-${nodeVersion}
|
|
511
598
|
path: |
|
|
512
599
|
${uploadPath}
|
|
513
600
|
if-no-files-found: error
|
|
@@ -523,7 +610,10 @@ ${uploadPath}
|
|
|
523
610
|
function releaseChainJobs(manifest, pm, artifactPrefix) {
|
|
524
611
|
const chain = manifest.candidate?.releaseChain;
|
|
525
612
|
if (!chain) return "";
|
|
526
|
-
const
|
|
613
|
+
const windowsTarget = candidateTargets(manifest.candidate).find((target) => target.platform === "windows");
|
|
614
|
+
const windowsRunner = windowsTarget.os;
|
|
615
|
+
const windowsArchitecture = windowsTarget.architecture;
|
|
616
|
+
const macosTarget = candidateTargets(manifest.candidate).find((target) => target.platform === "macos");
|
|
527
617
|
const nodeVersion = String(manifest.matrix.node[0]);
|
|
528
618
|
const macosDependency = manifest.candidate.signMacos === true ? ", macos-sign" : "";
|
|
529
619
|
const macosSuccess = manifest.candidate.signMacos === true ? " && needs.macos-sign.result == 'success'" : "";
|
|
@@ -531,7 +621,7 @@ function releaseChainJobs(manifest, pm, artifactPrefix) {
|
|
|
531
621
|
? ` - name: Download same-run signed & notarized macOS release
|
|
532
622
|
uses: actions/download-artifact@${DOWNLOAD_ARTIFACT_SHA} # v4.3.0
|
|
533
623
|
with:
|
|
534
|
-
name: ${artifactPrefix}-signed-macos-${nodeVersion}
|
|
624
|
+
name: ${artifactPrefix}-signed-macos-${macosTarget.architecture}-${nodeVersion}
|
|
535
625
|
path: \${{ runner.temp }}/right-git-release/macos`
|
|
536
626
|
: "";
|
|
537
627
|
const publishSecretEnvironment = (chain.publishSecrets ?? [])
|
|
@@ -555,7 +645,7 @@ function releaseChainJobs(manifest, pm, artifactPrefix) {
|
|
|
555
645
|
- name: Download same-run finalized Windows release
|
|
556
646
|
uses: actions/download-artifact@${DOWNLOAD_ARTIFACT_SHA} # v4.3.0
|
|
557
647
|
with:
|
|
558
|
-
name: ${artifactPrefix}-signed-windows-${nodeVersion}
|
|
648
|
+
name: ${artifactPrefix}-signed-windows-${windowsArchitecture}-${nodeVersion}
|
|
559
649
|
path: \${{ runner.temp }}/right-git-release/windows
|
|
560
650
|
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
561
651
|
with:
|
|
@@ -590,7 +680,7 @@ ${pm.setup}
|
|
|
590
680
|
- name: Download same-run finalized Windows release
|
|
591
681
|
uses: actions/download-artifact@${DOWNLOAD_ARTIFACT_SHA} # v4.3.0
|
|
592
682
|
with:
|
|
593
|
-
name: ${artifactPrefix}-signed-windows-${nodeVersion}
|
|
683
|
+
name: ${artifactPrefix}-signed-windows-${windowsArchitecture}-${nodeVersion}
|
|
594
684
|
path: \${{ runner.temp }}/right-git-release/windows
|
|
595
685
|
${macosDownload}
|
|
596
686
|
- name: Download same-run installed qualification evidence
|
package/manifest.schema.json
CHANGED
|
@@ -95,6 +95,20 @@
|
|
|
95
95
|
"checkScript": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9:_-]*$" },
|
|
96
96
|
"artifactRoot": { "type": "string", "pattern": "^(?!.*\\.\\.)(?!.*\\s)[A-Za-z0-9][A-Za-z0-9._/-]*$" },
|
|
97
97
|
"os": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } },
|
|
98
|
+
"targets": {
|
|
99
|
+
"type": "array",
|
|
100
|
+
"minItems": 1,
|
|
101
|
+
"items": {
|
|
102
|
+
"type": "object",
|
|
103
|
+
"required": ["os", "platform", "architecture"],
|
|
104
|
+
"additionalProperties": false,
|
|
105
|
+
"properties": {
|
|
106
|
+
"os": { "type": "string", "minLength": 1 },
|
|
107
|
+
"platform": { "enum": ["windows", "macos", "linux"] },
|
|
108
|
+
"architecture": { "enum": ["x86_64", "arm64"] }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
98
112
|
"signWindows": { "type": "boolean" },
|
|
99
113
|
"signMacos": { "type": "boolean" },
|
|
100
114
|
"releaseChain": {
|
package/manifest.test.mjs
CHANGED
|
@@ -62,6 +62,35 @@ test("rust-hybrid supports ci plus a configured unsigned release-candidate lane"
|
|
|
62
62
|
assert.equal(validateManifest({ ...releaseChain, candidate: { ...releaseChain.candidate, releaseChain: { ...releaseChain.candidate.releaseChain, publishSecrets: ["TOKEN", "TOKEN"] } } }).valid, false);
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
+
test("candidate targets permit native multi-architecture matrices independent of normal CI runners", () => {
|
|
66
|
+
const multiArchitecture = {
|
|
67
|
+
...VALID_MINIMAL,
|
|
68
|
+
profile: "rust-hybrid",
|
|
69
|
+
gate: { path: "scripts/gate.sh" },
|
|
70
|
+
lanes: ["ci", "release-candidate"],
|
|
71
|
+
candidate: {
|
|
72
|
+
buildScript: "ci:candidate",
|
|
73
|
+
checkScript: "ci:candidate-check",
|
|
74
|
+
artifactRoot: "widgets-unsigned",
|
|
75
|
+
os: ["windows-2025", "windows-11-arm", "macos-15", "macos-15-intel"],
|
|
76
|
+
targets: [
|
|
77
|
+
{ os: "windows-2025", platform: "windows", architecture: "x86_64" },
|
|
78
|
+
{ os: "windows-11-arm", platform: "windows", architecture: "arm64" },
|
|
79
|
+
{ os: "macos-15", platform: "macos", architecture: "arm64" },
|
|
80
|
+
{ os: "macos-15-intel", platform: "macos", architecture: "x86_64" },
|
|
81
|
+
],
|
|
82
|
+
signWindows: true,
|
|
83
|
+
signMacos: true,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
assert.equal(validateManifest(multiArchitecture).valid, true, JSON.stringify(validateManifest(multiArchitecture).errors));
|
|
87
|
+
assert.equal(validateManifest({ ...multiArchitecture, candidate: { ...multiArchitecture.candidate, targets: [{ os: "windows-2025", platform: "windows", architecture: "riscv64" }] } }).valid, false);
|
|
88
|
+
assert.equal(validateManifest({ ...multiArchitecture, candidate: { ...multiArchitecture.candidate, targets: [{ os: "windows-11-arm", platform: "windows", architecture: "x86_64" }] } }).valid, false);
|
|
89
|
+
assert.equal(validateManifest({ ...multiArchitecture, candidate: { ...multiArchitecture.candidate, targets: [{ os: "ubuntu-24.04", platform: "windows", architecture: "x86_64" }] } }).valid, false);
|
|
90
|
+
assert.equal(validateManifest({ ...multiArchitecture, candidate: { ...multiArchitecture.candidate, targets: [{ os: "${{ secrets.X }}", platform: "windows", architecture: "x86_64" }] } }).valid, false);
|
|
91
|
+
assert.equal(validateManifest({ ...multiArchitecture, candidate: { ...multiArchitecture.candidate, releaseChain: { windowsFinalizeScript: "release:finalize:win", macosFinalizeScript: "release:finalize:mac", installedQualificationScript: "release:qualify", publishScript: "release:publish", qualificationRunnerVariable: "QUALIFICATION_RUNNER_LABEL" } } }).valid, false);
|
|
92
|
+
});
|
|
93
|
+
|
|
65
94
|
test("rejects non-object input", () => {
|
|
66
95
|
for (const bad of [null, undefined, "not an object", 42, ["array"]]) {
|
|
67
96
|
const { valid, errors } = validateManifest(bad);
|
package/package.json
CHANGED
package/render.test.mjs
CHANGED
|
@@ -130,6 +130,46 @@ test("rust-hybrid release candidate can opt into protected macOS signing & notar
|
|
|
130
130
|
assert.doesNotMatch(content, /pull_request_target/);
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
test("rust-hybrid multi-architecture candidates bind every native target to exact artifacts and signing", () => {
|
|
134
|
+
const manifest = {
|
|
135
|
+
...FULL_MANIFEST,
|
|
136
|
+
lanes: ["ci", "release-candidate"],
|
|
137
|
+
profile: "rust-hybrid",
|
|
138
|
+
gate: { path: "scripts/gate.sh" },
|
|
139
|
+
matrix: { os: ["ubuntu-24.04"], node: ["22.23.2"] },
|
|
140
|
+
candidate: {
|
|
141
|
+
buildScript: "ci:candidate",
|
|
142
|
+
checkScript: "ci:candidate-check",
|
|
143
|
+
artifactRoot: "widgets-unsigned",
|
|
144
|
+
os: ["windows-2025", "windows-11-arm", "macos-15", "macos-15-intel"],
|
|
145
|
+
targets: [
|
|
146
|
+
{ os: "windows-2025", platform: "windows", architecture: "x86_64" },
|
|
147
|
+
{ os: "windows-11-arm", platform: "windows", architecture: "arm64" },
|
|
148
|
+
{ os: "macos-15", platform: "macos", architecture: "arm64" },
|
|
149
|
+
{ os: "macos-15-intel", platform: "macos", architecture: "x86_64" },
|
|
150
|
+
],
|
|
151
|
+
signWindows: true,
|
|
152
|
+
signMacos: true,
|
|
153
|
+
},
|
|
154
|
+
templateVersion: "1.5.0",
|
|
155
|
+
};
|
|
156
|
+
const content = renderTemplate(
|
|
157
|
+
fs.readFileSync(path.join(templatesDir, "profiles/rust-hybrid/release-candidate.yml"), "utf8"),
|
|
158
|
+
buildRenderContext(manifest, "/repos/widgets"),
|
|
159
|
+
);
|
|
160
|
+
for (const [runner, triple] of [["windows-2025", "x86_64-pc-windows-msvc"], ["windows-11-arm", "aarch64-pc-windows-msvc"], ["macos-15", "aarch64-apple-darwin"], ["macos-15-intel", "x86_64-apple-darwin"]]) {
|
|
161
|
+
assert.match(content, new RegExp(`os: ${JSON.stringify(runner).replace(/[.*+?^${}()|[\\]\\]/g, "\\$&")}`));
|
|
162
|
+
assert.match(content, new RegExp(`targetTriple: ${JSON.stringify(triple).replace(/[.*+?^${}()|[\\]\\]/g, "\\$&")}`));
|
|
163
|
+
}
|
|
164
|
+
assert.match(content, /targets: \$\{\{ matrix\.targetTriple \}\}/);
|
|
165
|
+
assert.match(content, /windows-sign:[\s\S]*matrix:[\s\S]*windows-11-arm/);
|
|
166
|
+
assert.match(content, /macos-sign:[\s\S]*matrix:[\s\S]*macos-15-intel/);
|
|
167
|
+
assert.match(content, /name: widgets-unsigned-\$\{\{ matrix\.platform \}\}-\$\{\{ matrix\.architecture \}\}-22\.23\.2/);
|
|
168
|
+
assert.match(content, /name: widgets-signed-windows-\$\{\{ matrix\.architecture \}\}-22\.23\.2/);
|
|
169
|
+
assert.match(content, /name: widgets-signed-macos-\$\{\{ matrix\.architecture \}\}-22\.23\.2/);
|
|
170
|
+
assert.equal((content.match(/RIGHT_GIT_RELEASE_ARCHITECTURE: \$\{\{ matrix\.architecture \}\}/g) ?? []).length, 4);
|
|
171
|
+
});
|
|
172
|
+
|
|
133
173
|
test("rust-hybrid release chain preserves signed candidate, installed qualification, then publish", () => {
|
|
134
174
|
const manifest = {
|
|
135
175
|
...FULL_MANIFEST,
|
|
@@ -34,6 +34,7 @@ ${PACKAGE_MANAGER_SETUP}
|
|
|
34
34
|
with:
|
|
35
35
|
toolchain: 1.98.0
|
|
36
36
|
components: rustfmt, clippy
|
|
37
|
+
targets: ${{ matrix.targetTriple }}
|
|
37
38
|
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
|
38
39
|
with:
|
|
39
40
|
workspaces: engine
|
|
@@ -53,7 +54,7 @@ ${PACKAGE_MANAGER_SETUP}
|
|
|
53
54
|
- name: Upload unsigned candidate handoff
|
|
54
55
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
|
55
56
|
with:
|
|
56
|
-
name: ${ARTIFACT_PREFIX}-unsigned-${{ matrix.
|
|
57
|
+
name: ${ARTIFACT_PREFIX}-unsigned-${{ matrix.platform }}-${{ matrix.architecture }}-${{ matrix.node }}
|
|
57
58
|
path: ${{ runner.temp }}/${CANDIDATE_ARTIFACT_ROOT}/**
|
|
58
59
|
if-no-files-found: error
|
|
59
60
|
${WINDOWS_SIGNING_JOB}
|