@@ -9,6 +9,9 @@ export class PluginsService implements IPluginsService {
9
9
private static NPM_CONFIG = {
10
10
save : true
11
11
} ;
12
+
13
+ private static LOCK_FILES = [ "package-lock.json" , "npm-shrinkwrap.json" , "yarn.lock" , "pnpm-lock.yaml" ] ;
14
+
12
15
private get $platformsDataService ( ) : IPlatformsDataService {
13
16
return this . $injector . resolve ( "platformsDataService" ) ;
14
17
}
@@ -169,24 +172,17 @@ export class PluginsService implements IPluginsService {
169
172
return _ . filter ( nodeModules , nodeModuleData => nodeModuleData && nodeModuleData . isPlugin ) ;
170
173
}
171
174
172
- //This method will traverse all non dev dependencies (not only the root/installed ones) and filter the plugins.
173
175
public getAllProductionPlugins ( projectData : IProjectData , dependencies ?: IDependencyData [ ] ) : IPluginData [ ] {
174
- const allProductionPlugins : IPluginData [ ] = [ ] ;
175
176
dependencies = dependencies || this . $nodeModulesDependenciesBuilder . getProductionDependencies ( projectData . projectDir ) ;
176
177
177
178
if ( _ . isEmpty ( dependencies ) ) {
178
- return allProductionPlugins ;
179
+ return [ ] ;
179
180
}
180
181
181
- _ . forEach ( dependencies , dependency => {
182
- const isPlugin = ! ! dependency . nativescript ;
183
- if ( isPlugin ) {
184
- const pluginData = this . convertToPluginData ( dependency , projectData . projectDir ) ;
185
- allProductionPlugins . push ( pluginData ) ;
186
- }
187
- } ) ;
188
-
189
- return allProductionPlugins ;
182
+ let productionPlugins : IDependencyData [ ] = dependencies . filter ( d => ! ! d . nativescript ) ;
183
+ productionPlugins = this . ensureValidProductionPlugins ( productionPlugins , projectData . projectDir ) ;
184
+ const pluginData = productionPlugins . map ( plugin => this . convertToPluginData ( plugin , projectData . projectDir ) ) ;
185
+ return pluginData ;
190
186
}
191
187
192
188
public getDependenciesFromPackageJson ( projectDir : string ) : IPackageJsonDepedenciesResult {
@@ -206,14 +202,71 @@ export class PluginsService implements IPluginsService {
206
202
return pluginPackageJsonContent && pluginPackageJsonContent . nativescript ;
207
203
}
208
204
209
- public convertToPluginData ( cacheData : any , projectDir : string ) : IPluginData {
205
+ private ensureValidProductionPlugins = _ . memoize < ( productionDependencies : IDependencyData [ ] , projectDir : string ) => IDependencyData [ ] > ( this . _ensureValidProductionPlugins , ( productionDependencies : IDependencyData [ ] , projectDir : string ) => {
206
+ let key = _ . sortBy ( productionDependencies , p => p . directory ) . map ( d => JSON . stringify ( d , null , 2 ) ) . join ( "\n" ) ;
207
+ key += projectDir ;
208
+ return key ;
209
+ } ) ;
210
+
211
+ private _ensureValidProductionPlugins ( productionDependencies : IDependencyData [ ] , projectDir : string ) : IDependencyData [ ] {
212
+ const clonedProductionDependencies = _ . cloneDeep ( productionDependencies ) ;
213
+ const dependenciesGroupedByName = _ . groupBy ( clonedProductionDependencies , p => p . name ) ;
214
+ _ . each ( dependenciesGroupedByName , ( dependencyOccurrences , dependencyName ) => {
215
+ if ( dependencyOccurrences . length > 1 ) {
216
+ // the dependency exists multiple times in node_modules
217
+ const dependencyOccurrencesGroupedByVersion = _ . groupBy ( dependencyOccurrences , g => g . version ) ;
218
+ const versions = _ . keys ( dependencyOccurrencesGroupedByVersion ) ;
219
+ if ( versions . length === 1 ) {
220
+ // all dependencies with this name have the same version
221
+ this . $logger . warn ( `Detected same versions (${ _ . first ( versions ) } ) of the ${ dependencyName } installed at locations: ${ _ . map ( dependencyOccurrences , d => d . directory ) . join ( ", " ) } ` ) ;
222
+ const selectedPackage = _ . minBy ( dependencyOccurrences , d => d . depth ) ;
223
+ this . $logger . info ( `CLI will use only the native code from '${ selectedPackage . directory } '.` . green ) ;
224
+ _ . each ( dependencyOccurrences , dependency => {
225
+ if ( dependency !== selectedPackage ) {
226
+ clonedProductionDependencies . splice ( clonedProductionDependencies . indexOf ( dependency ) , 1 ) ;
227
+ }
228
+ } ) ;
229
+ } else {
230
+ const message = this . getFailureMessageForDifferentDependencyVersions ( dependencyName , dependencyOccurrencesGroupedByVersion , projectDir ) ;
231
+ this . $errors . fail ( message ) ;
232
+ }
233
+ }
234
+ } ) ;
235
+
236
+ return clonedProductionDependencies ;
237
+ }
238
+
239
+ private getFailureMessageForDifferentDependencyVersions ( dependencyName : string , dependencyOccurrencesGroupedByVersion : IDictionary < IDependencyData [ ] > , projectDir : string ) : string {
240
+ let message = `Cannot use different versions of a NativeScript plugin in your application.
241
+ ${ dependencyName } plugin occurs multiple times in node_modules:\n`;
242
+ _ . each ( dependencyOccurrencesGroupedByVersion , ( dependencies , version ) => {
243
+ message += dependencies . map ( d => `* Path: ${ d . directory } , version: ${ d . version } \n` ) ;
244
+ } ) ;
245
+
246
+ const existingLockFiles : string [ ] = [ ] ;
247
+ PluginsService . LOCK_FILES . forEach ( lockFile => {
248
+ if ( this . $fs . exists ( path . join ( projectDir , lockFile ) ) ) {
249
+ existingLockFiles . push ( lockFile ) ;
250
+ }
251
+ } ) ;
252
+
253
+ let msgForLockFiles : string = "" ;
254
+ if ( existingLockFiles . length ) {
255
+ msgForLockFiles += ` and ${ existingLockFiles . join ( ", " ) } ` ;
256
+ }
257
+
258
+ message += `Probably you need to update your dependencies, remove node_modules${ msgForLockFiles } and try again.` ;
259
+ return message ;
260
+ }
261
+
262
+ private convertToPluginData ( cacheData : IDependencyData | INodeModuleData , projectDir : string ) : IPluginData {
210
263
const pluginData : any = { } ;
211
264
pluginData . name = cacheData . name ;
212
265
pluginData . version = cacheData . version ;
213
- pluginData . fullPath = cacheData . directory || path . dirname ( this . getPackageJsonFilePathForModule ( cacheData . name , projectDir ) ) ;
214
- pluginData . isPlugin = ! ! cacheData . nativescript || ! ! cacheData . moduleInfo ;
266
+ pluginData . fullPath = ( < IDependencyData > cacheData ) . directory || path . dirname ( this . getPackageJsonFilePathForModule ( cacheData . name , projectDir ) ) ;
267
+ pluginData . isPlugin = ! ! cacheData . nativescript ;
215
268
pluginData . pluginPlatformsFolderPath = ( platform : string ) => path . join ( pluginData . fullPath , "platforms" , platform . toLowerCase ( ) ) ;
216
- const data = cacheData . nativescript || cacheData . moduleInfo ;
269
+ const data = cacheData . nativescript ;
217
270
218
271
if ( pluginData . isPlugin ) {
219
272
pluginData . platformsData = data . platforms ;
@@ -280,7 +333,7 @@ export class PluginsService implements IPluginsService {
280
333
version : data . version ,
281
334
fullPath : path . dirname ( module ) ,
282
335
isPlugin : data . nativescript !== undefined ,
283
- moduleInfo : data . nativescript
336
+ nativescript : data . nativescript
284
337
} ;
285
338
}
286
339
0 commit comments