Skip to content
Merged
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
5 changes: 4 additions & 1 deletion workspaces/arborist/lib/arborist/load-actual.js
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
// mix-in implementing the loadActual method

const { dirname, join, normalize, relative, resolve } = require('node:path')
const { dirname, join, normalize, relative, resolve, sep } = require('node:path')

const PackageJson = require('@npmcli/package-json')
const { readdirScoped } = require('@npmcli/fs')
Expand DownExpand Up@@ -269,6 +269,9 @@ module.exports = cls => class ActualLoader extends cls {
parent,
root,
loadOverrides,
// A package physically located in the linked strategy's store is a transitive dependency, not a real tree top, so it must not load its devDependencies.
// Never flag the loaded root itself, even if its own path happens to sit under a .store directory.
isInStore: path !== this.path && real.includes(`${sep}node_modules${sep}.store${sep}`),
}

try {
Expand Down
3 changes: 2 additions & 1 deletion workspaces/arborist/lib/node.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -936,7 +936,8 @@ class Node {
isTop: srcTop,
path: srcPath,
} = sourceReference || {}
const thisDev = isTop && !globalTop && path
// A package in the linked strategy's .store is a transitive dependency that is structurally a tree top, but its devDependencies are never installed or required, so they must not be loaded.
const thisDev = isTop && !globalTop && path && !this.isInStore
const srcDev = !sourceReference || srcTop && !srcGlobalTop && srcPath
if (thisDev && srcDev) {
this.#loadDepType(this.package.devDependencies, 'dev', ad)
Expand Down
60 changes: 59 additions & 1 deletion workspaces/arborist/test/arborist/load-actual.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@ const t = require('tap')
const { format } = require('tcompare')
const Arborist = require('../../lib/arborist')

const { resolve } = require('node:path')
const { join, resolve } = require('node:path')
const Node = require('../../lib/node.js')
const Shrinkwrap = require('../../lib/shrinkwrap.js')
const fs = require('node:fs')
Expand DownExpand Up@@ -545,3 +545,61 @@ t.test('applies root packageExtensions to a linked actual tree', async t => {
t.strictSame(broken.packageExtensionsApplied, applied, 'provenance recorded on the store node')
t.strictSame(brokenLink.packageExtensionsApplied, applied, 'provenance mirrored onto the link')
})

t.test('store nodes do not load devDependencies as required edges', async t => {
// A package in the linked store is structurally a tree top, so without the isInStore guard its devDependencies would load as required edges and surface as missing (e.g. npm sbom ESBOMPROBLEMS).
const path = t.testdir({
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
dependencies: { dep: '1.0.0' },
}),
node_modules: {
dep: t.fixture('symlink', '.store/dep@1.0.0/node_modules/dep'),
'.store': {
'dep@1.0.0': {
node_modules: {
dep: {
'package.json': JSON.stringify({
name: 'dep',
version: '1.0.0',
devDependencies: { 'a-dev-dep': '^1.0.0' },
}),
},
},
},
},
},
})

const tree = await loadActual(path)
const dep = tree.children.get('dep').target
t.equal(dep.isInStore, true, 'store node is flagged isInStore')
t.notOk(dep.edgesOut.get('a-dev-dep'), 'devDependency of a store node is not a required edge')
})

t.test('a project located under a .store path still loads its own devDependencies', async t => {
// The loaded root must never be treated as a store node, even when its own path happens to sit under a node_modules/.store directory.
const path = t.testdir({
node_modules: {
'.store': {
'root@1.0.0': {
node_modules: {
root: {
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
devDependencies: { 'a-dev-dep': '^1.0.0' },
}),
},
},
},
},
},
})
const root = join(path, 'node_modules/.store/root@1.0.0/node_modules/root')

const tree = await loadActual(root)
t.equal(tree.isInStore, false, 'loaded root is not flagged isInStore')
t.ok(tree.edgesOut.get('a-dev-dep'), 'root devDependency is a required edge')
})
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix(arborist): don't load store packages' devDependencies as required edges by manzoorwanijk · Pull Request #9626 · npm/cli · GitHub
Skip to content
Merged
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
5 changes: 4 additions & 1 deletion workspaces/arborist/lib/arborist/load-actual.js
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
// mix-in implementing the loadActual method

