From bf682d73d480376e9f6d549fda7f57563e4ef795 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Mon, 8 Jun 2020 22:04:38 +0300 Subject: [PATCH] fix(ngcc): correctly get config for packages in nested `node_modules/` (#37040) Previously, ngcc would only be able to match an ngcc configuration to packages that were located inside the project's top-level `node_modules/`. However, if there are multiple versions of a package in a project (e.g. as a transitive dependency of other packages), multiple copies of a package (at different versions) may exist in nested `node_modules/` directories. For example, one at `/node_modules/some-package/` and one at `/node_modules/other-package/node_modules/some-package/`. In such cases, ngcc was only able to detect the config for the first copy but not for the second. This commit fixes this by returning a new instance of `ProcessedNgccPackageConfig` for each different package path (even if they refer to the same package name). In these `ProcessedNgccPackageConfig`, the `entryPoints` paths have been processed to take the package path into account. PR Close #37040 --- .../src/dependencies/dependency_resolver.ts | 5 +- .../ngcc/src/packages/configuration.ts | 136 ++-- .../ngcc/src/packages/entry_point.ts | 5 +- .../dependencies/dependency_resolver_spec.ts | 15 +- ...irectory_walker_entry_point_finder_spec.ts | 28 +- .../targeted_entry_point_finder_spec.ts | 28 +- .../ngcc/test/packages/configuration_spec.ts | 595 ++++++++++++------ .../packages/entry_point_manifest_spec.ts | 15 +- .../ngcc/test/packages/entry_point_spec.ts | 34 +- 9 files changed, 541 insertions(+), 320 deletions(-) diff --git a/packages/compiler-cli/ngcc/src/dependencies/dependency_resolver.ts b/packages/compiler-cli/ngcc/src/dependencies/dependency_resolver.ts index 05f19c2a53..ba8e6e2c3c 100644 --- a/packages/compiler-cli/ngcc/src/dependencies/dependency_resolver.ts +++ b/packages/compiler-cli/ngcc/src/dependencies/dependency_resolver.ts @@ -225,8 +225,9 @@ export class DependencyResolver { private filterIgnorableDeepImports(entryPoint: EntryPoint, deepImports: Set): AbsoluteFsPath[] { const version = (entryPoint.packageJson.version || null) as string | null; - const packageConfig = this.config.getPackageConfig(entryPoint.packagePath, version); - const matchers = packageConfig.ignorableDeepImportMatchers || []; + const packageConfig = + this.config.getPackageConfig(entryPoint.packageName, entryPoint.packagePath, version); + const matchers = packageConfig.ignorableDeepImportMatchers; return Array.from(deepImports) .filter(deepImport => !matchers.some(matcher => matcher.test(deepImport))); } diff --git a/packages/compiler-cli/ngcc/src/packages/configuration.ts b/packages/compiler-cli/ngcc/src/packages/configuration.ts index 1c9100fdf1..9423158251 100644 --- a/packages/compiler-cli/ngcc/src/packages/configuration.ts +++ b/packages/compiler-cli/ngcc/src/packages/configuration.ts @@ -16,11 +16,11 @@ import {PackageJsonFormatPropertiesMap} from './entry_point'; /** * The format of a project level configuration file. */ -export interface NgccProjectConfig { +export interface NgccProjectConfig { /** * The packages that are configured by this project config. */ - packages?: {[packagePath: string]: T}; + packages?: {[packagePath: string]: T|undefined}; /** * Options that control how locking the process is handled. */ @@ -44,17 +44,16 @@ export interface ProcessLockingConfiguration { } /** - * The format of a package level configuration file. + * The raw format of a package level configuration (as it appears in configuration files). */ -export interface NgccPackageConfig { +export interface RawNgccPackageConfig { /** * The entry-points to configure for this package. * - * In the config file the keys can be paths relative to the package path; - * but when being read back from the `NgccConfiguration` service, these paths - * will be absolute. + * In the config file the keys are paths relative to the package path. */ - entryPoints: {[entryPointPath: string]: NgccEntryPointConfig;}; + entryPoints?: {[entryPointPath: string]: NgccEntryPointConfig}; + /** * A collection of regexes that match deep imports to ignore, for this package, rather than * displaying a warning. @@ -71,6 +70,7 @@ export interface NgccPackageConfig { export interface NgccEntryPointConfig { /** Do not process (or even acknowledge the existence of) this entry-point, if true. */ ignore?: boolean; + /** * This property, if provided, holds values that will override equivalent properties in an * entry-point's package.json file. @@ -92,6 +92,12 @@ export interface NgccEntryPointConfig { generateDeepReexports?: boolean; } +interface VersionedPackageConfig extends RawNgccPackageConfig { + versionRange: string; +} + +type PartiallyProcessedConfig = Required>; + /** * The default configuration for ngcc. * @@ -152,14 +158,48 @@ export const DEFAULT_NGCC_CONFIG: NgccProjectConfig = { } }; -interface VersionedPackageConfig extends NgccPackageConfig { - versionRange: string; -} - -type ProcessedConfig = Required>; - const NGCC_CONFIG_FILENAME = 'ngcc.config.js'; +/** + * The processed package level configuration as a result of processing a raw package level config. + */ +export class ProcessedNgccPackageConfig implements Omit { + /** + * The absolute path to this instance of the package. + * Note that there may be multiple instances of a package inside a project in nested + * `node_modules/`. For example, one at `/node_modules/some-package/` and one at + * `/node_modules/other-package/node_modules/some-package/`. + */ + packagePath: AbsoluteFsPath; + + /** + * The entry-points to configure for this package. + * + * In contrast to `RawNgccPackageConfig`, the paths are absolute and take the path of the specific + * instance of the package into account. + */ + entryPoints: Map; + + /** + * A collection of regexes that match deep imports to ignore, for this package, rather than + * displaying a warning. + */ + ignorableDeepImportMatchers: RegExp[]; + + constructor(packagePath: AbsoluteFsPath, { + entryPoints = {}, + ignorableDeepImportMatchers = [], + }: RawNgccPackageConfig) { + const absolutePathEntries: [AbsoluteFsPath, NgccEntryPointConfig][] = + Object.entries(entryPoints).map(([relativePath, + config]) => [resolve(packagePath, relativePath), config]); + + this.packagePath = packagePath; + this.entryPoints = new Map(absolutePathEntries); + this.ignorableDeepImportMatchers = ignorableDeepImportMatchers; + } +} + /** * Ngcc has a hierarchical configuration system that lets us "fix up" packages that do not * work with ngcc out of the box. @@ -185,14 +225,14 @@ const NGCC_CONFIG_FILENAME = 'ngcc.config.js'; * configuration for a package is returned. */ export class NgccConfiguration { - private defaultConfig: ProcessedConfig; - private projectConfig: ProcessedConfig; + private defaultConfig: PartiallyProcessedConfig; + private projectConfig: PartiallyProcessedConfig; private cache = new Map(); readonly hash: string; constructor(private fs: FileSystem, baseDir: AbsoluteFsPath) { - this.defaultConfig = this.processProjectConfig(baseDir, DEFAULT_NGCC_CONFIG); - this.projectConfig = this.processProjectConfig(baseDir, this.loadProjectConfig(baseDir)); + this.defaultConfig = this.processProjectConfig(DEFAULT_NGCC_CONFIG); + this.projectConfig = this.processProjectConfig(this.loadProjectConfig(baseDir)); this.hash = this.computeHash(); } @@ -213,18 +253,27 @@ export class NgccConfiguration { /** * Get a configuration for the given `version` of a package at `packagePath`. * + * @param packageName The name of the package whose config we want. * @param packagePath The path to the package whose config we want. * @param version The version of the package whose config we want, or `null` if the package's * package.json did not exist or was invalid. */ - getPackageConfig(packagePath: AbsoluteFsPath, version: string|null): VersionedPackageConfig { - const cacheKey = packagePath + (version !== null ? `@${version}` : ''); + getPackageConfig(packageName: string, packagePath: AbsoluteFsPath, version: string|null): + ProcessedNgccPackageConfig { + const rawPackageConfig = this.getRawPackageConfig(packageName, packagePath, version); + return new ProcessedNgccPackageConfig(packagePath, rawPackageConfig); + } + + private getRawPackageConfig( + packageName: string, packagePath: AbsoluteFsPath, + version: string|null): VersionedPackageConfig { + const cacheKey = packageName + (version !== null ? `@${version}` : ''); if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey)!; } const projectLevelConfig = this.projectConfig.packages ? - findSatisfactoryVersion(this.projectConfig.packages[packagePath], version) : + findSatisfactoryVersion(this.projectConfig.packages[packageName], version) : null; if (projectLevelConfig !== null) { this.cache.set(cacheKey, projectLevelConfig); @@ -238,19 +287,18 @@ export class NgccConfiguration { } const defaultLevelConfig = this.defaultConfig.packages ? - findSatisfactoryVersion(this.defaultConfig.packages[packagePath], version) : + findSatisfactoryVersion(this.defaultConfig.packages[packageName], version) : null; if (defaultLevelConfig !== null) { this.cache.set(cacheKey, defaultLevelConfig); return defaultLevelConfig; } - return {versionRange: '*', entryPoints: {}}; + return {versionRange: '*'}; } - private processProjectConfig(baseDir: AbsoluteFsPath, projectConfig: NgccProjectConfig): - ProcessedConfig { - const processedConfig: ProcessedConfig = {packages: {}, locking: {}}; + private processProjectConfig(projectConfig: NgccProjectConfig): PartiallyProcessedConfig { + const processedConfig: PartiallyProcessedConfig = {packages: {}, locking: {}}; // locking configuration if (projectConfig.locking !== undefined) { @@ -258,15 +306,13 @@ export class NgccConfiguration { } // packages configuration - for (const packagePathAndVersion in projectConfig.packages) { - const packageConfig = projectConfig.packages[packagePathAndVersion]; + for (const packageNameAndVersion in projectConfig.packages) { + const packageConfig = projectConfig.packages[packageNameAndVersion]; if (packageConfig) { - const [packagePath, versionRange = '*'] = this.splitPathAndVersion(packagePathAndVersion); - const absPackagePath = resolve(baseDir, 'node_modules', packagePath); - const entryPoints = this.processEntryPoints(absPackagePath, packageConfig); - processedConfig.packages[absPackagePath] = processedConfig.packages[absPackagePath] || []; - processedConfig.packages[absPackagePath].push( - {...packageConfig, versionRange, entryPoints}); + const [packageName, versionRange = '*'] = this.splitNameAndVersion(packageNameAndVersion); + const packageConfigs = + processedConfig.packages[packageName] || (processedConfig.packages[packageName] = []); + packageConfigs!.push({...packageConfig, versionRange}); } } @@ -295,7 +341,6 @@ export class NgccConfiguration { return { ...packageConfig, versionRange: version || '*', - entryPoints: this.processEntryPoints(packagePath, packageConfig), }; } catch (e) { throw new Error(`Invalid package configuration file at "${configFilePath}": ` + e.message); @@ -319,27 +364,16 @@ export class NgccConfiguration { return sandbox.module.exports; } - private processEntryPoints(packagePath: AbsoluteFsPath, packageConfig: NgccPackageConfig): - {[entryPointPath: string]: NgccEntryPointConfig;} { - const processedEntryPoints: {[entryPointPath: string]: NgccEntryPointConfig;} = {}; - for (const entryPointPath in packageConfig.entryPoints) { - // Change the keys to be absolute paths - processedEntryPoints[resolve(packagePath, entryPointPath)] = - packageConfig.entryPoints[entryPointPath]; - } - return processedEntryPoints; - } - - private splitPathAndVersion(packagePathAndVersion: string): [string, string|undefined] { - const versionIndex = packagePathAndVersion.lastIndexOf('@'); + private splitNameAndVersion(packageNameAndVersion: string): [string, string|undefined] { + const versionIndex = packageNameAndVersion.lastIndexOf('@'); // Note that > 0 is because we don't want to match @ at the start of the line // which is what you would have with a namespaced package, e.g. `@angular/common`. return versionIndex > 0 ? [ - packagePathAndVersion.substring(0, versionIndex), - packagePathAndVersion.substring(versionIndex + 1) + packageNameAndVersion.substring(0, versionIndex), + packageNameAndVersion.substring(versionIndex + 1), ] : - [packagePathAndVersion, undefined]; + [packageNameAndVersion, undefined]; } private computeHash(): string { diff --git a/packages/compiler-cli/ngcc/src/packages/entry_point.ts b/packages/compiler-cli/ngcc/src/packages/entry_point.ts index 5fab055868..d3d63c07e5 100644 --- a/packages/compiler-cli/ngcc/src/packages/entry_point.ts +++ b/packages/compiler-cli/ngcc/src/packages/entry_point.ts @@ -138,8 +138,9 @@ export function getEntryPointInfo( loadPackageJson(fs, entryPointPackageJsonPath); const {packageName, packageVersion} = getPackageNameAndVersion( fs, packagePath, loadedPackagePackageJson, loadedEntryPointPackageJson); - const entryPointConfig = - config.getPackageConfig(packagePath, packageVersion).entryPoints[entryPointPath]; + + const packageConfig = config.getPackageConfig(packageName, packagePath, packageVersion); + const entryPointConfig = packageConfig.entryPoints.get(entryPointPath); let entryPointPackageJson: EntryPointPackageJson; if (entryPointConfig === undefined) { diff --git a/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts b/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts index 77e204803c..00209f311c 100644 --- a/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts +++ b/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts @@ -280,7 +280,7 @@ runInEachFileSystem(() => { it('should not log a warning for ignored deep imports', () => { spyOn(host, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/project/node_modules/test-package/index.js')]: { + [_('/project/node_modules/test-package/test-entry-point/index.js')]: { resolved: [], missing: [], deepImports: [ @@ -290,7 +290,10 @@ runInEachFileSystem(() => { }, })); spyOn(dtsHost, 'collectDependencies').and.callFake(createFakeComputeDependencies({ - [_('/project/node_modules/test-package/index.d.ts')]: {resolved: [], missing: []}, + [_('/project/node_modules/test-package/test-entry-point/index.d.ts')]: { + resolved: [], + missing: [], + }, })); // Setup the configuration to ignore deep imports that contain either "deep/" or "two". fs.ensureDir(_('/project')); @@ -300,12 +303,12 @@ runInEachFileSystem(() => { config = new NgccConfiguration(fs, _('/project')); resolver = new DependencyResolver(fs, logger, config, {esm5: host, esm2015: host}, dtsHost); const testEntryPoint = { - name: 'test-package', - path: _('/project/node_modules/test-package'), + name: 'test-package/test-entry-point', + path: _('/project/node_modules/test-package/test-entry-point'), packageName: 'test-package', packagePath: _('/project/node_modules/test-package'), packageJson: {esm5: './index.js'}, - typings: _('/project/node_modules/test-package/index.d.ts'), + typings: _('/project/node_modules/test-package/test-entry-point/index.d.ts'), compiledByAngular: true, ignoreMissingDependencies: false, } as EntryPoint; @@ -314,7 +317,7 @@ runInEachFileSystem(() => { getEntryPointsWithDeps(resolver, [testEntryPoint])); expect(result.entryPoints).toEqual([testEntryPoint]); expect(logger.logs.warn).toEqual([[ - `Entry point 'test-package' contains deep imports into '${ + `Entry point 'test-package/test-entry-point' contains deep imports into '${ _('/project/node_modules/deeper/one')}'. This is probably not a problem, but may cause the compilation of entry points to be out of order.` ]]); }); diff --git a/packages/compiler-cli/ngcc/test/entry_point_finder/directory_walker_entry_point_finder_spec.ts b/packages/compiler-cli/ngcc/test/entry_point_finder/directory_walker_entry_point_finder_spec.ts index 89a55c7bb7..0248b58f67 100644 --- a/packages/compiler-cli/ngcc/test/entry_point_finder/directory_walker_entry_point_finder_spec.ts +++ b/packages/compiler-cli/ngcc/test/entry_point_finder/directory_walker_entry_point_finder_spec.ts @@ -13,7 +13,7 @@ import {DtsDependencyHost} from '../../src/dependencies/dts_dependency_host'; import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host'; import {ModuleResolver} from '../../src/dependencies/module_resolver'; import {DirectoryWalkerEntryPointFinder} from '../../src/entry_point_finder/directory_walker_entry_point_finder'; -import {NgccConfiguration} from '../../src/packages/configuration'; +import {NgccConfiguration, ProcessedNgccPackageConfig} from '../../src/packages/configuration'; import {EntryPoint} from '../../src/packages/entry_point'; import {EntryPointManifest, EntryPointManifestFile} from '../../src/packages/entry_point_manifest'; import {PathMappings} from '../../src/path_mappings'; @@ -109,12 +109,13 @@ runInEachFileSystem(() => { fs, config, logger, resolver, manifest, basePath, undefined); loadTestFiles(createPackage(basePath, 'some-package')); - spyOn(config, 'getPackageConfig').and.returnValue({ - versionRange: '*', - entryPoints: { - [_Abs('/project/node_modules/some-package')]: {ignore: true}, - }, - }); + spyOn(config, 'getPackageConfig') + .and.returnValue( + new ProcessedNgccPackageConfig(_Abs('/project/node_modules/some-package'), { + entryPoints: { + '.': {ignore: true}, + }, + })); const {entryPoints} = finder.findEntryPoints(); expect(entryPoints).toEqual([]); @@ -131,12 +132,13 @@ runInEachFileSystem(() => { ...createPackage( fs.resolve(basePath, 'some-package/sub-entry-point-1'), 'sub-entry-point-2'), ]); - spyOn(config, 'getPackageConfig').and.returnValue({ - versionRange: '*', - entryPoints: { - [_Abs('/project/node_modules/some-package/sub-entry-point-1')]: {ignore: true}, - }, - }); + spyOn(config, 'getPackageConfig') + .and.returnValue( + new ProcessedNgccPackageConfig(_Abs('/project/node_modules/some-package'), { + entryPoints: { + './sub-entry-point-1': {ignore: true}, + }, + })); const {entryPoints} = finder.findEntryPoints(); expect(dumpEntryPointPaths(basePath, entryPoints)).toEqual([ diff --git a/packages/compiler-cli/ngcc/test/entry_point_finder/targeted_entry_point_finder_spec.ts b/packages/compiler-cli/ngcc/test/entry_point_finder/targeted_entry_point_finder_spec.ts index 71c93c2d40..30d8d9ea02 100644 --- a/packages/compiler-cli/ngcc/test/entry_point_finder/targeted_entry_point_finder_spec.ts +++ b/packages/compiler-cli/ngcc/test/entry_point_finder/targeted_entry_point_finder_spec.ts @@ -14,7 +14,7 @@ import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host'; import {ModuleResolver} from '../../src/dependencies/module_resolver'; import {TargetedEntryPointFinder} from '../../src/entry_point_finder/targeted_entry_point_finder'; import {NGCC_VERSION} from '../../src/packages/build_marker'; -import {NgccConfiguration} from '../../src/packages/configuration'; +import {NgccConfiguration, ProcessedNgccPackageConfig} from '../../src/packages/configuration'; import {EntryPoint} from '../../src/packages/entry_point'; import {PathMappings} from '../../src/path_mappings'; import {MockLogger} from '../helpers/mock_logger'; @@ -116,12 +116,13 @@ runInEachFileSystem(() => { fs, config, logger, resolver, basePath, undefined, targetPath); loadTestFiles(createPackage(basePath, 'some-package')); - spyOn(config, 'getPackageConfig').and.returnValue({ - versionRange: '*', - entryPoints: { - [_Abs('/project/node_modules/some-package')]: {ignore: true}, - }, - }); + spyOn(config, 'getPackageConfig') + .and.returnValue( + new ProcessedNgccPackageConfig(_Abs('/project/node_modules/some-package'), { + entryPoints: { + '.': {ignore: true}, + }, + })); const {entryPoints} = finder.findEntryPoints(); expect(entryPoints).toEqual([]); @@ -416,12 +417,13 @@ runInEachFileSystem(() => { fs, config, logger, resolver, basePath, undefined, targetPath); loadTestFiles(createPackage(basePath, 'some-package')); - spyOn(config, 'getPackageConfig').and.returnValue({ - versionRange: '*', - entryPoints: { - [_Abs('/project/node_modules/some-package')]: {ignore: true}, - }, - }); + spyOn(config, 'getPackageConfig') + .and.returnValue( + new ProcessedNgccPackageConfig(_Abs('/project/node_modules/some-package'), { + entryPoints: { + '.': {ignore: true}, + }, + })); expect(finder.targetNeedsProcessingOrCleaning(['fesm2015'], true)).toBe(false); }); diff --git a/packages/compiler-cli/ngcc/test/packages/configuration_spec.ts b/packages/compiler-cli/ngcc/test/packages/configuration_spec.ts index 6525cccf8c..e646e9a350 100644 --- a/packages/compiler-cli/ngcc/test/packages/configuration_spec.ts +++ b/packages/compiler-cli/ngcc/test/packages/configuration_spec.ts @@ -36,9 +36,6 @@ runInEachFileSystem(() => { it('should compute a hash from the loaded and processed project configuration', () => { const project1 = _Abs('/project-1'); const project1Config = fs.resolve(project1, 'ngcc.config.js'); - const project1NodeModules = fs.resolve(project1, 'node_modules'); - const project1Package1 = fs.resolve(project1NodeModules, 'package-1'); - const project1Package1EntryPoint1 = fs.resolve(project1Package1, 'entry-point-1'); loadTestFiles([{ name: project1Config, @@ -50,16 +47,13 @@ runInEachFileSystem(() => { };` }]); const project1Conf = new NgccConfiguration(fs, project1); - const expectedProject1Config = `{"packages":{"${project1Package1}":[{"entryPoints":{"${ - project1Package1EntryPoint1}":{}},"versionRange":"*"}]},"locking":{}}`; + const expectedProject1Config = + `{"packages":{"package-1":[{"entryPoints":{"./entry-point-1":{}},"versionRange":"*"}]},"locking":{}}`; expect(project1Conf.hash) .toEqual(createHash('md5').update(expectedProject1Config).digest('hex')); const project2 = _Abs('/project-2'); const project2Config = fs.resolve(project2, 'ngcc.config.js'); - const project2NodeModules = fs.resolve(project2, 'node_modules'); - const project2Package1 = fs.resolve(project2NodeModules, 'package-1'); - const project2Package1EntryPoint1 = fs.resolve(project2Package1, 'entry-point-1'); loadTestFiles([{ name: project2Config, @@ -71,8 +65,8 @@ runInEachFileSystem(() => { };` }]); const project2Conf = new NgccConfiguration(fs, project2); - const expectedProject2Config = `{"packages":{"${project2Package1}":[{"entryPoints":{"${ - project2Package1EntryPoint1}":{"ignore":true}},"versionRange":"*"}]},"locking":{}}`; + const expectedProject2Config = + `{"packages":{"package-1":[{"entryPoints":{"./entry-point-1":{"ignore":true}},"versionRange":"*"}]},"locking":{}}`; expect(project2Conf.hash) .toEqual(createHash('md5').update(expectedProject2Config).digest('hex')); }); @@ -94,29 +88,58 @@ runInEachFileSystem(() => { loadTestFiles(packageWithConfigFiles('package-1', 'entry-point-1', '1.0.0')); const readFileSpy = spyOn(fs, 'readFile').and.callThrough(); const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = - configuration.getPackageConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0'); + const config = configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); - expect(config).toEqual({ - versionRange: '1.0.0', - entryPoints: {[_Abs('/project-1/node_modules/package-1/entry-point-1')]: {}} - }); + expect(config).toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-1/entry-point-1'), {}], + ]), + })); expect(readFileSpy) .toHaveBeenCalledWith(_Abs('/project-1/node_modules/package-1/ngcc.config.js')); }); + it('should cope with configurations missing an `entryPoints` property', () => { + loadTestFiles([ + { + name: _Abs('/project-1/node_modules/package-1/package.json'), + contents: '{"version": "1.0.0"}', + }, + { + name: _Abs('/project-1/node_modules/package-1/ngcc.config.js'), + contents: ` + module.exports = { + ignorableDeepImportMatchers: [ /xxx/ ], + }; + `, + }, + ]); + + const configuration = new NgccConfiguration(fs, _Abs('/project-1')); + const config = configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); + + expect(config).toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [/xxx/], + entryPoints: new Map(), + })); + }); + it('should read extra package config from package level file', () => { loadTestFiles(packageWithConfigFiles( 'package-1', 'entry-point-1', '1.0.0', 'ignorableDeepImportMatchers: [ /xxx/ ]')); const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = - configuration.getPackageConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0'); + const config = configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); - expect(config).toEqual({ - versionRange: '1.0.0', - entryPoints: {[_Abs('/project-1/node_modules/package-1/entry-point-1')]: {}}, + expect(config).toEqual(jasmine.objectContaining({ ignorableDeepImportMatchers: [/xxx/], - }); + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-1/entry-point-1'), {}], + ]), + })); }); it('should used cached configuration for a package if available', () => { @@ -124,16 +147,19 @@ runInEachFileSystem(() => { const configuration = new NgccConfiguration(fs, _Abs('/project-1')); // Populate the cache - configuration.getPackageConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0'); + configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); const readFileSpy = spyOn(fs, 'readFile').and.callThrough(); - const config = - configuration.getPackageConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0'); + const config = configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); - expect(config).toEqual({ - versionRange: '1.0.0', - entryPoints: {[_Abs('/project-1/node_modules/package-1/entry-point-1')]: {}} - }); + expect(config).toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-1/entry-point-1'), {}], + ]), + })); expect(readFileSpy).not.toHaveBeenCalled(); }); @@ -141,9 +167,12 @@ runInEachFileSystem(() => { () => { loadTestFiles(packageWithConfigFiles('package-2', 'entry-point-1', '1.0.0')); const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = - configuration.getPackageConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0'); - expect(config).toEqual({versionRange: '*', entryPoints: {}}); + const config = configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); + expect(config).toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map(), + })); }); it('should error if a package level config file is badly formatted', () => { @@ -154,11 +183,59 @@ runInEachFileSystem(() => { const configuration = new NgccConfiguration(fs, _Abs('/project-1')); expect( () => configuration.getPackageConfig( - _Abs('/project-1/node_modules/package-1'), '1.0.0')) + 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0')) .toThrowError(`Invalid package configuration file at "${ _Abs( '/project-1/node_modules/package-1/ngcc.config.js')}": Unexpected identifier`); }); + + it('should correctly differentiate packages in nested `node_modules/`', () => { + loadTestFiles([ + ...packageWithConfigFiles('package-1', 'entry-point-1', '1.0.0'), + ...packageWithConfigFiles('package-2/node_modules/package-1', 'entry-point-2', '2.0.0'), + ]); + + const configuration = new NgccConfiguration(fs, _Abs('/project-1')); + + expect(configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0')) + .toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-1/entry-point-1'), {}], + ]), + })); + + expect(configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-2/node_modules/package-1'), + '2.0.0')) + .toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [ + _Abs('/project-1/node_modules/package-2/node_modules/package-1/entry-point-2'), + {}, + ], + ]), + })); + + // It should also be able to return a cached config for a package name/version + // combination, but adjust the entry-point paths. + // NOTE: While the package does not exist on the test file system, we are able to retrieve + // the config from cache. + expect(configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-3/node_modules/package-1'), + '1.0.0')) + .toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [ + _Abs('/project-1/node_modules/package-3/node_modules/package-1/entry-point-1'), + {}, + ], + ]), + })); + }); }); describe('at the project level', () => { @@ -178,13 +255,40 @@ runInEachFileSystem(() => { };` }]); const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = - configuration.getPackageConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0'); - expect(config).toEqual({ - versionRange: '*', - entryPoints: {[_Abs('/project-1/node_modules/package-1/entry-point-1')]: {}}, + const config = configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); + expect(config).toEqual(jasmine.objectContaining({ ignorableDeepImportMatchers: [/xxx/], - }); + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-1/entry-point-1'), {}], + ]), + })); + }); + + it('should cope with configurations missing an `entryPoints` property', () => { + loadTestFiles([ + { + name: _Abs('/project-1/ngcc.config.js'), + contents: ` + module.exports = { + packages: { + 'package-1': { + ignorableDeepImportMatchers: [ /xxx/ ], + }, + }, + }; + `, + }, + ]); + + const configuration = new NgccConfiguration(fs, _Abs('/project-1')); + const config = configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); + + expect(config).toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [/xxx/], + entryPoints: new Map(), + })); }); it('should return configuration for the correct version of a package found in a project level file', @@ -214,27 +318,36 @@ runInEachFileSystem(() => { }]); const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - expect( - configuration.getPackageConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0')) - .toEqual({ - versionRange: '1.0.0', - entryPoints: {[_Abs('/project-1/node_modules/package-1/entry-point-1')]: {}} - }); - expect( - configuration.getPackageConfig(_Abs('/project-1/node_modules/package-1'), '2.5.0')) - .toEqual({ - versionRange: '2.*', - entryPoints: {[_Abs('/project-1/node_modules/package-1/entry-point-2')]: {}} - }); - expect( - configuration.getPackageConfig(_Abs('/project-1/node_modules/package-1'), '3.2.5')) - .toEqual({ - versionRange: '^3.2.0', - entryPoints: {[_Abs('/project-1/node_modules/package-1/entry-point-3')]: {}} - }); - expect( - configuration.getPackageConfig(_Abs('/project-1/node_modules/package-1'), '4.0.0')) - .toEqual({versionRange: '*', entryPoints: {}}); + expect(configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0')) + .toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-1/entry-point-1'), {}], + ]), + })); + expect(configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '2.5.0')) + .toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-1/entry-point-2'), {}], + ]), + })); + expect(configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '3.2.5')) + .toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-1/entry-point-3'), {}], + ]), + })); + expect(configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '4.0.0')) + .toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map(), + })); }); it('should correctly handle pre-release versions and version ranges', () => { @@ -265,85 +378,62 @@ runInEachFileSystem(() => { }, ]); + const NO_CONFIG = jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map(), + }); + const PACKAGE_1_CONFIG = jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-1/entry-point-1'), {}], + ]), + }); + const PACKAGE_2_CONFIG = jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-2/entry-point-2'), {}], + ]), + }); + const PACKAGE_3_CONFIG = jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-3/entry-point-3'), {}], + ]), + }); + const configuration = new NgccConfiguration(fs, _Abs('/project-1')); const getConfig = (packageName: string, version: string|null) => configuration.getPackageConfig( - _Abs(`/project-1/node_modules/${packageName}`), version); + packageName, _Abs(`/project-1/node_modules/${packageName}`), version); // Default version range: * expect(getConfig('package-1', '1.0.0-beta.2')) - .toEqual( - { - versionRange: '*', - entryPoints: {[_Abs('/project-1/node_modules/package-1/entry-point-1')]: {}}, - }, - 'Config for package-1@1.0.0-beta.2'); + .toEqual(PACKAGE_1_CONFIG, 'Config for package-1@1.0.0-beta.2'); // Version range: 1.0.0-beta.2 expect(getConfig('package-2', '1.0.0-beta.2')) - .toEqual( - { - versionRange: '1.0.0-beta.2', - entryPoints: {[_Abs('/project-1/node_modules/package-2/entry-point-2')]: {}}, - }, - 'Config for package-2@1.0.0-beta.2'); + .toEqual(PACKAGE_2_CONFIG, 'Config for package-2@1.0.0-beta.2'); - expect(getConfig('package-2', '1.0.0')) - .toEqual( - { - versionRange: '*', - entryPoints: {}, - }, - 'Config for package-2@1.0.0'); + expect(getConfig('package-2', '1.0.0')).toEqual(NO_CONFIG, 'Config for package-2@1.0.0'); expect(getConfig('package-2', null)) - .toEqual( - { - versionRange: '1.0.0-beta.2', - entryPoints: {[_Abs('/project-1/node_modules/package-2/entry-point-2')]: {}}, - }, - 'Config for package-2@null'); + .toEqual(PACKAGE_2_CONFIG, 'Config for package-2@null'); // Version range: >=1.0.0-beta.2 expect(getConfig('package-3', '1.0.0-beta.2')) - .toEqual( - { - versionRange: '>=1.0.0-beta.2', - entryPoints: {[_Abs('/project-1/node_modules/package-3/entry-point-3')]: {}}, - }, - 'Config for package-3@1.0.0-beta.2'); + .toEqual(PACKAGE_3_CONFIG, 'Config for package-3@1.0.0-beta.2'); expect(getConfig('package-3', '1.0.0')) - .toEqual( - { - versionRange: '>=1.0.0-beta.2', - entryPoints: {[_Abs('/project-1/node_modules/package-3/entry-point-3')]: {}}, - }, - 'Config for package-3@1.0.0'); + .toEqual(PACKAGE_3_CONFIG, 'Config for package-3@1.0.0'); expect(getConfig('package-3', '2.0.0')) - .toEqual( - { - versionRange: '>=1.0.0-beta.2', - entryPoints: {[_Abs('/project-1/node_modules/package-3/entry-point-3')]: {}}, - }, - 'Config for package-3@2.0.0'); + .toEqual(PACKAGE_3_CONFIG, 'Config for package-3@2.0.0'); expect(getConfig('package-3', '1.0.0-beta.1')) - .toEqual( - { - versionRange: '*', - entryPoints: {}, - }, - 'Config for package-3@1.0.0-beta.1'); + .toEqual(NO_CONFIG, 'Config for package-3@1.0.0-beta.1'); expect(getConfig('package-3', '0.9.99')) - .toEqual( - { - versionRange: '*', - entryPoints: {}, - }, - 'Config for package-3@0.9.99'); + .toEqual(NO_CONFIG, 'Config for package-3@0.9.99'); }); it('should not get confused by the @ in namespaced packages', () => { @@ -363,11 +453,13 @@ runInEachFileSystem(() => { const configuration = new NgccConfiguration(fs, _Abs('/project-1')); expect(configuration.getPackageConfig( - _Abs('/project-1/node_modules/@angular/common'), '1.0.0')) - .toEqual({ - versionRange: '*', - entryPoints: {[_Abs('/project-1/node_modules/@angular/common')]: {}} - }); + '@angular/common', _Abs('/project-1/node_modules/@angular/common'), '1.0.0')) + .toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/@angular/common'), {}], + ]), + })); }); it('should override package level config with project level config per package', () => { @@ -393,29 +485,79 @@ runInEachFileSystem(() => { const configuration = new NgccConfiguration(fs, _Abs('/project-1')); expect(readFileSpy).toHaveBeenCalledWith(_Abs('/project-1/ngcc.config.js')); - const package1Config = - configuration.getPackageConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0'); - expect(package1Config).toEqual({ - versionRange: '1.0.0', - entryPoints: - {[_Abs('/project-1/node_modules/package-1/package-setting-entry-point')]: {}} - }); + const package1Config = configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); + expect(package1Config).toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-1/package-setting-entry-point'), {}], + ]), + })); expect(readFileSpy) .toHaveBeenCalledWith(_Abs('/project-1/node_modules/package-1/ngcc.config.js')); // Note that for `package-2` only the project level entry-point is left. // This is because overriding happens for packages as a whole and there is no attempt to // merge entry-points. - const package2Config = - configuration.getPackageConfig(_Abs('/project-1/node_modules/package-2'), '1.0.0'); - expect(package2Config).toEqual({ - versionRange: '*', - entryPoints: - {[_Abs('/project-1/node_modules/package-2/project-setting-entry-point')]: {}} - }); + const package2Config = configuration.getPackageConfig( + 'package-2', _Abs('/project-1/node_modules/package-2'), '1.0.0'); + expect(package2Config).toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-2/project-setting-entry-point'), {}], + ]), + })); expect(readFileSpy) .not.toHaveBeenCalledWith(_Abs('/project-1/node_modules/package-2/ngcc.config.js')); }); + + it('should correctly match packages in nested `node_modules/` (and adjust entry-point paths)', + () => { + loadTestFiles([ + { + name: _Abs('/project-1/ngcc.config.js'), + contents: ` + module.exports = { + packages: { + 'package-1': { + entryPoints: { + '.': {}, + 'foo': {}, + './bar': {}, + }, + }, + }, + }; + `, + }, + ]); + + const configuration = new NgccConfiguration(fs, _Abs('/project-1')); + + expect(configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0')) + .toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-1'), {}], + [_Abs('/project-1/node_modules/package-1/foo'), {}], + [_Abs('/project-1/node_modules/package-1/bar'), {}], + ]), + })); + + expect(configuration.getPackageConfig( + 'package-1', + _Abs('/project-1/node_modules/other-package/node_modules/package-1'), + '2.0.0')) + .toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/other-package/node_modules/package-1'), {}], + [_Abs('/project-1/node_modules/other-package/node_modules/package-1/foo'), {}], + [_Abs('/project-1/node_modules/other-package/node_modules/package-1/bar'), {}], + ]), + })); + }); }); describe('at the default level', () => { @@ -432,26 +574,45 @@ runInEachFileSystem(() => { const configuration = new NgccConfiguration(fs, _Abs('/project-1')); expect(readFileSpy).not.toHaveBeenCalled(); - const config = - configuration.getPackageConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0'); - expect(config).toEqual({ - versionRange: '*', - entryPoints: {[_Abs('/project-1/node_modules/package-1/default-level-entry-point')]: {}} - }); + const config = configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); + expect(config).toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-1/default-level-entry-point'), {}], + ]), + })); + }); + + it('should cope with configurations missing an `entryPoints` property', () => { + DEFAULT_NGCC_CONFIG.packages!['package-1'] = { + ignorableDeepImportMatchers: [/xxx/], + }; + + const configuration = new NgccConfiguration(fs, _Abs('/project-1')); + const config = configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); + + expect(config).toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [/xxx/], + entryPoints: new Map(), + })); }); it('should override default level config with package level config, if provided', () => { loadTestFiles(packageWithConfigFiles('package-1', 'package-level-entry-point', '1.0.0')); const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = - configuration.getPackageConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0'); + const config = configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); // Note that only the package-level-entry-point is left. // This is because overriding happens for packages as a whole and there is no attempt to // merge entry-points. - expect(config).toEqual({ - versionRange: '1.0.0', - entryPoints: {[_Abs('/project-1/node_modules/package-1/package-level-entry-point')]: {}} - }); + expect(config).toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-1/package-level-entry-point'), {}], + ]), + })); }); it('should override default level config with project level config, if provided', () => { @@ -480,15 +641,17 @@ runInEachFileSystem(() => { ]); const configuration = new NgccConfiguration(fs, _Abs('/project-1')); - const config = - configuration.getPackageConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0'); + const config = configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0'); // Note that only the project-level-entry-point is left. // This is because overriding happens for packages as a whole and there is no attempt to // merge entry-points. - expect(config).toEqual({ - versionRange: '*', - entryPoints: {[_Abs('/project-1/node_modules/package-1/project-level-entry-point')]: {}} - }); + expect(config).toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-1/project-level-entry-point'), {}], + ]), + })); }); it('should correctly handle pre-release versions and version ranges', () => { @@ -510,86 +673,100 @@ runInEachFileSystem(() => { }, }); + const NO_CONFIG = jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map(), + }); + const PACKAGE_1_CONFIG = jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-1/entry-point-1'), {}], + ]), + }); + const PACKAGE_2_CONFIG = jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-2/entry-point-2'), {}], + ]), + }); + const PACKAGE_3_CONFIG = jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-3/entry-point-3'), {}], + ]), + }); + const configuration = new NgccConfiguration(fs, _Abs('/project-1')); const getConfig = (packageName: string, version: string|null) => configuration.getPackageConfig( - _Abs(`/project-1/node_modules/${packageName}`), version); + packageName, _Abs(`/project-1/node_modules/${packageName}`), version); // Default version range: * expect(getConfig('package-1', '1.0.0-beta.2')) - .toEqual( - { - versionRange: '*', - entryPoints: {[_Abs('/project-1/node_modules/package-1/entry-point-1')]: {}}, - }, - 'Config for package-1@1.0.0-beta.2'); + .toEqual(PACKAGE_1_CONFIG, 'Config for package-1@1.0.0-beta.2'); // Version range: 1.0.0-beta.2 expect(getConfig('package-2', '1.0.0-beta.2')) - .toEqual( - { - versionRange: '1.0.0-beta.2', - entryPoints: {[_Abs('/project-1/node_modules/package-2/entry-point-2')]: {}}, - }, - 'Config for package-2@1.0.0-beta.2'); + .toEqual(PACKAGE_2_CONFIG, 'Config for package-2@1.0.0-beta.2'); - expect(getConfig('package-2', '1.0.0')) - .toEqual( - { - versionRange: '*', - entryPoints: {}, - }, - 'Config for package-2@1.0.0'); + expect(getConfig('package-2', '1.0.0')).toEqual(NO_CONFIG, 'Config for package-2@1.0.0'); expect(getConfig('package-2', null)) - .toEqual( - { - versionRange: '1.0.0-beta.2', - entryPoints: {[_Abs('/project-1/node_modules/package-2/entry-point-2')]: {}}, - }, - 'Config for package-2@null'); + .toEqual(PACKAGE_2_CONFIG, 'Config for package-2@null'); // Version range: >=1.0.0-beta.2 expect(getConfig('package-3', '1.0.0-beta.2')) - .toEqual( - { - versionRange: '>=1.0.0-beta.2', - entryPoints: {[_Abs('/project-1/node_modules/package-3/entry-point-3')]: {}}, - }, - 'Config for package-3@1.0.0-beta.2'); + .toEqual(PACKAGE_3_CONFIG, 'Config for package-3@1.0.0-beta.2'); expect(getConfig('package-3', '1.0.0')) - .toEqual( - { - versionRange: '>=1.0.0-beta.2', - entryPoints: {[_Abs('/project-1/node_modules/package-3/entry-point-3')]: {}}, - }, - 'Config for package-3@1.0.0'); + .toEqual(PACKAGE_3_CONFIG, 'Config for package-3@1.0.0'); expect(getConfig('package-3', '2.0.0')) - .toEqual( - { - versionRange: '>=1.0.0-beta.2', - entryPoints: {[_Abs('/project-1/node_modules/package-3/entry-point-3')]: {}}, - }, - 'Config for package-3@2.0.0'); + .toEqual(PACKAGE_3_CONFIG, 'Config for package-3@2.0.0'); expect(getConfig('package-3', '1.0.0-beta.1')) - .toEqual( - { - versionRange: '*', - entryPoints: {}, - }, - 'Config for package-3@1.0.0-beta.1'); + .toEqual(NO_CONFIG, 'Config for package-3@1.0.0-beta.1'); expect(getConfig('package-3', '0.9.99')) - .toEqual( - { - versionRange: '*', - entryPoints: {}, - }, - 'Config for package-3@0.9.99'); + .toEqual(NO_CONFIG, 'Config for package-3@0.9.99'); }); + + it('should correctly match packages in nested `node_modules/` (and adjust entry-point paths)', + () => { + DEFAULT_NGCC_CONFIG.packages!['package-1'] = { + entryPoints: { + '.': {}, + 'foo': {}, + './bar': {}, + }, + }; + + const configuration = new NgccConfiguration(fs, _Abs('/project-1')); + + expect(configuration.getPackageConfig( + 'package-1', _Abs('/project-1/node_modules/package-1'), '1.0.0')) + .toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/package-1'), {}], + [_Abs('/project-1/node_modules/package-1/foo'), {}], + [_Abs('/project-1/node_modules/package-1/bar'), {}], + ]), + })); + + expect(configuration.getPackageConfig( + 'package-1', + _Abs('/project-1/node_modules/other-package/node_modules/package-1'), + '2.0.0')) + .toEqual(jasmine.objectContaining({ + ignorableDeepImportMatchers: [], + entryPoints: new Map([ + [_Abs('/project-1/node_modules/other-package/node_modules/package-1'), {}], + [_Abs('/project-1/node_modules/other-package/node_modules/package-1/foo'), {}], + [_Abs('/project-1/node_modules/other-package/node_modules/package-1/bar'), {}], + ]), + })); + }); }); }); diff --git a/packages/compiler-cli/ngcc/test/packages/entry_point_manifest_spec.ts b/packages/compiler-cli/ngcc/test/packages/entry_point_manifest_spec.ts index d8359827e2..294ffa250e 100644 --- a/packages/compiler-cli/ngcc/test/packages/entry_point_manifest_spec.ts +++ b/packages/compiler-cli/ngcc/test/packages/entry_point_manifest_spec.ts @@ -12,7 +12,7 @@ import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; import {loadTestFiles} from '../../../test/helpers'; import {EntryPointWithDependencies} from '../../src/dependencies/dependency_host'; import {NGCC_VERSION} from '../../src/packages/build_marker'; -import {NgccConfiguration} from '../../src/packages/configuration'; +import {NgccConfiguration, ProcessedNgccPackageConfig} from '../../src/packages/configuration'; import {EntryPointManifest, EntryPointManifestFile} from '../../src/packages/entry_point_manifest'; import {MockLogger} from '../helpers/mock_logger'; @@ -223,12 +223,13 @@ runInEachFileSystem(() => { fs.writeFile( _Abs('/project/node_modules/__ngcc_entry_points__.json'), JSON.stringify(manifestFile)); - spyOn(config, 'getPackageConfig').and.returnValue({ - versionRange: '*', - entryPoints: { - [_Abs('/project/node_modules/some_package/ignored_entry_point')]: {ignore: true}, - }, - }); + spyOn(config, 'getPackageConfig') + .and.returnValue( + new ProcessedNgccPackageConfig(_Abs('/project/node_modules/some_package'), { + entryPoints: { + './ignored_entry_point': {ignore: true}, + }, + })); const entryPoints = manifest.readEntryPointsUsingManifest(_Abs('/project/node_modules')); expect(entryPoints).toEqual(null); diff --git a/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts b/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts index 063adcba39..174f3271cf 100644 --- a/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts +++ b/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts @@ -9,7 +9,7 @@ import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem, join, relative} from '../../../src/ngtsc/file_system'; import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; import {loadTestFiles} from '../../../test/helpers'; -import {NgccConfiguration} from '../../src/packages/configuration'; +import {NgccConfiguration, ProcessedNgccPackageConfig} from '../../src/packages/configuration'; import {EntryPoint, EntryPointJsonProperty, getEntryPointFormat, getEntryPointInfo, IGNORED_ENTRY_POINT, INCOMPATIBLE_ENTRY_POINT, isEntryPoint, NO_ENTRY_POINT, SUPPORTED_FORMAT_PROPERTIES} from '../../src/packages/entry_point'; import {MockLogger} from '../helpers/mock_logger'; @@ -69,9 +69,10 @@ runInEachFileSystem(() => { }, ]); const config = new NgccConfiguration(fs, _('/project')); - spyOn(config, 'getPackageConfig').and.returnValue({ - entryPoints: {[_('/project/node_modules/some_package/valid_entry_point')]: {ignore: true}} - } as any); + spyOn(config, 'getPackageConfig') + .and.returnValue(new ProcessedNgccPackageConfig( + _('/project/node_modules/some_package'), + {entryPoints: {'./valid_entry_point': {ignore: true}}})); const entryPoint = getEntryPointInfo( fs, config, new MockLogger(), SOME_PACKAGE, _('/project/node_modules/some_package/valid_entry_point')); @@ -181,10 +182,10 @@ runInEachFileSystem(() => { typings: './some_other.d.ts', esm2015: './some_other.js', }; - spyOn(config, 'getPackageConfig').and.returnValue({ - entryPoints: {[_('/project/node_modules/some_package/valid_entry_point')]: {override}}, - versionRange: '*' - }); + spyOn(config, 'getPackageConfig') + .and.returnValue(new ProcessedNgccPackageConfig( + _('/project/node_modules/some_package'), + {entryPoints: {'./valid_entry_point': {override}}})); const entryPoint = getEntryPointInfo( fs, config, new MockLogger(), SOME_PACKAGE, _('/project/node_modules/some_package/valid_entry_point')); @@ -233,11 +234,10 @@ runInEachFileSystem(() => { const config = new NgccConfiguration(fs, _('/project')); const override = JSON.parse(createPackageJson('missing_package_json', {excludes: ['name']})); - spyOn(config, 'getPackageConfig').and.returnValue({ - entryPoints: - {[_('/project/node_modules/some_package/missing_package_json')]: {override}}, - versionRange: '*' - }); + spyOn(config, 'getPackageConfig') + .and.returnValue(new ProcessedNgccPackageConfig( + _('/project/node_modules/some_package/'), + {entryPoints: {'./missing_package_json': {override}}})); const entryPoint = getEntryPointInfo( fs, config, new MockLogger(), SOME_PACKAGE, _('/project/node_modules/some_package/missing_package_json')); @@ -520,10 +520,10 @@ runInEachFileSystem(() => { // no metadata.json! ]); const config = new NgccConfiguration(fs, _('/project')); - spyOn(config, 'getPackageConfig').and.returnValue({ - entryPoints: {[_('/project/node_modules/some_package/missing_metadata')]: {}}, - versionRange: '*' - }); + spyOn(config, 'getPackageConfig') + .and.returnValue(new ProcessedNgccPackageConfig( + _('/project/node_modules/some_package'), + {entryPoints: {'./missing_metadata': {}}})); const entryPoint = getEntryPointInfo( fs, config, new MockLogger(), SOME_PACKAGE, _('/project/node_modules/some_package/missing_metadata'));