Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions docs/bootstrap/tier-a-ci-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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`
Expand Down
13 changes: 13 additions & 0 deletions tests/reusable-workflows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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)");
Expand Down
Loading