const { dirname, join, normalize, relative, resolve } = require('node:path')
const { dirname, join, normalize, relative, resolve, sep } = require('node:path')

const PackageJson = require('@npmcli/package-json')
const { readdirScoped } = require('@npmcli/fs')
Expand DownExpand Up@@ -269,6 +269,9 @@ module.exports = cls => class ActualLoader extends cls {
parent,
root,
loadOverrides,
// A package physically located in the linked strategy's store is a transitive dependency, not a real tree top, so it must not load its devDependencies.
// Never flag the loaded root itself, even if its own path happens to sit under a .store directory.
isInStore: path !== this.path && real.includes(`${sep}node_modules${sep}.store${sep}`),
}

try {
Expand Down
3 changes: 2 additions & 1 deletion workspaces/arborist/lib/node.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -936,7 +936,8 @@ class Node {
isTop: srcTop,
path: srcPath,
} = sourceReference || {}
const thisDev = isTop && !globalTop && path
// A package in the linked strategy's .store is a transitive dependency that is structurally a tree top, but its devDependencies are never installed or required, so they must not be loaded.
const thisDev = isTop && !globalTop && path && !this.isInStore
const srcDev = !sourceReference || srcTop && !srcGlobalTop && srcPath
if (thisDev && srcDev) {
this.#loadDepType(this.package.devDependencies, 'dev', ad)
Expand Down
60 changes: 59 additions & 1 deletion workspaces/arborist/test/arborist/load-actual.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@ const t = require('tap')
const { format } = require('tcompare')
const Arborist = require('../../lib/arborist')

const { resolve } = require('node:path')
const { join, resolve } = require('node:path')
const Node = require('../../lib/node.js')
const Shrinkwrap = require('../../lib/shrinkwrap.js')
const fs = require('node:fs')
Expand DownExpand Up@@ -545,3 +545,61 @@ t.test('applies root packageExtensions to a linked actual tree', async t => {
t.strictSame(broken.packageExtensionsApplied, applied, 'provenance recorded on the store node')
t.strictSame(brokenLink.packageExtensionsApplied, applied, 'provenance mirrored onto the link')
})

t.test('store nodes do not load devDependencies as required edges', async t => {
// A package in the linked store is structurally a tree top, so without the isInStore guard its devDependencies would load as required edges and surface as missing (e.g. npm sbom ESBOMPROBLEMS).
const path = t.testdir({
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
dependencies: { dep: '1.0.0' },
}),
node_modules: {
dep: t.fixture('symlink', '.store/dep@1.0.0/node_modules/dep'),
'.store': {
'dep@1.0.0': {
node_modules: {
dep: {
'package.json': JSON.stringify({
name: 'dep',
version: '1.0.0',
devDependencies: { 'a-dev-dep': '^1.0.0' },
}),
},
},
},
},
},
})

const tree = await loadActual(path)
const dep = tree.children.get('dep').target
t.equal(dep.isInStore, true, 'store node is flagged isInStore')
t.notOk(dep.edgesOut.get('a-dev-dep'), 'devDependency of a store node is not a required edge')
})

t.test('a project located under a .store path still loads its own devDependencies', async t => {
// The loaded root must never be treated as a store node, even when its own path happens to sit under a node_modules/.store directory.
const path = t.testdir({
node_modules: {
'.store': {
'root@1.0.0': {
node_modules: {
root: {
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
devDependencies: { 'a-dev-dep': '^1.0.0' },
}),
},
},
},
},
},
})
const root = join(path, 'node_modules/.store/root@1.0.0/node_modules/root')

const tree = await loadActual(root)
t.equal(tree.isInStore, false, 'loaded root is not flagged isInStore')
t.ok(tree.edgesOut.get('a-dev-dep'), 'root devDependency is a required edge')
})
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(arborist): don't load store packages' devDependencies as required edges by manzoorwanijk · Pull Request #9626 · npm/cli · GitHub
Skip to content
Merged
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
5 changes: 4 additions & 1 deletion workspaces/arborist/lib/arborist/load-actual.js
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
// mix-in implementing the loadActual method

