From 3a2203eaacd47f1f059d31f86561b0e9372e9ce1 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 23 Sep 2026 04:21:36 +0530 Subject: [PATCH] fix(core): install git plugins from branch subdirectories (#50754) --- bun.lock | 1 + package.json | 2 +- packages/core/test/npm.test.ts | 17 ++++++---- patches/pacote@21.5.0.patch | 18 ----------- patches/pacote@21.5.1.patch | 58 ++++++++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+), 26 deletions(-) delete mode 100644 patches/pacote@21.5.0.patch create mode 100644 patches/pacote@21.5.1.patch diff --git a/bun.lock b/bun.lock index 25c27fad235..e0fae1826ca 100644 --- a/bun.lock +++ b/bun.lock @@ -1075,6 +1075,7 @@ "@tanstack/virtual-core@3.17.8": "patches/@tanstack%2Fvirtual-core@3.17.8.patch", "ghostty-web@github:anomalyco/ghostty-web#83c0a07": "patches/ghostty-web@0.3.0.patch", "@modelcontextprotocol/client@2.0.0": "patches/@modelcontextprotocol%2Fclient@2.0.0.patch", + "pacote@21.5.1": "patches/pacote@21.5.1.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", "@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch", "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch", diff --git a/package.json b/package.json index d6f182a0d4e..85f3dbc2862 100644 --- a/package.json +++ b/package.json @@ -173,7 +173,7 @@ "solid-js@1.9.15": "patches/solid-js@1.9.15.patch", "@ai-sdk/mistral@3.0.51": "patches/@ai-sdk%2Fmistral@3.0.51.patch", "gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch", - "pacote@21.5.0": "patches/pacote@21.5.0.patch", + "pacote@21.5.1": "patches/pacote@21.5.1.patch", "@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch", "@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch", "@tanstack/virtual-core@3.17.8": "patches/@tanstack%2Fvirtual-core@3.17.8.patch", diff --git a/packages/core/test/npm.test.ts b/packages/core/test/npm.test.ts index ff73b82a954..3140218d17b 100644 --- a/packages/core/test/npm.test.ts +++ b/packages/core/test/npm.test.ts @@ -248,20 +248,23 @@ describe("Npm.add", () => { expect(entries.added.directory).toContain("node_modules") }) - test("installs a Git package from an npm ::path: subdirectory", async () => { + test.each(["commit", "branch"])("installs a Git package from an npm ::path: subdirectory at a %s", async (ref) => { await using tmp = await tmpdir() const fixture = await createGitFixture(tmp.path) - const spec = `git+file://${fixture.repository}#${fixture.commit}::path:packages/subdirectory-plugin` - const entry = await Effect.gen(function* () { + const spec = `git+file://${fixture.repository}#${ref === "commit" ? fixture.commit : "fixture-branch"}::path:packages/subdirectory-plugin` + const result = await Effect.gen(function* () { const npm = yield* Npm.Service - return yield* npm.add(spec) + return { added: yield* npm.add(spec), outdated: yield* npm.check(spec) } }).pipe(Effect.scoped, Effect.provide(npmLayer(path.join(tmp.path, "cache"))), Effect.runPromise) - expect(entry.directory).toEndWith(path.join("node_modules", "fixture-subdirectory-plugin")) - expect(entry.name).toBe("fixture-subdirectory-plugin") + expect(result.added.directory).toEndWith(path.join("node_modules", "fixture-subdirectory-plugin")) + expect(result.added.name).toBe("fixture-subdirectory-plugin") + expect(result.added.version).toBe(fixture.commit) + expect(await Bun.file(path.join(result.added.directory, "index.js")).text()).toContain("subdirectory: true") expect( - await fs.stat(path.join(path.dirname(entry.directory), "fixture-subdirectory-dependency", "package.json")), + await fs.stat(path.join(path.dirname(result.added.directory), "fixture-subdirectory-dependency", "package.json")), ).toBeTruthy() + expect(result.outdated).toBeFalse() }) // Several real Git installs and updates exceed Bun's default timeout on Windows. diff --git a/patches/pacote@21.5.0.patch b/patches/pacote@21.5.0.patch deleted file mode 100644 index a681e1c2a93..00000000000 --- a/patches/pacote@21.5.0.patch +++ /dev/null @@ -1,18 +0,0 @@ -diff --git a/lib/git.js b/lib/git.js -index 000ee9fc..a2a6cbb7 100644 ---- a/lib/git.js -+++ b/lib/git.js -@@ -254,8 +254,11 @@ class GitFetcher extends Fetcher { - resolved: this.resolved, - integrity: null, // it'll always be different, if we have one - }).extract(tmp).then(() => handler(`${tmp}${this.spec.gitSubdir || ''}`), er => { -- // fall back to ssh download if tarball fails -- if (er.constructor.name.match(/^Http/)) { -+ // fall back to clone if the tarball download fails due to an -+ // HTTP error or if the response is not a valid tarball (e.g. -+ // a hosted provider returning an HTML sign-in page with 200) -+ if ((typeof er.statusCode === 'number' && er.statusCode >= 400) || -+ /^TAR_/.test(er.code)) { - return this.#clone(handler, false) - } else { - throw er diff --git a/patches/pacote@21.5.1.patch b/patches/pacote@21.5.1.patch new file mode 100644 index 00000000000..cf0d758fe90 --- /dev/null +++ b/patches/pacote@21.5.1.patch @@ -0,0 +1,58 @@ +diff --git a/node_modules/pacote/.bun-tag-eb0e1d32c8fdffa0 b/.bun-tag-eb0e1d32c8fdffa0 +new file mode 100644 +index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +diff --git a/lib/git.js b/lib/git.js +index 8faf125c6e5e572e980cfc8aaf4e6c9a21d740cf..b1ae3c8cdfa9cbdbd7573be7fa74197d158e6bcb 100644 +--- a/lib/git.js ++++ b/lib/git.js +@@ -247,15 +247,20 @@ class GitFetcher extends Fetcher { + // if we're resolved, and have a tarball url, shell out to RemoteFetcher + if (tarballOk) { + const nameat = this.spec.name ? `${this.spec.name}@` : '' +- return new RemoteFetcher(h.tarball({ noCommittish: false }), { ++ // the hosted committish also carries ::path:, which is not a valid ++ // archive ref; download the commit and select the subdirectory below ++ return new RemoteFetcher(h.tarball({ committish: this.resolvedSha }), { + ...this.opts, + allowGitIgnore: true, + pkgid: `git:${nameat}${this.resolved}`, + resolved: this.resolved, + integrity: null, // it'll always be different, if we have one + }).extract(tmp).then(() => handler(`${tmp}${this.spec.gitSubdir || ''}`), er => { +- // fall back to ssh download if tarball fails +- if (er.constructor.name.match(/^Http/)) { ++ // fall back to clone if the tarball download fails due to an ++ // HTTP error or if the response is not a valid tarball (e.g. ++ // a hosted provider returning an HTML sign-in page with 200) ++ if ((typeof er.statusCode === 'number' && er.statusCode >= 400) || ++ /^TAR_/.test(er.code)) { + return this.#clone(handler, false) + } else { + throw er +diff --git a/lib/util/add-git-sha.js b/lib/util/add-git-sha.js +index 8518bff9e202a61cd4451d184b74015b43f24ad0..f46620d77c8ee336c20c1c93916307ec3be6e183 100644 +--- a/lib/util/add-git-sha.js ++++ b/lib/util/add-git-sha.js +@@ -1,17 +1,20 @@ + // add a sha to a git remote url spec + const addGitSha = (spec, sha) => { ++ // the sha replaces any committish or semver range, but the subdirectory ++ // still selects the package within the resolved commit ++ const subdir = spec.gitSubdir ? `::path:${spec.gitSubdir.slice(1)}` : '' + if (spec.hosted) { + const h = spec.hosted + const opt = { noCommittish: true } + const base = h.https && h.auth ? h.https(opt) : h.shortcut(opt) + +- return `${base}#${sha}` ++ return `${base}#${sha}${subdir}` + } else { + // don't use new URL for this, because it doesn't handle scp urls + // strip the committish with indexOf/slice to avoid a regexp redos + const hashIndex = spec.rawSpec.indexOf('#') + const base = hashIndex === -1 ? spec.rawSpec : spec.rawSpec.slice(0, hashIndex) +- return `${base}#${sha}` ++ return `${base}#${sha}${subdir}` + } + } +