Skip to content

feat: store config deps and package manager integrities in pnpm-lock.env.yaml#10912

Merged
zkochan merged 42 commits intomainfrom
config-deps-lockfile
Mar 10, 2026
Merged

feat: store config deps and package manager integrities in pnpm-lock.env.yaml#10912
zkochan merged 42 commits intomainfrom
config-deps-lockfile

Conversation

@zkochan
Copy link
Copy Markdown
Member

@zkochan zkochan commented Mar 8, 2026

Summary

Store config dependency and package manager integrity info in a separate pnpm-lock.env.yaml lockfile instead of inlining it in pnpm-workspace.yaml. The workspace manifest now contains only clean version specifiers for configDependencies, while the resolved versions, integrity hashes, and tarball URLs are recorded in the new env lockfile.

Key changes

  • New pnpm-lock.env.yaml lockfile: Uses the standard lockfile format (importers, packages, snapshots) to store resolved config dependencies and package manager dependencies with integrity hashes and tarball URLs.
  • Automatic migration: Projects using the old inline-hash format in pnpm-workspace.yaml are automatically migrated on install.
  • Global Virtual Store (GVS) for version switching: When switching pnpm versions via the packageManager field, pnpm is installed to the global virtual store ($STORE_DIR/links/) instead of globalPkgDir, reusing the content-addressable store for deduplication.
  • Self-update uses headless install: pnpm self-update performs frozen headless installs using integrity hashes from the env lockfile, then links bins to PNPM_HOME.
  • packageManagerDependencies: The env lockfile also stores resolved packageManagerDependencies during version switching and self-update.
  • @pnpm/exe support: Replicates @pnpm/exe's postinstall script (linking platform-specific binaries) since install scripts are disabled.
  • pnpm setup refactored: Uses pnpm add -g instead of copying the CLI binary directly.
  • Extracted toLockfileResolution to @pnpm/lockfile.utils and deduplicated iteratePkgMeta into @pnpm/calc-dep-state.
  • Removed unused @pnpm/tools.path package.

Test plan

  • Existing unit tests updated and passing
  • E2E test for self-update with managePackageManagerVersions
  • E2E test for version switch reusing pnpm previously installed by self-update (GVS)
  • Config dependency migration and resolution tests
  • Linting passes

Move integrity hashes from inline `configDependencies` values in
pnpm-workspace.yaml to a dedicated pnpm-config-lock.yaml lockfile.
The workspace manifest now contains only clean version specifiers,
while the config lockfile stores resolved versions, integrity hashes,
and tarball URLs using a format similar to pnpm-lock.yaml.

Projects using the old inline-hash format are automatically migrated
when running pnpm install.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 8, 2026 12:34
@zkochan zkochan added this to the v11.0 milestone Mar 8, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a dedicated pnpm-config-lock.yaml to store resolved config dependency integrity (and tarball when needed), keeping pnpm-workspace.yaml configDependencies as clean specifiers and migrating older inline-integrity entries during install.

Changes:

  • Introduces config lockfile read/write utilities and uses them when resolving/installing config dependencies.
  • Updates resolve/install flows to write clean specifiers to pnpm-workspace.yaml and integrity data to pnpm-config-lock.yaml, with migration support for the legacy inline format.
  • Updates types/constants and adjusts unit/e2e tests and workspace wiring to cover the new lockfile format.

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
pnpm/tsconfig.json Adds project reference so pnpm can consume the updated config deps installer package.
pnpm/test/configurationalDependencies.test.ts Updates e2e coverage for pnpm add --config to assert clean specifiers + lockfile integrity.
pnpm/package.json Adds @pnpm/config.deps-installer devDependency for tests.
pnpm-lock.yaml Updates workspace lock entries to include new deps needed by config lockfile support.
packages/types/src/package.ts Documents old vs new configDependencies formats and introduces ConfigDependencySpecifiers type.
packages/constants/src/index.ts Adds CONFIG_LOCKFILE constant for pnpm-config-lock.yaml.
config/deps-installer/tsconfig.json Adds TS project reference to @pnpm/constants.
config/deps-installer/test/resolveConfigDeps.test.ts Extends unit test to validate lockfile writing + clean workspace manifest.
config/deps-installer/test/installConfigDeps.ts Refactors install tests to install from lockfile, adds migration test scaffolding.
config/deps-installer/src/resolveConfigDeps.ts Writes config deps into pnpm-config-lock.yaml and workspace manifest specifiers, then installs from lockfile.
config/deps-installer/src/migrateConfigDeps.ts New migration helper to convert legacy inline-integrity config deps into the lockfile format.
config/deps-installer/src/installConfigDeps.ts Changes install API to accept either config lockfile or legacy manifest config deps (migration path).
config/deps-installer/src/index.ts Exposes config lockfile utilities/types from the package public API.
config/deps-installer/src/configLockfile.ts New YAML read/write + sorting logic for pnpm-config-lock.yaml.
config/deps-installer/package.json Adds runtime deps needed for YAML lockfile parsing/writing and constants usage.
.changeset/config-deps-lockfile.md Changeset describing the new config lockfile behavior and migration.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

