Skip to content

feat(package-manager): write .pnpm-workspace-state-v1.json after install#11665

Merged
zkochan merged 3 commits into
mainfrom
implement-state-file
May 15, 2026
Merged

feat(package-manager): write .pnpm-workspace-state-v1.json after install#11665
zkochan merged 3 commits into
mainfrom
implement-state-file

Conversation

@zkochan

@zkochan zkochan commented May 15, 2026

Copy link
Copy Markdown
Member

Summary

  • Ported @pnpm/workspace.state to a new pacquet-workspace-state crate so pacquet writes <workspaceDir>/node_modules/.pnpm-workspace-state-v1.json at the end of every install.
  • Wired the writer into Install::run right after .modules.yaml and the current lockfile are persisted, mirroring upstream's updateWorkspaceState call site.
  • Without this file, pnpm's verifyDepsBeforeRun gate immediately returns upToDate: false with the issue "Cannot check whether dependencies are outdated" — which is why ci: run install with pacquet #11657 had to disable the check in CI.

The file records the project list (read from lockfile importers, falling back to the root manifest), nodeLinker, dev / production / optional (from the dispatched IncludedDependencies), autoInstallPeers, dedupePeerDependents, hoistPattern, publicHoistPattern, hoistWorkspacePackages, ignoredOptionalDependencies, patchedDependencies, and allowBuilds. Settings pacquet does not track yet (e.g. dedupeDirectDeps, peersSuffixMaxLength, overrides, packageExtensions) are omitted; pnpm's check only iterates fields present in the JSON, so an absent key is silently skipped.

After this lands, the pnpm_config_verify_deps_before_run: false env var introduced in 7ff112b can be dropped from .github/workflows/ci.yml and .github/workflows/test.yml.

Test plan

  • cargo nextest run -p pacquet-workspace-state — 5/5 new unit tests (file path, round-trip, missing-file, nodeLinker lowercase, omits-None settings).
  • cargo nextest run -p pacquet-package-manager — 268/268 pass (267 existing + new install_writes_workspace_state integration test that asserts the file shape after a minimal frozen-lockfile install).
  • cargo clippy -p pacquet-workspace-state --all-targets -- --deny warnings and cargo clippy -p pacquet-package-manager --lib -- --deny warnings clean.
  • cargo fmt --check clean.
  • cargo check --workspace clean.
  • Once merged, drop pnpm_config_verify_deps_before_run: false and the no-op tests workaround from .github/workflows/{ci,test}.yml.

Written by an agent (Claude Code, claude-opus-4-7).

Summary by CodeRabbit

  • New Features

    • Installer now records and writes pnpm-style workspace state after installs, improving workspace metadata persistence.
  • Tests

    • Added integration and unit tests validating workspace state write/load, JSON serialization, and round-trip consistency.
  • Chores

    • Added a new workspace-state component/crate and updated manifests to manage on-disk workspace metadata.

Review Change Stack

pnpm's verifyDepsBeforeRun gate bails out with "Cannot check whether
dependencies are outdated" as soon as the workspace state file is
missing, so a node_modules tree materialized by pacquet always tripped
the check and forced a reinstall. Port @pnpm/workspace.state to a new
pacquet-workspace-state crate and write the file at the end of
Install::run so pnpm can fast-path the freshness check after pacquet
has done the install.

Closes the gap behind the pnpm_config_verify_deps_before_run: false
workaround in 7ff112b.
Copilot AI review requested due to automatic review settings May 15, 2026 07:02
@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fc43369d-bc47-4c80-8322-5403db8fa9d0

📥 Commits

Reviewing files that changed from the base of the PR and between cdde9d5 and dec8398.

📒 Files selected for processing (1)
  • pacquet/crates/workspace-state/src/tests.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • pacquet/crates/workspace-state/src/tests.rs
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Agent
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Code Coverage
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Dylint
  • GitHub Check: Compile & Lint

📝 Walkthrough

Walkthrough

This PR adds a new workspace-state crate and integrates workspace-state persistence into the package-manager install flow to write pnpm’s .pnpm-workspace-state-v1.json (types, I/O, timestamping, install wiring, and tests).

Changes

Workspace state persistence

Layer / File(s) Summary
Workspace-state library: types and I/O
pacquet/crates/workspace-state/Cargo.toml, pacquet/crates/workspace-state/src/lib.rs, pacquet/crates/workspace-state/src/tests.rs
Defines ProjectEntry, WorkspaceState, WorkspaceStateSettings, and NodeLinker types with serde mappings matching pnpm's JSON format. Implements update_workspace_state and load_workspace_state for disk I/O with proper error handling and trailing newline formatting. Tests verify path resolution, round-trip persistence, missing-file behavior, optional field omission, and enum serialization.
Install integration: dependencies and workspace-state writing
Cargo.toml, pacquet/crates/package-manager/Cargo.toml, pacquet/crates/package-manager/src/install.rs
Declares pacquet-workspace-state as a workspace dependency and adds it to the package-manager crate. Adds InstallError::WriteWorkspaceState. Inserts an install step that builds a WorkspaceState payload (timestamp, projects extracted from subproject manifests, node-linker mapping, dependency-group settings) and persists it via update_workspace_state.
Integration test: workspace-state written during install
pacquet/crates/package-manager/src/install/tests.rs
Adds install_writes_workspace_state test that runs a frozen-lockfile install and asserts the produced workspace state file exists, contains a validation timestamp, registers the workspace project (name/version), and contains expected settings flags (node-linker and dependency-group booleans).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • pnpm/pnpm#11366: Modifies pnpm workspace-state handling; both PRs touch the workspace-state JSON used for build/change detection.

Poem

I'm a rabbit with a tiny key,
I tuck workspace bits in JSON tree.
Timestamped projects snug and neat,
Install runs finish — state complete.
🐰📦✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: writing workspace state file (.pnpm-workspace-state-v1.json) after package installation, which aligns with all file additions and the new integration test.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch implement-state-file

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Re-align the dependency-block padding to taplo's expected width — CI's
`taplo format --check` flagged the 13-character padding the manual draft
shipped with.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pacquet/crates/package-manager/src/install.rs (1)

724-728: ⚡ Quick win

Avoid re-resolving workspace root inside build_workspace_state.

Install::run already resolves workspace_root with error propagation. Recomputing it here and swallowing errors can silently fall back to manifest_dir, which risks inconsistent project keys vs. write location in edge cases. Thread &workspace_root into this helper instead.

♻️ Suggested refactor
-fn build_workspace_state(
+fn build_workspace_state(
+    workspace_root: &std::path::Path,
     config: &Config,
     node_linker: NodeLinker,
     included: IncludedDependencies,
     manifest: &PackageManifest,
     lockfile: Option<&Lockfile>,
 ) -> WorkspaceState {
-    let manifest_dir = manifest.path().parent().expect("manifest path always has a parent dir");
-    let workspace_root = pacquet_workspace::find_workspace_dir(manifest_dir)
-        .ok()
-        .flatten()
-        .unwrap_or_else(|| manifest_dir.to_path_buf());
-
     let allow_builds = (!config.allow_builds.is_empty()).then(|| {
         config.allow_builds.iter().map(|(k, v)| (k.clone(), serde_json::Value::Bool(*v))).collect()
     });

     WorkspaceState {
         last_validated_timestamp: now_millis(),
-        projects: build_projects_map(&workspace_root, manifest, lockfile),
+        projects: build_projects_map(workspace_root, manifest, lockfile),
         update_workspace_state(
             &workspace_root,
-            &build_workspace_state(config, node_linker, included, manifest, lockfile),
+            &build_workspace_state(&workspace_root, config, node_linker, included, manifest, lockfile),
         )
         .map_err(InstallError::WriteWorkspaceState)?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pacquet/crates/package-manager/src/install.rs` around lines 724 - 728, The
helper build_workspace_state must not re-resolve the workspace root; instead,
change its signature to accept a &workspace_root (Path or PathBuf reference) and
use that value rather than calling pacquet_workspace::find_workspace_dir inside
build_workspace_state (remove the manifest_dir/workspace_root recomputation and
the unwrap_or_else fallback). Update the call site in Install::run to pass the
already-resolved workspace_root (propagating errors as currently done there) so
workspace resolution is centralized and no silent fallback to manifest_dir
occurs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@pacquet/crates/package-manager/src/install.rs`:
- Around line 724-728: The helper build_workspace_state must not re-resolve the
workspace root; instead, change its signature to accept a &workspace_root (Path
or PathBuf reference) and use that value rather than calling
pacquet_workspace::find_workspace_dir inside build_workspace_state (remove the
manifest_dir/workspace_root recomputation and the unwrap_or_else fallback).
Update the call site in Install::run to pass the already-resolved workspace_root
(propagating errors as currently done there) so workspace resolution is
centralized and no silent fallback to manifest_dir occurs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: de6446ca-b30b-4f15-83e3-d353d3645320

📥 Commits

Reviewing files that changed from the base of the PR and between 7ff112b and 351a98b.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • Cargo.toml
  • pacquet/crates/package-manager/Cargo.toml
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install/tests.rs
  • pacquet/crates/workspace-state/Cargo.toml
  • pacquet/crates/workspace-state/src/lib.rs
  • pacquet/crates/workspace-state/src/tests.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Agent
  • GitHub Check: Code Coverage
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Dylint
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (1)
pacquet/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

No star imports inside module bodies — write use super::{Foo, bar} instead of use super::*; and the same for any other glob. Exception: external-crate preludes like use rayon::prelude::*; and root-of-module re-exports like pub use submodule::*; in lib.rs

Files:

  • pacquet/crates/workspace-state/src/tests.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install/tests.rs
  • pacquet/crates/workspace-state/src/lib.rs
🔇 Additional comments (7)
pacquet/crates/workspace-state/Cargo.toml (1)

1-24: LGTM!

pacquet/crates/workspace-state/src/lib.rs (1)

14-225: LGTM!

pacquet/crates/workspace-state/src/tests.rs (1)

1-98: LGTM!

Cargo.toml (1)

38-38: LGTM!

pacquet/crates/package-manager/Cargo.toml (1)

32-32: LGTM!

pacquet/crates/package-manager/src/install.rs (1)

24-27: LGTM!

Also applies to: 148-155, 520-534, 635-705, 730-762

pacquet/crates/package-manager/src/install/tests.rs (1)

16-18: LGTM!

Also applies to: 686-796

@github-actions

github-actions Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.00      7.8±0.21ms   552.6 KB/sec    1.01      7.9±0.34ms   548.2 KB/sec

@codecov-commenter

codecov-commenter commented May 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.90909% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.14%. Comparing base (763ddf1) to head (dec8398).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
pacquet/crates/package-manager/src/install.rs 93.02% 6 Missing ⚠️
pacquet/crates/workspace-state/src/lib.rs 85.71% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #11665      +/-   ##
==========================================
+ Coverage   89.13%   89.14%   +0.01%     
==========================================
  Files         126      127       +1     
  Lines       14337    14458     +121     
==========================================
+ Hits        12779    12889     +110     
- Misses       1558     1569      +11     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Dylint's `perfectionist::macro_trailing_comma` flags single-line macro
invocations that end with a trailing comma. Rustfmt's earlier collapse
of the multi-line assertion left the comma intact; remove it so the
nightly dylint check passes.
Copilot AI review requested due to automatic review settings May 15, 2026 07:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@github-actions

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Scenario: Frozen Lockfile

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.672 ± 0.086 2.569 2.822 1.01 ± 0.04
pacquet@main 2.653 ± 0.080 2.523 2.734 1.00
pnpm 5.015 ± 0.051 4.936 5.105 1.89 ± 0.06
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.67203833806,
      "stddev": 0.08583373177202643,
      "median": 2.66344435116,
      "user": 2.59712534,
      "system": 3.730567979999999,
      "min": 2.5694548196600002,
      "max": 2.82177194566,
      "times": [
        2.60403678066,
        2.72496114066,
        2.77217629766,
        2.58769840866,
        2.71187870466,
        2.82177194566,
        2.5694548196600002,
        2.63112424666,
        2.69576445566,
        2.6015165806600002
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.65255090956,
      "stddev": 0.08020325402914395,
      "median": 2.67723251066,
      "user": 2.61847504,
      "system": 3.6944637799999995,
      "min": 2.5233185816600003,
      "max": 2.73387510066,
      "times": [
        2.72850961466,
        2.73387510066,
        2.70125381666,
        2.60734307866,
        2.6532112046600003,
        2.6157162936600002,
        2.53021886366,
        2.71838988266,
        2.5233185816600003,
        2.71367265866
      ]
    },
    {
      "command": "pnpm",
      "mean": 5.01547437916,
      "stddev": 0.051150328625306045,
      "median": 5.0038302546599995,
      "user": 8.23112784,
      "system": 4.21627378,
      "min": 4.93624354466,
      "max": 5.10539405966,
      "times": [
        5.10539405966,
        5.07839182566,
        4.986988128659999,
        5.0495809206599995,
        5.02725059666,
        4.983793921659999,
        4.98312501666,
        5.02067238066,
        4.983303396659999,
        4.93624354466
      ]
    }
  ]
}

Scenario: Frozen Lockfile (Hot Cache)

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 747.4 ± 45.6 702.8 831.9 1.00
pacquet@main 831.5 ± 55.5 773.0 933.2 1.11 ± 0.10
pnpm 2718.3 ± 129.3 2581.6 2993.3 3.64 ± 0.28
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.7473898256400002,
      "stddev": 0.04556598222646133,
      "median": 0.7297346974400001,
      "user": 0.3815628,
      "system": 1.62438802,
      "min": 0.70279533344,
      "max": 0.8318790774400001,
      "times": [
        0.8318790774400001,
        0.7151882904400001,
        0.70279533344,
        0.7168967354400001,
        0.71755070344,
        0.71064239344,
        0.7419186914400001,
        0.8101602564400001,
        0.7842523664400001,
        0.74261440844
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.83145604314,
      "stddev": 0.05554386153951669,
      "median": 0.8093636929400001,
      "user": 0.3954014,
      "system": 1.65340802,
      "min": 0.7730499014400001,
      "max": 0.9332127174400001,
      "times": [
        0.9332127174400001,
        0.7730499014400001,
        0.7844849944400001,
        0.83858969244,
        0.91294734444,
        0.81152212544,
        0.8072052604400001,
        0.80129463944,
        0.86581575844,
        0.7864379974400001
      ]
    },
    {
      "command": "pnpm",
      "mean": 2.71827581064,
      "stddev": 0.12934283433903235,
      "median": 2.70415829644,
      "user": 3.3927828000000004,
      "system": 2.27928042,
      "min": 2.58162992544,
      "max": 2.99327651844,
      "times": [
        2.75416880344,
        2.68311252344,
        2.86460070144,
        2.58162992544,
        2.64284524544,
        2.72713482444,
        2.62810254244,
        2.58268295244,
        2.99327651844,
        2.72520406944
      ]
    }
  ]
}

@zkochan zkochan merged commit 4ababc0 into main May 15, 2026
35 of 37 checks passed
@zkochan zkochan deleted the implement-state-file branch May 15, 2026 09:43
github-actions Bot pushed a commit to Eyalm321/pnpm that referenced this pull request May 18, 2026
…all (pnpm#11665)

* feat(package-manager): write .pnpm-workspace-state-v1.json after install

pnpm's verifyDepsBeforeRun gate bails out with "Cannot check whether
dependencies are outdated" as soon as the workspace state file is
missing, so a node_modules tree materialized by pacquet always tripped
the check and forced a reinstall. Port @pnpm/workspace.state to a new
pacquet-workspace-state crate and write the file at the end of
Install::run so pnpm can fast-path the freshness check after pacquet
has done the install.

Closes the gap behind the pnpm_config_verify_deps_before_run: false
workaround in 7ff112b.

* chore(workspace-state): apply taplo formatting

Re-align the dependency-block padding to taplo's expected width — CI's
`taplo format --check` flagged the 13-character padding the manual draft
shipped with.

* chore(workspace-state): drop trailing comma in single-line assert_eq!

Dylint's `perfectionist::macro_trailing_comma` flags single-line macro
invocations that end with a trailing comma. Rustfmt's earlier collapse
of the multi-line assertion left the comma intact; remove it so the
nightly dylint check passes.
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.

3 participants