const { dirname, join, normalize, relative, resolve } = require('node:path')
const { dirname, join, normalize, relative, resolve, sep } = require('node:path')

const PackageJson = require('@npmcli/package-json')
const { readdirScoped } = require('@npmcli/fs')
Expand DownExpand Up@@ -269,6 +269,9 @@ module.exports = cls => class ActualLoader extends cls {
parent,
root,
loadOverrides,
// A package physically located in the linked strategy's store is a transitive dependency, not a real tree top, so it must not load its devDependencies.
// Never flag the loaded root itself, even if its own path happens to sit under a .store directory.
isInStore: path !== this.path && real.includes(`${sep}node_modules${sep}.store${sep}`),
}

try {
Expand Down
3 changes: 2 additions & 1 deletion workspaces/arborist/lib/node.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -936,7 +936,8 @@ class Node {
isTop: srcTop,
path: srcPath,
} = sourceReference || {}
const thisDev = isTop && !globalTop && path
// A package in the linked strategy's .store is a transitive dependency that is structurally a tree top, but its devDependencies are never installed or required, so they must not be loaded.
const thisDev = isTop && !globalTop && path && !this.isInStore
const srcDev = !sourceReference || srcTop && !srcGlobalTop && srcPath
if (thisDev && srcDev) {
this.#loadDepType(this.package.devDependencies, 'dev', ad)
Expand Down
60 changes: 59 additions & 1 deletion workspaces/arborist/test/arborist/load-actual.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@ const t = require('tap')
const { format } = require('tcompare')
const Arborist = require('../../lib/arborist')

const { resolve } = require('node:path')
const { join, resolve } = require('node:path')
const Node = require('../../lib/node.js')
const Shrinkwrap = require('../../lib/shrinkwrap.js')
const fs = require('node:fs')
Expand DownExpand Up@@ -545,3 +545,61 @@ t.test('applies root packageExtensions to a linked actual tree', async t => {
t.strictSame(broken.packageExtensionsApplied, applied, 'provenance recorded on the store node')
t.strictSame(brokenLink.packageExtensionsApplied, applied, 'provenance mirrored onto the link')
})

t.test('store nodes do not load devDependencies as required edges', async t => {
// A package in the linked store is structurally a tree top, so without the isInStore guard its devDependencies would load as required edges and surface as missing (e.g. npm sbom ESBOMPROBLEMS).
const path = t.testdir({
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
dependencies: { dep: '1.0.0' },
}),
node_modules: {
dep: t.fixture('symlink', '.store/dep@1.0.0/node_modules/dep'),
'.store': {
'dep@1.0.0': {
node_modules: {
dep: {
'package.json': JSON.stringify({
name: 'dep',
version: '1.0.0',
devDependencies: { 'a-dev-dep': '^1.0.0' },
}),
},
},
},
},
},
})

const tree = await loadActual(path)
const dep = tree.children.get('dep').target
t.equal(dep.isInStore, true, 'store node is flagged isInStore')
t.notOk(dep.edgesOut.get('a-dev-dep'), 'devDependency of a store node is not a required edge')
})

t.test('a project located under a .store path still loads its own devDependencies', async t => {
// The loaded root must never be treated as a store node, even when its own path happens to sit under a node_modules/.store directory.
const path = t.testdir({
node_modules: {
'.store': {
'root@1.0.0': {
node_modules: {
root: {
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
devDependencies: { 'a-dev-dep': '^1.0.0' },
}),
},
},
},
},
},
})
const root = join(path, 'node_modules/.store/root@1.0.0/node_modules/root')

const tree = await loadActual(root)
t.equal(tree.isInStore, false, 'loaded root is not flagged isInStore')
t.ok(tree.edgesOut.get('a-dev-dep'), 'root devDependency is a required edge')
})
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(arborist): don't load store packages' devDependencies as required edges by manzoorwanijk · Pull Request #9626 · npm/cli · GitHub
Skip to content
Merged
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
5 changes: 4 additions & 1 deletion workspaces/arborist/lib/arborist/load-actual.js
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
// mix-in implementing the loadActual method

