diff --git a/.github/workflows/PR-Auto-Deploy-V2.yml b/.github/workflows/PR-Auto-Deploy-V2.yml index b6ab8d34c8..a19a616735 100644 --- a/.github/workflows/PR-Auto-Deploy-V2.yml +++ b/.github/workflows/PR-Auto-Deploy-V2.yml @@ -23,13 +23,9 @@ permissions: pull-requests: write jobs: - pick: - uses: ./.github/workflows/_runner-pick.yml - check-pr: if: (github.event_name == 'pull_request' && github.event.action != 'closed') || github.event_name == 'workflow_dispatch' - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} + runs-on: ubuntu-latest outputs: should_deploy: ${{ steps.decide.outputs.should_deploy }} is_fork: ${{ steps.resolve.outputs.is_fork }} @@ -101,8 +97,8 @@ jobs: echo "allow_fork=${allow_fork:-false}" >> $GITHUB_OUTPUT deploy-v2-pr: - needs: [pick, check-pr] - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} + needs: check-pr + runs-on: ubuntu-latest if: needs.check-pr.outputs.should_deploy == 'true' && (needs.check-pr.outputs.is_fork == 'false' || needs.check-pr.outputs.allow_fork == 'true') # Concurrency control - only one deployment per PR at a time concurrency: @@ -112,10 +108,10 @@ jobs: contents: read issues: write pull-requests: write - id-token: write env: - USE_DEPOT: ${{ needs.pick.outputs.is_fork != 'true' }} - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} + # Single source of truth for whether this preview embeds the admin portal: + # drives the image build-arg and the deployment comment. + BUILD_PORTAL: "true" steps: - name: Harden Runner @@ -187,12 +183,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} fetch-depth: 0 # Fetch full history for commit hash detection - - name: Set up Depot CLI - if: env.USE_DEPOT == 'true' - uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.0.0 - - name: Set up Docker Buildx - if: env.USE_DEPOT != 'true' uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Get version number @@ -237,21 +228,9 @@ jobs: echo "Image needs to be built" fi - - name: Build and push V2 image (Depot) - if: env.USE_DEPOT == 'true' && steps.check-image.outputs.exists == 'false' - uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1.16.0 - with: - project: ${{ vars.DEPOT_PROJECT_ID }} - context: . - file: ./docker/embedded/Dockerfile - push: true - tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-${{ steps.commit-hash.outputs.app_short }} - build-args: VERSION_TAG=v2-alpha - platforms: linux/amd64 - - - name: Build and push V2 image (Docker fork fallback) - if: env.USE_DEPOT != 'true' && steps.check-image.outputs.exists == 'false' - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + - name: Build and push V2 image + if: steps.check-image.outputs.exists == 'false' + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: . file: ./docker/embedded/Dockerfile @@ -259,7 +238,9 @@ jobs: cache-from: type=gha,scope=stirling-pdf-latest cache-to: type=gha,mode=max,scope=stirling-pdf-latest tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-${{ steps.commit-hash.outputs.app_short }} - build-args: VERSION_TAG=v2-alpha + build-args: | + VERSION_TAG=v2-alpha + BUILD_PORTAL=${{ env.BUILD_PORTAL }} platforms: linux/amd64 - name: Set up SSH @@ -290,6 +271,7 @@ jobs: - /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/storage:/storage:rw environment: DISABLE_ADDITIONAL_FEATURES: "false" + STIRLING_BILLING_ACCOUNT_LINK_ENABLED: "true" SECURITY_ENABLELOGIN: "true" SECURITY_INITIALLOGIN_USERNAME: "${{ secrets.TEST_LOGIN_USERNAME }}" SECURITY_INITIALLOGIN_PASSWORD: "${{ secrets.TEST_LOGIN_PASSWORD }}" @@ -333,9 +315,70 @@ jobs: # Set port for output echo "v2_port=${V2_PORT}" >> $GITHUB_OUTPUT + # ---- Storybook preview (only when this PR touches stories/.storybook) ---- + # Runs inside the same approved-contributor-gated deploy job, so it deploys + # under the exact same access rules as the app preview. + - name: Detect Storybook changes + id: sb-changes + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 + with: + list-files: json + filters: | + storybook: + - 'frontend/**/*.stories.@(ts|tsx|mdx)' + - 'frontend/**/*.mdx' + - 'frontend/.storybook/**' + + - name: Set up Node.js for Storybook + if: steps.sb-changes.outputs.storybook == 'true' + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "22" + cache: "npm" + cache-dependency-path: frontend/package-lock.json + + - name: Install Task for Storybook + if: steps.sb-changes.outputs.storybook == 'true' + uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0 + + - name: Build and deploy Storybook + id: storybook + if: steps.sb-changes.outputs.storybook == 'true' + env: + VPS_HOST: ${{ secrets.NEW_VPS_HOST }} + VPS_USER: ${{ secrets.NEW_VPS_USERNAME }} + run: | + set -euo pipefail + # `prepare` generates the icon set stories import (not committed). + task frontend:prepare + task frontend:storybook:build + PR=${{ needs.check-pr.outputs.pr_number }} + # Served at the ROOT of its own port so Storybook's global MSW worker + # (/mockServiceWorker.js) resolves. Port = PR + 20000 (bijective, offset + # from the app preview's bare-PR-number port). + SB_PORT=$((PR + 20000)) + DIR=/stirling/SB-PR-$PR + tar czf storybook.tgz -C frontend/storybook-static . + scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ + storybook.tgz "$VPS_USER@$VPS_HOST:/tmp/storybook-$PR.tgz" + ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T \ + "$VPS_USER@$VPS_HOST" << ENDSSH + set -e + rm -rf "$DIR" && mkdir -p "$DIR" + tar xzf /tmp/storybook-$PR.tgz -C "$DIR" + rm -f /tmp/storybook-$PR.tgz + docker rm -f storybook-pr-$PR 2>/dev/null || true + docker run -d --name storybook-pr-$PR --restart unless-stopped \ + -p $SB_PORT:80 -v "$DIR":/usr/share/nginx/html:ro nginx:alpine + ENDSSH + echo "url=http://$VPS_HOST:$SB_PORT/" >> "$GITHUB_OUTPUT" + - name: Post V2 deployment URL to PR if: success() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + SB_URL: ${{ steps.storybook.outputs.url }} + SB_FILES: ${{ steps.sb-changes.outputs.storybook_files }} with: github-token: ${{ steps.setup-bot.outputs.token }} script: | @@ -359,12 +402,40 @@ jobs: } const deploymentUrl = `http://${{ secrets.NEW_VPS_HOST }}:${v2Port}`; - const httpsUrl = `https://${v2Port}.ssl.stirlingpdf.cloud`; + + // Only mention the portal when this image actually embeds it. + // Use the direct IP URL - the SSL hostname isn't supported yet. + const withPortal = "${{ env.BUILD_PORTAL }}" === "true"; + const portalNote = withPortal + ? `🧩 **Admin portal** included - try it at [${deploymentUrl}/portal](${deploymentUrl}/portal).\n\n` + : ``; + + // Storybook preview: only present when this PR changed stories/config. + const sbUrl = process.env.SB_URL; + let storybookNote = ""; + if (sbUrl) { + const files = JSON.parse(process.env.SB_FILES || "[]"); + const stories = files.filter((f) => /\.stories\.(ts|tsx|mdx)$/.test(f)); + const config = files.filter((f) => f.startsWith("frontend/.storybook/")); + const shorten = (f) => + f.replace(/^frontend\/editor\/src\//, "").replace(/^frontend\//, ""); + const storyList = stories.map((f) => `- \`${shorten(f)}\``).join("\n"); + const configList = config.map((f) => `- \`${shorten(f)}\``).join("\n"); + const summary = + `${stories.length} stor${stories.length === 1 ? "y" : "ies"} changed` + + (config.length ? ` (+${config.length} config file${config.length === 1 ? "" : "s"})` : ""); + storybookNote = + `📚 **Storybook:** [${sbUrl}](${sbUrl})\n\n` + + `
\n${summary}\n\n` + + (storyList ? `**Stories**\n${storyList}\n\n` : "") + + (configList ? `**Config**\n${configList}\n` : "") + + `
\n\n`; + } const commentBody = `## 🚀 V2 Auto-Deployment Complete!\n\n` + - `Your V2 PR with embedded architecture has been deployed!\n\n` + `🔗 **Direct Test URL (non-SSL)** [${deploymentUrl}](${deploymentUrl})\n\n` + - `🔐 **Secure HTTPS URL**: [${httpsUrl}](${httpsUrl})\n\n` + + portalNote + + storybookNote + `_This deployment will be automatically cleaned up when the PR is closed._\n\n` + `🔄 **Auto-deployed** for approved V2 contributors.`; @@ -377,8 +448,7 @@ jobs: cleanup-v2-deployment: if: github.event.action == 'closed' - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} + runs-on: ubuntu-latest permissions: contents: read issues: write @@ -460,7 +530,11 @@ jobs: else echo "V2 PR directory not found, nothing to clean up" fi - + + # Remove this PR's Storybook preview (container + files), if any. + docker rm -f storybook-pr-${{ github.event.pull_request.number }} 2>/dev/null || true + rm -rf /stirling/SB-PR-${{ github.event.pull_request.number }} + # Clean up old unused images (older than 2 weeks) but keep recent ones for reuse docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true diff --git a/.github/workflows/PR-Demo-Comment-with-react.yml b/.github/workflows/PR-Demo-Comment-with-react.yml index e478f95c89..2b81b8c777 100644 --- a/.github/workflows/PR-Demo-Comment-with-react.yml +++ b/.github/workflows/PR-Demo-Comment-with-react.yml @@ -34,12 +34,8 @@ permissions: pull-requests: read jobs: - pick: - uses: ./.github/workflows/_runner-pick.yml - check-comment: - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} + runs-on: ubuntu-latest permissions: issues: write if: | @@ -179,15 +175,11 @@ jobs: } deploy-pr: - needs: [pick, check-comment] - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} + needs: check-comment + runs-on: ubuntu-latest permissions: issues: write pull-requests: write - id-token: write - env: - USE_DEPOT: ${{ needs.pick.outputs.is_fork != 'true' }} - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} steps: - name: Harden Runner @@ -220,9 +212,9 @@ jobs: distribution: "temurin" - name: Setup Gradle - uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 + uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 with: - gradle-version: 9.6.0 + gradle-version: 9.6.1 - name: Install Task uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0 @@ -240,12 +232,7 @@ jobs: MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }} STIRLING_PDF_DESKTOP_UI: false - - name: Set up Depot CLI - if: env.USE_DEPOT == 'true' - uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.0.0 - - name: Set up Docker Buildx - if: env.USE_DEPOT != 'true' uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Login to Docker Hub @@ -254,23 +241,8 @@ jobs: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_API }} - - name: Build and push PR-specific image (Depot) - if: env.USE_DEPOT == 'true' - uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1.16.0 - with: - project: ${{ vars.DEPOT_PROJECT_ID }} - context: . - file: ./docker/embedded/Dockerfile - push: true - tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ needs.check-comment.outputs.pr_number }} - build-args: | - VERSION_TAG=alpha - PROTOTYPES_BUILD=${{ needs.check-comment.outputs.enable_prototypes }} - platforms: linux/amd64 - - - name: Build and push PR-specific image (Docker fork fallback) - if: env.USE_DEPOT != 'true' - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + - name: Build and push PR-specific image + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: . file: ./docker/embedded/Dockerfile @@ -283,20 +255,9 @@ jobs: PROTOTYPES_BUILD=${{ needs.check-comment.outputs.enable_prototypes }} platforms: linux/amd64 - - name: Build and push engine image (Depot) - if: env.USE_DEPOT == 'true' && needs.check-comment.outputs.enable_prototypes == 'true' - uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1.16.0 - with: - project: ${{ vars.DEPOT_PROJECT_ID }} - context: ./engine - file: ./engine/Dockerfile - push: true - tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:engine-pr-${{ needs.check-comment.outputs.pr_number }} - platforms: linux/amd64 - - - name: Build and push engine image (Docker fork fallback) - if: env.USE_DEPOT != 'true' && needs.check-comment.outputs.enable_prototypes == 'true' - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + - name: Build and push engine image + if: needs.check-comment.outputs.enable_prototypes == 'true' + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: ./engine file: ./engine/Dockerfile @@ -510,8 +471,7 @@ jobs: handle-label-commands: if: ${{ github.event.issue.pull_request != null }} - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} + runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 diff --git a/.github/workflows/_runner-pick.yml b/.github/workflows/_runner-pick.yml index 023325d383..0f32d79b95 100644 --- a/.github/workflows/_runner-pick.yml +++ b/.github/workflows/_runner-pick.yml @@ -2,8 +2,8 @@ name: _runner-pick # Tiny reusable workflow that classifies the trigger as either a "fork PR # from an untrusted contributor" or a "trusted commit" so downstream jobs -# can pick a runner class without each one duplicating the 200-char gate -# expression in their own `runs-on:`. +# can trust-gate (skip secret-dependent jobs on forks) without each one +# duplicating the gate expression. # # Caller pattern: # @@ -13,12 +13,12 @@ name: _runner-pick # # real-work: # needs: pick -# runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-8' }} +# if: needs.pick.outputs.is_fork != 'true' # steps: [...] # -# Output: -# is_fork: "true" when the trigger is a pull_request from a fork or an -# untrusted author_association, "false" otherwise. +# Outputs: +# is_fork: "true" when the trigger is a pull_request from a fork or an +# untrusted author_association, "false" otherwise. on: workflow_call: @@ -50,21 +50,18 @@ jobs: AUTHOR_ASSOC: ${{ github.event.pull_request.author_association }} run: | set -eu + if [ -z "${PR_NUMBER:-}" ]; then # Not a pull_request event at all (push, schedule, workflow_dispatch, # workflow_call from a non-PR trigger) -> trusted by default. - echo "is_fork=false" >> "$GITHUB_OUTPUT" - exit 0 + is_fork=false + elif [ "${HEAD_REPO_FORK}" = "true" ]; then + is_fork=true + else + case "${AUTHOR_ASSOC}" in + OWNER|MEMBER|COLLABORATOR) is_fork=false ;; + *) is_fork=true ;; + esac fi - if [ "${HEAD_REPO_FORK}" = "true" ]; then - echo "is_fork=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - case "${AUTHOR_ASSOC}" in - OWNER|MEMBER|COLLABORATOR) - echo "is_fork=false" >> "$GITHUB_OUTPUT" - ;; - *) - echo "is_fork=true" >> "$GITHUB_OUTPUT" - ;; - esac + + echo "is_fork=${is_fork}" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/ai-engine.yml b/.github/workflows/ai-engine.yml index 223490f45f..5061401067 100644 --- a/.github/workflows/ai-engine.yml +++ b/.github/workflows/ai-engine.yml @@ -18,8 +18,6 @@ jobs: permissions: contents: read pull-requests: write - env: - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 diff --git a/.github/workflows/backend-build.yml b/.github/workflows/backend-build.yml index 37a42c419f..28974a5ff6 100644 --- a/.github/workflows/backend-build.yml +++ b/.github/workflows/backend-build.yml @@ -19,14 +19,8 @@ permissions: pull-requests: write jobs: - pick: - uses: ./.github/workflows/_runner-pick.yml - build: - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-8' }} - env: - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} + runs-on: ubuntu-latest strategy: fail-fast: false matrix: @@ -47,7 +41,7 @@ jobs: distribution: "temurin" - name: Cache Gradle dependency artifacts - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | ~/.gradle/wrapper @@ -56,9 +50,9 @@ jobs: key: gradle-deps-${{ runner.os }}-jdk-${{ matrix.jdk-version }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties', '**/*.gradle', '**/*.gradle.kts', 'settings.gradle', 'settings.gradle.kts', 'gradle/libs.versions.toml') }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 + uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 with: - gradle-version: 9.6.0 + gradle-version: 9.6.1 cache-disabled: true - name: Install Task diff --git a/.github/workflows/build-enterprise.yml b/.github/workflows/build-enterprise.yml index d29002ac37..8c06175a5d 100644 --- a/.github/workflows/build-enterprise.yml +++ b/.github/workflows/build-enterprise.yml @@ -2,7 +2,7 @@ name: Enterprise E2E (Playwright) # Enterprise Playwright suite — exercises premium-key gated features (audit, # teams, analytics) plus full OAuth + SAML logins via the Keycloak compose -# stacks under testing/compose. Slow and secret-gated, so it runs in three +# stacks under testing/compose. Slow and secret-gated, so it runs in four # situations: # # - PRs that touch proprietary / premium / SSO compose / enterprise tests @@ -12,28 +12,14 @@ name: Enterprise E2E (Playwright) # - on a nightly cron schedule (catches Keycloak image drift, license # expiry, upstream proprietary changes), # - manual workflow_dispatch. -# -# Auto-skipped when secrets.PREMIUM_KEY_ENTERPRISE is missing (forks, dependabot). on: workflow_call: - inputs: - depot_cores: - description: "Depot runner vCPU count (used in runs-on). Override for benchmarking." - required: false - type: string - default: "8" push: branches: ["main"] schedule: - cron: "0 4 * * *" workflow_dispatch: - inputs: - depot_cores: - description: "Depot runner vCPU count (used in runs-on). Override for benchmarking." - required: false - type: string - default: "8" # No `concurrency:` block here on purpose. When this workflow is called via # workflow_call from build.yml, ${{ github.workflow }}/event_name/pr_number @@ -52,13 +38,16 @@ jobs: playwright-e2e-enterprise: needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || format('depot-ubuntu-24.04-{0}', inputs.depot_cores || '8') }} + # Skip on fork PRs / untrusted authors: they have no PREMIUM_KEY_ENTERPRISE, + # so the suite can't boot premium and would fail. See the header comment. + # GitHub reports the skipped reusable workflow as success. + if: needs.pick.outputs.is_fork != 'true' + runs-on: ubuntu-latest timeout-minutes: 45 env: PREMIUM_KEY: ${{ secrets.PREMIUM_KEY_ENTERPRISE }} PREMIUM_ENABLED: "true" SYSTEM_ENABLEANALYTICS: "false" - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} steps: - name: Harden Runner uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 @@ -165,6 +154,8 @@ jobs: wait_for_backend - name: Run enterprise OAuth Playwright tests id: oauth-tests + env: + PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results-oauth.json run: task e2e:enterprise -- --grep "OAuth" - name: Stop backend + tear down OAuth Keycloak if: always() @@ -238,6 +229,8 @@ jobs: wait_for_backend - name: Run enterprise SAML Playwright tests id: saml-tests + env: + PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results-saml.json run: task e2e:enterprise -- --grep "SAML" - name: Stop backend + tear down SAML Keycloak if: always() @@ -268,6 +261,8 @@ jobs: wait_for_backend - name: Run enterprise feature Playwright tests id: feature-tests + env: + PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results-feature.json run: task e2e:enterprise -- --grep "Enterprise license" - name: Print backend log on failure if: failure() @@ -280,10 +275,23 @@ jobs: run: | source /tmp/helpers.sh stop_backend + - name: Flag flaky tests + # Runs regardless of the test outcomes: a flaky test (passed on retry) + # leaves its step green, so this is the only place it surfaces. Merges + # all three phase reports (some may be absent if an earlier phase hard- + # failed and skipped the rest). Emits ::warning:: annotations + a job + # summary; never fails the job. + if: always() + working-directory: frontend + run: > + npx tsx editor/scripts/report-flaky-tests.mts + "${{ github.workspace }}/frontend/playwright-report/results-oauth.json" + "${{ github.workspace }}/frontend/playwright-report/results-saml.json" + "${{ github.workspace }}/frontend/playwright-report/results-feature.json" - name: Upload Playwright report if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: playwright-report-enterprise-${{ github.run_id }} - path: frontend/editor/playwright-report/ + path: frontend/playwright-report/ retention-days: 7 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7018a38120..8994382d12 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -147,7 +147,6 @@ jobs: permissions: contents: read packages: read - id-token: write uses: ./.github/workflows/test-build-docker.yml secrets: inherit with: diff --git a/.github/workflows/check-licence.yml b/.github/workflows/check-licence.yml index b984f7d47d..ba93d1d91f 100644 --- a/.github/workflows/check-licence.yml +++ b/.github/workflows/check-licence.yml @@ -11,8 +11,6 @@ permissions: jobs: check-licence: runs-on: ubuntu-latest - env: - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} steps: - name: Harden Runner uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 diff --git a/.github/workflows/check-openapi.yml b/.github/workflows/check-openapi.yml index 46ce829a87..d853afa86f 100644 --- a/.github/workflows/check-openapi.yml +++ b/.github/workflows/check-openapi.yml @@ -10,14 +10,8 @@ permissions: contents: read jobs: - pick: - uses: ./.github/workflows/_runner-pick.yml - check-generate-openapi-docs: - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} - env: - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} + runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 @@ -34,7 +28,7 @@ jobs: distribution: "temurin" - name: Cache Gradle dependency artifacts - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | ~/.gradle/wrapper @@ -43,9 +37,9 @@ jobs: key: gradle-deps-${{ runner.os }}-jdk-25-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties', '**/*.gradle', '**/*.gradle.kts', 'settings.gradle', 'settings.gradle.kts', 'gradle/libs.versions.toml') }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 + uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 with: - gradle-version: 9.6.0 + gradle-version: 9.6.1 cache-disabled: true - name: Install Task diff --git a/.github/workflows/coverage-aggregate.yml b/.github/workflows/coverage-aggregate.yml index bbfaf09363..bbed57db4d 100644 --- a/.github/workflows/coverage-aggregate.yml +++ b/.github/workflows/coverage-aggregate.yml @@ -13,17 +13,24 @@ name: Aggregate backend coverage # producers themselves on: workflow_call: + inputs: + frontend-validation-result: + description: Result of the frontend-validation producer job + required: false + type: string + default: skipped + playwright-e2e-live-result: + description: Result of the playwright-e2e-live producer job + required: false + type: string + default: skipped permissions: contents: read jobs: - pick: - uses: ./.github/workflows/_runner-pick.yml - aggregate: - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} + runs-on: ubuntu-latest timeout-minutes: 15 steps: - name: Harden Runner @@ -40,7 +47,7 @@ jobs: distribution: "temurin" - name: Cache Gradle dependency artifacts - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | ~/.gradle/wrapper @@ -49,9 +56,9 @@ jobs: key: gradle-deps-${{ runner.os }}-jdk-25-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties', '**/*.gradle', '**/*.gradle.kts', 'settings.gradle', 'settings.gradle.kts', 'gradle/libs.versions.toml') }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 + uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 with: - gradle-version: 9.6.0 + gradle-version: 9.6.1 cache-disabled: true - name: Set up Python @@ -196,9 +203,9 @@ jobs: # -------------------------------------------------------------- - name: Download vitest coverage artifact # frontend-validation uploads as `frontend-coverage`. Tolerate - # absence so a backend-only PR still produces the matrix with - # just backend rows populated. - if: always() + # absence on backend-only runs by skipping the download entirely + # when the producer job was not part of this workflow run. + if: inputs.frontend-validation-result == 'success' uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v6.0.0 with: name: frontend-coverage @@ -206,12 +213,12 @@ jobs: continue-on-error: true - name: Download Playwright frontend coverage artifact - # e2e-live uploads as `playwright-frontend-coverage-`. - # Same tolerance as vitest - matrix script handles missing inputs. - if: always() + # e2e-live uploads the artifact with a stable name. Skip the + # download entirely when the producer job did not run. + if: inputs.playwright-e2e-live-result == 'success' uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v6.0.0 with: - name: playwright-frontend-coverage-${{ github.run_id }} + name: playwright-frontend-coverage path: matrix-inputs/playwright/ continue-on-error: true diff --git a/.github/workflows/db-migration-test.yml b/.github/workflows/db-migration-test.yml index 4b5ca8b674..c6ebc481a7 100644 --- a/.github/workflows/db-migration-test.yml +++ b/.github/workflows/db-migration-test.yml @@ -12,15 +12,9 @@ permissions: contents: read jobs: - pick: - uses: ./.github/workflows/_runner-pick.yml - migration-test: - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-8' }} + runs-on: ubuntu-latest timeout-minutes: 30 - env: - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} steps: - name: Harden Runner uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 @@ -37,7 +31,7 @@ jobs: distribution: temurin - name: Cache Gradle dependency artifacts - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | ~/.gradle/wrapper @@ -46,9 +40,9 @@ jobs: key: gradle-deps-${{ runner.os }}-jdk-25-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties', '**/*.gradle', '**/*.gradle.kts', 'settings.gradle', 'settings.gradle.kts', 'gradle/libs.versions.toml') }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 + uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 with: - gradle-version: 9.6.0 + gradle-version: 9.6.1 cache-disabled: true # No `-PnoSpotless` here yet because the upstream cache layer matches the diff --git a/.github/workflows/deploy-on-v2-commit.yml b/.github/workflows/deploy-on-v2-commit.yml index 850dcb12e4..2dcbe2ae65 100644 --- a/.github/workflows/deploy-on-v2-commit.yml +++ b/.github/workflows/deploy-on-v2-commit.yml @@ -10,21 +10,11 @@ permissions: contents: read jobs: - pick: - uses: ./.github/workflows/_runner-pick.yml - deploy-v2-on-push: - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} + runs-on: ubuntu-latest concurrency: group: deploy-v2-push-V2 cancel-in-progress: true - permissions: - contents: read - id-token: write - env: - USE_DEPOT: ${{ needs.pick.outputs.is_fork != 'true' }} - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} steps: - name: Harden Runner @@ -35,12 +25,7 @@ jobs: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Set up Depot CLI - if: env.USE_DEPOT == 'true' - uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.0.0 - - name: Set up Docker Buildx - if: env.USE_DEPOT != 'true' uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Get commit hashes for frontend and backend @@ -105,23 +90,9 @@ jobs: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_API }} - - name: Build and push frontend image (Depot) - if: env.USE_DEPOT == 'true' && steps.check-frontend.outputs.exists == 'false' - uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1.16.0 - with: - project: ${{ vars.DEPOT_PROJECT_ID }} - context: . - file: ./docker/frontend/Dockerfile - push: true - tags: | - ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }} - ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-latest - build-args: VERSION_TAG=v2-alpha - platforms: linux/amd64 - - - name: Build and push frontend image (Docker fork fallback) - if: env.USE_DEPOT != 'true' && steps.check-frontend.outputs.exists == 'false' - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + - name: Build and push frontend image + if: steps.check-frontend.outputs.exists == 'false' + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: . file: ./docker/frontend/Dockerfile @@ -134,23 +105,9 @@ jobs: build-args: VERSION_TAG=v2-alpha platforms: linux/amd64 - - name: Build and push backend image (Depot) - if: env.USE_DEPOT == 'true' && steps.check-backend.outputs.exists == 'false' - uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1.16.0 - with: - project: ${{ vars.DEPOT_PROJECT_ID }} - context: . - file: ./docker/backend/Dockerfile - push: true - tags: | - ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }} - ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-latest - build-args: VERSION_TAG=v2-alpha - platforms: linux/amd64 - - - name: Build and push backend image (Docker fork fallback) - if: env.USE_DEPOT != 'true' && steps.check-backend.outputs.exists == 'false' - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + - name: Build and push backend image + if: steps.check-backend.outputs.exists == 'false' + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: . file: ./docker/backend/Dockerfile diff --git a/.github/workflows/docker-compose-tests.yml b/.github/workflows/docker-compose-tests.yml index 155b990b32..2db9e5cf72 100644 --- a/.github/workflows/docker-compose-tests.yml +++ b/.github/workflows/docker-compose-tests.yml @@ -11,28 +11,17 @@ on: required: false type: string default: "false" - depot_cores: - description: "Depot runner vCPU count (used in runs-on). Override for benchmarking. Tuned to 4 because bench showed 16 was within noise of 4." - required: false - type: string - default: "4" permissions: contents: read jobs: - pick: - uses: ./.github/workflows/_runner-pick.yml - docker-compose-tests: - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || format('depot-ubuntu-24.04-{0}', inputs.depot_cores || '4') }} + runs-on: ubuntu-latest permissions: actions: write contents: read checks: write - env: - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} steps: - name: Harden Runner @@ -50,7 +39,7 @@ jobs: distribution: "temurin" - name: Cache Gradle dependency artifacts - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | ~/.gradle/wrapper @@ -59,9 +48,9 @@ jobs: key: gradle-deps-${{ runner.os }}-jdk-25-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties', '**/*.gradle', '**/*.gradle.kts', 'settings.gradle', 'settings.gradle.kts', 'gradle/libs.versions.toml') }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 + uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 with: - gradle-version: 9.6.0 + gradle-version: 9.6.1 cache-disabled: true # When the PR changes the base image, test.sh builds it locally diff --git a/.github/workflows/e2e-live.yml b/.github/workflows/e2e-live.yml index eb6d8d7be5..3a1b4e3af1 100644 --- a/.github/workflows/e2e-live.yml +++ b/.github/workflows/e2e-live.yml @@ -5,23 +5,13 @@ name: Playwright E2E (live backend) # server. on: workflow_call: - inputs: - depot_cores: - description: "Depot runner vCPU count (used in runs-on). Override for benchmarking." - required: false - type: string - default: "8" permissions: contents: read jobs: - pick: - uses: ./.github/workflows/_runner-pick.yml - playwright-e2e-live: - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || format('depot-ubuntu-24.04-{0}', inputs.depot_cores || '8') }} + runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Harden Runner @@ -62,7 +52,17 @@ jobs: # .test-state/playwright/coverage-pw/ for the post-process step # to aggregate. Chromium-only - other engines silently skip. PW_COVERAGE: "1" + PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results.json run: task e2e:live + - name: Flag flaky tests + # Runs regardless of the test outcome: a flaky test (passed on retry) + # leaves the step green, so this is the only place it surfaces. Emits + # ::warning:: annotations + a job summary; never fails the job. + if: always() + working-directory: frontend + run: npx tsx editor/scripts/report-flaky-tests.mts "$PLAYWRIGHT_JSON_OUTPUT_FILE" + env: + PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results.json - name: Generate JaCoCo report from e2e:live .exec if: always() id: live-coverage @@ -169,7 +169,7 @@ jobs: if: always() && steps.pw-frontend-coverage.outputs.summary == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: playwright-frontend-coverage-${{ github.run_id }} + name: playwright-frontend-coverage path: | .test-state/playwright/coverage-pw-summary/ .test-state/playwright/coverage-pw/ diff --git a/.github/workflows/e2e-stubbed.yml b/.github/workflows/e2e-stubbed.yml index dd6bcc8ea7..214a7188df 100644 --- a/.github/workflows/e2e-stubbed.yml +++ b/.github/workflows/e2e-stubbed.yml @@ -5,23 +5,13 @@ name: Playwright E2E (stubbed) # mocks API responses in the browser. on: workflow_call: - inputs: - depot_cores: - description: "Depot runner vCPU count (used in runs-on). Override for benchmarking. Tuned to 8 to match the other playwright workflows; bench showed flat scaling above 8." - required: false - type: string - default: "8" permissions: contents: read jobs: - pick: - uses: ./.github/workflows/_runner-pick.yml - playwright-e2e: - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || format('depot-ubuntu-24.04-{0}', inputs.depot_cores || '8') }} + runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 @@ -44,11 +34,22 @@ jobs: VITE_BUILD_FOR_PREVIEW: "1" run: task frontend:build - name: Run stubbed E2E tests (chromium) + env: + PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results.json run: task e2e:stubbed -- --workers=3 + - name: Flag flaky tests + # Runs regardless of the test outcome: a flaky test (passed on retry) + # leaves the step green, so this is the only place it surfaces. Emits + # ::warning:: annotations + a job summary; never fails the job. + if: always() + working-directory: frontend + run: npx tsx editor/scripts/report-flaky-tests.mts "$PLAYWRIGHT_JSON_OUTPUT_FILE" + env: + PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results.json - name: Upload Playwright report if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: playwright-report-stubbed-${{ github.run_id }} - path: frontend/editor/playwright-report/ + path: frontend/playwright-report/ retention-days: 7 diff --git a/.github/workflows/frontend-backend-licenses-update.yml b/.github/workflows/frontend-backend-licenses-update.yml index 41d3f35c6e..fcf6e18d0e 100644 --- a/.github/workflows/frontend-backend-licenses-update.yml +++ b/.github/workflows/frontend-backend-licenses-update.yml @@ -19,13 +19,9 @@ permissions: contents: read jobs: - pick: - uses: ./.github/workflows/_runner-pick.yml - files-changed: name: detect what files changed - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} + runs-on: ubuntu-latest timeout-minutes: 3 outputs: licenses-frontend: ${{ steps.changes.outputs.licenses-frontend }} @@ -48,8 +44,8 @@ jobs: generate-frontend-license-report: if: needs.files-changed.outputs.licenses-frontend == 'true' name: Generate Frontend License Report - needs: [pick, files-changed] - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} + needs: files-changed + runs-on: ubuntu-latest permissions: contents: write pull-requests: write @@ -98,6 +94,13 @@ jobs: - name: Install Task uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0 + + - name: Generate frontend license report (Push only) + if: github.event_name == 'push' + env: + PR_IS_FORK: "false" + run: task frontend:licenses:generate + - name: Generate frontend license report (internal PR) if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false env: @@ -292,7 +295,10 @@ jobs: base: main title: "Update Frontend 3rd Party Licenses" body: ${{ env.PR_BODY }} - labels: Licenses,github-actions,frontend + labels: | + Licenses + github-actions + Front End draft: false delete-branch: true sign-commits: true @@ -311,15 +317,13 @@ jobs: generate-backend-license-report: if: needs.files-changed.outputs.licenses-backend == 'true' - needs: [pick, files-changed] + needs: files-changed name: Generate Backend License Report - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} + runs-on: ubuntu-latest permissions: contents: write pull-requests: write repository-projects: write # Required for enabling automerge - env: - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} steps: - name: Harden Runner uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 @@ -347,12 +351,13 @@ jobs: distribution: "temurin" - name: Setup Gradle - uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 + uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 with: - gradle-version: 9.6.0 + gradle-version: 9.6.1 - name: Install Task uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0 + - name: Check licenses and generate report id: license-check run: task backend:licenses:generate || echo "LICENSE_CHECK_FAILED=true" >> $GITHUB_ENV @@ -512,7 +517,10 @@ jobs: base: main title: "Update Backend 3rd Party Licenses" body: ${{ env.PR_BODY }} - labels: Licenses,github-actions,backend + labels: | + Licenses + github-actions + Back End delete-branch: true sign-commits: true diff --git a/.github/workflows/frontend-validation.yml b/.github/workflows/frontend-validation.yml index 70187df412..a539a1b7b7 100644 --- a/.github/workflows/frontend-validation.yml +++ b/.github/workflows/frontend-validation.yml @@ -11,12 +11,8 @@ permissions: pull-requests: write jobs: - pick: - uses: ./.github/workflows/_runner-pick.yml - frontend-validation: - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} + runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 diff --git a/.github/workflows/multiOSReleases.yml b/.github/workflows/multiOSReleases.yml index 3a76708460..41ddeb7a13 100644 --- a/.github/workflows/multiOSReleases.yml +++ b/.github/workflows/multiOSReleases.yml @@ -36,13 +36,9 @@ permissions: contents: read jobs: - pick: - uses: ./.github/workflows/_runner-pick.yml - determine-matrix: if: ${{ vars.CI_PROFILE != 'lite' }} - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} + runs-on: ubuntu-latest outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} version: ${{ steps.versionNumber.outputs.versionNumber }} @@ -61,7 +57,7 @@ jobs: distribution: "temurin" - name: Cache Gradle dependencies - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | ~/.gradle/caches @@ -71,9 +67,9 @@ jobs: gradle-${{ runner.os }}- - name: Setup Gradle - uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 + uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 with: - gradle-version: 9.6.0 + gradle-version: 9.6.1 - name: Install Task uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0 @@ -112,10 +108,8 @@ jobs: fi build-jars: - needs: [pick, determine-matrix] - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} - env: - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} + needs: determine-matrix + runs-on: ubuntu-latest strategy: matrix: variant: @@ -146,9 +140,9 @@ jobs: distribution: "temurin" - name: Setup Gradle - uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 + uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 with: - gradle-version: 9.6.0 + gradle-version: 9.6.1 - name: Setup Node.js if: matrix.variant.build_frontend == true @@ -195,7 +189,6 @@ jobs: SM_API_KEY: ${{ secrets.SM_API_KEY }} WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} RELEASE_GPG_PRIVATE_KEY: ${{ secrets.RELEASE_GPG_PRIVATE_KEY }} - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} steps: - name: Harden Runner uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 @@ -250,9 +243,9 @@ jobs: distribution: "temurin" - name: Setup Gradle - uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 + uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 with: - gradle-version: 9.6.0 + gradle-version: 9.6.1 - name: Install Task uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0 @@ -638,8 +631,8 @@ jobs: retention-days: 1 collect-and-release: - needs: [pick, determine-matrix, build, build-jars] - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} + needs: [determine-matrix, build, build-jars] + runs-on: ubuntu-latest permissions: contents: write steps: diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 11a8ea2be5..4047157a45 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -13,13 +13,9 @@ permissions: contents: read jobs: - pick: - uses: ./.github/workflows/_runner-pick.yml - playwright-all-browsers: name: Playwright (chromium + firefox + webkit) - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} + runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 @@ -53,8 +49,8 @@ jobs: if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: playwright-nightly-${{ github.run_id }} - path: frontend/editor/playwright-report/ + name: playwright-report-nightly-${{ github.run_id }} + path: frontend/playwright-report/ retention-days: 14 # Builds all desktop platforms on a schedule so the Rust dependency cache is diff --git a/.github/workflows/swagger.yml b/.github/workflows/swagger.yml index 9696aa419c..90c5984693 100644 --- a/.github/workflows/swagger.yml +++ b/.github/workflows/swagger.yml @@ -22,15 +22,9 @@ permissions: contents: read jobs: - pick: - uses: ./.github/workflows/_runner-pick.yml - push: if: ${{ vars.CI_PROFILE != 'lite' }} - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} - env: - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} + runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 @@ -46,9 +40,9 @@ jobs: distribution: "temurin" - name: Setup Gradle - uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 + uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 with: - gradle-version: 9.6.0 + gradle-version: 9.6.1 - name: Generate Swagger documentation run: ./gradlew :stirling-pdf:generateOpenApiDocs diff --git a/.github/workflows/tauri-build.yml b/.github/workflows/tauri-build.yml index 0a017d6273..5b2fa21c28 100644 --- a/.github/workflows/tauri-build.yml +++ b/.github/workflows/tauri-build.yml @@ -96,7 +96,6 @@ jobs: WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} RELEASE_GPG_PRIVATE_KEY: ${{ secrets.RELEASE_GPG_PRIVATE_KEY }} - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} steps: - name: Harden Runner uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 diff --git a/.github/workflows/test-build-docker.yml b/.github/workflows/test-build-docker.yml index b6a081bb1e..f8344da116 100644 --- a/.github/workflows/test-build-docker.yml +++ b/.github/workflows/test-build-docker.yml @@ -12,19 +12,16 @@ on: required: false type: string default: "false" - depot_cores: - description: "Depot runner vCPU count (used in runs-on). Override for benchmarking." + dockerfiles-changed: + description: "Whether any Dockerfile changed (forwarded from files-changed). Gates the slow arm64 build leg." required: false type: string - default: "8" + default: "false" permissions: contents: read jobs: - pick: - uses: ./.github/workflows/_runner-pick.yml - # TODO: extract a pre-matrix `prepare` job that runs once and produces # shared artifacts for the three matrix entries below to consume: # 1. `task backend:build` — currently runs 3× in parallel with @@ -40,14 +37,7 @@ jobs: # spring-security=true matrix entry if `task backend:build` and # `task backend:build:ci` produce equivalent JARs (verify before wiring). test-build-docker-images: - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || format('depot-ubuntu-24.04-{0}', inputs.depot_cores || '8') }} - permissions: - contents: read - id-token: write - env: - USE_DEPOT: ${{ needs.pick.outputs.is_fork != 'true' && inputs.docker-base-changed != 'true' }} - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} + runs-on: ubuntu-latest strategy: fail-fast: false matrix: @@ -95,7 +85,7 @@ jobs: distribution: "temurin" - name: Cache Gradle dependency artifacts - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | ~/.gradle/wrapper @@ -104,9 +94,9 @@ jobs: key: gradle-deps-${{ runner.os }}-jdk-25-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties', '**/*.gradle', '**/*.gradle.kts', 'settings.gradle', 'settings.gradle.kts', 'gradle/libs.versions.toml') }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 + uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 with: - gradle-version: 9.6.0 + gradle-version: 9.6.1 cache-disabled: true - name: Install Task @@ -120,16 +110,10 @@ jobs: DISABLE_ADDITIONAL_FEATURES: true STIRLING_PDF_DESKTOP_UI: false - - name: Set up Depot CLI - if: env.USE_DEPOT == 'true' - uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.0.0 - - name: Set up QEMU - if: env.USE_DEPOT != 'true' uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 - name: Set up Docker Buildx - if: env.USE_DEPOT != 'true' id: buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 @@ -146,13 +130,22 @@ jobs: # GITHUB_EVENT_NAME is already provided by the runner. env: DOCKER_BASE_CHANGED: ${{ inputs.docker-base-changed }} + DOCKERFILES_CHANGED: ${{ inputs.dockerfiles-changed }} run: | if [ "$GITHUB_EVENT_NAME" = "pull_request" ] && [ "$DOCKER_BASE_CHANGED" = "true" ]; then + # Base Dockerfile changed: build against the locally-built base, + # which only exists for amd64. echo "base_image=stirling-pdf-base:pr-test" >> "$GITHUB_OUTPUT" echo "platforms=linux/amd64" >> "$GITHUB_OUTPUT" - else + elif [ "$DOCKERFILES_CHANGED" = "true" ]; then + # A Dockerfile changed: also verify the arm64 build (slow QEMU leg). echo "base_image=stirlingtools/stirling-pdf-base:latest" >> "$GITHUB_OUTPUT" echo "platforms=linux/amd64,linux/arm64/v8" >> "$GITHUB_OUTPUT" + else + # No Dockerfile change: amd64 only. arm64 is exercised on the base + # image publish and on release, not on every code PR. + echo "base_image=stirlingtools/stirling-pdf-base:latest" >> "$GITHUB_OUTPUT" + echo "platforms=linux/amd64" >> "$GITHUB_OUTPUT" fi # Base-changed PRs build the embedded image with the local docker driver @@ -168,25 +161,11 @@ jobs: --tag stirling-pdf-embedded:pr-test \ . - - name: Build ${{ matrix.docker-rev }} (Depot) - if: env.USE_DEPOT == 'true' - uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1.16.0 - with: - project: ${{ vars.DEPOT_PROJECT_ID }} - context: . - file: ./${{ matrix.docker-rev }} - push: false - platforms: ${{ steps.build-params.outputs.platforms }} - build-args: | - BASE_IMAGE=${{ steps.build-params.outputs.base_image }} - provenance: true - sbom: true - - # Fork PRs that did NOT change the base use the buildx container builder + # PRs that did NOT change the base use the buildx container builder # (multi-platform + gha cache) against the published base image. - - name: Build ${{ matrix.docker-rev }} (Docker fork fallback) - if: env.USE_DEPOT != 'true' && inputs.docker-base-changed != 'true' - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + - name: Build ${{ matrix.docker-rev }} + if: inputs.docker-base-changed != 'true' + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: builder: ${{ steps.buildx.outputs.name }} context: . @@ -213,14 +192,7 @@ jobs: if-no-files-found: warn test-build-unoserver-image: - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || format('depot-ubuntu-24.04-{0}', inputs.depot_cores || '8') }} - permissions: - contents: read - id-token: write - env: - USE_DEPOT: ${{ needs.pick.outputs.is_fork != 'true' && inputs.docker-base-changed != 'true' }} - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} + runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 @@ -230,36 +202,15 @@ jobs: - name: Checkout Repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Set up Depot CLI - if: env.USE_DEPOT == 'true' - uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.0.0 - - name: Set up QEMU - if: env.USE_DEPOT != 'true' uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 - name: Set up Docker Buildx - if: env.USE_DEPOT != 'true' id: buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - name: Build docker/unoserver/Dockerfile (Depot) - if: env.USE_DEPOT == 'true' - uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1.16.0 - with: - project: ${{ vars.DEPOT_PROJECT_ID }} - context: . - file: ./docker/unoserver/Dockerfile - push: false - load: true - platforms: linux/amd64 - tags: stirling-unoserver:pr-test - provenance: false - sbom: false - - - name: Build docker/unoserver/Dockerfile (Docker fork fallback) - if: env.USE_DEPOT != 'true' - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + - name: Build docker/unoserver/Dockerfile + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: builder: ${{ steps.buildx.outputs.name }} context: . diff --git a/.github/workflows/testdriver.yml b/.github/workflows/testdriver.yml index e9774694e7..f86191656f 100644 --- a/.github/workflows/testdriver.yml +++ b/.github/workflows/testdriver.yml @@ -20,19 +20,9 @@ permissions: contents: read jobs: - pick: - uses: ./.github/workflows/_runner-pick.yml - deploy: if: ${{ vars.CI_PROFILE != 'lite' }} - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} - permissions: - contents: read - id-token: write - env: - USE_DEPOT: ${{ needs.pick.outputs.is_fork != 'true' }} - DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }} + runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 @@ -49,9 +39,9 @@ jobs: distribution: "temurin" - name: Setup Gradle - uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 + uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 with: - gradle-version: 9.6.0 + gradle-version: 9.6.1 - name: Build with Gradle run: ./gradlew build @@ -61,12 +51,7 @@ jobs: MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }} DISABLE_ADDITIONAL_FEATURES: true - - name: Set up Depot CLI - if: env.USE_DEPOT == 'true' - uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.0.0 - - name: Set up Docker Buildx - if: env.USE_DEPOT != 'true' uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Get version number @@ -81,21 +66,8 @@ jobs: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_API }} - - name: Build and push test image (Depot) - if: env.USE_DEPOT == 'true' - uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1.16.0 - with: - project: ${{ vars.DEPOT_PROJECT_ID }} - context: . - file: ./docker/embedded/Dockerfile - push: true - tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:test-${{ github.sha }} - build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }} - platforms: linux/amd64 - - - name: Build and push test image (Docker fork fallback) - if: env.USE_DEPOT != 'true' - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + - name: Build and push test image + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: . file: ./docker/embedded/Dockerfile @@ -153,8 +125,7 @@ jobs: files-changed: if: always() name: detect what files changed - needs: pick - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} + runs-on: ubuntu-latest timeout-minutes: 3 outputs: frontend: ${{ steps.changes.outputs.frontend }} @@ -174,8 +145,8 @@ jobs: test: if: needs.files-changed.outputs.frontend == 'true' - needs: [pick, deploy, files-changed] - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} + needs: [deploy, files-changed] + runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 @@ -208,8 +179,8 @@ jobs: FORCE_COLOR: "3" cleanup: - needs: [pick, deploy, test] - runs-on: ${{ needs.pick.outputs.is_fork == 'true' && 'ubuntu-latest' || 'depot-ubuntu-24.04-4' }} + needs: [deploy, test] + runs-on: ubuntu-latest if: always() steps: diff --git a/settings.gradle b/settings.gradle index c712f672a1..85b1c45207 100644 --- a/settings.gradle +++ b/settings.gradle @@ -23,29 +23,6 @@ plugins { id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' } -// Depot remote build cache. Silently no-ops when DEPOT_TOKEN is absent -// (local dev without depot login, and fork PRs where GitHub hides secrets), -// so contributors without Depot access still build fine on local cache only. -buildCache { - def depotToken = System.getenv('DEPOT_TOKEN') - local { - enabled = true - } - if (depotToken) { - remote(HttpBuildCache) { - url = 'https://cache.depot.dev' - enabled = true - // Only CI runs push to the shared cache; dev laptops pull-only - // so a misconfigured local task can't poison everyone else. - push = System.getenv('CI') == 'true' - credentials { - username = '' - password = depotToken - } - } - } -} - rootProject.name = 'Stirling PDF' // Flavors: core | proprietary (default) | saas.