@@ -4,12 +4,13 @@ import {ItemOptions}
4
4
import { Pad } from '@yarnpkg/libui/sources/components/Pad' ;
5
5
import { ScrollableItems } from '@yarnpkg/libui/sources/components/ScrollableItems' ;
6
6
import { useMinistore } from '@yarnpkg/libui/sources/hooks/useMinistore' ;
7
+ import { useKeypress } from '@yarnpkg/libui/sources/hooks/useKeypress' ;
7
8
import { renderForm , SubmitInjectedComponent } from '@yarnpkg/libui/sources/misc/renderForm' ;
8
9
import { suggestUtils } from '@yarnpkg/plugin-essentials' ;
9
10
import { Command , Usage } from 'clipanion' ;
10
11
import { diffWords } from 'diff' ;
11
12
import { Box , Text } from 'ink' ;
12
- import React , { useEffect , useRef , useState } from 'react' ;
13
+ import React , { useCallback , useEffect , useRef , useState } from 'react' ;
13
14
import semver from 'semver' ;
14
15
15
16
const SIMPLE_SEMVER = / ^ ( (?: [ \^ ~ ] | > = ? ) ? ) ( [ 0 - 9 ] + ) ( \. [ 0 - 9 ] + ) ( \. [ 0 - 9 ] + ) ( (?: - \S + ) ? ) $ / ;
@@ -140,6 +141,46 @@ export default class UpgradeInteractiveCommand extends BaseCommand {
140
141
return suggestions ;
141
142
} ;
142
143
144
+ const formatAction = ( { isIgnore, version} : { isIgnore : boolean ; version : string } ) => {
145
+ return `${ isIgnore ? `ignore` : `update` } __${ version } ` ;
146
+ } ;
147
+
148
+ const parseAction = ( action : string ) => {
149
+ const [ command , version ] = action . split ( `__` ) ;
150
+
151
+ return {
152
+ isIgnore : command === `ignore` ,
153
+ version,
154
+ } ;
155
+ } ;
156
+
157
+ const readIgnoreList = ( ) => {
158
+ return configuration . get ( `upgradeInteractiveIgnoredVersions` ) . reduce ( ( allVersions , versionString ) => {
159
+ const [ packageName , versionRange = `*` ] = versionString . split ( `:` ) ;
160
+
161
+ if ( ! packageName ) return allVersions ;
162
+
163
+ return {
164
+ ...allVersions ,
165
+ [ packageName ] : versionRange ,
166
+ } ;
167
+ } , { } as Record < string , string > ) ;
168
+ } ;
169
+
170
+ const writeIgnoreList = async ( ignoredDependencyUpdates : Record < string , string > ) => {
171
+ const currentList = readIgnoreList ( ) ;
172
+
173
+ const newList = {
174
+ ...currentList ,
175
+ ...ignoredDependencyUpdates ,
176
+ } ;
177
+
178
+ await Configuration . updateConfiguration ( configuration . startingCwd , {
179
+ upgradeInteractiveIgnoredVersions : Object . entries ( newList )
180
+ . map ( ( [ packageName , versionRange ] ) => `${ packageName } :${ versionRange } ` ) ,
181
+ } ) ;
182
+ } ;
183
+
143
184
const Prompt = ( ) => {
144
185
return (
145
186
< Box flexDirection = "row" >
@@ -154,6 +195,11 @@ export default class UpgradeInteractiveCommand extends BaseCommand {
154
195
Press < Text bold color = "cyanBright" > { `<left>` } </ Text > /< Text bold color = "cyanBright" > { `<right>` } </ Text > to select versions.
155
196
</ Text >
156
197
</ Box >
198
+ < Box marginLeft = { 1 } >
199
+ < Text >
200
+ Press < Text bold color = "cyanBright" > { `<i>` } </ Text > to toggle ignore mode.
201
+ </ Text >
202
+ </ Box >
157
203
</ Box >
158
204
< Box flexDirection = "column" >
159
205
< Box marginLeft = { 1 } >
@@ -188,9 +234,37 @@ export default class UpgradeInteractiveCommand extends BaseCommand {
188
234
189
235
const UpgradeEntry = ( { active, descriptor, suggestions} : { active : boolean , descriptor : Descriptor , suggestions : Array < UpgradeSuggestion > } ) => {
190
236
const [ action , setAction ] = useMinistore < string | null > ( descriptor . descriptorHash , null ) ;
237
+ const [ isIgnoreMode , setIgnoreMode ] = useState ( false ) ;
191
238
192
239
const packageIdentifier = structUtils . stringifyIdent ( descriptor ) ;
193
240
const padLength = Math . max ( 0 , 45 - packageIdentifier . length ) ;
241
+
242
+ useKeypress ( { active} , ( ch , key ) => {
243
+ switch ( key . name ) {
244
+ case `i` :
245
+ setIgnoreMode ( prevIsIgnore => {
246
+ const nextIsIgnore = ! prevIsIgnore ;
247
+
248
+ if ( action !== null ) {
249
+ const { version} = parseAction ( action ) ;
250
+ setAction ( formatAction ( { isIgnore : nextIsIgnore , version} ) ) ;
251
+ }
252
+
253
+ return nextIsIgnore ;
254
+ } ) ;
255
+ }
256
+ } , [ action , setAction , setIgnoreMode ] ) ;
257
+
258
+ const onItemChange = useCallback ( ( newAction : string | null ) => {
259
+ if ( newAction === null ) {
260
+ setAction ( null ) ;
261
+ } else {
262
+ setAction ( formatAction ( { isIgnore : isIgnoreMode , version : newAction } ) ) ;
263
+ }
264
+ } , [ isIgnoreMode , setAction ] ) ;
265
+
266
+ const { version : value } = action ? parseAction ( action ) : { version : null } ;
267
+
194
268
return < >
195
269
< Box >
196
270
< Box width = { 45 } >
@@ -200,7 +274,15 @@ export default class UpgradeInteractiveCommand extends BaseCommand {
200
274
< Pad active = { active } length = { padLength } />
201
275
</ Box >
202
276
{ suggestions !== null
203
- ? < ItemOptions active = { active } options = { suggestions } value = { action } skewer = { true } onChange = { setAction } sizes = { [ 17 , 17 , 17 ] } />
277
+ ? < ItemOptions
278
+ active = { active }
279
+ gemColor = { isIgnoreMode ? `red` : undefined /* use default */ }
280
+ options = { suggestions }
281
+ value = { value }
282
+ skewer = { true }
283
+ onChange = { onItemChange }
284
+ sizes = { [ 17 , 17 , 17 ] }
285
+ />
204
286
: < Box marginLeft = { 2 } > < Text color = "gray" > Fetching suggestions...</ Text > </ Box >
205
287
}
206
288
</ Box >
@@ -218,12 +300,28 @@ export default class UpgradeInteractiveCommand extends BaseCommand {
218
300
} ) ;
219
301
220
302
useEffect ( ( ) => {
303
+ const ignoreList = readIgnoreList ( ) ;
304
+
221
305
Promise . all ( dependencies . map ( descriptor => fetchSuggestions ( descriptor ) ) )
222
306
. then ( allSuggestions => {
223
307
const mappedToSuggestions = dependencies . map ( ( descriptor , i ) => {
224
308
const suggestionsForDescriptor = allSuggestions [ i ] ;
225
309
return [ descriptor , suggestionsForDescriptor ] as const ;
226
- } ) . filter ( ( [ _ , suggestions ] ) => suggestions . length > 1 ) ;
310
+ } ) . filter ( ( [ _ , suggestions ] ) => suggestions . length > 1 )
311
+ . filter ( ( [ { scope, name} , suggestions ] ) => {
312
+ const ignoredVersionRange = ignoreList [ scope ? `@${ scope } /${ name } ` : name ] ;
313
+
314
+ if ( ! ignoredVersionRange )
315
+ return true ;
316
+
317
+ // The latest version is always the last one in the array
318
+ const latestVersion = suggestions [ suggestions . length - 1 ] ?. value ?. replace ( `^` , `` ) ;
319
+ if ( ! latestVersion )
320
+ return true ;
321
+
322
+ // If the latest version satisfies the ignore range, we filter out the dependency
323
+ return ! semver . satisfies ( latestVersion , ignoredVersionRange ) ;
324
+ } ) ;
227
325
228
326
if ( mountedRef . current ) {
229
327
setSuggestions ( mappedToSuggestions ) ;
@@ -269,24 +367,38 @@ export default class UpgradeInteractiveCommand extends BaseCommand {
269
367
if ( typeof updateRequests === `undefined` )
270
368
return 1 ;
271
369
272
- let hasChanged = false ;
370
+ let shouldInstall = false ;
371
+ let shouldUpdateIgnoreList = false ;
372
+
373
+ const ignoredDependencyUpdates : Record < string , string > = { } ;
273
374
274
375
for ( const workspace of project . workspaces ) {
275
376
for ( const dependencyType of [ `dependencies` , `devDependencies` ] as Array < HardDependencies > ) {
276
377
const dependencies = workspace . manifest [ dependencyType ] ;
277
378
278
379
for ( const descriptor of dependencies . values ( ) ) {
279
- const newRange = updateRequests . get ( descriptor . descriptorHash ) ;
280
-
281
- if ( typeof newRange !== `undefined` && newRange !== null ) {
282
- dependencies . set ( descriptor . identHash , structUtils . makeDescriptor ( descriptor , newRange ) ) ;
283
- hasChanged = true ;
380
+ const action = updateRequests . get ( descriptor . descriptorHash ) ;
381
+
382
+ if ( typeof action !== `undefined` && action !== null ) {
383
+ const { isIgnore, version : newRange } = parseAction ( action ) ;
384
+
385
+ if ( isIgnore ) {
386
+ const key = descriptor . scope ? `@${ descriptor . scope } /${ descriptor . name } ` : descriptor . name ;
387
+ ignoredDependencyUpdates [ key ] = newRange ;
388
+ shouldUpdateIgnoreList = true ;
389
+ } else {
390
+ dependencies . set ( descriptor . identHash , structUtils . makeDescriptor ( descriptor , newRange ) ) ;
391
+ shouldInstall = true ;
392
+ }
284
393
}
285
394
}
286
395
}
287
396
}
288
397
289
- if ( ! hasChanged )
398
+ if ( shouldUpdateIgnoreList )
399
+ await writeIgnoreList ( ignoredDependencyUpdates ) ;
400
+
401
+ if ( ! shouldInstall )
290
402
return 0 ;
291
403
292
404
const installReport = await StreamReport . start ( {
0 commit comments