Skip to content

Commit bb8e816

Browse files
Merge pull request #5221 from NativeScript/vladimirov/conflicting-native-resources
feat: improve detection of NativeScript plugins
2 parents 39cc606 + 9f948a1 commit bb8e816

File tree

7 files changed

+427
-69
lines changed

7 files changed

+427
-69
lines changed

lib/declarations.d.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,8 @@ interface IDependencyData {
368368
* Dependencies of the current module.
369369
*/
370370
dependencies?: string[];
371+
372+
version: string;
371373
}
372374

373375
interface INpmsResult {
@@ -1071,4 +1073,4 @@ interface IWatchIgnoreListService {
10711073

10721074
interface INpmConfigService {
10731075
getConfig(): IDictionary<any>;
1074-
}
1076+
}

lib/definitions/plugins.d.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ interface IPluginsService {
1414
*/
1515
getDependenciesFromPackageJson(projectDir: string): IPackageJsonDepedenciesResult;
1616
preparePluginNativeCode(preparePluginNativeCodeData: IPreparePluginNativeCodeData): Promise<void>;
17-
convertToPluginData(cacheData: any, projectDir: string): IPluginData;
1817
isNativeScriptPlugin(pluginPackageJsonPath: string): boolean;
1918
}
2019

@@ -44,7 +43,7 @@ interface IPluginData extends INodeModuleData {
4443
interface INodeModuleData extends IBasePluginData {
4544
fullPath: string;
4645
isPlugin: boolean;
47-
moduleInfo: any;
46+
nativescript: any;
4847
}
4948

5049
interface IPluginPlatformsData {

lib/services/plugins-service.ts

+70-17
Original file line numberDiff line numberDiff line change
@@ -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

lib/tools/node-modules/node-modules-dependencies-builder.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ export class NodeModulesDependenciesBuilder implements INodeModulesDependenciesB
9393
const dependency: IDependencyData = {
9494
name,
9595
directory,
96-
depth
96+
depth,
97+
version: null
9798
};
9899

99100
const packageJsonPath = path.join(directory, PACKAGE_JSON_FILE_NAME);
@@ -102,6 +103,7 @@ export class NodeModulesDependenciesBuilder implements INodeModulesDependenciesB
102103
if (packageJsonExists) {
103104
const packageJsonContents = this.$fs.readJson(packageJsonPath);
104105

106+
dependency.version = packageJsonContents.version;
105107
if (!!packageJsonContents.nativescript) {
106108
// add `nativescript` property, necessary for resolving plugins
107109
dependency.nativescript = packageJsonContents.nativescript;

0 commit comments

Comments
 (0)