From 79b709f9ef71901628819c1bc63ac635e01de91a Mon Sep 17 00:00:00 2001 From: John McChesney TenEyck Jr Date: Wed, 20 May 2026 14:00:43 -0500 Subject: [PATCH] Expose release hook secrets Pass optional release hook secret values through the reusable release job environment so repo publish scripts can sign, notarize, and upload artifacts. Also document caller secret inheritance and cover the contract in reusable workflow tests. --- .github/workflows/release.yml | 28 ++++++++++++++++++++++++++++ docs/bootstrap/tier-a-ci-contract.md | 9 +++++++++ tests/reusable-workflows.test.ts | 13 +++++++++++++ 3 files changed, 50 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9da803d..ab9a702 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -63,6 +63,25 @@ on: required: false default: true type: boolean + secrets: + DOCKYARD_DEVELOPER_ID_APPLICATION: + description: Optional Dockyard Developer ID signing identity for release hooks + required: false + DOCKYARD_KEYCHAIN_ACCESS_GROUP: + description: Optional Dockyard keychain access group for release hooks + required: false + DOCKYARD_NOTARY_KEYCHAIN_PROFILE: + description: Optional Dockyard notarytool keychain profile for release hooks + required: false + DOCKYARD_SPARKLE_PRIVATE_KEY_FILE: + description: Optional Dockyard Sparkle private key file path for release hooks + required: false + DOCKYARD_SPARKLE_SIGN_UPDATE: + description: Optional Dockyard Sparkle sign_update executable path for release hooks + required: false + SPARKLE_FRAMEWORK_PATH: + description: Optional Sparkle framework path used by release hooks + required: false permissions: contents: write @@ -73,6 +92,13 @@ jobs: release: name: release runs-on: ${{ fromJSON(inputs.runs-on) }} + env: + DOCKYARD_DEVELOPER_ID_APPLICATION: ${{ secrets.DOCKYARD_DEVELOPER_ID_APPLICATION }} + DOCKYARD_KEYCHAIN_ACCESS_GROUP: ${{ secrets.DOCKYARD_KEYCHAIN_ACCESS_GROUP }} + DOCKYARD_NOTARY_KEYCHAIN_PROFILE: ${{ secrets.DOCKYARD_NOTARY_KEYCHAIN_PROFILE }} + DOCKYARD_SPARKLE_PRIVATE_KEY_FILE: ${{ secrets.DOCKYARD_SPARKLE_PRIVATE_KEY_FILE }} + DOCKYARD_SPARKLE_SIGN_UPDATE: ${{ secrets.DOCKYARD_SPARKLE_SIGN_UPDATE }} + SPARKLE_FRAMEWORK_PATH: ${{ secrets.SPARKLE_FRAMEWORK_PATH }} steps: - uses: actions/checkout@v4 with: @@ -158,6 +184,8 @@ jobs: echo "No executable release artifact build hook is configured: ${{ inputs.build-script }}" fi - name: Publish release artifacts + env: + GH_TOKEN: ${{ github.token }} run: | if [[ ! -x "${{ inputs.publish-script }}" ]]; then echo "Missing executable publish script: ${{ inputs.publish-script }}" >&2 diff --git a/docs/bootstrap/tier-a-ci-contract.md b/docs/bootstrap/tier-a-ci-contract.md index 25aa43f..d2f571e 100644 --- a/docs/bootstrap/tier-a-ci-contract.md +++ b/docs/bootstrap/tier-a-ci-contract.md @@ -69,6 +69,7 @@ on: jobs: release: uses: OMT-Global/bootstrap/.github/workflows/release.yml@refs/heads/main + secrets: inherit with: runs-on: '["ubuntu-latest"]' verify-script: scripts/ci/run-release-verification.sh @@ -83,6 +84,14 @@ jobs: update-minor-tag: true ``` +Release hooks that need signing, notarization, or external publish credentials +must read them from environment variables. The reusable release workflow passes +declared caller secrets through to hook scripts, and the caller must include +`secrets: inherit` or explicitly map those values. For macOS app releases this +includes Dockyard-compatible variables such as +`DOCKYARD_DEVELOPER_ID_APPLICATION`, `DOCKYARD_KEYCHAIN_ACCESS_GROUP`, +`DOCKYARD_NOTARY_KEYCHAIN_PROFILE`, and optional Sparkle paths. + Release policy: - create immutable exact tags such as `v1.2.3` diff --git a/tests/reusable-workflows.test.ts b/tests/reusable-workflows.test.ts index b5a0ad9..b72dca8 100644 --- a/tests/reusable-workflows.test.ts +++ b/tests/reusable-workflows.test.ts @@ -30,8 +30,19 @@ describe("reusable workflows", () => { expect((workflow.on as any).workflow_call.inputs["artifact-dir"].default).toBe("dist/release"); expect((workflow.on as any).workflow_call.inputs["tag-prefix"].default).toBe("v"); expect((workflow.on as any).workflow_call.inputs["update-major-tag"].default).toBe(true); + const releaseSecrets = (workflow.on as any).workflow_call.secrets; + expect(releaseSecrets.DOCKYARD_DEVELOPER_ID_APPLICATION.required).toBe(false); + expect(releaseSecrets.DOCKYARD_KEYCHAIN_ACCESS_GROUP.required).toBe(false); + expect(releaseSecrets.DOCKYARD_NOTARY_KEYCHAIN_PROFILE.required).toBe(false); const releaseJob = (workflow.jobs as any).release; expect(releaseJob).toBeTruthy(); + expect(releaseJob.env.DOCKYARD_DEVELOPER_ID_APPLICATION).toBe( + "${{ secrets.DOCKYARD_DEVELOPER_ID_APPLICATION }}" + ); + expect(releaseJob.env.DOCKYARD_KEYCHAIN_ACCESS_GROUP).toBe("${{ secrets.DOCKYARD_KEYCHAIN_ACCESS_GROUP }}"); + expect(releaseJob.env.DOCKYARD_NOTARY_KEYCHAIN_PROFILE).toBe( + "${{ secrets.DOCKYARD_NOTARY_KEYCHAIN_PROFILE }}" + ); const stepNames = releaseJob.steps.map((step: any) => step.name).filter(Boolean); expect(stepNames).toEqual([ "Derive release metadata", @@ -44,9 +55,11 @@ describe("reusable workflows", () => { "Promote floating SemVer tags" ]); const deriveMetadata = releaseJob.steps.find((step: any) => step.name === "Derive release metadata"); + const publishArtifacts = releaseJob.steps.find((step: any) => step.name === "Publish release artifacts"); const createRelease = releaseJob.steps.find((step: any) => step.name === "Create GitHub release"); const promoteTags = releaseJob.steps.find((step: any) => step.name === "Promote floating SemVer tags"); expect(deriveMetadata.run).toContain("semver_component='(0|[1-9][0-9]*)'"); + expect(publishArtifacts.env.GH_TOKEN).toBe("${{ github.token }}"); expect(deriveMetadata.run).toContain("prerelease_identifier="); expect(deriveMetadata.run).toContain("is_prerelease=${is_prerelease}"); expect(createRelease.run).toContain("release_create_args=(--prerelease --latest=false)");