name: Release on: push: tags: - 'v*.*.*' # Manual runs rebuild the assets of an existing release: pass its tag # and the job uploads into that release instead of one named after the # branch. Used when a leg failed after the tag was pushed. workflow_dispatch: inputs: tag: description: 'Existing release tag to (re)build assets for, e.g. v1.0.0' required: true type: string permissions: contents: write jobs: # Each platform is its own job because electron-builder needs the # native toolchain (NSIS for .exe, hdiutil for .dmg, etc.) of the # runner OS — cross-compiling is not supported. The matrix wires both # jobs to the same release tag; whichever finishes first creates the # release, the other appends its artifacts via softprops/action-gh-release # which handles the duplicate-tag case idempotently. build: strategy: fail-fast: false matrix: include: - os: windows-latest build_script: build:win build_args: '' artifact_name: windows-installer artifact_paths: | apps/desktop/release/*.exe apps/desktop/release/*.exe.blockmap apps/desktop/release/latest.yml # macOS is built once per architecture rather than both arches # in one job. The bundled Playwright browsers are arch-specific # (`chrome-mac-arm64` vs `chrome-mac-x64`), so a single job on # an Apple-silicon runner used to ship arm64 binaries inside the # Intel .dmg — where Playwright looked for `chrome-mac-x64`, # found nothing, and every Intel user landed on the "browser # missing" dialog. Each job now downloads the browsers for the # architecture it is packaging. The `--arm64` / `--x64` flag is # the ONLY place the arch is chosen: electron-builder.yml must not # list `mac.target[].arch`, or the config list silently overrides # the flag and each leg builds both images again (that is what # broke 1.0.0's first run). # # Each leg also names its .dmg with the words a user actually # knows ("Apple-Silicon" / "Intel") instead of arm64 / x64. # electron-builder's artifactName template cannot branch on # arch, so the override rides on the CLI; single quotes keep # ${version} / ${ext} away from the shell so electron-builder # expands them. Keep in sync with pickInstallerAsset() in # apps/desktop/src/main/index.ts. - os: macos-latest build_script: build:mac build_args: "-- --arm64 -c.mac.artifactName='FreeCrawl-SEO-Tool-${version}-macOS-Apple-Silicon.${ext}'" artifact_name: macos-installer-arm64 artifact_paths: | apps/desktop/release/*Apple-Silicon*.dmg apps/desktop/release/*Apple-Silicon*.dmg.blockmap apps/desktop/release/*Apple-Silicon*.zip apps/desktop/release/latest-mac.yml - os: macos-latest build_script: build:mac build_args: "-- --x64 -c.mac.artifactName='FreeCrawl-SEO-Tool-${version}-macOS-Intel.${ext}'" # Cross-download the Intel browsers on the arm64 runner. # Playwright picks its download by host platform, so this # override is what makes the x64 bundle actually x64. playwright_host_platform: mac15 artifact_name: macos-installer-x64 # latest-mac.yml is published by the arm64 job only — both # jobs would otherwise upload a same-named file describing # just their own arch, and the winner would be whichever # finished last. artifact_paths: | apps/desktop/release/*.dmg apps/desktop/release/*.dmg.blockmap apps/desktop/release/*.zip - os: ubuntu-latest build_script: build:linux build_args: '' artifact_name: linux-installer artifact_paths: | apps/desktop/release/*.AppImage apps/desktop/release/*.deb apps/desktop/release/*.rpm apps/desktop/release/latest-linux.yml runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm - name: Install dependencies run: npm ci env: # Skip the postinstall browser download on cross-arch jobs so # the cache holds only the target architecture — otherwise the # installer would carry both (~+180 MB of dead weight). PLAYWRIGHT_SKIP_BROWSER_INSTALL: ${{ matrix.playwright_host_platform && '1' || '0' }} - name: Build shared packages run: npx tsc -b - name: Ensure Playwright browsers downloaded # `npm ci` runs `postinstall` which already pulls Chromium + # chrome-headless-shell, but we re-invoke explicitly to make # the dependency obvious in the workflow logs and to force a # re-check if Playwright was upgraded since the lockfile was # written. run: npm run playwright:install env: PLAYWRIGHT_HOST_PLATFORM_OVERRIDE: ${{ matrix.playwright_host_platform }} - name: Build installer (with bundled Playwright browsers) run: npm --workspace apps/desktop run ${{ matrix.build_script }} ${{ matrix.build_args }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Disable mac code-signing — we don't have an Apple Developer # cert in CI yet. Users will see a Gatekeeper warning on first # launch and must right-click → Open the .dmg's app once. CSC_IDENTITY_AUTO_DISCOVERY: 'false' # V2 Faz 1 — Bundle the Playwright Chromium browser into the # installer so JS rendering works offline / on first launch # with zero extra download. The prepare-playwright-bundle.mjs # script reads this env var and copies the OS cache into # apps/desktop/resources/playwright-browsers/ before # electron-builder packs the installer. BUNDLE_PLAYWRIGHT: '1' # Also read by prepare-playwright-bundle.mjs, which refuses to # package a browser cache that doesn't match the architecture # being built — the exact mismatch that shipped an arm64 # Chromium inside the Intel .dmg. PLAYWRIGHT_HOST_PLATFORM_OVERRIDE: ${{ matrix.playwright_host_platform }} - name: Upload build artifacts uses: actions/upload-artifact@v4 with: name: ${{ matrix.artifact_name }} path: ${{ matrix.artifact_paths }} if-no-files-found: warn - name: Publish GitHub Release uses: softprops/action-gh-release@v2 with: tag_name: ${{ inputs.tag || github.ref_name }} files: ${{ matrix.artifact_paths }} generate_release_notes: true draft: false prerelease: false