@@ -2,6 +2,7 @@ import * as debugLib from 'debug';
2
2
import * as pathLib from 'path' ;
3
3
4
4
import {
5
+ DependencyPins ,
5
6
EntityToFix ,
6
7
FixChangesSummary ,
7
8
FixOptions ,
@@ -18,6 +19,11 @@ import {
18
19
extractProvenance ,
19
20
PythonProvenance ,
20
21
} from './extract-version-provenance' ;
22
+ import {
23
+ ParsedRequirements ,
24
+ parseRequirementsFile ,
25
+ Requirement ,
26
+ } from './update-dependencies/requirements-file-parser' ;
21
27
22
28
const debug = debugLib ( 'snyk-fix:python:requirements.txt' ) ;
23
29
@@ -37,17 +43,18 @@ export async function pipRequirementsTxt(
37
43
38
44
for ( const entity of fixable ) {
39
45
try {
40
- const { remediation, targetFile, workspace } = getRequiredData ( entity ) ;
41
- const { dir, base } = pathLib . parse ( targetFile ) ;
42
- const provenance = await extractProvenance ( workspace , dir , base ) ;
43
- const changes = await fixIndividualRequirementsTxt (
44
- workspace ,
45
- dir ,
46
- base ,
47
- remediation ,
48
- provenance ,
46
+ const { changes } = await applyAllFixes (
47
+ entity ,
48
+ // dir,
49
+ // base,
50
+ // remediation,
51
+ // provenance,
49
52
options ,
50
53
) ;
54
+ if ( ! changes . length ) {
55
+ debug ( 'Manifest has not changed!' ) ;
56
+ throw new NoFixesCouldBeAppliedError ( ) ;
57
+ }
51
58
handlerResult . succeeded . push ( { original : entity , changes } ) ;
52
59
} catch ( e ) {
53
60
handlerResult . failed . push ( { original : entity , error : e } ) ;
@@ -82,27 +89,95 @@ export function getRequiredData(
82
89
export async function fixIndividualRequirementsTxt (
83
90
workspace : Workspace ,
84
91
dir : string ,
92
+ entryFileName : string ,
85
93
fileName : string ,
86
94
remediation : RemediationChanges ,
87
- provenance : PythonProvenance ,
95
+ parsedRequirements : ParsedRequirements ,
88
96
options : FixOptions ,
89
- ) : Promise < FixChangesSummary [ ] > {
90
- // TODO: allow handlers per fix type (later also strategies or combine with strategies)
91
- const { updatedManifest, changes } = updateDependencies (
92
- provenance [ fileName ] ,
97
+ directUpgradesOnly : boolean ,
98
+ ) : Promise < { changes : FixChangesSummary [ ] ; appliedRemediation : string [ ] } > {
99
+ const fullFilePath = pathLib . join ( dir , fileName ) ;
100
+ const { updatedManifest, changes, appliedRemediation } = updateDependencies (
101
+ parsedRequirements ,
93
102
remediation . pin ,
103
+ directUpgradesOnly ,
104
+ pathLib . join ( dir , entryFileName ) !== fullFilePath ? fileName : undefined ,
94
105
) ;
95
-
96
- if ( ! changes . length ) {
97
- debug ( 'Manifest has not changed!' ) ;
98
- throw new NoFixesCouldBeAppliedError ( ) ;
99
- }
100
- if ( ! options . dryRun ) {
106
+ if ( ! options . dryRun && changes . length > 0 ) {
101
107
debug ( 'Writing changes to file' ) ;
102
108
await workspace . writeFile ( pathLib . join ( dir , fileName ) , updatedManifest ) ;
103
109
} else {
104
110
debug ( 'Skipping writing changes to file in --dry-run mode' ) ;
105
111
}
106
112
107
- return changes ;
113
+ return { changes, appliedRemediation } ;
114
+ }
115
+
116
+ export async function applyAllFixes (
117
+ entity : EntityToFix ,
118
+ options : FixOptions ,
119
+ ) : Promise < { changes : FixChangesSummary [ ] } > {
120
+ const { remediation, targetFile : entryFileName , workspace } = getRequiredData (
121
+ entity ,
122
+ ) ;
123
+ const { dir, base } = pathLib . parse ( entryFileName ) ;
124
+ const provenance = await extractProvenance ( workspace , dir , base ) ;
125
+ const upgradeChanges : FixChangesSummary [ ] = [ ] ;
126
+ const appliedUpgradeRemediation : string [ ] = [ ] ;
127
+ for ( const fileName of Object . keys ( provenance ) ) {
128
+ const skipApplyingPins = true ;
129
+ const { changes, appliedRemediation } = await fixIndividualRequirementsTxt (
130
+ workspace ,
131
+ dir ,
132
+ base ,
133
+ fileName ,
134
+ remediation ,
135
+ provenance [ fileName ] ,
136
+ options ,
137
+ skipApplyingPins ,
138
+ ) ;
139
+ appliedUpgradeRemediation . push ( ...appliedRemediation ) ;
140
+ // what if we saw the file before and already fixed it?
141
+ upgradeChanges . push ( ...changes ) ;
142
+ }
143
+ // now do left overs as pins + add tests
144
+ const requirementsTxt = await workspace . readFile ( entryFileName ) ;
145
+
146
+ const toPin : RemediationChanges = filterOutAppliedUpgrades (
147
+ remediation ,
148
+ appliedUpgradeRemediation ,
149
+ ) ;
150
+ const directUpgradesOnly = false ;
151
+ const { changes : pinnedChanges } = await fixIndividualRequirementsTxt (
152
+ workspace ,
153
+ dir ,
154
+ base ,
155
+ base ,
156
+ toPin ,
157
+ parseRequirementsFile ( requirementsTxt ) ,
158
+ options ,
159
+ directUpgradesOnly ,
160
+ ) ;
161
+
162
+ return { changes : [ ...upgradeChanges , ...pinnedChanges ] } ;
163
+ }
164
+
165
+ function filterOutAppliedUpgrades (
166
+ remediation : RemediationChanges ,
167
+ appliedRemediation : string [ ] ,
168
+ ) : RemediationChanges {
169
+ const pinRemediation : RemediationChanges = {
170
+ ...remediation ,
171
+ pin : { } , // delete the pin remediation so we can add only not applied
172
+ } ;
173
+ const pins = remediation . pin ;
174
+ const lowerCasedAppliedRemediation = appliedRemediation . map ( ( i ) =>
175
+ i . toLowerCase ( ) ,
176
+ ) ;
177
+ for ( const pkgAtVersion of Object . keys ( pins ) ) {
178
+ if ( ! lowerCasedAppliedRemediation . includes ( pkgAtVersion . toLowerCase ( ) ) ) {
179
+ pinRemediation . pin [ pkgAtVersion ] = pins [ pkgAtVersion ] ;
180
+ }
181
+ }
182
+ return pinRemediation ;
108
183
}
0 commit comments