const { dirname, join, normalize, relative, resolve } = require('node:path')
const { dirname, join, normalize, relative, resolve, sep } = require('node:path')

const PackageJson = require('@npmcli/package-json')
const { readdirScoped } = require('@npmcli/fs')
Expand DownExpand Up@@ -269,6 +269,9 @@ module.exports = cls => class ActualLoader extends cls {
parent,
root,
loadOverrides,
// A package physically located in the linked strategy's store is a transitive dependency, not a real tree top, so it must not load its devDependencies.
// Never flag the loaded root itself, even if its own path happens to sit under a .store directory.
isInStore: path !== this.path && real.includes(`${sep}node_modules${sep}.store${sep}`),
}

try {
Expand Down
3 changes: 2 additions & 1 deletion workspaces/arborist/lib/node.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -936,7 +936,8 @@ class Node {
isTop: srcTop,
path: srcPath,
} = sourceReference || {}
const thisDev = isTop && !globalTop && path
// A package in the linked strategy's .store is a transitive dependency that is structurally a tree top, but its devDependencies are never installed or required, so they must not be loaded.
const thisDev = isTop && !globalTop && path && !this.isInStore
const srcDev = !sourceReference || srcTop && !srcGlobalTop && srcPath
if (thisDev && srcDev) {
this.#loadDepType(this.package.devDependencies, 'dev', ad)
Expand Down
60 changes: 59 additions & 1 deletion workspaces/arborist/test/arborist/load-actual.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@ const t = require('tap')
const { format } = require('tcompare')
const Arborist = require('../../lib/arborist')

const { resolve } = require('node:path')
const { join, resolve } = require('node:path')
const Node = require('../../lib/node.js')
const Shrinkwrap = require('../../lib/shrinkwrap.js')
const fs = require('node:fs')
Expand DownExpand Up@@ -545,3 +545,61 @@ t.test('applies root packageExtensions to a linked actual tree', async t => {
t.strictSame(broken.packageExtensionsApplied, applied, 'provenance recorded on the store node')
t.strictSame(brokenLink.packageExtensionsApplied, applied, 'provenance mirrored onto the link')
})

t.test('store nodes do not load devDependencies as required edges', async t => {
// A package in the linked store is structurally a tree top, so without the isInStore guard its devDependencies would load as required edges and surface as missing (e.g. npm sbom ESBOMPROBLEMS).
const path = t.testdir({
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
dependencies: { dep: '1.0.0' },
}),
node_modules: {
dep: t.fixture('symlink', '.store/dep@1.0.0/node_modules/dep'),
'.store': {
'dep@1.0.0': {
node_modules: {
dep: {
'package.json': JSON.stringify({
name: 'dep',
version: '1.0.0',
devDependencies: { 'a-dev-dep': '^1.0.0' },
}),
},
},
},
},
},
})

const tree = await loadActual(path)
const dep = tree.children.get('dep').target
t.equal(dep.isInStore, true, 'store node is flagged isInStore')
t.notOk(dep.edgesOut.get('a-dev-dep'), 'devDependency of a store node is not a required edge')
})

t.test('a project located under a .store path still loads its own devDependencies', async t => {
// The loaded root must never be treated as a store node, even when its own path happens to sit under a node_modules/.store directory.
const path = t.testdir({
node_modules: {
'.store': {
'root@1.0.0': {
node_modules: {
root: {
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
devDependencies: { 'a-dev-dep': '^1.0.0' },
}),
},
},
},
},
},
})
const root = join(path, 'node_modules/.store/root@1.0.0/node_modules/root')

const tree = await loadActual(root)
t.equal(tree.isInStore, false, 'loaded root is not flagged isInStore')
t.ok(tree.edgesOut.get('a-dev-dep'), 'root devDependency is a required edge')
})
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' fix(arborist): don't load store packages' devDependencies as required edges by manzoorwanijk · Pull Request #9626 · npm/cli · GitHub
Skip to content
Merged
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
5 changes: 4 additions & 1 deletion workspaces/arborist/lib/arborist/load-actual.js
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
// mix-in implementing the loadActual method

