+60
-5
@@ -1,2 +0,4 @@ | ||
| const { resolve } = require('node:path') | ||
| const { resolve, join } = require('node:path') | ||
| const { mkdtemp, writeFile, rm } = require('node:fs/promises') | ||
| const { tmpdir } = require('node:os') | ||
| const packlist = require('npm-packlist') | ||
@@ -6,2 +8,3 @@ const runScript = require('@npmcli/run-script') | ||
| const { Minipass } = require('minipass') | ||
| const PackageJson = require('@npmcli/package-json') | ||
| const Fetcher = require('./fetcher.js') | ||
@@ -70,3 +73,3 @@ const FileFetcher = require('./file.js') | ||
| const { prefix, workspaces } = this.opts | ||
| const { prefix, workspaces, globalIgnoreFile } = this.opts | ||
@@ -81,6 +84,12 @@ // run the prepare script, get the list of files, and tar it up | ||
| } | ||
| return packlist(this.tree, { path: this.resolved, prefix, workspaces }) | ||
| return packlist(this.tree, { path: this.resolved, prefix, workspaces, globalIgnoreFile }) | ||
| }) | ||
| .then(files => tar.c(tarCreateOptions(this.package), files) | ||
| .on('error', er => stream.emit('error', er)).pipe(stream)) | ||
| .then(async files => { | ||
| const { options, cleanup } = await this.#tarOptions() | ||
| const source = tar.c(options, files) | ||
| // the strip temp file must outlive content consumption, so clean up once the stream is done | ||
| source.once('end', cleanup) | ||
| source.once('error', cleanup) | ||
| return source.on('error', er => stream.emit('error', er)).pipe(stream) | ||
| }) | ||
| .catch(er => stream.emit('error', er)) | ||
@@ -90,2 +99,48 @@ return stream | ||
| // Build the tar create options. | ||
| // When the packed package.json declares patchedDependencies, redirect it to a stripped copy so project-local patches never ship. | ||
| // Non-patched packs are unchanged. | ||
| async #tarOptions () { | ||
| const options = tarCreateOptions(this.package) | ||
| // read package.json from disk after prepare so the strip reflects the actually-packed manifest. | ||
| const pkgJson = await PackageJson.load(this.resolved) | ||
| if (!('patchedDependencies' in pkgJson.content)) { | ||
| return { options, cleanup: () => {} } | ||
| } | ||
| // serialize the package.json minus patchedDependencies, preserving its indent and newline. | ||
| // JSON.stringify ignores the indent and newline symbols @npmcli/package-json attaches to content. | ||
| delete pkgJson.content.patchedDependencies | ||
| const { content } = pkgJson | ||
| const indent = content[Symbol.for('indent')] | ||
| const newline = content[Symbol.for('newline')] | ||
| const stripped = `${JSON.stringify(content, null, indent)}\n`.replace(/\n/g, newline) | ||
| // write the stripped copy to a temp dir, removing it if the write itself fails. | ||
| const dir = await mkdtemp(join(tmpdir(), 'pacote-pack-')) | ||
| const strippedPath = join(dir, 'package.json') | ||
| try { | ||
| await writeFile(strippedPath, stripped) | ||
| } catch (er) { | ||
| /* istanbul ignore next - writing to a freshly created temp dir is not deterministically failable */ | ||
| await rm(dir, { recursive: true, force: true }) | ||
| /* istanbul ignore next */ | ||
| throw er | ||
| } | ||
| const size = Buffer.byteLength(stripped) | ||
| // point only the top-level package.json entry at the stripped copy; every other file is untouched. | ||
| // onWriteEntry runs before the tar header and the file's hardlink check, so size and nlink here are honored. | ||
| options.onWriteEntry = (entry) => { | ||
| if (entry.path === 'package.json') { | ||
| entry.absolute = strippedPath | ||
| entry.stat.size = size | ||
| entry.stat.nlink = 1 | ||
| } | ||
| } | ||
| return { options, cleanup: () => rm(dir, { recursive: true, force: true }) } | ||
| } | ||
| manifest () { | ||
@@ -92,0 +147,0 @@ if (this.package) { |
+4
-0
@@ -121,2 +121,6 @@ // This is the base class that the other fetcher types in lib | ||
| '--no-dry-run', | ||
| // override npm_config_global from the parent process: this inner | ||
| // `npm install` is preparing deps inside a tmp git clone, and it | ||
| // must reify into that clone's cwd, never the outer global prefix. | ||
| '--global=false', | ||
| ] | ||
@@ -123,0 +127,0 @@ } |
+11
-3
@@ -22,3 +22,3 @@ const cacache = require('cacache') | ||
| const repoUrl = (h, opts) => | ||
| h.sshurl && !(h.https && h.auth) && addGitPlus(h.sshurl(opts)) || | ||
| h.sshurl && !(h.https && (h.auth || h.default === 'https')) && addGitPlus(h.sshurl(opts)) || | ||
| h.https && addGitPlus(h.https(opts)) | ||
@@ -175,2 +175,7 @@ | ||
| // honor ignoreScripts: spawning `npm install` here would run lifecycle scripts (install, preinstall, postinstall, prepare) from the cloned repo, defeating the caller's explicit opt-out. | ||
| if (this.opts.ignoreScripts) { | ||
| return | ||
| } | ||
| // to avoid cases where we have an cycle of git deps that depend | ||
@@ -259,4 +264,7 @@ // on one another, we only ever do preparation for one instance | ||
| }).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) | ||
@@ -263,0 +271,0 @@ } else { |
@@ -6,3 +6,3 @@ // add a sha to a git remote url spec | ||
| const opt = { noCommittish: true } | ||
| const base = h.https && h.auth ? h.https(opt) : h.shortcut(opt) | ||
| const base = h.https && (h.auth || h.default === 'https') ? h.https(opt) : h.shortcut(opt) | ||
@@ -9,0 +9,0 @@ return `${base}#${sha}` |
+19
-18
| { | ||
| "name": "pacote", | ||
| "version": "21.5.1", | ||
| "version": "22.0.0", | ||
| "description": "JavaScript package downloader", | ||
@@ -30,5 +30,5 @@ "author": "GitHub Inc.", | ||
| "@npmcli/arborist": "^9.0.2", | ||
| "@npmcli/eslint-config": "^6.0.0", | ||
| "@npmcli/eslint-config": "^7.0.0", | ||
| "@npmcli/template-oss": "5.1.0", | ||
| "hosted-git-info": "^9.0.0", | ||
| "hosted-git-info": "^10.1.1", | ||
| "mutate-fs": "^2.1.1", | ||
@@ -51,21 +51,21 @@ "nock": "^13.2.4", | ||
| "@gar/promise-retry": "^1.0.0", | ||
| "@npmcli/git": "^7.0.0", | ||
| "@npmcli/installed-package-contents": "^4.0.0", | ||
| "@npmcli/package-json": "^7.0.0", | ||
| "@npmcli/promise-spawn": "^9.0.0", | ||
| "@npmcli/run-script": "^10.0.0", | ||
| "cacache": "^20.0.0", | ||
| "@npmcli/git": "^8.0.0", | ||
| "@npmcli/installed-package-contents": "^5.0.0", | ||
| "@npmcli/package-json": "^8.0.0", | ||
| "@npmcli/promise-spawn": "^10.0.0", | ||
| "@npmcli/run-script": "^11.0.0", | ||
| "cacache": "^21.0.1", | ||
| "fs-minipass": "^3.0.0", | ||
| "minipass": "^7.0.2", | ||
| "npm-package-arg": "^13.0.0", | ||
| "npm-packlist": "^10.0.1", | ||
| "npm-pick-manifest": "^11.0.1", | ||
| "npm-registry-fetch": "^19.0.0", | ||
| "proc-log": "^6.0.0", | ||
| "sigstore": "^4.0.0", | ||
| "ssri": "^13.0.0", | ||
| "npm-package-arg": "^14.0.0", | ||
| "npm-packlist": "^11.2.0", | ||
| "npm-pick-manifest": "^12.0.0", | ||
| "npm-registry-fetch": "^20.0.1", | ||
| "proc-log": "^7.0.0", | ||
| "sigstore": "^5.0.0", | ||
| "ssri": "^14.0.0", | ||
| "tar": "^7.4.3" | ||
| }, | ||
| "engines": { | ||
| "node": "^20.17.0 || >=22.9.0" | ||
| "node": "^22.22.2 || ^24.15.0 || >=26.0.0" | ||
| }, | ||
@@ -80,4 +80,5 @@ "repository": { | ||
| "windowsCI": false, | ||
| "publish": "true" | ||
| "publish": "true", | ||
| "updateNpm": false | ||
| } | ||
| } |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
80176
4.32%1666
3.74%11
22.22%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated
Updated