'use strict'

const shadowNpmPaths = require('./shadow-npm-paths.js')
const process$1 = require('node:process')
const vendor = require('./vendor.js')
const logger = require('@socketsecurity/registry/lib/logger')
const constants = require('./constants.js')
const arrays = require('@socketsecurity/registry/lib/arrays')
const packageurlJs = require('@socketregistry/packageurl-js')
const registry = require('@socketsecurity/registry')
const debug = require('@socketsecurity/registry/lib/debug')
const objects = require('@socketsecurity/registry/lib/objects')
const isInteractive = require('@socketregistry/is-interactive/index.cjs')
const registryConstants = require('@socketsecurity/registry/lib/constants')
const prompts = require('@socketsecurity/registry/lib/prompts')
const strings = require('@socketsecurity/registry/lib/strings')
const sdk = require('@socketsecurity/sdk')
const fs = require('node:fs')
const os = require('node:os')
const path = require('node:path')
const fs$1 = require('@socketsecurity/registry/lib/fs')
const packages = require('@socketsecurity/registry/lib/packages')
const promises = require('node:timers/promises')
const sorts = require('@socketsecurity/registry/lib/sorts')
const indentString = require('@socketregistry/indent-string/index.cjs')

const { NPM: NPM$3, PNPM } = constants
const PNPM_WORKSPACE = `${PNPM}-workspace`
const ignoredDirs = [
  // Taken from ignore-by-default:
  // https://github.com/novemberborn/ignore-by-default/blob/v2.1.0/index.js
  '.git',
  // Git repository files, see <https://git-scm.com/>
  '.log',
  // Log files emitted by tools such as `tsserver`, see <https://github.com/Microsoft/TypeScript/wiki/Standalone-Server-%28tsserver%29>
  '.nyc_output',
  // Temporary directory where nyc stores coverage data, see <https://github.com/bcoe/nyc>
  '.sass-cache',
  // Cache folder for node-sass, see <https://github.com/sass/node-sass>
  '.yarn',
  // Where node modules are installed when using Yarn, see <https://yarnpkg.com/>
  'bower_components',
  // Where Bower packages are installed, see <http://bower.io/>
  'coverage',
  // Standard output directory for code coverage reports, see <https://github.com/gotwarlost/istanbul>
  'node_modules',
  // Where Node modules are installed, see <https://nodejs.org/>
  // Taken from globby:
  // https://github.com/sindresorhus/globby/blob/v14.0.2/ignore.js#L11-L16
  'flow-typed'
]
const ignoredDirPatterns = ignoredDirs.map(i => `**/${i}`)
async function getWorkspaceGlobs(agent, cwd = process$1.cwd()) {
  let workspacePatterns
  if (agent === PNPM) {
    for (const workspacePath of [
      path.join(cwd, `${PNPM_WORKSPACE}.yaml`),
      path.join(cwd, `${PNPM_WORKSPACE}.yml`)
    ]) {
      // eslint-disable-next-line no-await-in-loop
      const yml = await safeReadFile(workspacePath)
      if (yml) {
        try {
          workspacePatterns = vendor.distExports$1.parse(yml)?.packages
        } catch {}
        if (workspacePatterns) {
          break
        }
      }
    }
  } else {
    workspacePatterns = (
      await packages.readPackageJson(cwd, {
        throws: false
      })
    )?.['workspaces']
  }
  return Array.isArray(workspacePatterns)
    ? workspacePatterns
        .filter(strings.isNonEmptyString)
        .map(workspacePatternToGlobPattern)
    : []
}
function ignoreFileLinesToGlobPatterns(lines, filepath, cwd) {
  const base = path.relative(cwd, path.dirname(filepath)).replace(/\\/g, '/')
  const patterns = []
  for (let i = 0, { length } = lines; i < length; i += 1) {
    const pattern = lines[i].trim()
    if (pattern.length > 0 && pattern.charCodeAt(0) !== 35 /*'#'*/) {
      patterns.push(
        ignorePatternToMinimatch(
          pattern.length && pattern.charCodeAt(0) === 33 /*'!'*/
            ? `!${path.posix.join(base, pattern.slice(1))}`
            : path.posix.join(base, pattern)
        )
      )
    }
  }
  return patterns
}
function ignoreFileToGlobPatterns(content, filepath, cwd) {
  return ignoreFileLinesToGlobPatterns(content.split(/\r?\n/), filepath, cwd)
}

// Based on `@eslint/compat` convertIgnorePatternToMinimatch.
// Apache v2.0 licensed
// Copyright Nicholas C. Zakas
// https://github.com/eslint/rewrite/blob/compat-v1.2.1/packages/compat/src/ignore-file.js#L28
function ignorePatternToMinimatch(pattern) {
  const isNegated = pattern.startsWith('!')
  const negatedPrefix = isNegated ? '!' : ''
  const patternToTest = (isNegated ? pattern.slice(1) : pattern).trimEnd()
  // Special cases.
  if (
    patternToTest === '' ||
    patternToTest === '**' ||
    patternToTest === '/**' ||
    patternToTest === '**'
  ) {
    return `${negatedPrefix}${patternToTest}`
  }
  const firstIndexOfSlash = patternToTest.indexOf('/')
  const matchEverywherePrefix =
    firstIndexOfSlash === -1 || firstIndexOfSlash === patternToTest.length - 1
      ? '**/'
      : ''
  const patternWithoutLeadingSlash =
    firstIndexOfSlash === 0 ? patternToTest.slice(1) : patternToTest
  // Escape `{` and `(` because in gitignore patterns they are just
  // literal characters without any specific syntactic meaning,
  // while in minimatch patterns they can form brace expansion or extglob syntax.
  //
  // For example, gitignore pattern `src/{a,b}.js` ignores file `src/{a,b}.js`.
  // But, the same minimatch pattern `src/{a,b}.js` ignores files `src/a.js` and `src/b.js`.
  // Minimatch pattern `src/\{a,b}.js` is equivalent to gitignore pattern `src/{a,b}.js`.
  const escapedPatternWithoutLeadingSlash =
    patternWithoutLeadingSlash.replaceAll(
      /(?=((?:\\.|[^{(])*))\1([{(])/guy,
      '$1\\$2'
    )
  const matchInsideSuffix = patternToTest.endsWith('/**') ? '/*' : ''
  return `${negatedPrefix}${matchEverywherePrefix}${escapedPatternWithoutLeadingSlash}${matchInsideSuffix}`
}
function workspacePatternToGlobPattern(workspace) {
  const { length } = workspace
  if (!length) {
    return ''
  }
  // If the workspace ends with "/"
  if (workspace.charCodeAt(length - 1) === 47 /*'/'*/) {
    return `${workspace}/*/package.json`
  }
  // If the workspace ends with "/**"
  if (
    workspace.charCodeAt(length - 1) === 42 /*'*'*/ &&
    workspace.charCodeAt(length - 2) === 42 /*'*'*/ &&
    workspace.charCodeAt(length - 3) === 47 /*'/'*/
  ) {
    return `${workspace}/*/**/package.json`
  }
  // Things like "packages/a" or "packages/*"
  return `${workspace}/package.json`
}
async function filterGlobResultToSupportedFiles(entries, supportedFiles) {
  const patterns = ['golang', NPM$3, 'maven', 'pypi', 'gem', 'nuget'].reduce(
    (r, n) => {
      const supported = supportedFiles[n]
      r.push(
        ...(supported
          ? Object.values(supported).map(p => `**/${p.pattern}`)
          : [])
      )
      return r
    },
    []
  )
  return entries.filter(p => vendor.micromatchExports.some(p, patterns))
}
async function globWithGitIgnore(patterns, options) {
  const {
    cwd = process$1.cwd(),
    socketConfig,
    ...additionalOptions
  } = {
    __proto__: null,
    ...options
  }
  const projectIgnorePaths = socketConfig?.projectIgnorePaths
  const ignoreFiles = await vendor.distExports.glob(['**/.gitignore'], {
    absolute: true,
    cwd,
    expandDirectories: true
  })
  const ignores = [
    ...ignoredDirPatterns,
    ...(Array.isArray(projectIgnorePaths)
      ? ignoreFileLinesToGlobPatterns(
          projectIgnorePaths,
          path.join(cwd, '.gitignore'),
          cwd
        )
      : []),
    ...(
      await Promise.all(
        ignoreFiles.map(async filepath =>
          ignoreFileToGlobPatterns(
            await fs.promises.readFile(filepath, 'utf8'),
            filepath,
            cwd
          )
        )
      )
    ).flat()
  ]
  const hasNegatedPattern = ignores.some(p => p.charCodeAt(0) === 33 /*'!'*/)
  const globOptions = {
    absolute: true,
    cwd,
    expandDirectories: false,
    ignore: hasNegatedPattern ? [] : ignores,
    ...additionalOptions
  }
  const result = await vendor.distExports.glob(patterns, globOptions)
  if (!hasNegatedPattern) {
    return result
  }
  const { absolute } = globOptions

  // Note: the input files must be INSIDE the cwd. If you get strange looking
  // relative path errors here, most likely your path is outside the given cwd.
  const filtered = vendor
    .ignoreExports()
    .add(ignores)
    .filter(absolute ? result.map(p => path.relative(cwd, p)) : result)
  return absolute ? filtered.map(p => path.resolve(cwd, p)) : filtered
}
async function globNodeModules(cwd = process$1.cwd()) {
  return await vendor.distExports.glob('**/node_modules/**', {
    absolute: true,
    cwd
  })
}
async function globWorkspace(agent, cwd = process$1.cwd()) {
  const workspaceGlobs = await getWorkspaceGlobs(agent, cwd)
  return workspaceGlobs.length
    ? await vendor.distExports.glob(workspaceGlobs, {
        absolute: true,
        cwd,
        ignore: ['**/node_modules/**', '**/bower_components/**']
      })
    : []
}
function pathsToGlobPatterns(paths) {
  // TODO: Does not support `~/` paths.
  return paths.map(p => (p === '.' || p === './' ? '**/*' : p))
}

const { abortSignal } = constants
async function removeNodeModules(cwd = process$1.cwd()) {
  const nodeModulesPaths = await globNodeModules(cwd)
  await Promise.all(nodeModulesPaths.map(p => fs$1.remove(p)))
}
async function findUp(name, { cwd = process$1.cwd(), signal = abortSignal }) {
  let dir = path.resolve(cwd)
  const { root } = path.parse(dir)
  const names = [name].flat()
  while (dir && dir !== root) {
    for (const name of names) {
      if (signal?.aborted) {
        return undefined
      }
      const filePath = path.join(dir, name)
      try {
        // eslint-disable-next-line no-await-in-loop
        const stats = await fs.promises.stat(filePath)
        if (stats.isFile()) {
          return filePath
        }
      } catch {}
    }
    dir = path.dirname(dir)
  }
  return undefined
}
async function readFileBinary(filepath, options) {
  return await fs.promises.readFile(filepath, {
    signal: abortSignal,
    ...options,
    encoding: 'binary'
  })
}
async function readFileUtf8(filepath, options) {
  return await fs.promises.readFile(filepath, {
    signal: abortSignal,
    ...options,
    encoding: 'utf8'
  })
}
async function safeReadFile(filepath, options) {
  try {
    return await fs.promises.readFile(filepath, {
      encoding: 'utf8',
      signal: abortSignal,
      ...(typeof options === 'string'
        ? {
            encoding: options
          }
        : options)
    })
  } catch {}
  return undefined
}
function safeReadFileSync(filepath, options) {
  try {
    return fs.readFileSync(filepath, {
      encoding: 'utf8',
      ...(typeof options === 'string'
        ? {
            encoding: options
          }
        : options)
    })
  } catch {}
  return undefined
}

const { LOCALAPPDATA, SOCKET_APP_DIR, XDG_DATA_HOME } = constants
const supportedConfigKeys = new Map([
  ['apiBaseUrl', 'Base URL of the API endpoint'],
  ['apiProxy', 'A proxy through which to access the API'],
  ['apiToken', 'The API token required to access most API endpoints'],
  [
    'defaultOrg',
    'The default org slug to use; usually the org your API token has access to. When set, all orgSlug arguments are implied to be this value.'
  ],
  [
    'enforcedOrgs',
    'Orgs in this list have their security policies enforced on this machine'
  ]
])
const sensitiveConfigKeys = new Set(['apiToken'])
let _cachedConfig
// When using --config or SOCKET_CLI_CONFIG, do not persist the config.
let _readOnlyConfig = false
function overrideCachedConfig(jsonConfig) {
  debug.debugLog('Overriding entire config, marking config as read-only')
  let config
  try {
    config = JSON.parse(String(jsonConfig))
    if (!config || typeof config !== 'object') {
      // Just throw to reuse the error message. `null` is valid json,
      // so are primitive values. They're not valid config objects :)
      throw new Error()
    }
  } catch {
    return {
      ok: false,
      message:
        "Could not JSON parse the config override. Make sure it's a proper JSON object (double-quoted keys and strings, no unquoted `undefined`) and try again."
    }
  }

  // @ts-ignore Override an illegal object.
  _cachedConfig = config
  _readOnlyConfig = true

  // Normalize apiKey to apiToken.
  if (_cachedConfig['apiKey']) {
    if (_cachedConfig['apiToken']) {
      logger.logger.warn(
        'Note: The config override had both apiToken and apiKey. Using the apiToken value. Remove the apiKey to get rid of this message.'
      )
    }
    _cachedConfig['apiToken'] = _cachedConfig['apiKey']
    delete _cachedConfig['apiKey']
  }
  return {
    ok: true,
    message: undefined
  }
}
function overrideConfigApiToken(apiToken) {
  debug.debugLog('Overriding API token, marking config as read-only')
  // Set token to the local cached config and mark it read-only so it doesn't persist
  _cachedConfig = {
    ...vendor.configExports,
    ...(apiToken === undefined
      ? {}
      : {
          apiToken: String(apiToken)
        })
  }
  _readOnlyConfig = true
}
function getConfigValues() {
  if (_cachedConfig === undefined) {
    _cachedConfig = {}
    // Order: env var > --config flag > file
    const configPath = getConfigPath()
    if (configPath) {
      const raw = safeReadFileSync(configPath)
      if (raw) {
        try {
          Object.assign(
            _cachedConfig,
            JSON.parse(Buffer.from(raw, 'base64').toString())
          )
        } catch {
          logger.logger.warn(`Failed to parse config at ${configPath}`)
        }
        // Normalize apiKey to apiToken and persist it.
        // This is a one time migration per user.
        if (_cachedConfig['apiKey']) {
          const token = _cachedConfig['apiKey']
          delete _cachedConfig['apiKey']
          updateConfigValue('apiToken', token)
        }
      } else {
        fs.mkdirSync(path.dirname(configPath), {
          recursive: true
        })
      }
    }
  }
  return _cachedConfig
}
let _configPath
let _warnedConfigPathWin32Missing = false
function getConfigPath() {
  // Get the OS app data folder:
  // - Win: %LOCALAPPDATA% or fail?
  // - Mac: %XDG_DATA_HOME% or fallback to "~/Library/Application Support/"
  // - Linux: %XDG_DATA_HOME% or fallback to "~/.local/share/"
  // Note: LOCALAPPDATA is typically: C:\Users\USERNAME\AppData
  // Note: XDG stands for "X Desktop Group", nowadays "freedesktop.org"
  //       On most systems that path is: $HOME/.local/share
  // Then append `socket/settings`, so:
  // - Win: %LOCALAPPDATA%\socket\settings or return undefined
  // - Mac: %XDG_DATA_HOME%/socket/settings or "~/Library/Application Support/socket/settings"
  // - Linux: %XDG_DATA_HOME%/socket/settings or "~/.local/share/socket/settings"

  if (_configPath === undefined) {
    // Lazily access constants.WIN32.
    const { WIN32 } = constants
    let dataHome = WIN32
      ? // Lazily access constants.ENV[LOCALAPPDATA]
        constants.ENV[LOCALAPPDATA]
      : // Lazily access constants.ENV[XDG_DATA_HOME]
        constants.ENV[XDG_DATA_HOME]
    if (!dataHome) {
      if (WIN32) {
        if (!_warnedConfigPathWin32Missing) {
          _warnedConfigPathWin32Missing = true
          logger.logger.warn(`Missing %${LOCALAPPDATA}%`)
        }
      } else {
        dataHome = path.join(
          os.homedir(),
          ...(process$1.platform === 'darwin'
            ? ['Library', 'Application Support']
            : ['.local', 'share'])
        )
      }
    }
    _configPath = dataHome ? path.join(dataHome, SOCKET_APP_DIR) : undefined
  }
  return _configPath
}
function normalizeConfigKey(key) {
  // Note: apiKey was the old name of the token. When we load a config with
  //       property apiKey, we'll copy that to apiToken and delete the old property.
  const normalizedKey = key === 'apiKey' ? 'apiToken' : key
  if (!supportedConfigKeys.has(normalizedKey)) {
    throw new Error(`Invalid config key: ${normalizedKey}`)
  }
  return normalizedKey
}
function findSocketYmlSync(dir = process$1.cwd()) {
  let prevDir = null
  while (dir !== prevDir) {
    let ymlPath = path.join(dir, 'socket.yml')
    let yml = safeReadFileSync(ymlPath)
    if (yml === undefined) {
      ymlPath = path.join(dir, 'socket.yaml')
      yml = safeReadFileSync(ymlPath)
    }
    if (typeof yml === 'string') {
      try {
        return {
          path: ymlPath,
          parsed: vendor.configExports.parseSocketConfig(yml)
        }
      } catch {
        throw new Error(`Found file but was unable to parse ${ymlPath}`)
      }
    }
    prevDir = dir
    dir = path.join(dir, '..')
  }
  return null
}
function getConfigValue(key) {
  const localConfig = getConfigValues()
  return localConfig[normalizeConfigKey(key)]
}
function isReadOnlyConfig() {
  return _readOnlyConfig
}
let _pendingSave = false
function updateConfigValue(key, value) {
  const localConfig = getConfigValues()
  localConfig[normalizeConfigKey(key)] = value
  if (_readOnlyConfig) {
    logger.logger.warn(
      'Not persisting config change; current config overridden through env var or flag'
    )
  } else if (!_pendingSave) {
    _pendingSave = true
    process$1.nextTick(() => {
      _pendingSave = false
      const configPath = getConfigPath()
      if (configPath) {
        fs.writeFileSync(
          configPath,
          Buffer.from(JSON.stringify(localConfig)).toString('base64')
        )
      }
    })
  }
}

const {
  kInternalsSymbol: kInternalsSymbol$1,
  [kInternalsSymbol$1]: { getSentry }
} = constants
class AuthError extends Error {}
class InputError extends Error {
  constructor(message, body) {
    super(message)
    this.body = body
  }
}
async function captureException(exception, hint) {
  const result = captureExceptionSync(exception, hint)
  // "Sleep" for a second, just in case, hopefully enough time to initiate fetch.
  await promises.setTimeout(1000)
  return result
}
function captureExceptionSync(exception, hint) {
  const Sentry = getSentry()
  if (!Sentry) {
    return ''
  }
  debug.debugLog('captureException: Sending exception to Sentry')
  return Sentry.captureException(exception, hint)
}

const {
  SOCKET_CLI_NO_API_TOKEN,
  SOCKET_SECURITY_API_BASE_URL,
  SOCKET_SECURITY_API_PROXY,
  SOCKET_SECURITY_API_TOKEN
} = constants

// The API server that should be used for operations.
function getDefaultApiBaseUrl() {
  const baseUrl =
    // Lazily access constants.ENV[SOCKET_SECURITY_API_BASE_URL].
    constants.ENV[SOCKET_SECURITY_API_BASE_URL] || getConfigValue('apiBaseUrl')
  return strings.isNonEmptyString(baseUrl) ? baseUrl : undefined
}

// The API server that should be used for operations.
function getDefaultHttpProxy() {
  const apiProxy =
    // Lazily access constants.ENV[SOCKET_SECURITY_API_PROXY].
    constants.ENV[SOCKET_SECURITY_API_PROXY] || getConfigValue('apiProxy')
  return strings.isNonEmptyString(apiProxy) ? apiProxy : undefined
}

// This API key should be stored globally for the duration of the CLI execution.
let _defaultToken
function getDefaultToken() {
  // Lazily access constants.ENV[SOCKET_CLI_NO_API_TOKEN].
  if (constants.ENV[SOCKET_CLI_NO_API_TOKEN]) {
    _defaultToken = undefined
  } else {
    const key =
      // Lazily access constants.ENV[SOCKET_SECURITY_API_TOKEN].
      constants.ENV[SOCKET_SECURITY_API_TOKEN] ||
      getConfigValue('apiToken') ||
      _defaultToken
    _defaultToken = strings.isNonEmptyString(key) ? key : undefined
  }
  return _defaultToken
}
function getPublicToken() {
  return (
    // Lazily access constants.ENV[SOCKET_SECURITY_API_TOKEN].
    (constants.ENV[SOCKET_SECURITY_API_TOKEN] || getDefaultToken()) ??
    registryConstants.SOCKET_PUBLIC_API_TOKEN
  )
}
async function setupSdk(
  apiToken = getDefaultToken(),
  apiBaseUrl = getDefaultApiBaseUrl(),
  proxy = getDefaultHttpProxy()
) {
  if (typeof apiToken !== 'string' && isInteractive()) {
    apiToken = await prompts.password({
      message:
        'Enter your Socket.dev API key (not saved, use socket login to persist)'
    })
    _defaultToken = apiToken
  }
  if (!apiToken) {
    throw new AuthError('You need to provide an API key')
  }
  return new sdk.SocketSdk(apiToken, {
    agent: proxy
      ? new vendor.HttpsProxyAgent({
          proxy
        })
      : undefined,
    baseUrl: apiBaseUrl,
    userAgent: sdk.createUserAgentFromPkgJson({
      // The '@rollup/plugin-replace' will replace "process.env['INLINED_SOCKET_CLI_NAME']".
      name: 'socket',
      // The '@rollup/plugin-replace' will replace "process.env['INLINED_SOCKET_CLI_VERSION']".
      version: '0.14.123',
      // The '@rollup/plugin-replace' will replace "process.env['INLINED_SOCKET_CLI_HOMEPAGE']".
      homepage: 'https://github.com/SocketDev/socket-cli'
    })
  })
}

const RangeStyles = ['caret', 'gt', 'lt', 'pin', 'preserve', 'tilde']
function applyRange(refRange, version, style = 'preserve') {
  switch (style) {
    case 'caret':
      return `^${version}`
    case 'gt':
      return `>${version}`
    case 'gte':
      return `>=${version}`
    case 'lt':
      return `<${version}`
    case 'lte':
      return `<=${version}`
    case 'preserve': {
      const comparators = [
        ...new vendor.semverExports.Range(refRange).set
      ].flat()
      const { length } = comparators
      return !length || length > 1
        ? version
        : `${comparators[0].operator}${version}`
    }
    case 'tilde':
      return `~${version}`
    case 'pin':
    default:
      return version
  }
}
function getMajor(version) {
  const coerced = vendor.semverExports.coerce(version)
  if (coerced) {
    try {
      return vendor.semverExports.major(coerced)
    } catch (e) {
      debug.debugLog(`Error parsing '${version}'`, e)
    }
  }
  return null
}

const DiffAction = /*#__PURE__*/ (function (DiffAction) {
  DiffAction['add'] = 'ADD'
  DiffAction['change'] = 'CHANGE'
  DiffAction['remove'] = 'REMOVE'
  return DiffAction
})({})

const depValid = require(shadowNpmPaths.getArboristDepValidPath())

const { UNDEFINED_TOKEN } = constants
function tryRequire(req, ...ids) {
  for (const data of ids) {
    let id
    let transformer
    if (Array.isArray(data)) {
      id = data[0]
      transformer = data[1]
    } else {
      id = data
      transformer = mod => mod
    }
    try {
      // Check that the transformed value isn't `undefined` because older
      // versions of packages like 'proc-log' may not export a `log` method.
      const exported = transformer(req(id))
      if (exported !== undefined) {
        return exported
      }
    } catch {}
  }
  return undefined
}
let _log = UNDEFINED_TOKEN
function getLogger() {
  if (_log === UNDEFINED_TOKEN) {
    _log = tryRequire(
      shadowNpmPaths.getNpmRequire(),
      [
        'proc-log/lib/index.js',
        // The proc-log DefinitelyTyped definition is incorrect. The type definition
        // is really that of its export log.
        mod => mod.log
      ],
      'npmlog/lib/log.js'
    )
  }
  return _log
}

const OverrideSet = require(shadowNpmPaths.getArboristOverrideSetClassPath())

// Implementation code not related to patch https://github.com/npm/cli/pull/8089
// is based on https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/override-set.js:
class SafeOverrideSet extends OverrideSet {
  // Patch adding doOverrideSetsConflict is based on
  // https://github.com/npm/cli/pull/8089.
  static doOverrideSetsConflict(first, second) {
    // If override sets contain one another then we can try to use the more
    // specific one. If neither one is more specific, then we consider them to
    // be in conflict.
    return this.findSpecificOverrideSet(first, second) === undefined
  }

  // Patch adding findSpecificOverrideSet is based on
  // https://github.com/npm/cli/pull/8089.
  static findSpecificOverrideSet(first, second) {
    for (
      let overrideSet = second;
      overrideSet;
      overrideSet = overrideSet.parent
    ) {
      if (overrideSet.isEqual(first)) {
        return second
      }
    }
    for (
      let overrideSet = first;
      overrideSet;
      overrideSet = overrideSet.parent
    ) {
      if (overrideSet.isEqual(second)) {
        return first
      }
    }
    // The override sets are incomparable. Neither one contains the other.
    const log = getLogger()
    log?.silly('Conflicting override sets', first, second)
    return undefined
  }

  // Patch adding childrenAreEqual is based on
  // https://github.com/npm/cli/pull/8089.
  childrenAreEqual(otherOverrideSet) {
    if (this.children.size !== otherOverrideSet.children.size) {
      return false
    }
    for (const { 0: key, 1: childOverrideSet } of this.children) {
      const otherChildOverrideSet = otherOverrideSet.children.get(key)
      if (!otherChildOverrideSet) {
        return false
      }
      if (childOverrideSet.value !== otherChildOverrideSet.value) {
        return false
      }
      if (!childOverrideSet.childrenAreEqual(otherChildOverrideSet)) {
        return false
      }
    }
    return true
  }
  getEdgeRule(edge) {
    for (const rule of this.ruleset.values()) {
      if (rule.name !== edge.name) {
        continue
      }
      // If keySpec is * we found our override.
      if (rule.keySpec === '*') {
        return rule
      }
      // Patch replacing
      // let spec = npa(`${edge.name}@${edge.spec}`)
      // is based on https://github.com/npm/cli/pull/8089.
      //
      // We need to use the rawSpec here, because the spec has the overrides
      // applied to it already. The rawSpec can be undefined, so we need to use
      // the fallback value of spec if it is.
      let spec = vendor.npaExports(`${edge.name}@${edge.rawSpec || edge.spec}`)
      if (spec.type === 'alias') {
        spec = spec.subSpec
      }
      if (spec.type === 'git') {
        if (
          spec.gitRange &&
          vendor.semverExports.intersects(spec.gitRange, rule.keySpec)
        ) {
          return rule
        }
        continue
      }
      if (spec.type === 'range' || spec.type === 'version') {
        if (vendor.semverExports.intersects(spec.fetchSpec, rule.keySpec)) {
          return rule
        }
        continue
      }
      // If we got this far, the spec type is one of tag, directory or file
      // which means we have no real way to make version comparisons, so we
      // just accept the override.
      return rule
    }
    return this
  }

  // Patch adding isEqual is based on
  // https://github.com/npm/cli/pull/8089.
  isEqual(otherOverrideSet) {
    if (this === otherOverrideSet) {
      return true
    }
    if (!otherOverrideSet) {
      return false
    }
    if (
      this.key !== otherOverrideSet.key ||
      this.value !== otherOverrideSet.value
    ) {
      return false
    }
    if (!this.childrenAreEqual(otherOverrideSet)) {
      return false
    }
    if (!this.parent) {
      return !otherOverrideSet.parent
    }
    return this.parent.isEqual(otherOverrideSet.parent)
  }
}

const Node = require(shadowNpmPaths.getArboristNodeClassPath())

// Implementation code not related to patch https://github.com/npm/cli/pull/8089
// is based on https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/node.js:
class SafeNode extends Node {
  // Return true if it's safe to remove this node, because anything that is
  // depending on it would be fine with the thing that they would resolve to if
  // it was removed, or nothing is depending on it in the first place.
  canDedupe(preferDedupe = false) {
    // Not allowed to mess with shrinkwraps or bundles.
    if (this.inDepBundle || this.inShrinkwrap) {
      return false
    }
    // It's a top level pkg, or a dep of one.
    if (!this.resolveParent?.resolveParent) {
      return false
    }
    // No one wants it, remove it.
    if (this.edgesIn.size === 0) {
      return true
    }
    const other = this.resolveParent.resolveParent.resolve(this.name)
    // Nothing else, need this one.
    if (!other) {
      return false
    }
    // If it's the same thing, then always fine to remove.
    if (other.matches(this)) {
      return true
    }
    // If the other thing can't replace this, then skip it.
    if (!other.canReplace(this)) {
      return false
    }
    // Patch replacing
    // if (preferDedupe || semver.gte(other.version, this.version)) {
    //   return true
    // }
    // is based on https://github.com/npm/cli/pull/8089.
    //
    // If we prefer dedupe, or if the version is equal, take the other.
    if (preferDedupe || vendor.semverExports.eq(other.version, this.version)) {
      return true
    }
    // If our current version isn't the result of an override, then prefer to
    // take the greater version.
    if (
      !this.overridden &&
      vendor.semverExports.gt(other.version, this.version)
    ) {
      return true
    }
    return false
  }

  // Is it safe to replace one node with another?  check the edges to
  // make sure no one will get upset.  Note that the node might end up
  // having its own unmet dependencies, if the new node has new deps.
  // Note that there are cases where Arborist will opt to insert a node
  // into the tree even though this function returns false!  This is
  // necessary when a root dependency is added or updated, or when a
  // root dependency brings peer deps along with it.  In that case, we
  // will go ahead and create the invalid state, and then try to resolve
  // it with more tree construction, because it's a user request.
  canReplaceWith(node, ignorePeers) {
    if (this.name !== node.name || this.packageName !== node.packageName) {
      return false
    }
    // Patch replacing
    // if (node.overrides !== this.overrides) {
    //   return false
    // }
    // is based on https://github.com/npm/cli/pull/8089.
    //
    // If this node has no dependencies, then it's irrelevant to check the
    // override rules of the replacement node.
    if (this.edgesOut.size) {
      // XXX need to check for two root nodes?
      if (node.overrides) {
        if (!node.overrides.isEqual(this.overrides)) {
          return false
        }
      } else {
        if (this.overrides) {
          return false
        }
      }
    }
    // To satisfy the patch we ensure `node.overrides === this.overrides`
    // so that the condition we want to replace,
    // if (this.overrides !== node.overrides) {
    // , is not hit.`
    const oldOverrideSet = this.overrides
    let result = true
    if (oldOverrideSet !== node.overrides) {
      this.overrides = node.overrides
    }
    try {
      result = super.canReplaceWith(node, ignorePeers)
      this.overrides = oldOverrideSet
    } catch (e) {
      this.overrides = oldOverrideSet
      throw e
    }
    return result
  }

  // Patch adding deleteEdgeIn is based on https://github.com/npm/cli/pull/8089.
  deleteEdgeIn(edge) {
    this.edgesIn.delete(edge)
    const { overrides } = edge
    if (overrides) {
      this.updateOverridesEdgeInRemoved(overrides)
    }
  }
  addEdgeIn(edge) {
    // Patch replacing
    // if (edge.overrides) {
    //   this.overrides = edge.overrides
    // }
    // is based on https://github.com/npm/cli/pull/8089.
    //
    // We need to handle the case where the new edge in has an overrides field
    // which is different from the current value.
    if (!this.overrides || !this.overrides.isEqual(edge.overrides)) {
      this.updateOverridesEdgeInAdded(edge.overrides)
    }
    this.edgesIn.add(edge)
    // Try to get metadata from the yarn.lock file.
    this.root.meta?.addEdge(edge)
  }

  // @ts-ignore: Incorrectly typed as a property instead of an accessor.
  get overridden() {
    // Patch replacing
    // return !!(this.overrides && this.overrides.value && this.overrides.name === this.name)
    // is based on https://github.com/npm/cli/pull/8089.
    if (
      !this.overrides ||
      !this.overrides.value ||
      this.overrides.name !== this.name
    ) {
      return false
    }
    // The overrides rule is for a package with this name, but some override
    // rules only apply to specific versions. To make sure this package was
    // actually overridden, we check whether any edge going in had the rule
    // applied to it, in which case its overrides set is different than its
    // source node.
    for (const edge of this.edgesIn) {
      if (
        edge.overrides &&
        edge.overrides.name === this.name &&
        edge.overrides.value === this.version
      ) {
        if (!edge.overrides.isEqual(edge.from?.overrides)) {
          return true
        }
      }
    }
    return false
  }
  set parent(newParent) {
    // Patch removing
    // if (parent.overrides) {
    //   this.overrides = parent.overrides.getNodeRule(this)
    // }
    // is based on https://github.com/npm/cli/pull/8089.
    //
    // The "parent" setter is a really large and complex function. To satisfy
    // the patch we hold on to the old overrides value and set `this.overrides`
    // to `undefined` so that the condition we want to remove is not hit.
    const { overrides } = this
    if (overrides) {
      this.overrides = undefined
    }
    try {
      super.parent = newParent
      this.overrides = overrides
    } catch (e) {
      this.overrides = overrides
      throw e
    }
  }

  // Patch adding recalculateOutEdgesOverrides is based on
  // https://github.com/npm/cli/pull/8089.
  recalculateOutEdgesOverrides() {
    // For each edge out propagate the new overrides through.
    for (const edge of this.edgesOut.values()) {
      edge.reload(true)
      if (edge.to) {
        edge.to.updateOverridesEdgeInAdded(edge.overrides)
      }
    }
  }

  // @ts-ignore: Incorrectly typed to accept null.
  set root(newRoot) {
    // Patch removing
    // if (!this.overrides && this.parent && this.parent.overrides) {
    //   this.overrides = this.parent.overrides.getNodeRule(this)
    // }
    // is based on https://github.com/npm/cli/pull/8089.
    //
    // The "root" setter is a really large and complex function. To satisfy the
    // patch we add a dummy value to `this.overrides` so that the condition we
    // want to remove is not hit.
    if (!this.overrides) {
      this.overrides = new SafeOverrideSet({
        overrides: ''
      })
    }
    try {
      super.root = newRoot
      this.overrides = undefined
    } catch (e) {
      this.overrides = undefined
      throw e
    }
  }

  // Patch adding updateOverridesEdgeInAdded is based on
  // https://github.com/npm/cli/pull/7025.
  //
  // This logic isn't perfect either. When we have two edges in that have
  // different override sets, then we have to decide which set is correct. This
  // function assumes the more specific override set is applicable, so if we have
  // dependencies A->B->C and A->C and an override set that specifies what happens
  // for C under A->B, this will work even if the new A->C edge comes along and
  // tries to change the override set. The strictly correct logic is not to allow
  // two edges with different overrides to point to the same node, because even
  // if this node can satisfy both, one of its dependencies might need to be
  // different depending on the edge leading to it. However, this might cause a
  // lot of duplication, because the conflict in the dependencies might never
  // actually happen.
  updateOverridesEdgeInAdded(otherOverrideSet) {
    if (!otherOverrideSet) {
      // Assuming there are any overrides at all, the overrides field is never
      // undefined for any node at the end state of the tree. So if the new edge's
      // overrides is undefined it will be updated later. So we can wait with
      // updating the node's overrides field.
      return false
    }
    if (!this.overrides) {
      this.overrides = otherOverrideSet
      this.recalculateOutEdgesOverrides()
      return true
    }
    if (this.overrides.isEqual(otherOverrideSet)) {
      return false
    }
    const newOverrideSet = SafeOverrideSet.findSpecificOverrideSet(
      this.overrides,
      otherOverrideSet
    )
    if (newOverrideSet) {
      if (this.overrides.isEqual(newOverrideSet)) {
        return false
      }
      this.overrides = newOverrideSet
      this.recalculateOutEdgesOverrides()
      return true
    }
    // This is an error condition. We can only get here if the new override set
    // is in conflict with the existing.
    const log = getLogger()
    log?.silly('Conflicting override sets', this.name)
    return false
  }

  // Patch adding updateOverridesEdgeInRemoved is based on
  // https://github.com/npm/cli/pull/7025.
  updateOverridesEdgeInRemoved(otherOverrideSet) {
    // If this edge's overrides isn't equal to this node's overrides,
    // then removing it won't change newOverrideSet later.
    if (!this.overrides || !this.overrides.isEqual(otherOverrideSet)) {
      return false
    }
    let newOverrideSet
    for (const edge of this.edgesIn) {
      const { overrides: edgeOverrides } = edge
      if (newOverrideSet && edgeOverrides) {
        newOverrideSet = SafeOverrideSet.findSpecificOverrideSet(
          edgeOverrides,
          newOverrideSet
        )
      } else {
        newOverrideSet = edgeOverrides
      }
    }
    if (this.overrides.isEqual(newOverrideSet)) {
      return false
    }
    this.overrides = newOverrideSet
    if (newOverrideSet) {
      // Optimization: If there's any override set at all, then no non-extraneous
      // node has an empty override set. So if we temporarily have no override set
      // (for example, we removed all the edges in), there's no use updating all
      // the edges out right now. Let's just wait until we have an actual override
      // set later.
      this.recalculateOutEdgesOverrides()
    }
    return true
  }
}

const Edge = require(shadowNpmPaths.getArboristEdgeClassPath())

// The Edge class makes heavy use of private properties which subclasses do NOT
// have access to. So we have to recreate any functionality that relies on those
// private properties and use our own "safe" prefixed non-conflicting private
// properties. Implementation code not related to patch https://github.com/npm/cli/pull/8089
// is based on https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/edge.js.
//
// The npm application
// Copyright (c) npm, Inc. and Contributors
// Licensed on the terms of The Artistic License 2.0
//
// An edge in the dependency graph.
// Represents a dependency relationship of some kind.
class SafeEdge extends Edge {
  #safeError
  #safeExplanation
  #safeFrom
  #safeTo
  constructor(options) {
    const { from } = options
    // Defer to supper to validate options and assign non-private values.
    super(options)
    if (from.constructor !== SafeNode) {
      Reflect.setPrototypeOf(from, SafeNode.prototype)
    }
    this.#safeError = null
    this.#safeExplanation = null
    this.#safeFrom = from
    this.#safeTo = null
    this.reload(true)
  }
  get bundled() {
    return !!this.#safeFrom?.package?.bundleDependencies?.includes(this.name)
  }
  get error() {
    if (!this.#safeError) {
      if (!this.#safeTo) {
        if (this.optional) {
          this.#safeError = null
        } else {
          this.#safeError = 'MISSING'
        }
      } else if (
        this.peer &&
        this.#safeFrom === this.#safeTo.parent &&
        // Patch adding "?." use based on
        // https://github.com/npm/cli/pull/8089.
        !this.#safeFrom?.isTop
      ) {
        this.#safeError = 'PEER LOCAL'
      } else if (!this.satisfiedBy(this.#safeTo)) {
        this.#safeError = 'INVALID'
      }
      // Patch adding "else if" condition is based on
      // https://github.com/npm/cli/pull/8089.
      else if (
        this.overrides &&
        this.#safeTo.edgesOut.size &&
        SafeOverrideSet.doOverrideSetsConflict(
          this.overrides,
          this.#safeTo.overrides
        )
      ) {
        // Any inconsistency between the edge's override set and the target's
        // override set is potentially problematic. But we only say the edge is
        // in error if the override sets are plainly conflicting. Note that if
        // the target doesn't have any dependencies of their own, then this
        // inconsistency is irrelevant.
        this.#safeError = 'INVALID'
      } else {
        this.#safeError = 'OK'
      }
    }
    if (this.#safeError === 'OK') {
      return null
    }
    return this.#safeError
  }

  // @ts-ignore: Incorrectly typed as a property instead of an accessor.
  get from() {
    return this.#safeFrom
  }

  // @ts-ignore: Incorrectly typed as a property instead of an accessor.
  get spec() {
    if (
      this.overrides?.value &&
      this.overrides.value !== '*' &&
      this.overrides.name === this.name
    ) {
      if (this.overrides.value.startsWith('$')) {
        const ref = this.overrides.value.slice(1)
        // We may be a virtual root, if we are we want to resolve reference
        // overrides from the real root, not the virtual one.
        //
        // Patch adding "?." use based on
        // https://github.com/npm/cli/pull/8089.
        const pkg = this.#safeFrom?.sourceReference
          ? this.#safeFrom?.sourceReference.root.package
          : this.#safeFrom?.root?.package
        if (pkg?.devDependencies?.[ref]) {
          return pkg.devDependencies[ref]
        }
        if (pkg?.optionalDependencies?.[ref]) {
          return pkg.optionalDependencies[ref]
        }
        if (pkg?.dependencies?.[ref]) {
          return pkg.dependencies[ref]
        }
        if (pkg?.peerDependencies?.[ref]) {
          return pkg.peerDependencies[ref]
        }
        throw new Error(`Unable to resolve reference ${this.overrides.value}`)
      }
      return this.overrides.value
    }
    return this.rawSpec
  }

  // @ts-ignore: Incorrectly typed as a property instead of an accessor.
  get to() {
    return this.#safeTo
  }
  detach() {
    this.#safeExplanation = null
    // Patch replacing
    // if (this.#to) {
    //   this.#to.edgesIn.delete(this)
    // }
    // this.#from.edgesOut.delete(this.#name)
    // is based on https://github.com/npm/cli/pull/8089.
    this.#safeTo?.deleteEdgeIn(this)
    this.#safeFrom?.edgesOut.delete(this.name)
    this.#safeTo = null
    this.#safeError = 'DETACHED'
    this.#safeFrom = null
  }

  // Return the edge data, and an explanation of how that edge came to be here.
  // @ts-ignore: Edge#explain is defined with an unused `seen = []` param.
  explain() {
    if (!this.#safeExplanation) {
      const explanation = {
        type: this.type,
        name: this.name,
        spec: this.spec,
        bundled: false,
        overridden: false,
        error: undefined,
        from: undefined,
        rawSpec: undefined
      }
      if (this.rawSpec !== this.spec) {
        explanation.rawSpec = this.rawSpec
        explanation.overridden = true
      }
      if (this.bundled) {
        explanation.bundled = this.bundled
      }
      if (this.error) {
        explanation.error = this.error
      }
      if (this.#safeFrom) {
        explanation.from = this.#safeFrom.explain()
      }
      this.#safeExplanation = explanation
    }
    return this.#safeExplanation
  }
  reload(hard = false) {
    this.#safeExplanation = null
    // Patch replacing
    // if (this.#from.overrides) {
    // is based on https://github.com/npm/cli/pull/8089.
    let needToUpdateOverrideSet = false
    let newOverrideSet
    let oldOverrideSet
    if (this.#safeFrom?.overrides) {
      newOverrideSet = this.#safeFrom.overrides.getEdgeRule(this)
      if (newOverrideSet && !newOverrideSet.isEqual(this.overrides)) {
        // If there's a new different override set we need to propagate it to
        // the nodes. If we're deleting the override set then there's no point
        // propagating it right now since it will be filled with another value
        // later.
        needToUpdateOverrideSet = true
        oldOverrideSet = this.overrides
        this.overrides = newOverrideSet
      }
    } else {
      this.overrides = undefined
    }
    // Patch adding "?." use based on
    // https://github.com/npm/cli/pull/8089.
    const newTo = this.#safeFrom?.resolve(this.name)
    if (newTo !== this.#safeTo) {
      // Patch replacing
      // this.#to.edgesIn.delete(this)
      // is based on https://github.com/npm/cli/pull/8089.
      this.#safeTo?.deleteEdgeIn(this)
      this.#safeTo = newTo ?? null
      this.#safeError = null
      this.#safeTo?.addEdgeIn(this)
    } else if (hard) {
      this.#safeError = null
    }
    // Patch adding "else if" condition based on
    // https://github.com/npm/cli/pull/8089.
    else if (needToUpdateOverrideSet && this.#safeTo) {
      // Propagate the new override set to the target node.
      this.#safeTo.updateOverridesEdgeInRemoved(oldOverrideSet)
      this.#safeTo.updateOverridesEdgeInAdded(newOverrideSet)
    }
  }
  satisfiedBy(node) {
    // Patch replacing
    // if (node.name !== this.#name) {
    //   return false
    // }
    // is based on https://github.com/npm/cli/pull/8089.
    if (node.name !== this.name || !this.#safeFrom) {
      return false
    }
    // NOTE: this condition means we explicitly do not support overriding
    // bundled or shrinkwrapped dependencies
    if (node.hasShrinkwrap || node.inShrinkwrap || node.inBundle) {
      return depValid(node, this.rawSpec, this.accept, this.#safeFrom)
    }
    // Patch replacing
    // return depValid(node, this.spec, this.#accept, this.#from)
    // is based on https://github.com/npm/cli/pull/8089.
    //
    // If there's no override we just use the spec.
    if (!this.overrides?.keySpec) {
      return depValid(node, this.spec, this.accept, this.#safeFrom)
    }
    // There's some override. If the target node satisfies the overriding spec
    // then it's okay.
    if (depValid(node, this.spec, this.accept, this.#safeFrom)) {
      return true
    }
    // If it doesn't, then it should at least satisfy the original spec.
    if (!depValid(node, this.rawSpec, this.accept, this.#safeFrom)) {
      return false
    }
    // It satisfies the original spec, not the overriding spec. We need to make
    // sure it doesn't use the overridden spec.
    // For example:
    //   we might have an ^8.0.0 rawSpec, and an override that makes
    //   keySpec=8.23.0 and the override value spec=9.0.0.
    //   If the node is 9.0.0, then it's okay because it's consistent with spec.
    //   If the node is 8.24.0, then it's okay because it's consistent with the rawSpec.
    //   If the node is 8.23.0, then it's not okay because even though it's consistent
    //   with the rawSpec, it's also consistent with the keySpec.
    //   So we're looking for ^8.0.0 or 9.0.0 and not 8.23.0.
    return !depValid(node, this.overrides.keySpec, this.accept, this.#safeFrom)
  }
}

const { LOOP_SENTINEL, NPM: NPM$2, NPM_REGISTRY_URL } = constants
function getUrlOrigin(input) {
  try {
    // TODO: URL.parse is available in Node 22.1.0. We can use it when we drop Node 18.
    // https://nodejs.org/docs/latest-v22.x/api/url.html#urlparseinput-base
    // return URL.parse(input)?.origin ?? ''
    return new URL(input).origin ?? ''
  } catch {}
  return ''
}
function findBestPatchVersion(
  node,
  availableVersions,
  vulnerableVersionRange,
  _firstPatchedVersionIdentifier
) {
  const manifestData = registry.getManifestData(NPM$2, node.name)
  let eligibleVersions
  if (manifestData && manifestData.name === manifestData.package) {
    const major = getMajor(manifestData.version)
    if (typeof major !== 'number') {
      return null
    }
    eligibleVersions = availableVersions.filter(v => getMajor(v) === major)
  } else {
    const major = getMajor(node.version)
    if (typeof major !== 'number') {
      return null
    }
    eligibleVersions = availableVersions.filter(
      v =>
        // Filter for versions that are within the current major version and
        // are NOT in the vulnerable range.
        getMajor(v) === major &&
        (!vulnerableVersionRange ||
          !vendor.semverExports.satisfies(v, vulnerableVersionRange))
    )
  }
  return eligibleVersions
    ? vendor.semverExports.maxSatisfying(eligibleVersions, '*')
    : null
}
function findPackageNode(tree, name, version) {
  const queue = [tree]
  let sentinel = 0
  while (queue.length) {
    if (sentinel++ === LOOP_SENTINEL) {
      throw new Error('Detected infinite loop in findPackageNodes')
    }
    const currentNode = queue.pop()
    const node = currentNode.children.get(name)
    if (node && (typeof version !== 'string' || node.version === version)) {
      return node
    }
    const children = [...currentNode.children.values()]
    for (let i = children.length - 1; i >= 0; i -= 1) {
      queue.push(children[i])
    }
  }
}
function findPackageNodes(tree, name, version) {
  const queue = [tree]
  const matches = []
  let sentinel = 0
  while (queue.length) {
    if (sentinel++ === LOOP_SENTINEL) {
      throw new Error('Detected infinite loop in findPackageNodes')
    }
    const currentNode = queue.pop()
    const node = currentNode.children.get(name)
    if (node && 'undefined' !== 'string') {
      matches.push(node)
    }
    const children = [...currentNode.children.values()]
    for (let i = children.length - 1; i >= 0; i -= 1) {
      queue.push(children[i])
    }
  }
  return matches
}
function getDetailsFromDiff(diff_, options) {
  const details = []
  // `diff_` is `null` when `npm install --package-lock-only` is passed.
  if (!diff_) {
    return details
  }
  const include = {
    __proto__: null,
    unchanged: false,
    unknownOrigin: false,
    ...{
      __proto__: null,
      ...options
    }.include
  }
  const queue = [...diff_.children]
  let pos = 0
  let { length: queueLength } = queue
  while (pos < queueLength) {
    if (pos === LOOP_SENTINEL) {
      throw new Error('Detected infinite loop while walking Arborist diff')
    }
    const diff = queue[pos++]
    const { action } = diff
    if (action) {
      // The `pkgNode`, i.e. the `ideal` node, will be `undefined` if the diff
      // action is 'REMOVE'
      // The `oldNode`, i.e. the `actual` node, will be `undefined` if the diff
      // action is 'ADD'.
      const { actual: oldNode, ideal: pkgNode } = diff
      let existing
      let keep = false
      if (action === DiffAction.change) {
        if (pkgNode?.package.version !== oldNode?.package.version) {
          keep = true
          if (
            oldNode?.package.name &&
            oldNode.package.name === pkgNode?.package.name
          ) {
            existing = oldNode
          }
        } else {
          debug.debugLog('SKIPPING META CHANGE ON\n', diff)
        }
      } else {
        keep = action !== DiffAction.remove
      }
      if (keep && pkgNode?.resolved && (!oldNode || oldNode.resolved)) {
        if (
          include.unknownOrigin ||
          getUrlOrigin(pkgNode.resolved) === NPM_REGISTRY_URL
        ) {
          details.push({
            node: pkgNode,
            existing
          })
        }
      }
    }
    for (const child of diff.children) {
      queue[queueLength++] = child
    }
  }
  if (include.unchanged) {
    const { unchanged } = diff_
    for (let i = 0, { length } = unchanged; i < length; i += 1) {
      const pkgNode = unchanged[i]
      if (
        include.unknownOrigin ||
        getUrlOrigin(pkgNode.resolved) === NPM_REGISTRY_URL
      ) {
        details.push({
          node: pkgNode,
          existing: pkgNode
        })
      }
    }
  }
  return details
}
function isTopLevel(tree, node) {
  return tree.children.get(node.name) === node
}
function updateNode(
  node,
  packument,
  vulnerableVersionRange,
  firstPatchedVersionIdentifier
) {
  const availableVersions = Object.keys(packument.versions)
  // Find the highest non-vulnerable version within the same major range
  const targetVersion = findBestPatchVersion(
    node,
    availableVersions,
    vulnerableVersionRange
  )
  const targetPackument = targetVersion
    ? packument.versions[targetVersion]
    : undefined
  // Check !targetVersion to make TypeScript happy.
  if (!targetVersion || !targetPackument) {
    // No suitable patch version found.
    return false
  }
  // Object.defineProperty is needed to set the version property and replace
  // the old value with targetVersion.
  Object.defineProperty(node, 'version', {
    configurable: true,
    enumerable: true,
    get: () => targetVersion
  })
  // Update package.version associated with the node.
  node.package.version = targetVersion
  // Update node.resolved.
  const purlObj = packageurlJs.PackageURL.fromString(`pkg:npm/${node.name}`)
  node.resolved = `${NPM_REGISTRY_URL}/${node.name}/-/${purlObj.name}-${targetVersion}.tgz`
  // Update node.integrity with the targetPackument.dist.integrity value if available
  // else delete node.integrity so a new value is resolved for the target version.
  const { integrity } = targetPackument.dist
  if (integrity) {
    node.integrity = integrity
  } else {
    delete node.integrity
  }
  // Update node.package.deprecated based on targetPackument.deprecated.
  if (objects.hasOwn(targetPackument, 'deprecated')) {
    node.package['deprecated'] = targetPackument.deprecated
  } else {
    delete node.package['deprecated']
  }
  // Update node.package.dependencies.
  const newDeps = {
    ...targetPackument.dependencies
  }
  const { dependencies: oldDeps } = node.package
  node.package.dependencies = newDeps
  if (oldDeps) {
    for (const oldDepName of Object.keys(oldDeps)) {
      if (!objects.hasOwn(newDeps, oldDepName)) {
        // Detach old edges for dependencies that don't exist on the updated
        // node.package.dependencies.
        node.edgesOut.get(oldDepName)?.detach()
      }
    }
  }
  for (const newDepName of Object.keys(newDeps)) {
    if (!objects.hasOwn(oldDeps, newDepName)) {
      // Add new edges for dependencies that don't exist on the old
      // node.package.dependencies.
      node.addEdgeOut(
        new Edge({
          from: node,
          name: newDepName,
          spec: newDeps[newDepName],
          type: 'prod'
        })
      )
    }
  }
  return true
}
function updatePackageJsonFromNode(
  editablePkgJson,
  tree,
  node,
  targetVersion,
  rangeStyle
) {
  let result = false
  if (!isTopLevel(tree, node)) {
    return result
  }
  const { name } = node
  for (const depField of [
    'dependencies',
    'optionalDependencies',
    'peerDependencies'
  ]) {
    const depObject = editablePkgJson.content[depField]
    if (depObject) {
      const oldRange = depObject[name]
      if (oldRange) {
        const newRange = applyRange(oldRange, targetVersion, rangeStyle)
        if (oldRange !== newRange) {
          result = true
          editablePkgJson.update({
            [depField]: {
              ...depObject,
              [name]: newRange
            }
          })
        }
      }
    }
  }
  return result
}

const {
  ALERT_TYPE_CRITICAL_CVE,
  ALERT_TYPE_CVE,
  ALERT_TYPE_MEDIUM_CVE,
  ALERT_TYPE_MILD_CVE
} = constants
function isArtifactAlertCve(alert) {
  const { type } = alert
  return (
    type === ALERT_TYPE_CVE ||
    type === ALERT_TYPE_MEDIUM_CVE ||
    type === ALERT_TYPE_MILD_CVE ||
    type === ALERT_TYPE_CRITICAL_CVE
  )
}

const ALERT_FIX_TYPE = /*#__PURE__*/ (function (ALERT_FIX_TYPE) {
  ALERT_FIX_TYPE['cve'] = 'cve'
  ALERT_FIX_TYPE['upgrade'] = 'upgrade'
  return ALERT_FIX_TYPE
})({})

function pick(input, keys) {
  const result = {}
  for (const key of keys) {
    result[key] = input[key]
  }
  return result
}

function stringJoinWithSeparateFinalSeparator(list, separator = ' and ') {
  const values = list.filter(Boolean)
  const { length } = values
  if (!length) {
    return ''
  }
  if (length === 1) {
    return values[0]
  }
  const finalValue = values.pop()
  return `${values.join(', ')}${separator}${finalValue}`
}

const ALERT_SEVERITY = /*#__PURE__*/ (function (ALERT_SEVERITY) {
  ALERT_SEVERITY['critical'] = 'critical'
  ALERT_SEVERITY['high'] = 'high'
  ALERT_SEVERITY['middle'] = 'middle'
  ALERT_SEVERITY['low'] = 'low'
  return ALERT_SEVERITY
})({})
// Ordered from most severe to least.
const ALERT_SEVERITIES_SORTED = Object.freeze([
  'critical',
  'high',
  'middle',
  'low'
])
function getDesiredSeverities(lowestToInclude) {
  const result = []
  for (const severity of ALERT_SEVERITIES_SORTED) {
    result.push(severity)
    if (severity === lowestToInclude) {
      break
    }
  }
  return result
}
function formatSeverityCount(severityCount) {
  const summary = []
  for (const severity of ALERT_SEVERITIES_SORTED) {
    if (severityCount[severity]) {
      summary.push(`${severityCount[severity]} ${severity}`)
    }
  }
  return stringJoinWithSeparateFinalSeparator(summary)
}
function getSeverityCount(issues, lowestToInclude) {
  const severityCount = pick(
    {
      low: 0,
      middle: 0,
      high: 0,
      critical: 0
    },
    getDesiredSeverities(lowestToInclude)
  )
  for (const issue of issues) {
    const { value } = issue
    if (!value) {
      continue
    }
    const { severity } = value
    if (severityCount[severity] !== undefined) {
      severityCount[severity] += 1
    }
  }
  return severityCount
}

class ColorOrMarkdown {
  constructor(useMarkdown) {
    this.useMarkdown = !!useMarkdown
  }
  bold(text) {
    return this.useMarkdown
      ? `**${text}**`
      : vendor.yoctocolorsCjsExports.bold(`${text}`)
  }
  header(text, level = 1) {
    return this.useMarkdown
      ? `\n${''.padStart(level, '#')} ${text}\n`
      : vendor.yoctocolorsCjsExports.underline(
          `\n${level === 1 ? vendor.yoctocolorsCjsExports.bold(text) : text}\n`
        )
  }
  hyperlink(text, url, { fallback = true, fallbackToUrl } = {}) {
    if (url) {
      return this.useMarkdown
        ? `[${text}](${url})`
        : vendor.terminalLinkExports(text, url, {
            fallback: fallbackToUrl ? (_text, url) => url : fallback
          })
    }
    return text
  }
  indent(...args) {
    return indentString(...args)
  }
  italic(text) {
    return this.useMarkdown
      ? `_${text}_`
      : vendor.yoctocolorsCjsExports.italic(`${text}`)
  }
  json(value) {
    return this.useMarkdown
      ? '```json\n' + JSON.stringify(value) + '\n```'
      : JSON.stringify(value)
  }
  list(items) {
    const indentedContent = items.map(item => this.indent(item).trimStart())
    return this.useMarkdown
      ? `* ${indentedContent.join('\n* ')}\n`
      : `${indentedContent.join('\n')}\n`
  }
}

function getSocketDevAlertUrl(alertType) {
  return `https://socket.dev/alerts/${alertType}`
}
function getSocketDevPackageOverviewUrl(eco, name, version) {
  return `https://socket.dev/${eco}/package/${name}${version ? `/overview/${version}` : ''}`
}

let _translations
function getTranslations() {
  if (_translations === undefined) {
    _translations = require(
      // Lazily access constants.rootPath.
      path.join(constants.rootPath, 'translations.json')
    )
  }
  return _translations
}

const ALERT_SEVERITY_COLOR = /*#__PURE__*/ (function (ALERT_SEVERITY_COLOR) {
  ALERT_SEVERITY_COLOR['critical'] = 'magenta'
  ALERT_SEVERITY_COLOR['high'] = 'red'
  ALERT_SEVERITY_COLOR['middle'] = 'yellow'
  ALERT_SEVERITY_COLOR['low'] = 'white'
  return ALERT_SEVERITY_COLOR
})({})
const ALERT_SEVERITY_ORDER = /*#__PURE__*/ (function (ALERT_SEVERITY_ORDER) {
  ALERT_SEVERITY_ORDER[(ALERT_SEVERITY_ORDER['critical'] = 0)] = 'critical'
  ALERT_SEVERITY_ORDER[(ALERT_SEVERITY_ORDER['high'] = 1)] = 'high'
  ALERT_SEVERITY_ORDER[(ALERT_SEVERITY_ORDER['middle'] = 2)] = 'middle'
  ALERT_SEVERITY_ORDER[(ALERT_SEVERITY_ORDER['low'] = 3)] = 'low'
  ALERT_SEVERITY_ORDER[(ALERT_SEVERITY_ORDER['none'] = 4)] = 'none'
  return ALERT_SEVERITY_ORDER
})({})
const { CVE_ALERT_PROPS_FIRST_PATCHED_VERSION_IDENTIFIER, NPM: NPM$1 } =
  constants
const MIN_ABOVE_THE_FOLD_COUNT = 3
const MIN_ABOVE_THE_FOLD_ALERT_COUNT = 1
const format = new ColorOrMarkdown(false)
function alertsHaveBlocked(alerts) {
  return alerts.find(a => a.blocked) !== undefined
}
function alertsHaveSeverity(alerts, severity) {
  return alerts.find(a => a.raw.severity === severity) !== undefined
}
function alertSeverityComparator(a, b) {
  return getAlertSeverityOrder(a) - getAlertSeverityOrder(b)
}
function getAlertSeverityOrder(alert) {
  const { severity } = alert.raw
  return severity === ALERT_SEVERITY.critical
    ? 0
    : severity === ALERT_SEVERITY.high
      ? 1
      : severity === ALERT_SEVERITY.middle
        ? 2
        : severity === ALERT_SEVERITY.low
          ? 3
          : 4
}
function getAlertsSeverityOrder(alerts) {
  return alertsHaveBlocked(alerts) ||
    alertsHaveSeverity(alerts, ALERT_SEVERITY.critical)
    ? 0
    : alertsHaveSeverity(alerts, ALERT_SEVERITY.high)
      ? 1
      : alertsHaveSeverity(alerts, ALERT_SEVERITY.middle)
        ? 2
        : alertsHaveSeverity(alerts, ALERT_SEVERITY.low)
          ? 3
          : 4
}
function getHiddenRiskCounts(hiddenAlerts) {
  const riskCounts = {
    critical: 0,
    high: 0,
    middle: 0,
    low: 0
  }
  for (const alert of hiddenAlerts) {
    switch (getAlertSeverityOrder(alert)) {
      case ALERT_SEVERITY_ORDER.critical:
        riskCounts.critical += 1
        break
      case ALERT_SEVERITY_ORDER.high:
        riskCounts.high += 1
        break
      case ALERT_SEVERITY_ORDER.middle:
        riskCounts.middle += 1
        break
      case ALERT_SEVERITY_ORDER.low:
        riskCounts.low += 1
        break
    }
  }
  return riskCounts
}
function getHiddenRisksDescription(riskCounts) {
  const descriptions = []
  if (riskCounts.critical) {
    descriptions.push(`${riskCounts.critical} ${getSeverityLabel('critical')}`)
  }
  if (riskCounts.high) {
    descriptions.push(`${riskCounts.high} ${getSeverityLabel('high')}`)
  }
  if (riskCounts.middle) {
    descriptions.push(`${riskCounts.middle} ${getSeverityLabel('middle')}`)
  }
  if (riskCounts.low) {
    descriptions.push(`${riskCounts.low} ${getSeverityLabel('low')}`)
  }
  return `(${descriptions.join('; ')})`
}
function getSeverityLabel(severity) {
  return severity === 'middle' ? 'moderate' : severity
}
async function addArtifactToAlertsMap(artifact, alertsByPkgId, options) {
  // Make TypeScript happy.
  if (!artifact.name || !artifact.version || !artifact.alerts?.length) {
    return alertsByPkgId
  }
  const {
    consolidate = false,
    include: _include,
    overrides
  } = {
    __proto__: null,
    ...options
  }
  const include = {
    __proto__: null,
    blocked: true,
    critical: true,
    cve: true,
    unfixable: true,
    upgradable: false,
    ..._include
  }
  const name = packages.resolvePackageName(artifact)
  const { version } = artifact
  const pkgId = `${name}@${version}`
  const major = vendor.semverExports.major(version)
  const socketYml = findSocketYmlSync()
  const enabledState = {
    __proto__: null,
    ...socketYml?.parsed.issueRules
  }
  let sockPkgAlerts = []
  for (const alert of artifact.alerts) {
    const action = alert.action ?? ''
    const enabledFlag = enabledState[alert.type]
    if (
      (action === 'ignore' && enabledFlag !== true) ||
      enabledFlag === false
    ) {
      continue
    }
    const blocked = action === 'error'
    const critical = alert.severity === ALERT_SEVERITY.critical
    const cve = isArtifactAlertCve(alert)
    const fixType = alert.fix?.type ?? ''
    const fixableCve = fixType === ALERT_FIX_TYPE.cve
    const fixableUpgrade = fixType === ALERT_FIX_TYPE.upgrade
    const fixable = fixableCve || fixableUpgrade
    const upgradable = fixableUpgrade && !objects.hasOwn(overrides, name)
    if (
      (include.blocked && blocked) ||
      (include.critical && critical) ||
      (include.cve && cve) ||
      (include.unfixable && !fixable) ||
      (include.upgradable && upgradable)
    ) {
      sockPkgAlerts.push({
        name,
        version,
        key: alert.key,
        type: alert.type,
        blocked,
        critical,
        fixable,
        raw: alert,
        upgradable
      })
    }
  }
  if (!sockPkgAlerts.length) {
    return alertsByPkgId
  }
  if (consolidate) {
    const highestForCve = new Map()
    const highestForUpgrade = new Map()
    const unfixableAlerts = []
    for (const sockPkgAlert of sockPkgAlerts) {
      const alert = sockPkgAlert.raw
      const fixType = alert.fix?.type ?? ''
      if (fixType === ALERT_FIX_TYPE.cve) {
        const patchedVersion =
          alert.props[CVE_ALERT_PROPS_FIRST_PATCHED_VERSION_IDENTIFIER]
        const patchedMajor = vendor.semverExports.major(patchedVersion)
        const oldHighest = highestForCve.get(patchedMajor)
        const highest = oldHighest?.version ?? '0.0.0'
        if (vendor.semverExports.gt(patchedVersion, highest)) {
          highestForCve.set(patchedMajor, {
            alert: sockPkgAlert,
            version: patchedVersion
          })
        }
      } else if (fixType === ALERT_FIX_TYPE.upgrade) {
        const oldHighest = highestForUpgrade.get(major)
        const highest = oldHighest?.version ?? '0.0.0'
        if (vendor.semverExports.gt(version, highest)) {
          highestForUpgrade.set(major, {
            alert: sockPkgAlert,
            version
          })
        }
      } else {
        unfixableAlerts.push(sockPkgAlert)
      }
    }
    sockPkgAlerts = [
      ...unfixableAlerts,
      ...[...highestForCve.values()].map(d => d.alert),
      ...[...highestForUpgrade.values()].map(d => d.alert)
    ]
  }
  if (sockPkgAlerts.length) {
    sockPkgAlerts.sort((a, b) => sorts.naturalCompare(a.type, b.type))
    alertsByPkgId.set(pkgId, sockPkgAlerts)
  }
  return alertsByPkgId
}
function getCveInfoByAlertsMap(alertsMap, options) {
  const exclude = {
    upgradable: true,
    ...{
      __proto__: null,
      ...options
    }.exclude
  }
  let infoByPkg = null
  for (const [pkgId, sockPkgAlerts] of alertsMap) {
    const purlObj = packageurlJs.PackageURL.fromString(`pkg:npm/${pkgId}`)
    const name = packages.resolvePackageName(purlObj)
    for (const sockPkgAlert of sockPkgAlerts) {
      const alert = sockPkgAlert.raw
      if (
        alert.fix?.type !== ALERT_FIX_TYPE.cve ||
        (exclude.upgradable && registry.getManifestData(NPM$1, name))
      ) {
        continue
      }
      if (!infoByPkg) {
        infoByPkg = new Map()
      }
      let infos = infoByPkg.get(name)
      if (!infos) {
        infos = []
        infoByPkg.set(name, infos)
      }
      const { firstPatchedVersionIdentifier, vulnerableVersionRange } =
        alert.props
      infos.push({
        firstPatchedVersionIdentifier,
        vulnerableVersionRange: new vendor.semverExports.Range(
          vulnerableVersionRange
        ).format()
      })
    }
  }
  return infoByPkg
}
function logAlertsMap(alertsMap, options) {
  const { hideAt = 'middle', output = process.stderr } = {
    __proto__: null,
    ...options
  }
  const translations = getTranslations()
  const sortedEntries = [...alertsMap.entries()].sort(
    (a, b) => getAlertsSeverityOrder(a[1]) - getAlertsSeverityOrder(b[1])
  )
  const aboveTheFoldPkgIds = new Set()
  const viewableAlertsByPkgId = new Map()
  const hiddenAlertsByPkgId = new Map()
  for (let i = 0, { length } = sortedEntries; i < length; i += 1) {
    const { 0: pkgId, 1: alerts } = sortedEntries[i]
    const hiddenAlerts = []
    const viewableAlerts = alerts.filter(a => {
      const keep =
        a.blocked || getAlertSeverityOrder(a) < ALERT_SEVERITY_ORDER[hideAt]
      if (!keep) {
        hiddenAlerts.push(a)
      }
      return keep
    })
    if (hiddenAlerts.length) {
      hiddenAlertsByPkgId.set(pkgId, hiddenAlerts.sort(alertSeverityComparator))
    }
    if (!viewableAlerts.length) {
      continue
    }
    viewableAlerts.sort(alertSeverityComparator)
    viewableAlertsByPkgId.set(pkgId, viewableAlerts)
    if (
      viewableAlerts.find(
        a => a.blocked || getAlertSeverityOrder(a) < ALERT_SEVERITY_ORDER.middle
      )
    ) {
      aboveTheFoldPkgIds.add(pkgId)
    }
  }

  // If MIN_ABOVE_THE_FOLD_COUNT is NOT met add more from viewable pkg ids.
  for (const { 0: pkgId } of viewableAlertsByPkgId.entries()) {
    if (aboveTheFoldPkgIds.size >= MIN_ABOVE_THE_FOLD_COUNT) {
      break
    }
    aboveTheFoldPkgIds.add(pkgId)
  }
  // If MIN_ABOVE_THE_FOLD_COUNT is STILL NOT met add more from hidden pkg ids.
  for (const { 0: pkgId, 1: hiddenAlerts } of hiddenAlertsByPkgId.entries()) {
    if (aboveTheFoldPkgIds.size >= MIN_ABOVE_THE_FOLD_COUNT) {
      break
    }
    aboveTheFoldPkgIds.add(pkgId)
    const viewableAlerts = viewableAlertsByPkgId.get(pkgId) ?? []
    if (viewableAlerts.length < MIN_ABOVE_THE_FOLD_ALERT_COUNT) {
      const neededCount = MIN_ABOVE_THE_FOLD_ALERT_COUNT - viewableAlerts.length
      let removedHiddenAlerts
      if (hiddenAlerts.length - neededCount > 0) {
        removedHiddenAlerts = hiddenAlerts.splice(
          0,
          MIN_ABOVE_THE_FOLD_ALERT_COUNT
        )
      } else {
        removedHiddenAlerts = hiddenAlerts
        hiddenAlertsByPkgId.delete(pkgId)
      }
      viewableAlertsByPkgId.set(pkgId, [
        ...viewableAlerts,
        ...removedHiddenAlerts
      ])
    }
  }
  const mentionedPkgIdsWithHiddenAlerts = new Set()
  for (
    let i = 0,
      prevAboveTheFold = true,
      entries = [...viewableAlertsByPkgId.entries()],
      { length } = entries;
    i < length;
    i += 1
  ) {
    const { 0: pkgId, 1: alerts } = entries[i]
    const lines = new Set()
    for (const alert of alerts) {
      const { type } = alert
      const severity = alert.raw.severity ?? ''
      const attributes = [
        ...(severity
          ? [
              vendor.yoctocolorsCjsExports[ALERT_SEVERITY_COLOR[severity]](
                getSeverityLabel(severity)
              )
            ]
          : []),
        ...(alert.blocked
          ? [
              vendor.yoctocolorsCjsExports.bold(
                vendor.yoctocolorsCjsExports.red('blocked')
              )
            ]
          : []),
        ...(alert.fixable ? ['fixable'] : [])
      ]
      const maybeAttributes = attributes.length
        ? ` ${vendor.yoctocolorsCjsExports.italic(`(${attributes.join('; ')})`)}`
        : ''
      // Based data from { pageProps: { alertTypes } } of:
      // https://socket.dev/_next/data/94666139314b6437ee4491a0864e72b264547585/en-US.json
      const info = translations.alerts[type]
      const title = info?.title ?? type
      const maybeDesc = info?.description ? ` - ${info.description}` : ''
      const content = `${title}${maybeAttributes}${maybeDesc}`
      // TODO: emoji seems to mis-align terminals sometimes
      lines.add(`  ${content}`)
    }
    const purlObj = packageurlJs.PackageURL.fromString(`pkg:npm/${pkgId}`)
    const hyperlink = format.hyperlink(
      pkgId,
      getSocketDevPackageOverviewUrl(
        NPM$1,
        packages.resolvePackageName(purlObj),
        purlObj.version
      )
    )
    const isAboveTheFold = aboveTheFoldPkgIds.has(pkgId)
    if (isAboveTheFold) {
      aboveTheFoldPkgIds.add(pkgId)
      output.write(`${i ? '\n' : ''}${hyperlink}:\n`)
    } else {
      output.write(`${prevAboveTheFold ? '\n' : ''}${hyperlink}:\n`)
    }
    for (const line of lines) {
      output.write(`${line}\n`)
    }
    const hiddenAlerts = hiddenAlertsByPkgId.get(pkgId) ?? []
    const { length: hiddenAlertsCount } = hiddenAlerts
    if (hiddenAlertsCount) {
      mentionedPkgIdsWithHiddenAlerts.add(pkgId)
      if (hiddenAlertsCount === 1) {
        output.write(
          `  ${vendor.yoctocolorsCjsExports.dim(`+1 Hidden ${getSeverityLabel(hiddenAlerts[0].raw.severity ?? 'low')} risk alert`)}\n`
        )
      } else {
        output.write(
          `  ${vendor.yoctocolorsCjsExports.dim(`+${hiddenAlertsCount} Hidden alerts ${vendor.yoctocolorsCjsExports.italic(getHiddenRisksDescription(getHiddenRiskCounts(hiddenAlerts)))}`)}\n`
        )
      }
    }
    prevAboveTheFold = isAboveTheFold
  }
  const additionalHiddenCount =
    hiddenAlertsByPkgId.size - mentionedPkgIdsWithHiddenAlerts.size
  if (additionalHiddenCount) {
    const totalRiskCounts = {
      critical: 0,
      high: 0,
      middle: 0,
      low: 0
    }
    for (const { 0: pkgId, 1: alerts } of hiddenAlertsByPkgId.entries()) {
      if (mentionedPkgIdsWithHiddenAlerts.has(pkgId)) {
        continue
      }
      const riskCounts = getHiddenRiskCounts(alerts)
      totalRiskCounts.critical += riskCounts.critical
      totalRiskCounts.high += riskCounts.high
      totalRiskCounts.middle += riskCounts.middle
      totalRiskCounts.low += riskCounts.low
    }
    output.write(
      `${aboveTheFoldPkgIds.size ? '\n' : ''}${vendor.yoctocolorsCjsExports.dim(`${aboveTheFoldPkgIds.size ? '+' : ''}${additionalHiddenCount} Packages with hidden alerts ${vendor.yoctocolorsCjsExports.italic(getHiddenRisksDescription(totalRiskCounts))}`)}\n`
    )
  }
  output.write('\n')
}

async function getAlertsMapFromArborist(arb, options_) {
  const options = {
    __proto__: null,
    consolidate: false,
    nothrow: false,
    ...options_
  }
  const include = {
    __proto__: null,
    existing: false,
    ...options.include
  }
  const needInfoOn = getDetailsFromDiff(arb.diff, {
    include: {
      unchanged: include.existing
    }
  })
  const purls = needInfoOn.map(d => `pkg:npm/${d.node.pkgid}`)
  let overrides
  const overridesMap = (
    arb.actualTree ??
    arb.idealTree ??
    (await arb.loadActual())
  )?.overrides?.children
  if (overridesMap) {
    overrides = Object.fromEntries(
      [...overridesMap.entries()].map(([key, overrideSet]) => {
        return [key, overrideSet.value]
      })
    )
  }
  return await getAlertsMapFromPurls(purls, {
    overrides,
    ...options
  })
}
async function getAlertsMapFromPnpmLockfile(lockfile, options_) {
  const options = {
    __proto__: null,
    consolidate: false,
    nothrow: false,
    ...options_
  }
  const depTypes = vendor.libExports$1.detectDepTypes(lockfile)
  const purls = Object.keys(depTypes).map(id => `pkg:npm/${id}`)
  return await getAlertsMapFromPurls(purls, {
    overrides: lockfile.overrides,
    ...options
  })
}
async function getAlertsMapFromPurls(purls, options_) {
  const options = {
    __proto__: null,
    consolidate: false,
    nothrow: false,
    ...options_
  }
  const include = {
    __proto__: null,
    actions: undefined,
    blocked: true,
    critical: true,
    cve: true,
    existing: false,
    unfixable: true,
    upgradable: false,
    ...options.include
  }
  const { spinner } = options
  const uniqPurls = arrays.arrayUnique(purls)
  let { length: remaining } = uniqPurls
  const alertsByPkgId = new Map()
  if (!remaining) {
    return alertsByPkgId
  }
  const getText = () => `Looking up data for ${remaining} packages`
  spinner?.start(getText())
  const sockSdk = await setupSdk(getPublicToken())
  const toAlertsMapOptions = {
    overrides: options.overrides,
    consolidate: options.consolidate,
    include,
    spinner
  }
  for await (const batchResult of sockSdk.batchPackageStream(
    {
      alerts: 'true',
      compact: 'true',
      ...(include.actions
        ? {
            actions: include.actions.join(',')
          }
        : {}),
      ...(include.unfixable
        ? {}
        : {
            fixable: 'true'
          })
    },
    {
      components: uniqPurls.map(purl => ({
        purl
      }))
    }
  )) {
    if (batchResult.success) {
      await addArtifactToAlertsMap(
        batchResult.data,
        alertsByPkgId,
        toAlertsMapOptions
      )
    } else if (!options.nothrow) {
      const statusCode = batchResult.status ?? 'unknown'
      const statusMessage = batchResult.error ?? 'No status message'
      throw new Error(
        `Socket API server error (${statusCode}): ${statusMessage}`
      )
    }
    remaining -= 1
    if (spinner && remaining > 0) {
      spinner.start()
      spinner.setText(getText())
    }
  }
  spinner?.stop()
  return alertsByPkgId
}

const {
  NPM,
  NPX,
  SOCKET_CLI_ACCEPT_RISKS,
  SOCKET_CLI_SAFE_BIN,
  SOCKET_CLI_SAFE_PROGRESS,
  SOCKET_CLI_VIEW_ALL_RISKS,
  kInternalsSymbol,
  [kInternalsSymbol]: { getIpc }
} = constants
const SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES = {
  __proto__: null,
  audit: false,
  dryRun: true,
  fund: false,
  ignoreScripts: true,
  progress: false,
  save: false,
  saveBundle: false,
  silent: true
}
const kCtorArgs = Symbol('ctorArgs')
const kRiskyReify = Symbol('riskyReify')
const Arborist = require(shadowNpmPaths.getArboristClassPath())

// Implementation code not related to our custom behavior is based on
// https://github.com/npm/cli/blob/v11.0.0/workspaces/arborist/lib/arborist/index.js:
class SafeArborist extends Arborist {
  constructor(...ctorArgs) {
    super(
      {
        path:
          (ctorArgs.length ? ctorArgs[0]?.path : undefined) ?? process$1.cwd(),
        ...(ctorArgs.length ? ctorArgs[0] : undefined),
        ...SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES
      },
      ...ctorArgs.slice(1)
    )
    this[kCtorArgs] = ctorArgs
  }
  async [kRiskyReify](...args) {
    const ctorArgs = this[kCtorArgs]
    const arb = new Arborist(
      {
        ...(ctorArgs.length ? ctorArgs[0] : undefined),
        progress: false
      },
      ...ctorArgs.slice(1)
    )
    const ret = await arb.reify(
      {
        ...(args.length ? args[0] : undefined),
        progress: false
      },
      ...args.slice(1)
    )
    Object.assign(this, arb)
    return ret
  }

  // @ts-ignore Incorrectly typed.
  async reify(...args) {
    const options = {
      __proto__: null,
      ...(args.length ? args[0] : undefined)
    }
    const ipc = await getIpc()
    const binName = ipc[SOCKET_CLI_SAFE_BIN]
    if (!binName) {
      return await this[kRiskyReify](...args)
    }
    await super.reify(
      {
        ...options,
        ...SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES,
        progress: false
      },
      // @ts-ignore: TypeScript gets grumpy about rest parameters.
      ...args.slice(1)
    )
    // Lazily access constants.ENV[SOCKET_CLI_ACCEPT_RISKS].
    const acceptRisks = constants.ENV[SOCKET_CLI_ACCEPT_RISKS]
    // Lazily access constants.ENV[SOCKET_CLI_VIEW_ALL_RISKS].
    const viewAllRisks = constants.ENV[SOCKET_CLI_VIEW_ALL_RISKS]
    const progress = ipc[SOCKET_CLI_SAFE_PROGRESS]
    const spinner =
      options['silent'] || !progress
        ? undefined
        : // Lazily access constants.spinner.
          constants.spinner
    const isSafeNpm = binName === NPM
    const isSafeNpx = binName === NPX
    const alertsMap = await getAlertsMapFromArborist(this, {
      spinner,
      include:
        acceptRisks || options.dryRun || options['yes']
          ? {
              actions: ['error'],
              blocked: true,
              critical: false,
              cve: false,
              existing: true,
              unfixable: false
            }
          : {
              existing: isSafeNpx,
              unfixable: isSafeNpm
            }
    })
    if (alertsMap.size) {
      process$1.exitCode = 1
      logAlertsMap(alertsMap, {
        hideAt: viewAllRisks ? 'none' : 'middle',
        output: process$1.stderr
      })
      throw new Error(vendor.html`
          Socket ${binName} exiting due to risks.${viewAllRisks ? '' : `\nView all risks - Rerun with environment variable ${SOCKET_CLI_VIEW_ALL_RISKS}=1.`}${acceptRisks ? '' : `\nAccept risks - Rerun with environment variable ${SOCKET_CLI_ACCEPT_RISKS}=1.`}
        `)
    } else if (!options['silent']) {
      logger.logger.success(
        `Socket ${binName} ${acceptRisks ? 'accepted' : 'found no'} risks`
      )
      if (binName === NPX) {
        logger.logger.log(`Running ${options.add[0]}`)
      }
    }
    return await this[kRiskyReify](...args)
  }
}

function installSafeArborist() {
  // Override '@npmcli/arborist' module exports with patched variants based on
  // https://github.com/npm/cli/pull/8089.
  const cache = require.cache
  cache[shadowNpmPaths.getArboristClassPath()] = {
    exports: SafeArborist
  }
  cache[shadowNpmPaths.getArboristEdgeClassPath()] = {
    exports: SafeEdge
  }
  cache[shadowNpmPaths.getArboristNodeClassPath()] = {
    exports: SafeNode
  }
  cache[shadowNpmPaths.getArboristOverrideSetClassPath()] = {
    exports: SafeOverrideSet
  }
}

installSafeArborist()

exports.ALERT_SEVERITY = ALERT_SEVERITY
exports.Arborist = Arborist
exports.AuthError = AuthError
exports.ColorOrMarkdown = ColorOrMarkdown
exports.InputError = InputError
exports.RangeStyles = RangeStyles
exports.SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES =
  SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES
exports.SafeArborist = SafeArborist
exports.applyRange = applyRange
exports.captureException = captureException
exports.filterGlobResultToSupportedFiles = filterGlobResultToSupportedFiles
exports.findBestPatchVersion = findBestPatchVersion
exports.findPackageNode = findPackageNode
exports.findPackageNodes = findPackageNodes
exports.findUp = findUp
exports.formatSeverityCount = formatSeverityCount
exports.getAlertsMapFromArborist = getAlertsMapFromArborist
exports.getAlertsMapFromPnpmLockfile = getAlertsMapFromPnpmLockfile
exports.getAlertsMapFromPurls = getAlertsMapFromPurls
exports.getConfigValue = getConfigValue
exports.getCveInfoByAlertsMap = getCveInfoByAlertsMap
exports.getDefaultToken = getDefaultToken
exports.getPublicToken = getPublicToken
exports.getSeverityCount = getSeverityCount
exports.getSocketDevAlertUrl = getSocketDevAlertUrl
exports.getSocketDevPackageOverviewUrl = getSocketDevPackageOverviewUrl
exports.globWithGitIgnore = globWithGitIgnore
exports.globWorkspace = globWorkspace
exports.isReadOnlyConfig = isReadOnlyConfig
exports.overrideCachedConfig = overrideCachedConfig
exports.overrideConfigApiToken = overrideConfigApiToken
exports.pathsToGlobPatterns = pathsToGlobPatterns
exports.readFileBinary = readFileBinary
exports.readFileUtf8 = readFileUtf8
exports.removeNodeModules = removeNodeModules
exports.safeReadFile = safeReadFile
exports.sensitiveConfigKeys = sensitiveConfigKeys
exports.setupSdk = setupSdk
exports.supportedConfigKeys = supportedConfigKeys
exports.updateConfigValue = updateConfigValue
exports.updateNode = updateNode
exports.updatePackageJsonFromNode = updatePackageJsonFromNode
//# debugId=807fb76b-361a-4b60-aa32-37175be97c4
//# sourceMappingURL=shadow-npm-inject.js.map
