Projects and workspaces

A project pin tells jup which package manager to run. jup reads the pin from package.json and supports both the packageManager field and Node's devEngines.packageManager object.

#Pin an exact version

The usual form is:

{
  "packageManager": "pnpm@11.1.2+sha512.abc123..."
}

The suffix contains a digest of the downloaded package manager. jup checks that digest after downloading the file and checks it again when using a cached copy.

You do not need to calculate or enter the digest yourself. Run:

jup use pnpm@11

Although pnpm@11 is a range, jup use resolves it immediately and writes an exact version and digest.

Other accepted selectors include:

jup use pnpm@11.1.2   # exact version
jup use pnpm@^11.0.0  # semantic version range
jup use pnpm@latest   # registry tag
jup use pnpm          # current stable release

Automatic selection excludes prereleases such as 12.0.0-beta.1. To use one, request it explicitly, provide a range that includes prereleases, or set JUP_ENABLE_PRERELEASES=1.

#Use devEngines.packageManager

Node also supports a structured declaration:

{
  "devEngines": {
    "packageManager": {
      "name": "pnpm",
      "version": "11.x",
      "onFail": "error"
    }
  }
}

version may be exact or a semver range. onFail controls how a mismatch or invalid declaration is handled:

  • error stops the command;
  • warn prints a warning and continues;
  • ignore continues without a warning.

When both forms are present, packageManager supplies the active pin and devEngines.packageManager supplies an additional constraint. jup checks that they agree.

For example, this manifest pins one release while allowing any pnpm 11 release:

{
  "packageManager": "pnpm@11.1.2+sha512.abc123...",
  "devEngines": {
    "packageManager": {
      "name": "pnpm",
      "version": "11.x",
      "onFail": "error"
    }
  }
}

When jup up updates the exact pin, it preserves the 11.x constraint rather than replacing it with an exact version.

#Keep a range in packageManager

A manifest may contain a range directly:

{
  "packageManager": "pnpm@^11.0.0"
}

A range can resolve differently as new releases appear. To make the result repeatable, jup records the selected release and digest in .corepack.lock:

{
  "version": 1,
  "resolutions": {
    "pnpm@^11.0.0": {
      "resolved": "11.1.2",
      "integrity": "sha512-..."
    }
  }
}

Commit .corepack.lock. Future runs use its recorded resolution without asking the registry which release matches the range.

This is different from passing a range to jup use: jup use pnpm@^11 normally writes an exact pin. A range remains in packageManager when it is already present there, for example because it was written manually or by another tool.

When the CI environment variable is set, jup freezes range resolutions by default. A missing or stale .corepack.lock entry then fails instead of changing during the build. Resolve and commit it locally with:

jup up

Set JUP_FROZEN_LOCKFILE=0 only if you deliberately want CI to update the resolution. An explicit JUP_FROZEN_LOCKFILE=1 also prevents jup up from refreshing it.

Exact packageManager pins do not need .corepack.lock.

#Choose which manifest to edit

By default, commands that write a pin walk up from the current directory and stop at the workspace root. jup prints the path it changed.

Use --here to target package.json in the current directory:

jup use --here pnpm@11.1.2
jup up --here

If there is no manifest in that directory, jup use --here creates one.

Manifest lookup is broader when jup only needs to read a pin. A package without its own declaration may inherit one from a parent, which lets a single workspace pin apply to all packages.

#Choose where to store the digest

By default, jup appends the digest to packageManager. This suffix style works with Corepack:

{
  "packageManager": "yarn@4.14.1+sha512.abc123..."
}

Some tools accept only a plain version in packageManager. For those projects, use the sidecar style:

jup use --pin-style=sidecar yarn@4.14.1

It writes the digest separately:

{
  "packageManager": "yarn@4.14.1",
  "devEngines": {
    "packageManager": {
      "name": "yarn",
      "version": "4.14.1",
      "integrity": "sha512-..."
    }
  }
}

jup verifies both styles. Use the default suffix unless another tool specifically requires the plain version text.

jup  Run the package manager your project pinned.