@rightkit/git 0.2.23 → 0.2.24
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/cli/right-git.mjs +0 -0
- package/manifest.mjs +281 -51
- package/manifest.schema.json +17 -2
- package/manifest.test.mjs +108 -1
- package/package.json +6 -5
- package/render.test.mjs +252 -21
- package/sync.test.mjs +3 -0
- package/templates/profiles/rust-hybrid/release-candidate.yml +105 -7
package/cli/right-git.mjs
CHANGED
|
File without changes
|
package/manifest.mjs
CHANGED
|
@@ -188,9 +188,17 @@ export function validateManifest(manifest) {
|
|
|
188
188
|
errors.push("candidate must be an object");
|
|
189
189
|
} else {
|
|
190
190
|
for (const key of Object.keys(candidate)) {
|
|
191
|
-
if (!["buildScript", "checkScript", "artifactRoot", "os", "targets", "signWindows", "signMacos", "releaseChain"].includes(key)) errors.push(`unknown candidate field: ${key}`);
|
|
191
|
+
if (!["buildScript", "checkScript", "admissionScript", "stageSummaryScript", "artifactRoot", "os", "targets", "signWindows", "signMacos", "releaseChain"].includes(key)) errors.push(`unknown candidate field: ${key}`);
|
|
192
192
|
}
|
|
193
|
-
|
|
193
|
+
// admissionScript & stageSummaryScript are required for EVERY release-candidate
|
|
194
|
+
// lane, not just signed/full-releaseChain ones: the tag-last workflow's
|
|
195
|
+
// `admission` job (dispatch-envelope validation) and the `candidate` job's
|
|
196
|
+
// stage-summary init/finalize steps are unconditional parts of the rendered
|
|
197
|
+
// template, independent of candidate.signWindows/signMacos/releaseChain. If
|
|
198
|
+
// either script were only required inside the optional releaseChain object,
|
|
199
|
+
// an unsigned release-candidate manifest (no releaseChain at all) would still
|
|
200
|
+
// render those unconditional steps with an unresolved script name.
|
|
201
|
+
for (const key of ["buildScript", "checkScript", "admissionScript", "stageSummaryScript"]) {
|
|
194
202
|
if (typeof candidate[key] !== "string" || !PACKAGE_SCRIPT_RE.test(candidate[key])) {
|
|
195
203
|
errors.push(`candidate.${key} must be one safe package script name`);
|
|
196
204
|
}
|
|
@@ -252,14 +260,18 @@ export function validateManifest(manifest) {
|
|
|
252
260
|
}
|
|
253
261
|
if (candidate.releaseChain !== undefined) {
|
|
254
262
|
const chain = candidate.releaseChain;
|
|
255
|
-
|
|
263
|
+
// admissionScript & stageSummaryScript live on candidate directly (see
|
|
264
|
+
// above) — the full signed release chain adds evidenceVerificationScript
|
|
265
|
+
// (runs before publish, verifying aggregated same-run evidence) plus the
|
|
266
|
+
// finalize/qualify/publish scripts.
|
|
267
|
+
const allowedChainKeys = ["evidenceVerificationScript", "windowsFinalizeScript", "macosFinalizeScript", "installedQualificationScript", "publishScript", "qualificationRunnerVariable", "publishSecrets"];
|
|
256
268
|
if (chain === null || typeof chain !== "object" || Array.isArray(chain)) {
|
|
257
269
|
errors.push("candidate.releaseChain must be an object");
|
|
258
270
|
} else {
|
|
259
271
|
for (const key of Object.keys(chain)) {
|
|
260
272
|
if (!allowedChainKeys.includes(key)) errors.push(`unknown candidate.releaseChain field: ${key}`);
|
|
261
273
|
}
|
|
262
|
-
for (const key of ["windowsFinalizeScript", "installedQualificationScript", "publishScript"]) {
|
|
274
|
+
for (const key of ["evidenceVerificationScript", "windowsFinalizeScript", "installedQualificationScript", "publishScript"]) {
|
|
263
275
|
if (typeof chain[key] !== "string" || !PACKAGE_SCRIPT_RE.test(chain[key])) {
|
|
264
276
|
errors.push(`candidate.releaseChain.${key} must be one safe package script name`);
|
|
265
277
|
}
|
|
@@ -462,7 +474,7 @@ function windowsSigningJob(manifest, pm, artifactPrefix) {
|
|
|
462
474
|
const finalizeEnvironment = releaseChain
|
|
463
475
|
? ` RIGHT_GIT_UNSIGNED_CANDIDATE_ROOT: \${{ runner.temp }}/unsigned-windows-candidate
|
|
464
476
|
RIGHT_GIT_FINALIZED_WINDOWS_ROOT: \${{ runner.temp }}/finalized-windows
|
|
465
|
-
RIGHT_GIT_SOURCE_REVISION: \${{
|
|
477
|
+
RIGHT_GIT_SOURCE_REVISION: \${{ needs.admission.outputs.source_revision }}`
|
|
466
478
|
: ` LEGION_UNSIGNED_CANDIDATE_ROOT: \${{ runner.temp }}/unsigned-windows-candidate
|
|
467
479
|
LEGION_SOURCE_REVISION: \${{ env.RIGHT_GIT_SOURCE_REVISION }}`;
|
|
468
480
|
const uploadPath = releaseChain
|
|
@@ -470,8 +482,8 @@ function windowsSigningJob(manifest, pm, artifactPrefix) {
|
|
|
470
482
|
: " dist/releases/windows/**\n .right-release/receipts/windows-*.json";
|
|
471
483
|
return `
|
|
472
484
|
windows-sign:
|
|
473
|
-
needs: [candidate]
|
|
474
|
-
if: \${{
|
|
485
|
+
needs: [admission, candidate]
|
|
486
|
+
if: \${{ needs.admission.outputs.signed_qualification == 'true' }}
|
|
475
487
|
environment: release
|
|
476
488
|
strategy:
|
|
477
489
|
fail-fast: false
|
|
@@ -483,9 +495,9 @@ ${signingMatrix(manifest, "windows")}
|
|
|
483
495
|
id-token: write
|
|
484
496
|
steps:
|
|
485
497
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
498
|
+
with:
|
|
499
|
+
ref: \${{ needs.admission.outputs.source_revision }}
|
|
500
|
+
persist-credentials: false
|
|
489
501
|
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
490
502
|
with:
|
|
491
503
|
node-version: ${nodeVersion}
|
|
@@ -495,10 +507,24 @@ ${pm.setup}
|
|
|
495
507
|
with:
|
|
496
508
|
targets: \${{ matrix.targetTriple }}
|
|
497
509
|
- run: ${pm.installCommand}
|
|
510
|
+
- name: Initialize Windows signing stage summary
|
|
511
|
+
run: ${pm.runPrefix} ${manifest.candidate.stageSummaryScript}
|
|
512
|
+
env:
|
|
513
|
+
RIGHT_GIT_STAGE_ACTION: init
|
|
514
|
+
RIGHT_GIT_STAGE_ROOT: \${{ runner.temp }}/right-git-stage-summary
|
|
515
|
+
RIGHT_GIT_STAGE_EVIDENCE_ROOT: \${{ runner.temp }}/finalized-windows
|
|
516
|
+
RIGHT_GIT_STAGE: windows-sign
|
|
517
|
+
RIGHT_GIT_STAGE_PRODUCER: windows-sign-\${{ matrix.architecture }}
|
|
518
|
+
RIGHT_GIT_RELEASE_VERSION: \${{ needs.admission.outputs.version }}
|
|
519
|
+
RIGHT_GIT_SOURCE_REVISION: \${{ needs.admission.outputs.source_revision }}
|
|
520
|
+
RIGHT_GIT_RELEASE_PLATFORM: \${{ matrix.platform }}
|
|
521
|
+
RIGHT_GIT_RELEASE_ARCHITECTURE: \${{ matrix.architecture }}
|
|
522
|
+
RIGHT_GIT_RUN_ID: \${{ github.run_id }}
|
|
523
|
+
RIGHT_GIT_RUN_ATTEMPT: \${{ github.run_attempt }}
|
|
498
524
|
- name: Download exact unsigned Windows candidate
|
|
499
525
|
uses: actions/download-artifact@${DOWNLOAD_ARTIFACT_SHA} # v4.3.0
|
|
500
526
|
with:
|
|
501
|
-
name: ${artifactPrefix}-
|
|
527
|
+
name: ${artifactPrefix}-\${{ github.run_id }}-\${{ github.run_attempt }}-candidate-\${{ matrix.platform }}-\${{ matrix.architecture }}-\${{ needs.admission.outputs.version }}-\${{ needs.admission.outputs.source_revision }}
|
|
502
528
|
path: \${{ runner.temp }}/unsigned-windows-candidate
|
|
503
529
|
- name: Authenticate to Azure with GitHub OIDC
|
|
504
530
|
uses: azure/login@${AZURE_LOGIN_SHA} # v3
|
|
@@ -531,13 +557,38 @@ ${finalizeEnvironment}
|
|
|
531
557
|
AZURE_ARTIFACT_SIGNING_ENDPOINT: \${{ vars.AZURE_ARTIFACT_SIGNING_ENDPOINT }}
|
|
532
558
|
AZURE_ARTIFACT_SIGNING_ACCOUNT: \${{ vars.AZURE_ARTIFACT_SIGNING_ACCOUNT }}
|
|
533
559
|
AZURE_ARTIFACT_SIGNING_PROFILE: \${{ vars.AZURE_ARTIFACT_SIGNING_PROFILE }}
|
|
560
|
+
- name: Finalize Windows signing stage summary
|
|
561
|
+
if: \${{ always() }}
|
|
562
|
+
run: ${pm.runPrefix} ${manifest.candidate.stageSummaryScript}
|
|
563
|
+
env:
|
|
564
|
+
RIGHT_GIT_STAGE_ACTION: finalize
|
|
565
|
+
RIGHT_GIT_STAGE_ROOT: \${{ runner.temp }}/right-git-stage-summary
|
|
566
|
+
RIGHT_GIT_STAGE_EVIDENCE_ROOT: \${{ runner.temp }}/finalized-windows
|
|
567
|
+
RIGHT_GIT_STAGE: windows-sign
|
|
568
|
+
RIGHT_GIT_STAGE_PRODUCER: windows-sign-\${{ matrix.architecture }}
|
|
569
|
+
RIGHT_GIT_STAGE_STATUS: \${{ job.status == 'success' && 'SUCCEEDED' || 'FAILED' }}
|
|
570
|
+
RIGHT_GIT_STAGE_EXIT_CODE: \${{ job.status == 'success' && '0' || '1' }}
|
|
571
|
+
RIGHT_GIT_RELEASE_VERSION: \${{ needs.admission.outputs.version }}
|
|
572
|
+
RIGHT_GIT_SOURCE_REVISION: \${{ needs.admission.outputs.source_revision }}
|
|
573
|
+
RIGHT_GIT_RELEASE_PLATFORM: \${{ matrix.platform }}
|
|
574
|
+
RIGHT_GIT_RELEASE_ARCHITECTURE: \${{ matrix.architecture }}
|
|
575
|
+
RIGHT_GIT_RUN_ID: \${{ github.run_id }}
|
|
576
|
+
RIGHT_GIT_RUN_ATTEMPT: \${{ github.run_attempt }}
|
|
534
577
|
- name: Upload signed Windows candidate
|
|
578
|
+
if: \${{ always() }}
|
|
535
579
|
uses: actions/upload-artifact@${UPLOAD_ARTIFACT_SHA} # v4
|
|
536
580
|
with:
|
|
537
|
-
name: ${artifactPrefix}-signed-windows-\${{ matrix.architecture }}
|
|
581
|
+
name: ${artifactPrefix}-\${{ github.run_id }}-\${{ github.run_attempt }}-signed-windows-\${{ matrix.architecture }}-\${{ needs.admission.outputs.version }}-\${{ needs.admission.outputs.source_revision }}
|
|
538
582
|
path: |
|
|
539
583
|
${uploadPath}
|
|
540
584
|
if-no-files-found: error
|
|
585
|
+
- name: Upload Windows signing stage summary
|
|
586
|
+
if: \${{ always() }}
|
|
587
|
+
uses: actions/upload-artifact@${UPLOAD_ARTIFACT_SHA} # v4
|
|
588
|
+
with:
|
|
589
|
+
name: ${artifactPrefix}-\${{ github.run_id }}-\${{ github.run_attempt }}-summary-windows-sign-\${{ matrix.architecture }}-\${{ needs.admission.outputs.version }}-\${{ needs.admission.outputs.source_revision }}
|
|
590
|
+
path: \${{ runner.temp }}/right-git-stage-summary/stage-summary.json
|
|
591
|
+
if-no-files-found: error
|
|
541
592
|
`.trimEnd();
|
|
542
593
|
}
|
|
543
594
|
|
|
@@ -551,7 +602,7 @@ function macosSigningJob(manifest, pm, artifactPrefix) {
|
|
|
551
602
|
const finalizeEnvironment = releaseChain
|
|
552
603
|
? ` RIGHT_GIT_UNSIGNED_CANDIDATE_ROOT: \${{ runner.temp }}/unsigned-macos-candidate
|
|
553
604
|
RIGHT_GIT_FINALIZED_MACOS_ROOT: \${{ runner.temp }}/finalized-macos
|
|
554
|
-
RIGHT_GIT_SOURCE_REVISION: \${{
|
|
605
|
+
RIGHT_GIT_SOURCE_REVISION: \${{ needs.admission.outputs.source_revision }}`
|
|
555
606
|
: ` LEGION_UNSIGNED_CANDIDATE_ROOT: \${{ runner.temp }}/unsigned-macos-candidate
|
|
556
607
|
LEGION_SOURCE_REVISION: \${{ env.RIGHT_GIT_SOURCE_REVISION }}`;
|
|
557
608
|
const uploadPath = releaseChain
|
|
@@ -559,8 +610,8 @@ function macosSigningJob(manifest, pm, artifactPrefix) {
|
|
|
559
610
|
: " dist/releases/mac/**\n .right-release/receipts/macos-*.json";
|
|
560
611
|
return `
|
|
561
612
|
macos-sign:
|
|
562
|
-
needs: [candidate]
|
|
563
|
-
if: \${{
|
|
613
|
+
needs: [admission, candidate]
|
|
614
|
+
if: \${{ needs.admission.outputs.signed_qualification == 'true' }}
|
|
564
615
|
environment: release
|
|
565
616
|
strategy:
|
|
566
617
|
fail-fast: false
|
|
@@ -571,9 +622,9 @@ ${signingMatrix(manifest, "macos")}
|
|
|
571
622
|
contents: read
|
|
572
623
|
steps:
|
|
573
624
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
625
|
+
with:
|
|
626
|
+
ref: \${{ needs.admission.outputs.source_revision }}
|
|
627
|
+
persist-credentials: false
|
|
577
628
|
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
578
629
|
with:
|
|
579
630
|
node-version: ${nodeVersion}
|
|
@@ -583,10 +634,24 @@ ${pm.setup}
|
|
|
583
634
|
with:
|
|
584
635
|
targets: \${{ matrix.targetTriple }}
|
|
585
636
|
- run: ${pm.installCommand}
|
|
637
|
+
- name: Initialize macOS signing stage summary
|
|
638
|
+
run: ${pm.runPrefix} ${manifest.candidate.stageSummaryScript}
|
|
639
|
+
env:
|
|
640
|
+
RIGHT_GIT_STAGE_ACTION: init
|
|
641
|
+
RIGHT_GIT_STAGE_ROOT: \${{ runner.temp }}/right-git-stage-summary
|
|
642
|
+
RIGHT_GIT_STAGE_EVIDENCE_ROOT: \${{ runner.temp }}/finalized-macos
|
|
643
|
+
RIGHT_GIT_STAGE: macos-sign
|
|
644
|
+
RIGHT_GIT_STAGE_PRODUCER: macos-sign-\${{ matrix.architecture }}
|
|
645
|
+
RIGHT_GIT_RELEASE_VERSION: \${{ needs.admission.outputs.version }}
|
|
646
|
+
RIGHT_GIT_SOURCE_REVISION: \${{ needs.admission.outputs.source_revision }}
|
|
647
|
+
RIGHT_GIT_RELEASE_PLATFORM: \${{ matrix.platform }}
|
|
648
|
+
RIGHT_GIT_RELEASE_ARCHITECTURE: \${{ matrix.architecture }}
|
|
649
|
+
RIGHT_GIT_RUN_ID: \${{ github.run_id }}
|
|
650
|
+
RIGHT_GIT_RUN_ATTEMPT: \${{ github.run_attempt }}
|
|
586
651
|
- name: Download exact unsigned macOS candidate
|
|
587
652
|
uses: actions/download-artifact@${DOWNLOAD_ARTIFACT_SHA} # v4.3.0
|
|
588
653
|
with:
|
|
589
|
-
name: ${artifactPrefix}-
|
|
654
|
+
name: ${artifactPrefix}-\${{ github.run_id }}-\${{ github.run_attempt }}-candidate-\${{ matrix.platform }}-\${{ matrix.architecture }}-\${{ needs.admission.outputs.version }}-\${{ needs.admission.outputs.source_revision }}
|
|
590
655
|
path: \${{ runner.temp }}/unsigned-macos-candidate
|
|
591
656
|
- name: Install Apple signing identity in temporary keychain
|
|
592
657
|
shell: bash
|
|
@@ -618,13 +683,38 @@ ${finalizeEnvironment}
|
|
|
618
683
|
APPLE_DEVELOPER_ID: \${{ vars.APPLE_DEVELOPER_ID }}
|
|
619
684
|
APPLE_API_KEY: \${{ secrets.APPLE_API_KEY }}
|
|
620
685
|
APPLE_API_ISSUER: \${{ secrets.APPLE_API_ISSUER }}
|
|
686
|
+
- name: Finalize macOS signing stage summary
|
|
687
|
+
if: \${{ always() }}
|
|
688
|
+
run: ${pm.runPrefix} ${manifest.candidate.stageSummaryScript}
|
|
689
|
+
env:
|
|
690
|
+
RIGHT_GIT_STAGE_ACTION: finalize
|
|
691
|
+
RIGHT_GIT_STAGE_ROOT: \${{ runner.temp }}/right-git-stage-summary
|
|
692
|
+
RIGHT_GIT_STAGE_EVIDENCE_ROOT: \${{ runner.temp }}/finalized-macos
|
|
693
|
+
RIGHT_GIT_STAGE: macos-sign
|
|
694
|
+
RIGHT_GIT_STAGE_PRODUCER: macos-sign-\${{ matrix.architecture }}
|
|
695
|
+
RIGHT_GIT_STAGE_STATUS: \${{ job.status == 'success' && 'SUCCEEDED' || 'FAILED' }}
|
|
696
|
+
RIGHT_GIT_STAGE_EXIT_CODE: \${{ job.status == 'success' && '0' || '1' }}
|
|
697
|
+
RIGHT_GIT_RELEASE_VERSION: \${{ needs.admission.outputs.version }}
|
|
698
|
+
RIGHT_GIT_SOURCE_REVISION: \${{ needs.admission.outputs.source_revision }}
|
|
699
|
+
RIGHT_GIT_RELEASE_PLATFORM: \${{ matrix.platform }}
|
|
700
|
+
RIGHT_GIT_RELEASE_ARCHITECTURE: \${{ matrix.architecture }}
|
|
701
|
+
RIGHT_GIT_RUN_ID: \${{ github.run_id }}
|
|
702
|
+
RIGHT_GIT_RUN_ATTEMPT: \${{ github.run_attempt }}
|
|
621
703
|
- name: Upload signed & notarized macOS candidate
|
|
704
|
+
if: \${{ always() }}
|
|
622
705
|
uses: actions/upload-artifact@${UPLOAD_ARTIFACT_SHA} # v4
|
|
623
706
|
with:
|
|
624
|
-
name: ${artifactPrefix}-signed-macos-\${{ matrix.architecture }}
|
|
707
|
+
name: ${artifactPrefix}-\${{ github.run_id }}-\${{ github.run_attempt }}-signed-macos-\${{ matrix.architecture }}-\${{ needs.admission.outputs.version }}-\${{ needs.admission.outputs.source_revision }}
|
|
625
708
|
path: |
|
|
626
709
|
${uploadPath}
|
|
627
710
|
if-no-files-found: error
|
|
711
|
+
- name: Upload macOS signing stage summary
|
|
712
|
+
if: \${{ always() }}
|
|
713
|
+
uses: actions/upload-artifact@${UPLOAD_ARTIFACT_SHA} # v4
|
|
714
|
+
with:
|
|
715
|
+
name: ${artifactPrefix}-\${{ github.run_id }}-\${{ github.run_attempt }}-summary-macos-sign-\${{ matrix.architecture }}-\${{ needs.admission.outputs.version }}-\${{ needs.admission.outputs.source_revision }}
|
|
716
|
+
path: \${{ runner.temp }}/right-git-stage-summary/stage-summary.json
|
|
717
|
+
if-no-files-found: error
|
|
628
718
|
- name: Remove Apple signing material
|
|
629
719
|
if: \${{ always() }}
|
|
630
720
|
shell: bash
|
|
@@ -641,30 +731,71 @@ function releaseChainJobs(manifest, pm, artifactPrefix) {
|
|
|
641
731
|
const windowsRunner = windowsTarget.os;
|
|
642
732
|
const windowsArchitecture = windowsTarget.architecture;
|
|
643
733
|
const macosTarget = candidateTargets(manifest.candidate).find((target) => target.platform === "macos");
|
|
644
|
-
const
|
|
645
|
-
const macosDependency =
|
|
646
|
-
const macosSuccess =
|
|
647
|
-
|
|
734
|
+
const signMacos = manifest.candidate.signMacos === true;
|
|
735
|
+
const macosDependency = signMacos ? ", macos-sign" : "";
|
|
736
|
+
const macosSuccess = signMacos ? " && needs.macos-sign.result == 'success'" : "";
|
|
737
|
+
// Run- and identity-bound: matches exactly what windows-sign/macos-sign
|
|
738
|
+
// uploaded (see windowsSigningJob/macosSigningJob) — no matrix context is
|
|
739
|
+
// available in these non-matrixed jobs, so the platform architecture is a
|
|
740
|
+
// literal derived from the manifest's single windows/macos target.
|
|
741
|
+
const VERSION = "${{ needs.admission.outputs.version }}";
|
|
742
|
+
const SOURCE_REVISION = "${{ needs.admission.outputs.source_revision }}";
|
|
743
|
+
const windowsSignedArtifact = `${artifactPrefix}-\${{ github.run_id }}-\${{ github.run_attempt }}-signed-windows-${windowsArchitecture}-${VERSION}-${SOURCE_REVISION}`;
|
|
744
|
+
const windowsSummaryArtifact = `${artifactPrefix}-\${{ github.run_id }}-\${{ github.run_attempt }}-summary-windows-sign-${windowsArchitecture}-${VERSION}-${SOURCE_REVISION}`;
|
|
745
|
+
const macosSignedArtifact = signMacos ? `${artifactPrefix}-\${{ github.run_id }}-\${{ github.run_attempt }}-signed-macos-${macosTarget.architecture}-${VERSION}-${SOURCE_REVISION}` : "";
|
|
746
|
+
const macosSummaryArtifact = signMacos ? `${artifactPrefix}-\${{ github.run_id }}-\${{ github.run_attempt }}-summary-macos-sign-${macosTarget.architecture}-${VERSION}-${SOURCE_REVISION}` : "";
|
|
747
|
+
// Candidate stage summaries are per (platform, architecture) — see the
|
|
748
|
+
// `Upload candidate stage summary` step in the template. releaseChain
|
|
749
|
+
// requires at most one target per signing platform (validateManifest), so
|
|
750
|
+
// exactly one candidate-windows summary and at most one candidate-macos
|
|
751
|
+
// summary exist for this manifest.
|
|
752
|
+
const candidateWindowsSummaryArtifact = `${artifactPrefix}-\${{ github.run_id }}-\${{ github.run_attempt }}-summary-candidate-windows-${windowsArchitecture}-${VERSION}-${SOURCE_REVISION}`;
|
|
753
|
+
const candidateMacosSummaryArtifact = signMacos ? `${artifactPrefix}-\${{ github.run_id }}-\${{ github.run_attempt }}-summary-candidate-macos-${macosTarget.architecture}-${VERSION}-${SOURCE_REVISION}` : "";
|
|
754
|
+
const qualificationArtifact = `${artifactPrefix}-\${{ github.run_id }}-\${{ github.run_attempt }}-installed-qualification-${VERSION}-${SOURCE_REVISION}`;
|
|
755
|
+
const qualificationSummaryArtifact = `${artifactPrefix}-\${{ github.run_id }}-\${{ github.run_attempt }}-summary-installed-qualification-${VERSION}-${SOURCE_REVISION}`;
|
|
756
|
+
const macosDownload = signMacos
|
|
648
757
|
? ` - name: Download same-run signed & notarized macOS release
|
|
649
758
|
uses: actions/download-artifact@${DOWNLOAD_ARTIFACT_SHA} # v4.3.0
|
|
650
759
|
with:
|
|
651
|
-
name: ${
|
|
760
|
+
name: ${macosSignedArtifact}
|
|
652
761
|
path: \${{ runner.temp }}/right-git-release/macos`
|
|
653
762
|
: "";
|
|
763
|
+
const macosSummaryDownload = signMacos
|
|
764
|
+
? ` - name: Download same-run macOS signing stage summary
|
|
765
|
+
uses: actions/download-artifact@${DOWNLOAD_ARTIFACT_SHA} # v4.3.0
|
|
766
|
+
with:
|
|
767
|
+
name: ${macosSummaryArtifact}
|
|
768
|
+
path: \${{ runner.temp }}/right-git-release/stage-summaries/macos-sign`
|
|
769
|
+
: "";
|
|
770
|
+
const candidateMacosSummaryDownload = signMacos
|
|
771
|
+
? ` - name: Download same-run candidate (macOS) stage summary
|
|
772
|
+
uses: actions/download-artifact@${DOWNLOAD_ARTIFACT_SHA} # v4.3.0
|
|
773
|
+
with:
|
|
774
|
+
name: ${candidateMacosSummaryArtifact}
|
|
775
|
+
path: \${{ runner.temp }}/right-git-release/stage-summaries/candidate-macos`
|
|
776
|
+
: "";
|
|
777
|
+
const finalizedMacosEnv = signMacos ? `\n RIGHT_GIT_FINALIZED_MACOS_ROOT: \${{ runner.temp }}/right-git-release/macos` : "";
|
|
778
|
+
// Evidence must be bound to this exact run — not merely this
|
|
779
|
+
// version+source_revision — so a stale artifact from a different run
|
|
780
|
+
// cannot satisfy verification. Every job/step from installed-qualification
|
|
781
|
+
// onward carries RIGHT_GIT_RUN_ID/RIGHT_GIT_RUN_ATTEMPT alongside version
|
|
782
|
+
// and source_revision.
|
|
783
|
+
const runIdentityEnv = ` RIGHT_GIT_RUN_ID: \${{ github.run_id }}
|
|
784
|
+
RIGHT_GIT_RUN_ATTEMPT: \${{ github.run_attempt }}`;
|
|
654
785
|
const publishSecretEnvironment = (chain.publishSecrets ?? [])
|
|
655
786
|
.map((name) => ` ${name}: \${{ secrets.${name} }}`)
|
|
656
787
|
.join("\n");
|
|
657
788
|
return `
|
|
658
789
|
installed-qualification:
|
|
659
|
-
needs: [candidate, windows-sign]
|
|
660
|
-
if: \${{ always() &&
|
|
790
|
+
needs: [admission, candidate, windows-sign]
|
|
791
|
+
if: \${{ always() && needs.admission.outputs.signed_qualification == 'true' && needs.candidate.result == 'success' }}
|
|
661
792
|
environment: release
|
|
662
793
|
runs-on: \${{ vars.${chain.qualificationRunnerVariable} || '${windowsRunner}' }}
|
|
663
794
|
steps:
|
|
664
795
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
796
|
+
with:
|
|
797
|
+
ref: \${{ needs.admission.outputs.source_revision }}
|
|
798
|
+
persist-credentials: false
|
|
668
799
|
- name: Fail closed when protected finalization is absent
|
|
669
800
|
if: \${{ needs.windows-sign.result != 'success' }}
|
|
670
801
|
shell: pwsh
|
|
@@ -673,64 +804,127 @@ function releaseChainJobs(manifest, pm, artifactPrefix) {
|
|
|
673
804
|
- name: Download same-run finalized Windows release
|
|
674
805
|
uses: actions/download-artifact@${DOWNLOAD_ARTIFACT_SHA} # v4.3.0
|
|
675
806
|
with:
|
|
676
|
-
name: ${
|
|
807
|
+
name: ${windowsSignedArtifact}
|
|
677
808
|
path: \${{ runner.temp }}/right-git-release/windows
|
|
678
809
|
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
679
810
|
with:
|
|
680
|
-
node-version: ${
|
|
811
|
+
node-version: ${String(manifest.matrix.node[0])}
|
|
681
812
|
package-manager-cache: false
|
|
682
813
|
${pm.setup}
|
|
683
814
|
- run: ${pm.installCommand}
|
|
815
|
+
- name: Initialize installed-qualification stage summary
|
|
816
|
+
run: ${pm.runPrefix} ${manifest.candidate.stageSummaryScript}
|
|
817
|
+
env:
|
|
818
|
+
RIGHT_GIT_STAGE_ACTION: init
|
|
819
|
+
RIGHT_GIT_STAGE_ROOT: \${{ runner.temp }}/right-git-stage-summary
|
|
820
|
+
RIGHT_GIT_STAGE_EVIDENCE_ROOT: \${{ runner.temp }}/right-git-release/qualification
|
|
821
|
+
RIGHT_GIT_STAGE: installed-qualification
|
|
822
|
+
RIGHT_GIT_STAGE_PRODUCER: installed-qualification
|
|
823
|
+
RIGHT_GIT_RELEASE_VERSION: \${{ needs.admission.outputs.version }}
|
|
824
|
+
RIGHT_GIT_SOURCE_REVISION: \${{ needs.admission.outputs.source_revision }}
|
|
825
|
+
${runIdentityEnv}
|
|
684
826
|
- name: Run installed-artifact qualification
|
|
685
827
|
run: ${pm.runPrefix} ${chain.installedQualificationScript}
|
|
686
828
|
env:
|
|
687
829
|
GH_TOKEN: \${{ github.token }}
|
|
688
830
|
RIGHT_GIT_FINALIZED_WINDOWS_ROOT: \${{ runner.temp }}/right-git-release/windows
|
|
689
831
|
RIGHT_GIT_QUALIFICATION_EVIDENCE_ROOT: \${{ runner.temp }}/right-git-release/qualification
|
|
690
|
-
|
|
832
|
+
RIGHT_GIT_RELEASE_VERSION: \${{ needs.admission.outputs.version }}
|
|
833
|
+
RIGHT_GIT_SOURCE_REVISION: \${{ needs.admission.outputs.source_revision }}
|
|
834
|
+
- name: Finalize installed-qualification stage summary
|
|
835
|
+
if: \${{ always() }}
|
|
836
|
+
run: ${pm.runPrefix} ${manifest.candidate.stageSummaryScript}
|
|
837
|
+
env:
|
|
838
|
+
RIGHT_GIT_STAGE_ACTION: finalize
|
|
839
|
+
RIGHT_GIT_STAGE_ROOT: \${{ runner.temp }}/right-git-stage-summary
|
|
840
|
+
RIGHT_GIT_STAGE_EVIDENCE_ROOT: \${{ runner.temp }}/right-git-release/qualification
|
|
841
|
+
RIGHT_GIT_STAGE: installed-qualification
|
|
842
|
+
RIGHT_GIT_STAGE_PRODUCER: installed-qualification
|
|
843
|
+
RIGHT_GIT_STAGE_STATUS: \${{ job.status == 'success' && 'SUCCEEDED' || 'FAILED' }}
|
|
844
|
+
RIGHT_GIT_STAGE_EXIT_CODE: \${{ job.status == 'success' && '0' || '1' }}
|
|
845
|
+
RIGHT_GIT_RELEASE_VERSION: \${{ needs.admission.outputs.version }}
|
|
846
|
+
RIGHT_GIT_SOURCE_REVISION: \${{ needs.admission.outputs.source_revision }}
|
|
847
|
+
${runIdentityEnv}
|
|
691
848
|
- name: Upload installed qualification evidence
|
|
849
|
+
if: \${{ always() }}
|
|
692
850
|
uses: actions/upload-artifact@${UPLOAD_ARTIFACT_SHA} # v4
|
|
693
851
|
with:
|
|
694
|
-
name: ${
|
|
852
|
+
name: ${qualificationArtifact}
|
|
695
853
|
path: \${{ runner.temp }}/right-git-release/qualification/**
|
|
696
854
|
if-no-files-found: error
|
|
855
|
+
- name: Upload installed-qualification stage summary
|
|
856
|
+
if: \${{ always() }}
|
|
857
|
+
uses: actions/upload-artifact@${UPLOAD_ARTIFACT_SHA} # v4
|
|
858
|
+
with:
|
|
859
|
+
name: ${qualificationSummaryArtifact}
|
|
860
|
+
path: \${{ runner.temp }}/right-git-stage-summary/stage-summary.json
|
|
861
|
+
if-no-files-found: error
|
|
697
862
|
publish:
|
|
698
|
-
needs: [installed-qualification${macosDependency}]
|
|
699
|
-
if: \${{
|
|
863
|
+
needs: [admission, installed-qualification${macosDependency}]
|
|
864
|
+
if: \${{ needs.admission.outputs.publish == 'true' && needs.installed-qualification.result == 'success'${macosSuccess} }}
|
|
700
865
|
environment: release
|
|
701
866
|
runs-on: \${{ vars.${chain.qualificationRunnerVariable} || '${windowsRunner}' }}
|
|
702
867
|
permissions:
|
|
703
868
|
contents: write
|
|
704
869
|
steps:
|
|
705
870
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
871
|
+
with:
|
|
872
|
+
ref: \${{ needs.admission.outputs.source_revision }}
|
|
873
|
+
persist-credentials: false
|
|
709
874
|
- name: Download same-run finalized Windows release
|
|
710
875
|
uses: actions/download-artifact@${DOWNLOAD_ARTIFACT_SHA} # v4.3.0
|
|
711
876
|
with:
|
|
712
|
-
name: ${
|
|
877
|
+
name: ${windowsSignedArtifact}
|
|
713
878
|
path: \${{ runner.temp }}/right-git-release/windows
|
|
714
879
|
${macosDownload}
|
|
715
880
|
- name: Download same-run installed qualification evidence
|
|
716
881
|
uses: actions/download-artifact@${DOWNLOAD_ARTIFACT_SHA} # v4.3.0
|
|
717
882
|
with:
|
|
718
|
-
name: ${
|
|
883
|
+
name: ${qualificationArtifact}
|
|
719
884
|
path: \${{ runner.temp }}/right-git-release/qualification
|
|
885
|
+
- name: Download same-run candidate (Windows) stage summary
|
|
886
|
+
uses: actions/download-artifact@${DOWNLOAD_ARTIFACT_SHA} # v4.3.0
|
|
887
|
+
with:
|
|
888
|
+
name: ${candidateWindowsSummaryArtifact}
|
|
889
|
+
path: \${{ runner.temp }}/right-git-release/stage-summaries/candidate-windows
|
|
890
|
+
${candidateMacosSummaryDownload}
|
|
891
|
+
- name: Download same-run Windows signing stage summary
|
|
892
|
+
uses: actions/download-artifact@${DOWNLOAD_ARTIFACT_SHA} # v4.3.0
|
|
893
|
+
with:
|
|
894
|
+
name: ${windowsSummaryArtifact}
|
|
895
|
+
path: \${{ runner.temp }}/right-git-release/stage-summaries/windows-sign
|
|
896
|
+
${macosSummaryDownload}
|
|
897
|
+
- name: Download same-run installed-qualification stage summary
|
|
898
|
+
uses: actions/download-artifact@${DOWNLOAD_ARTIFACT_SHA} # v4.3.0
|
|
899
|
+
with:
|
|
900
|
+
name: ${qualificationSummaryArtifact}
|
|
901
|
+
path: \${{ runner.temp }}/right-git-release/stage-summaries/installed-qualification
|
|
720
902
|
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
721
903
|
with:
|
|
722
|
-
node-version: ${
|
|
904
|
+
node-version: ${String(manifest.matrix.node[0])}
|
|
723
905
|
package-manager-cache: false
|
|
724
906
|
${pm.setup}
|
|
725
907
|
- run: ${pm.installCommand}
|
|
908
|
+
- name: Verify aggregated release evidence
|
|
909
|
+
run: ${pm.runPrefix} ${chain.evidenceVerificationScript}
|
|
910
|
+
env:
|
|
911
|
+
GH_TOKEN: \${{ github.token }}
|
|
912
|
+
RIGHT_GIT_FINALIZED_WINDOWS_ROOT: \${{ runner.temp }}/right-git-release/windows${finalizedMacosEnv}
|
|
913
|
+
RIGHT_GIT_QUALIFICATION_EVIDENCE_ROOT: \${{ runner.temp }}/right-git-release/qualification
|
|
914
|
+
RIGHT_GIT_STAGE_SUMMARY_ROOT: \${{ runner.temp }}/right-git-release/stage-summaries
|
|
915
|
+
RIGHT_GIT_RELEASE_VERSION: \${{ needs.admission.outputs.version }}
|
|
916
|
+
RIGHT_GIT_SOURCE_REVISION: \${{ needs.admission.outputs.source_revision }}
|
|
917
|
+
${runIdentityEnv}
|
|
726
918
|
- name: Publish qualified signed release
|
|
727
919
|
run: ${pm.runPrefix} ${chain.publishScript}
|
|
728
920
|
env:
|
|
729
921
|
GH_TOKEN: \${{ github.token }}
|
|
730
|
-
RIGHT_GIT_FINALIZED_WINDOWS_ROOT: \${{ runner.temp }}/right-git-release/windows
|
|
731
|
-
RIGHT_GIT_FINALIZED_MACOS_ROOT: \${{ runner.temp }}/right-git-release/macos
|
|
922
|
+
RIGHT_GIT_FINALIZED_WINDOWS_ROOT: \${{ runner.temp }}/right-git-release/windows${finalizedMacosEnv}
|
|
732
923
|
RIGHT_GIT_QUALIFICATION_EVIDENCE_ROOT: \${{ runner.temp }}/right-git-release/qualification
|
|
733
|
-
|
|
924
|
+
RIGHT_GIT_STAGE_SUMMARY_ROOT: \${{ runner.temp }}/right-git-release/stage-summaries
|
|
925
|
+
RIGHT_GIT_RELEASE_VERSION: \${{ needs.admission.outputs.version }}
|
|
926
|
+
RIGHT_GIT_SOURCE_REVISION: \${{ needs.admission.outputs.source_revision }}
|
|
927
|
+
${runIdentityEnv}
|
|
734
928
|
${publishSecretEnvironment}
|
|
735
929
|
`.trimEnd();
|
|
736
930
|
}
|
|
@@ -777,19 +971,39 @@ export function buildRenderContext(manifest, repoPath) {
|
|
|
777
971
|
PACKAGE_MANAGER_SETUP: pm.setup,
|
|
778
972
|
ARTIFACT_PREFIX: artifactPrefix,
|
|
779
973
|
TEMPLATE_VERSION: manifest.templateVersion,
|
|
780
|
-
GATE_PATH: manifest.gate?.path,
|
|
781
974
|
RUST_CACHE_WORKSPACES: rustWorkspaces.map((workspace) => ` ${workspace} -> target`).join("\n"),
|
|
782
975
|
RUST_REQUIRED_FOR_GATE: manifest.rustCache?.requiredForGate === true ? "true" : "false",
|
|
783
|
-
CANDIDATE_BUILD_SCRIPT: manifest.candidate?.buildScript,
|
|
784
|
-
CANDIDATE_CHECK_SCRIPT: manifest.candidate?.checkScript,
|
|
785
|
-
CANDIDATE_ARTIFACT_ROOT: manifest.candidate?.artifactRoot,
|
|
786
|
-
CANDIDATE_OS_MATRIX: yamlFlowStringList(manifest.candidate?.os ?? manifest.matrix.os),
|
|
787
|
-
CANDIDATE_MATRIX: candidateMatrix(manifest),
|
|
788
976
|
WINDOWS_SIGNING_JOB: windowsSigningJob(manifest, pm, artifactPrefix),
|
|
789
977
|
MACOS_SIGNING_JOB: macosSigningJob(manifest, pm, artifactPrefix),
|
|
790
978
|
RELEASE_CHAIN_JOBS: releaseChainJobs(manifest, pm, artifactPrefix),
|
|
791
979
|
};
|
|
792
980
|
|
|
981
|
+
// Every key below is populated ONLY when its source manifest section is
|
|
982
|
+
// actually present — mirroring the publish.npm/homebrew/winget pattern.
|
|
983
|
+
// This keeps the fail-closed check just below honest: a key that is
|
|
984
|
+
// legitimately inapplicable to this manifest (e.g. CANDIDATE_* for a
|
|
985
|
+
// non-rust-hybrid manifest, GATE_PATH for a non-rust-hybrid profile) is
|
|
986
|
+
// simply ABSENT from the context rather than present-with-undefined, so
|
|
987
|
+
// render.mjs's own `Object.hasOwn` check does the right thing if a
|
|
988
|
+
// template ever references a token this manifest didn't opt into.
|
|
989
|
+
if (manifest.gate) {
|
|
990
|
+
context.GATE_PATH = manifest.gate.path;
|
|
991
|
+
}
|
|
992
|
+
if (manifest.candidate) {
|
|
993
|
+
context.CANDIDATE_BUILD_SCRIPT = manifest.candidate.buildScript;
|
|
994
|
+
context.CANDIDATE_CHECK_SCRIPT = manifest.candidate.checkScript;
|
|
995
|
+
context.CANDIDATE_ARTIFACT_ROOT = manifest.candidate.artifactRoot;
|
|
996
|
+
context.CANDIDATE_OS_MATRIX = yamlFlowStringList(manifest.candidate.os ?? manifest.matrix.os);
|
|
997
|
+
context.CANDIDATE_MATRIX = candidateMatrix(manifest);
|
|
998
|
+
// admissionScript & stageSummaryScript are required candidate fields
|
|
999
|
+
// (see validateManifest) whenever release-candidate is configured at
|
|
1000
|
+
// all — the admission job and the candidate job's stage-summary steps
|
|
1001
|
+
// are unconditional parts of the rendered template, independent of
|
|
1002
|
+
// signing/releaseChain.
|
|
1003
|
+
context.RELEASE_ADMISSION_SCRIPT = manifest.candidate.admissionScript;
|
|
1004
|
+
context.RELEASE_STAGE_SUMMARY_SCRIPT = manifest.candidate.stageSummaryScript;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
793
1007
|
if (manifest.publish?.npm) {
|
|
794
1008
|
context.NPM_PROVENANCE_FLAG = manifest.publish.npm.provenance ? " --provenance" : "";
|
|
795
1009
|
}
|
|
@@ -801,5 +1015,21 @@ export function buildRenderContext(manifest, repoPath) {
|
|
|
801
1015
|
context.WINGET_PACKAGE_IDENTIFIER = manifest.publish.winget.packageIdentifier;
|
|
802
1016
|
}
|
|
803
1017
|
|
|
1018
|
+
// F-undefined-script fix: render.mjs's `Object.hasOwn` check only proves a
|
|
1019
|
+
// key EXISTS in the context — it does not prove the key resolved to a real
|
|
1020
|
+
// value. Every field above that is sourced from an optional manifest path
|
|
1021
|
+
// (candidate.*, candidate.releaseChain.* via the job-builder functions,
|
|
1022
|
+
// gate.path) must resolve to a real string once its guarding `if` above
|
|
1023
|
+
// decided the section applies; a bug in one of those guards (or a
|
|
1024
|
+
// mis-nested manifest field) must fail the render loudly here rather than
|
|
1025
|
+
// silently emit a literal "undefined" into a workflow file that GitHub
|
|
1026
|
+
// Actions will then try to execute as a real step (e.g. `run: pnpm run
|
|
1027
|
+
// undefined`).
|
|
1028
|
+
for (const [key, value] of Object.entries(context)) {
|
|
1029
|
+
if (value === undefined || value === null) {
|
|
1030
|
+
throw new Error(`buildRenderContext: placeholder \${${key}} resolved to ${value} — check the manifest's candidate/releaseChain/gate/publish configuration`);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
804
1034
|
return context;
|
|
805
1035
|
}
|
package/manifest.schema.json
CHANGED
|
@@ -105,11 +105,21 @@
|
|
|
105
105
|
},
|
|
106
106
|
"candidate": {
|
|
107
107
|
"type": "object",
|
|
108
|
-
"required": ["buildScript", "checkScript", "artifactRoot", "os"],
|
|
108
|
+
"required": ["buildScript", "checkScript", "admissionScript", "stageSummaryScript", "artifactRoot", "os"],
|
|
109
109
|
"additionalProperties": false,
|
|
110
110
|
"properties": {
|
|
111
111
|
"buildScript": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9:_-]*$" },
|
|
112
112
|
"checkScript": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9:_-]*$" },
|
|
113
|
+
"admissionScript": {
|
|
114
|
+
"type": "string",
|
|
115
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9:_-]*$",
|
|
116
|
+
"description": "Runs in the unconditional `admission` job (workflow_dispatch envelope validation). Required for every release-candidate lane, signed or not."
|
|
117
|
+
},
|
|
118
|
+
"stageSummaryScript": {
|
|
119
|
+
"type": "string",
|
|
120
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9:_-]*$",
|
|
121
|
+
"description": "Runs (init/finalize) in the `candidate` job and, when configured, the windows-sign/macos-sign jobs. Required for every release-candidate lane, signed or not."
|
|
122
|
+
},
|
|
113
123
|
"artifactRoot": { "type": "string", "pattern": "^(?!.*\\.\\.)(?!.*\\s)[A-Za-z0-9][A-Za-z0-9._/-]*$" },
|
|
114
124
|
"os": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } },
|
|
115
125
|
"targets": {
|
|
@@ -130,9 +140,14 @@
|
|
|
130
140
|
"signMacos": { "type": "boolean" },
|
|
131
141
|
"releaseChain": {
|
|
132
142
|
"type": "object",
|
|
133
|
-
"required": ["windowsFinalizeScript", "installedQualificationScript", "publishScript", "qualificationRunnerVariable"],
|
|
143
|
+
"required": ["evidenceVerificationScript", "windowsFinalizeScript", "installedQualificationScript", "publishScript", "qualificationRunnerVariable"],
|
|
134
144
|
"additionalProperties": false,
|
|
135
145
|
"properties": {
|
|
146
|
+
"evidenceVerificationScript": {
|
|
147
|
+
"type": "string",
|
|
148
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9:_-]*$",
|
|
149
|
+
"description": "Runs in the `publish` job before the publish step, verifying the aggregated same-run signed/qualification evidence."
|
|
150
|
+
},
|
|
136
151
|
"windowsFinalizeScript": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9:_-]*$" },
|
|
137
152
|
"macosFinalizeScript": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9:_-]*$" },
|
|
138
153
|
"installedQualificationScript": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9:_-]*$" },
|