const { dirname, join, normalize, relative, resolve } = require('node:path')
const { dirname, join, normalize, relative, resolve, sep } = require('node:path')

const PackageJson = require('@npmcli/package-json')
const { readdirScoped } = require('@npmcli/fs')
Expand DownExpand Up@@ -269,6 +269,9 @@ module.exports = cls => class ActualLoader extends cls {
parent,
root,
loadOverrides,
// A package physically located in the linked strategy's store is a transitive dependency, not a real tree top, so it must not load its devDependencies.
// Never flag the loaded root itself, even if its own path happens to sit under a .store directory.
isInStore: path !== this.path && real.includes(`${sep}node_modules${sep}.store${sep}`),
}

try {
Expand Down
3 changes: 2 additions & 1 deletion workspaces/arborist/lib/node.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -936,7 +936,8 @@ class Node {
isTop: srcTop,
path: srcPath,
} = sourceReference || {}
const thisDev = isTop && !globalTop && path
// A package in the linked strategy's .store is a transitive dependency that is structurally a tree top, but its devDependencies are never installed or required, so they must not be loaded.
const thisDev = isTop && !globalTop && path && !this.isInStore
const srcDev = !sourceReference || srcTop && !srcGlobalTop && srcPath
if (thisDev && srcDev) {
this.#loadDepType(this.package.devDependencies, 'dev', ad)
Expand Down
60 changes: 59 additions & 1 deletion workspaces/arborist/test/arborist/load-actual.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@ const t = require('tap')
const { format } = require('tcompare')
const Arborist = require('../../lib/arborist')

const { resolve } = require('node:path')
const { join, resolve } = require('node:path')
const Node = require('../../lib/node.js')
const Shrinkwrap = require('../../lib/shrinkwrap.js')
const fs = require('node:fs')
Expand DownExpand Up@@ -545,3 +545,61 @@ t.test('applies root packageExtensions to a linked actual tree', async t => {
t.strictSame(broken.packageExtensionsApplied, applied, 'provenance recorded on the store node')
t.strictSame(brokenLink.packageExtensionsApplied, applied, 'provenance mirrored onto the link')
})

t.test('store nodes do not load devDependencies as required edges', async t => {
// A package in the linked store is structurally a tree top, so without the isInStore guard its devDependencies would load as required edges and surface as missing (e.g. npm sbom ESBOMPROBLEMS).
const path = t.testdir({
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
dependencies: { dep: '1.0.0' },
}),
node_modules: {
dep: t.fixture('symlink', '.store/dep@1.0.0/node_modules/dep'),
'.store': {
'dep@1.0.0': {
node_modules: {
dep: {
'package.json': JSON.stringify({
name: 'dep',
version: '1.0.0',
devDependencies: { 'a-dev-dep': '^1.0.0' },
}),
},
},
},
},
},
})

const tree = await loadActual(path)
const dep = tree.children.get('dep').target
t.equal(dep.isInStore, true, 'store node is flagged isInStore')
t.notOk(dep.edgesOut.get('a-dev-dep'), 'devDependency of a store node is not a required edge')
})

t.test('a project located under a .store path still loads its own devDependencies', async t => {
// The loaded root must never be treated as a store node, even when its own path happens to sit under a node_modules/.store directory.
const path = t.testdir({
node_modules: {
'.store': {
'root@1.0.0': {
node_modules: {
root: {
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
devDependencies: { 'a-dev-dep': '^1.0.0' },
}),
},
},
},
},
},
})
const root = join(path, 'node_modules/.store/root@1.0.0/node_modules/root')

const tree = await loadActual(root)
t.equal(tree.isInStore, false, 'loaded root is not flagged isInStore')
t.ok(tree.edgesOut.get('a-dev-dep'), 'root devDependency is a required edge')
})
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(arborist): don't load store packages' devDependencies as required edges by manzoorwanijk · Pull Request #9626 · npm/cli · GitHub
Skip to content
Merged
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
5 changes: 4 additions & 1 deletion workspaces/arborist/lib/arborist/load-actual.js
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
// mix-in implementing the loadActual method

