
# 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:

```json
{
  "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:

```sh
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:

```sh
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:

```json
{
  "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:

```json
{
  "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:

```json
{
  "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`:

```json
{
  "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:

```sh
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:

```sh
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:

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

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

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

It writes the digest separately:

```json
{
  "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.
