chore: resolve Electron Forge audit findings #84
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: Build & Release | |
| on: | |
| push: | |
| branches: [main, develop] | |
| paths-ignore: | |
| - 'docs/**' | |
| - 'README.md' | |
| - 'CHANGELOG.md' | |
| - 'LICENSE' | |
| - '*.md' | |
| workflow_dispatch: {} | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # TEST BUILD (develop only) — Verify code compiles and tests pass | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| test-python: | |
| if: github.ref == 'refs/heads/develop' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| strategy: | |
| matrix: | |
| python-version: ['3.11', '3.12', '3.13'] | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: pip | |
| cache-dependency-path: python/requirements.txt | |
| - name: Install and run tests | |
| run: | | |
| cd python | |
| pip install -r requirements.txt -q | |
| pip install pytest pytest-asyncio httpx -q | |
| pytest tests/ -v | |
| test-frontend: | |
| if: github.ref == 'refs/heads/develop' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: '22' | |
| cache: npm | |
| cache-dependency-path: frontend/package-lock.json | |
| - name: Install and build | |
| run: | | |
| cd frontend | |
| npm ci | |
| npm run build | |
| test-electron: | |
| if: github.ref == 'refs/heads/develop' | |
| needs: test-frontend | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| env: | |
| ELECTRON_CACHE: ${{ github.workspace }}/.electron-cache | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.12' | |
| cache: pip | |
| cache-dependency-path: python/requirements.txt | |
| - name: Install Python | |
| run: | | |
| cd python | |
| pip install -r requirements.txt -q | |
| pip install pyinstaller -q | |
| - name: Build sidecar | |
| run: | | |
| cd python | |
| python -m PyInstaller main.py --onefile --name metalens-sidecar --distpath dist --clean --noconfirm \ | |
| --hidden-import piexif \ | |
| --hidden-import mutagen \ | |
| --hidden-import hachoir \ | |
| --hidden-import olefile \ | |
| --hidden-import openpyxl \ | |
| --hidden-import docx \ | |
| --hidden-import pptx \ | |
| --hidden-import pypdf | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: '22' | |
| cache: npm | |
| cache-dependency-path: frontend/package-lock.json | |
| - name: Build frontend | |
| run: | | |
| cd frontend | |
| npm ci | |
| npm run build | |
| - name: Cache Electron download | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ env.ELECTRON_CACHE }} | |
| key: electron-${{ runner.os }}-${{ hashFiles('electron/package-lock.json') }} | |
| - name: Test electron setup | |
| run: | | |
| cd electron | |
| npm ci --include=dev | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # BETA RELEASE (develop only) — Package and publish a prerelease build | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| get-version-beta: | |
| if: github.ref == 'refs/heads/develop' | |
| needs: [test-python, test-frontend, test-electron] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| base_version: ${{ steps.ver.outputs.base_version }} | |
| beta_version: ${{ steps.ver.outputs.beta_version }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - id: ver | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| BASE=$(grep -oP 'VERSION\s*=\s*"\K[^"]+' python/config.py) | |
| # Count existing beta releases for this exact base version and increment — | |
| # keeps beta.N scoped to the version instead of the workflow's global run number. | |
| LAST=$(gh release list --repo "${{ github.repository }}" --limit 200 \ | |
| | grep -oP "v${BASE}-beta\.\K[0-9]+" | sort -n | tail -1) | |
| NEXT=$(( ${LAST:-0} + 1 )) | |
| BETA="${BASE}-beta.${NEXT}" | |
| echo "base_version=$BASE" >> "$GITHUB_OUTPUT" | |
| echo "beta_version=$BETA" >> "$GITHUB_OUTPUT" | |
| echo "Building MetaLens beta v$BETA" | |
| build-windows-beta: | |
| if: github.ref == 'refs/heads/develop' | |
| needs: get-version-beta | |
| runs-on: windows-latest | |
| permissions: | |
| contents: read | |
| env: | |
| ELECTRON_CACHE: ${{ github.workspace }}/.electron-cache | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.12' | |
| cache: pip | |
| cache-dependency-path: python/requirements.txt | |
| - name: Build Python sidecar | |
| shell: pwsh | |
| run: | | |
| cd python | |
| pip install -r requirements.txt -q | |
| pip install pyinstaller -q | |
| python -m PyInstaller main.py --onefile --name metalens-sidecar --distpath dist --clean --noconfirm ` | |
| --hidden-import piexif ` | |
| --hidden-import mutagen ` | |
| --hidden-import hachoir ` | |
| --hidden-import olefile ` | |
| --hidden-import openpyxl ` | |
| --hidden-import docx ` | |
| --hidden-import pptx ` | |
| --hidden-import pypdf | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: '22' | |
| cache: npm | |
| cache-dependency-path: frontend/package-lock.json | |
| - name: Build React frontend | |
| run: | | |
| cd frontend | |
| npm ci | |
| npm run build | |
| - name: Copy frontend dist into electron | |
| run: cp -r frontend/dist electron/frontend | |
| - name: Sync electron version to beta | |
| run: | | |
| cd electron | |
| npm version ${{ needs.get-version-beta.outputs.beta_version }} --no-git-tag-version --allow-same-version | |
| - name: Cache Electron download | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ env.ELECTRON_CACHE }} | |
| key: electron-${{ runner.os }}-${{ hashFiles('electron/package-lock.json') }} | |
| - name: Package with Electron Forge | |
| run: | | |
| cd electron | |
| npm ci --include=dev | |
| npx electron-forge make | |
| - name: Upload Windows beta artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: windows-installer-beta | |
| path: electron/out/make/**/*.exe | |
| if-no-files-found: error | |
| build-linux-beta: | |
| if: github.ref == 'refs/heads/develop' | |
| needs: get-version-beta | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| env: | |
| ELECTRON_CACHE: ${{ github.workspace }}/.electron-cache | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install system dependencies | |
| run: sudo apt-get update -qq && sudo apt-get install -y rpm fakeroot | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.12' | |
| cache: pip | |
| cache-dependency-path: python/requirements.txt | |
| - name: Build Python sidecar | |
| run: | | |
| cd python | |
| pip install -r requirements.txt -q | |
| pip install pyinstaller -q | |
| python -m PyInstaller main.py --onefile --name metalens-sidecar --distpath dist --clean --noconfirm \ | |
| --hidden-import piexif \ | |
| --hidden-import mutagen \ | |
| --hidden-import hachoir \ | |
| --hidden-import olefile \ | |
| --hidden-import openpyxl \ | |
| --hidden-import docx \ | |
| --hidden-import pptx \ | |
| --hidden-import pypdf | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: '22' | |
| cache: npm | |
| cache-dependency-path: frontend/package-lock.json | |
| - name: Build React frontend | |
| run: | | |
| cd frontend | |
| npm ci | |
| npm run build | |
| - name: Copy frontend dist into electron | |
| run: cp -r frontend/dist electron/frontend | |
| - name: Sync electron version to beta | |
| run: | | |
| cd electron | |
| npm version ${{ needs.get-version-beta.outputs.beta_version }} --no-git-tag-version --allow-same-version | |
| - name: Configure RPM macros | |
| run: echo '%_build_id_links none' >> ~/.rpmmacros | |
| - name: Cache Electron download | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ env.ELECTRON_CACHE }} | |
| key: electron-${{ runner.os }}-${{ hashFiles('electron/package-lock.json') }} | |
| - name: Package with Electron Forge | |
| run: | | |
| cd electron | |
| npm ci --include=dev | |
| npx electron-forge make | |
| - name: Create Linux tar.gz | |
| run: | | |
| VERSION=${{ needs.get-version-beta.outputs.beta_version }} | |
| APP_DIR=$(find electron/out -maxdepth 1 -type d -name "MetaLens-linux-*" | head -1) | |
| if [ -n "$APP_DIR" ]; then | |
| tar -czf "electron/out/make/MetaLens-${VERSION}-Linux.tar.gz" \ | |
| -C "$(dirname "$APP_DIR")" "$(basename "$APP_DIR")" | |
| echo "Created MetaLens-${VERSION}-Linux.tar.gz" | |
| else | |
| echo "Warning: no MetaLens-linux-* directory found, skipping tar.gz" | |
| fi | |
| - name: Upload Linux beta artifacts | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: linux-packages-beta | |
| path: | | |
| electron/out/make/**/*.deb | |
| electron/out/make/**/*.rpm | |
| electron/out/make/**/*.tar.gz | |
| if-no-files-found: error | |
| release-beta: | |
| if: github.ref == 'refs/heads/develop' | |
| needs: [get-version-beta, build-windows-beta, build-linux-beta] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| path: artifacts | |
| - name: List artifacts | |
| run: find artifacts -type f | |
| - name: Create beta GitHub Release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| tag_name: v${{ needs.get-version-beta.outputs.beta_version }} | |
| target_commitish: ${{ github.sha }} | |
| name: MetaLens v${{ needs.get-version-beta.outputs.beta_version }} (beta) | |
| draft: false | |
| prerelease: true | |
| generate_release_notes: true | |
| make_latest: false | |
| files: | | |
| artifacts/windows-installer-beta/** | |
| artifacts/linux-packages-beta/** | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # STABLE RELEASE (main only) — Promote the matching beta build, no rebuild | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| get-version: | |
| if: github.ref == 'refs/heads/main' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| version: ${{ steps.ver.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - id: ver | |
| run: | | |
| VERSION=$(grep -oP 'VERSION\s*=\s*"\K[^"]+' python/config.py) | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "Building MetaLens v$VERSION" | |
| find-latest-beta: | |
| if: github.ref == 'refs/heads/main' | |
| needs: get-version | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| beta_tag: ${{ steps.find.outputs.beta_tag }} | |
| steps: | |
| - id: find | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| VERSION: ${{ needs.get-version.outputs.version }} | |
| run: | | |
| TAG=$(gh release list --repo "$GITHUB_REPOSITORY" --limit 200 --json tagName \ | |
| --jq '.[].tagName' \ | |
| | grep -E "^v${VERSION}-beta\.[0-9]+$" \ | |
| | sort -t. -k4 -n \ | |
| | tail -1) | |
| if [ -z "$TAG" ]; then | |
| echo "::error::Nessuna release beta trovata per v${VERSION}. Genera prima una beta da develop con la stessa VERSION in python/config.py." | |
| exit 1 | |
| fi | |
| echo "beta_tag=$TAG" >> "$GITHUB_OUTPUT" | |
| echo "Promuovendo $TAG a stable v${VERSION}" | |
| promote-stable: | |
| if: github.ref == 'refs/heads/main' | |
| needs: [get-version, find-latest-beta] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Download beta release assets | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| mkdir -p promoted | |
| gh release download "${{ needs.find-latest-beta.outputs.beta_tag }}" \ | |
| --repo "$GITHUB_REPOSITORY" --dir promoted | |
| - name: Strip beta suffix from filenames | |
| run: | | |
| cd promoted | |
| # Each maker encodes the beta suffix differently: "-beta.N" (exe/tar.gz), | |
| # "beta.N" with no separator (rpm, dash stripped for Version field rules), | |
| # ".beta.N" (deb, dot-separated per Debian version conventions). | |
| for f in *; do | |
| new=$(echo "$f" | sed -E 's/[-.]?beta\.?[0-9]+//') | |
| if [ "$f" != "$new" ]; then mv -- "$f" "$new"; fi | |
| done | |
| ls -la | |
| - name: Create stable GitHub Release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| tag_name: v${{ needs.get-version.outputs.version }} | |
| target_commitish: ${{ github.sha }} | |
| name: MetaLens v${{ needs.get-version.outputs.version }} | |
| draft: false | |
| prerelease: false | |
| generate_release_notes: true | |
| make_latest: true | |
| files: promoted/* |