@@ -9,6 +9,9 @@ export class PluginsService implements IPluginsService {
99 private static NPM_CONFIG = {
1010 save : true
1111 } ;
12+
13+ private static LOCK_FILES = [ "package-lock.json" , "npm-shrinkwrap.json" , "yarn.lock" , "pnpm-lock.yaml" ] ;
14+
1215 private get $platformsDataService ( ) : IPlatformsDataService {
1316 return this . $injector . resolve ( "platformsDataService" ) ;
1417 }
@@ -169,24 +172,17 @@ export class PluginsService implements IPluginsService {
169172 return _ . filter ( nodeModules , nodeModuleData => nodeModuleData && nodeModuleData . isPlugin ) ;
170173 }
171174
172- //This method will traverse all non dev dependencies (not only the root/installed ones) and filter the plugins.
173175 public getAllProductionPlugins ( projectData : IProjectData , dependencies ?: IDependencyData [ ] ) : IPluginData [ ] {
174- const allProductionPlugins : IPluginData [ ] = [ ] ;
175176 dependencies = dependencies || this . $nodeModulesDependenciesBuilder . getProductionDependencies ( projectData . projectDir ) ;
176177
177178 if ( _ . isEmpty ( dependencies ) ) {
178- return allProductionPlugins ;
179+ return [ ] ;
179180 }
180181
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 ;
190186 }
191187
192188 public getDependenciesFromPackageJson ( projectDir : string ) : IPackageJsonDepedenciesResult {
@@ -206,14 +202,71 @@ export class PluginsService implements IPluginsService {
206202 return pluginPackageJsonContent && pluginPackageJsonContent . nativescript ;
207203 }
208204
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 {
210263 const pluginData : any = { } ;
211264 pluginData . name = cacheData . name ;
212265 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 ;
215268 pluginData . pluginPlatformsFolderPath = ( platform : string ) => path . join ( pluginData . fullPath , "platforms" , platform . toLowerCase ( ) ) ;
216- const data = cacheData . nativescript || cacheData . moduleInfo ;
269+ const data = cacheData . nativescript ;
217270
218271 if ( pluginData . isPlugin ) {
219272 pluginData . platformsData = data . platforms ;
@@ -280,7 +333,7 @@ export class PluginsService implements IPluginsService {
280333 version : data . version ,
281334 fullPath : path . dirname ( module ) ,
282335 isPlugin : data . nativescript !== undefined ,
283- moduleInfo : data . nativescript
336+ nativescript : data . nativescript
284337 } ;
285338 }
286339
0 commit comments