From a70ce0c87ed01c4973b5cd8e32176411ea782b24 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sat, 15 Aug 2026 11:51:35 +0000 Subject: [PATCH 1/5] install: read file: tarballs relative to the file: folder package that declares them A local tarball dependency declared in the package.json of a file: folder dependency was read relative to the project directory instead of the folder, so the install failed with ENOENT, or installed a same-named tarball from the project directory when one existed. Only workspace declarers had their directory joined in. enqueue_local_tarball now asks local_tarball_base_dir for the base: the directory of the declaring workspace or file: folder package when that package's own specifier is the tarball path, and the top-level dir otherwise. The second case covers paths substituted from the root package.json (overrides, resolutions, catalogs), which were previously read relative to the workspace when applied to a workspace dependency. Both the resolve pass and the install from bun.lock go through the same function, so they read the same file. --- .../PackageManager/PackageManagerEnqueue.rs | 70 +++++++----- src/install/PackageManagerTask.rs | 12 +- src/install/lockfile.rs | 10 ++ test/cli/install/bun-install.test.ts | 105 ++++++++++++++++++ test/cli/install/bun-workspaces.test.ts | 45 ++++++++ 5 files changed, 212 insertions(+), 30 deletions(-) diff --git a/src/install/PackageManager/PackageManagerEnqueue.rs b/src/install/PackageManager/PackageManagerEnqueue.rs index db8f7c3058d3..76405a0e80ef 100644 --- a/src/install/PackageManager/PackageManagerEnqueue.rs +++ b/src/install/PackageManager/PackageManagerEnqueue.rs @@ -1903,31 +1903,18 @@ fn enqueue_local_tarball( // other dependencies (e.g. `appendPackage` / `StringBuilder.allocate` // in `Package.fromNPM`). let mut abs_buf = PathBuffer::uninit(); - let (tarball_path, normalize): (&[u8], bool) = 'tarball_path: { - let workspace_pkg_id = this - .lockfile - .get_workspace_pkg_if_workspace_dep(dependency_id); - if workspace_pkg_id == invalid_package_id { - break 'tarball_path (path, true); - } - - let workspace_res = this.lockfile.packages.items_resolution()[workspace_pkg_id as usize]; - if workspace_res.tag != ResolutionTag::Workspace { - break 'tarball_path (path, true); - } - - // Construct an absolute path to the tarball. - // Normally tarball paths are always relative to the root directory, but if a - // workspace depends on a tarball path, it should be relative to the workspace. - let workspace_str = *workspace_res.workspace(); - let workspace_path = workspace_str.slice(this.lockfile.buffers.string_bytes.as_slice()); - let joined = Path::resolve_path::join_abs_string_buf::( - FileSystem::instance().top_level_dir(), - &mut abs_buf, - &[workspace_path, path], - ); - break 'tarball_path (joined, false); - }; + let (tarball_path, normalize): (&[u8], bool) = + match local_tarball_base_dir(&this.lockfile, dependency_id, path) { + None => (path, true), + Some(base_dir) => ( + Path::resolve_path::join_abs_string_buf::( + FileSystem::instance().top_level_dir(), + &mut abs_buf, + &[base_dir, path], + ), + false, + ), + }; // Build the `Task` value *before* claiming a hive slot — the `.expect()`s // below can unwind, and `Task` carries drop glue. See `enqueue_git_clone`. @@ -1978,6 +1965,39 @@ fn enqueue_local_tarball( unsafe { &raw mut (*task).threadpool_task } } +/// A local tarball path is relative to the package.json that wrote it. When that is a +/// workspace or a `file:` folder package, returns its directory relative to the top-level +/// dir. `None` means the top-level dir itself: the root wrote the path, either as its own +/// dependency or as an override / resolutions / catalog entry substituted for whatever the +/// declaring package asked for (those only exist in the root package.json, so the declared +/// specifier is then not this path), or the declaring package was extracted from the cache +/// and has no directory in the project. +fn local_tarball_base_dir<'a>( + lockfile: &'a Lockfile::Lockfile, + dependency_id: DependencyID, + path: &[u8], +) -> Option<&'a [u8]> { + let declared = &lockfile.buffers.dependencies[dependency_id as usize].version; + if declared.tag != dependency::version::Tag::Tarball { + return None; + } + let dependency::tarball::Uri::Local(declared_path) = &declared.tarball().uri else { + return None; + }; + if lockfile.str(declared_path) != path { + return None; + } + + let declarer = lockfile.get_parent_pkg_of_dependency(dependency_id)?; + let declarer_res = &lockfile.packages.items_resolution()[declarer as usize]; + let base_dir = match declarer_res.tag { + ResolutionTag::Workspace => declarer_res.workspace(), + ResolutionTag::Folder => declarer_res.folder(), + _ => return None, + }; + Some(lockfile.str(base_dir)) +} + fn update_name_and_name_hash_from_version_replacement( lockfile: &Lockfile::Lockfile, original_name: SemverString, diff --git a/src/install/PackageManagerTask.rs b/src/install/PackageManagerTask.rs index 18d868675bb0..81292de88811 100644 --- a/src/install/PackageManagerTask.rs +++ b/src/install/PackageManagerTask.rs @@ -700,11 +700,13 @@ pub struct GitCheckoutRequest { pub struct LocalTarballRequest { pub(crate) tarball: ExtractTarball, /// Path to read the tarball from. May be the same as `tarball.url` (when - /// `normalize` is true) or an absolute path joined with a workspace - /// directory. Computed on the main thread in `enqueueLocalTarball` because - /// resolving it requires reading `lockfile.packages` / `string_bytes`, - /// which can be reallocated concurrently by the main thread while this - /// task runs on a ThreadPool worker. + /// `normalize` is true) or an absolute path joined with the directory of + /// the workspace or `file:` folder package that declared it + /// (`local_tarball_base_dir`). Computed on the main thread in + /// `enqueueLocalTarball` because resolving it requires reading + /// `lockfile.packages` / `string_bytes`, which can be reallocated + /// concurrently by the main thread while this task runs on a ThreadPool + /// worker. pub(crate) tarball_path: StringOrTinyString, /// When true, `tarball_path` is a user-provided path resolved relative to /// cwd. When false, it is already an absolute path. diff --git a/src/install/lockfile.rs b/src/install/lockfile.rs index da5691de7ee9..2b63cb6b73d2 100644 --- a/src/install/lockfile.rs +++ b/src/install/lockfile.rs @@ -782,6 +782,16 @@ impl Lockfile { self.get_workspace_pkg_if_workspace_dep(id) != invalid_package_id } + /// `None` for the edges `enqueue_dependency_to_root` appends outside of any package. + pub(crate) fn get_parent_pkg_of_dependency(&self, id: DependencyID) -> Option { + for (pkg_id, dependencies) in self.packages.items_dependencies().iter().enumerate() { + if dependencies.contains(id) { + return Some(PackageID::try_from(pkg_id).expect("int cast")); + } + } + None + } + pub(crate) fn get_workspace_pkg_if_workspace_dep(&self, id: DependencyID) -> PackageID { let packages = self.packages.slice(); let resolutions = packages.items_resolution(); diff --git a/test/cli/install/bun-install.test.ts b/test/cli/install/bun-install.test.ts index e64ecf3dcf4e..d8102c10e084 100644 --- a/test/cli/install/bun-install.test.ts +++ b/test/cli/install/bun-install.test.ts @@ -10571,6 +10571,111 @@ it("fails when a transitive file: dependency's folder does not exist", async () expect(exitCode).toBe(1); }); +describe.concurrent("file: tarball declared by a file: folder dependency", () => { + // `bar-0.0.2.tgz` is planted at the path the declaration means and + // `baz-0.0.3.tgz` at the other candidate path, so reading the tarball + // relative to the wrong directory installs `baz` instead of failing with ENOENT. + const expected = readFileSync(join(import.meta.dir, "bar-0.0.2.tgz")); + const decoy = readFileSync(join(import.meta.dir, "baz-0.0.3.tgz")); + + const fixture = (root: object, lib: object, tarballs: Record) => ({ + "package.json": JSON.stringify({ + name: "my-app", + version: "1.0.0", + dependencies: { lib: "file:./vendor/lib" }, + ...root, + }), + "vendor/lib/package.json": JSON.stringify({ name: "lib", version: "1.0.0", main: "index.js", ...lib }), + "vendor/lib/index.js": `const pkg = require("tool/package.json"); module.exports = pkg.name + "@" + pkg.version;`, + ...tarballs, + }); + + // The first install resolves `tool` from vendor/lib/package.json and reads + // the tarball in the process. The second one starts from the lockfile with + // an empty cache, so it has to read the tarball again from the path recorded + // there; both have to pick the same file. + async function installAndRequireLib(projectDir: string, linker: "hoisted" | "isolated") { + const cacheDir = join(projectDir, ".bun-cache"); + const installed: string[] = []; + + for (const args of [["install"], ["install", "--frozen-lockfile"]]) { + await Promise.all([ + rm(join(projectDir, "node_modules"), { recursive: true, force: true }), + rm(cacheDir, { recursive: true, force: true }), + ]); + + await using install = spawn({ + cmd: [bunExe(), ...args, `--linker=${linker}`], + cwd: projectDir, + stdout: "pipe", + stderr: "pipe", + env: { ...env, BUN_INSTALL_CACHE_DIR: cacheDir }, + }); + const [installErr, installOut, installExit] = await Promise.all([ + install.stderr.text(), + install.stdout.text(), + install.exited, + ]); + expect(installErr).not.toContain("error:"); + expect(installOut).toContain("2 packages installed"); + expect(installExit).toBe(0); + + await using run = spawn({ + cmd: [bunExe(), "-e", `console.log(require("lib"))`], + cwd: projectDir, + stdout: "pipe", + stderr: "pipe", + env, + }); + const [runErr, runOut, runExit] = await Promise.all([run.stderr.text(), run.stdout.text(), run.exited]); + expect(runErr).toBe(""); + expect(runExit).toBe(0); + installed.push(runOut.trim()); + } + + return { installed, lockfile: await file(join(projectDir, "bun.lock")).text() }; + } + + for (const linker of ["hoisted", "isolated"] as const) { + it(`is read relative to the folder (${linker} linker)`, async () => { + using dir = tempDir( + "folder-dep-tarball", + fixture( + {}, + { dependencies: { tool: "file:./tool.tgz" } }, + { "vendor/lib/tool.tgz": expected, "tool.tgz": decoy }, + ), + ); + + const { installed, lockfile } = await installAndRequireLib(String(dir), linker); + expect(installed).toEqual(["bar@0.0.2", "bar@0.0.2"]); + // The lockfile records the path as declared; the name in front of it is + // read from the tarball that was extracted. + expect(lockfile).toContain('"lib": ["lib@file:vendor/lib", { "dependencies": { "tool": "file:./tool.tgz" } }]'); + expect(lockfile).toContain('"tool": ["bar@./tool.tgz", {}, "sha512-'); + }); + } + + it("is read relative to the project when a root override supplies the path", async () => { + // `overrides` can only be written in the root package.json, so the path it + // contains means the project directory even though the dependency it is + // applied to is declared by vendor/lib/package.json. + using dir = tempDir( + "folder-dep-tarball-override", + fixture( + { overrides: { tool: "file:./tool.tgz" } }, + { dependencies: { tool: "^1.0.0" } }, + { "tool.tgz": expected, "vendor/lib/tool.tgz": decoy }, + ), + ); + + const { installed, lockfile } = await installAndRequireLib(String(dir), "hoisted"); + expect(installed).toEqual(["bar@0.0.2", "bar@0.0.2"]); + expect(lockfile).toContain('"lib": ["lib@file:vendor/lib", { "dependencies": { "tool": "^1.0.0" } }]'); + expect(lockfile).toContain('"tool": ["bar@./tool.tgz", {}, "sha512-'); + }); +}); + it("does not extract a local file: tarball outside the temp dir for a dependency alias containing '..' path segments", async () => { // For `file:` tarball dependencies, the dependency alias (the key in // `dependencies`) is used to derive the temporary extraction folder name. diff --git a/test/cli/install/bun-workspaces.test.ts b/test/cli/install/bun-workspaces.test.ts index 48903d67ec9f..8d5522f4c242 100644 --- a/test/cli/install/bun-workspaces.test.ts +++ b/test/cli/install/bun-workspaces.test.ts @@ -734,6 +734,51 @@ describe("relative tarballs", async () => { }, }); }); + test.concurrent("from a root override applied to a workspace dependency", async () => { + using ctx = await setupTest(); + const { packageDir, env } = ctx; + // The path comes from the root package.json, so it is relative to the root + // even though the dependency it replaces is declared by the workspace. The + // workspace gets a different tarball at the same relative path so reading + // it relative to the workspace installs `baz` instead of failing. + await Promise.all([ + write( + join(packageDir, "package.json"), + JSON.stringify({ + name: "foo", + workspaces: ["pkgs/*"], + overrides: { bar: "file:./bar.tgz" }, + }), + ), + write( + join(packageDir, "pkgs", "pkg1", "package.json"), + JSON.stringify({ + name: "pkg1", + dependencies: { bar: "^0.0.2" }, + }), + ), + cp(join(import.meta.dir, "bar-0.0.2.tgz"), join(packageDir, "bar.tgz")), + ]); + await cp(join(import.meta.dir, "baz-0.0.3.tgz"), join(packageDir, "pkgs", "pkg1", "bar.tgz")); + + // The second install starts from the lockfile and an empty cache, so it + // reads the tarball again from the path recorded there. + for (const frozenLockfile of [false, true]) { + await Promise.all([ + rm(join(packageDir, "node_modules"), { recursive: true, force: true }), + rm(env.BUN_INSTALL_CACHE_DIR, { recursive: true, force: true }), + ]); + + await runBunInstall(env, packageDir, { frozenLockfile }); + + expect(await file(join(packageDir, "node_modules", "bar", "package.json")).json()).toEqual({ + name: "bar", + version: "0.0.2", + }); + } + + expect(await file(join(packageDir, "bun.lock")).text()).toContain('"bar": ["bar@./bar.tgz", {}, "sha512-'); + }); // Regression test for a data race where the `.local_tarball` task callback // (running on a ThreadPool worker) read `lockfile.packages` and From 2ed666b13d787f8346bfafc0ad9736ff6759b216 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sat, 15 Aug 2026 12:04:29 +0000 Subject: [PATCH 2/5] test: cover a root catalog entry pointing a workspace dependency at a local tarball --- test/cli/install/bun-workspaces.test.ts | 76 ++++++++++++------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/test/cli/install/bun-workspaces.test.ts b/test/cli/install/bun-workspaces.test.ts index 8d5522f4c242..bbfe6925b439 100644 --- a/test/cli/install/bun-workspaces.test.ts +++ b/test/cli/install/bun-workspaces.test.ts @@ -734,51 +734,47 @@ describe("relative tarballs", async () => { }, }); }); - test.concurrent("from a root override applied to a workspace dependency", async () => { - using ctx = await setupTest(); - const { packageDir, env } = ctx; - // The path comes from the root package.json, so it is relative to the root - // even though the dependency it replaces is declared by the workspace. The - // workspace gets a different tarball at the same relative path so reading - // it relative to the workspace installs `baz` instead of failing. - await Promise.all([ - write( - join(packageDir, "package.json"), - JSON.stringify({ - name: "foo", - workspaces: ["pkgs/*"], - overrides: { bar: "file:./bar.tgz" }, - }), - ), - write( - join(packageDir, "pkgs", "pkg1", "package.json"), - JSON.stringify({ - name: "pkg1", - dependencies: { bar: "^0.0.2" }, - }), - ), - cp(join(import.meta.dir, "bar-0.0.2.tgz"), join(packageDir, "bar.tgz")), - ]); - await cp(join(import.meta.dir, "baz-0.0.3.tgz"), join(packageDir, "pkgs", "pkg1", "bar.tgz")); - - // The second install starts from the lockfile and an empty cache, so it - // reads the tarball again from the path recorded there. - for (const frozenLockfile of [false, true]) { + // The tarball path is written in the root package.json, so it is relative to + // the root even though the dependency it ends up satisfying is declared by + // the workspace (#25835 for overrides, #25752 for catalogs). The workspace + // gets a different tarball at the same relative path, so reading it relative + // to the workspace installs `baz` instead of failing. + for (const [source, root, specifier] of [ + ["override", { overrides: { bar: "file:./bar.tgz" } }, "^0.0.2"], + ["catalog entry", { catalogs: { vendored: { bar: "file:./bar.tgz" } } }, "catalog:vendored"], + ] as const) { + test.concurrent(`from a root ${source} applied to a workspace dependency`, async () => { + using ctx = await setupTest(); + const { packageDir, env } = ctx; await Promise.all([ - rm(join(packageDir, "node_modules"), { recursive: true, force: true }), - rm(env.BUN_INSTALL_CACHE_DIR, { recursive: true, force: true }), + write(join(packageDir, "package.json"), JSON.stringify({ name: "foo", workspaces: ["pkgs/*"], ...root })), + write( + join(packageDir, "pkgs", "pkg1", "package.json"), + JSON.stringify({ name: "pkg1", dependencies: { bar: specifier } }), + ), + cp(join(import.meta.dir, "bar-0.0.2.tgz"), join(packageDir, "bar.tgz")), ]); + await cp(join(import.meta.dir, "baz-0.0.3.tgz"), join(packageDir, "pkgs", "pkg1", "bar.tgz")); - await runBunInstall(env, packageDir, { frozenLockfile }); + // The second install starts from the lockfile and an empty cache, so it + // reads the tarball again from the path recorded there. + for (const frozenLockfile of [false, true]) { + await Promise.all([ + rm(join(packageDir, "node_modules"), { recursive: true, force: true }), + rm(env.BUN_INSTALL_CACHE_DIR, { recursive: true, force: true }), + ]); - expect(await file(join(packageDir, "node_modules", "bar", "package.json")).json()).toEqual({ - name: "bar", - version: "0.0.2", - }); - } + await runBunInstall(env, packageDir, { frozenLockfile }); - expect(await file(join(packageDir, "bun.lock")).text()).toContain('"bar": ["bar@./bar.tgz", {}, "sha512-'); - }); + expect(await file(join(packageDir, "node_modules", "bar", "package.json")).json()).toEqual({ + name: "bar", + version: "0.0.2", + }); + } + + expect(await file(join(packageDir, "bun.lock")).text()).toContain('"bar": ["bar@./bar.tgz", {}, "sha512-'); + }); + } // Regression test for a data race where the `.local_tarball` task callback // (running on a ThreadPool worker) read `lockfile.packages` and From ef9994758fe32a9559a757d2487293ab5c9ef2b0 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sat, 15 Aug 2026 12:08:35 +0000 Subject: [PATCH 3/5] install: shorten the local tarball base dir doc comments --- src/install/PackageManager/PackageManagerEnqueue.rs | 11 ++++------- src/install/PackageManagerTask.rs | 12 ++++-------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/install/PackageManager/PackageManagerEnqueue.rs b/src/install/PackageManager/PackageManagerEnqueue.rs index 76405a0e80ef..241c60c53206 100644 --- a/src/install/PackageManager/PackageManagerEnqueue.rs +++ b/src/install/PackageManager/PackageManagerEnqueue.rs @@ -1965,13 +1965,10 @@ fn enqueue_local_tarball( unsafe { &raw mut (*task).threadpool_task } } -/// A local tarball path is relative to the package.json that wrote it. When that is a -/// workspace or a `file:` folder package, returns its directory relative to the top-level -/// dir. `None` means the top-level dir itself: the root wrote the path, either as its own -/// dependency or as an override / resolutions / catalog entry substituted for whatever the -/// declaring package asked for (those only exist in the root package.json, so the declared -/// specifier is then not this path), or the declaring package was extracted from the cache -/// and has no directory in the project. +/// The directory `path` is relative to when that is not the top-level dir: the workspace or +/// `file:` folder package whose package.json wrote it. An edge whose own specifier is not +/// `path` got it from the root's overrides / resolutions / catalogs, so it stays relative +/// to the top-level dir. fn local_tarball_base_dir<'a>( lockfile: &'a Lockfile::Lockfile, dependency_id: DependencyID, diff --git a/src/install/PackageManagerTask.rs b/src/install/PackageManagerTask.rs index 81292de88811..8e57df0cfae0 100644 --- a/src/install/PackageManagerTask.rs +++ b/src/install/PackageManagerTask.rs @@ -699,14 +699,10 @@ pub struct GitCheckoutRequest { pub struct LocalTarballRequest { pub(crate) tarball: ExtractTarball, - /// Path to read the tarball from. May be the same as `tarball.url` (when - /// `normalize` is true) or an absolute path joined with the directory of - /// the workspace or `file:` folder package that declared it - /// (`local_tarball_base_dir`). Computed on the main thread in - /// `enqueueLocalTarball` because resolving it requires reading - /// `lockfile.packages` / `string_bytes`, which can be reallocated - /// concurrently by the main thread while this task runs on a ThreadPool - /// worker. + /// `tarball.url` itself (`normalize` set) or the absolute path it was joined + /// into (`local_tarball_base_dir`). Computed on the main thread: resolving it + /// reads `lockfile.packages` / `string_bytes`, which the main thread may + /// reallocate while this task runs on a ThreadPool worker. pub(crate) tarball_path: StringOrTinyString, /// When true, `tarball_path` is a user-provided path resolved relative to /// cwd. When false, it is already an absolute path. From 223e0a45a2917fef867a8786db0da4d430f34ea5 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sat, 15 Aug 2026 12:14:31 +0000 Subject: [PATCH 4/5] install: name the declared-by-parent check instead of documenting it --- .../PackageManager/PackageManagerEnqueue.rs | 19 ++++++++----------- src/install/PackageManagerTask.rs | 6 ++---- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/install/PackageManager/PackageManagerEnqueue.rs b/src/install/PackageManager/PackageManagerEnqueue.rs index 241c60c53206..72aa9715ba81 100644 --- a/src/install/PackageManager/PackageManagerEnqueue.rs +++ b/src/install/PackageManager/PackageManagerEnqueue.rs @@ -1965,23 +1965,20 @@ fn enqueue_local_tarball( unsafe { &raw mut (*task).threadpool_task } } -/// The directory `path` is relative to when that is not the top-level dir: the workspace or -/// `file:` folder package whose package.json wrote it. An edge whose own specifier is not -/// `path` got it from the root's overrides / resolutions / catalogs, so it stays relative -/// to the top-level dir. +/// The workspace or `file:` folder directory that `path` is relative to; `None` is the top-level dir. fn local_tarball_base_dir<'a>( lockfile: &'a Lockfile::Lockfile, dependency_id: DependencyID, path: &[u8], ) -> Option<&'a [u8]> { let declared = &lockfile.buffers.dependencies[dependency_id as usize].version; - if declared.tag != dependency::version::Tag::Tarball { - return None; - } - let dependency::tarball::Uri::Local(declared_path) = &declared.tarball().uri else { - return None; - }; - if lockfile.str(declared_path) != path { + let declared_by_parent = declared.tag == dependency::version::Tag::Tarball + && matches!( + &declared.tarball().uri, + dependency::tarball::Uri::Local(declared_path) if lockfile.str(declared_path) == path + ); + if !declared_by_parent { + // Overrides, resolutions and catalogs are all written in the root package.json. return None; } diff --git a/src/install/PackageManagerTask.rs b/src/install/PackageManagerTask.rs index 8e57df0cfae0..0283ea0d266f 100644 --- a/src/install/PackageManagerTask.rs +++ b/src/install/PackageManagerTask.rs @@ -699,10 +699,8 @@ pub struct GitCheckoutRequest { pub struct LocalTarballRequest { pub(crate) tarball: ExtractTarball, - /// `tarball.url` itself (`normalize` set) or the absolute path it was joined - /// into (`local_tarball_base_dir`). Computed on the main thread: resolving it - /// reads `lockfile.packages` / `string_bytes`, which the main thread may - /// reallocate while this task runs on a ThreadPool worker. + /// `tarball.url` itself (`normalize` set) or the absolute path `enqueue_local_tarball` + /// resolved it to on the main thread; the worker must not read the lockfile for this. pub(crate) tarball_path: StringOrTinyString, /// When true, `tarball_path` is a user-provided path resolved relative to /// cwd. When false, it is already an absolute path. From 5ce56966e9c79aec586a7cd05a74c5d552569685 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sat, 15 Aug 2026 12:15:29 +0000 Subject: [PATCH 5/5] install: one-line doc for LocalTarballRequest.tarball_path --- src/install/PackageManagerTask.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/install/PackageManagerTask.rs b/src/install/PackageManagerTask.rs index 0283ea0d266f..82d62768f65d 100644 --- a/src/install/PackageManagerTask.rs +++ b/src/install/PackageManagerTask.rs @@ -699,8 +699,7 @@ pub struct GitCheckoutRequest { pub struct LocalTarballRequest { pub(crate) tarball: ExtractTarball, - /// `tarball.url` itself (`normalize` set) or the absolute path `enqueue_local_tarball` - /// resolved it to on the main thread; the worker must not read the lockfile for this. + /// Resolved by `enqueue_local_tarball` on the main thread; the worker must not read the lockfile. pub(crate) tarball_path: StringOrTinyString, /// When true, `tarball_path` is a user-provided path resolved relative to /// cwd. When false, it is already an absolute path.