chore(deps): bump fast-uri from 3.1.3 to 3.1.7 #561
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: CI | |
| on: | |
| push: | |
| branches: [main, electron] | |
| tags: ['v*'] | |
| pull_request: | |
| branches: [main, electron] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| # Deny all permissions by default; each job grants only what it needs. | |
| # This limits blast radius if a step is compromised (supply chain attack). | |
| permissions: {} | |
| jobs: | |
| # ── Lint & typecheck (fast, ubuntu-only) ────────────────────────────────── | |
| lint: | |
| name: Lint & Typecheck | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| - uses: actions/cache@v6 | |
| with: | |
| path: .turbo | |
| key: turbo-lint-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ github.sha }} | |
| restore-keys: | | |
| turbo-lint-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}- | |
| turbo-lint-${{ runner.os }}- | |
| - run: pnpm typecheck | |
| - run: pnpm lint | |
| # Gate only on files/dependencies/binaries — high-confidence categories | |
| # with no legitimate exceptions. Unused-exports/types are excluded | |
| # because packages/ui and e2e/helpers* intentionally export library-style | |
| # helpers that aren't consumed anywhere in this repo yet; run `pnpm knip` | |
| # locally (no flags) to see the full report including those. | |
| - run: pnpm knip --exclude exports,types | |
| # ── Unit / Vite tests (all 3 platforms) ─────────────────────────────────── | |
| test: | |
| name: Unit Tests (${{ matrix.os }}) | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-14, windows-latest] | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| - uses: actions/cache@v6 | |
| with: | |
| path: .turbo | |
| key: turbo-test-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ github.sha }} | |
| restore-keys: | | |
| turbo-test-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}- | |
| turbo-test-${{ runner.os }}- | |
| - run: pnpm test --filter=!@sproutgit/e2e | |
| # ── Compute the version this run will build/release ─────────────────────── | |
| # Tag pushes use the tag itself. Main-branch pushes derive {major}.{minor} | |
| # from app/package.json (the committed baseline) and auto-increment the | |
| # patch by looking at existing v{major}.{minor}.* tags — so patch resets to | |
| # 0 the next time major/minor is bumped in package.json, instead of growing | |
| # forever off github.run_number regardless of what's actually committed. | |
| version: | |
| name: Compute version | |
| if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| version: ${{ steps.compute.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 # need full tag history to find the last patch for this major.minor | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| - name: Compute version | |
| id: compute | |
| shell: bash | |
| run: | | |
| if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then | |
| VERSION="${GITHUB_REF_NAME#v}" | |
| elif [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then | |
| MAJOR_MINOR=$(node -e "console.log(require('./app/package.json').version.split('.').slice(0,2).join('.'))") | |
| ESCAPED="${MAJOR_MINOR//./\\.}" | |
| # `|| true` on the pipeline: grep exits 1 when no v{major}.{minor}.* | |
| # tags exist yet (the first release for a new major.minor), and | |
| # GitHub Actions' bash steps run with -e/pipefail by default — an | |
| # unmatched grep would otherwise abort the step instead of | |
| # correctly falling through to patch 0 below. | |
| LAST_PATCH=$(git tag -l "v${MAJOR_MINOR}.*" | sed -E "s/^v${ESCAPED}\.//" | grep -E '^[0-9]+$' | sort -n | tail -1 || true) | |
| NEXT_PATCH=$([ -z "$LAST_PATCH" ] && echo 0 || echo $((LAST_PATCH + 1))) | |
| VERSION="${MAJOR_MINOR}.${NEXT_PATCH}" | |
| else | |
| VERSION="0.0.0-dev.${GITHUB_RUN_NUMBER}" | |
| fi | |
| echo "version=${VERSION}" >> "$GITHUB_OUTPUT" | |
| # ── Build packages + app (all 3 platforms) ──────────────────────────────── | |
| build: | |
| name: Build (${{ matrix.platform }}) | |
| if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') | |
| needs: [lint, test, version] | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # macOS ships both arches from one arm64 host: cctools/lipo can | |
| # target x64 without an x64 machine, unlike Windows/Linux native | |
| # modules (node-pty) which need to actually compile on that arch. | |
| - os: macos-14 | |
| platform: mac | |
| arch: universal | |
| - os: windows-latest | |
| platform: win | |
| arch: x64 | |
| # Hosted arm64 runner so node-pty's node-gyp fallback compiles | |
| # natively instead of cross-compiling. Confirm availability/plan | |
| # requirements for this runner label before relying on it. | |
| - os: windows-11-arm | |
| platform: win | |
| arch: arm64 | |
| - os: ubuntu-latest | |
| platform: linux | |
| arch: x64 | |
| - os: ubuntu-24.04-arm | |
| platform: linux | |
| arch: arm64 | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| # runner.temp is only available within steps, not job-level env. | |
| - name: Set Electron cache paths | |
| shell: bash | |
| run: | | |
| echo "ELECTRON_CACHE=${{ runner.temp }}/electron-cache" >> "$GITHUB_ENV" | |
| echo "ELECTRON_BUILDER_CACHE=${{ runner.temp }}/electron-builder-cache" >> "$GITHUB_ENV" | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| - uses: actions/cache@v6 | |
| with: | |
| path: .turbo | |
| key: turbo-build-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ github.sha }} | |
| restore-keys: | | |
| turbo-build-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}- | |
| turbo-build-${{ runner.os }}- | |
| - uses: actions/cache@v6 | |
| with: | |
| path: | | |
| ${{ runner.temp }}/electron-cache | |
| ${{ runner.temp }}/electron-builder-cache | |
| key: electron-${{ runner.os }}-${{ hashFiles('app/package.json') }} | |
| - run: pnpm build --filter=!@sproutgit/app --filter=!sproutgit-website | |
| # Stamp app/package.json with the version the release job will actually | |
| # tag (computed once in the `version` job above, shared by every | |
| # platform in this matrix), so electron-builder embeds a matching | |
| # version in the artifacts instead of the placeholder committed to the repo. | |
| - name: Set package version | |
| working-directory: app | |
| shell: bash | |
| env: | |
| VERSION: ${{ needs.version.outputs.version }} | |
| run: | | |
| node -e " | |
| const fs = require('fs'); | |
| const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); | |
| pkg.version = process.env.VERSION; | |
| fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n'); | |
| " | |
| - name: Package app | |
| env: | |
| CSC_IDENTITY_AUTO_DISCOVERY: false | |
| working-directory: app | |
| # Explicit bash: windows-latest/windows-11-arm default to pwsh, which | |
| # can't parse the `if` below (Git Bash ships on all GitHub-hosted | |
| # Windows runners, so this works cross-platform). | |
| shell: bash | |
| # Run vite build then electron-builder separately so we can pass | |
| # --publish never. This prevents electron-builder from trying to | |
| # upload to GitHub Releases (the release job does that), and ensures | |
| # GH_TOKEN is never needed in the build matrix — narrowing the | |
| # attack surface if a dependency is compromised. | |
| run: | | |
| pnpm exec electron-vite build | |
| if [ "${{ matrix.arch }}" = "universal" ]; then | |
| pnpm exec electron-builder --${{ matrix.platform }} --publish never | |
| else | |
| pnpm exec electron-builder --${{ matrix.platform }} --${{ matrix.arch }} --publish never | |
| fi | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: SproutGit-${{ matrix.platform }}-${{ matrix.arch }} | |
| path: | | |
| app/dist-electron/*.dmg | |
| app/dist-electron/*.zip | |
| app/dist-electron/*.exe | |
| app/dist-electron/*.AppImage | |
| app/dist-electron/*.deb | |
| app/dist-electron/latest*.yml | |
| if-no-files-found: ignore | |
| # ── E2E tests (all 3 platforms) ─────────────────────────────────────────── | |
| e2e: | |
| name: E2E Tests (${{ matrix.platform }}) | |
| if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') | |
| needs: [lint, test] | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: macos-14 | |
| platform: mac | |
| - os: windows-latest | |
| platform: win | |
| - os: ubuntu-latest | |
| platform: linux | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| # runner.temp is only available within steps, not job-level env. | |
| - name: Set Electron cache path | |
| shell: bash | |
| run: echo "ELECTRON_CACHE=${{ runner.temp }}/electron-cache" >> "$GITHUB_ENV" | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| # Restore Electron binary cache BEFORE pnpm install so the postinstall | |
| # script can use the cached zip instead of downloading from the network. | |
| # If placed after install, the cache is restored too late to help. | |
| - uses: actions/cache@v6 | |
| with: | |
| path: | | |
| ${{ runner.temp }}/electron-cache | |
| key: electron-${{ runner.os }}-${{ hashFiles('app/package.json') }} | |
| - run: pnpm install --frozen-lockfile | |
| - name: Install Electron system dependencies (Linux) | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update -q | |
| sudo apt-get install -y -q \ | |
| xvfb \ | |
| libgbm1 libnss3 libatk1.0-0 libatk-bridge2.0-0 \ | |
| libdrm2 libxkbcommon0 libxdamage1 libxrandr2 libxfixes3 \ | |
| libxcomposite1 libxcursor1 libxi6 libxss1 libxtst6 \ | |
| libasound2t64 libpango-1.0-0 libpangocairo-1.0-0 \ | |
| libdbus-1-3 libglib2.0-0 | |
| # Build all packages, then the renderer (no electron-builder packaging needed) | |
| - run: pnpm build --filter=!@sproutgit/app --filter=!sproutgit-website | |
| - name: Build renderer | |
| working-directory: app | |
| run: pnpm exec electron-vite build | |
| # Linux needs a virtual display for Electron | |
| - name: Run E2E tests (Linux) | |
| if: matrix.platform == 'linux' | |
| run: xvfb-run --auto-servernum --server-args="-screen 0 1280x800x24" pnpm --filter @sproutgit/e2e test | |
| - name: Run E2E tests (macOS / Windows) | |
| if: matrix.platform != 'linux' | |
| run: pnpm --filter @sproutgit/e2e test | |
| # ── Diagnostics on failure ─────────────────────────────────────────── | |
| - name: Dump E2E diagnostics (Linux/macOS) | |
| if: failure() && runner.os != 'Windows' | |
| run: | | |
| echo "=== Chromium internal log (--log-file) ===" | |
| cat "$(node -e "const{tmpdir}=require('os');process.stdout.write(tmpdir())")/electron-e2e-chromium.log" 2>/dev/null | tail -n 200 || echo "(not found)" | |
| echo "" | |
| echo "=== electron-log main.log ===" | |
| if [ "$(uname)" = "Darwin" ]; then | |
| cat "${HOME}/Library/Logs/SproutGit/main.log" 2>/dev/null | tail -n 200 || echo "(not found)" | |
| else | |
| cat "${HOME}/.config/SproutGit/logs/main.log" 2>/dev/null | tail -n 200 || echo "(not found)" | |
| echo "" | |
| echo "=== Electron binary / ldd ===" | |
| ELECTRON_BIN=$(node -e "process.stdout.write(require('electron'))" 2>/dev/null) || true | |
| ldd "${ELECTRON_BIN}" 2>/dev/null | grep "not found" || echo "(no missing libs)" | |
| echo "DISPLAY=${DISPLAY}" | |
| fi | |
| echo "" | |
| echo "=== sg-e2e-latest.log ===" | |
| cat "$(node -e "const{tmpdir}=require('os');process.stdout.write(tmpdir())")/sg-e2e-latest.log" 2>/dev/null || echo "(not found)" | |
| working-directory: e2e | |
| - name: Dump E2E diagnostics (Windows) | |
| if: failure() && runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $tmp = [System.IO.Path]::GetTempPath() | |
| Write-Host "=== Chromium internal log (--log-file) ===" | |
| $chromLog = Join-Path $tmp "electron-e2e-chromium.log" | |
| if (Test-Path $chromLog) { Get-Content $chromLog | Select-Object -Last 200 } else { Write-Host "(not found)" } | |
| Write-Host "" | |
| Write-Host "=== electron-log main.log ===" | |
| $appLog = "$env:APPDATA\SproutGit\logs\main.log" | |
| if (Test-Path $appLog) { Get-Content $appLog | Select-Object -Last 200 } else { Write-Host "(not found)" } | |
| Write-Host "" | |
| Write-Host "=== sg-e2e-latest.log ===" | |
| $sgLog = Join-Path $tmp "sg-e2e-latest.log" | |
| if (Test-Path $sgLog) { Get-Content $sgLog } else { Write-Host "(not found)" } | |
| working-directory: e2e | |
| # ──────────────────────────────────────────────────────────────────── | |
| - uses: actions/upload-artifact@v7 | |
| if: failure() | |
| with: | |
| name: e2e-results-${{ matrix.platform }} | |
| path: | | |
| e2e/test-results/ | |
| tmp/playwright-report/ | |
| if-no-files-found: ignore | |
| # ── Release ─────────────────────────────────────────────────────────────── | |
| release: | |
| name: Create release | |
| if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') | |
| needs: [build, e2e, version] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write # needed to create the GitHub Release | |
| steps: | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| path: artifacts | |
| merge-multiple: true | |
| - uses: softprops/action-gh-release@v3 | |
| with: | |
| tag_name: v${{ needs.version.outputs.version }} | |
| files: artifacts/** | |
| generate_release_notes: true | |
| prerelease: ${{ contains(github.ref_name, '-') }} |