| 1 | #!/usr/bin/env bash |
| 2 | # Resolve and authorize the immutable source commit for a Desktop publication. |
| 3 | # The workflow control plane must run from protected main-v2. Stable/RC builds |
| 4 | # use an existing tag on main-v2 history; Preview builds use the exact main-v2 |
| 5 | # commit selected when the protected workflow was dispatched. |
| 6 | set -euo pipefail |
| 7 | |
| 8 | channel="${RELEASE_CHANNEL:?RELEASE_CHANNEL is required}" |
| 9 | tag="${RELEASE_TAG:?RELEASE_TAG is required}" |
| 10 | orchestrated="${IN_ORCHESTRATED:-false}" |
| 11 | orchestrator="${IN_ORCHESTRATOR:-}" |
| 12 | approved_sha="${APPROVED_SHA:-}" |
| 13 | caller_event="${CALLER_EVENT_NAME:-}" |
| 14 | caller_ref="${CALLER_REF:-}" |
| 15 | caller_ref_protected="${CALLER_REF_PROTECTED:-}" |
| 16 | caller_sha="${CALLER_SHA:-}" |
| 17 | caller_workflow_sha="${CALLER_WORKFLOW_SHA:-}" |
| 18 | release_remote="${RELEASE_REMOTE:-origin}" |
| 19 | require_current_main="${REQUIRE_CURRENT_MAIN:-true}" |
| 20 | verify_checkout="${VERIFY_RELEASE_CHECKOUT:-false}" |
| 21 | |
| 22 | case "$orchestrated" in |
| 23 | true | false) ;; |
| 24 | *) |
| 25 | echo "::error::IN_ORCHESTRATED must be true or false, got: $orchestrated" >&2 |
| 26 | exit 2 |
| 27 | ;; |
| 28 | esac |
| 29 | if [ "$orchestrated" = "true" ]; then |
| 30 | case "$orchestrator" in |
| 31 | stable | preview) ;; |
| 32 | *) |
| 33 | echo "::error::IN_ORCHESTRATOR must be stable or preview for an orchestrated Desktop release, got: $orchestrator" >&2 |
| 34 | exit 2 |
| 35 | ;; |
| 36 | esac |
| 37 | if [ "$orchestrator" != "$channel" ]; then |
| 38 | echo "::error::the $orchestrator orchestrator cannot authorize a Desktop $channel candidate" >&2 |
| 39 | exit 1 |
| 40 | fi |
| 41 | fi |
| 42 | case "$require_current_main" in |
| 43 | true | false) ;; |
| 44 | *) |
| 45 | echo "::error::REQUIRE_CURRENT_MAIN must be true or false, got: $require_current_main" >&2 |
| 46 | exit 2 |
| 47 | ;; |
| 48 | esac |
| 49 | case "$verify_checkout" in |
| 50 | true | false) ;; |
| 51 | *) |
| 52 | echo "::error::VERIFY_RELEASE_CHECKOUT must be true or false, got: $verify_checkout" >&2 |
| 53 | exit 2 |
| 54 | ;; |
| 55 | esac |
| 56 | |
| 57 | stable_tag_pattern='^desktop-v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-([0-9A-Za-z-]+)(\.[0-9A-Za-z-]+)*)?)?$' |
| 58 | preview_tag_pattern='^desktop-v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)-preview\.(0|[1-9][0-9]*)$' |
| 59 | |
| 60 | case "$channel" in |
| 61 | stable) |
| 62 | if [[ ! "$tag" =~ $stable_tag_pattern ]] || [[ "$tag" =~ $preview_tag_pattern ]]; then |
| 63 | echo "::error::Stable Desktop candidate requires desktop-vMAJOR.MINOR.PATCH[-PRERELEASE], excluding -preview.N: $tag" >&2 |
| 64 | exit 1 |
| 65 | fi |
| 66 | ;; |
| 67 | preview) |
| 68 | if [[ ! "$tag" =~ $preview_tag_pattern ]]; then |
| 69 | echo "::error::Preview Desktop candidate requires desktop-vMAJOR.MINOR.PATCH-preview.N: $tag" >&2 |
| 70 | exit 1 |
| 71 | fi |
| 72 | ;; |
| 73 | *) |
| 74 | echo "::error::Desktop release channel must be stable or preview, got: $channel" >&2 |
| 75 | exit 2 |
| 76 | ;; |
| 77 | esac |
| 78 | |
| 79 | git fetch "$release_remote" main-v2 --tags |
| 80 | main_sha="$(git rev-parse "$release_remote/main-v2^{commit}")" |
| 81 | |
| 82 | if [ "$orchestrated" = "true" ]; then |
| 83 | candidate="$approved_sha" |
| 84 | else |
| 85 | if [ "$caller_event" != "workflow_dispatch" ] || |
| 86 | [ "$caller_ref" != "refs/heads/main-v2" ] || |
| 87 | [ "$caller_ref_protected" != "true" ]; then |
| 88 | echo "::error::standalone Desktop releases must run from protected main-v2" >&2 |
| 89 | exit 1 |
| 90 | fi |
| 91 | if [ "$caller_sha" != "$caller_workflow_sha" ]; then |
| 92 | echo "::error::standalone Desktop workflow SHA is $caller_workflow_sha, expected caller SHA $caller_sha" >&2 |
| 93 | exit 1 |
| 94 | fi |
| 95 | if [ "$channel" = "preview" ]; then |
| 96 | candidate="$caller_sha" |
| 97 | else |
| 98 | candidate="$(git rev-parse "$tag^{commit}")" |
| 99 | fi |
| 100 | fi |
| 101 | |
| 102 | if [[ ! "$candidate" =~ ^[0-9a-f]{40}$ ]]; then |
| 103 | echo "::error::Desktop candidate SHA must be a full commit SHA, got: $candidate" >&2 |
| 104 | exit 1 |
| 105 | fi |
| 106 | if ! git cat-file -e "$candidate^{commit}" 2>/dev/null; then |
| 107 | echo "::error::Desktop candidate commit is unavailable: $candidate" >&2 |
| 108 | exit 1 |
| 109 | fi |
| 110 | if ! git merge-base --is-ancestor "$candidate" "$main_sha"; then |
| 111 | echo "::error::Desktop candidate $candidate is not on main-v2 history at $main_sha" >&2 |
| 112 | exit 1 |
| 113 | fi |
| 114 | |
| 115 | if [ "$channel" = "stable" ]; then |
| 116 | tag_sha="$(git rev-parse "$tag^{commit}")" |
| 117 | if [ "$tag_sha" != "$candidate" ]; then |
| 118 | echo "::error::$tag points to $tag_sha, expected Desktop candidate $candidate" >&2 |
| 119 | exit 1 |
| 120 | fi |
| 121 | elif git show-ref --verify --quiet "refs/tags/$tag"; then |
| 122 | echo "::error::Desktop Preview uses an immutable asset directory, not a Git tag: $tag" >&2 |
| 123 | exit 1 |
| 124 | elif [ "$orchestrated" != "true" ] && [ "$require_current_main" = "true" ] && [ "$candidate" != "$main_sha" ]; then |
| 125 | echo "::error::Desktop Preview must use current main-v2 $main_sha, got: $candidate" >&2 |
| 126 | exit 1 |
| 127 | fi |
| 128 | |
| 129 | if [ "$verify_checkout" = "true" ]; then |
| 130 | head_sha="$(git rev-parse HEAD^{commit})" |
| 131 | if [ "$head_sha" != "$candidate" ]; then |
| 132 | echo "::error::Desktop release checkout is $head_sha, expected candidate $candidate" >&2 |
| 133 | exit 1 |
| 134 | fi |
| 135 | fi |
| 136 | |
| 137 | echo "sha=$candidate" >>"${GITHUB_OUTPUT:-/dev/stdout}" |
| 138 | echo "Desktop candidate verified: channel=$channel tag=$tag sha=$candidate" |
| 139 |