const { dirname, join, normalize, relative, resolve } = require('node:path')
const { dirname, join, normalize, relative, resolve, sep } = require('node:path')

const PackageJson = require('@npmcli/package-json')
const { readdirScoped } = require('@npmcli/fs')
Expand DownExpand Up@@ -269,6 +269,9 @@ module.exports = cls => class ActualLoader extends cls {
parent,
root,
loadOverrides,
// A package physically located in the linked strategy's store is a transitive dependency, not a real tree top, so it must not load its devDependencies.
// Never flag the loaded root itself, even if its own path happens to sit under a .store directory.
isInStore: path !== this.path && real.includes(`${sep}node_modules${sep}.store${sep}`),
}

try {
Expand Down
3 changes: 2 additions & 1 deletion workspaces/arborist/lib/node.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -936,7 +936,8 @@ class Node {
isTop: srcTop,
path: srcPath,
} = sourceReference || {}
const thisDev = isTop && !globalTop && path
// A package in the linked strategy's .store is a transitive dependency that is structurally a tree top, but its devDependencies are never installed or required, so they must not be loaded.
const thisDev = isTop && !globalTop && path && !this.isInStore
const srcDev = !sourceReference || srcTop && !srcGlobalTop && srcPath
if (thisDev && srcDev) {
this.#loadDepType(this.package.devDependencies, 'dev', ad)
Expand Down
60 changes: 59 additions & 1 deletion workspaces/arborist/test/arborist/load-actual.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@ const t = require('tap')
const { format } = require('tcompare')
const Arborist = require('../../lib/arborist')

const { resolve } = require('node:path')
const { join, resolve } = require('node:path')
const Node = require('../../lib/node.js')
const Shrinkwrap = require('../../lib/shrinkwrap.js')
const fs = require('node:fs')
Expand DownExpand Up@@ -545,3 +545,61 @@ t.test('applies root packageExtensions to a linked actual tree', async t => {
t.strictSame(broken.packageExtensionsApplied, applied, 'provenance recorded on the store node')
t.strictSame(brokenLink.packageExtensionsApplied, applied, 'provenance mirrored onto the link')
})

t.test('store nodes do not load devDependencies as required edges', async t => {
// A package in the linked store is structurally a tree top, so without the isInStore guard its devDependencies would load as required edges and surface as missing (e.g. npm sbom ESBOMPROBLEMS).
const path = t.testdir({
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
dependencies: { dep: '1.0.0' },
}),
node_modules: {
dep: t.fixture('symlink', '.store/dep@1.0.0/node_modules/dep'),
'.store': {
'dep@1.0.0': {
node_modules: {
dep: {
'package.json': JSON.stringify({
name: 'dep',
version: '1.0.0',
devDependencies: { 'a-dev-dep': '^1.0.0' },
}),
},
},
},
},
},
})

const tree = await loadActual(path)
const dep = tree.children.get('dep').target
t.equal(dep.isInStore, true, 'store node is flagged isInStore')
t.notOk(dep.edgesOut.get('a-dev-dep'), 'devDependency of a store node is not a required edge')
})

t.test('a project located under a .store path still loads its own devDependencies', async t => {
// The loaded root must never be treated as a store node, even when its own path happens to sit under a node_modules/.store directory.
const path = t.testdir({
node_modules: {
'.store': {
'root@1.0.0': {
node_modules: {
root: {
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
devDependencies: { 'a-dev-dep': '^1.0.0' },
}),
},
},
},
},
},
})
const root = join(path, 'node_modules/.store/root@1.0.0/node_modules/root')

