diff --git a/.github/actions/calculate-prefetch-matrix/action.yml b/.github/actions/calculate-prefetch-matrix/action.yml new file mode 100644 index 0000000000000000000000000000000000000000..18bc88e2cf16a1ff8a4fa19a4cab31680b942211 --- /dev/null +++ b/.github/actions/calculate-prefetch-matrix/action.yml @@ -0,0 +1,81 @@ +# This is the composite action: +# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action +# +# Composite actions have some limitations: +# - many contexts are unavailable, e.g. `runner` +# - `env` can be specified per-step only +# - if `run` is used, `shell` must be explicitly specified +name: 'Calculate matrix for `node_modules` prefetch' +inputs: + repo: + description: 'Repository name' + required: true + token: + description: 'GitHub token' + required: true + node-version: + description: 'Node version' + required: true + hash: + description: 'Yarn lock hash' + required: true +outputs: + matrix: + description: 'Matrix of OSes to prefetch `node_modules` for' + value: ${{ steps.os-matrix-prefetch.outputs.os-matrix-prefetch }} +runs: + using: 'composite' + steps: + - name: Calculate cache keys + id: cache-keys + env: + LOCK_HASH: ${{ steps.yarn-lock-hash.outputs.yarn-lock-hash }} + shell: bash + run: | + echo 'macos-cache-key=node-modules-macOS-${{ inputs.node-version }}-${{ inputs.hash }}' >> "$GITHUB_OUTPUT" + echo 'windows-cache-key=node-modules-Windows-${{ inputs.node-version }}-${{ inputs.hash }}' >> "$GITHUB_OUTPUT" + + - name: Fetch available cache keys + id: caches-list + env: + GH_TOKEN: ${{ inputs.token }} + GH_REPO: ${{ inputs.repo }} + CACHES_URL: /repos/{owner}/{repo}/actions/caches + JQ_FILTER: >- + "keys=" + ([.[].actions_caches[].key] | tostring) + shell: bash + run: | + gh api ${{ env.CACHES_URL }} --paginate | jq -rcs '${{ env.JQ_FILTER }}' >> "$GITHUB_OUTPUT" + + - name: Calculate cache misses for Windows and MacOS + id: modules-caches + env: + ACTIONS_CACHE_KEYS: ${{ steps.caches-list.outputs.keys }} + MACOS_CACHE_KEY: ${{ steps.cache-keys.outputs.macos-cache-key }} + WINDOWS_CACHE_KEY: ${{ steps.cache-keys.outputs.windows-cache-key }} + shell: bash + run: | + echo 'macos=${{ + !contains(fromJSON(env.ACTIONS_CACHE_KEYS), env.MACOS_CACHE_KEY) && 'true' || '' + }}' >> "$GITHUB_OUTPUT" + echo 'windows=${{ + !contains(fromJSON(env.ACTIONS_CACHE_KEYS), env.WINDOWS_CACHE_KEY) && 'true' || '' + }}' >> "$GITHUB_OUTPUT" + + - name: Dispatch `node_modules` prefetch for MacOS and Windows + id: os-matrix-prefetch + env: + MACOS_MISS: ${{ steps.modules-caches.outputs.macos }} + WINDOWS_MISS: ${{ steps.modules-caches.outputs.windows }} + PREFETCH_MAC_ONLY: '["macos-latest"]' + PREFETCH_WINDOWS_ONLY: '["windows-latest"]' + PREFETCH_BOTH: '["macos-latest", "windows-latest"]' + PREFETCH_FALLBACK: '["ubuntu-latest"]' + shell: bash + run: | + echo 'os-matrix-prefetch=${{ + (env.OS_MATRIX_IS_FULL && env.WINDOWS_MISS && env.MACOS_MISS && env.PREFETCH_BOTH) || + (env.OS_MATRIX_IS_FULL && env.MACOS_MISS && env.PREFETCH_MAC_ONLY) || + (env.OS_MATRIX_IS_FULL && env.WINDOWS_MISS && env.PREFETCH_WINDOWS_ONLY) || + env.PREFETCH_FALLBACK + }}' >> "$GITHUB_OUTPUT" diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml index 7327949214e1a854146468aaec001c78dd59324f..3b22954036a1e6e66b7245aa308dfc30347c29d3 100644 --- a/.github/actions/setup-node/action.yml +++ b/.github/actions/setup-node/action.yml @@ -40,8 +40,8 @@ runs: with: timeout_minutes: 10 max_attempts: 3 - command: yarn --silent install --frozen-lockfile --ignore-scripts + command: yarn install --frozen-lockfile --ignore-scripts - name: Run scripts shell: bash - run: yarn --silent prepare + run: yarn prepare diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 87832d462a4e1b2dc1ff430054f5c410b82236c5..5f38e3a20937d24cb47569db4ffcdb66b08528e8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,6 +27,12 @@ env: DEFAULT_BRANCH: main NODE_VERSION: 18 DRY_RUN: true + SPARSE_CHECKOUT: |- + .github/actions/ + data/ + tools/ + package.json + yarn.lock jobs: setup: @@ -34,6 +40,8 @@ jobs: outputs: os-matrix: ${{ steps.os-matrix.outputs.os-matrix }} + os-matrix-is-full: ${{ steps.os-matrix-is-full.outputs.os-matrix-is-full }} + os-matrix-prefetch: ${{ steps.os-matrix-prefetch.outputs.matrix }} env: # Field required for GitHub CLI @@ -46,27 +54,98 @@ jobs: steps: - name: Fetch PR data - if: ${{ env.PR }} + if: env.PR env: PR_URL: https://api.github.com/repos/{owner}/{repo}/pulls/${{ env.PR }} JQ_FILTER: >- "PR_LABELS=" + ([.labels[].name] | tostring) run: gh api ${{ env.PR_URL }} | jq -rc '${{ env.JQ_FILTER }}' >> "$GITHUB_ENV" - - name: Detect OS matrix + - name: Calculate `CI_FULLTEST` variable + run: | + echo 'CI_FULLTEST=${{ + contains(fromJSON(env.PR_LABELS), 'ci:fulltest') && 'true' || '' + }}' >> "$GITHUB_ENV" + + - name: Calculate `os-matrix-is-full` output + id: os-matrix-is-full + env: + IS_FULL: ${{ (!env.PR || env.CI_FULLTEST) && 'true' || '' }} + run: | + echo 'OS_MATRIX_IS_FULL=${{ env.IS_FULL }}' >> "$GITHUB_ENV" + echo 'os-matrix-is-full=${{ env.IS_FULL }}' >> "$GITHUB_OUTPUT" + + - name: Calculate `os-matrix` output id: os-matrix env: - CI_FULLTEST: >- - ${{ contains(fromJSON(env.PR_LABELS), 'ci:fulltest') && 'true' || '' }} OS_ALL: '["ubuntu-latest", "macos-latest", "windows-latest"]' OS_LINUX_ONLY: '["ubuntu-latest"]' - run: >- + run: | echo 'os-matrix=${{ - (!env.PR || env.CI_FULLTEST) && env.OS_ALL || env.OS_LINUX_ONLY + env.OS_MATRIX_IS_FULL && env.OS_ALL || env.OS_LINUX_ONLY }}' >> "$GITHUB_OUTPUT" + - name: Checkout code + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + with: + sparse-checkout: ${{ env.SPARSE_CHECKOUT }} + + - name: Calculate `yarn-lock-hash` output + id: yarn-lock-hash + run: | + echo 'yarn-lock-hash=${{ hashFiles('yarn.lock') }}' >> "$GITHUB_OUTPUT" + + - name: Calculate matrix for `node_modules` prefetch + uses: ./.github/actions/calculate-prefetch-matrix + id: os-matrix-prefetch + with: + repo: ${{ env.GH_REPO }} + token: ${{ env.GH_TOKEN }} + node-version: ${{ env.NODE_VERSION }} + hash: ${{ steps.yarn-lock-hash.outputs.yarn-lock-hash }} + + - name: Prefetch modules for `ubuntu-latest` + id: setup-node + uses: ./.github/actions/setup-node + with: + node-version: ${{ env.NODE_VERSION }} + os: ${{ runner.os }} + + prefetch: + needs: [setup] + # We can't use `if: needs.setup.outputs.os-matrix-is-full` here, + # as it will lead to further complications that aren't solvable + # with current GitHub Actions feature set. + # + # Although this job sometimes may act as short-lived `no-op`, + # it's actually the best option available. + + strategy: + matrix: + os: ${{ fromJSON(needs.setup.outputs.os-matrix-prefetch) }} + node-version: [18] + + runs-on: ${{ matrix.os }} + + timeout-minutes: 10 + + steps: + - name: Checkout code + if: needs.setup.outputs.os-matrix-is-full && runner.os != 'Linux' + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + with: + sparse-checkout: ${{ env.SPARSE_CHECKOUT }} + + - name: Setup Node.js + if: needs.setup.outputs.os-matrix-is-full && runner.os != 'Linux' + uses: ./.github/actions/setup-node + with: + node-version: ${{ env.NODE_VERSION }} + os: ${{ runner.os }} + test: - needs: setup + needs: [setup, prefetch] + name: ${{ matrix.node-version == 18 && format('test ({0})', matrix.os) || format('test ({0}, node-{1})', matrix.os, matrix.node-version) }} runs-on: ${{ matrix.os }} @@ -125,6 +204,7 @@ jobs: run: yarn test-e2e lint: + needs: [setup] runs-on: ubuntu-latest # lint shouldn't need more than 10 min