From 82879f69c72681f636be73d13c4464e35f258954 Mon Sep 17 00:00:00 2001 From: Gar Date: Tue, 18 Apr 2023 10:12:54 -0700 Subject: [PATCH] fix: lazy loading of arborist and pacote (#6225) --- lib/commands/audit.js | 3 +- lib/commands/cache.js | 3 +- lib/commands/ci.js | 2 +- lib/commands/dedupe.js | 2 +- lib/commands/diff.js | 3 +- lib/commands/explain.js | 4 +- lib/commands/fund.js | 5 +- lib/commands/install.js | 2 +- lib/commands/link.js | 5 +- lib/commands/ls.js | 6 +- lib/commands/outdated.js | 3 +- lib/commands/prune.js | 2 +- lib/commands/query.js | 3 +- lib/commands/rebuild.js | 4 +- lib/commands/shrinkwrap.js | 2 +- lib/commands/uninstall.js | 2 +- lib/commands/update.js | 4 +- lib/npm.js | 5 -- lib/package-url-cmd.js | 2 - lib/utils/completion/installed-deep.js | 2 +- lib/utils/update-notifier.js | 88 ++++++++++----------- lib/workspaces/update-workspaces.js | 2 +- test/lib/utils/completion/installed-deep.js | 1 + workspaces/arborist/lib/arborist/index.js | 1 + workspaces/arborist/lib/arborist/reify.js | 1 - 25 files changed, 76 insertions(+), 81 deletions(-) diff --git a/lib/commands/audit.js b/lib/commands/audit.js index dab871669eba5..7b75ecbf2e024 100644 --- a/lib/commands/audit.js +++ b/lib/commands/audit.js @@ -1,4 +1,3 @@ -const Arborist = require('@npmcli/arborist') const auditReport = require('npm-audit-report') const fetch = require('npm-registry-fetch') const localeCompare = require('@isaacs/string-locale-compare')('en') @@ -413,6 +412,7 @@ class Audit extends ArboristWorkspaceCmd { async auditAdvisories (args) { const reporter = this.npm.config.get('json') ? 'json' : 'detail' + const Arborist = require('@npmcli/arborist') const opts = { ...this.npm.flatOptions, audit: true, @@ -445,6 +445,7 @@ class Audit extends ArboristWorkspaceCmd { } log.verbose('loading installed dependencies') + const Arborist = require('@npmcli/arborist') const opts = { ...this.npm.flatOptions, path: this.npm.prefix, diff --git a/lib/commands/cache.js b/lib/commands/cache.js index 0ab40b9ed44a9..9965f7085d9ad 100644 --- a/lib/commands/cache.js +++ b/lib/commands/cache.js @@ -1,5 +1,4 @@ const cacache = require('cacache') -const Arborist = require('@npmcli/arborist') const pacote = require('pacote') const fs = require('fs/promises') const { join } = require('path') @@ -162,7 +161,7 @@ class Cache extends BaseCommand { return pacote.tarball.stream(spec, stream => { stream.resume() return stream.promise() - }, { ...this.npm.flatOptions, Arborist }) + }, { ...this.npm.flatOptions }) })) } diff --git a/lib/commands/ci.js b/lib/commands/ci.js index 7ed961ad9272c..97c292e8c5389 100644 --- a/lib/commands/ci.js +++ b/lib/commands/ci.js @@ -1,4 +1,3 @@ -const Arborist = require('@npmcli/arborist') const reifyFinish = require('../utils/reify-finish.js') const runScript = require('@npmcli/run-script') const fs = require('fs/promises') @@ -36,6 +35,7 @@ class CI extends ArboristWorkspaceCmd { } const where = this.npm.prefix + const Arborist = require('@npmcli/arborist') const opts = { ...this.npm.flatOptions, packageLock: true, // npm ci should never skip lock files diff --git a/lib/commands/dedupe.js b/lib/commands/dedupe.js index 0cc0e80709883..d4e8d0e8c80c3 100644 --- a/lib/commands/dedupe.js +++ b/lib/commands/dedupe.js @@ -1,5 +1,4 @@ // dedupe duplicated packages, or find them in the tree -const Arborist = require('@npmcli/arborist') const reifyFinish = require('../utils/reify-finish.js') const ArboristWorkspaceCmd = require('../arborist-cmd.js') @@ -31,6 +30,7 @@ class Dedupe extends ArboristWorkspaceCmd { const dryRun = this.npm.config.get('dry-run') const where = this.npm.prefix + const Arborist = require('@npmcli/arborist') const opts = { ...this.npm.flatOptions, path: where, diff --git a/lib/commands/diff.js b/lib/commands/diff.js index 1f4bfd3eb1151..3924166af0a88 100644 --- a/lib/commands/diff.js +++ b/lib/commands/diff.js @@ -2,7 +2,6 @@ const { resolve } = require('path') const semver = require('semver') const libnpmdiff = require('libnpmdiff') const npa = require('npm-package-arg') -const Arborist = require('@npmcli/arborist') const pacote = require('pacote') const pickManifest = require('npm-pick-manifest') const log = require('../utils/log-shim') @@ -146,6 +145,7 @@ class Diff extends BaseCommand { if (spec.registry) { let actualTree let node + const Arborist = require('@npmcli/arborist') try { const opts = { ...this.npm.flatOptions, @@ -257,6 +257,7 @@ class Diff extends BaseCommand { async findVersionsByPackageName (specs) { let actualTree + const Arborist = require('@npmcli/arborist') try { const opts = { ...this.npm.flatOptions, diff --git a/lib/commands/explain.js b/lib/commands/explain.js index 44c32c0e5663a..a72514fb97805 100644 --- a/lib/commands/explain.js +++ b/lib/commands/explain.js @@ -1,6 +1,4 @@ const { explainNode } = require('../utils/explain-dep.js') -const completion = require('../utils/completion/installed-deep.js') -const Arborist = require('@npmcli/arborist') const npa = require('npm-package-arg') const semver = require('semver') const { relative, resolve } = require('path') @@ -21,6 +19,7 @@ class Explain extends ArboristWorkspaceCmd { // TODO /* istanbul ignore next */ async completion (opts) { + const completion = require('../utils/completion/installed-deep.js') return completion(this.npm, opts) } @@ -29,6 +28,7 @@ class Explain extends ArboristWorkspaceCmd { throw this.usageError() } + const Arborist = require('@npmcli/arborist') const arb = new Arborist({ path: this.npm.prefix, ...this.npm.flatOptions }) const tree = await arb.loadActual() diff --git a/lib/commands/fund.js b/lib/commands/fund.js index 479ff487ec8b0..1e8981967fc32 100644 --- a/lib/commands/fund.js +++ b/lib/commands/fund.js @@ -1,12 +1,10 @@ const archy = require('archy') -const Arborist = require('@npmcli/arborist') const pacote = require('pacote') const semver = require('semver') const npa = require('npm-package-arg') const { depth } = require('treeverse') const { readTree: getFundingInfo, normalizeFunding, isValidFunding } = require('libnpmfund') -const completion = require('../utils/completion/installed-deep.js') const openUrl = require('../utils/open-url.js') const ArboristWorkspaceCmd = require('../arborist-cmd.js') @@ -39,6 +37,7 @@ class Fund extends ArboristWorkspaceCmd { // TODO /* istanbul ignore next */ async completion (opts) { + const completion = require('../utils/completion/installed-deep.js') return completion(this.npm, opts) } @@ -64,6 +63,7 @@ class Fund extends ArboristWorkspaceCmd { } const where = this.npm.prefix + const Arborist = require('@npmcli/arborist') const arb = new Arborist({ ...this.npm.flatOptions, path: where }) const tree = await arb.loadActual() @@ -80,6 +80,7 @@ class Fund extends ArboristWorkspaceCmd { // TODO: add !workspacesEnabled option handling to libnpmfund const fundingInfo = getFundingInfo(tree, { ...this.flatOptions, + Arborist, workspaces: this.workspaceNames, }) diff --git a/lib/commands/install.js b/lib/commands/install.js index 273edcf45c7a8..99f5b326f7b2b 100644 --- a/lib/commands/install.js +++ b/lib/commands/install.js @@ -5,7 +5,6 @@ const readdir = util.promisify(fs.readdir) const reifyFinish = require('../utils/reify-finish.js') const log = require('../utils/log-shim.js') const { resolve, join } = require('path') -const Arborist = require('@npmcli/arborist') const runScript = require('@npmcli/run-script') const pacote = require('pacote') const checks = require('npm-install-checks') @@ -136,6 +135,7 @@ class Install extends ArboristWorkspaceCmd { throw this.usageError() } + const Arborist = require('@npmcli/arborist') const opts = { ...this.npm.flatOptions, auditLevel: null, diff --git a/lib/commands/link.js b/lib/commands/link.js index 6d8027f2b9cb6..61ae5a9808868 100644 --- a/lib/commands/link.js +++ b/lib/commands/link.js @@ -3,7 +3,6 @@ const util = require('util') const readdir = util.promisify(fs.readdir) const { resolve } = require('path') -const Arborist = require('@npmcli/arborist') const npa = require('npm-package-arg') const rpj = require('read-package-json-fast') const semver = require('semver') @@ -67,8 +66,10 @@ class Link extends ArboristWorkspaceCmd { // load current packages from the global space, // and then add symlinks installs locally const globalTop = resolve(this.npm.globalDir, '..') + const Arborist = require('@npmcli/arborist') const globalOpts = { ...this.npm.flatOptions, + Arborist, path: globalTop, global: true, prune: false, @@ -138,8 +139,10 @@ class Link extends ArboristWorkspaceCmd { const paths = wsp && wsp.length ? wsp : [this.npm.prefix] const add = paths.map(path => `file:${path.replace(/#/g, '%23')}`) const globalTop = resolve(this.npm.globalDir, '..') + const Arborist = require('@npmcli/arborist') const arb = new Arborist({ ...this.npm.flatOptions, + Arborist, path: globalTop, global: true, }) diff --git a/lib/commands/ls.js b/lib/commands/ls.js index a737f44b73b29..eb9114802d5e0 100644 --- a/lib/commands/ls.js +++ b/lib/commands/ls.js @@ -3,12 +3,9 @@ const relativePrefix = `.${sep}` const { EOL } = require('os') const archy = require('archy') -const Arborist = require('@npmcli/arborist') const { breadth } = require('treeverse') const npa = require('npm-package-arg') -const completion = require('../utils/completion/installed-deep.js') - const _depth = Symbol('depth') const _dedupe = Symbol('dedupe') const _filteredBy = Symbol('filteredBy') @@ -44,6 +41,7 @@ class LS extends ArboristWorkspaceCmd { // TODO /* istanbul ignore next */ async completion (opts) { + const completion = require('../utils/completion/installed-deep.js') return completion(this.npm, opts) } @@ -63,6 +61,8 @@ class LS extends ArboristWorkspaceCmd { const path = global ? resolve(this.npm.globalDir, '..') : this.npm.prefix + const Arborist = require('@npmcli/arborist') + const arb = new Arborist({ global, ...this.npm.flatOptions, diff --git a/lib/commands/outdated.js b/lib/commands/outdated.js index 49626ebd5e20e..419fb5f5c76ac 100644 --- a/lib/commands/outdated.js +++ b/lib/commands/outdated.js @@ -6,8 +6,6 @@ const npa = require('npm-package-arg') const pickManifest = require('npm-pick-manifest') const localeCompare = require('@isaacs/string-locale-compare')('en') -const Arborist = require('@npmcli/arborist') - const ansiTrim = require('../utils/ansi-trim.js') const ArboristWorkspaceCmd = require('../arborist-cmd.js') @@ -30,6 +28,7 @@ class Outdated extends ArboristWorkspaceCmd { ? global : this.npm.prefix + const Arborist = require('@npmcli/arborist') const arb = new Arborist({ ...this.npm.flatOptions, path: where, diff --git a/lib/commands/prune.js b/lib/commands/prune.js index ee2c30553f1c5..12136e20e4943 100644 --- a/lib/commands/prune.js +++ b/lib/commands/prune.js @@ -1,5 +1,4 @@ // prune extraneous packages -const Arborist = require('@npmcli/arborist') const reifyFinish = require('../utils/reify-finish.js') const ArboristWorkspaceCmd = require('../arborist-cmd.js') @@ -19,6 +18,7 @@ class Prune extends ArboristWorkspaceCmd { async exec () { const where = this.npm.prefix + const Arborist = require('@npmcli/arborist') const opts = { ...this.npm.flatOptions, path: where, diff --git a/lib/commands/query.js b/lib/commands/query.js index b5f4d8e57ddf5..ba39f004fae23 100644 --- a/lib/commands/query.js +++ b/lib/commands/query.js @@ -1,7 +1,6 @@ 'use strict' const { resolve } = require('path') -const Arborist = require('@npmcli/arborist') const BaseCommand = require('../base-command.js') class QuerySelectorItem { @@ -58,6 +57,7 @@ class Query extends BaseCommand { async exec (args) { // one dir up from wherever node_modules lives const where = resolve(this.npm.dir, '..') + const Arborist = require('@npmcli/arborist') const opts = { ...this.npm.flatOptions, path: where, @@ -73,6 +73,7 @@ class Query extends BaseCommand { async execWorkspaces (args) { await this.setWorkspaces() + const Arborist = require('@npmcli/arborist') const opts = { ...this.npm.flatOptions, path: this.npm.prefix, diff --git a/lib/commands/rebuild.js b/lib/commands/rebuild.js index df791106fdd21..527447e427917 100644 --- a/lib/commands/rebuild.js +++ b/lib/commands/rebuild.js @@ -1,8 +1,6 @@ const { resolve } = require('path') -const Arborist = require('@npmcli/arborist') const npa = require('npm-package-arg') const semver = require('semver') -const completion = require('../utils/completion/installed-deep.js') const ArboristWorkspaceCmd = require('../arborist-cmd.js') class Rebuild extends ArboristWorkspaceCmd { @@ -21,12 +19,14 @@ class Rebuild extends ArboristWorkspaceCmd { // TODO /* istanbul ignore next */ async completion (opts) { + const completion = require('../utils/completion/installed-deep.js') return completion(this.npm, opts) } async exec (args) { const globalTop = resolve(this.npm.globalDir, '..') const where = this.npm.global ? globalTop : this.npm.prefix + const Arborist = require('@npmcli/arborist') const arb = new Arborist({ ...this.npm.flatOptions, path: where, diff --git a/lib/commands/shrinkwrap.js b/lib/commands/shrinkwrap.js index a240f039356e7..c6d817d480142 100644 --- a/lib/commands/shrinkwrap.js +++ b/lib/commands/shrinkwrap.js @@ -1,6 +1,5 @@ const { resolve, basename } = require('path') const { unlink } = require('fs').promises -const Arborist = require('@npmcli/arborist') const log = require('../utils/log-shim') const BaseCommand = require('../base-command.js') class Shrinkwrap extends BaseCommand { @@ -21,6 +20,7 @@ class Shrinkwrap extends BaseCommand { throw er } + const Arborist = require('@npmcli/arborist') const path = this.npm.prefix const sw = resolve(path, 'npm-shrinkwrap.json') const arb = new Arborist({ ...this.npm.flatOptions, path }) diff --git a/lib/commands/uninstall.js b/lib/commands/uninstall.js index 8c44f2e32106c..e5373119ec757 100644 --- a/lib/commands/uninstall.js +++ b/lib/commands/uninstall.js @@ -1,5 +1,4 @@ const { resolve } = require('path') -const Arborist = require('@npmcli/arborist') const rpj = require('read-package-json-fast') const reifyFinish = require('../utils/reify-finish.js') @@ -42,6 +41,7 @@ class Uninstall extends ArboristWorkspaceCmd { ? resolve(this.npm.globalDir, '..') : this.npm.localPrefix + const Arborist = require('@npmcli/arborist') const opts = { ...this.npm.flatOptions, path, diff --git a/lib/commands/update.js b/lib/commands/update.js index fd30bcb41e2b3..26be5ad681983 100644 --- a/lib/commands/update.js +++ b/lib/commands/update.js @@ -1,10 +1,8 @@ const path = require('path') -const Arborist = require('@npmcli/arborist') const log = require('../utils/log-shim.js') const reifyFinish = require('../utils/reify-finish.js') -const completion = require('../utils/completion/installed-deep.js') const ArboristWorkspaceCmd = require('../arborist-cmd.js') class Update extends ArboristWorkspaceCmd { @@ -34,6 +32,7 @@ class Update extends ArboristWorkspaceCmd { // TODO /* istanbul ignore next */ async completion (opts) { + const completion = require('../utils/completion/installed-deep.js') return completion(this.npm, opts) } @@ -53,6 +52,7 @@ class Update extends ArboristWorkspaceCmd { 'https://github.com/npm/rfcs/blob/latest/implemented/0019-remove-update-depth-option.md') } + const Arborist = require('@npmcli/arborist') const opts = { ...this.npm.flatOptions, path: where, diff --git a/lib/npm.js b/lib/npm.js index 5a347a2fd69e8..eebb453dbbacb 100644 --- a/lib/npm.js +++ b/lib/npm.js @@ -1,4 +1,3 @@ -const Arborist = require('@npmcli/arborist') const EventEmitter = require('events') const { resolve, dirname, join } = require('path') const Config = require('@npmcli/config') @@ -310,10 +309,6 @@ class Npm extends EventEmitter { get flatOptions () { const { flat } = this.config - // the Arborist constructor is used almost everywhere we call pacote, it's - // easiest to attach it to flatOptions so it goes everywhere without having - // to touch every call - flat.Arborist = Arborist flat.nodeVersion = process.version flat.npmVersion = pkg.version if (this.command) { diff --git a/lib/package-url-cmd.js b/lib/package-url-cmd.js index 20e6a16fe1523..250b46eeeddbe 100644 --- a/lib/package-url-cmd.js +++ b/lib/package-url-cmd.js @@ -2,7 +2,6 @@ const pacote = require('pacote') const hostedGitInfo = require('hosted-git-info') -const Arborist = require('@npmcli/arborist') const openUrl = require('./utils/open-url.js') const log = require('./utils/log-shim') @@ -33,7 +32,6 @@ class PackageUrlCommand extends BaseCommand { ...this.npm.flatOptions, where: this.npm.localPrefix, fullMetadata: true, - Arborist, } const mani = await pacote.manifest(arg, opts) const url = this.getUrl(arg, mani) diff --git a/lib/utils/completion/installed-deep.js b/lib/utils/completion/installed-deep.js index 7098d81fe7b49..045708f74bd2a 100644 --- a/lib/utils/completion/installed-deep.js +++ b/lib/utils/completion/installed-deep.js @@ -1,8 +1,8 @@ const { resolve } = require('path') -const Arborist = require('@npmcli/arborist') const localeCompare = require('@isaacs/string-locale-compare')('en') const installedDeep = async (npm) => { + const Arborist = require('@npmcli/arborist') const { depth, global, diff --git a/lib/utils/update-notifier.js b/lib/utils/update-notifier.js index cb9184bcdb41d..2c839bfeff843 100644 --- a/lib/utils/update-notifier.js +++ b/lib/utils/update-notifier.js @@ -2,20 +2,11 @@ // but not in CI, and not if we're doing that already. // Check daily for betas, and weekly otherwise. -const pacote = require('pacote') const ciInfo = require('ci-info') const semver = require('semver') const { stat, writeFile } = require('fs/promises') const { resolve } = require('path') -const SKIP = Symbol('SKIP') - -const isGlobalNpmUpdate = npm => { - return npm.flatOptions.global && - ['install', 'update'].includes(npm.command) && - npm.argv.some(arg => /^npm(@|$)/.test(arg)) -} - // update check frequency const DAILY = 1000 * 60 * 60 * 24 const WEEKLY = DAILY * 7 @@ -24,39 +15,10 @@ const WEEKLY = DAILY * 7 const lastCheckedFile = npm => resolve(npm.flatOptions.cache, '../_update-notifier-last-checked') -const checkTimeout = async (npm, duration) => { - const t = new Date(Date.now() - duration) - const f = lastCheckedFile(npm) - // if we don't have a file, then definitely check it. - const st = await stat(f).catch(() => ({ mtime: t - 1 })) - return t > st.mtime -} - -const updateNotifier = async (npm, spec = 'latest') => { - // never check for updates in CI, when updating npm already, or opted out - if (!npm.config.get('update-notifier') || - isGlobalNpmUpdate(npm) || - ciInfo.isCI) { - return SKIP - } - - // if we're on a prerelease train, then updates are coming fast - // check for a new one daily. otherwise, weekly. - const { version } = npm - const current = semver.parse(version) - - // if we're on a beta train, always get the next beta - if (current.prerelease.length) { - spec = `^${version}` - } - - // while on a beta train, get updates daily - const duration = spec !== 'latest' ? DAILY : WEEKLY - - // if we've already checked within the specified duration, don't check again - if (!(await checkTimeout(npm, duration))) { - return null - } +// Actual check for updates. This is a separate function so that we only load +// this if we are doing the actual update +const updateCheck = async (npm, spec, version, current) => { + const pacote = require('pacote') const mani = await pacote.manifest(`npm@${spec}`, { // always prefer latest, even if doing --tag=whatever on the cmd @@ -112,15 +74,49 @@ const updateNotifier = async (npm, spec = 'latest') => { return message } +const updateNotifier = async (npm, spec = 'latest') => { + // if we're on a prerelease train, then updates are coming fast + // check for a new one daily. otherwise, weekly. + const { version } = npm + const current = semver.parse(version) + + // if we're on a beta train, always get the next beta + if (current.prerelease.length) { + spec = `^${version}` + } + + // while on a beta train, get updates daily + const duration = spec !== 'latest' ? DAILY : WEEKLY + + const t = new Date(Date.now() - duration) + // if we don't have a file, then definitely check it. + const st = await stat(lastCheckedFile(npm)).catch(() => ({ mtime: t - 1 })) + + // if we've already checked within the specified duration, don't check again + if (!(t > st.mtime)) { + return null + } + + return updateCheck(npm, spec, version, current) +} + // only update the notification timeout if we actually finished checking module.exports = async npm => { - const notification = await updateNotifier(npm) - - // dont write the file if we skipped checking altogether - if (notification === SKIP) { + if ( + // opted out + !npm.config.get('update-notifier') + // global npm update + || (npm.flatOptions.global && + ['install', 'update'].includes(npm.command) && + npm.argv.some(arg => /^npm(@|$)/.test(arg))) + // CI + || ciInfo.isCI + ) { return null } + const notification = await updateNotifier(npm) + // intentional. do not await this. it's a best-effort update. if this // fails, it's ok. might be using /dev/null as the cache or something weird // like that. diff --git a/lib/workspaces/update-workspaces.js b/lib/workspaces/update-workspaces.js index d22abda7c5b0b..892f366e9980a 100644 --- a/lib/workspaces/update-workspaces.js +++ b/lib/workspaces/update-workspaces.js @@ -1,6 +1,5 @@ 'use strict' -const Arborist = require('@npmcli/arborist') const reifyFinish = require('../utils/reify-finish.js') async function updateWorkspaces ({ @@ -31,6 +30,7 @@ async function updateWorkspaces ({ path: localPrefix, save, } + const Arborist = require('@npmcli/arborist') const arb = new Arborist(opts) await arb.reify({ ...opts, update: workspaces }) diff --git a/test/lib/utils/completion/installed-deep.js b/test/lib/utils/completion/installed-deep.js index fa39f0f0734b8..434d0214db4c8 100644 --- a/test/lib/utils/completion/installed-deep.js +++ b/test/lib/utils/completion/installed-deep.js @@ -8,6 +8,7 @@ const _flatOptions = { depth: Infinity, global: false, workspacesEnabled: true, + Arborist: require('@npmcli/arborist'), get prefix () { return prefix }, diff --git a/workspaces/arborist/lib/arborist/index.js b/workspaces/arborist/lib/arborist/index.js index afcc3ec27150b..ba68e883a4317 100644 --- a/workspaces/arborist/lib/arborist/index.js +++ b/workspaces/arborist/lib/arborist/index.js @@ -71,6 +71,7 @@ class Arborist extends Base { this.options = { nodeVersion: process.version, ...options, + Arborist: this.constructor, path: options.path || '.', cache: options.cache || `${homedir()}/.npm/_cacache`, packumentCache: options.packumentCache || new Map(), diff --git a/workspaces/arborist/lib/arborist/reify.js b/workspaces/arborist/lib/arborist/reify.js index 6c9fc438a024b..0057d38f11756 100644 --- a/workspaces/arborist/lib/arborist/reify.js +++ b/workspaces/arborist/lib/arborist/reify.js @@ -702,7 +702,6 @@ module.exports = cls => class Reifier extends cls { }) await pacote.extract(res, node.path, { ...this.options, - Arborist: this.constructor, resolved: node.resolved, integrity: node.integrity, })