const tree = await loadActual(root)
t.equal(tree.isInStore, false, 'loaded root is not flagged isInStore')
t.ok(tree.edgesOut.get('a-dev-dep'), 'root devDependency is a required edge')
})
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(arborist): don't load store packages' devDependencies as required edges by manzoorwanijk · Pull Request #9626 · npm/cli · GitHub
Skip to content
Merged
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
5 changes: 4 additions & 1 deletion workspaces/arborist/lib/arborist/load-actual.js
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
// mix-in implementing the loadActual method

const { dirname, join, normalize, relative, resolve } = require('node:path')
const { dirname, join, normalize, relative, resolve, sep } = require('node:path')

const PackageJson = require('@npmcli/package-json')
const { readdirScoped } = require('@npmcli/fs')
Expand DownExpand Up@@ -269,6 +269,9 @@ module.exports = cls => class ActualLoader extends cls {
parent,
root,
loadOverrides,
// A package physically located in the linked strategy's store is a transitive dependency, not a real tree top, so it must not load its devDependencies.
// Never flag the loaded root itself, even if its own path happens to sit under a .store directory.
isInStore: path !== this.path && real.includes(`${sep}node_modules${sep}.store${sep}`),
}

try {
Expand Down
3 changes: 2 additions & 1 deletion workspaces/arborist/lib/node.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -936,7 +936,8 @@ class Node {
isTop: srcTop,
path: srcPath,
} = sourceReference || {}
const thisDev = isTop && !globalTop && path
// A package in the linked strategy's .store is a transitive dependency that is structurally a tree top, but its devDependencies are never installed or required, so they must not be loaded.
const thisDev = isTop && !globalTop && path && !this.isInStore
const srcDev = !sourceReference || srcTop && !srcGlobalTop && srcPath
if (thisDev && srcDev) {
this.#loadDepType(this.package.devDependencies, 'dev', ad)
Expand Down
60 changes: 59 additions & 1 deletion workspaces/arborist/test/arborist/load-actual.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@ const t = require('tap')
const { format } = require('tcompare')
const Arborist = require('../../lib/arborist')

const { resolve } = require('node:path')
const { join, resolve } = require('node:path')
const Node = require('../../lib/node.js')
const Shrinkwrap = require('../../lib/shrinkwrap.js')
const fs = require('node:fs')
Expand DownExpand Up@@ -545,3 +545,61 @@ t.test('applies root packageExtensions to a linked actual tree', async t => {
t.strictSame(broken.packageExtensionsApplied, applied, 'provenance recorded on the store node')
t.strictSame(brokenLink.packageExtensionsApplied, applied, 'provenance mirrored onto the link')
})

t.test('store nodes do not load devDependencies as required edges', async t => {
// A package in the linked store is structurally a tree top, so without the isInStore guard its devDependencies would load as required edges and surface as missing (e.g. npm sbom ESBOMPROBLEMS).
const path = t.testdir({
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
dependencies: { dep: '1.0.0' },
}),
node_modules: {
dep: t.fixture('symlink', '.store/dep@1.0.0/node_modules/dep'),
'.store': {
'dep@1.0.0': {
node_modules: {
dep: {
'package.json': JSON.stringify({
name: 'dep',
version: '1.0.0',
devDependencies: { 'a-dev-dep': '^1.0.0' },
}),
},
},
},
},
},
})

const tree = await loadActual(path)
const dep = tree.children.get('dep').target
t.equal(dep.isInStore, true, 'store node is flagged isInStore')
t.notOk(dep.edgesOut.get('a-dev-dep'), 'devDependency of a store node is not a required edge')
})

t.test('a project located under a .store path still loads its own devDependencies', async t => {
// The loaded root must never be treated as a store node, even when its own path happens to sit under a node_modules/.store directory.
const path = t.testdir({
node_modules: {
'.store': {
'root@1.0.0': {
node_modules: {
root: {
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
devDependencies: { 'a-dev-dep': '^1.0.0' },
}),
},
},
},
},
},
})
const root = join(path, 'node_modules/.store/root@1.0.0/node_modules/root')

