1
1
const { resolve } = require ( 'path' )
2
2
3
3
const semver = require ( 'semver' )
4
- const libdiff = require ( 'libnpmdiff' )
4
+ const libnpmdiff = require ( 'libnpmdiff' )
5
5
const npa = require ( 'npm-package-arg' )
6
6
const Arborist = require ( '@npmcli/arborist' )
7
7
const npmlog = require ( 'npmlog' )
8
8
const pacote = require ( 'pacote' )
9
9
const pickManifest = require ( 'npm-pick-manifest' )
10
10
11
+ const getWorkspaces = require ( './workspaces/get-workspaces.js' )
11
12
const readPackageName = require ( './utils/read-package-name.js' )
12
13
const BaseCommand = require ( './base-command.js' )
13
14
@@ -25,10 +26,6 @@ class Diff extends BaseCommand {
25
26
static get usage ( ) {
26
27
return [
27
28
'[...<paths>]' ,
28
- '--diff=<pkg-name> [...<paths>]' ,
29
- '--diff=<version-a> [--diff=<version-b>] [...<paths>]' ,
30
- '--diff=<spec-a> [--diff=<spec-b>] [...<paths>]' ,
31
- '[--diff-ignore-all-space] [--diff-name-only] [...<paths>] [...<paths>]' ,
32
29
]
33
30
}
34
31
@@ -45,19 +42,19 @@ class Diff extends BaseCommand {
45
42
'diff-text' ,
46
43
'global' ,
47
44
'tag' ,
45
+ 'workspace' ,
46
+ 'workspaces' ,
48
47
]
49
48
}
50
49
51
- get where ( ) {
52
- const globalTop = resolve ( this . npm . globalDir , '..' )
53
- const global = this . npm . config . get ( 'global' )
54
- return global ? globalTop : this . npm . prefix
55
- }
56
-
57
50
exec ( args , cb ) {
58
51
this . diff ( args ) . then ( ( ) => cb ( ) ) . catch ( cb )
59
52
}
60
53
54
+ execWorkspaces ( args , filters , cb ) {
55
+ this . diffWorkspaces ( args , filters ) . then ( ( ) => cb ( ) ) . catch ( cb )
56
+ }
57
+
61
58
async diff ( args ) {
62
59
const specs = this . npm . config . get ( 'diff' ) . filter ( d => d )
63
60
if ( specs . length > 2 ) {
@@ -67,86 +64,96 @@ class Diff extends BaseCommand {
67
64
)
68
65
}
69
66
67
+ // diffWorkspaces may have set this already
68
+ if ( ! this . prefix )
69
+ this . prefix = this . npm . prefix
70
+
71
+ // this is the "top" directory, one up from node_modules
72
+ // in global mode we have to walk one up from globalDir because our
73
+ // node_modules is sometimes under ./lib, and in global mode we're only ever
74
+ // walking through node_modules (because we will have been given a package
75
+ // name already)
76
+ if ( this . npm . config . get ( 'global' ) )
77
+ this . top = resolve ( this . npm . globalDir , '..' )
78
+ else
79
+ this . top = this . prefix
80
+
70
81
const [ a , b ] = await this . retrieveSpecs ( specs )
71
82
npmlog . info ( 'diff' , { src : a , dst : b } )
72
83
73
- const res = await libdiff ( [ a , b ] , {
84
+ const res = await libnpmdiff ( [ a , b ] , {
74
85
...this . npm . flatOptions ,
75
86
diffFiles : args ,
76
- where : this . where ,
87
+ where : this . top ,
77
88
} )
78
89
return this . npm . output ( res )
79
90
}
80
91
81
- async retrieveSpecs ( [ a , b ] ) {
82
- // no arguments, defaults to comparing cwd
83
- // to its latest published registry version
84
- if ( ! a )
85
- return this . defaultSpec ( )
86
-
87
- // single argument, used to compare wanted versions of an
88
- // installed dependency or to compare the cwd to a published version
89
- if ( ! b )
90
- return this . transformSingleSpec ( a )
91
-
92
- const specs = await this . convertVersionsToSpecs ( [ a , b ] )
93
- return this . findVersionsByPackageName ( specs )
92
+ async diffWorkspaces ( args , filters ) {
93
+ const workspaces =
94
+ await getWorkspaces ( filters , { path : this . npm . localPrefix } )
95
+ for ( const workspacePath of workspaces . values ( ) ) {
96
+ this . top = workspacePath
97
+ this . prefix = workspacePath
98
+ await this . diff ( args )
99
+ }
94
100
}
95
101
96
- async defaultSpec ( ) {
97
- let noPackageJson
98
- let pkgName
102
+ // get the package name from the packument at `path`
103
+ // throws if no packument is present OR if it does not have `name` attribute
104
+ async packageName ( path ) {
105
+ let name
99
106
try {
100
- pkgName = await readPackageName ( this . npm . prefix )
107
+ // TODO this won't work as expected in global mode
108
+ name = await readPackageName ( this . prefix )
101
109
} catch ( e ) {
102
110
npmlog . verbose ( 'diff' , 'could not read project dir package.json' )
103
- noPackageJson = true
104
111
}
105
112
106
- if ( ! pkgName || noPackageJson ) {
107
- throw new Error (
108
- 'Needs multiple arguments to compare or run from a project dir.\n\n' +
109
- `Usage:\n${ this . usage } `
110
- )
111
- }
113
+ if ( ! name )
114
+ throw this . usageError ( 'Needs multiple arguments to compare or run from a project dir.\n' )
112
115
113
- return [
114
- `${ pkgName } @${ this . npm . config . get ( 'tag' ) } ` ,
115
- `file:${ this . npm . prefix } ` ,
116
- ]
116
+ return name
117
117
}
118
118
119
- async transformSingleSpec ( a ) {
119
+ async retrieveSpecs ( [ a , b ] ) {
120
+ if ( a && b ) {
121
+ const specs = await this . convertVersionsToSpecs ( [ a , b ] )
122
+ return this . findVersionsByPackageName ( specs )
123
+ }
124
+
125
+ // no arguments, defaults to comparing cwd
126
+ // to its latest published registry version
127
+ if ( ! a ) {
128
+ const pkgName = await this . packageName ( this . prefix )
129
+ return [
130
+ `${ pkgName } @${ this . npm . config . get ( 'tag' ) } ` ,
131
+ `file:${ this . prefix } ` ,
132
+ ]
133
+ }
134
+
135
+ // single argument, used to compare wanted versions of an
136
+ // installed dependency or to compare the cwd to a published version
120
137
let noPackageJson
121
138
let pkgName
122
139
try {
123
- pkgName = await readPackageName ( this . npm . prefix )
140
+ pkgName = await readPackageName ( this . prefix )
124
141
} catch ( e ) {
125
142
npmlog . verbose ( 'diff' , 'could not read project dir package.json' )
126
143
noPackageJson = true
127
144
}
128
- const missingPackageJson = new Error (
129
- 'Needs multiple arguments to compare or run from a project dir.\n\n' +
130
- `Usage:\n${ this . usage } `
131
- )
132
145
133
- const specSelf = ( ) => {
134
- if ( noPackageJson )
135
- throw missingPackageJson
136
-
137
- return `file:${ this . npm . prefix } `
138
- }
146
+ const missingPackageJson = this . usageError ( 'Needs multiple arguments to compare or run from a project dir.\n' )
139
147
140
148
// using a valid semver range, that means it should just diff
141
149
// the cwd against a published version to the registry using the
142
150
// same project name and the provided semver range
143
151
if ( semver . validRange ( a ) ) {
144
152
if ( ! pkgName )
145
153
throw missingPackageJson
146
-
147
154
return [
148
155
`${ pkgName } @${ a } ` ,
149
- specSelf ( ) ,
156
+ `file: ${ this . prefix } ` ,
150
157
]
151
158
}
152
159
@@ -160,7 +167,7 @@ class Diff extends BaseCommand {
160
167
try {
161
168
const opts = {
162
169
...this . npm . flatOptions ,
163
- path : this . where ,
170
+ path : this . top ,
164
171
}
165
172
const arb = new Arborist ( opts )
166
173
actualTree = await arb . loadActual ( opts )
@@ -172,9 +179,11 @@ class Diff extends BaseCommand {
172
179
}
173
180
174
181
if ( ! node || ! node . name || ! node . package || ! node . package . version ) {
182
+ if ( noPackageJson )
183
+ throw missingPackageJson
175
184
return [
176
185
`${ spec . name } @${ spec . fetchSpec } ` ,
177
- specSelf ( ) ,
186
+ `file: ${ this . prefix } ` ,
178
187
]
179
188
}
180
189
@@ -220,14 +229,10 @@ class Diff extends BaseCommand {
220
229
} else if ( spec . type === 'directory' ) {
221
230
return [
222
231
`file:${ spec . fetchSpec } ` ,
223
- specSelf ( ) ,
232
+ `file: ${ this . prefix } ` ,
224
233
]
225
- } else {
226
- throw new Error (
227
- 'Spec type not supported.\n\n' +
228
- `Usage:\n${ this . usage } `
229
- )
230
- }
234
+ } else
235
+ throw this . usageError ( `Spec type ${ spec . type } not supported.\n` )
231
236
}
232
237
233
238
async convertVersionsToSpecs ( [ a , b ] ) {
@@ -238,17 +243,14 @@ class Diff extends BaseCommand {
238
243
if ( semverA && semverB ) {
239
244
let pkgName
240
245
try {
241
- pkgName = await readPackageName ( this . npm . prefix )
246
+ pkgName = await readPackageName ( this . prefix )
242
247
} catch ( e ) {
243
248
npmlog . verbose ( 'diff' , 'could not read project dir package.json' )
244
249
}
245
250
246
- if ( ! pkgName ) {
247
- throw new Error (
248
- 'Needs to be run from a project dir in order to diff two versions.\n\n' +
249
- `Usage:\n${ this . usage } `
250
- )
251
- }
251
+ if ( ! pkgName )
252
+ throw this . usageError ( 'Needs to be run from a project dir in order to diff two versions.\n' )
253
+
252
254
return [ `${ pkgName } @${ a } ` , `${ pkgName } @${ b } ` ]
253
255
}
254
256
@@ -269,7 +271,7 @@ class Diff extends BaseCommand {
269
271
try {
270
272
const opts = {
271
273
...this . npm . flatOptions ,
272
- path : this . where ,
274
+ path : this . top ,
273
275
}
274
276
const arb = new Arborist ( opts )
275
277
actualTree = await arb . loadActual ( opts )
0 commit comments