diff --git a/.github/workflows/Release-Chrome-Web-Store.yml b/.github/workflows/Release-Chrome-Web-Store.yml new file mode 100644 index 0000000..1a11df8 --- /dev/null +++ b/.github/workflows/Release-Chrome-Web-Store.yml @@ -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" = "" ]; 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 }} + 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 }} + 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 diff --git a/.github/workflows/release-chrome-store.yml b/.github/workflows/release-chrome-store.yml deleted file mode 100644 index 5179587..0000000 --- a/.github/workflows/release-chrome-store.yml +++ /dev/null @@ -1,215 +0,0 @@ -name: Release to Chrome Web Store - -on: - push: - tags: - - 'v*' - workflow_dispatch: - inputs: - release_type: - description: 'Release type' - required: true - default: 'patch' - type: choice - options: - - patch - - minor - - major - -jobs: - release: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '18' - - - name: Get version from manifest - id: get_version - run: | - VERSION=$(python3 -c "import json; print(json.load(open('manifest.json'))['version'])") - echo "VERSION=$VERSION" >> $GITHUB_OUTPUT - echo "Current version: $VERSION" - - - name: Update version (if workflow_dispatch) - if: github.event_name == 'workflow_dispatch' - id: bump_version - run: | - python3 -c " - import json - import sys - - def bump_version(version, bump_type): - parts = version.split('.') - major, minor, patch = int(parts[0]), int(parts[1]), int(parts[2]) - - if bump_type == 'major': - major += 1 - minor = 0 - patch = 0 - elif bump_type == 'minor': - minor += 1 - patch = 0 - elif bump_type == 'patch': - patch += 1 - - return f'{major}.{minor}.{patch}' - - with open('manifest.json', 'r') as f: - manifest = json.load(f) - - old_version = manifest['version'] - new_version = bump_version(old_version, '${{ github.event.inputs.release_type }}') - manifest['version'] = new_version - - with open('manifest.json', 'w') as f: - json.dump(manifest, f, indent=2) - - print(f'Version bumped from {old_version} to {new_version}') - print(f'NEW_VERSION={new_version}') - " - NEW_VERSION=$(python3 -c "import json; print(json.load(open('manifest.json'))['version'])") - echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Install dependencies - run: bun install - - - name: Build and package extension - run: | - echo "Building and packaging extension..." - bun run pack - echo "✅ Extension package created at dist/Clean-Autofill.zip" - - - name: Upload to Chrome Web Store - id: upload - env: - CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} - CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} - CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} - CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} - run: | - # Check if credentials are set - if [ -z "$CHROME_CLIENT_ID" ] || [ -z "$CHROME_CLIENT_SECRET" ] || [ -z "$CHROME_REFRESH_TOKEN" ] || [ -z "$CHROME_EXTENSION_ID" ]; then - echo "⚠️ Chrome Web Store credentials not configured. Skipping upload." - echo "Please set the following secrets in your repository:" - echo " - CHROME_CLIENT_ID" - echo " - CHROME_CLIENT_SECRET" - echo " - CHROME_REFRESH_TOKEN" - echo " - CHROME_EXTENSION_ID" - echo "UPLOAD_SKIPPED=true" >> $GITHUB_OUTPUT - exit 0 - fi - - # Get access token - echo "Getting access token..." - ACCESS_TOKEN=$(curl -s -X POST \ - -d "client_id=$CHROME_CLIENT_ID" \ - -d "client_secret=$CHROME_CLIENT_SECRET" \ - -d "refresh_token=$CHROME_REFRESH_TOKEN" \ - -d "grant_type=refresh_token" \ - https://oauth2.googleapis.com/token | python3 -c "import sys, json; print(json.load(sys.stdin)['access_token'])") - - # Upload to Chrome Web Store - echo "Uploading to Chrome Web Store..." - 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/$CHROME_EXTENSION_ID") - - echo "Upload response: $UPLOAD_RESPONSE" - - # Check upload status - if echo "$UPLOAD_RESPONSE" | grep -q "SUCCESS"; then - echo "✅ Upload successful" - echo "UPLOAD_SUCCESS=true" >> $GITHUB_OUTPUT - else - echo "❌ Upload failed" - echo "$UPLOAD_RESPONSE" - exit 1 - fi - - - name: Publish to Chrome Web Store - if: steps.upload.outputs.UPLOAD_SUCCESS == 'true' - env: - CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} - CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} - CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} - CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} - run: | - # Get access token - ACCESS_TOKEN=$(curl -s -X POST \ - -d "client_id=$CHROME_CLIENT_ID" \ - -d "client_secret=$CHROME_CLIENT_SECRET" \ - -d "refresh_token=$CHROME_REFRESH_TOKEN" \ - -d "grant_type=refresh_token" \ - https://oauth2.googleapis.com/token | python3 -c "import sys, json; print(json.load(sys.stdin)['access_token'])") - - # Publish the extension - 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/$CHROME_EXTENSION_ID/publish") - - echo "Publish response: $PUBLISH_RESPONSE" - - if echo "$PUBLISH_RESPONSE" | grep -q "OK"; then - echo "✅ Extension published successfully" - else - echo "⚠️ Publishing might be pending review" - fi - - - name: Create GitHub Release - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v1 - with: - files: dist/Clean-Autofill.zip - generate_release_notes: true - body: | - ## Clean-Autofill Chrome Extension Release - - ### Version: ${{ steps.get_version.outputs.VERSION }} - - This release includes the packaged Chrome extension ready for installation. - - #### Installation Instructions: - 1. Download `Clean-Autofill.zip` - 2. Extract the zip file - 3. Open Chrome and go to `chrome://extensions/` - 4. Enable Developer Mode - 5. Click "Load unpacked" and select the extracted folder - - #### Chrome Web Store: - The extension has been submitted to the Chrome Web Store for review. - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: Clean-Autofill-release - path: dist/Clean-Autofill.zip - retention-days: 90 - - - name: Commit version bump - if: github.event_name == 'workflow_dispatch' && steps.bump_version.outputs.NEW_VERSION - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add manifest.json - git commit -m "Bump version to ${{ steps.bump_version.outputs.NEW_VERSION }}" - git tag "v${{ steps.bump_version.outputs.NEW_VERSION }}" - git push origin HEAD:${GITHUB_REF#refs/heads/} - git push origin "v${{ steps.bump_version.outputs.NEW_VERSION }}" diff --git a/manifest.json b/manifest.json index c2067a7..17c1589 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Clean Autofill", - "version": "1.0.0", + "version": "1.1.0", "description": "Automatically fill email addresses based on the current website domain", "permissions": [ "activeTab", diff --git a/package.json b/package.json index 550a7e8..264e46a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "clean-autofill", - "version": "1.0.0", + "version": "1.1.0", "description": "Chrome extension that automatically fills email addresses based on the current website domain", "scripts": { "prepare": "husky toolkit/husky", @@ -49,5 +49,8 @@ }, "dependencies": { "psl": "^1.15.0" + }, + "chromeWebStore": { + "extensionId": "klbbkndjohchnidkbnjijdbggfadpppf" } } diff --git a/toolkit/scripts/build.js b/toolkit/scripts/build.js index 51d30e8..c1bad8f 100644 --- a/toolkit/scripts/build.js +++ b/toolkit/scripts/build.js @@ -87,8 +87,10 @@ if (usesESModules) { // Remove "export {};" (empty exports from files with only type imports) // Handles both spaced "export {};" and minified "export{}" content = content.replace(/(^|[;\n])\s*export\s*\{\s*\};?/g, '$1'); + // Remove inline export declarations (e.g., "export function" → "function") + content = content.replace(/\bexport\s+(function|const|let|var|class)\b/g, '$1'); fs.writeFileSync(filePath, content.trim() + '\n'); - console.log(` ✅ ${file} (stripped empty exports)`); + console.log(` ✅ ${file} (stripped exports)`); } } console.log(` ✅ background.js (ES module preserved)`);