name: "pull-request" on: workflow_call: inputs: dry-run-flag: description: "enable dry-run on artifact push" required: false type: boolean default: true devel: description: "devel" required: false type: boolean default: false cron-run: description: "disables jobs which should not run when cron runs e2es" required: false type: boolean default: false pull_request: types: [opened, synchronize, reopened] branches: - develop - master - develop-patch* - master-patch* concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: GH_COMMIT: ${{ github.sha }} NODE_OPTIONS: "--max-old-space-size=5120" jobs: pre-checks: runs-on: ubuntu-latest outputs: code-changed: ${{ steps.path-filter.outputs.code-changed }} steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Ensure SHA pinned actions uses: hyland/github-actions-ensure-sha-pinned-actions@7957efb76aba0eec7580a9c0392d3e5bec381359 # v2.0.1 - name: Check pnpm-lock.yaml version run: | if [[ -f "pnpm-lock.yaml" ]]; then LOCKFILE_VERSION=$(grep "^lockfileVersion:" pnpm-lock.yaml | cut -d"'" -f2) if [[ "$LOCKFILE_VERSION" == "9.0" ]]; then echo "pnpm-lock.yaml has correct version: $LOCKFILE_VERSION" else echo "pnpm-lock.yaml must be version 9.0, found: $LOCKFILE_VERSION" exit 1 fi else echo "pnpm-lock.yaml is missing" exit 1 fi - name: Detect code changes id: path-filter env: GH_TOKEN: ${{ github.token }} run: | if [ "${{ github.event_name }}" != "pull_request" ]; then echo "Not a PR event — assuming code changed" echo "code-changed=true" >> $GITHUB_OUTPUT exit 0 fi FILES=$(gh api /repos/$GITHUB_REPOSITORY/pulls/${{ github.event.pull_request.number }}/files --paginate --jq '.[].filename') CODE_CHANGED="false" while IFS= read -r file; do case "$file" in *.md|docs/*|.github/*.md|.github/CODEOWNERS|.github/dependabot.yml|LICENSE*|NOTICE*|.editorconfig|.gitattributes) ;; *) CODE_CHANGED="true" break ;; esac done <<< "$FILES" echo "code-changed=$CODE_CHANGED" >> $GITHUB_OUTPUT echo "Code changed: $CODE_CHANGED" check-if-pr-is-approved: runs-on: ubuntu-latest steps: - name: Checkout repository if: ${{ github.event_name == 'pull_request' }} uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 1 - name: Check if PR approval can be skipped id: skip-check env: EVENT_NAME: ${{ github.event_name }} ACTOR: ${{ github.actor }} DEVEL_FLAG: ${{ inputs.devel }} PR_TITLE: ${{ github.event.pull_request.title }} PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} run: | skip="false" # Get commit message safely from git to avoid script injection if [ "$EVENT_NAME" == "pull_request" ] && [ -n "$PR_HEAD_SHA" ]; then COMMIT_MESSAGE=$(git log -1 --format=%B "$PR_HEAD_SHA" 2>/dev/null || echo "") else COMMIT_MESSAGE=$(git log -1 --format=%B 2>/dev/null || echo "") fi if [ "$EVENT_NAME" == "schedule" ] || [ "$EVENT_NAME" == "workflow_dispatch" ]; then echo -e "\033[32mSchedule/dispatch event — skipping approval check\033[0m" skip="true" elif [ "$ACTOR" == "dependabot[bot]" ] || [ "$ACTOR" == "alfresco-build" ]; then echo -e "\033[32mCommit by $ACTOR — skipping approval check\033[0m" skip="true" elif echo "$PR_TITLE" | grep -qF "[ci:force]"; then echo -e "\033[32m[ci:force] flag detected in PR title — skipping approval check\033[0m" skip="true" elif echo "$COMMIT_MESSAGE" | grep -qF "[ci:force]"; then echo -e "\033[32m[ci:force] flag detected in commit message — skipping approval check\033[0m" skip="true" elif [[ "$DEVEL_FLAG" == "true" ]]; then echo -e "\033[32mDevel flag — skipping approval check\033[0m" skip="true" fi echo "skip=$skip" >> $GITHUB_OUTPUT - name: Get PR number if: ${{ steps.skip-check.outputs.skip != 'true' }} id: pr-number env: GH_TOKEN: ${{ github.token }} run: | PR_NUMBER="${{ github.event.pull_request.number }}" if [ -z "$PR_NUMBER" ]; then PR_NUMBER=$(gh pr view --json number --jq '.number' 2>/dev/null || echo "") fi echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT echo "PR: $PR_NUMBER" - name: Check if PR is approved if: ${{ steps.skip-check.outputs.skip != 'true' && steps.pr-number.outputs.pr_number != '' }} env: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ steps.pr-number.outputs.pr_number }} run: | echo "Checking approval for PR: $PR_NUMBER" checkApproval=$(gh api /repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/reviews | jq '.[] | select(.state == "APPROVED") | .user.login') if [[ $checkApproval ]]; then echo -e "\033[32mPR approved\033[0m" else echo -e "\033[31mPR NOT approved\033[0m" exit 1 fi setup: timeout-minutes: 30 name: "Setup" runs-on: ubuntu-latest needs: [check-if-pr-is-approved, pre-checks] if: ${{ needs.pre-checks.outputs.code-changed == 'true' }} steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 - name: Setup environment id: setup-env uses: ./.github/actions/setup with: cache-suffix: setup - name: Bundle run: | pnpm bundle:js-api pnpm bundle:cli - name: Save nx cache if: ${{ success() }} uses: ./.github/actions/save-nx-cache with: cache-suffix: setup lint: timeout-minutes: 30 name: "Lint" runs-on: ubuntu-latest needs: [setup] steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 - name: Setup environment id: setup-env uses: ./.github/actions/setup with: cache-suffix: lint full-setup: 'false' - name: Run lint env: BASE_REF: ${{ github.base_ref || 'develop' }} run: pnpm nx affected --target=lint --base=origin/$BASE_REF --head=HEAD - name: Save nx cache if: ${{ success() }} uses: ./.github/actions/save-nx-cache with: cache-suffix: lint trigger-build: name: "Build Libs" needs: [setup] uses: ./.github/workflows/build-lib-workflow.yml with: base_ref: ${{ github.base_ref || 'develop' }} build-storybook: timeout-minutes: 30 name: "Build Storybook" needs: [setup] runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 - name: Setup environment id: setup-env uses: ./.github/actions/setup with: cache-suffix: storybook full-setup: 'false' - name: Build Storybook env: BASE_REF: ${{ github.base_ref || 'develop' }} run: | pnpm nx affected --target=build-storybook --base=origin/$BASE_REF --head=HEAD --configuration=ci - name: Save nx cache if: ${{ success() }} uses: ./.github/actions/save-nx-cache with: cache-suffix: storybook trigger-unit-tests: name: "Unit Tests" needs: [setup] uses: ./.github/workflows/unit-test-workflow.yml with: base_ref: ${{ github.base_ref || 'develop' }} PR-size-check: if: ${{ github.event_name == 'pull_request' }} runs-on: ubuntu-latest steps: - name: Check PR size uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { data: pr } = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number, }); const additions = pr.additions; const deletions = pr.deletions; const totalChanges = additions + deletions; const changedFiles = pr.changed_files; let size = 'S'; if (totalChanges > 1000 || changedFiles > 30) size = 'XL'; else if (totalChanges > 500 || changedFiles > 20) size = 'L'; else if (totalChanges > 200 || changedFiles > 10) size = 'M'; core.summary .addHeading('PR Size: ' + size, 3) .addTable([ [{data: 'Metric', header: true}, {data: 'Count', header: true}], ['Files changed', String(changedFiles)], ['Additions', '+' + String(additions)], ['Deletions', '-' + String(deletions)], ['Total changes', String(totalChanges)], ]); if (size === 'XL') { core.summary.addRaw('⚠️ This PR is very large. Consider splitting it into smaller PRs for easier review.'); } await core.summary.write(); if (size === 'XL') { core.warning('This PR has ' + totalChanges + ' changes across ' + changedFiles + ' files. Consider splitting it for easier review.'); } PR-forbidden-labels: if: ${{ inputs.cron-run == '' || inputs.cron-run == 'false' }} runs-on: ubuntu-latest steps: - name: Check for forbidden labels uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { data: issue } = await github.rest.issues.get({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, }); const labels = issue.labels?.map(item => item.name) || []; const forbidden = ['next version ➡️', 'do not merge🙅🏻‍♂️']; if (forbidden.some(l => labels.includes(l))) { core.setFailed('The PR contains a forbidden label! You are not allowed to merge until the label is there.'); } finalize: if: ${{ always() }} runs-on: ubuntu-latest name: Final Results needs: [ check-if-pr-is-approved, pre-checks, setup, trigger-unit-tests, lint, trigger-build, build-storybook, PR-forbidden-labels, ] steps: - name: Check job execution status if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} run: exit 1