77// <key> <value>
88//
99// Assume that any key or value might be quoted, though that's only done
10- // in practice if certain chars are in the string. Quoting unnecessarily
11- // does not cause problems for yarn, so that's what we do when we write
12- // it back.
10+ // in practice if certain chars are in the string. When writing back, we follow
11+ // Yarn's rules for quoting, to cause minimal friction.
1312//
1413// The data format would support nested objects, but at this time, it
1514// appears that yarn does not use that for anything, so in the interest
@@ -33,10 +32,44 @@ const consistentResolve = require('./consistent-resolve.js')
3332const { dirname } = require ( 'path' )
3433const { breadth } = require ( 'treeverse' )
3534
35+ // Sort Yarn entries respecting the yarn.lock sort order
36+ const yarnEntryPriorities = {
37+ name : 1 ,
38+ version : 2 ,
39+ uid : 3 ,
40+ resolved : 4 ,
41+ integrity : 5 ,
42+ registry : 6 ,
43+ dependencies : 7 ,
44+ }
45+
46+ const priorityThenLocaleCompare = ( a , b ) => {
47+ if ( ! yarnEntryPriorities [ a ] && ! yarnEntryPriorities [ b ] ) {
48+ return localeCompare ( a , b )
49+ }
50+ /* istanbul ignore next */
51+ return ( yarnEntryPriorities [ a ] || 100 ) > ( yarnEntryPriorities [ b ] || 100 ) ? 1 : - 1
52+ }
53+
54+ const quoteIfNeeded = val => {
55+ if (
56+ typeof val === 'boolean' ||
57+ typeof val === 'number' ||
58+ val . startsWith ( 'true' ) ||
59+ val . startsWith ( 'false' ) ||
60+ / [: \s \n \\ " , [ \] ] / g. test ( val ) ||
61+ ! / ^ [ a - z A - Z ] / g. test ( val )
62+ ) {
63+ return JSON . stringify ( val )
64+ }
65+
66+ return val
67+ }
68+
3669// sort a key/value object into a string of JSON stringified keys and vals
3770const sortKV = obj => Object . keys ( obj )
3871 . sort ( localeCompare )
39- . map ( k => ` ${ JSON . stringify ( k ) } ${ JSON . stringify ( obj [ k ] ) } ` )
72+ . map ( k => ` ${ quoteIfNeeded ( k ) } ${ quoteIfNeeded ( obj [ k ] ) } ` )
4073 . join ( '\n' )
4174
4275// for checking against previous entries
@@ -171,7 +204,7 @@ class YarnLock {
171204 toString ( ) {
172205 return prefix + [ ...new Set ( [ ...this . entries . values ( ) ] ) ]
173206 . map ( e => e . toString ( ) )
174- . sort ( localeCompare ) . join ( '\n\n' ) + '\n'
207+ . sort ( ( a , b ) => localeCompare ( a . replace ( / " / g , '' ) , b . replace ( / " / g , '' ) ) ) . join ( '\n\n' ) + '\n'
175208 }
176209
177210 fromTree ( tree ) {
@@ -323,19 +356,14 @@ class YarnLockEntry {
323356 // sort objects to the bottom, then alphabetical
324357 return ( [ ...this [ _specs ] ]
325358 . sort ( localeCompare )
326- . map ( JSON . stringify ) . join ( ', ' ) +
359+ . map ( quoteIfNeeded ) . join ( ', ' ) +
327360 ':\n' +
328361 Object . getOwnPropertyNames ( this )
329362 . filter ( prop => this [ prop ] !== null )
330- . sort (
331- ( a , b ) =>
332- /* istanbul ignore next - sort call order is unpredictable */
333- ( typeof this [ a ] === 'object' ) === ( typeof this [ b ] === 'object' )
334- ? localeCompare ( a , b )
335- : typeof this [ a ] === 'object' ? 1 : - 1 )
363+ . sort ( priorityThenLocaleCompare )
336364 . map ( prop =>
337365 typeof this [ prop ] !== 'object'
338- ? ` ${ JSON . stringify ( prop ) } ${ JSON . stringify ( this [ prop ] ) } \n`
366+ ? ` ${ prop } ${ prop === 'integrity' ? this [ prop ] : JSON . stringify ( this [ prop ] ) } \n`
339367 : Object . keys ( this [ prop ] ) . length === 0 ? ''
340368 : ` ${ prop } :\n` + sortKV ( this [ prop ] ) + '\n' )
341369 . join ( '' ) ) . trim ( )
0 commit comments