const tree = await loadActual(root)
t.equal(tree.isInStore, false, 'loaded root is not flagged isInStore')
t.ok(tree.edgesOut.get('a-dev-dep'), 'root devDependency is a required edge')
})
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); fix(arborist): don't load store packages' devDependencies as required edges by manzoorwanijk · Pull Request #9626 · npm/cli · GitHub
Skip to content
Merged
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
5 changes: 4 additions & 1 deletion workspaces/arborist/lib/arborist/load-actual.js
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
// mix-in implementing the loadActual method

const { dirname, join, normalize, relative, resolve } = require('node:path')
const { dirname, join, normalize, relative, resolve, sep } = require('node:path')

const PackageJson = require('@npmcli/package-json')
const { readdirScoped } = require('@npmcli/fs')
Expand DownExpand Up@@ -269,6 +269,9 @@ module.exports = cls => class ActualLoader extends cls {
parent,
root,
loadOverrides,
// A package physically located in the linked strategy's store is a transitive dependency, not a real tree top, so it must not load its devDependencies.
// Never flag the loaded root itself, even if its own path happens to sit under a .store directory.
isInStore: path !== this.path && real.includes(`${sep}node_modules${sep}.store${sep}`),
}

try {
Expand Down
3 changes: 2 additions & 1 deletion workspaces/arborist/lib/node.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -936,7 +936,8 @@ class Node {
isTop: srcTop,
path: srcPath,
} = sourceReference || {}
const thisDev = isTop && !globalTop && path
// A package in the linked strategy's .store is a transitive dependency that is structurally a tree top, but its devDependencies are never installed or required, so they must not be loaded.
const thisDev = isTop && !globalTop && path && !this.isInStore
const srcDev = !sourceReference || srcTop && !srcGlobalTop && srcPath
if (thisDev && srcDev) {
this.#loadDepType(this.package.devDependencies, 'dev', ad)
Expand Down
60 changes: 59 additions & 1 deletion workspaces/arborist/test/arborist/load-actual.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@ const t = require('tap')
const { format } = require('tcompare')
const Arborist = require('../../lib/arborist')

const { resolve } = require('node:path')
const { join, resolve } = require('node:path')
const Node = require('../../lib/node.js')
const Shrinkwrap = require('../../lib/shrinkwrap.js')
const fs = require('node:fs')
Expand DownExpand Up@@ -545,3 +545,61 @@ t.test('applies root packageExtensions to a linked actual tree', async t => {
t.strictSame(broken.packageExtensionsApplied, applied, 'provenance recorded on the store node')
t.strictSame(brokenLink.packageExtensionsApplied, applied, 'provenance mirrored onto the link')
})

t.test('store nodes do not load devDependencies as required edges', async t => {
// A package in the linked store is structurally a tree top, so without the isInStore guard its devDependencies would load as required edges and surface as missing (e.g. npm sbom ESBOMPROBLEMS).
const path = t.testdir({
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
dependencies: { dep: '1.0.0' },
}),
node_modules: {
dep: t.fixture('symlink', '.store/dep@1.0.0/node_modules/dep'),
'.store': {
'dep@1.0.0': {
node_modules: {
dep: {
'package.json': JSON.stringify({
name: 'dep',
version: '1.0.0',
devDependencies: { 'a-dev-dep': '^1.0.0' },
}),
},
},
},
},
},
})

const tree = await loadActual(path)
const dep = tree.children.get('dep').target
t.equal(dep.isInStore, true, 'store node is flagged isInStore')
t.notOk(dep.edgesOut.get('a-dev-dep'), 'devDependency of a store node is not a required edge')
})

t.test('a project located under a .store path still loads its own devDependencies', async t => {
// The loaded root must never be treated as a store node, even when its own path happens to sit under a node_modules/.store directory.
const path = t.testdir({
node_modules: {
'.store': {
'root@1.0.0': {
node_modules: {
root: {
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
devDependencies: { 'a-dev-dep': '^1.0.0' },
}),
},
},
},
},
},
})
const root = join(path, 'node_modules/.store/root@1.0.0/node_modules/root')

const tree = await loadActual(root)
t.equal(tree.isInStore, false, 'loaded root is not flagged isInStore')
t.ok(tree.edgesOut.get('a-dev-dep'), 'root devDependency is a required edge')
})
Loading