Skip to content

Instantly share code, notes, and snippets.

@igorjs
Forked from belgattitude/ci-yarn-install.md
Created October 24, 2022 13:45
Show Gist options
  • Save igorjs/361e1ef83d10117f6a306fb74218b37b to your computer and use it in GitHub Desktop.
Save igorjs/361e1ef83d10117f6a306fb74218b37b to your computer and use it in GitHub Desktop.
Composite github action to improve CI time with yarn 3+ / node-modules linker.

Not using yarn ? see the corresponding pnpm action gist

Why

While @setup/node has a built-in cache parameter for popular package managers, it discards the cache on every lock file update. This composite action allows to run install with (almost always) warm cache. Depending on repo usage, that might reduces the monthly ci-time and decrease the carbon emissions. See also actions/setup-node#325.

Bench

Based on the nextjs-monorepo-example. A cold cache install on the ci is more than 2 minutes. With warmed cache: 1 minute. Crafted from benchmarks results in https://gist.github.com/belgattitude/0ecd26155b47e7be1be6163ecfbb0f0b. Depending on repo (renovatebot...), the slight complexity increase in ci setup might worth it.

Structure

.
└── .github
    ├── actions
    │   └── yarn-nm-install/action.yml (composite action)    
    └── workflows
        └── ci.yml (uses: ./.github/actions/yarn-nm-install)    

Composite action

Create a file in .github/actions/yarn-nm-install/action.yml and paste

########################################################################################
# "yarn install" composite action for yarn 2/3/4+ and "nodeLinker: node-modules"       #
#--------------------------------------------------------------------------------------#
# Cache:                                                                               #
#   - Downloaded zip archive (multi-arch, preserved across yarn.lock changes)          #
#   - Yarn install state (discarded on yarn.lock changes)                              #
# References:                                                                          #
#   - bench: https://gist.github.com/belgattitude/0ecd26155b47e7be1be6163ecfbb0f0b     #
#   - vs @setup/node: https://github.com/actions/setup-node/issues/325                 #
########################################################################################

name: 'Yarn install'
description: 'Run yarn install with node_modules linker and cache enabled'
inputs:
  skip-prisma-postinstall-generate:
    description: 'Avoid prisma to automatically generate schema on postinstall'
    required: false
    default: 'false'

runs:
  using: 'composite'
  steps:
    - name: Expose yarn config as "$GITHUB_OUTPUT"
      id: yarn-config
      shell: bash
      run: |
        echo "CACHE_FOLDER=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT

    # Yarn rotates the downloaded cache archives, @see https://github.com/actions/setup-node/issues/325
    # Yarn cache is also reusable between arch and os.
    - name: Restore yarn cache
      uses: actions/cache@v3
      id: yarn-download-cache
      with:
        path: ${{ steps.yarn-config.outputs.CACHE_FOLDER }}
        key: yarn-download-cache-${{ hashFiles('yarn.lock') }}
        restore-keys: |
          yarn-download-cache-

    # Invalidated on yarn.lock changes
    - name: Restore yarn install state
      id: yarn-install-state-cache
      uses: actions/cache@v3
      with:
        path: .yarn/ci-cache/
        key: ${{ runner.os }}-yarn-install-state-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }}

    - name: Install dependencies
      shell: bash
      run: |
        yarn install --immutable --inline-builds
      env:
        # CI optimizations. Overrides yarnrc.yml options (or their defaults) in the CI action.
        YARN_ENABLE_GLOBAL_CACHE: 'false' # Use local cache folder to keep downloaded archives
        YARN_NM_MODE: 'hardlinks-local' # Hardlinks-(local|global) reduces io / node_modules size
        YARN_INSTALL_STATE_PATH: .yarn/ci-cache/install-state.gz # Very small speedup when lock does not change
        # Other environment variables
        HUSKY: '0' # By default do not run HUSKY install
        PRISMA_SKIP_POSTINSTALL_GENERATE: ${{ inputs.skip-prisma-postinstall-generate }}       

Workflow action

To use it in the workflows

    steps:
      - uses: actions/checkout@v3

      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}

      - name: 📥 Monorepo install
        uses: ./.github/actions/yarn-nm-install

yarnrc.yml

# .yarnrc.yml
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.0.0-rc.24.cjs # or v3...

Results

On install, when only few deps changed

image

Cost of action/cache compression

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment