fix(pacquet/store-dir): append STORE_VERSION to storeDir so pnpm and pacquet stay interchangeable (#11891)

* fix(store-dir): append STORE_VERSION to storeDir so pnpm and pacquet stay interchangeable

pnpm's `getStorePath` appends `STORE_VERSION` (`"v11"`) to whatever the
user configured, so the `.modules.yaml` it writes records the v11-suffixed
path. pacquet stored the suffix only as an internal sub-path accessor
(`StoreDir::v11`), which meant `config.store_dir.display()` — the value
pacquet writes to `.modules.yaml`, prints from `pacquet store path`, and
emits in the NDJSON `context` log — yielded the un-suffixed parent.
Switching between the two CLIs in the same project tripped pnpm's
`checkCompatibility` with `ERR_PNPM_UNEXPECTED_STORE`.

Fix is centralised in `From<PathBuf> for StoreDir`, mirroring pnpm's
`if (endsWith(v11)) return; else append(v11)` branch at
store/path/src/index.ts:39-42. Every consumer reading from `StoreDir`
(`display()`, `root()`, `files()`, `tmp()`, `links()`, `projects()`)
now sees the v11-suffixed path through one source of truth, so the
on-disk layout is unchanged and the externally-reported `storeDir`
matches pnpm's exactly.

Ref: https://github.com/pnpm/pnpm/blob/29a42efc3b/store/path/src/index.ts#L39-L42

* fix(store-dir): satisfy Perfectionist macro-trailing-comma on remaining multi-line assert

* fix(store-dir): enforce STORE_VERSION suffix on deserialize via #[serde(from)]

CodeRabbit flagged that the previous `#[serde(transparent)]` derive on
`StoreDir` deserialised straight into `StoreDir::root`, bypassing the
auto-append in `impl From<PathBuf> for StoreDir`. A persisted unsuffixed
path would therefore violate the [`STORE_VERSION`] invariant on the live
struct until the next reconstruction. Pacquet doesn't currently
deserialize `StoreDir` from any disk shape, but the type-level guarantee
is part of the public contract — future serialised state must round-trip
through the suffix logic.

Route both directions through `PathBuf` with
`#[serde(from = "PathBuf", into = "PathBuf")]`. Deserialize now flows
through `From<PathBuf>` (which applies the suffix); serialize converts
to `PathBuf` and back to the same wire shape `transparent` produced, so
no on-disk format change. `Clone` is required by `into` and was added.

Also fix CodeRabbit's doc-comment nit at
project_registry::register_skips_when_store_is_inside_project — the
comment referenced `StoreDir::from` while the test calls `StoreDir::new`;
clarified that `new` routes through `From<PathBuf>`.

Added round-trip tests in `store_dir::tests`:
- `deserialize_applies_store_version_to_unsuffixed_path`
- `deserialize_preserves_already_suffixed_path`
This commit is contained in:
Zoltan Kochan
2026-05-24 01:48:24 +02:00
committed by GitHub
parent e0bd879dea
commit 3209c2510c
10 changed files with 187 additions and 69 deletions

View File

@@ -1,4 +1,5 @@
use command_extra::CommandExtra;
use pacquet_store_dir::STORE_VERSION;
use pacquet_testing_utils::bin::CommandTempCwd;
use pipe_trait::Pipe;
use pretty_assertions::assert_eq;
@@ -39,7 +40,11 @@ fn store_path_should_return_store_dir_from_pnpm_workspace_yaml() {
let normalize = |path: &str| path.replace('\\', "/");
assert_eq!(
String::from_utf8_lossy(&output.stdout).trim_end().pipe(normalize),
canonicalize(&workspace).join("foo/bar").to_string_lossy().pipe_as_ref(normalize),
canonicalize(&workspace)
.join("foo/bar")
.join(STORE_VERSION)
.to_string_lossy()
.pipe_as_ref(normalize),
);
drop(root); // cleanup

View File

@@ -4,7 +4,7 @@ use super::{
resolve_child_concurrency_with_parallelism,
};
use crate::api::{EnvVar, GetCurrentDir, GetHomeDir};
use pacquet_store_dir::StoreDir;
use pacquet_store_dir::{STORE_VERSION, StoreDir};
use pretty_assertions::assert_eq;
use std::{io, path::PathBuf};
@@ -48,7 +48,7 @@ fn test_default_store_dir_with_pnpm_home_env() {
}
}
let store_dir = default_store_dir::<EnvWithPnpmHome>();
assert_eq!(display_store_dir(&store_dir), "/tmp/pnpm-home/store");
assert_eq!(display_store_dir(&store_dir), format!("/tmp/pnpm-home/store/{STORE_VERSION}"));
}
/// `default_store_dir`'s `XDG_DATA_HOME` branch fires only when
@@ -77,7 +77,10 @@ fn test_default_store_dir_with_xdg_env() {
}
}
let store_dir = default_store_dir::<EnvWithXdgDataHome>();
assert_eq!(display_store_dir(&store_dir), "/tmp/xdg_data_home/pnpm/store");
assert_eq!(
display_store_dir(&store_dir),
format!("/tmp/xdg_data_home/pnpm/store/{STORE_VERSION}"),
);
}
/// When neither `PNPM_HOME` nor `XDG_DATA_HOME` is set, the
@@ -110,8 +113,8 @@ fn test_default_store_dir_falls_back_to_home_dir() {
}
let store_dir = default_store_dir::<NoEnvWithHome>();
let expected = match std::env::consts::OS {
"linux" => "/home/test-user/.local/share/pnpm/store",
"macos" => "/home/test-user/Library/pnpm/store",
"linux" => format!("/home/test-user/.local/share/pnpm/store/{STORE_VERSION}"),
"macos" => format!("/home/test-user/Library/pnpm/store/{STORE_VERSION}"),
other => panic!("unexpected target OS in test: {other}"),
};
assert_eq!(display_store_dir(&store_dir), expected);

View File

@@ -1222,13 +1222,17 @@ impl Config {
// diverges from TypeScript's case-sensitive program when the
// workspace is case-sensitive and the home is not.
if !store_dir_explicit && let Some(home_dir) = Sys::home_dir() {
// The "pnpm home dir" pnpm probes against is the parent of
// the home store (`~/Library/pnpm` for `~/Library/pnpm/store`).
// Fall back to the user's actual home if no parent is
// available — same-volume linkability is what we're after,
// and the home dir is on the same volume as any of its
// children.
let store_root = self.store_dir.root().to_path_buf();
// `store_dir.root()` already carries the [`STORE_VERSION`]
// suffix that [`StoreDir::from`] applied, so the
// un-suffixed home store sits one level above. The "pnpm
// home dir" pnpm probes against is the parent of that
// un-suffixed home store (`~/Library/pnpm` for
// `~/Library/pnpm/store`). Fall back to the user's actual
// home whenever a parent is unavailable — same-volume
// linkability is what we're after, and the home dir is on
// the same volume as any of its children.
let store_root_versioned = self.store_dir.root().to_path_buf();
let store_root = store_root_versioned.parent().unwrap_or(&home_dir).to_path_buf();
let pnpm_home_dir = store_root.parent().unwrap_or(&home_dir).to_path_buf();
let resolved =
store_path::resolve_store_dir::<Sys>(store_root, &pnpm_home_dir, start_dir);
@@ -1423,7 +1427,10 @@ mod tests {
}
}
let store_dir = default_store_dir::<EnvWithPnpmHome>();
assert_eq!(display_store_dir(&store_dir), "/hello/store");
assert_eq!(
display_store_dir(&store_dir),
format!("/hello/store/{}", pacquet_store_dir::STORE_VERSION),
);
}
/// Companion to [`should_use_pnpm_home_env_var`]: when
@@ -1451,7 +1458,10 @@ mod tests {
}
}
let store_dir = default_store_dir::<EnvWithXdgDataHome>();
assert_eq!(display_store_dir(&store_dir), "/hello/pnpm/store");
assert_eq!(
display_store_dir(&store_dir),
format!("/hello/pnpm/store/{}", pacquet_store_dir::STORE_VERSION),
);
}
#[test]

View File

@@ -32,12 +32,17 @@
//! question without touching disk. The production [`Host`] impl
//! performs the real link attempts via [`host_can_link_between_dirs`].
//!
//! `STORE_VERSION` ("v11") is intentionally *not* appended here:
//! pacquet's [`pacquet_store_dir::StoreDir`] stores the root one
//! level above v11 and appends it at access time via
//! [`pacquet_store_dir::StoreDir::v11`], where pnpm's `getStorePath`
//! returns the v11-suffixed path directly. Both implementations land
//! on identical on-disk paths.
//! [`pacquet_store_dir::STORE_VERSION`] (`"v11"`) is *not* appended in
//! this module; the path returned here is the un-suffixed base. Every
//! caller wraps the result in [`pacquet_store_dir::StoreDir::from`],
//! which appends the suffix in one place — mirroring pnpm's
//! [`getStorePath`](https://github.com/pnpm/pnpm/blob/29a42efc3b/store/path/src/index.ts#L39-L42)
//! `if (!endsWith(v11)) append(v11)` branch. Doing the join at
//! construction guarantees that everything pacquet exposes externally
//! (the `storeDir` written to `.modules.yaml`, the path printed by
//! `pacquet store path`, the NDJSON `context` log event) matches the
//! value pnpm produces, so switching between the two tools no longer
//! trips `ERR_PNPM_UNEXPECTED_STORE`.
//!
//! [`Host`]: crate::api::Host

View File

@@ -12,6 +12,7 @@ use pacquet_reporter::{
PackageManifestMessage, ProgressLog, ProgressMessage, Reporter, SilentReporter, Stage,
StageLog, StatsLog, StatsMessage, SummaryLog,
};
use pacquet_store_dir::STORE_VERSION;
use pacquet_testing_utils::fs::{get_all_folders, is_symlink_or_junction};
use pacquet_workspace_state::{
self as workspace_state, NodeLinker as WorkspaceStateNodeLinker, load_workspace_state,
@@ -578,7 +579,7 @@ async fn install_emits_pnpm_event_sequence() {
unreachable!("second event is context, asserted above");
};
assert!(!current_lockfile_exists);
assert_eq!(emitted_store_dir, &store_dir.display().to_string());
assert_eq!(emitted_store_dir, &store_dir.join(STORE_VERSION).display().to_string());
assert_eq!(emitted_virtual_store_dir, &virtual_store_dir.to_string_lossy().into_owned());
// Summary's `prefix` must equal the manifest-parent value
@@ -675,7 +676,7 @@ async fn install_writes_modules_yaml() {
assert!(included.dependencies);
assert!(!included.dev_dependencies);
assert!(included.optional_dependencies);
assert_eq!(emitted_store_dir, store_dir.display().to_string());
assert_eq!(emitted_store_dir, store_dir.join(STORE_VERSION).display().to_string());
// `read_modules_manifest` resolves `virtualStoreDir` against
// `modules_dir`, so a relative on-disk value round-trips back
// to the absolute install-time path.

View File

@@ -454,13 +454,20 @@ mod tests {
/// Subdir guard: when the store lives inside the project, the
/// function is a silent no-op — registering would otherwise create
/// a self-referential symlink.
/// a self-referential symlink. The `STORE_VERSION` subdir
/// (`store_dir.root()` after [`StoreDir::new`] routes the path
/// through [`From<PathBuf>`] and applies the suffix) is
/// materialised on disk so [`path_contains`]'s canonical-form
/// comparison sees both sides as canonical paths even on macOS,
/// where `/tmp` symlinks to `/private/tmp` and a missing target
/// would silently fall back to lexical comparison and miss the
/// containment.
#[test]
fn register_skips_when_store_is_inside_project() {
let project = tempdir().unwrap();
let store_path = project.path().join("nested-store");
fs::create_dir_all(&store_path).unwrap();
let store_dir = StoreDir::new(store_path);
let store_dir = StoreDir::new(&store_path);
fs::create_dir_all(store_dir.root()).unwrap();
register_project(&store_dir, project.path()).expect("subdir case is a no-op");
// No projects/ dir should have been created.

View File

@@ -6,20 +6,41 @@ use std::path::{self, PathBuf};
/// Content hash of a file.
pub type FileHash = digest::Output<Sha512>;
/// Major version of the pnpm store layout that pacquet writes to and reads
/// from. Mirrors pnpm's [`STORE_VERSION`](https://github.com/pnpm/pnpm/blob/29a42efc3b/core/constants/src/index.ts#L9).
///
/// The constant is part of the public contract pnpm exposes to every
/// project's `.modules.yaml` (the recorded `storeDir` is the
/// `STORE_VERSION`-suffixed path), so changing it requires moving in
/// lockstep with pnpm — otherwise both tools start refusing each
/// other's stores with `ERR_PNPM_UNEXPECTED_STORE`.
pub const STORE_VERSION: &str = "v11";
/// Represent a store directory.
///
/// * The store directory stores all files that were acquired by installing packages with pacquet or pnpm.
/// * The files in `node_modules` directories are hardlinks or reflinks to the files in the store directory.
/// * The store directory can and often act as a global shared cache of all installation of different workspaces.
/// * The location of the store directory can be customized by `store-dir` field.
/// * The on-disk layout matches pnpm v11 (`<root>/v11/files/XX/…[-exec]` + `<root>/v11/index.db`)
/// so the two tools can share a store.
#[derive(Debug, Serialize, Deserialize)]
#[serde(transparent)]
/// * The on-disk layout matches pnpm v11 (`<root>/files/XX/…[-exec]` + `<root>/index.db`)
/// where `<root>` already includes the `v11` suffix, so the two tools share both the
/// physical layout *and* the user-visible `storeDir` string written to
/// `.modules.yaml`.
//
// `#[serde(from = "PathBuf", into = "PathBuf")]` routes both
// directions through the `PathBuf` boundary so deserialization goes
// back through [`From<PathBuf>`] and the [`STORE_VERSION`] suffix
// invariant holds for persisted unsuffixed paths too — the previous
// `#[serde(transparent)]` derive deserialised straight into the
// `root` field and bypassed the auto-append.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(from = "PathBuf", into = "PathBuf")]
pub struct StoreDir {
/// Path to the root of the store directory from which all sub-paths are derived.
///
/// Consumer of this struct should interact with the sub-paths instead of this path.
/// The `STORE_VERSION`-suffixed store path, equivalent to pnpm's
/// [`storeDir`](https://github.com/pnpm/pnpm/blob/29a42efc3b/store/path/src/index.ts#L39-L42).
/// Consumers should reach for the purpose-built helpers
/// ([`Self::files`][], [`Self::tmp`], [`Self::links`],
/// [`Self::projects`]) rather than this raw path.
root: PathBuf,
/// Runtime cache of shard bytes (`files/XX/`) this process has already
@@ -30,11 +51,19 @@ pub struct StoreDir {
/// `stat` per file. After the first hit, the shard is cached and
/// subsequent writes skip the syscall entirely. Populated lazily by
/// [`StoreDir::write_cas_file`]; duplicate inserts across threads are
/// harmless since `create_dir_all` is idempotent.
#[serde(skip, default)]
/// harmless since `create_dir_all` is idempotent. Not part of the
/// serialised wire shape — `#[serde(from/into)]` round-trips only
/// through `PathBuf`, so the cache is regenerated empty on every
/// deserialise.
ensured_shards: DashSet<u8>,
}
impl From<StoreDir> for PathBuf {
fn from(store_dir: StoreDir) -> Self {
store_dir.root
}
}
/// Manual `PartialEq` / `Eq`: the shard cache is runtime state, two stores
/// are equal iff they point at the same path.
impl PartialEq for StoreDir {
@@ -46,7 +75,18 @@ impl PartialEq for StoreDir {
impl Eq for StoreDir {}
impl From<PathBuf> for StoreDir {
/// Wrap a raw path into a [`StoreDir`], appending [`STORE_VERSION`]
/// when the path doesn't already end with that segment. Mirrors
/// pnpm's [`getStorePath`](https://github.com/pnpm/pnpm/blob/29a42efc3b/store/path/src/index.ts#L39-L42),
/// so both tools record the same `storeDir` string in
/// `.modules.yaml` and switching between them stops tripping
/// `ERR_PNPM_UNEXPECTED_STORE`.
fn from(root: PathBuf) -> Self {
let root = if root.file_name().and_then(|name| name.to_str()) == Some(STORE_VERSION) {
root
} else {
root.join(STORE_VERSION)
};
StoreDir { root, ensured_shards: DashSet::new() }
}
}
@@ -76,14 +116,9 @@ impl StoreDir {
self.root.display()
}
/// Get `{store}/v11` — the root of the pnpm v11 store layout.
pub fn v11(&self) -> PathBuf {
self.root.join("v11")
}
/// The directory that contains all content-addressed files.
fn files(&self) -> PathBuf {
self.v11().join("files")
self.root.join("files")
}
/// Path to a file in the store directory.
@@ -108,7 +143,7 @@ impl StoreDir {
/// Path to the temporary directory inside the store.
pub fn tmp(&self) -> PathBuf {
self.v11().join("tmp")
self.root.join("tmp")
}
/// Path to the shared global-virtual-store directory inside the
@@ -117,27 +152,26 @@ impl StoreDir {
/// `globalVirtualStoreDir = path.join(extendedOpts.storeDir, 'links')`.
/// `extendedOpts.storeDir` has already been routed through
/// [`getStorePath`](https://github.com/pnpm/pnpm/blob/29a42efc3b/store/path/src/index.ts#L39-L42)
/// by the time that join runs, and `getStorePath` appends
/// `STORE_VERSION` (`"v11"`) to whatever the user configured. So
/// the resulting on-disk location is `<root>/v11/links`, not
/// `<root>/links` — the latter would put pacquet one level
/// shallower than pnpm and split slot caches across the two
/// tools. Sharing the path across pnpm and pacquet is the whole
/// point, so anchor under [`Self::v11`].
/// — which appends [`STORE_VERSION`] (`"v11"`) to whatever the
/// user configured — by the time that join runs. Pacquet's
/// [`StoreDir::from`] applies the same suffix, so `self.root` is
/// already the v11 path and the on-disk location is
/// `<root>/links`, identical to pnpm's. Sharing this path across
/// pnpm and pacquet is the whole point.
pub fn links(&self) -> PathBuf {
self.v11().join("links")
self.root.join("links")
}
/// Path to the per-store projects registry — a flat directory of
/// symlinks (`<store>/v11/projects/<short-hash>` → project dir)
/// the global-virtual-store prune sweep walks when deciding which
/// `<store>/v11/links/...` slots are still referenced. Mirrors
/// pnpm 11's
/// [`{storeDir}/v11/projects/` layout](https://github.com/pnpm/pnpm/blob/29a42efc3b/store/controller/CHANGELOG.md#L136)
/// — same `getStorePath`-driven `v11` reasoning as
/// symlinks (`<store>/projects/<short-hash>` → project dir) the
/// global-virtual-store prune sweep walks when deciding which
/// `<store>/links/...` slots are still referenced. Mirrors pnpm
/// 11's
/// [`{storeDir}/projects/` layout](https://github.com/pnpm/pnpm/blob/29a42efc3b/store/controller/CHANGELOG.md#L136)
/// — `<store>` already carries the v11 suffix on both sides per
/// [`Self::links`].
pub fn projects(&self) -> PathBuf {
self.v11().join("projects")
self.root.join("projects")
}
/// Borrow the raw store-root path. Most code should prefer the
@@ -148,7 +182,7 @@ impl StoreDir {
&self.root
}
/// On a fresh store, eagerly create `<store>/v11/files/` plus every
/// On a fresh store, eagerly create `<store>/files/` plus every
/// `files/XX/` shard (00..ff) and seed the shard cache with the
/// bytes we just created, so CAFS writes never pay a
/// `create_dir_all` syscall in the hot path.

View File

@@ -1,7 +1,7 @@
use super::StoreDir;
use super::{STORE_VERSION, StoreDir};
use pipe_trait::Pipe;
use pretty_assertions::assert_eq;
use std::path::Path;
use std::path::{Path, PathBuf};
#[test]
fn file_path_by_head_tail() {
@@ -20,6 +20,59 @@ fn tmp() {
assert_eq!(received, expected);
}
/// `StoreDir::from(PathBuf)` appends [`STORE_VERSION`] to any path
/// that doesn't already end with it — matching pnpm's
/// [`getStorePath`](https://github.com/pnpm/pnpm/blob/29a42efc3b/store/path/src/index.ts#L39-L42)
/// branch. Both the auto-append happy path and the
/// already-suffixed idempotent path are pinned here so a regression
/// would surface as either a missing `v11` (the original bug — pnpm
/// rejects the resulting `.modules.yaml` with
/// `ERR_PNPM_UNEXPECTED_STORE`) or a duplicated `v11/v11` segment.
#[test]
fn from_pathbuf_auto_appends_store_version_when_missing() {
let store = StoreDir::from(PathBuf::from("/home/user/.local/share/pnpm/store"));
assert_eq!(store.root(), Path::new("/home/user/.local/share/pnpm/store/v11"));
}
#[test]
fn from_pathbuf_does_not_double_append_when_already_suffixed() {
let store = StoreDir::from(PathBuf::from("/home/user/.local/share/pnpm/store/v11"));
assert_eq!(store.root(), Path::new("/home/user/.local/share/pnpm/store/v11"));
}
/// Round-trip the `storeDir` string pacquet writes to `.modules.yaml`
/// against the pnpm comparison contract: pnpm rebuilds `<X>/v11`
/// from the user's home and demands an exact match against the
/// recorded value. The constant test makes the bug
/// `ERR_PNPM_UNEXPECTED_STORE` triggered legible for future readers.
#[test]
fn modules_yaml_serialized_store_dir_carries_store_version() {
let store = StoreDir::new("/tmp/.pnpm-store");
let recorded = store.display().to_string();
let pnpm_would_emit = format!("/tmp/.pnpm-store/{STORE_VERSION}");
assert_eq!(recorded, pnpm_would_emit);
}
/// Deserialising an unsuffixed path (e.g. one persisted by an older
/// pacquet that hadn't normalised yet) must route through
/// [`From<PathBuf>`] and gain the [`STORE_VERSION`] suffix. The
/// previous `#[serde(transparent)]` derive wrote straight into
/// `root` and bypassed the auto-append, leaving the suffix invariant
/// silently violated on the live `StoreDir`.
#[test]
fn deserialize_applies_store_version_to_unsuffixed_path() {
let json = r#""/home/user/.local/share/pnpm/store""#;
let store: StoreDir = serde_json::from_str(json).expect("deserialize StoreDir");
assert_eq!(store.root(), Path::new("/home/user/.local/share/pnpm/store/v11"));
}
#[test]
fn deserialize_preserves_already_suffixed_path() {
let json = r#""/home/user/.local/share/pnpm/store/v11""#;
let store: StoreDir = serde_json::from_str(json).expect("deserialize StoreDir");
assert_eq!(store.root(), Path::new("/home/user/.local/share/pnpm/store/v11"));
}
/// `init` on a fresh store should materialize `v11/files/00..ff`
/// and populate the shard cache so later `write_cas_file` calls
/// can skip their lazy mkdir.

View File

@@ -120,10 +120,10 @@ impl StoreIndexWriter {
pub fn spawn(
store_dir: &StoreDir,
) -> (Arc<StoreIndexWriter>, tokio::task::JoinHandle<Result<(), StoreIndexError>>) {
let v11_dir = store_dir.v11();
let store_root = store_dir.root().to_path_buf();
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<WriteMsg>();
let handle = tokio::task::spawn_blocking(move || -> Result<(), StoreIndexError> {
let mut index = StoreIndex::open(&v11_dir)?;
let mut index = StoreIndex::open(&store_root)?;
let mut batch: Vec<WriteMsg> = Vec::with_capacity(MAX_BATCH_SIZE);
while let Some(first) = rx.blocking_recv() {
batch.push(first);
@@ -401,9 +401,9 @@ impl StoreIndex {
Ok(StoreIndex { conn })
}
/// Open the `index.db` that lives under a [`StoreDir`]'s `v11` subdirectory.
/// Open the `index.db` that lives directly under a [`StoreDir`]'s root.
pub fn open_in(store_dir: &StoreDir) -> Result<Self, StoreIndexError> {
StoreIndex::open(&store_dir.v11())
StoreIndex::open(store_dir.root())
}
/// Open an existing `index.db` read-only. Skips the schema-mutating
@@ -427,7 +427,7 @@ impl StoreIndex {
/// Read-only counterpart to [`StoreIndex::open_in`].
pub fn open_readonly_in(store_dir: &StoreDir) -> Result<Self, StoreIndexError> {
StoreIndex::open_readonly(&store_dir.v11())
StoreIndex::open_readonly(store_dir.root())
}
/// Open a read-only index wrapped in `Arc<Mutex<…>>` so it can be shared
@@ -440,11 +440,11 @@ impl StoreIndex {
/// redoing its PRAGMAs) on every package, which otherwise scales
/// linearly with the snapshot count.
pub fn shared_readonly_in(store_dir: &StoreDir) -> Option<SharedReadonlyStoreIndex> {
let v11_dir = store_dir.v11();
if !v11_dir.join("index.db").exists() {
let store_root = store_dir.root();
if !store_root.join("index.db").exists() {
return None;
}
StoreIndex::open_readonly(&v11_dir).ok().map(|index| Arc::new(Mutex::new(index)))
StoreIndex::open_readonly(store_root).ok().map(|index| Arc::new(Mutex::new(index)))
}
/// Look up a package-files index by key. Returns `Ok(None)` if no row exists.

View File

@@ -135,7 +135,7 @@ fn index_db_lives_at_store_dir_v11() {
let store = StoreDir::new(root.path());
let idx = StoreIndex::open_in(&store).unwrap();
idx.set("k\tv", &sample_index()).unwrap();
assert!(store.v11().join("index.db").exists());
assert!(store.root().join("index.db").exists());
}
/// A row whose bytes are msgpackr-records (as pnpm writes) must decode