zkochan and others added 3 commits March 8, 2026 19:41
Resolved conflicts in:
- config/deps-installer/package.json (combined both branch deps)
- config/deps-installer/src/installConfigDeps.ts (integrated lockfile approach with calcLeafGlobalVirtualStorePath and symlinkDir from main)
- config/deps-installer/tsconfig.json (included both calc-dep-state and constants references)
- pnpm-lock.yaml (regenerated)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Validate integrity is a non-empty string in resolveConfigDeps
- Detect clean-specifier format without lockfile in migrateConfigDeps
  and throw a descriptive error instead of misleading "no integrity" hint
- Strengthen isConfigLockfile check to verify structural shape
- Throw on missing packages entry in normalizeFromLockfile instead of
  silently skipping
- Validate parsed YAML structure in readConfigLockfile
- Add lockfile content assertions to migration test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The default import doesn't work with verbatimModuleSyntax since
read-yaml-file is a CJS module. Use the named `sync` export
consistent with the rest of the codebase.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zkochan zkochan requested a review from a team March 9, 2026 01:17
zkochan and others added 10 commits March 9, 2026 08:03
When a project has a `packageManager` field in package.json specifying
a pnpm version, resolve integrity checksums for both `pnpm` and
`@pnpm/exe` (including their transitive dependencies) and store them
in the `packageManager` section of pnpm-config-lock.yaml.

This runs during both `pnpm self-update` (when updating the
packageManager field) and `pnpm install` (when the field is present).
Uses `pnpm install --lockfile-only` in a temp directory to resolve
the full dependency tree.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…cess

Replace `runPnpmCli` calls with direct `@pnpm/core` API usage in:
- `resolvePackageManagerIntegrities`: uses `install()` with `lockfileOnly: true`
- `installPnpmToTools`: uses `mutateModulesInSingleProject()` with
  `ignoreScripts: true` and `nodeLinker: 'hoisted'`
- `switchCliVersion`: creates store controller for `installPnpmToTools`

When integrities are available in pnpm-config-lock.yaml, self-update
writes a pnpm-lock.yaml and performs a frozen install for verification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… in config lockfile

- Add packageManagerDependencies to importers section (matching configDependencies pattern)
- Store full packages and snapshots data using convertToLockfileFile
- Use LockfilePackageInfo and LockfilePackageSnapshot from @pnpm/lockfile.types
- Remove custom ConfigLockfilePackageInfo and ConfigLockfileSnapshotInfo types
- Call resolvePackageManagerIntegrities in regular self-update path
- Fix nock mock URL encoding for @pnpm/exe

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace mutateModulesInSingleProject with direct fetchPackage/importPackage
into the global virtual store, making pnpm a regular global package. Add
version discovery via findPnpmInGlobalStore for CLI version switching.
Use pruneSharedLockfile to remove orphan entries from config lockfile.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use tryReadProjectManifest to detect if package.json exists. When it
doesn't, fall back to pnpmHomeDir for config lockfile location and
skip writing the packageManager field.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the `copyCli` function in `pnpm setup` with `installCliGlobally`
which runs `pnpm add -g file:<execDir>` to install the CLI into the
standard global directory. This removes the need for the `.tools/pnpm-exe/`
directory and aligns setup with the normal global package installation flow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
zkochan and others added 2 commits March 9, 2026 14:56
Cast the resolution to the expected shape since config dependencies
always use tarball resolutions with integrity. Fixes TS2339 errors
where LockfileResolution union includes types without these properties.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ading from disk

resolvePackageManagerIntegrities now returns the ConfigLockfile it
produced, which is then passed directly to installPnpmToTools via
a new required configLockfile option. This avoids a redundant disk
read of pnpm-config-lock.yaml.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 28 changed files in this pull request and generated 3 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

zkochan and others added 4 commits March 9, 2026 15:22
…g lockfile

Replace the manual fetch/import approach in installPnpmToTools with
headlessInstall from @pnpm/headless, which properly installs all
dependencies including subdeps of @pnpm/exe. Install location changed
from $PNPM_HOME/.tools/ to the global directory ($PNPM_HOME/global/v11)
using the @pnpm/global.packages utilities for consistency with
pnpm add -g.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace manual headlessInstall with handleGlobalAdd from
@pnpm/global.commands, which properly handles the global virtual store,
hash links, bin linking, and the full installation pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…r version switching

- self-update: uses headlessInstall with config lockfile integrity hashes
  for secure frozen install, falls back to full resolution without lockfile.
  Installs to globalPkgDir and links bins to pnpmHomeDir.
- version switch: uses installPnpmToStore which installs pnpm to the global
  virtual store ($STORE_DIR/links/) instead of globalPkgDir. Computes GVS
  hash from lockfile to check if already cached. Version-switched pnpm does
  not appear in `pnpm ls -g` and does not overwrite global binary.
- Restores linkExePlatformBinary for @pnpm/exe support without Node.js.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zkochan zkochan changed the title feat: store config dependency integrity in pnpm-config-lock.yaml refactor: use config lockfile for secure pnpm installation and GVS for version switching Mar 9, 2026
The version switch now installs pnpm to the global virtual store instead
of the tools dir. Update the test to find and corrupt the pnpm binary
in the GVS path within the store.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
zkochan and others added 4 commits March 10, 2026 00:52
The static imports of @pnpm/store-connection-manager,
@pnpm/config.deps-installer, and @pnpm/tools.plugin-commands-self-updater
(which now transitively imports @pnpm/headless) were loading on every
pnpm CLI startup, causing hangs in unrelated commands. Switch to dynamic
import() so these modules are only loaded when version switching is
actually needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The resolvePackageManagerIntegrities call in getConfig ran for every
command when packageManager was set in package.json, even for non-pnpm
package managers like yarn. This caused hangs because it tries to
resolve pnpm@VERSION from the registry for versions that may not exist.

The resolution is only needed during version switching and is already
called in switchCliVersion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move ConfigLockfile interface to @pnpm/lockfile.types,
  reusing SpecifierAndResolution
- Move create/read/writeConfigLockfile to @pnpm/lockfile.fs
- Use PnpmError instead of plain Error for validation
- Use sortDirectKeys from @pnpm/object.key-sorting
- Remove unused deps from @pnpm/config.deps-installer
- Update all consumers to import from new locations
- Sort imports in touched files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move toLockfileResolution from updateLockfile.ts to
  @pnpm/lockfile.utils for shared use
- Use it in resolveConfigDeps.ts and migrateConfigDeps.ts
  instead of custom tarball comparison logic
- Adds protocol normalization and %2f handling to config
  deps resolution (was missing before)
- Remove get-npm-tarball-url from resolve-dependencies deps

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
zkochan and others added 12 commits March 10, 2026 14:50
Replace the install() + temp directory approach in
resolvePackageManagerIntegrities with a direct call to
resolveDependencies via a new resolveManifestDependencies
helper. This eliminates temp dir creation, package.json
writing, lockfile read-back, and cleanup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Inline the config lockfile filename string to avoid a tsgo
resolution issue after @pnpm/core was removed from dependencies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the separate configLockfileToLockfileObject helper with a direct
convertToLockfileObject call in resolvePackageManagerIntegrities. This
removes the redundant manual merge/prune logic and reuses the existing
lockfile format converters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract shared lockfileYamlDump and writeFileAtomic from write.ts
and reuse them in configLockfile.ts instead of duplicating the
YAML format config and write-file-atomic callback wrapper.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split getConfig into two phases: basic config and config deps/hooks installation.
This allows switchCliVersion to run before config deps are installed, and ensures
packageManagerDependencies are always written to the config lockfile even when
the wanted pnpm version matches the current version.

Also adds tests for version switching scenarios (v9.3.0, v10.32.0) and
verifying packageManagerDependencies are saved when versions match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
write-file-atomic already returns a Promise when called without a
callback, so util.promisify wraps it incorrectly (DEP0174) and the
returned promise never resolves.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename ConfigLockfile to EnvLockfile, and all related functions
(createConfigLockfile, readConfigLockfile, writeConfigLockfile)
and variables throughout the codebase.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The getToolDirPath function was never imported anywhere.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…update

