Skip to content

pnpm Operations

Manage pnpm (Node.js) packages. See https://pnpm.io/

Operations taking a directory run pnpm inside it rather than under pnpm's --dir, so corepack - which resolves the pnpm version from the working directory - picks up the project's packageManager pin. No _chdir needed.

Facts used in these operations: pnpm.PnpmModulesUpToDate, pnpm.PnpmPackages.

pnpm.install

Install every dependency a project declares, as resolved by its lockfile.

pnpm.install(
         directory: 'str',
         *,
         frozen_lockfile: 'bool | None' = None,
         ignore_scripts: 'bool' = False,
         package_import_method: "Literal['auto', 'clone', 'copy', 'hardlink'] | None" = None,
         **kwargs,
    )
  • directory: project directory holding package.json and pnpm-lock.yaml
  • frozen_lockfile: fail rather than update an outdated lockfile, defaults to pnpm's own behaviour of doing so on CI only
  • ignore_scripts: don't run lifecycle scripts of the project or the installed packages
  • package_import_method: how packages are placed into node_modules from the store, one of auto (default), hardlink, clone or copy

Note: The install is skipped when node_modules was already built from the lockfile now in the directory, so package.json edits not yet written to pnpm-lock.yaml are missed - deploy both files together.

Example:

pnpm.install(
    name="Install app dependencies",
    directory="/opt/app",
    package_import_method="hardlink",
)

Global arguments

This operation also inherits all global arguments.

pnpm.packages

Install/remove/update pnpm packages.

pnpm.packages(
         packages: 'str | list[str] | None' = None,
         present: 'bool' = True,
         latest: 'bool' = False,
         directory: 'str | None' = None,
         dev: 'bool' = False,
         **kwargs,
    )
  • packages: list of packages to ensure
  • present: whether the packages should be present
  • latest: whether to upgrade packages without a specified version
  • directory: directory to manage packages for, defaults to global
  • dev: add the packages as development dependencies, requires directory

Versions: Pin like pnpm: <pkg>@<version>. Only exact versions are compared against the installed ones, so ranges and dist-tags (eg <pkg>@^5) re-apply on every run.

Note: Packages match by name across every dependency group, so one already installed as a (dev/optional) dependency stays where it is rather than moving. latest=True upgrades via pnpm update --latest, ignoring the ranges in package.json; with no local version to compare against the newest published one, it always reports as changed.

Example:

pnpm.packages(
    name="Install typescript for the app",
    packages=["typescript@5.4.5"],
    directory="/opt/app",
    dev=True,
)

Global arguments

This operation also inherits all global arguments.

pnpm.run

Run one of a project's package.json scripts.

pnpm.run(
         script: 'str',
         directory: 'str',
         *,
         args: 'str | list[str] | None' = None,
         if_present: 'bool' = False,
         **kwargs,
    )
  • script: name of the script to run
  • directory: project directory holding package.json
  • args: argument(s) to append to the script's own command line, one per item
  • if_present: succeed quietly when the project has no such script, instead of failing

Not idempotent: the script runs on every deploy. To build only when something new arrived, gate it with _if on whatever delivered the source - the checkout, not the dependency install, which reports no change when only application code moved.

Example:

checkout = git.repo(src="git@github.com:me/app.git", dest="/opt/app", branch=VERSION)
pnpm.install(directory="/opt/app")

pnpm.run(
    name="Build the app",
    script="build",
    directory="/opt/app",
    _if=checkout.did_change,
)

Global arguments

This operation also inherits all global arguments.