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
220 changes: 220 additions & 0 deletions .github/workflows/Release-Chrome-Web-Store.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
name: Release to Chrome Web Store

on:
workflow_dispatch:
inputs:
publish:
description: 'Publish to Chrome Web Store after upload'
required: true
default: false
type: boolean

concurrency:
group: release
cancel-in-progress: false

jobs:
ci:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- name: Install dependencies
run: bun install

- name: Type check
run: bun run typecheck

- name: Lint and format check
run: bun run check

- name: Run tests
run: bun run test

- name: Build extension
run: bun run build

- name: Validate extension
run: bun run validate

release:
needs: ci
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Extract and validate version
id: version
run: |
MANIFEST_VERSION=$(python3 -c "import json; print(json.load(open('manifest.json'))['version'])")
echo "version=$MANIFEST_VERSION" >> $GITHUB_OUTPUT

PKG_VERSION=$(python3 -c "import json; print(json.load(open('package.json'))['version'])")
if [ "$PKG_VERSION" != "$MANIFEST_VERSION" ]; then
echo "::error::package.json version ($PKG_VERSION) does not match manifest.json version ($MANIFEST_VERSION)"
exit 1
fi

echo "Release version: $MANIFEST_VERSION"

- name: Read extension ID from package.json
id: config
run: |
EXTENSION_ID=$(python3 -c "import json; print(json.load(open('package.json'))['chromeWebStore']['extensionId'])")
if [ -z "$EXTENSION_ID" ] || [ "$EXTENSION_ID" = "<EXTENSION_ID>" ]; then
echo "::error::Chrome Web Store extension ID not configured in package.json"
exit 1
fi
echo "extension_id=$EXTENSION_ID" >> $GITHUB_OUTPUT

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- name: Install dependencies
run: bun install

- name: Package extension
run: bun run pack

- name: Upload to Chrome Web Store
id: upload
env:
CHROME_WEB_STORE_CLIENT_ID: ${{ secrets.CHROME_WEB_STORE_CLIENT_ID }}
CHROME_WEB_STORE_CLIENT_SECRET: ${{ secrets.CHROME_WEB_STORE_CLIENT_SECRET }}
CHROME_WEB_STORE_REFRESH_TOKEN: ${{ secrets.CHROME_WEB_STORE_REFRESH_TOKEN }}
Comment on lines +93 to +95
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breaking change: The secret names have been changed from the old workflow (CHROME_CLIENT_ID, CHROME_CLIENT_SECRET, CHROME_REFRESH_TOKEN) to new names with CHROME_WEB_STORE_ prefix. This will break existing deployments unless the GitHub repository secrets are renamed to match these new names. Consider documenting this breaking change in the PR description or migration guide, or maintain backward compatibility by checking both old and new secret names.

Copilot uses AI. Check for mistakes.
run: |
MISSING=""
[ -z "$CHROME_WEB_STORE_CLIENT_ID" ] && MISSING="$MISSING CHROME_WEB_STORE_CLIENT_ID"
[ -z "$CHROME_WEB_STORE_CLIENT_SECRET" ] && MISSING="$MISSING CHROME_WEB_STORE_CLIENT_SECRET"
[ -z "$CHROME_WEB_STORE_REFRESH_TOKEN" ] && MISSING="$MISSING CHROME_WEB_STORE_REFRESH_TOKEN"

if [ -n "$MISSING" ]; then
echo "::error::Missing required secrets:$MISSING"
exit 1
fi

