Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Use an appropriate Node.js base image (v20 is suitable for current npm cli)
FROM node:20

# Set the working directory
WORKDIR /usr/src/app

# Copy application source code (required first for workspaces)
COPY . .

# Install dependencies statically
RUN npm install

# Default command to run the tests
CMD ["npm", "test"]
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,17 @@ npm <command>
#### Is "npm" an acronym for "Node Package Manager"?

Contrary to popular belief, **`npm`** **is not** in fact an acronym for "Node Package Manager"; It is a recursive bacronymic abbreviation for **"npm is not an acronym"** (if the project was named "ninaa", then it would be an acronym). The precursor to **`npm`** was actually a bash utility named **"pm"**, which was the shortform name of **"pkgmakeinst"** - a bash function that installed various things on various platforms. If **`npm`** were to ever have been considered an acronym, it would be as "node pm" or, potentially "new pm".

## How to Run the tests (Local Development)

1. Install dependencies from source:
`ash
npm install
`

2. Run tests:
`ash
npm test
`

*(Note: Node.js version 14 or compatible is recommended for testing this repository)*
140 changes: 70 additions & 70 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,31 +51,31 @@
"./package.json": "./package.json"
},
"dependencies": {
"@isaacs/string-locale-compare": "^1.1.0",
"@isaacs/string-locale-compare": "1.1.0",
"@npmcli/arborist": "^9.1.2",
"@npmcli/config": "^10.3.0",
"@npmcli/fs": "^4.0.0",
"@npmcli/map-workspaces": "^4.0.2",
"@npmcli/package-json": "^6.2.0",
"@npmcli/promise-spawn": "^8.0.2",
"@npmcli/redact": "^3.2.2",
"@npmcli/run-script": "^9.1.0",
"@sigstore/tuf": "^3.1.1",
"abbrev": "^3.0.1",
"archy": "~1.0.0",
"cacache": "^19.0.1",
"chalk": "^5.4.1",
"ci-info": "^4.2.0",
"cli-columns": "^4.0.0",
"fastest-levenshtein": "^1.0.16",
"fs-minipass": "^3.0.3",
"glob": "^10.4.5",
"graceful-fs": "^4.2.11",
"hosted-git-info": "^8.1.0",
"ini": "^5.0.0",
"init-package-json": "^8.2.1",
"is-cidr": "^5.1.1",
"json-parse-even-better-errors": "^4.0.0",
"@npmcli/fs": "4.0.0",
"@npmcli/map-workspaces": "4.0.2",
"@npmcli/package-json": "6.2.0",
"@npmcli/promise-spawn": "8.0.2",
"@npmcli/redact": "3.2.2",
"@npmcli/run-script": "9.1.0",
"@sigstore/tuf": "3.1.1",
"abbrev": "3.0.1",
"archy": "1.0.0",
"cacache": "19.0.1",
"chalk": "5.4.1",
"ci-info": "4.2.0",
"cli-columns": "4.0.0",
"fastest-levenshtein": "1.0.16",
"fs-minipass": "3.0.3",
"glob": "10.4.5",
"graceful-fs": "4.2.11",
"hosted-git-info": "8.1.0",
"ini": "5.0.0",
"init-package-json": "8.2.1",
"is-cidr": "5.1.1",
"json-parse-even-better-errors": "4.0.0",
"libnpmaccess": "^10.0.1",
"libnpmdiff": "^8.0.5",
"libnpmexec": "^10.1.4",
Expand All @@ -86,37 +86,37 @@
"libnpmsearch": "^9.0.0",
"libnpmteam": "^8.0.1",
"libnpmversion": "^8.0.1",
"make-fetch-happen": "^14.0.3",
"minimatch": "^9.0.5",
"minipass": "^7.1.1",
"minipass-pipeline": "^1.2.4",
"ms": "^2.1.2",
"node-gyp": "^11.2.0",
"nopt": "^8.1.0",
"normalize-package-data": "^7.0.0",
"npm-audit-report": "^6.0.0",
"npm-install-checks": "^7.1.1",
"npm-package-arg": "^12.0.2",
"npm-pick-manifest": "^10.0.0",
"npm-profile": "^11.0.1",
"npm-registry-fetch": "^18.0.2",
"npm-user-validate": "^3.0.0",
"p-map": "^7.0.3",
"pacote": "^21.0.0",
"parse-conflict-json": "^4.0.0",
"proc-log": "^5.0.0",
"qrcode-terminal": "^0.12.0",
"read": "^4.1.0",
"semver": "^7.7.2",
"spdx-expression-parse": "^4.0.0",
"ssri": "^12.0.0",
"supports-color": "^10.0.0",
"tar": "^6.2.1",
"text-table": "~0.2.0",
"tiny-relative-date": "^1.3.0",
"treeverse": "^3.0.0",
"validate-npm-package-name": "^6.0.1",
"which": "^5.0.0"
"make-fetch-happen": "14.0.3",
"minimatch": "9.0.5",
"minipass": "7.1.2",
"minipass-pipeline": "1.2.4",
"ms": "2.1.3",
"node-gyp": "11.2.0",
"nopt": "8.1.0",
"normalize-package-data": "7.0.0",
"npm-audit-report": "6.0.0",
"npm-install-checks": "7.1.1",
"npm-package-arg": "12.0.2",
"npm-pick-manifest": "10.0.0",
"npm-profile": "11.0.1",
"npm-registry-fetch": "18.0.2",
"npm-user-validate": "3.0.0",
"p-map": "7.0.3",
"pacote": "21.0.0",
"parse-conflict-json": "4.0.0",
"proc-log": "5.0.0",
"qrcode-terminal": "0.12.0",
"read": "4.1.0",
"semver": "7.7.2",
"spdx-expression-parse": "4.0.0",
"ssri": "12.0.0",
"supports-color": "10.0.0",
"tar": "6.2.1",
"text-table": "0.2.0",
"tiny-relative-date": "1.3.0",
"treeverse": "3.0.0",
"validate-npm-package-name": "6.0.1",
"which": "5.0.0"
},
"bundleDependencies": [
"@isaacs/string-locale-compare",
Expand Down Expand Up @@ -188,25 +188,25 @@
],
"devDependencies": {
"@npmcli/docs": "^1.0.0",
"@npmcli/eslint-config": "^5.1.0",
"@npmcli/git": "^6.0.3",
"@npmcli/eslint-config": "5.1.0",
"@npmcli/git": "6.0.3",
"@npmcli/mock-globals": "^1.0.0",
"@npmcli/mock-registry": "^1.0.0",
"@npmcli/template-oss": "4.24.4",
"@tufjs/repo-mock": "^3.0.1",
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"ajv-formats-draft2019": "^1.6.1",
"cli-table3": "^0.6.4",
"diff": "^7.0.0",
"nock": "^13.4.0",
"npm-packlist": "^10.0.0",
"remark": "^14.0.2",
"remark-gfm": "^3.0.1",
"remark-github": "^11.2.4",
"rimraf": "^5.0.5",
"spawk": "^1.7.1",
"tap": "^16.3.9"
"@tufjs/repo-mock": "3.0.1",
"ajv": "8.17.1",
"ajv-formats": "2.1.1",
"ajv-formats-draft2019": "1.6.1",
"cli-table3": "0.6.5",
"diff": "7.0.0",
"nock": "13.5.6",
"npm-packlist": "10.0.0",
"remark": "14.0.3",
"remark-gfm": "3.0.1",
"remark-github": "11.2.4",
"rimraf": "5.0.10",
"spawk": "1.8.2",
"tap": "16.3.10"
},
"scripts": {
"dependencies": "node scripts/bundle-and-gitignore-deps.js && node scripts/dependency-graph.js",
Expand Down
3 changes: 2 additions & 1 deletion workspaces/arborist/lib/arborist/build-ideal-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,9 @@ module.exports = cls => class IdealTreeBuilder extends cls {

async #checkEngineAndPlatform () {
const { engineStrict, npmVersion, nodeVersion } = this.options
const omit = new Set(this.options.omit || [])
for (const node of this.idealTree.inventory.values()) {
if (!node.optional) {
if (!node.optional && !node.shouldOmit(omit)) {
try {
// if devEngines is present in the root node we ignore the engines check
if (!(node.isRoot && node.package.devEngines)) {
Expand Down
18 changes: 4 additions & 14 deletions workspaces/arborist/lib/arborist/reify.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,7 @@ module.exports = cls => class Reifier extends cls {
#bundleUnpacked = new Set() // the nodes we unpack to read their bundles
#dryRun
#nmValidated = new Set()
#omitDev
#omitPeer
#omitOptional
#omitSet = new Set()
#retiredPaths = {}
#retiredUnchanged = {}
#savePrefix
Expand All @@ -110,10 +108,7 @@ module.exports = cls => class Reifier extends cls {
throw er
}

const omit = new Set(options.omit || [])
this.#omitDev = omit.has('dev')
this.#omitOptional = omit.has('optional')
this.#omitPeer = omit.has('peer')
this.#omitSet = new Set(options.omit || [])

// start tracker block
this.addTracker('reify')
Expand Down Expand Up @@ -562,7 +557,7 @@ module.exports = cls => class Reifier extends cls {
// adding to the trash list will skip reifying, and delete them
// if they are currently in the tree and otherwise untouched.
[_addOmitsToTrashList] () {
if (!this.#omitDev && !this.#omitOptional && !this.#omitPeer) {
if (!this.#omitSet.size) {
return
}

Expand All @@ -583,12 +578,7 @@ module.exports = cls => class Reifier extends cls {
}

// omit node if the dep type matches any omit flags that were set
if (
node.peer && this.#omitPeer ||
node.dev && this.#omitDev ||
node.optional && this.#omitOptional ||
node.devOptional && this.#omitOptional && this.#omitDev
) {
if (node.shouldOmit(this.#omitSet)) {
this[_addNodeToTrashList](node)
}
}
Expand Down
8 changes: 1 addition & 7 deletions workspaces/arborist/lib/audit-report.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,13 +316,7 @@ const shouldAudit = (node, omit, filterSet) =>
!node.version ? false
: node.isRoot ? false
: filterSet && filterSet.size !== 0 && !filterSet.has(node) ? false
: omit.size === 0 ? true
: !( // otherwise, just ensure we're not omitting this one
node.dev && omit.has('dev') ||
node.optional && omit.has('optional') ||
node.devOptional && omit.has('dev') && omit.has('optional') ||
node.peer && omit.has('peer')
)
: !node.shouldOmit(omit)

const prepareBulkData = (tree, omit, filterSet) => {
const payload = {}
Expand Down
37 changes: 37 additions & 0 deletions workspaces/arborist/lib/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,43 @@ class Node {
return this === this.root || this === this.root.target
}

// Stateless predicate that determines whether this node should be
// excluded from an operation (engine check, reify, audit) given the
// caller-supplied omitSet. No omit configuration is cached on the
// node itself, so sequential calls with different sets (e.g. audit
// followed by reify with different --omit flags) are always correct.
//
// A node is omitted when its dep-type flag matches an entry in the set:
// dev → omitSet.has('dev')
// optional → omitSet.has('optional')
// peer → omitSet.has('peer')
//
// devOptional is special: it marks a node that exists in the *overlap*
// of the dev and optional dependency trees (see calc-dep-flags.js).
// Because such a node is reachable through either tree, it can only be
// safely omitted when *both* trees are excluded — i.e. the set must
// contain both 'dev' AND 'optional'. Omitting only one still leaves
// the node reachable through the other tree, so it must be kept.
shouldOmit (omitSet) {
if (!omitSet) {
return false
}

// coerce array input to a Set for callers that pass options.omit
// directly without wrapping
const set = omitSet instanceof Set ? omitSet : new Set(omitSet)

if (set.size === 0) {
return false
}
return !!(
this.peer && set.has('peer') ||
this.dev && set.has('dev') ||
this.optional && set.has('optional') ||
this.devOptional && set.has('optional') && set.has('dev')
)
}

get isRegistryDependency () {
if (this.edgesIn.size === 0) {
return false
Expand Down
Loading