File Explorer

/var/lang/lib/node_modules/npm/node_modules/@npmcli/arborist/lib/arborist

This explorer reads the filesystem of the server it runs on, so /workspace/user isn't present here. Browsing and the terminal still work against this server's own disk from /.

load-virtual.js10.1 KB · 312 lines
const { isAbsolute, resolve } = require('node:path')// mixin providing the loadVirtual methodconst mapWorkspaces = require('@npmcli/map-workspaces')const PackageJson = require('@npmcli/package-json')const nameFromFolder = require('@npmcli/name-from-folder') const consistentResolve = require('../consistent-resolve.js')const Shrinkwrap = require('../shrinkwrap.js')const Node = require('../node.js')const Link = require('../link.js')const relpath = require('../relpath.js')const calcDepFlags = require('../calc-dep-flags.js')const treeCheck = require('../tree-check.js') const flagsSuspect = Symbol.for('flagsSuspect')const setWorkspaces = Symbol.for('setWorkspaces') module.exports = cls => class VirtualLoader extends cls {  #rootOptionProvided  // when true, lockfile entries must stay inside `this.path`  #subtreeOnly = false   // public method  async loadVirtual (options = {}) {    if (this.virtualTree) {      return this.virtualTree    }     // allow the user to set reify options on the ctor as well.    // XXX: deprecate separate reify() options object.    options = { ...this.options, ...options }     this.#subtreeOnly = !!options.subtreeOnly     if (options.root && options.root.meta) {      await this.#loadFromShrinkwrap(options.root.meta, options.root)      return treeCheck(this.virtualTree)    }     const s = await Shrinkwrap.load({      path: this.path,      lockfileVersion: this.options.lockfileVersion,      resolveOptions: this.options,    })    if (!s.loadedFromDisk && !options.root) {      const er = new Error('loadVirtual requires existing shrinkwrap file')      throw Object.assign(er, { code: 'ENOLOCK' })    }     // when building the ideal tree, we pass in a root node to this function    // otherwise, load it from the root package json or the lockfile    const pkg = await PackageJson.normalize(this.path).then(p => p.content).catch(() => s.data.packages[''] || {})    // TODO clean this up    const {      root = await this[setWorkspaces](this.#loadNode('', pkg, true)),    } = options    this.#rootOptionProvided = options.root     await this.#loadFromShrinkwrap(s, root)    root.assertRootOverrides()    return treeCheck(this.virtualTree)  }   async #loadFromShrinkwrap (s, root) {    if (!this.#rootOptionProvided) {      // root is never any of these things, but might be a brand new      // baby Node object that never had its dep flags calculated.      root.unsetDepFlags()    } else {      this[flagsSuspect] = true    }     this.#checkRootEdges(s, root)    root.meta = s    this.virtualTree = root    // separate out link metadata, and create Node objects for nodes    const links = new Map()    const nodes = new Map([['', root]])    for (const [location, meta] of Object.entries(s.data.packages)) {      // skip the root because we already got it      if (!location) {        continue      }       if (meta.link) {        links.set(location, meta)      } else {        nodes.set(location, this.#loadNode(location, meta))      }    }    await this.#resolveLinks(links, nodes)    if (!(s.originalLockfileVersion >= 2)) {      this.#assignBundles(nodes)    }    if (this[flagsSuspect]) {      // reset all dep flags      // can't use inventory here, because virtualTree might not be root      for (const node of nodes.values()) {        if (node.isRoot || node === this.#rootOptionProvided) {          continue        }        node.resetDepFlags()      }      calcDepFlags(this.virtualTree, !this.#rootOptionProvided)    }    return root  }   // check the lockfile deps, and see if they match.  if they do not  // then we have to reset dep flags at the end.  for example, if the  // user manually edits their package.json file, then we need to know  // that the idealTree is no longer entirely trustworthy.  #checkRootEdges (s, root) {    // loaded virtually from tree, no chance of being out of sync    // ancient lockfiles are critically damaged by this process,    // so we need to just hope for the best in those cases.    if (!s.loadedFromDisk || s.ancientLockfile) {      return    }     const lock = s.get('')    const prod = lock.dependencies || {}    const dev = lock.devDependencies || {}    const optional = lock.optionalDependencies || {}    const peer = lock.peerDependencies || {}    const peerOptional = {}     if (lock.peerDependenciesMeta) {      for (const [name, meta] of Object.entries(lock.peerDependenciesMeta)) {        if (meta.optional && peer[name] !== undefined) {          peerOptional[name] = peer[name]          delete peer[name]        }      }    }     for (const name of Object.keys(optional)) {      delete prod[name]    }     const lockWS = {}    const workspaces = mapWorkspaces.virtual({      cwd: this.path,      lockfile: s.data,    })     for (const [name, path] of workspaces.entries()) {      lockWS[name] = `file:${path}`    }     // Should rootNames exclude optional?    const rootNames = new Set(root.edgesOut.keys())     const lockByType = ({ dev, optional, peer, peerOptional, prod, workspace: lockWS })     // Find anything in shrinkwrap deps that doesn't match root's type or spec    for (const type in lockByType) {      const deps = lockByType[type]      for (const name in deps) {        const edge = root.edgesOut.get(name)        if (!edge || edge.type !== type || edge.spec !== deps[name]) {          return this[flagsSuspect] = true        }        rootNames.delete(name)      }    }    // Something was in root that's not accounted for in shrinkwrap    if (rootNames.size) {      return this[flagsSuspect] = true    }  }   // throw if the resolved path is outside `base`, only in subtreeOnly mode  #assertContained (base, resolvedPath, location) {    if (!this.#subtreeOnly) {      return    }    if (isAbsolute(location)) {      throw Object.assign(        new Error(`invalid lockfile entry: "${location}" must be a relative location`),        { code: 'EINVALIDLOCATION', location, base }      )    }    const rel = relpath(base, resolvedPath)    if (      rel === '..' ||      rel.startsWith('../') ||      isAbsolute(rel) ||      // non-root key that collapses back to base (e.g. 'node_modules/..')      (rel === '' && location !== '')    ) {      throw Object.assign(        new Error(`invalid lockfile entry: "${location}" resolves outside of "${base}"`),        { code: 'EINVALIDLOCATION', location, base, resolvedPath }      )    }  }   // links is the set of metadata, and nodes is the map of non-Link nodes  // Set the targets to nodes in the set, if we have them (we might not)  // XXX build-ideal-tree also has a #resolveLinks, is there overlap?  async #resolveLinks (links, nodes) {    for (const [location, meta] of links.entries()) {      const targetPath = resolve(this.path, meta.resolved)      // check before nodes.get so we surface EINVALIDLOCATION instead of EMISSINGTARGET      this.#assertContained(this.path, targetPath, meta.resolved || location)      const targetLoc = relpath(this.path, targetPath)      const target = nodes.get(targetLoc)       if (!target) {        const err = new Error(`Missing target in lock file: "${targetLoc}" is referenced by "${location}" but does not exist.To fix:1. rm package-lock.json2. npm install`        )        err.code = 'EMISSINGTARGET'        throw err      }       const link = this.#loadLink(location, targetLoc, target, meta)      nodes.set(location, link)      nodes.set(targetLoc, link.target)       // we always need to read the package.json for link targets      // outside node_modules because they can be changed by the local user      if (!link.target.parent) {        await PackageJson.normalize(link.realpath).then(p => link.target.package = p.content).catch(() => null)      }    }  }   #assignBundles (nodes) {    for (const [location, node] of nodes) {      // Skip assignment of parentage for the root package      if (!location || node.isLink && !node.target.location) {        continue      }      const { name, parent, package: { inBundle } } = node       if (!parent) {        continue      }       // read inBundle from package because 'package' here is      // actually a v2 lockfile metadata entry.      // If the *parent* is also bundled, though, or if the parent has      // no dependency on it, then we assume that it's being pulled in      // just by virtue of its parent or a transitive dep being bundled.      const { package: ppkg } = parent      const { inBundle: parentBundled } = ppkg      if (inBundle && !parentBundled && parent.edgesOut.has(node.name)) {        if (!ppkg.bundleDependencies) {          ppkg.bundleDependencies = [name]        } else {          ppkg.bundleDependencies.push(name)        }      }    }  }   #loadNode (location, sw, loadOverrides) {    const p = this.virtualTree ? this.virtualTree.realpath : this.path    const path = resolve(p, location)    this.#assertContained(p, path, location)    // shrinkwrap doesn't include package name unless necessary    if (!sw.name) {      sw.name = nameFromFolder(path)    }     const node = new Node({      installLinks: this.installLinks,      legacyPeerDeps: this.legacyPeerDeps,      root: this.virtualTree,      path,      realpath: path,      integrity: sw.integrity,      resolved: consistentResolve(sw.resolved, this.path, path),      pkg: sw,      hasShrinkwrap: sw.hasShrinkwrap,      loadOverrides,      // cast to boolean because they're undefined in the lock file when false      extraneous: !!sw.extraneous,      devOptional: !!(sw.devOptional || sw.dev || sw.optional),      peer: !!sw.peer,      optional: !!sw.optional,      dev: !!sw.dev,    })     return node  }   #loadLink (location, targetLoc, target) {    const path = resolve(this.path, location)    this.#assertContained(this.path, path, location)    const link = new Link({      installLinks: this.installLinks,      legacyPeerDeps: this.legacyPeerDeps,      path,      realpath: resolve(this.path, targetLoc),      target,      pkg: target && target.package,    })    link.extraneous = target.extraneous    link.devOptional = target.devOptional    link.peer = target.peer    link.optional = target.optional    link.dev = target.dev    return link  }}