diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ab8ed52..b87c03dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ Note: In this file, do not use the hard wrap in the middle of a sentence for com ## [Unreleased] +- Support uploading assets to existing draft releases. + ## [1.30.2] - 2026-04-17 - Enhance security when `dry-run` is true. diff --git a/README.md b/README.md index 9044d873..b84553db 100644 --- a/README.md +++ b/README.md @@ -718,6 +718,9 @@ The following two events are supported by default: types: [created] ``` +Draft releases are supported as long as the GitHub release already exists before this action runs. +This is useful for workflows that create a draft release first, upload all assets, and publish only after every upload succeeds. + You can upload binaries from arbitrary event to arbitrary tag by specifying the `ref` input option. For example, to upload binaries to the `my_tag` tag, specify `ref` input option as follows: @@ -727,6 +730,8 @@ with: ref: refs/tags/my_tag ``` +This also works when the existing release for `my_tag` is still a draft, such as when using release automation that creates draft releases first. + ## Security The `@v` tags are updated with each release. If you want to enhance workflow stability and security against supply chain attacks, consider using the `@v..` tag or their hash to pin the version and regularly updating with dependency cooldown. Since all releases are immutable, pinning the version in either way should have the same effect. diff --git a/main.sh b/main.sh index ac3da506..03e5e1e0 100755 --- a/main.sh +++ b/main.sh @@ -29,6 +29,29 @@ warn() { info() { printf >&2 'info: %s\n' "$*" } +gh_api() { + GH_TOKEN="${token}" gh api "$@" +} +upload_release_assets() { + local release_id upload_url asset asset_name asset_id + release_id="$(gh_api "repos/${GITHUB_REPOSITORY}/releases" --paginate --jq ".[] | select(.tag_name == \"${tag}\") | .id" | head -n1)" + if [[ -z "${release_id}" ]]; then + bail "GitHub release for tag '${tag}' not found; create the release before running this action" + fi + upload_url="$(gh_api "repos/${GITHUB_REPOSITORY}/releases/${release_id}" --jq '.upload_url' | sed -E 's/{\?name,label}//')" + for asset in "${final_assets[@]}"; do + asset_name="$(basename -- "${asset}")" + asset_id="$(gh_api "repos/${GITHUB_REPOSITORY}/releases/${release_id}/assets" --paginate --jq ".[] | select(.name == \"${asset_name}\") | .id" | head -n1 || true)" + if [[ -n "${asset_id}" ]]; then + retry gh_api "repos/${GITHUB_REPOSITORY}/releases/assets/${asset_id}" -X DELETE >/dev/null + fi + retry curl --fail --silent --show-error \ + -H "Authorization: Bearer ${token}" \ + -H "Content-Type: application/octet-stream" \ + "${upload_url}?name=${asset_name}" \ + --data-binary "@${asset}" >/dev/null + done +} normalize_comma_or_space_separated() { # Normalize whitespace characters into space because it's hard to handle single input contains lines with POSIX sed alone. local list="${1//[$'\r\n\t']/ }" @@ -635,6 +658,5 @@ if [[ -n "${dry_run}" ]]; then printf 'assets: %s\n' "${final_assets[*]}" IFS=$'\n\t' else - # https://cli.github.com/manual/gh_release_upload - GITHUB_TOKEN="${token}" retry gh release upload "${tag}" "${final_assets[@]}" --clobber + upload_release_assets fi