The global install path in self-update should not modify package.json.
This matches the v10 behavior where only the managePackageManagerVersions
path updates the packageManager field.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zkochan zkochan changed the title refactor: use config lockfile for secure pnpm installation and GVS for version switching feat: store config deps and package manager integrities in pnpm-lock.env.yaml Mar 10, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 50 out of 51 changed files in this pull request and generated 6 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Use replaceAll instead of replace for '%2f' in toLockfileResolution
  to handle multiple encoded slashes in tarball URLs
- Strengthen resolvePackageManagerIntegrities early-return check to
  verify both pnpm and @pnpm/exe match the requested version

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zkochan
Copy link
Copy Markdown
Member Author

zkochan commented Mar 10, 2026

Copilot review feedback — addressed

Reviewed all 16 comments. Two were valid and fixed in 4a9b0d7. The rest are false positives (Copilot was reviewing outdated file snapshots).

Fixed (2)

  • toLockfileResolution.ts:32.replace('%2f', '/') only replaced the first occurrence. Fixed with .replaceAll().
  • resolvePackageManagerIntegrities.ts:30 — Early-return check was too weak (checked if any entry matched). Now verifies both pnpm and @pnpm/exe match the requested version.

Already addressed — false positives (14)

These were flagged against older file snapshots. The current code already handles all of them:

  • Integrity validation (resolveConfigDeps.ts:43) — Line 43 already checks typeof !== 'string' || !resolution.resolution.integrity.
  • Clean specifier handling (migrateConfigDeps.ts:64) — Lines 65-76 already have the !pkgSpec.includes('+') check with CONFIG_DEP_MISSING_LOCKFILE error.
  • isEnvLockfile discrimination (installConfigDeps.ts:104) — Lines 116-127 already check all four fields (lockfileVersion, importers, packages, snapshots).
  • Missing packages entry (installConfigDeps.ts:116) — Lines 138-143 already throw ENV_LOCKFILE_CORRUPTED.
  • readEnvLockfile validation (configLockfile.ts:64) — envLockfile.ts validates all fields (lines 38-53) and throws INVALID_ENV_LOCKFILE.
  • findPnpmInGlobalStore path (installPnpmToTools.ts:95) — This file was deleted. findPnpmGvsPath in installPnpm.ts uses iterateHashedGraphNodes for proper hash computation.
  • SelfUpdateCommandOptions missing fields (selfUpdate.ts:50) — CreateStoreControllerOptions inherits registries and rawConfig from CreateNewStoreControllerOptions.
  • resolvePackageManagerIntegrities in getConfig (getConfig.ts:49) — Already removed in commit 3190b2c.
  • depPath format (resolveConfigDeps.ts:66, migrateConfigDeps.ts:57, installConfigDeps.ts:149, installPnpm.ts:354) — The env lockfile uses name@version keys consistently for both read and write. When merged with standard lockfiles, convertToLockfileObject/convertToLockfileFile handles the format conversion. These are simple top-level deps without peer suffixes, so depPath === pkgId.
  • Changeset filename (.changeset:8) — Already corrected to pnpm-lock.env.yaml in 9ba96ab.

zkochan and others added 3 commits March 10, 2026 23:14
…kfile in switchCliVersion

- Extract shared parseIntegrity function and NormalizedConfigDep interface
  into config/deps-installer/src/parseIntegrity.ts to remove duplication
  across normalizeConfigDeps.ts, migrateConfigDeps.ts, and installConfigDeps.ts
- Fix bug in switchCliVersion.ts where envLockfile was never updated after
  resolvePackageManagerIntegrities() wrote it, causing first-time version
  switches to always throw NO_PKG_MANAGER_INTEGRITY
- Replace confusing `&& true` with `Boolean()` in installPnpm.ts
- Use util.types.isNativeError() instead of type assertion for error checking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
normalizeConfigDeps is no longer called by any code in the codebase.
installConfigDeps was refactored to use normalizeForInstall which
delegates to normalizeFromLockfile or migrateConfigDepsToLockfile.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zkochan zkochan merged commit a8f016c into main Mar 10, 2026
10 of 13 checks passed
@zkochan zkochan deleted the config-deps-lockfile branch March 10, 2026 23:39
@zkochan zkochan mentioned this pull request Mar 11, 2026
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants