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