text: // mixin providing the loadVirtual method
  
  const {resolve} = require('path')
  
  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 rpj = require('read-package-json-fast')
  const treeCheck = require('../tree-check.js')
  
  const loadFromShrinkwrap = Symbol('loadFromShrinkwrap')
  const resolveNodes = Symbol('resolveNodes')
  const resolveLinks = Symbol('resolveLinks')
  const assignBundles = Symbol('assignBundles')
  const loadRoot = Symbol('loadRoot')
  const loadNode = Symbol('loadVirtualNode')
  const loadLink = Symbol('loadVirtualLink')
  const loadWorkspaces = Symbol.for('loadWorkspaces')
  const loadWorkspacesVirtual = Symbol.for('loadWorkspacesVirtual')
  const flagsSuspect = Symbol.for('flagsSuspect')
  const reCalcDepFlags = Symbol('reCalcDepFlags')
  const checkRootEdges = Symbol('checkRootEdges')
  const rootOptionProvided = Symbol('rootOptionProvided')
  
  const depsToEdges = (type, deps) =>
    Object.entries(deps).map(d => [type, ...d])
  
  module.exports = cls => class VirtualLoader extends cls {
    constructor (options) {
      super(options)
  
      // the virtual tree we load from a shrinkwrap
      this.virtualTree = options.virtualTree
      this[flagsSuspect] = 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 }
  
      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 })
      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 {
        root = await this[loadRoot](s),
      } = options
  
      this[rootOptionProvided] = options.root
  
      await this[loadFromShrinkwrap](s, root)
      return treeCheck(this.virtualTree)
    }
  
    async [loadRoot] (s) {
      const pj = this.path + '/package.json'
      const pkg = await rpj(pj).catch(() => s.data.packages['']) || {}
      return this[loadWorkspaces](this[loadNode]('', pkg))
    }
  
    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.extraneous = false
        root.dev = false
        root.optional = false
        root.devOptional = false
        root.peer = false
      } else
        this[flagsSuspect] = true
  
      this[checkRootEdges](s, root)
      root.meta = s
      this.virtualTree = root
      const {links, nodes} = this[resolveNodes](s, root)
      await this[resolveLinks](links, nodes)
      if (!(s.originalLockfileVersion >= 2))
        this[assignBundles](nodes)
      if (this[flagsSuspect])
        this[reCalcDepFlags](nodes.values())
      return root
    }
  
    [reCalcDepFlags] (nodes) {
      // reset all dep flags
      // can't use inventory here, because virtualTree might not be root
      for (const node of nodes) {
        if (node.isRoot || node === this[rootOptionProvided])
          continue
        node.extraneous = true
        node.dev = true
        node.optional = true
        node.devOptional = true
        node.peer = true
      }
      calcDepFlags(this.virtualTree, !this[rootOptionProvided])
    }
  
    // 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 = this[loadWorkspacesVirtual]({
        cwd: this.path,
        lockfile: s.data,
      })
      for (const [name, path] of workspaces.entries())
        lockWS.push(['workspace', name, `file:${path}`])
  
      const lockEdges = [
        ...depsToEdges('prod', prod),
        ...depsToEdges('dev', dev),
        ...depsToEdges('optional', optional),
        ...depsToEdges('peer', peer),
        ...depsToEdges('peerOptional', peerOptional),
        ...lockWS,
      ].sort(([atype, aname], [btype, bname]) =>
        atype.localeCompare(btype) || aname.localeCompare(bname))
  
      const rootEdges = [...root.edgesOut.values()]
        .map(e => [e.type, e.name, e.spec])
        .sort(([atype, aname], [btype, bname]) =>
          atype.localeCompare(btype) || aname.localeCompare(bname))
  
      if (rootEdges.length !== lockEdges.length) {
        // something added or removed
        return this[flagsSuspect] = true
      }
  
      for (let i = 0; i < lockEdges.length; i++) {
        if (rootEdges[i][0] !== lockEdges[i][0] ||
            rootEdges[i][1] !== lockEdges[i][1] ||
            rootEdges[i][2] !== lockEdges[i][2])
          return this[flagsSuspect] = true
      }
    }
  
    // separate out link metadatas, and create Node objects for nodes
    [resolveNodes] (s, root) {
      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))
      }
      return {links, nodes}
    }
  
    // 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)
    async [resolveLinks] (links, nodes) {
      for (const [location, meta] of links.entries()) {
        const targetPath = resolve(this.path, meta.resolved)
        const targetLoc = relpath(this.path, targetPath)
        const target = nodes.get(targetLoc)
        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) {
          const pj = link.realpath + '/package.json'
          const pkg = await rpj(pj).catch(() => null)
          if (pkg)
            link.target.package = pkg
        }
      }
    }
  
    [assignBundles] (nodes) {
      for (const [location, node] of nodes) {
        // Skip assignment of parentage for the root package
        if (!location || node.target && !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) {
      const p = this.virtualTree ? this.virtualTree.realpath : this.path
      const path = resolve(p, location)
      // shrinkwrap doesn't include package name unless necessary
      if (!sw.name)
        sw.name = nameFromFolder(path)
  
      const dev = sw.dev
      const optional = sw.optional
      const devOptional = dev || optional || sw.devOptional
      const peer = sw.peer
  
      const node = new Node({
        legacyPeerDeps: this.legacyPeerDeps,
        root: this.virtualTree,
        path,
        realpath: path,
        integrity: sw.integrity,
        resolved: consistentResolve(sw.resolved, this.path, path),
        pkg: sw,
        hasShrinkwrap: sw.hasShrinkwrap,
        dev,
        optional,
        devOptional,
        peer,
      })
      // cast to boolean because they're undefined in the lock file when false
      node.extraneous = !!sw.extraneous
      node.devOptional = !!(sw.devOptional || sw.dev || sw.optional)
      node.peer = !!sw.peer
      node.optional = !!sw.optional
      node.dev = !!sw.dev
      return node
    }
  
    [loadLink] (location, targetLoc, target, meta) {
      const path = resolve(this.path, location)
      const link = new Link({
        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
    }
  }
  