diff --git a/.github/scripts/check-release-metadata.sh b/.github/scripts/check-release-metadata.sh index 6e5221e..a9b30c5 100755 --- a/.github/scripts/check-release-metadata.sh +++ b/.github/scripts/check-release-metadata.sh @@ -3,28 +3,27 @@ # is asked to approve it, if that package is inconsistent: pubspec, changelog, # version.dart and the tag must all agree on the version. # -# Inputs (env): EVENT github.event_name (push of a tag, or workflow_dispatch) -# TAG github.ref_name, read only for push -# PACKAGE workflow_dispatch input: path under packages/ +# Inputs (env): REF_TYPE github.ref_type; must be `tag` (pub.dev rejects OIDC +# tokens from branch refs, even for workflow_dispatch) +# TAG github.ref_name: -v # GITHUB_OUTPUT receives `package=` set -euo pipefail fail() { echo "::error::$*"; exit 1; } -if [[ "$EVENT" == "push" ]]; then - # pub.dev only accepts tag pushes for push-triggered publishing, and the tag - # is the sole selector of the package: -v. - name="${TAG%%-v[0-9]*}" - case "$name" in - segment_analytics) PACKAGE=core ;; - segment_analytics_plugin_[a-z_]*) PACKAGE="plugins/plugin_${name#segment_analytics_plugin_}" ;; - *) fail "tag '$TAG' does not name a package in this repo" ;; - esac -fi +# pub.dev only accepts tokens from tag refs, for push and workflow_dispatch +# alike, so the tag is the sole selector of the package: -v. +[[ "${REF_TYPE:-}" == "tag" ]] || fail "run from a release tag (gh workflow run publish.yml --ref ), not a ${REF_TYPE:-unknown} ref" +name="${TAG%%-v[0-9]*}" +case "$name" in + segment_analytics) PACKAGE=core ;; + segment_analytics_plugin_[a-z_]*) PACKAGE="plugins/plugin_${name#segment_analytics_plugin_}" ;; + *) fail "tag '$TAG' does not name a package in this repo" ;; +esac # PACKAGE reaches working-directory in the workflow; reject anything that could # escape packages/. -[[ "${PACKAGE:-}" =~ ^(core|plugins/plugin_[a-z_]+)$ ]] || fail "invalid package '${PACKAGE:-}'" +[[ "$PACKAGE" =~ ^(core|plugins/plugin_[a-z_]+)$ ]] || fail "invalid package '$PACKAGE'" dir="packages/$PACKAGE" [[ -f "$dir/pubspec.yaml" ]] || fail "$dir/pubspec.yaml not found" @@ -33,11 +32,9 @@ version=$(sed -n 's/^version: *//p' "$dir/pubspec.yaml" | head -1) [[ -n "$name" && -n "$version" ]] || fail "could not read name/version from $dir/pubspec.yaml" # pub.dev publishes whatever pubspec.yaml says, so the tag must say the same. -if [[ "$EVENT" == "push" ]]; then - [[ "$TAG" == "$name-v$version" ]] || fail "tag '$TAG' != '$name-v$version' (pubspec.yaml in $dir)" -fi +[[ "$TAG" == "$name-v$version" ]] || fail "tag '$TAG' != '$name-v$version' (pubspec.yaml in $dir)" -# Whatever triggered the run, the code being published must be on main. +# The code being published must be on main. git fetch -q origin main git merge-base --is-ancestor "$GITHUB_SHA" origin/main || fail "$GITHUB_SHA is not on main" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7a49fd5..8af86e0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,20 +1,16 @@ name: Publish to pub.dev -# pub.dev automated publishing accepts only tag pushes and workflow_dispatch; a -# `release` event is rejected. The tag selects the package (see -# .github/scripts/check-release-metadata.sh). workflow_dispatch is for re-running -# a publish whose tag-triggered run failed. +# pub.dev automated publishing accepts only tag pushes and workflow_dispatch, and +# only from a tag ref: a dispatch on a branch is rejected ("this token has +# 'branch' refType"). The tag selects the package (see +# .github/scripts/check-release-metadata.sh). workflow_dispatch re-runs a release +# tag's publish: gh workflow run publish.yml --ref . on: push: tags: - 'segment_analytics-v[0-9]+.[0-9]+.[0-9]+*' - 'segment_analytics_plugin_*-v[0-9]+.[0-9]+.[0-9]+*' workflow_dispatch: - inputs: - package: - description: "Package to publish (core, or plugins/plugin_)" - required: true - default: "core" permissions: id-token: write @@ -41,9 +37,8 @@ jobs: id: check run: .github/scripts/check-release-metadata.sh env: - EVENT: ${{ github.event_name }} + REF_TYPE: ${{ github.ref_type }} TAG: ${{ github.ref_name }} - PACKAGE: ${{ github.event.inputs.package }} test: name: Pre-publish tests diff --git a/RELEASING.md b/RELEASING.md index b7e3ad7..52af81f 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -55,13 +55,21 @@ When releasing several packages, wait for core to appear on pub.dev before taggi ## Re-running a failed publish -If the tag-triggered run failed for a transient reason, re-run it from the Actions tab. If the tag points at the wrong commit, fix `main`, then delete and recreate the tag. +pub.dev only accepts publishes from a tag ref. A run dispatched on a branch, `main` included, fails with "this token has 'branch' refType". -The **Publish to pub.dev** workflow can also be started by hand (`workflow_dispatch`) with `package` set to `core` or `plugins/plugin_`. It runs the same checks. Do not tag afterwards: the tag would start a second publish of a version that already exists, which fails. +- **Transient failure:** re-run the tag's run from the Actions tab, or `gh workflow run publish.yml --ref `. Both use the workflow and code as of the tag. +- **Wrong commit, or the workflow itself needed a fix:** fix `main`, then delete and recreate the tag on it. This is safe only while the version is not on pub.dev. + +``` +git push origin --delete +git tag -d +git tag # on the updated main +git push origin +``` ## One-time setup -**GitHub:** the `production` environment (Settings → Environments) needs required reviewers, and its deployment branches and tags limited to `main` and the tag patterns `segment_analytics-v*` and `segment_analytics_plugin_*-v*`. The pub.dev environment requirement below only checks that the job names the environment; the reviewers are what make it a gate. +**GitHub:** the `production` environment (Settings → Environments) needs required reviewers, and its deployment branches and tags limited to the tag patterns `segment_analytics-v*` and `segment_analytics_plugin_*-v*`. The pub.dev environment requirement below only checks that the job names the environment; the reviewers are what make it a gate. **pub.dev, per package:** on the package's **Admin** tab, under **Automated publishing**, choose **Publishing from GitHub Actions** (not the Google Cloud service account option) and set: