diff --git a/patches/@npmcli+arborist#9.1.1.patch b/patches/@npmcli+arborist#9.1.1.patch new file mode 100644 index 000000000..acc8b0aaa --- /dev/null +++ b/patches/@npmcli+arborist#9.1.1.patch @@ -0,0 +1,21 @@ +Index: /@npmcli/arborist/lib/node.js +=================================================================== +--- /@npmcli/arborist/lib/node.js ++++ /@npmcli/arborist/lib/node.js +@@ -1156,9 +1156,15 @@ + } + + // if they're links, they match if the targets match + if (this.isLink) { +- return node.isLink && this.target.matches(node.target) ++ if (node.isLink) { ++ if (this.target && node.target) { ++ return this.target.matches(node.target) ++ } ++ } else { ++ return false ++ } + } + + // if they're two project root nodes, they're different if the paths differ + if (this.isProjectRoot && node.isProjectRoot) { diff --git a/src/commands/fix/npm-fix.mts b/src/commands/fix/npm-fix.mts index bf68007ad..5cba75009 100644 --- a/src/commands/fix/npm-fix.mts +++ b/src/commands/fix/npm-fix.mts @@ -33,8 +33,7 @@ import constants from '../../constants.mts' import { Arborist, SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES, - SafeArborist, -} from '../../shadow/npm/arborist/lib/arborist/index.mts' +} from '../../shadow/npm/arborist/index.mts' import { findBestPatchVersion, findPackageNode, @@ -51,7 +50,10 @@ import { getCveInfoFromAlertsMap } from '../../utils/socket-package-alert.mts' import { idToPurl } from '../../utils/spec.mts' import type { NormalizedFixOptions } from './types.mts' -import type { SafeNode } from '../../shadow/npm/arborist/lib/node.mts' +import type { + ArboristInstance, + NodeClass, +} from '../../shadow/npm/arborist/types.mts' import type { EnvDetails } from '../../utils/package-environment.mts' import type { PackageJson } from '@socketsecurity/registry/lib/packages' @@ -62,9 +64,9 @@ type InstallOptions = { } async function install( - arb: SafeArborist, + arb: ArboristInstance, options: InstallOptions, -): Promise { +): Promise { const { cwd = process.cwd() } = { __proto__: null, ...options, @@ -103,7 +105,7 @@ export async function npmFix( const { pkgPath: rootPath } = pkgEnvDetails - const arb = new SafeArborist({ + const arb = new Arborist({ path: rootPath, ...SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES, }) diff --git a/src/commands/fix/pnpm-fix.mts b/src/commands/fix/pnpm-fix.mts index d370c190e..636014b61 100644 --- a/src/commands/fix/pnpm-fix.mts +++ b/src/commands/fix/pnpm-fix.mts @@ -32,9 +32,9 @@ import { import { getAlertsMapOptions } from './shared.mts' import constants from '../../constants.mts' import { + Arborist, SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES, - SafeArborist, -} from '../../shadow/npm/arborist/lib/arborist/index.mts' +} from '../../shadow/npm/arborist/index.mts' import { findBestPatchVersion, findPackageNode, @@ -59,7 +59,7 @@ import { getCveInfoFromAlertsMap } from '../../utils/socket-package-alert.mts' import { idToPurl } from '../../utils/spec.mts' import type { NormalizedFixOptions } from './types.mts' -import type { SafeNode } from '../../shadow/npm/arborist/lib/node.mts' +import type { NodeClass } from '../../shadow/npm/arborist/types.mts' import type { StringKeyValueObject } from '../../types.mts' import type { EnvDetails } from '../../utils/package-environment.mts' import type { PackageJson } from '@socketsecurity/registry/lib/packages' @@ -67,12 +67,12 @@ import type { Spinner } from '@socketsecurity/registry/lib/spinner' const { DRY_RUN_NOT_SAVING, NPM, OVERRIDES, PNPM } = constants -async function getActualTree(cwd: string = process.cwd()): Promise { +async function getActualTree(cwd: string = process.cwd()): Promise { // @npmcli/arborist DOES have partial support for pnpm structured node_modules // folders. However, support is iffy resulting in unhappy path errors and hangs. // So, to avoid the unhappy path, we restrict our usage to --dry-run loading // of the node_modules folder. - const arb = new SafeArborist({ + const arb = new Arborist({ path: cwd, ...SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES, }) @@ -88,7 +88,7 @@ type InstallOptions = { async function install( pkgEnvDetails: EnvDetails, options: InstallOptions, -): Promise { +): Promise { const { args, cwd, spinner } = { __proto__: null, ...options, @@ -135,7 +135,7 @@ export async function pnpmFix( spinner?.start() - let actualTree: SafeNode | undefined + let actualTree: NodeClass | undefined const lockfilePath = path.join(rootPath, 'pnpm-lock.yaml') let lockfileContent = await readPnpmLockfile(lockfilePath) diff --git a/src/commands/optimize/add-overrides.mts b/src/commands/optimize/add-overrides.mts index 07d61d3ae..5a72c5240 100644 --- a/src/commands/optimize/add-overrides.mts +++ b/src/commands/optimize/add-overrides.mts @@ -1,6 +1,5 @@ import path from 'node:path' -import npa from 'npm-package-arg' import semver from 'semver' import { getManifestData } from '@socketsecurity/registry' @@ -19,9 +18,12 @@ import { updateManifestByAgent } from './update-manifest-by-agent.mts' import constants from '../../constants.mts' import { cmdPrefixMessage } from '../../utils/cmd.mts' import { globWorkspace } from '../../utils/glob.mts' +import { npa } from '../../utils/npm-package-arg.mts' +import { getMajor } from '../../utils/semver.mts' import type { GetOverridesResult } from './get-overrides-by-agent.mts' import type { AgentLockIncludesFn } from './lockfile-includes-by-agent.mts' +import type { AliasResult } from '../../utils/npm-package-arg.mts' import type { EnvDetails } from '../../utils/package-environment.mts' import type { Logger } from '@socketsecurity/registry/lib/logger' import type { PackageJson } from '@socketsecurity/registry/lib/packages' @@ -120,7 +122,7 @@ export async function addOverrides( // Chunk package names to process them in parallel 3 at a time. await pEach(manifestEntries, 3, async ({ 1: data }) => { const { name: sockRegPkgName, package: origPkgName, version } = data - const major = semver.major(version) + const major = getMajor(version)! const sockOverridePrefix = `${NPM}:${sockRegPkgName}@` const sockOverrideSpec = `${sockOverridePrefix}${pin ? version : `^${major}`}` for (const { 1: depObj } of depEntries) { @@ -144,7 +146,8 @@ export async function addOverrides( thisSpec.startsWith(sockOverridePrefix) && // Check the validity of the spec by passing it through npa and // seeing if it will coerce to a version. - semver.coerce(npa(thisSpec).rawSpec)?.version + semver.coerce((npa(thisSpec) as AliasResult).subSpec.rawSpec) + ?.version ) ) { thisSpec = sockOverrideSpec @@ -198,19 +201,20 @@ export async function addOverrides( if (thisSpec.startsWith(sockOverridePrefix)) { if ( pin && - semver.major( + getMajor( // Check the validity of the spec by passing it through npa // and seeing if it will coerce to a version. semver.coerce // will strip leading v's, carets (^), comparators (<,<=,>,>=,=), // and tildes (~). If not coerced to a valid version then // default to the manifest entry version. - semver.coerce(npa(thisSpec).rawSpec)?.version ?? version, + semver.coerce((npa(thisSpec) as AliasResult).subSpec.rawSpec) + ?.version ?? version, ) !== major ) { const otherVersion = (await fetchPackageManifest(thisSpec)) ?.version if (otherVersion && otherVersion !== version) { - newSpec = `${sockOverridePrefix}${pin ? otherVersion : `^${semver.major(otherVersion)}`}` + newSpec = `${sockOverridePrefix}${pin ? otherVersion : `^${getMajor(otherVersion)!}`}` } } } else { diff --git a/src/shadow/npm/arborist-helpers.mts b/src/shadow/npm/arborist-helpers.mts index 2fe01dbd3..c8c38c647 100644 --- a/src/shadow/npm/arborist-helpers.mts +++ b/src/shadow/npm/arborist-helpers.mts @@ -4,20 +4,23 @@ import { PackageURL } from '@socketregistry/packageurl-js' import { getManifestData } from '@socketsecurity/registry' import { hasOwn } from '@socketsecurity/registry/lib/objects' import { fetchPackagePackument } from '@socketsecurity/registry/lib/packages' -import { isNonEmptyString } from '@socketsecurity/registry/lib/strings' import constants from '../../constants.mts' -import { applyRange, getMajor } from '../../utils/semver.mts' -import { idToPurl } from '../../utils/spec.mts' -import { DiffAction } from './arborist/lib/arborist/types.mts' -import { Edge } from './arborist/lib/edge.mts' +import { Edge } from './arborist/index.mts' +import { DiffAction } from './arborist/types.mts' import { getAlertsMapFromPurls } from '../../utils/alerts-map.mts' +import { type AliasResult, npa } from '../../utils/npm-package-arg.mts' +import { applyRange, getMajor, getMinVersion } from '../../utils/semver.mts' +import { idToPurl } from '../../utils/spec.mts' +import type { + ArboristInstance, + Diff, + EdgeClass, + LinkClass, + NodeClass, +} from './arborist/types.mts' import type { RangeStyle } from '../../utils/semver.mts' -import type { SafeArborist } from './arborist/lib/arborist/index.mts' -import type { Diff } from './arborist/lib/arborist/types.mts' -import type { SafeEdge } from './arborist/lib/edge.mts' -import type { LinkClass, SafeNode } from './arborist/lib/node.mts' import type { AlertIncludeFilter, AlertsByPkgId, @@ -38,7 +41,7 @@ function getUrlOrigin(input: string): string { } export function findBestPatchVersion( - node: SafeNode, + node: NodeClass, availableVersions: string[], vulnerableVersionRange?: string, _firstPatchedVersionIdentifier?: string | undefined, @@ -69,12 +72,12 @@ export function findBestPatchVersion( } export function findPackageNode( - tree: SafeNode, + tree: NodeClass, name: string, version?: string | undefined, -): SafeNode | undefined { - const queue: Array = [tree] - const visited = new Set() +): NodeClass | undefined { + const queue: Array = [tree] + const visited = new Set() let sentinel = 0 while (queue.length) { if (sentinel++ === LOOP_SENTINEL) { @@ -106,13 +109,13 @@ export function findPackageNode( } export function findPackageNodes( - tree: SafeNode, + tree: NodeClass, name: string, version?: string | undefined, -): SafeNode[] { - const matches: SafeNode[] = [] - const queue: Array = [tree] - const visited = new Set() +): NodeClass[] { + const matches: NodeClass[] = [] + const queue: Array = [tree] + const visited = new Set() let sentinel = 0 while (queue.length) { if (sentinel++ === LOOP_SENTINEL) { @@ -151,7 +154,7 @@ export type GetAlertsMapFromArboristOptions = { } export async function getAlertsMapFromArborist( - arb: SafeArborist, + arb: ArboristInstance, options_?: GetAlertsMapFromArboristOptions | undefined, ): Promise { const options = { @@ -215,8 +218,8 @@ export type DiffQueryOptions = { } export type PackageDetail = { - node: SafeNode - existing?: SafeNode | undefined + node: NodeClass + existing?: NodeClass | undefined } export function getDetailsFromDiff( @@ -251,7 +254,7 @@ export function getDetailsFromDiff( // The `oldNode`, i.e. the `actual` node, will be `undefined` if the diff // action is 'ADD'. const { actual: oldNode, ideal: pkgNode } = diff - let existing: SafeNode | undefined + let existing: NodeClass | undefined let keep = false if (action === DiffAction.change) { if (pkgNode?.package.version !== oldNode?.package.version) { @@ -303,13 +306,13 @@ export function getDetailsFromDiff( return details } -export function getTargetNode(nodeOrLink: SafeNode | LinkClass): SafeNode -export function getTargetNode(nodeOrLink: T): SafeNode | null -export function getTargetNode(nodeOrLink: any): SafeNode | null { +export function getTargetNode(nodeOrLink: NodeClass | LinkClass): NodeClass +export function getTargetNode(nodeOrLink: T): NodeClass | null +export function getTargetNode(nodeOrLink: any): NodeClass | null { return nodeOrLink?.isLink ? nodeOrLink.target : (nodeOrLink ?? null) } -export function isTopLevel(tree: SafeNode, node: SafeNode): boolean { +export function isTopLevel(tree: NodeClass, node: NodeClass): boolean { return getTargetNode(tree.children.get(node.name)) === node } @@ -319,7 +322,7 @@ export type Packument = Exclude< > export function updateNode( - node: SafeNode, + node: NodeClass, newVersion: string, newVersionPackument: Packument['versions'][number], ): void { @@ -372,7 +375,7 @@ export function updateNode( name: newDepName, spec: newDeps[newDepName], type: 'prod', - }) as unknown as SafeEdge, + }) as unknown as EdgeClass, ) } } @@ -380,8 +383,8 @@ export function updateNode( export function updatePackageJsonFromNode( editablePkgJson: EditablePackageJson, - tree: SafeNode, - node: SafeNode, + tree: NodeClass, + node: NodeClass, newVersion: string, rangeStyle?: RangeStyle | undefined, ): boolean { diff --git a/src/shadow/npm/arborist/index.mts b/src/shadow/npm/arborist/index.mts index ffbe94fce..0469905d4 100755 --- a/src/shadow/npm/arborist/index.mts +++ b/src/shadow/npm/arborist/index.mts @@ -1,24 +1,50 @@ import { createRequire } from 'node:module' +// @ts-ignore +import UntypedEdge from '@npmcli/arborist/lib/edge.js' +// @ts-ignore +import UntypedNode from '@npmcli/arborist/lib/node.js' +// @ts-ignore +import UntypedOverrideSet from '@npmcli/arborist/lib/override-set.js' + import { getArboristClassPath, getArboristEdgeClassPath, getArboristNodeClassPath, getArboristOverrideSetClassPath, } from '../paths.mts' -import { SafeArborist } from './lib/arborist/index.mts' -import { SafeEdge } from './lib/edge.mts' -import { SafeNode } from './lib/node.mts' -import { SafeOverrideSet } from './lib/override-set.mts' +import { Arborist, SafeArborist } from './lib/arborist/index.mts' + +import type { EdgeClass, NodeClass, OverrideSetClass } from './types.mts' const require = createRequire(import.meta.url) +export const SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES = { + __proto__: null, + audit: false, + dryRun: true, + fund: false, + ignoreScripts: true, + progress: false, + save: false, + saveBundle: false, + silent: true, +} + +export { Arborist, SafeArborist } + +export const Edge: EdgeClass = UntypedEdge + +export const Node: NodeClass = UntypedNode + +export const OverrideSet: OverrideSetClass = UntypedOverrideSet + export function installSafeArborist() { // Override '@npmcli/arborist' module exports with patched variants based on // https://github.com/npm/cli/pull/8089. const cache: { [key: string]: any } = require.cache cache[getArboristClassPath()] = { exports: SafeArborist } - cache[getArboristEdgeClassPath()] = { exports: SafeEdge } - cache[getArboristNodeClassPath()] = { exports: SafeNode } - cache[getArboristOverrideSetClassPath()] = { exports: SafeOverrideSet } + cache[getArboristEdgeClassPath()] = { exports: Edge } + cache[getArboristNodeClassPath()] = { exports: Node } + cache[getArboristOverrideSetClassPath()] = { exports: OverrideSet } } diff --git a/src/shadow/npm/arborist/lib/arborist/index.mts b/src/shadow/npm/arborist/lib/arborist/index.mts index 94ffd1e37..7d25a43ea 100755 --- a/src/shadow/npm/arborist/lib/arborist/index.mts +++ b/src/shadow/npm/arborist/lib/arborist/index.mts @@ -1,16 +1,17 @@ -import { createRequire } from 'node:module' +// @ts-ignore +import UntypedArborist from '@npmcli/arborist/lib/arborist/index.js' import { logger } from '@socketsecurity/registry/lib/logger' import constants from '../../../../../constants.mts' import { logAlertsMap } from '../../../../../utils/socket-package-alert.mts' import { getAlertsMapFromArborist } from '../../../arborist-helpers.mts' -import { getArboristClassPath } from '../../../paths.mts' -import type { ArboristClass, ArboristReifyOptions } from './types.mts' -import type { SafeNode } from '../node.mts' - -const require = createRequire(import.meta.url) +import type { + ArboristClass, + ArboristReifyOptions, + NodeClass, +} from '../../types.mts' const { NPM, @@ -39,7 +40,7 @@ export const kCtorArgs = Symbol('ctorArgs') export const kRiskyReify = Symbol('riskyReify') -export const Arborist: ArboristClass = require(getArboristClassPath()) +export const Arborist: ArboristClass = UntypedArborist // 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: @@ -59,7 +60,7 @@ export class SafeArborist extends Arborist { async [kRiskyReify]( ...args: Parameters['reify']> - ): Promise { + ): Promise { const ctorArgs = (this as any)[kCtorArgs] const arb = new Arborist( { @@ -68,7 +69,7 @@ export class SafeArborist extends Arborist { }, ...ctorArgs.slice(1), ) - const ret = await (arb.reify as (...args: any[]) => Promise)( + const ret = await (arb.reify as (...args: any[]) => Promise)( { ...(args.length ? args[0] : undefined), progress: false, @@ -83,7 +84,7 @@ export class SafeArborist extends Arborist { override async reify( this: SafeArborist, ...args: Parameters['reify']> - ): Promise { + ): Promise { const options = { __proto__: null, ...(args.length ? args[0] : undefined), diff --git a/src/shadow/npm/arborist/lib/arborist/types.mts b/src/shadow/npm/arborist/lib/arborist/types.mts deleted file mode 100755 index 7356fce5c..000000000 --- a/src/shadow/npm/arborist/lib/arborist/types.mts +++ /dev/null @@ -1,81 +0,0 @@ -import { createEnum } from '../../../../../utils/objects.mts' - -import type { SafeNode } from '../node.mts' -import type { - Options as ArboristOptions, - Advisory as BaseAdvisory, - Arborist as BaseArborist, - AuditReport as BaseAuditReport, - Diff as BaseDiff, - BuildIdealTreeOptions, - ReifyOptions, -} from '@npmcli/arborist' - -export type ArboristClass = ArboristInstance & { - new (...args: any): ArboristInstance -} - -export type ArboristInstance = Omit< - typeof BaseArborist, - | 'actualTree' - | 'auditReport' - | 'buildIdealTree' - | 'diff' - | 'idealTree' - | 'loadActual' - | 'loadVirtual' - | 'reify' -> & { - auditReport?: AuditReportInstance | null | undefined - actualTree?: SafeNode | null | undefined - diff: Diff | null - idealTree?: SafeNode | null | undefined - buildIdealTree(options?: BuildIdealTreeOptions): Promise - loadActual(options?: ArboristOptions): Promise - loadVirtual(options?: ArboristOptions): Promise - reify(options?: ArboristReifyOptions): Promise -} - -export type ArboristReifyOptions = ReifyOptions & ArboristOptions - -export type AuditReportInstance = Omit & { - report: { [dependency: string]: AuditAdvisory[] } -} - -export type AuditAdvisory = Omit & { - id: number - cwe: string[] - cvss: { - score: number - vectorString: string - } - vulnerable_versions: string -} - -export const DiffAction = createEnum({ - add: 'ADD', - change: 'CHANGE', - remove: 'REMOVE', -}) - -export type Diff = Omit< - BaseDiff, - | 'actual' - | 'children' - | 'filterSet' - | 'ideal' - | 'leaves' - | 'removed' - | 'shrinkwrapInflated' - | 'unchanged' -> & { - actual: SafeNode - children: Diff[] - filterSet: Set - ideal: SafeNode - leaves: SafeNode[] - parent: Diff | null - removed: SafeNode[] - shrinkwrapInflated: Set - unchanged: SafeNode[] -} diff --git a/src/shadow/npm/arborist/lib/dep-valid.mts b/src/shadow/npm/arborist/lib/dep-valid.mts deleted file mode 100755 index 1e345731a..000000000 --- a/src/shadow/npm/arborist/lib/dep-valid.mts +++ /dev/null @@ -1,27 +0,0 @@ -import { createRequire } from 'node:module' - -import { getArboristDepValidPath } from '../../paths.mts' - -import type { SafeNode } from './node.mts' - -const require = createRequire(import.meta.url) - -type DepValidFn = ( - child: SafeNode, - requested: string, - accept: string | undefined, - requester: SafeNode, -) => boolean - -let _depValid: DepValidFn | undefined -export function depValid( - child: SafeNode, - requested: string, - accept: string | undefined, - requester: SafeNode, -) { - if (_depValid === undefined) { - _depValid = require(getArboristDepValidPath()) as DepValidFn - } - return _depValid(child, requested, accept, requester) -} diff --git a/src/shadow/npm/arborist/lib/edge.mts b/src/shadow/npm/arborist/lib/edge.mts deleted file mode 100755 index 2f082e5d7..000000000 --- a/src/shadow/npm/arborist/lib/edge.mts +++ /dev/null @@ -1,327 +0,0 @@ -import { createRequire } from 'node:module' - -import { depValid } from './dep-valid.mts' -import { SafeNode } from './node.mts' -import { SafeOverrideSet } from './override-set.mts' -import { getArboristEdgeClassPath } from '../../paths.mts' - -import type { Edge as BaseEdge, DependencyProblem } from '@npmcli/arborist' - -const require = createRequire(import.meta.url) - -type EdgeClass = Omit< - BaseEdge, - | 'accept' - | 'detach' - | 'optional' - | 'overrides' - | 'peer' - | 'peerConflicted' - | 'rawSpec' - | 'reload' - | 'satisfiedBy' - | 'spec' - | 'to' -> & { - optional: boolean - overrides: SafeOverrideSet | undefined - peer: boolean - peerConflicted: boolean - rawSpec: string - get accept(): string | undefined - get spec(): string - get to(): SafeNode | null - new (...args: any): EdgeClass - detach(): void - reload(hard?: boolean): void - satisfiedBy(node: SafeNode): boolean -} - -export type EdgeOptions = { - type: string - name: string - spec: string - from: SafeNode - accept?: string | undefined - overrides?: SafeOverrideSet | undefined - to?: SafeNode | undefined -} - -export type ErrorStatus = DependencyProblem | 'OK' - -export type Explanation = { - type: string - name: string - spec: string - bundled: boolean - overridden: boolean - error: ErrorStatus | undefined - rawSpec: string | undefined - from: object | undefined -} | null - -export const Edge: EdgeClass = require(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. -export class SafeEdge extends Edge { - #safeError: ErrorStatus | null - #safeExplanation: Explanation | undefined - #safeFrom: SafeNode | null - #safeTo: SafeNode | null - - constructor(options: EdgeOptions) { - 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) - } - - override get bundled() { - return !!this.#safeFrom?.package?.bundleDependencies?.includes(this.name) - } - - override 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. - override get from() { - return this.#safeFrom - } - - // @ts-ignore: Incorrectly typed as a property instead of an accessor. - override get spec(): string { - 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] as string - } - if (pkg?.optionalDependencies?.[ref]) { - return pkg.optionalDependencies[ref] as string - } - if (pkg?.dependencies?.[ref]) { - return pkg.dependencies[ref] as string - } - if (pkg?.peerDependencies?.[ref]) { - return pkg.peerDependencies[ref] as string - } - 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. - override get to() { - return this.#safeTo - } - - override 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. - override explain() { - if (!this.#safeExplanation) { - const explanation: 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 - } - - override 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 as SafeNode) ?? 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) - } - } - - override satisfiedBy(node: SafeNode) { - // 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) - } -} diff --git a/src/shadow/npm/arborist/lib/node.mts b/src/shadow/npm/arborist/lib/node.mts deleted file mode 100755 index edd35ae57..000000000 --- a/src/shadow/npm/arborist/lib/node.mts +++ /dev/null @@ -1,394 +0,0 @@ -import { createRequire } from 'node:module' - -import semver from 'semver' - -import { SafeOverrideSet } from './override-set.mts' -import { getArboristNodeClassPath } from '../../paths.mts' -import { getLogger } from '../../proc-log/index.mts' - -import type { SafeEdge } from './edge.mts' -import type { Node as BaseNode } from '@npmcli/arborist' - -const require = createRequire(import.meta.url) - -type NodeClass = Omit< - BaseNode, - | 'addEdgeIn' - | 'addEdgeOut' - | 'canDedupe' - | 'canReplace' - | 'canReplaceWith' - | 'children' - | 'deleteEdgeIn' - | 'edgesIn' - | 'edgesOut' - | 'from' - | 'hasShrinkwrap' - | 'inDepBundle' - | 'inShrinkwrap' - | 'integrity' - | 'isTop' - | 'matches' - | 'meta' - | 'name' - | 'overrides' - | 'packageName' - | 'parent' - | 'recalculateOutEdgesOverrides' - | 'resolve' - | 'resolveParent' - | 'root' - | 'target' - | 'updateOverridesEdgeInAdded' - | 'updateOverridesEdgeInRemoved' - | 'version' - | 'versions' -> & { - name: string - version: string - children: Map - edgesIn: Set - edgesOut: Map - from: SafeNode | null - hasShrinkwrap: boolean - inShrinkwrap: boolean | undefined - integrity?: string | null - isTop: boolean | undefined - meta: BaseNode['meta'] & { - addEdge(edge: SafeEdge): void - } - overrides: SafeOverrideSet | undefined - target: SafeNode - versions: string[] - get inDepBundle(): boolean - get packageName(): string | null - get parent(): SafeNode | null - set parent(value: SafeNode | null) - get resolveParent(): SafeNode | null - get root(): SafeNode | null - set root(value: SafeNode | null) - new (...args: any): NodeClass - addEdgeIn(edge: SafeEdge): void - addEdgeOut(edge: SafeEdge): void - canDedupe(preferDedupe?: boolean): boolean - canReplace(node: SafeNode, ignorePeers?: string[]): boolean - canReplaceWith(node: SafeNode, ignorePeers?: string[]): boolean - deleteEdgeIn(edge: SafeEdge): void - matches(node: SafeNode): boolean - recalculateOutEdgesOverrides(): void - resolve(name: string): SafeNode - updateOverridesEdgeInAdded( - otherOverrideSet: SafeOverrideSet | undefined, - ): boolean - updateOverridesEdgeInRemoved(otherOverrideSet: SafeOverrideSet): boolean -} - -export type LinkClass = Omit & { - readonly isLink: true -} - -const Node: NodeClass = require(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: -export 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. - override 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 || semver.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 && semver.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. - override canReplaceWith(node: SafeNode, ignorePeers?: string[]): boolean { - 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. - override deleteEdgeIn(edge: SafeEdge) { - this.edgesIn.delete(edge) - const { overrides } = edge - if (overrides) { - this.updateOverridesEdgeInRemoved(overrides) - } - } - - override addEdgeIn(edge: SafeEdge): void { - // 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. - override 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 - } - - override set parent(newParent: SafeNode) { - // 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. - override 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. - override set root(newRoot: SafeNode) { - // 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. - override updateOverridesEdgeInAdded( - otherOverrideSet: SafeOverrideSet | undefined, - ) { - 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. - override updateOverridesEdgeInRemoved(otherOverrideSet: SafeOverrideSet) { - // 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 - } -} diff --git a/src/shadow/npm/arborist/lib/override-set.mts b/src/shadow/npm/arborist/lib/override-set.mts deleted file mode 100755 index 4d6e27723..000000000 --- a/src/shadow/npm/arborist/lib/override-set.mts +++ /dev/null @@ -1,168 +0,0 @@ -import { createRequire } from 'node:module' - -import npa from 'npm-package-arg' -import semver from 'semver' - -import { getArboristOverrideSetClassPath } from '../../paths.mts' -import { getLogger } from '../../proc-log/index.mts' - -import type { SafeEdge } from './edge.mts' -import type { SafeNode } from './node.mts' -import type { AliasResult, RegistryResult } from 'npm-package-arg' - -const require = createRequire(import.meta.url) - -interface OverrideSetClass { - children: Map - key: string | undefined - keySpec: string | undefined - name: string | undefined - parent: SafeOverrideSet | undefined - value: string | undefined - version: string | undefined - // eslint-disable-next-line @typescript-eslint/no-misused-new - new (...args: any[]): OverrideSetClass - get isRoot(): boolean - get ruleset(): Map - ancestry(): Generator - childrenAreEqual(otherOverrideSet: SafeOverrideSet | undefined): boolean - getEdgeRule(edge: SafeEdge): SafeOverrideSet - getNodeRule(node: SafeNode): SafeOverrideSet - getMatchingRule(node: SafeNode): SafeOverrideSet | null - isEqual(otherOverrideSet: SafeOverrideSet | undefined): boolean -} - -const OverrideSet: OverrideSetClass = require(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: -export class SafeOverrideSet extends OverrideSet { - // Patch adding doOverrideSetsConflict is based on - // https://github.com/npm/cli/pull/8089. - static doOverrideSetsConflict( - first: SafeOverrideSet | undefined, - second: SafeOverrideSet | undefined, - ) { - // 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: SafeOverrideSet | undefined, - second: SafeOverrideSet | undefined, - ) { - 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. - override childrenAreEqual(otherOverrideSet: SafeOverrideSet) { - 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 - } - - override getEdgeRule(edge: SafeEdge): SafeOverrideSet { - 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 = npa(`${edge.name}@${edge.rawSpec || edge.spec}`) - if (spec.type === 'alias') { - spec = (spec as AliasResult).subSpec - } - if (spec.type === 'git') { - if (spec.gitRange && semver.intersects(spec.gitRange, rule.keySpec!)) { - return rule - } - continue - } - if (spec.type === 'range' || spec.type === 'version') { - if ( - semver.intersects((spec as RegistryResult).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. - override isEqual(otherOverrideSet: SafeOverrideSet | undefined): boolean { - 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) - } -} diff --git a/src/shadow/npm/arborist/types.mts b/src/shadow/npm/arborist/types.mts new file mode 100755 index 000000000..5b52436cd --- /dev/null +++ b/src/shadow/npm/arborist/types.mts @@ -0,0 +1,217 @@ +import { createEnum } from '../../../utils/objects.mts' + +import type { + Options as ArboristOptions, + Advisory as BaseAdvisory, + Arborist as BaseArborist, + AuditReport as BaseAuditReport, + Diff as BaseDiff, + Edge as BaseEdge, + Node as BaseNode, + BaseOverrideSet, + BuildIdealTreeOptions, + ReifyOptions, +} from '@npmcli/arborist' + +export type ArboristClass = ArboristInstance & { + new (...args: any): ArboristInstance +} + +export type ArboristInstance = Omit< + typeof BaseArborist, + | 'actualTree' + | 'auditReport' + | 'buildIdealTree' + | 'diff' + | 'idealTree' + | 'loadActual' + | 'loadVirtual' + | 'reify' +> & { + auditReport?: AuditReportInstance | null | undefined + actualTree?: NodeClass | null | undefined + diff: Diff | null + idealTree?: NodeClass | null | undefined + buildIdealTree(options?: BuildIdealTreeOptions): Promise + loadActual(options?: ArboristOptions): Promise + loadVirtual(options?: ArboristOptions): Promise + reify(options?: ArboristReifyOptions): Promise +} + +export type ArboristReifyOptions = ReifyOptions & ArboristOptions + +export type AuditAdvisory = Omit & { + id: number + cwe: string[] + cvss: { + score: number + vectorString: string + } + vulnerable_versions: string +} + +export type AuditReportInstance = Omit & { + report: { [dependency: string]: AuditAdvisory[] } +} + +export const DiffAction = createEnum({ + add: 'ADD', + change: 'CHANGE', + remove: 'REMOVE', +}) + +export type Diff = Omit< + BaseDiff, + | 'actual' + | 'children' + | 'filterSet' + | 'ideal' + | 'leaves' + | 'removed' + | 'shrinkwrapInflated' + | 'unchanged' +> & { + actual: NodeClass + children: Diff[] + filterSet: Set + ideal: NodeClass + leaves: NodeClass[] + parent: Diff | null + removed: NodeClass[] + shrinkwrapInflated: Set + unchanged: NodeClass[] +} + +export type EdgeClass = Omit< + BaseEdge, + | 'accept' + | 'detach' + | 'optional' + | 'overrides' + | 'peer' + | 'peerConflicted' + | 'rawSpec' + | 'reload' + | 'satisfiedBy' + | 'spec' + | 'to' +> & { + optional: boolean + overrides: OverrideSetClass | undefined + peer: boolean + peerConflicted: boolean + rawSpec: string + get accept(): string | undefined + get spec(): string + get to(): NodeClass | null + new (...args: any): EdgeClass + detach(): void + reload(hard?: boolean): void + satisfiedBy(node: NodeClass): boolean +} + +export type LinkClass = Omit & { + readonly isLink: true +} + +export type NodeClass = Omit< + BaseNode, + | 'addEdgeIn' + | 'addEdgeOut' + | 'canDedupe' + | 'canReplace' + | 'canReplaceWith' + | 'children' + | 'deleteEdgeIn' + | 'edgesIn' + | 'edgesOut' + | 'from' + | 'hasShrinkwrap' + | 'inDepBundle' + | 'inShrinkwrap' + | 'integrity' + | 'isTop' + | 'matches' + | 'meta' + | 'name' + | 'overrides' + | 'packageName' + | 'parent' + | 'recalculateOutEdgesOverrides' + | 'resolve' + | 'resolveParent' + | 'root' + | 'target' + | 'updateOverridesEdgeInAdded' + | 'updateOverridesEdgeInRemoved' + | 'version' + | 'versions' +> & { + name: string + version: string + children: Map + edgesIn: Set + edgesOut: Map + from: NodeClass | null + hasShrinkwrap: boolean + inShrinkwrap: boolean | undefined + integrity?: string | null + isTop: boolean | undefined + meta: BaseNode['meta'] & { + addEdge(edge: EdgeClass): void + } + overrides: OverrideSetClass | undefined + target: NodeClass + versions: string[] + get inDepBundle(): boolean + get packageName(): string | null + get parent(): NodeClass | null + set parent(value: NodeClass | null) + get resolveParent(): NodeClass | null + get root(): NodeClass | null + set root(value: NodeClass | null) + new (...args: any): NodeClass + addEdgeIn(edge: EdgeClass): void + addEdgeOut(edge: EdgeClass): void + canDedupe(preferDedupe?: boolean): boolean + canReplace(node: NodeClass, ignorePeers?: string[]): boolean + canReplaceWith(node: NodeClass, ignorePeers?: string[]): boolean + deleteEdgeIn(edge: EdgeClass): void + matches(node: NodeClass): boolean + recalculateOutEdgesOverrides(): void + resolve(name: string): NodeClass + updateOverridesEdgeInAdded( + otherOverrideSet: OverrideSetClass | undefined, + ): boolean + updateOverridesEdgeInRemoved(otherOverrideSet: OverrideSetClass): boolean +} + +export interface OverrideSetClass + extends Omit< + BaseOverrideSet, + | 'ancestry' + | 'children' + | 'getEdgeRule' + | 'getMatchingRule' + | 'getNodeRule' + | 'parent' + | 'ruleset' + > { + children: Map + key: string | undefined + keySpec: string | undefined + name: string | undefined + parent: OverrideSetClass | undefined + value: string | undefined + version: string | undefined + // eslint-disable-next-line @typescript-eslint/no-misused-new + new (...args: any[]): OverrideSetClass + get isRoot(): boolean + get ruleset(): Map + ancestry(): Generator + childrenAreEqual(otherOverrideSet: OverrideSetClass | undefined): boolean + getEdgeRule(edge: EdgeClass): OverrideSetClass + getMatchingRule(node: NodeClass): OverrideSetClass | null + getNodeRule(node: NodeClass): OverrideSetClass + isEqual(otherOverrideSet: OverrideSetClass | undefined): boolean +}