持续集成

pnpm 可以很容易地用于各种持续集成系统。

安装 pnpm#

在 GitHub Actions 之外(它有专属的 action),通过独立脚本安装 pnpm:

curl -fsSL https://get.pnpm.io/install.sh | sh -

在 CI 中,有两点让这变得方便:

  • **它不需要 Node.js。**pnpm 是一个自包含的可执行文件,并且它可以在后续通过 pnpm runtime set node lts -g 为你安装运行时环境,因此一个任务从一开始就不需要依赖 Node.js 镜像。
  • **它选择自己的版本。**如果你的 package.json 有一个 packageManager 或者 devEngines.packageManager 字段,pnpm 会在首次使用时切换到那个版本,因此流水线不在两个地方固定版本。

这个脚本安装到 PNPM_HOME 并追加到 shell 配置文件中,而一个 CI 任务永远不会重新加载。自行声明 PNPM_HOME 并将 $PNPM_HOME/bin 添加到 PATH 中,具体方式根据平台提供的机制而定——下面的示例分别展示了各平台的实现方法。

注意

此页面的早期版本使用了 Corepack。 Corepack 安装了一个 JavaScript 垫片(shim)代替 pnpm,因此每次调用 pnpm 都会在 pnpm 本身启动前启动 Node.js 来运行垫片,每次调用都要付出代价,而 CI 的任务有许多这种代价。安装 pnpm 本身完全避免了这个问题。

注意

在所有提供的配置文件中,存储都被缓存。但是,这不是必需的,并且不能保证缓存的存储会使安装速度更快。因此,不必在作业中缓存 pnpm 存储。

Cache the metadata cache too

Since v11.22.0, pnpm cache path prints the directory pnpm uses for its metadata cache, so a job can cache it without mirroring pnpm's own path resolution. That directory also holds the lockfile verification log, which lets a job skip re-checking an unchanged lockfile against the configured supply-chain policies — the dominant cost of an install in CI once the store is warm.

Important

仅将 pnpm 的 store 和 cache 目录缓存到受信任作业具有写入权限的位置。切勿让不受信任的 CI 任务向存储或元数据缓存写入数据,以免受信任的任务随后将其恢复。这些目录是受信任的缓存;有关详细信息,请参阅 storeDir 和 cacheDir 设置。

CI 中的锁文件行为

当 pnpm 检测到它在 CI 中运行时,它会自动切换到 frozen-lockfile 模式。自 v11 版本以来,pnpm 在 CI 中遇到不兼容的 lockfile 时也会失败——如果 lockfile 是由较新的 pnpm 主版本写入的,则安装会出错,而不是默默地重写它。将你的 CI pnpm 版本升级到与生成 lockfile 时使用的版本一致。

AppVeyor#

在 AppVeyor上,你可以使用 pnpm 安装依赖项,只需将此 添加到你的 appveyor.yml中即可:

appveyor.yml
environment:
  PNPM_HOME: C:\pnpm

install:
  - ps: $env:PATH = "$env:PNPM_HOME;$env:PNPM_HOME\bin;$env:PATH"
  - ps: Invoke-WebRequest https://get.pnpm.io/install.ps1 -UseBasicParsing | Invoke-Expression
  - ps: pnpm install

pnpm 有自己的运行时,因此 Install-Product node 不再需要了。如果你的构建脚本直接调用 node,添加 pnpm runtime set node lts -g 命令。

Azure Pipelines#

在 Azure pipeline 中,你可以将以下内容添加到 Azure-Pipelines.yml 中,使用 pnpm 安装和缓存依赖项:

azure-pipelines.yml
variables:
  pnpm_config_cache: $(Pipeline.Workspace)/.pnpm-store
  PNPM_HOME: $(Pipeline.Workspace)/.pnpm

steps:
  - task: Cache@2
    inputs:
      key: 'pnpm | "$(Agent.OS)" | pnpm-lock.yaml'
      path: $(pnpm_config_cache)
    displayName: Cache pnpm

  - script: |
      curl -fsSL https://get.pnpm.io/install.sh | sh -
      echo "##vso[task.prependpath]$(PNPM_HOME)/bin"
      "$(PNPM_HOME)/bin/pnpm" config set store-dir $(pnpm_config_cache)
    displayName: "Setup pnpm"

  - script: |
      pnpm install
      pnpm run build
    displayName: "pnpm install and build"

task.prependpath 为后续的步骤将 pnpm 放到 PATH 中;在安装 pnpm 的步骤中,通过完整路径调用它。

Bitbucket 流水线#

你可以使用 pnpm 来安装和缓存你的依赖项:

.bitbucket-pipelines.yml
definitions:
  caches:
    pnpm: $BITBUCKET_CLONE_DIR/.pnpm-store

pipelines:
  pull-requests:
    "**":
      - step:
          name: 构建和测试
          image: debian:stable-slim
          script:
            - apt-get update && apt-get install -y --no-install-recommends ca-certificates curl libatomic1 libstdc++6
            - export PNPM_HOME="$HOME/.local/share/pnpm"
            - export PATH="$PNPM_HOME/bin:$PATH"
            - curl -fsSL https://get.pnpm.io/install.sh | sh -
            - pnpm config set store-dir "$BITBUCKET_CLONE_DIR/.pnpm-store"
            - pnpm runtime set node lts -g
            - pnpm install
            - pnpm run build # Replace with your build/test…etc. commands
          caches:
            - pnpm

一个步骤中的所有行都在同一个 shell 中运行,因此 export 会传递给后面的行,并且 store-dir 指向缓存保存的目录。

这个示例从一个空白镜像开始,并且让 pnpm 安装 Node.js。一个最小的镜像需要包含 pnpm 11 可执行文件所依赖的库,即 libatomic1 和 libstdc++6,这类库在 node:24 等镜像中已自带;详见 Linux 运行时要求。 pnpm 12 采用静态链接方式,无需依赖上述库。如果你不想修改你目前的 node 镜像,保留它并且移除 apt-get 和 pnpm runtime set 这几行。

CircleCI#

在 CircleCI 中,你可以将以下内容添加到 .circleci/config.yml 中,使用 pnpm 安装和缓存依赖项:

.circleci/config.yml
version: 2.1

jobs:
  build: # 这可以是你选择的任何名称
    docker:
      - image: node:24
    resource_class: large
    parallelism: 10
    environment:
      PNPM_HOME: /root/.local/share/pnpm

    steps:
      - checkout
      - restore_cache:
          name: 恢复 pnpm 包缓存
          keys:
            - pnpm-packages-{{ checksum "pnpm-lock.yaml" }}
      - run:
          name: 安装 pnpm 包管理器
          command: |
            curl -fsSL https://get.pnpm.io/install.sh | sh -
            echo 'export PATH="$PNPM_HOME/bin:$PATH"' >> "$BASH_ENV"
            "$PNPM_HOME/bin/pnpm" config set store-dir .pnpm-store
      - run:
          name: 安装依赖项
          command: |
            pnpm install
      - save_cache:
          name: 保存 pnpm 包缓存
          key: pnpm-packages-{{ checksum "pnpm-lock.yaml" }}
          paths:
            - .pnpm-store

CircleCI 会在每个步骤执行前加载 $BASH_ENV,pnpm 正是借此在后续步骤中生效的。

GitHub Actions#

在 GitHub Actions 上,你可以像这样使用 pnpm 安装和缓存你的依赖项(属于 .github/workflows/NAME.yml):

.github/workflows/NAME.yml
name: pnpm Example Workflow
on:
  push:

jobs:
  build:
    runs-on: ubuntu-24.04
    strategy:
      matrix:
        node-version: [24]
    steps:
      - uses: actions/checkout@v6
      - name: 安装 pnpm 和 Node.js
        uses: pnpm/setup@c9883cc79df532ad1a7b81bf9ab944ceb090d65c # v2.0.0
        with:
          runtime: node@${{ matrix.node-version }}
          cache: true

pnpm/setup 会安装 pnpm,然后用它安装指定的运行时,因此不需要单独的 actions/setup-node 步骤。它也会为你运行 pnpm install,并且 cache: true 会在不同运行之间缓存 pnpm 存储。如果你想自己安装依赖项,设置 install: false。

pnpm 版本取自 package.json 中的 packageManager 或 devEngines.packageManager 字段,因此当你更改该版本时,无需更新工作流。如果你的 package.json 中没有声明任一字段,添加一个 version 输入。

GitLab CI#

在 Gitlab 上,您像这样可以使用 pnpm 来安装和缓存你的依赖项 (在 .gitlab-ci.yml 中):

.gitlab-ci.yml
stages:
  - build

build:
  stage: build
  image: node:24.14.1
  variables:
    PNPM_HOME: "$CI_PROJECT_DIR/.pnpm"
  before_script:
    - export PATH="$PNPM_HOME/bin:$PATH"
    - curl -fsSL https://get.pnpm.io/install.sh | sh -
    - pnpm config set store-dir .pnpm-store
  script:
    - pnpm install # 安装依赖项
  cache:
    key:
      files:
        - pnpm-lock.yaml
    paths:
      - .pnpm-store

Jenkins#

你可以使用 pnpm 来安装和缓存你的依赖项:

pipeline {
    agent {
        docker {
            image 'node:lts-bookworm-slim'
            args '-p 3000:3000'
        }
    }
    environment {
        PNPM_HOME = "${WORKSPACE}/.pnpm"
        PATH = "${PNPM_HOME}/bin:${PATH}"
    }
    stages {
        stage('Build') {
            steps {
                sh 'curl -fsSL https://get.pnpm.io/install.sh | sh -'
                sh 'pnpm install'
            }
        }
    }
}

每个 sh 步骤在它自己的 shell 中运行,因此 PATH 在流水线的 environment 块中设置,而不是在步骤内导出。

Semaphore#

在 Semaphore 上,你可以通过 pnpm 安装并缓存依赖项,只需将此添加到你的 .semaphore/semaphore.yml 文件中:

.semaphore/semaphore.yml
version: v1.0
name: Semaphore CI pnpm 示例
agent:
  machine:
    type: e1-standard-2
    os_image: ubuntu2404
blocks:
  - name: 安装依赖项
    task:
      env_vars:
        - name: PNPM_HOME
          value: /home/semaphore/.local/share/pnpm
      jobs:
        - name: pnpm install
          commands:
            - export PATH="$PNPM_HOME/bin:$PATH"
            - curl -fsSL https://get.pnpm.io/install.sh | sh -
            - checkout
            - cache restore node-$(checksum pnpm-lock.yaml)
            - pnpm install
            - cache store node-$(checksum pnpm-lock.yaml) $(pnpm store path)

Travis#

在 Travis CI上,你可以使用 pnpm 安装依赖项,方法是将此 添加到你的 .travis.yml 文件中:

.travis.yml
cache:
  npm: false
  directories:
    - "~/.pnpm-store"
env:
  global:
    - PNPM_HOME="$HOME/.local/share/pnpm"
    - PATH="$PNPM_HOME/bin:$PATH"
before_install:
  - curl -fsSL https://get.pnpm.io/install.sh | sh -
  - pnpm config set store-dir ~/.pnpm-store
install:
  - pnpm install