11import { getManifestData } from '@socketsecurity/registry'
2+ import { logger } from '@socketsecurity/registry/lib/logger'
23import { runScript } from '@socketsecurity/registry/lib/npm'
34import {
45 fetchPackagePackument ,
56 readPackageJson
67} from '@socketsecurity/registry/lib/packages'
78
9+ import { openGitHubPullRequest } from './open-pr'
810import constants from '../../constants'
911import {
1012 Arborist ,
@@ -14,18 +16,32 @@ import {
1416import {
1517 findPackageNodes ,
1618 getAlertsMapFromArborist ,
17- updateNode
18- } from '../../utils/lockfile/package-lock-json'
19+ updateNode ,
20+ updatePackageJsonFromNode
21+ } from '../../utils/arborist-helpers'
1922import { getCveInfoByAlertsMap } from '../../utils/socket-package-alert'
2023
2124import type { SafeNode } from '../../shadow/npm/arborist/lib/node'
2225import type { EnvDetails } from '../../utils/package-environment'
2326import type { Spinner } from '@socketsecurity/registry/lib/spinner'
2427
25- const { NPM } = constants
28+ const { CI , NPM } = constants
2629
27- function isTopLevel ( tree : SafeNode , node : SafeNode ) : boolean {
28- return tree . children . get ( node . name ) === node
30+ type InstallOptions = {
31+ cwd ?: string | undefined
32+ }
33+
34+ async function install (
35+ idealTree : SafeNode ,
36+ options : InstallOptions
37+ ) : Promise < void > {
38+ const { cwd = process . cwd ( ) } = {
39+ __proto__ : null ,
40+ ...options
41+ } as InstallOptions
42+ const arb2 = new Arborist ( { path : cwd } )
43+ arb2 . idealTree = idealTree
44+ await arb2 . reify ( )
2945}
3046
3147type NpmFixOptions = {
@@ -78,9 +94,9 @@ export async function npmFix(
7894 for ( const { 0 : name , 1 : infos } of infoByPkg ) {
7995 const revertToIdealTree = arb . idealTree !
8096 arb . idealTree = null
97+
8198 // eslint-disable-next-line no-await-in-loop
8299 await arb . buildIdealTree ( )
83-
84100 const tree = arb . idealTree !
85101
86102 const hasUpgrade = ! ! getManifestData ( NPM , name )
@@ -100,16 +116,14 @@ export async function npmFix(
100116 continue
101117 }
102118
103- for ( let i = 0 , { length : nodesLength } = nodes ; i < nodesLength ; i += 1 ) {
104- const node = nodes [ i ] !
105- for (
106- let j = 0 , { length : infosLength } = infos ;
107- j < infosLength ;
108- j += 1
109- ) {
110- const { firstPatchedVersionIdentifier, vulnerableVersionRange } =
111- infos [ j ] !
119+ for ( const node of nodes ) {
120+ for ( const {
121+ firstPatchedVersionIdentifier,
122+ vulnerableVersionRange
123+ } of infos ) {
124+ spinner ?. stop ( )
112125 const { version : oldVersion } = node
126+ const oldSpec = `${ name } @${ oldVersion } `
113127 if (
114128 updateNode (
115129 node ,
@@ -118,45 +132,42 @@ export async function npmFix(
118132 firstPatchedVersionIdentifier
119133 )
120134 ) {
135+ const targetVersion = node . package . version !
136+ const fixSpec = `${ name } @^${ targetVersion } `
121137 try {
138+ spinner ?. start ( )
139+ spinner ?. info ( `Installing ${ fixSpec } ` )
140+ // eslint-disable-next-line no-await-in-loop
141+ await install ( arb . idealTree ! , { cwd } )
122142 if ( test ) {
143+ spinner ?. info ( `Testing ${ fixSpec } ` )
123144 // eslint-disable-next-line no-await-in-loop
124145 await runScript ( testScript , [ ] , { spinner, stdio : 'ignore' } )
125146 }
126-
127- spinner ?. info ( `Patched ${ name } ${ oldVersion } -> ${ node . version } ` )
128-
129- if ( isTopLevel ( tree , node ) ) {
130- for ( const depField of [
131- 'dependencies' ,
132- 'optionalDependencies' ,
133- 'peerDependencies'
134- ] ) {
135- const { content : pkgJson } = editablePkgJson
136- const oldVersion = ( pkgJson [ depField ] as any ) ?. [ name ]
137- if ( oldVersion ) {
138- const decorator = / ^ [ ~ ^ ] / . exec ( oldVersion ) ?. [ 0 ] ?? ''
139- ; ( pkgJson as any ) [ depField ] [ name ] =
140- `${ decorator } ${ node . version } `
141- }
142- }
147+ // Lazily access constants.ENV[CI].
148+ if ( constants . ENV [ CI ] ) {
149+ // eslint-disable-next-line no-await-in-loop
150+ await openGitHubPullRequest ( name , targetVersion , cwd )
143151 }
152+ updatePackageJsonFromNode ( editablePkgJson , tree , node )
144153 // eslint-disable-next-line no-await-in-loop
145154 await editablePkgJson . save ( )
155+ spinner ?. info ( `Fixed ${ name } ` )
146156 } catch {
147- spinner ?. error ( `Reverting ${ name } to ${ oldVersion } ` )
157+ spinner ?. error ( `Reverting ${ fixSpec } ` )
148158 arb . idealTree = revertToIdealTree
159+ // eslint-disable-next-line no-await-in-loop
160+ await install ( arb . idealTree ! , { cwd } )
161+ spinner ?. stop ( )
162+ logger . error ( `Failed to fix ${ oldSpec } ` )
149163 }
150164 } else {
151- spinner ?. error ( `Could not patch ${ name } ${ oldVersion } ` )
165+ spinner ?. stop ( )
166+ logger . error ( `Could not patch ${ oldSpec } ` )
152167 }
153168 }
154169 }
155170 }
156171
157- const arb2 = new Arborist ( { path : cwd } )
158- arb2 . idealTree = arb . idealTree
159- await arb2 . reify ( )
160-
161172 spinner ?. stop ( )
162173}
0 commit comments