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
53 changes: 45 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,50 @@ jobs:
echo "upload=true" >> "$GITHUB_OUTPUT"
fi

# Upload + publish via the v1.1 API directly (not a marketplace action) so
# a rejection prints the store's actual response instead of a bare
# "HTTPError: 400". A failed publish leaves the upload as a draft: fix
# the cause in the dashboard and submit there — a re-run would see the
# draft version and skip.
- name: Upload extension to the Chrome Web Store
if: steps.store.outputs.upload == 'true'
uses: mnao305/chrome-extension-upload@v5.0.0
with:
file-path: ${{ steps.zip.outputs.path }}
extension-id: ${{ secrets.CWS_EXTENSION_ID }}
client-id: ${{ secrets.CWS_CLIENT_ID }}
client-secret: ${{ secrets.CWS_CLIENT_SECRET }}
refresh-token: ${{ secrets.CWS_REFRESH_TOKEN }}
publish: true
env:
ZIP: ${{ steps.zip.outputs.path }}
CWS_EXTENSION_ID: ${{ secrets.CWS_EXTENSION_ID }}
CWS_CLIENT_ID: ${{ secrets.CWS_CLIENT_ID }}
CWS_CLIENT_SECRET: ${{ secrets.CWS_CLIENT_SECRET }}
CWS_REFRESH_TOKEN: ${{ secrets.CWS_REFRESH_TOKEN }}
run: |
api=https://www.googleapis.com/chromewebstore/v1.1/items/${CWS_EXTENSION_ID}
token=$(curl -sf https://oauth2.googleapis.com/token \
-d "client_id=${CWS_CLIENT_ID}&client_secret=${CWS_CLIENT_SECRET}&refresh_token=${CWS_REFRESH_TOKEN}&grant_type=refresh_token" \
| jq -r .access_token)
echo "::add-mask::${token}"

# $1 = what failed, $2 = response file. One annotation line with the
# store's reason, full body in the log.
fail() {
reason=$(jq -r '[.error.message?, (.itemError[]?.error_detail), (.statusDetail[]?)] | map(select(. != null and . != "")) | join(" / ")' "$2" 2>/dev/null)
echo "::error title=Chrome Web Store ${1} failed::${reason:-$(head -c 500 "$2")}"
exit 1
}

echo "uploading ${ZIP}"
code=$(curl -sS -o upload.json -w '%{http_code}' -X PUT \
-H "Authorization: Bearer ${token}" -H "x-goog-api-version: 2" \
-T "${ZIP}" "https://www.googleapis.com/upload/chromewebstore/v1.1/items/${CWS_EXTENSION_ID}")
echo "HTTP ${code}"; jq . upload.json 2>/dev/null || cat upload.json
if [ "$code" != 200 ] || [ "$(jq -r .uploadState upload.json)" != SUCCESS ]; then
fail upload upload.json
fi

echo "publishing"
code=$(curl -sS -o publish.json -w '%{http_code}' -X POST \
-H "Authorization: Bearer ${token}" -H "x-goog-api-version: 2" -H "Content-Length: 0" \
"${api}/publish")
echo "HTTP ${code}"; jq . publish.json 2>/dev/null || cat publish.json
# status is a list: OK, or codes like ITEM_PENDING_REVIEW (an older
# version is still in review) with the explanation in statusDetail.
if [ "$code" != 200 ] || ! jq -e '.status | index("OK")' publish.json >/dev/null; then
fail publish publish.json
fi
7 changes: 7 additions & 0 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ ignores it and it is never published on its own.
the zip and uploads + submits it. CLI-only releases skip this step, so
the store isn't re-reviewed for identical builds.

If the store step fails at **publish** (the upload succeeded), the error
annotation carries the store's reason — commonly a new manifest permission
with no justification on the dashboard's Privacy practices tab, or an older
version still in review. The upload sits as a draft: fix the cause in the
dashboard and hit **Submit for review** there. Re-running the job won't
help — it sees the draft version and skips.

That's it — no tagging by hand.

## Required secrets (repo → Settings → Secrets → Actions)
Expand Down
Loading