chore(release): v1.5.5 #121
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| run-name: > | |
| ${{ | |
| (github.event_name == 'workflow_dispatch' && inputs.version && format('chore(release): v{0}', inputs.version)) || | |
| (github.event_name == 'workflow_dispatch' && !inputs.version && 'chore(release): auto-increment') || | |
| (startsWith(github.ref, 'refs/tags/') && format('Release {0}', github.ref_name)) || | |
| format('Release #{0}', github.run_number) | |
| }} | |
| # 🚀 Release Workflow Guide | |
| # ------------------------- | |
| # This workflow supports two modes of operation: | |
| # | |
| # 1. Manual Release (Recommended for "One-Click" Releases) | |
| # - How: Go to "Actions" -> "Release" -> "Run workflow". | |
| # - Input: Optional "Version" (e.g., 1.2.0). If left empty, it auto-increments the patch version. | |
| # - What it does: | |
| # 1. Calculates the next version. | |
| # 2. Bumps version in package.json & manifest.json. | |
| # 3. Commits "chore(release): vX.Y.Z". | |
| # 4. Creates git tag "vX.Y.Z". | |
| # 5. Pushes commit & tag to main. | |
| # 6. Builds artifacts and creates a GitHub Release. | |
| # | |
| # 2. Tag-based Release (Manual Tagging) | |
| # - How: run `git commit -m "..." && git tag v1.2.0 && git push origin v1.2.0` locally. | |
| # - What it does: | |
| # 1. Detects the pushed tag (v1.2.0). | |
| # 2. SKIPS version calculation and bumping (assumes you already did it). | |
| # 3. Builds artifacts from that tag. | |
| # 4. Creates a GitHub Release for that tag. | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version to release (e.g. 1.0.8) - leave empty to auto-increment patch version' | |
| required: false | |
| type: string | |
| notes: | |
| description: 'Release notes (optional)' | |
| required: false | |
| type: string | |
| publish_only: | |
| description: 'Only re-publish an existing release to the Chrome Web Store (no bump/build). Requires version.' | |
| required: false | |
| type: boolean | |
| default: false | |
| publish_edge_only: | |
| description: 'Only publish an existing release package to Microsoft Edge Add-ons (no bump/build). Requires version.' | |
| required: false | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: write | |
| jobs: | |
| # Job 1: Calculate & Bump Version (Only runs on manual trigger) | |
| bump-version: | |
| if: github.event_name == 'workflow_dispatch' && !inputs.publish_only && !inputs.publish_edge_only | |
| runs-on: ubuntu-latest | |
| outputs: | |
| new_version: ${{ steps.next_version.outputs.version }} | |
| tag_name: ${{ steps.tag.outputs.name }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Calculate next version | |
| id: next_version | |
| run: | | |
| if [ -n "${{ inputs.version }}" ]; then | |
| echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT | |
| echo "Using manual version: ${{ inputs.version }}" | |
| else | |
| CURRENT=$(node -e "console.log(require('./package.json').version)") | |
| echo "Current version: ${CURRENT}" | |
| IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT" | |
| PATCH=$((PATCH + 1)) | |
| # Simple carry logic | |
| if [ $PATCH -ge 10 ]; then | |
| MINOR=$((MINOR + 1)) | |
| PATCH=0 | |
| fi | |
| if [ $MINOR -ge 10 ]; then | |
| MAJOR=$((MAJOR + 1)) | |
| MINOR=0 | |
| fi | |
| NEXT="${MAJOR}.${MINOR}.${PATCH}" | |
| echo "version=${NEXT}" >> $GITHUB_OUTPUT | |
| echo "Auto-calculated next version: ${NEXT}" | |
| fi | |
| - name: Compute tag name | |
| id: tag | |
| run: echo "name=v${{ steps.next_version.outputs.version }}" >> $GITHUB_OUTPUT | |
| - name: Update files, commit and push | |
| env: | |
| VERSION: ${{ steps.next_version.outputs.version }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| echo "Bumping to version ${VERSION}" | |
| # Update package.json and manifest.json | |
| node -e "const fs=require('fs');const v=process.env.VERSION;const p=JSON.parse(fs.readFileSync('package.json','utf8'));p.version=v;fs.writeFileSync('package.json',JSON.stringify(p,null,2)+'\n');const m=JSON.parse(fs.readFileSync('manifest.json','utf8'));m.version=v;fs.writeFileSync('manifest.json',JSON.stringify(m,null,2)+'\n');" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add package.json manifest.json | |
| git commit -m "chore(release): v${VERSION}" || echo "No changes to commit" | |
| git tag v${VERSION} || echo "Tag exists" | |
| # Push commit and tag | |
| git push origin HEAD:main --tags | |
| # Job 2: Build & Release (Runs on both manual and tag push) | |
| build-and-release: | |
| needs: bump-version | |
| if: always() && (needs.bump-version.result == 'success' || github.event_name == 'push') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Set Release Tag Variable | |
| id: vars | |
| run: | | |
| if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then | |
| echo "tag_name=${{ needs.bump-version.outputs.tag_name }}" >> $GITHUB_OUTPUT | |
| else | |
| echo "tag_name=${{ github.ref_name }}" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ steps.vars.outputs.tag_name }} | |
| fetch-depth: 0 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: 'latest' | |
| - name: Install deps | |
| run: bun i | |
| - name: Build All | |
| run: bun run build:all | |
| - name: Archive artifacts | |
| run: | | |
| TAG=${{ steps.vars.outputs.tag_name }} | |
| cd dist_chrome && zip -r ../voyager-chrome-${TAG}.zip . && cd .. | |
| - name: Validate Chrome artifact | |
| run: | | |
| TAG=${{ steps.vars.outputs.tag_name }} | |
| ZIP="voyager-chrome-${TAG}.zip" | |
| unzip -Z1 "$ZIP" > chrome-zip-files.txt | |
| grep -qx 'manifest.json' chrome-zip-files.txt | |
| grep -qx '_locales/en/messages.json' chrome-zip-files.txt | |
| - name: Sign Firefox Extension and Submit to AMO | |
| run: | | |
| TAG=${{ steps.vars.outputs.tag_name }} | |
| npx web-ext sign \ | |
| --source-dir=dist_firefox \ | |
| --api-key=${{ secrets.AMO_JWT_ISSUER }} \ | |
| --api-secret=${{ secrets.AMO_JWT_SECRET }} \ | |
| --channel=listed | |
| # Move signed XPI to root with proper naming | |
| mv web-ext-artifacts/*.xpi voyager-firefox-${TAG}.xpi | |
| - name: Prepare Release Notes | |
| id: release_body | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| TAG=${{ steps.vars.outputs.tag_name }} | |
| NOTES="${{ inputs.notes }}" | |
| if [ -n "$NOTES" ]; then | |
| echo "body<<EOF" >> $GITHUB_OUTPUT | |
| echo "$NOTES" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| else | |
| # Get the previous tag for auto-generated notes | |
| PREV_TAG=$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || echo "") | |
| # Generate release notes via GitHub API | |
| AUTO_NOTES="" | |
| if [ -n "$PREV_TAG" ]; then | |
| AUTO_NOTES=$(gh api repos/${{ github.repository }}/releases/generate-notes \ | |
| -f tag_name="${TAG}" \ | |
| -f target_commitish="main" \ | |
| -f previous_tag_name="${PREV_TAG}" \ | |
| --jq '.body' 2>/dev/null || echo "") | |
| fi | |
| cat > release_body.md << 'INSTALL_EOF' | |
| ## 📥 Installation | |
| <div align="center"> | |
| <a href="https://chromewebstore.google.com/detail/iifacdnjakkhjjiengaffnegbndgingi?utm_source=github&utm_medium=readme&utm_campaign=organic_growth&utm_content=en" target="_blank"> | |
| <img src="https://img.shields.io/badge/Chrome%20Web%20Store-4285F4?style=for-the-badge&logo=googlechrome&logoColor=white" alt="Chrome Web Store" height="36"> | |
| </a> | |
| | |
| <a href="https://microsoftedge.microsoft.com/addons/detail/voyager/gibmkggjijalcjinbdhcpklodjkhhlne" target="_blank"> | |
| <img src="https://img.shields.io/badge/Edge%20Add--ons-0078D7?style=for-the-badge&logo=microsoftedge&logoColor=white" alt="Edge Add-ons" height="36"> | |
| </a> | |
| | |
| <a href="https://addons.mozilla.org/firefox/addon/gemini-voyager/" target="_blank"> | |
| <img src="https://img.shields.io/badge/Firefox%20Add--ons-FF7139?style=for-the-badge&logo=firefox&logoColor=white" alt="Firefox Add-ons" height="36"> | |
| </a> | |
| </div> | |
| <p align="center"> | |
| <sub><b>Edge users:</b> Voyager is still maintained on Edge Add-ons for users who need Edge on mobile or tablet. If review is delayed, Chrome Web Store and GitHub manual packages remain available.</sub> | |
| </p> | |
| INSTALL_EOF | |
| # Append version-specific file names | |
| cat >> release_body.md << EOF | |
| - **Chrome / Edge / Chromium browsers**: \`voyager-chrome-${TAG}.zip\` | |
| - **Firefox**: \`voyager-firefox-${TAG}.xpi\` | |
| - **Safari**: \`voyager-${TAG}.dmg\` | |
| > Do not use GitHub's auto-generated **Source code (zip/tar.gz)** archives for manual extension installs. Those files are repository source snapshots, not browser-ready extension packages. | |
| ### 🍎 Safari 安装与限制 (Safari Installation & Limitations) | |
| - **安装 (Installation)**: 下载并打开 \`voyager-${TAG}.dmg\`,按提示安装应用。 | |
| Download and open \`voyager-${TAG}.dmg\`, then follow the prompts to install the app. | |
| - **限制 (Limitations)**: 受限于 Safari 特性,以下功能暂不支持:(a) Nano Banana 水印去除 (b) 图片导出 (建议使用 PDF 导出) (c) Google Drive 云同步。 | |
| Due to Safari's nature, the following features are not supported: (a) Watermark removal (b) Image export (PDF recommended) (c) Cloud sync with Google Drive. | |
| EOF | |
| # Combine: auto-generated notes first, then installation | |
| echo "body<<BODY_EOF" >> $GITHUB_OUTPUT | |
| if [ -n "$AUTO_NOTES" ]; then | |
| echo "$AUTO_NOTES" >> $GITHUB_OUTPUT | |
| echo "" >> $GITHUB_OUTPUT | |
| fi | |
| cat release_body.md >> $GITHUB_OUTPUT | |
| echo "BODY_EOF" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| name: Voyager ${{ steps.vars.outputs.tag_name }} | |
| tag_name: ${{ steps.vars.outputs.tag_name }} | |
| files: | | |
| voyager-chrome-*.zip | |
| voyager-firefox-*.xpi | |
| voyager-*.dmg | |
| body: ${{ steps.release_body.outputs.body }} | |
| # Store publishing happens after the GitHub Release and Firefox/AMO | |
| # submission, so a store-side failure does not prevent release assets. | |
| - name: Publish to Chrome Web Store | |
| run: | | |
| TAG=${{ steps.vars.outputs.tag_name }} | |
| # Chrome Web Store rejects a manifest that still carries the local dev `key` | |
| # field (PKG_MANIFEST_KEY_NOT_MATCH). Strip it into a store-only copy; the | |
| # GitHub Release zip keeps the key for manual unpacked installs. | |
| rm -rf cws_pkg "voyager-chrome-cws-${TAG}.zip" | |
| mkdir cws_pkg | |
| unzip -q "voyager-chrome-${TAG}.zip" -d cws_pkg | |
| node -e "const fs=require('fs');const m=JSON.parse(fs.readFileSync('cws_pkg/manifest.json','utf8'));delete m.key;fs.writeFileSync('cws_pkg/manifest.json',JSON.stringify(m,null,2)+'\n');" | |
| (cd cws_pkg && zip -qr "../voyager-chrome-cws-${TAG}.zip" .) | |
| bunx chrome-webstore-upload-cli@3 upload \ | |
| --source "voyager-chrome-cws-${TAG}.zip" \ | |
| --extension-id "$EXTENSION_ID" \ | |
| --client-id "$CLIENT_ID" \ | |
| --client-secret "$CLIENT_SECRET" \ | |
| --refresh-token "$REFRESH_TOKEN" \ | |
| --auto-publish | |
| env: | |
| EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} | |
| CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} | |
| CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} | |
| REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} | |
| - name: Build and publish to Edge Add-ons | |
| env: | |
| EDGE_CLIENT_ID: ${{ secrets.EDGE_CLIENT_ID }} | |
| EDGE_API_KEY: ${{ secrets.EDGE_API_KEY }} | |
| EDGE_PRODUCT_ID: ${{ secrets.EDGE_PRODUCT_ID }} | |
| run: | | |
| TAG=${{ steps.vars.outputs.tag_name }} | |
| bun run build:edge | |
| node scripts/publish-edge.js "voyager-edge-${TAG}.zip" \ | |
| --notes "Voyager ${TAG} automated release submission." | |
| # Job 3: Re-publish Chrome only (manual) — recover a failed Chrome Web Store | |
| # upload without re-cutting the release. Downloads the existing release's Chrome | |
| # zip, strips the dev `key`, and publishes. No version bump, no rebuild. | |
| republish-chrome: | |
| if: github.event_name == 'workflow_dispatch' && inputs.publish_only | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: 'latest' | |
| - name: Download Chrome zip from the existing release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| TAG="v${{ inputs.version }}" | |
| gh release download "$TAG" --repo "${{ github.repository }}" --pattern 'voyager-chrome-v*.zip' | |
| ls -la voyager-chrome-v*.zip | |
| - name: Strip dev key and publish to Chrome Web Store | |
| env: | |
| EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} | |
| CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} | |
| CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} | |
| REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} | |
| run: | | |
| TAG="v${{ inputs.version }}" | |
| ZIP="voyager-chrome-${TAG}.zip" | |
| rm -rf cws_pkg "voyager-chrome-cws-${TAG}.zip" | |
| mkdir cws_pkg | |
| unzip -q "$ZIP" -d cws_pkg | |
| node -e "const fs=require('fs');const m=JSON.parse(fs.readFileSync('cws_pkg/manifest.json','utf8'));delete m.key;fs.writeFileSync('cws_pkg/manifest.json',JSON.stringify(m,null,2)+'\n');" | |
| (cd cws_pkg && zip -qr "../voyager-chrome-cws-${TAG}.zip" .) | |
| bunx chrome-webstore-upload-cli@3 upload \ | |
| --source "voyager-chrome-cws-${TAG}.zip" \ | |
| --extension-id "$EXTENSION_ID" \ | |
| --client-id "$CLIENT_ID" \ | |
| --client-secret "$CLIENT_SECRET" \ | |
| --refresh-token "$REFRESH_TOKEN" \ | |
| --auto-publish | |
| # Job 4: Publish Edge only (manual) — recover or retry Microsoft Edge Add-ons | |
| # submission without re-cutting the release. Downloads the existing release's | |
| # Chrome zip, strips the dev `key`, and submits the package to Edge review. | |
| republish-edge: | |
| if: github.event_name == 'workflow_dispatch' && inputs.publish_edge_only | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout scripts | |
| uses: actions/checkout@v4 | |
| - name: Download Chrome zip from the existing release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| TAG="v${{ inputs.version }}" | |
| gh release download "$TAG" --repo "${{ github.repository }}" --pattern "voyager-chrome-${TAG}.zip" | |
| ls -la "voyager-chrome-${TAG}.zip" | |
| - name: Prepare Edge package and publish to Edge Add-ons | |
| env: | |
| EDGE_CLIENT_ID: ${{ secrets.EDGE_CLIENT_ID }} | |
| EDGE_API_KEY: ${{ secrets.EDGE_API_KEY }} | |
| EDGE_PRODUCT_ID: ${{ secrets.EDGE_PRODUCT_ID }} | |
| run: | | |
| TAG="v${{ inputs.version }}" | |
| ZIP="voyager-chrome-${TAG}.zip" | |
| rm -rf edge_pkg "voyager-edge-${TAG}.zip" | |
| mkdir edge_pkg | |
| unzip -q "$ZIP" -d edge_pkg | |
| node -e "const fs=require('fs');const m=JSON.parse(fs.readFileSync('edge_pkg/manifest.json','utf8'));delete m.key;fs.writeFileSync('edge_pkg/manifest.json',JSON.stringify(m,null,2)+'\n');" | |
| (cd edge_pkg && zip -qr "../voyager-edge-${TAG}.zip" .) | |
| node scripts/publish-edge.js "voyager-edge-${TAG}.zip" \ | |
| --notes "Voyager ${TAG} automated Edge Add-ons submission." |