11import { getManifestData } from '@socketsecurity/registry'
2+ import { arrayUnique } from '@socketsecurity/registry/lib/arrays'
23import { logger } from '@socketsecurity/registry/lib/logger'
34import { runScript } from '@socketsecurity/registry/lib/npm'
45import {
@@ -14,6 +15,7 @@ import {
1415 SafeArborist
1516} from '../../shadow/npm/arborist/lib/arborist'
1617import {
18+ findPackageNode ,
1719 findPackageNodes ,
1820 getAlertsMapFromArborist ,
1921 updateNode ,
@@ -23,22 +25,23 @@ import { getCveInfoByAlertsMap } from '../../utils/socket-package-alert'
2325
2426import type { SafeNode } from '../../shadow/npm/arborist/lib/node'
2527import type { EnvDetails } from '../../utils/package-environment'
28+ import type { PackageJson } from '@socketsecurity/registry/lib/packages'
2629import type { Spinner } from '@socketsecurity/registry/lib/spinner'
2730
2831const { CI , NPM } = constants
2932
30- type SaveAndInstallOptions = {
33+ type InstallOptions = {
3134 cwd ?: string | undefined
3235}
3336
34- async function saveAndInstall (
37+ async function install (
3538 idealTree : SafeNode ,
36- options : SaveAndInstallOptions
39+ options : InstallOptions
3740) : Promise < void > {
3841 const { cwd = process . cwd ( ) } = {
3942 __proto__ : null ,
4043 ...options
41- } as SaveAndInstallOptions
44+ } as InstallOptions
4245 const arb2 = new Arborist ( { path : cwd } )
4346 arb2 . idealTree = idealTree
4447 await arb2 . reify ( )
@@ -68,7 +71,7 @@ export async function npmFix(
6871 path : cwd ,
6972 ...SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES
7073 } )
71-
74+ // Calling arb.reify() creates the arb.diff object and nulls-out arb.idealTree.
7275 await arb . reify ( )
7376
7477 const alertsMap = await getAlertsMapFromArborist ( arb , {
@@ -87,42 +90,48 @@ export async function npmFix(
8790 return
8891 }
8992
90- await arb . buildIdealTree ( )
91-
9293 const editablePkgJson = await readPackageJson ( cwd , { editable : true } )
94+ const { content : pkgJson } = editablePkgJson
9395
94- for ( const { 0 : name , 1 : infos } of infoByPkg ) {
95- const revertToIdealTree = arb . idealTree !
96- arb . idealTree = null
96+ await arb . buildIdealTree ( )
9797
98- // eslint-disable-next-line no-await-in-loop
99- await arb . buildIdealTree ( )
100- const tree = arb . idealTree !
98+ spinner ?. stop ( )
10199
100+ for ( const { 0 : name , 1 : infos } of infoByPkg ) {
102101 const hasUpgrade = ! ! getManifestData ( NPM , name )
103102 if ( hasUpgrade ) {
104103 spinner ?. info ( `Skipping ${ name } . Socket Optimize package exists.` )
105104 continue
106105 }
107-
108- const nodes = findPackageNodes ( tree , name )
109-
106+ const specs = arrayUnique (
107+ findPackageNodes ( arb . idealTree ! , name ) . map ( n => ` ${ n . name } @ ${ n . version } ` )
108+ )
110109 const packument =
111- nodes . length && infos . length
110+ specs . length && infos . length
112111 ? // eslint-disable-next-line no-await-in-loop
113112 await fetchPackagePackument ( name )
114113 : null
115114 if ( ! packument ) {
116115 continue
117116 }
118117
119- for ( const node of nodes ) {
118+ for ( const spec of specs ) {
119+ const lastAtSignIndex = spec . lastIndexOf ( '@' )
120+ const name = spec . slice ( 0 , lastAtSignIndex )
121+ const oldVersion = spec . slice ( lastAtSignIndex + 1 )
120122 for ( const {
121123 firstPatchedVersionIdentifier,
122124 vulnerableVersionRange
123125 } of infos ) {
126+ const revertTree = arb . idealTree !
127+ arb . idealTree = null
128+ // eslint-disable-next-line no-await-in-loop
129+ await arb . buildIdealTree ( )
130+ const node = findPackageNode ( arb . idealTree ! , name , oldVersion )
131+ if ( ! node ) {
132+ continue
133+ }
124134 spinner ?. stop ( )
125- const { version : oldVersion } = node
126135 const oldSpec = `${ name } @${ oldVersion } `
127136 if (
128137 updateNode (
@@ -134,38 +143,54 @@ export async function npmFix(
134143 ) {
135144 const targetVersion = node . package . version !
136145 const fixSpec = `${ name } @^${ targetVersion } `
146+ const revertData = {
147+ ...( pkgJson . dependencies
148+ ? { dependencies : pkgJson . dependencies }
149+ : undefined ) ,
150+ ...( pkgJson . optionalDependencies
151+ ? { optionalDependencies : pkgJson . optionalDependencies }
152+ : undefined ) ,
153+ ...( pkgJson . peerDependencies
154+ ? { peerDependencies : pkgJson . peerDependencies }
155+ : undefined )
156+ } as PackageJson
137157
138158 spinner ?. start ( )
139159 spinner ?. info ( `Installing ${ fixSpec } ` )
140160
141161 let saved = false
142162 let installed = false
143163 try {
164+ updatePackageJsonFromNode ( editablePkgJson , arb . idealTree ! , node )
144165 // eslint-disable-next-line no-await-in-loop
145- await saveAndInstall ( arb . idealTree ! , { cwd } )
166+ await editablePkgJson . save ( )
146167 saved = true
168+
169+ // eslint-disable-next-line no-await-in-loop
170+ await install ( arb . idealTree ! , { cwd } )
147171 installed = true
148172
149173 if ( test ) {
150174 spinner ?. info ( `Testing ${ fixSpec } ` )
151175 // eslint-disable-next-line no-await-in-loop
152176 await runScript ( testScript , [ ] , { spinner, stdio : 'ignore' } )
153177 }
178+ spinner ?. info ( `Fixed ${ name } ` )
154179 // Lazily access constants.ENV[CI].
155180 if ( constants . ENV [ CI ] ) {
156181 // eslint-disable-next-line no-await-in-loop
157182 await openGitHubPullRequest ( name , targetVersion , cwd )
158183 }
159- updatePackageJsonFromNode ( editablePkgJson , tree , node )
160- // eslint-disable-next-line no-await-in-loop
161- await editablePkgJson . save ( )
162- spinner ?. info ( `Fixed ${ name } ` )
163184 } catch {
164185 spinner ?. error ( `Reverting ${ fixSpec } ` )
165- if ( saved || installed ) {
166- arb . idealTree = revertToIdealTree
186+ if ( saved ) {
187+ editablePkgJson . update ( revertData )
188+ // eslint-disable-next-line no-await-in-loop
189+ await editablePkgJson . save ( )
190+ }
191+ if ( installed ) {
167192 // eslint-disable-next-line no-await-in-loop
168- await saveAndInstall ( arb . idealTree ! , { cwd } )
193+ await install ( revertTree , { cwd } )
169194 }
170195 spinner ?. stop ( )
171196 logger . error ( `Failed to fix ${ oldSpec } ` )
0 commit comments