Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
539 lines
22 KiB
YAML
539 lines
22 KiB
YAML
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you under the Apache License, Version 2.0 (the
|
|
# "License"); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing,
|
|
# software distributed under the License is distributed on an
|
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
# KIND, either express or implied. See the License for the
|
|
# specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
name: Release
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
source_reference_tag:
|
|
description: Exact approved ASF source candidate tag (v<version>-incubating-rc<positive-integer>)
|
|
required: true
|
|
type: string
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: product-release
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
release-identity:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
outputs:
|
|
version: ${{ steps.identity.outputs.version }}
|
|
tag: ${{ steps.identity.outputs.tag }}
|
|
source_commit: ${{ steps.identity.outputs.source_commit }}
|
|
source_reference_tag: ${{ steps.identity.outputs.source_reference_tag }}
|
|
dmg: ${{ steps.identity.outputs.dmg }}
|
|
exe: ${{ steps.identity.outputs.exe }}
|
|
cli_archive: ${{ steps.identity.outputs.cli_archive }}
|
|
node_version: ${{ steps.identity.outputs.node_version }}
|
|
npm_version: ${{ steps.identity.outputs.npm_version }}
|
|
node_archive: ${{ steps.identity.outputs.node_archive }}
|
|
node_archive_sha256: ${{ steps.identity.outputs.node_archive_sha256 }}
|
|
node_source_url: ${{ steps.identity.outputs.node_source_url }}
|
|
|
|
steps:
|
|
- name: Check out the dispatched commit
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
|
|
- name: Resolve product release identity
|
|
id: identity
|
|
env:
|
|
GITHUB_SHA: ${{ github.sha }}
|
|
SOURCE_REFERENCE_TAG: ${{ inputs.source_reference_tag }}
|
|
run: node scripts/product-release-identity.mjs
|
|
|
|
- name: Require the exact ASF source reference
|
|
env:
|
|
RELEASE_REF: ${{ github.ref }}
|
|
RELEASE_REPOSITORY: ${{ github.repository }}
|
|
SOURCE_COMMIT: ${{ steps.identity.outputs.source_commit }}
|
|
SOURCE_REFERENCE_TAG: ${{ steps.identity.outputs.source_reference_tag }}
|
|
run: |
|
|
if [[ "$RELEASE_REPOSITORY" != "apache/maka" ]]; then
|
|
echo "Product releases must run in apache/maka; found $RELEASE_REPOSITORY" >&2
|
|
exit 1
|
|
fi
|
|
if [[ "$RELEASE_REF" != "refs/tags/$SOURCE_REFERENCE_TAG" ]]; then
|
|
echo "Product releases must be dispatched from $SOURCE_REFERENCE_TAG; found $RELEASE_REF" >&2
|
|
exit 1
|
|
fi
|
|
git fetch --force --no-tags origin \
|
|
"refs/tags/$SOURCE_REFERENCE_TAG:refs/tags/$SOURCE_REFERENCE_TAG"
|
|
test "$(git cat-file -t "refs/tags/$SOURCE_REFERENCE_TAG")" = tag
|
|
test "$(git rev-parse "refs/tags/$SOURCE_REFERENCE_TAG^{commit}")" = "$SOURCE_COMMIT"
|
|
git fetch --no-tags origin main:refs/remotes/origin/main
|
|
git merge-base --is-ancestor "$SOURCE_COMMIT" origin/main
|
|
|
|
desktop:
|
|
needs: release-identity
|
|
strategy:
|
|
# Both platforms ship from one dispatch, so a failure on one still leaves
|
|
# the other artifact available for diagnosis.
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- platform: macos
|
|
runner: macos-15
|
|
- platform: windows
|
|
runner: windows-2025
|
|
runs-on: ${{ matrix.runner }}
|
|
environment: release
|
|
timeout-minutes: 75
|
|
defaults:
|
|
run:
|
|
# Windows runners default to pwsh; the release steps are written once,
|
|
# in bash, for both platforms.
|
|
shell: bash
|
|
|
|
steps:
|
|
- name: Check out the dispatched commit
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
ref: ${{ needs.release-identity.outputs.source_commit }}
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
|
|
- name: Set up the pinned release Node.js
|
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
with:
|
|
node-version: ${{ needs.release-identity.outputs.node_version }}
|
|
cache: npm
|
|
|
|
- name: Select and verify the pinned npm release toolchain
|
|
run: |
|
|
npm install --global --no-audit --no-fund "npm@${{ needs.release-identity.outputs.npm_version }}"
|
|
test "$(npm --version)" = "${{ needs.release-identity.outputs.npm_version }}"
|
|
|
|
- name: Install dependencies with the pinned npm
|
|
run: npm ci
|
|
|
|
- name: Audit production dependencies
|
|
run: npm audit --omit=dev --audit-level=moderate
|
|
|
|
- name: Audit shipped desktop closure
|
|
run: node scripts/audit-shipped-dependencies.mjs
|
|
|
|
- name: Update stable Rust for Desktop native artifacts
|
|
run: rustup update stable --no-self-update
|
|
|
|
- name: Write App Store Connect API key
|
|
if: matrix.platform == 'macos'
|
|
env:
|
|
APPLE_API_KEY_CONTENT: ${{ secrets.APPLE_API_KEY }}
|
|
APPLE_API_KEY_PATH: ${{ runner.temp }}/AuthKey_Maka.p8
|
|
run: |
|
|
umask 077
|
|
printf '%s' "$APPLE_API_KEY_CONTENT" > "$APPLE_API_KEY_PATH"
|
|
|
|
- name: Package notarized app and signed DMG
|
|
if: matrix.platform == 'macos'
|
|
env:
|
|
CSC_LINK: ${{ secrets.CSC_LINK }}
|
|
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
|
APPLE_API_KEY: ${{ runner.temp }}/AuthKey_Maka.p8
|
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
|
run: npm run package:macos-arm64
|
|
|
|
- name: Notarize and staple the signed final DMG
|
|
if: matrix.platform == 'macos'
|
|
env:
|
|
APPLE_API_KEY: ${{ runner.temp }}/AuthKey_Maka.p8
|
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
|
DMG_PATH: apps/desktop/release/${{ needs.release-identity.outputs.dmg }}
|
|
run: |
|
|
codesign --verify --verbose=4 "$DMG_PATH"
|
|
xcrun notarytool submit "$DMG_PATH" \
|
|
--key "$APPLE_API_KEY" \
|
|
--key-id "$APPLE_API_KEY_ID" \
|
|
--issuer "$APPLE_API_ISSUER" \
|
|
--wait
|
|
xcrun stapler staple "$DMG_PATH"
|
|
|
|
codesign --verify --verbose=4 "$DMG_PATH"
|
|
xcrun stapler validate "$DMG_PATH"
|
|
spctl --assess \
|
|
--type open \
|
|
--context context:primary-signature \
|
|
--verbose=4 \
|
|
"$DMG_PATH"
|
|
|
|
- name: Verify the final DMG
|
|
if: matrix.platform == 'macos'
|
|
run: npm run verify:macos-arm64 -- "apps/desktop/release/${{ needs.release-identity.outputs.dmg }}"
|
|
|
|
- name: Build the version-bumped macOS update
|
|
if: matrix.platform == 'macos'
|
|
env:
|
|
CSC_LINK: ${{ secrets.CSC_LINK }}
|
|
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
|
run: npm run package:macos-autoupdate-next
|
|
|
|
- name: Verify macOS automatic update end to end
|
|
if: matrix.platform == 'macos'
|
|
run: |
|
|
npm run verify:macos-autoupdate -- \
|
|
"apps/desktop/release/Maka-${{ needs.release-identity.outputs.version }}-mac-arm64.zip" \
|
|
apps/desktop/release-autoupdate-next
|
|
|
|
# Windows has no Authenticode certificate yet, so this build is unsigned
|
|
# and there is nothing to notarize between packaging and verification.
|
|
- name: Package the Windows installer and ZIP
|
|
if: matrix.platform == 'windows'
|
|
run: npm run package:windows-x64
|
|
|
|
- name: Verify the Windows release
|
|
if: matrix.platform == 'windows'
|
|
run: npm run verify:windows-x64 -- "apps/desktop/release/${{ needs.release-identity.outputs.exe }}"
|
|
|
|
- name: Download and verify the pinned Windows upgrade baseline
|
|
id: previous
|
|
if: matrix.platform == 'windows'
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
previous_exe="$(node scripts/prepare-windows-upgrade-baseline.mjs \
|
|
"${{ needs.release-identity.outputs.version }}" artifacts/windows-upgrade-baseline)"
|
|
echo "exe=$previous_exe" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Exercise pinned Windows upgrade and uninstall
|
|
if: matrix.platform == 'windows'
|
|
run: |
|
|
npm run verify:windows-installer -- \
|
|
"apps/desktop/release/${{ needs.release-identity.outputs.exe }}" \
|
|
"${{ steps.previous.outputs.exe }}"
|
|
|
|
- name: Build the version-bumped autoupdate installer
|
|
if: matrix.platform == 'windows'
|
|
run: npm run package:windows-autoupdate-next
|
|
|
|
- name: Verify automatic update end to end
|
|
if: matrix.platform == 'windows'
|
|
run: |
|
|
npm run verify:windows-autoupdate -- \
|
|
"apps/desktop/release/${{ needs.release-identity.outputs.exe }}" \
|
|
apps/desktop/release-autoupdate-next
|
|
|
|
- name: Prove deterministic mid-install failure rollback
|
|
if: matrix.platform == 'windows'
|
|
run: |
|
|
npm run verify:windows-installer-rollback -- \
|
|
"apps/desktop/release/${{ needs.release-identity.outputs.exe }}" \
|
|
apps/desktop/release-autoupdate-next
|
|
|
|
- name: Stage the exact product artifact group
|
|
run: >-
|
|
node scripts/product-release-artifacts.mjs stage
|
|
"desktop-${{ matrix.platform }}"
|
|
apps/desktop/release
|
|
"${{ runner.temp }}/release-assets"
|
|
|
|
- name: Upload the verified release assets
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: release-desktop-${{ matrix.platform }}-${{ github.run_attempt }}
|
|
path: ${{ runner.temp }}/release-assets
|
|
if-no-files-found: error
|
|
retention-days: 30
|
|
|
|
- name: Remove temporary release credentials
|
|
if: always() && matrix.platform == 'macos'
|
|
run: rm -f "${{ runner.temp }}/AuthKey_Maka.p8"
|
|
|
|
cli-macos-arm64:
|
|
needs: release-identity
|
|
runs-on: macos-15
|
|
environment: release
|
|
timeout-minutes: 45
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
|
|
steps:
|
|
- name: Check out the dispatched commit
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
ref: ${{ needs.release-identity.outputs.source_commit }}
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
|
|
- name: Set up the pinned release Node.js
|
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
with:
|
|
node-version: ${{ needs.release-identity.outputs.node_version }}
|
|
cache: npm
|
|
|
|
- name: Select and verify the pinned npm release toolchain
|
|
run: |
|
|
npm install --global --no-audit --no-fund "npm@${{ needs.release-identity.outputs.npm_version }}"
|
|
test "$(npm --version)" = "${{ needs.release-identity.outputs.npm_version }}"
|
|
|
|
- name: Install dependencies with the pinned npm
|
|
run: npm ci
|
|
|
|
- name: Audit production dependencies
|
|
run: npm audit --omit=dev --audit-level=moderate
|
|
|
|
- name: Build the CLI production workspace closure
|
|
run: |
|
|
npm --workspace maka-agent run build:workspace-deps
|
|
npm --workspace maka-agent run build
|
|
npm run check:cli-third-party-notices
|
|
|
|
- name: Download and verify the official Node runtime
|
|
env:
|
|
NODE_ARCHIVE: ${{ runner.temp }}/${{ needs.release-identity.outputs.node_archive }}
|
|
NODE_ARCHIVE_SHA256: ${{ needs.release-identity.outputs.node_archive_sha256 }}
|
|
NODE_SOURCE_URL: ${{ needs.release-identity.outputs.node_source_url }}
|
|
run: |
|
|
curl --proto '=https' --tlsv1.2 --fail --location \
|
|
--output "$NODE_ARCHIVE" "$NODE_SOURCE_URL"
|
|
actual="$(shasum -a 256 "$NODE_ARCHIVE" | awk '{print $1}')"
|
|
test "$actual" = "$NODE_ARCHIVE_SHA256"
|
|
|
|
- name: Write App Store Connect API key
|
|
env:
|
|
APPLE_API_KEY_CONTENT: ${{ secrets.APPLE_API_KEY }}
|
|
APPLE_API_KEY_PATH: ${{ runner.temp }}/AuthKey_Maka.p8
|
|
run: |
|
|
umask 077
|
|
printf '%s' "$APPLE_API_KEY_CONTENT" > "$APPLE_API_KEY_PATH"
|
|
|
|
- name: Package signed and notarized CLI and TUI
|
|
env:
|
|
CSC_LINK: ${{ secrets.CSC_LINK }}
|
|
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
|
APPLE_API_KEY: ${{ runner.temp }}/AuthKey_Maka.p8
|
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
|
MAKA_CLI_NODE_ARCHIVE: ${{ runner.temp }}/${{ needs.release-identity.outputs.node_archive }}
|
|
MAKA_CLI_RELEASE_SIGNING: '1'
|
|
run: npm run package:cli:macos-arm64
|
|
|
|
- name: Verify the final CLI and TUI artifact
|
|
env:
|
|
MAKA_CLI_REQUIRE_RELEASE_SIGNING: '1'
|
|
run: >-
|
|
npm run verify:cli:macos-arm64 --
|
|
"apps/desktop/release/${{ needs.release-identity.outputs.cli_archive }}"
|
|
|
|
- name: Stage the exact product artifact group
|
|
run: >-
|
|
node scripts/product-release-artifacts.mjs stage
|
|
"cli-macos-arm64"
|
|
apps/desktop/release
|
|
"${{ runner.temp }}/release-assets"
|
|
|
|
- name: Upload the verified CLI release assets
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: release-cli-macos-arm64-${{ github.run_attempt }}
|
|
path: ${{ runner.temp }}/release-assets
|
|
if-no-files-found: error
|
|
retention-days: 30
|
|
|
|
- name: Remove temporary release credentials
|
|
if: always()
|
|
run: rm -f "${{ runner.temp }}/AuthKey_Maka.p8"
|
|
|
|
publish:
|
|
# One draft release carries both platforms, so it is created once, after
|
|
# every platform has been packaged and verified.
|
|
needs: [release-identity, desktop, cli-macos-arm64]
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
permissions:
|
|
contents: write
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
|
|
steps:
|
|
- name: Check out the dispatched commit
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
ref: ${{ needs.release-identity.outputs.source_commit }}
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
|
|
- name: Download the verified release assets
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
pattern: release-*-${{ github.run_attempt }}
|
|
path: release-assets
|
|
merge-multiple: true
|
|
|
|
- name: Set up the pinned release Node.js
|
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
with:
|
|
node-version: ${{ needs.release-identity.outputs.node_version }}
|
|
package-manager-cache: false
|
|
|
|
- name: Select the pinned npm release toolchain
|
|
run: |
|
|
npm install --global --no-audit --no-fund "npm@${{ needs.release-identity.outputs.npm_version }}"
|
|
test "$(npm --version)" = "${{ needs.release-identity.outputs.npm_version }}"
|
|
|
|
- name: Install the release verifier dependencies
|
|
run: npm ci --ignore-scripts --no-audit --no-fund
|
|
|
|
- name: Verify the exact product artifact manifest
|
|
run: node scripts/product-release-artifacts.mjs verify release-assets
|
|
|
|
- name: Record the immutable publication evidence
|
|
env:
|
|
GITHUB_SHA: ${{ needs.release-identity.outputs.source_commit }}
|
|
SOURCE_REFERENCE_TAG: ${{ needs.release-identity.outputs.source_reference_tag }}
|
|
run: |
|
|
node scripts/product-release-artifacts.mjs record \
|
|
release-assets \
|
|
"$RUNNER_TEMP/product-release.json" \
|
|
"$GITHUB_REPOSITORY" \
|
|
"$GITHUB_RUN_ID" \
|
|
"$GITHUB_RUN_ATTEMPT"
|
|
|
|
- name: Upload the immutable publication evidence
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: product-release-record-${{ github.run_attempt }}
|
|
path: ${{ runner.temp }}/product-release.json
|
|
if-no-files-found: error
|
|
compression-level: 0
|
|
retention-days: 30
|
|
|
|
- name: Revalidate the live ASF source reference
|
|
env:
|
|
SOURCE_COMMIT: ${{ needs.release-identity.outputs.source_commit }}
|
|
SOURCE_REFERENCE_TAG: ${{ needs.release-identity.outputs.source_reference_tag }}
|
|
run: |
|
|
git fetch --force --no-tags origin \
|
|
"refs/tags/$SOURCE_REFERENCE_TAG:refs/tags/$SOURCE_REFERENCE_TAG"
|
|
test "$(git cat-file -t "refs/tags/$SOURCE_REFERENCE_TAG")" = tag
|
|
test "$(git rev-parse "refs/tags/$SOURCE_REFERENCE_TAG^{commit}")" = "$SOURCE_COMMIT"
|
|
git fetch --no-tags origin main:refs/remotes/origin/main
|
|
git merge-base --is-ancestor "$SOURCE_COMMIT" origin/main
|
|
|
|
- name: Ensure the exact product tag
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
TAG: ${{ needs.release-identity.outputs.tag }}
|
|
SOURCE_COMMIT: ${{ needs.release-identity.outputs.source_commit }}
|
|
run: |
|
|
gh auth setup-git
|
|
node scripts/product-release-tag.mjs ensure "$TAG" "$SOURCE_COMMIT"
|
|
|
|
- name: Create or update the draft GitHub Release
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
VERSION: ${{ needs.release-identity.outputs.version }}
|
|
TAG: ${{ needs.release-identity.outputs.tag }}
|
|
SOURCE_COMMIT: ${{ needs.release-identity.outputs.source_commit }}
|
|
SOURCE_REFERENCE_TAG: ${{ needs.release-identity.outputs.source_reference_tag }}
|
|
run: |
|
|
notes="Maka Desktop and the required Apple Silicon CLI/TUI artifact built from approved ASF source reference ${SOURCE_REFERENCE_TAG} at commit ${SOURCE_COMMIT}.
|
|
|
|
Before publishing, download these draft assets on another Apple Silicon Mac and on a Windows x64 machine, and complete .github/RELEASE_CHECKLIST.md.
|
|
|
|
The Windows build is unsigned: SmartScreen warns on first launch, and the download has to be checked against its .sha256 file.
|
|
|
|
These files are convenience binaries built from the approved ASF source release; they are not ASF release artifacts.
|
|
|
|
Known limitations: Computer Use and Gitoxide-backed workspace execution are not included in this release. The Desktop convenience artifacts do not distribute a Git runtime."
|
|
|
|
if gh release view "$TAG" >/dev/null 2>&1; then
|
|
is_draft="$(gh release view "$TAG" --json isDraft --jq .isDraft)"
|
|
if [[ "$is_draft" != "true" ]]; then
|
|
echo "Release ${TAG} is already published and cannot be replaced." >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
gh release create "$TAG" \
|
|
--draft \
|
|
--verify-tag \
|
|
--prerelease=false \
|
|
--latest=false \
|
|
--title "Maka ${VERSION}" \
|
|
--notes "$notes"
|
|
fi
|
|
|
|
if [[ "$(gh release view "$TAG" --json isDraft --jq .isDraft)" != "true" ]]; then
|
|
echo "Release ${TAG} was published while recovery was running." >&2
|
|
exit 1
|
|
fi
|
|
gh release edit "$TAG" \
|
|
--prerelease=false \
|
|
--latest=false \
|
|
--title "Maka ${VERSION}" \
|
|
--notes "$notes"
|
|
actual_prerelease="$(gh release view "$TAG" --json isPrerelease --jq .isPrerelease)"
|
|
if [[ "$actual_prerelease" != "false" ]]; then
|
|
echo "Release ${TAG} must not be a prerelease." >&2
|
|
exit 1
|
|
fi
|
|
asset_names="$(gh release view "$TAG" --json assets --jq '.assets[].name')"
|
|
declare -A expected_assets=()
|
|
declare -A existing_assets=()
|
|
mapfile -t local_assets < <(find release-assets -maxdepth 1 -type f -printf '%f\n' | sort)
|
|
for asset in "${local_assets[@]}"; do
|
|
expected_assets["$asset"]=1
|
|
done
|
|
existing_dir="$RUNNER_TEMP/existing-release-assets"
|
|
mkdir -p "$existing_dir"
|
|
if [[ -n "$asset_names" ]]; then
|
|
while IFS= read -r asset; do
|
|
if [[ -z "${expected_assets[$asset]+present}" ]]; then
|
|
echo "Draft ${TAG} contains unexpected asset ${asset}; inspect and remove it manually before retrying." >&2
|
|
exit 1
|
|
fi
|
|
gh release download "$TAG" --pattern "$asset" --dir "$existing_dir"
|
|
if ! cmp -s "release-assets/$asset" "$existing_dir/$asset"; then
|
|
echo "Draft ${TAG} asset ${asset} differs from the verified artifact." >&2
|
|
exit 1
|
|
fi
|
|
existing_assets["$asset"]=1
|
|
done <<< "$asset_names"
|
|
fi
|
|
missing_assets=()
|
|
for asset in "${local_assets[@]}"; do
|
|
if [[ -z "${existing_assets[$asset]+present}" ]]; then
|
|
missing_assets+=("release-assets/$asset")
|
|
fi
|
|
done
|
|
release_state="$(gh release view "$TAG" --json isDraft,isPrerelease --jq '[.isDraft, .isPrerelease] | map(tostring) | join(" ")')"
|
|
if [[ "$release_state" != "true false" ]]; then
|
|
echo "Release ${TAG} changed state while assets were checked: ${release_state}." >&2
|
|
exit 1
|
|
fi
|
|
if (( ${#missing_assets[@]} > 0 )); then
|
|
gh release upload "$TAG" "${missing_assets[@]}"
|
|
fi
|
|
|
|
{
|
|
echo "Draft release ${TAG} created from ${SOURCE_COMMIT}."
|
|
echo "Release workflow run: ${GITHUB_RUN_ID}, attempt: ${GITHUB_RUN_ATTEMPT}."
|
|
} >> "$GITHUB_STEP_SUMMARY"
|