echo "Obtaining OAuth access token..."
TOKEN_RESPONSE=$(curl -s -X POST \
-d "client_id=$CHROME_WEB_STORE_CLIENT_ID" \
-d "client_secret=$CHROME_WEB_STORE_CLIENT_SECRET" \
-d "refresh_token=$CHROME_WEB_STORE_REFRESH_TOKEN" \
-d "grant_type=refresh_token" \
https://oauth2.googleapis.com/token)

ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | python3 -c "
import sys, json
data = json.load(sys.stdin)
if 'access_token' not in data:
print('Token error: ' + json.dumps(data), file=sys.stderr)
sys.exit(1)
print(data['access_token'])
")

echo "::add-mask::$ACCESS_TOKEN"

EXTENSION_ID="${{ steps.config.outputs.extension_id }}"

echo "Uploading dist/Clean-Autofill.zip..."
UPLOAD_RESPONSE=$(curl -s -X PUT \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-goog-api-version: 2" \
-T dist/Clean-Autofill.zip \
"https://www.googleapis.com/upload/chromewebstore/v1.1/items/$EXTENSION_ID")

UPLOAD_STATE=$(echo "$UPLOAD_RESPONSE" | python3 -c "
import sys, json
data = json.load(sys.stdin)
state = data.get('uploadState', 'UNKNOWN')
print(state)
if state != 'SUCCESS':
errors = data.get('itemError', [])
for e in errors:
print(f\"::error::{e.get('error_detail', e.get('error_code', 'unknown'))}\", file=sys.stderr)
")

if [ "$UPLOAD_STATE" = "SUCCESS" ]; then
echo "Upload successful"
else
echo "::error::Upload failed with state: $UPLOAD_STATE"
echo "$UPLOAD_RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$UPLOAD_RESPONSE"
exit 1
fi

- name: Publish to Chrome Web Store
if: github.event.inputs.publish == 'true'
env:
CHROME_WEB_STORE_CLIENT_ID: ${{ secrets.CHROME_WEB_STORE_CLIENT_ID }}
CHROME_WEB_STORE_CLIENT_SECRET: ${{ secrets.CHROME_WEB_STORE_CLIENT_SECRET }}
CHROME_WEB_STORE_REFRESH_TOKEN: ${{ secrets.CHROME_WEB_STORE_REFRESH_TOKEN }}
run: |
echo "Obtaining OAuth access token..."
TOKEN_RESPONSE=$(curl -s -X POST \
-d "client_id=$CHROME_WEB_STORE_CLIENT_ID" \
-d "client_secret=$CHROME_WEB_STORE_CLIENT_SECRET" \
-d "refresh_token=$CHROME_WEB_STORE_REFRESH_TOKEN" \
-d "grant_type=refresh_token" \
https://oauth2.googleapis.com/token)

ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | python3 -c "
import sys, json
data = json.load(sys.stdin)
if 'access_token' not in data:
print('Token error: ' + json.dumps(data), file=sys.stderr)
sys.exit(1)
print(data['access_token'])
")

echo "::add-mask::$ACCESS_TOKEN"

EXTENSION_ID="${{ steps.config.outputs.extension_id }}"

echo "Publishing extension..."
PUBLISH_RESPONSE=$(curl -s -X POST \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-goog-api-version: 2" \
-H "Content-Length: 0" \
"https://www.googleapis.com/chromewebstore/v1.1/items/$EXTENSION_ID/publish")

STATUS=$(echo "$PUBLISH_RESPONSE" | python3 -c "
import sys, json
data = json.load(sys.stdin)
statuses = data.get('status', [])
print(statuses[0] if statuses else 'UNKNOWN')
")

if [ "$STATUS" = "OK" ]; then
echo "Extension published successfully"
elif [ "$STATUS" = "PUBLISHED_WITH_FRICTION_WARNING" ]; then
echo "::warning::Extension published with friction warnings. Check the developer dashboard."
else
echo "::error::Publish failed with status: $STATUS. Check the Chrome Web Store developer dashboard."
exit 1
fi

- name: Create GitHub Release
if: github.event.inputs.publish == 'true'
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.version }}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow creates a GitHub release using only the version number as the tag (e.g., "1.1.0"). While this is valid, it deviates from the common convention of prefixing version tags with 'v' (e.g., "v1.1.0"). The old workflow used 'v' prefix for tags (as seen in the deleted file at line 214: git tag "v$NEW_VERSION"). Consider using "v${{ steps.version.outputs.version }}" to maintain consistency with common Git tagging practices and potentially with existing tags in the repository.

Suggested change
tag_name: ${{ steps.version.outputs.version }}
tag_name: v${{ steps.version.outputs.version }}

Copilot uses AI. Check for mistakes.
name: Clean Autofill ${{ steps.version.outputs.version }}
files: dist/Clean-Autofill.zip
generate_release_notes: true
make_latest: true

- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: Clean-Autofill-${{ steps.version.outputs.version }}
path: dist/Clean-Autofill.zip
retention-days: 90
Loading