feat(ngcc): support version ranges in project/default configurations (#33008)

By appending a version range to the package name, it is now possible to
target configuration to specific versions of a package.

PR Close #33008
This commit is contained in:
Pete Bacon Darwin 2019-10-04 11:54:33 +01:00 committed by Miško Hevery
parent 916762440c
commit 90007e97ca
7 changed files with 308 additions and 128 deletions

View File

@ -61,6 +61,7 @@
"@types/minimist": "^1.2.0", "@types/minimist": "^1.2.0",
"@types/node": "^10.9.4", "@types/node": "^10.9.4",
"@types/selenium-webdriver": "3.0.7", "@types/selenium-webdriver": "3.0.7",
"@types/semver": "^6.0.2",
"@types/shelljs": "^0.7.8", "@types/shelljs": "^0.7.8",
"@types/systemjs": "0.19.32", "@types/systemjs": "0.19.32",
"@types/yargs": "^11.1.1", "@types/yargs": "^11.1.1",
@ -159,7 +160,7 @@
"mutation-observer": "^1.0.3", "mutation-observer": "^1.0.3",
"rewire": "2.5.2", "rewire": "2.5.2",
"sauce-connect": "https://saucelabs.com/downloads/sc-4.5.1-linux.tar.gz", "sauce-connect": "https://saucelabs.com/downloads/sc-4.5.1-linux.tar.gz",
"semver": "5.4.1", "semver": "^6.3.0",
"tslint-eslint-rules": "4.1.1", "tslint-eslint-rules": "4.1.1",
"tslint-no-toplevel-property-access": "0.0.2", "tslint-no-toplevel-property-access": "0.0.2",
"tsutils": "2.27.2", "tsutils": "2.27.2",

View File

@ -27,10 +27,12 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/util", "//packages/compiler-cli/src/ngtsc/util",
"@npm//@types/convert-source-map", "@npm//@types/convert-source-map",
"@npm//@types/node", "@npm//@types/node",
"@npm//@types/semver",
"@npm//@types/yargs", "@npm//@types/yargs",
"@npm//canonical-path", "@npm//canonical-path",
"@npm//dependency-graph", "@npm//dependency-graph",
"@npm//magic-string", "@npm//magic-string",
"@npm//semver",
"@npm//source-map", "@npm//source-map",
"@npm//typescript", "@npm//typescript",
], ],

View File

@ -5,6 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {satisfies} from 'semver';
import * as vm from 'vm'; import * as vm from 'vm';
import {AbsoluteFsPath, FileSystem, dirname, join, resolve} from '../../../src/ngtsc/file_system'; import {AbsoluteFsPath, FileSystem, dirname, join, resolve} from '../../../src/ngtsc/file_system';
import {PackageJsonFormatPropertiesMap} from './entry_point'; import {PackageJsonFormatPropertiesMap} from './entry_point';
@ -12,7 +13,7 @@ import {PackageJsonFormatPropertiesMap} from './entry_point';
/** /**
* The format of a project level configuration file. * The format of a project level configuration file.
*/ */
export interface NgccProjectConfig { packages: {[packagePath: string]: NgccPackageConfig}; } export interface NgccProjectConfig<T = NgccPackageConfig> { packages: {[packagePath: string]: T}; }
/** /**
* The format of a package level configuration file. * The format of a package level configuration file.
@ -80,47 +81,96 @@ export const DEFAULT_NGCC_CONFIG: NgccProjectConfig = {
} }
}; };
interface VersionedPackageConfig extends NgccPackageConfig {
versionRange: string;
}
const NGCC_CONFIG_FILENAME = 'ngcc.config.js'; const NGCC_CONFIG_FILENAME = 'ngcc.config.js';
/**
* Ngcc has a hierarchical configuration system that lets us "fix up" packages that do not
* work with ngcc out of the box.
*
* There are three levels at which configuration can be declared:
*
* * Default level - ngcc comes with built-in configuration for well known cases.
* * Package level - a library author publishes a configuration with their package to fix known
* issues.
* * Project level - the application developer provides a configuration that fixes issues specific
* to the libraries used in their application.
*
* Ngcc will match configuration based on the package name but also on its version. This allows
* configuration to provide different fixes to different version ranges of a package.
*
* * Package level configuration is specific to the package version where the configuration is
* found.
* * Default and project level configuration should provide version ranges to ensure that the
* configuration is only applied to the appropriate versions of a package.
*
* When getting a configuration for a package (via `getConfig()`) the caller should provide the
* version of the package in question, if available. If it is not provided then the first available
* configuration for a package is returned.
*/
export class NgccConfiguration { export class NgccConfiguration {
private defaultConfig: NgccProjectConfig; private defaultConfig: NgccProjectConfig<VersionedPackageConfig[]>;
private cache = new Map<string, NgccPackageConfig>(); private projectConfig: NgccProjectConfig<VersionedPackageConfig[]>;
private cache = new Map<string, VersionedPackageConfig>();
constructor(private fs: FileSystem, baseDir: AbsoluteFsPath) { constructor(private fs: FileSystem, baseDir: AbsoluteFsPath) {
this.defaultConfig = this.processDefaultConfig(baseDir); this.defaultConfig = this.processProjectConfig(baseDir, DEFAULT_NGCC_CONFIG);
const projectConfig = this.loadProjectConfig(baseDir); this.projectConfig = this.processProjectConfig(baseDir, this.loadProjectConfig(baseDir));
for (const packagePath in projectConfig.packages) {
const absPackagePath = resolve(baseDir, 'node_modules', packagePath);
const packageConfig = projectConfig.packages[packagePath];
packageConfig.entryPoints =
this.processEntryPoints(absPackagePath, packageConfig.entryPoints);
this.cache.set(absPackagePath, packageConfig);
}
} }
getConfig(packagePath: AbsoluteFsPath): NgccPackageConfig { /**
if (this.cache.has(packagePath)) { * Get a configuration for the given `version` of a package at `packagePath`.
return this.cache.get(packagePath) !; *
* @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.
*/
getConfig(packagePath: AbsoluteFsPath, version: string|null): VersionedPackageConfig {
const cacheKey = packagePath + (version !== null ? `@${version}` : '');
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey) !;
} }
const packageConfig = this.loadPackageConfig(packagePath) || const projectLevelConfig =
this.defaultConfig.packages[packagePath] || {entryPoints: {}}; findSatisfactoryVersion(this.projectConfig.packages[packagePath], version);
this.cache.set(packagePath, packageConfig); if (projectLevelConfig !== null) {
return packageConfig; this.cache.set(cacheKey, projectLevelConfig);
return projectLevelConfig;
}
const packageLevelConfig = this.loadPackageConfig(packagePath, version);
if (packageLevelConfig !== null) {
this.cache.set(cacheKey, packageLevelConfig);
return packageLevelConfig;
}
const defaultLevelConfig =
findSatisfactoryVersion(this.defaultConfig.packages[packagePath], version);
if (defaultLevelConfig !== null) {
this.cache.set(cacheKey, defaultLevelConfig);
return defaultLevelConfig;
}
return {versionRange: '*', entryPoints: {}};
} }
private processDefaultConfig(baseDir: AbsoluteFsPath): NgccProjectConfig { private processProjectConfig(baseDir: AbsoluteFsPath, projectConfig: NgccProjectConfig):
const defaultConfig: NgccProjectConfig = {packages: {}}; NgccProjectConfig<VersionedPackageConfig[]> {
for (const packagePath in DEFAULT_NGCC_CONFIG.packages) { const processedConfig: NgccProjectConfig<VersionedPackageConfig[]> = {packages: {}};
const absPackagePath = resolve(baseDir, 'node_modules', packagePath); for (const packagePathAndVersion in projectConfig.packages) {
const packageConfig = DEFAULT_NGCC_CONFIG.packages[packagePath]; const packageConfig = projectConfig.packages[packagePathAndVersion];
if (packageConfig) { if (packageConfig) {
packageConfig.entryPoints = const [packagePath, versionRange = '*'] = this.splitPathAndVersion(packagePathAndVersion);
this.processEntryPoints(absPackagePath, packageConfig.entryPoints); const absPackagePath = resolve(baseDir, 'node_modules', packagePath);
defaultConfig.packages[absPackagePath] = packageConfig; const entryPoints = this.processEntryPoints(absPackagePath, packageConfig);
processedConfig.packages[absPackagePath] = processedConfig.packages[absPackagePath] || [];
processedConfig.packages[absPackagePath].push({versionRange, entryPoints});
} }
} }
return defaultConfig; return processedConfig;
} }
private loadProjectConfig(baseDir: AbsoluteFsPath): NgccProjectConfig { private loadProjectConfig(baseDir: AbsoluteFsPath): NgccProjectConfig {
@ -136,13 +186,15 @@ export class NgccConfiguration {
} }
} }
private loadPackageConfig(packagePath: AbsoluteFsPath): NgccPackageConfig|null { private loadPackageConfig(packagePath: AbsoluteFsPath, version: string|null):
VersionedPackageConfig|null {
const configFilePath = join(packagePath, NGCC_CONFIG_FILENAME); const configFilePath = join(packagePath, NGCC_CONFIG_FILENAME);
if (this.fs.exists(configFilePath)) { if (this.fs.exists(configFilePath)) {
try { try {
const packageConfig = this.evalSrcFile(configFilePath); return {
packageConfig.entryPoints = this.processEntryPoints(packagePath, packageConfig.entryPoints); versionRange: version || '*',
return packageConfig; entryPoints: this.processEntryPoints(packagePath, this.evalSrcFile(configFilePath)),
};
} catch (e) { } catch (e) {
throw new Error(`Invalid package configuration file at "${configFilePath}": ` + e.message); throw new Error(`Invalid package configuration file at "${configFilePath}": ` + e.message);
} }
@ -164,14 +216,41 @@ export class NgccConfiguration {
return sandbox.module.exports; return sandbox.module.exports;
} }
private processEntryPoints( private processEntryPoints(packagePath: AbsoluteFsPath, packageConfig: NgccPackageConfig):
packagePath: AbsoluteFsPath, entryPoints: {[entryPointPath: string]: NgccEntryPointConfig;}):
{[entryPointPath: string]: NgccEntryPointConfig;} { {[entryPointPath: string]: NgccEntryPointConfig;} {
const processedEntryPoints: {[entryPointPath: string]: NgccEntryPointConfig;} = {}; const processedEntryPoints: {[entryPointPath: string]: NgccEntryPointConfig;} = {};
for (const entryPointPath in entryPoints) { for (const entryPointPath in packageConfig.entryPoints) {
// Change the keys to be absolute paths // Change the keys to be absolute paths
processedEntryPoints[resolve(packagePath, entryPointPath)] = entryPoints[entryPointPath]; processedEntryPoints[resolve(packagePath, entryPointPath)] =
packageConfig.entryPoints[entryPointPath];
} }
return processedEntryPoints; return processedEntryPoints;
} }
private splitPathAndVersion(packagePathAndVersion: string): [string, string|undefined] {
const versionIndex = packagePathAndVersion.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)
] :
[packagePathAndVersion, undefined];
}
}
function findSatisfactoryVersion(
configs: VersionedPackageConfig[] | undefined, version: string | null): VersionedPackageConfig|
null {
if (configs === undefined) {
return null;
}
if (version === null) {
// The package has no version (!) - perhaps the entry-point was from a deep import, which made
// it impossible to find the package.json.
// So just return the first config that matches the package name.
return configs[0];
}
return configs.find(config => satisfies(version, config.versionRange)) || null;
} }

View File

@ -82,7 +82,9 @@ export function getEntryPointInfo(
fs: FileSystem, config: NgccConfiguration, logger: Logger, packagePath: AbsoluteFsPath, fs: FileSystem, config: NgccConfiguration, logger: Logger, packagePath: AbsoluteFsPath,
entryPointPath: AbsoluteFsPath): EntryPoint|null { entryPointPath: AbsoluteFsPath): EntryPoint|null {
const packageJsonPath = resolve(entryPointPath, 'package.json'); const packageJsonPath = resolve(entryPointPath, 'package.json');
const entryPointConfig = config.getConfig(packagePath).entryPoints[entryPointPath]; const packageVersion = getPackageVersion(fs, packageJsonPath);
const entryPointConfig =
config.getConfig(packagePath, packageVersion).entryPoints[entryPointPath];
if (entryPointConfig === undefined && !fs.exists(packageJsonPath)) { if (entryPointConfig === undefined && !fs.exists(packageJsonPath)) {
return null; return null;
} }
@ -224,3 +226,20 @@ function guessTypingsFromPackageJson(
} }
return null; return null;
} }
/**
* Find the version of the package at `packageJsonPath`.
*
* @returns the version string or `null` if the package.json does not exist or is invalid.
*/
function getPackageVersion(fs: FileSystem, packageJsonPath: AbsoluteFsPath): string|null {
try {
if (fs.exists(packageJsonPath)) {
const packageJson = JSON.parse(fs.readFile(packageJsonPath));
return packageJson['version'] || null;
}
} catch {
// Do nothing
}
return null;
}

View File

@ -32,49 +32,47 @@ runInEachFileSystem(() => {
describe('getConfig()', () => { describe('getConfig()', () => {
describe('at the package level', () => { describe('at the package level', () => {
it('should return configuration for a package found in a package level file', () => { it('should return configuration for a package found in a package level file, with a matching version',
loadTestFiles([{ () => {
name: _Abs('/project-1/node_modules/package-1/ngcc.config.js'), loadTestFiles(packageWithConfigFiles('package-1', 'entry-point-1', '1.0.0'));
contents: `module.exports = {entryPoints: { './entry-point-1': {}}}` const readFileSpy = spyOn(fs, 'readFile').and.callThrough();
}]); const configuration = new NgccConfiguration(fs, _Abs('/project-1'));
const readFileSpy = spyOn(fs, 'readFile').and.callThrough(); const config =
const configuration = new NgccConfiguration(fs, _Abs('/project-1')); configuration.getConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0');
const config = configuration.getConfig(_Abs('/project-1/node_modules/package-1'));
expect(config).toEqual( expect(config).toEqual({
{entryPoints: {[_Abs('/project-1/node_modules/package-1/entry-point-1')]: {}}}); versionRange: '1.0.0',
expect(readFileSpy) entryPoints: {[_Abs('/project-1/node_modules/package-1/entry-point-1')]: {}}
.toHaveBeenCalledWith(_Abs('/project-1/node_modules/package-1/ngcc.config.js')); });
}); expect(readFileSpy)
.toHaveBeenCalledWith(_Abs('/project-1/node_modules/package-1/ngcc.config.js'));
});
it('should used cached configuration for a package if available', () => { it('should used cached configuration for a package if available', () => {
loadTestFiles([{ loadTestFiles(packageWithConfigFiles('package-1', 'entry-point-1', '1.0.0'));
name: _Abs('/project-1/node_modules/package-1/ngcc.config.js'),
contents: `
module.exports = {
entryPoints: {
'./entry-point-1': {}
},
};`
}]);
const configuration = new NgccConfiguration(fs, _Abs('/project-1')); const configuration = new NgccConfiguration(fs, _Abs('/project-1'));
// Populate the cache // Populate the cache
configuration.getConfig(_Abs('/project-1/node_modules/package-1')); configuration.getConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0');
const readFileSpy = spyOn(fs, 'readFile').and.callThrough(); const readFileSpy = spyOn(fs, 'readFile').and.callThrough();
const config = configuration.getConfig(_Abs('/project-1/node_modules/package-1')); const config =
configuration.getConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0');
expect(config).toEqual( expect(config).toEqual({
{entryPoints: {[_Abs('/project-1/node_modules/package-1/entry-point-1')]: {}}}); versionRange: '1.0.0',
entryPoints: {[_Abs('/project-1/node_modules/package-1/entry-point-1')]: {}}
});
expect(readFileSpy).not.toHaveBeenCalled(); expect(readFileSpy).not.toHaveBeenCalled();
}); });
it('should return an empty configuration object if there is no matching configuration for the package', it('should return an empty configuration object if there is no matching configuration for the package',
() => { () => {
loadTestFiles(packageWithConfigFiles('package-2', 'entry-point-1', '1.0.0'));
const configuration = new NgccConfiguration(fs, _Abs('/project-1')); const configuration = new NgccConfiguration(fs, _Abs('/project-1'));
const config = configuration.getConfig(_Abs('/project-1/node_modules/package-1')); const config =
expect(config).toEqual({entryPoints: {}}); configuration.getConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0');
expect(config).toEqual({versionRange: '*', entryPoints: {}});
}); });
it('should error if a package level config file is badly formatted', () => { it('should error if a package level config file is badly formatted', () => {
@ -83,7 +81,7 @@ runInEachFileSystem(() => {
contents: `bad js code` contents: `bad js code`
}]); }]);
const configuration = new NgccConfiguration(fs, _Abs('/project-1')); const configuration = new NgccConfiguration(fs, _Abs('/project-1'));
expect(() => configuration.getConfig(_Abs('/project-1/node_modules/package-1'))) expect(() => configuration.getConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0'))
.toThrowError( .toThrowError(
`Invalid package configuration file at "${_Abs('/project-1/node_modules/package-1/ngcc.config.js')}": Unexpected identifier`); `Invalid package configuration file at "${_Abs('/project-1/node_modules/package-1/ngcc.config.js')}": Unexpected identifier`);
}); });
@ -94,65 +92,125 @@ runInEachFileSystem(() => {
loadTestFiles([{ loadTestFiles([{
name: _Abs('/project-1/ngcc.config.js'), name: _Abs('/project-1/ngcc.config.js'),
contents: ` contents: `
module.exports = { module.exports = {
packages: { packages: {
'package-1': { 'package-1': {
entryPoints: { entryPoints: {
'./entry-point-1': {} './entry-point-1': {}
},
},
}, },
}, };`
},
};`
}]); }]);
const readFileSpy = spyOn(fs, 'readFile').and.callThrough(); const readFileSpy = spyOn(fs, 'readFile').and.callThrough();
const configuration = new NgccConfiguration(fs, _Abs('/project-1')); const configuration = new NgccConfiguration(fs, _Abs('/project-1'));
expect(readFileSpy).toHaveBeenCalledWith(_Abs('/project-1/ngcc.config.js')); expect(readFileSpy).toHaveBeenCalledWith(_Abs('/project-1/ngcc.config.js'));
const config = configuration.getConfig(_Abs('/project-1/node_modules/package-1')); const config =
expect(config).toEqual( configuration.getConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0');
{entryPoints: {[_Abs('/project-1/node_modules/package-1/entry-point-1')]: {}}}); expect(config).toEqual({
versionRange: '*',
entryPoints: {[_Abs('/project-1/node_modules/package-1/entry-point-1')]: {}}
});
});
it('should return configuration for the correct version of a package found in a project level file',
() => {
loadTestFiles([{
name: _Abs('/project-1/ngcc.config.js'),
contents: `
module.exports = {
packages: {
'package-1@1.0.0': {
entryPoints: {
'./entry-point-1': {}
},
},
'package-1@2.*': {
entryPoints: {
'./entry-point-2': {}
},
},
'package-1@^3.2.0': {
entryPoints: {
'./entry-point-3': {}
},
},
},
};`
}]);
const configuration = new NgccConfiguration(fs, _Abs('/project-1'));
expect(configuration.getConfig(_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.getConfig(_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.getConfig(_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.getConfig(_Abs('/project-1/node_modules/package-1'), '4.0.0'))
.toEqual({versionRange: '*', entryPoints: {}});
});
it('should not get confused by the @ in namespaced packages', () => {
loadTestFiles([{
name: _Abs('/project-1/ngcc.config.js'),
contents: `
module.exports = {
packages: {
'@angular/common': {
entryPoints: {
'.': {}
},
},
},
};`
}]);
const configuration = new NgccConfiguration(fs, _Abs('/project-1'));
expect(configuration.getConfig(_Abs('/project-1/node_modules/@angular/common'), '1.0.0'))
.toEqual({
versionRange: '*',
entryPoints: {[_Abs('/project-1/node_modules/@angular/common')]: {}}
});
}); });
it('should override package level config with project level config per package', () => { it('should override package level config with project level config per package', () => {
loadTestFiles([ loadTestFiles([{
{ name: _Abs('/project-1/ngcc.config.js'),
name: _Abs('/project-1/ngcc.config.js'), contents: `
contents: ` module.exports = {
module.exports = { packages: {
packages: { 'package-2': {
'package-2': { entryPoints: {
entryPoints: { './project-setting-entry-point': {}
'./project-setting-entry-point': {} },
},
}, },
}, };`,
}, }]);
};`, loadTestFiles(
}, packageWithConfigFiles('package-1', 'package-setting-entry-point', '1.0.0'));
{ loadTestFiles(
name: _Abs('/project-1/node_modules/package-1/ngcc.config.js'), packageWithConfigFiles('package-2', 'package-setting-entry-point', '1.0.0'));
contents: `
module.exports = {
entryPoints: {
'./package-setting-entry-point': {}
},
};`,
},
{
name: _Abs('/project-1/node_modules/package-2/ngcc.config.js'),
contents: `
module.exports = {
entryPoints: {
'./package-setting-entry-point': {}
},
};`,
}
]);
const readFileSpy = spyOn(fs, 'readFile').and.callThrough(); const readFileSpy = spyOn(fs, 'readFile').and.callThrough();
const configuration = new NgccConfiguration(fs, _Abs('/project-1')); const configuration = new NgccConfiguration(fs, _Abs('/project-1'));
expect(readFileSpy).toHaveBeenCalledWith(_Abs('/project-1/ngcc.config.js')); expect(readFileSpy).toHaveBeenCalledWith(_Abs('/project-1/ngcc.config.js'));
const package1Config = configuration.getConfig(_Abs('/project-1/node_modules/package-1')); const package1Config =
configuration.getConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0');
expect(package1Config).toEqual({ expect(package1Config).toEqual({
versionRange: '1.0.0',
entryPoints: entryPoints:
{[_Abs('/project-1/node_modules/package-1/package-setting-entry-point')]: {}} {[_Abs('/project-1/node_modules/package-1/package-setting-entry-point')]: {}}
}); });
@ -162,8 +220,10 @@ runInEachFileSystem(() => {
// Note that for `package-2` only the project level entry-point is left. // 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 // This is because overriding happens for packages as a whole and there is no attempt to
// merge entry-points. // merge entry-points.
const package2Config = configuration.getConfig(_Abs('/project-1/node_modules/package-2')); const package2Config =
configuration.getConfig(_Abs('/project-1/node_modules/package-2'), '1.0.0');
expect(package2Config).toEqual({ expect(package2Config).toEqual({
versionRange: '*',
entryPoints: entryPoints:
{[_Abs('/project-1/node_modules/package-2/project-setting-entry-point')]: {}} {[_Abs('/project-1/node_modules/package-2/project-setting-entry-point')]: {}}
}); });
@ -182,38 +242,36 @@ runInEachFileSystem(() => {
afterEach(() => { DEFAULT_NGCC_CONFIG.packages['package-1'] = originalDefaultConfig; }); afterEach(() => { DEFAULT_NGCC_CONFIG.packages['package-1'] = originalDefaultConfig; });
it('should return configuration for a package found in the default config', () => { it('should return configuration for a package found in the default config', () => {
const readFileSpy = spyOn(fs, 'readFile').and.callThrough(); const readFileSpy = spyOn(fs, 'readFile').and.callThrough();
const configuration = new NgccConfiguration(fs, _Abs('/project-1')); const configuration = new NgccConfiguration(fs, _Abs('/project-1'));
expect(readFileSpy).not.toHaveBeenCalled(); expect(readFileSpy).not.toHaveBeenCalled();
const config = configuration.getConfig(_Abs('/project-1/node_modules/package-1')); const config =
configuration.getConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0');
expect(config).toEqual({ expect(config).toEqual({
versionRange: '*',
entryPoints: entryPoints:
{[_Abs('/project-1/node_modules/package-1/default-level-entry-point')]: {}} {[_Abs('/project-1/node_modules/package-1/default-level-entry-point')]: {}}
}); });
}); });
it('should override default level config with package level config, if provided', () => { it('should override default level config with package level config, if provided', () => {
loadTestFiles([{ loadTestFiles(packageWithConfigFiles('package-1', 'package-level-entry-point', '1.0.0'));
name: _Abs('/project-1/node_modules/package-1/ngcc.config.js'),
contents: `
module.exports = {
entryPoints: {'./package-level-entry-point': {}},
};`,
}]);
const configuration = new NgccConfiguration(fs, _Abs('/project-1')); const configuration = new NgccConfiguration(fs, _Abs('/project-1'));
const config = configuration.getConfig(_Abs('/project-1/node_modules/package-1')); const config =
configuration.getConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0');
// Note that only the package-level-entry-point is left. // 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 // This is because overriding happens for packages as a whole and there is no attempt to
// merge entry-points. // merge entry-points.
expect(config).toEqual({ expect(config).toEqual({
versionRange: '1.0.0',
entryPoints: entryPoints:
{[_Abs('/project-1/node_modules/package-1/package-level-entry-point')]: {}} {[_Abs('/project-1/node_modules/package-1/package-level-entry-point')]: {}}
}); });
}); });
it('should override default level config with project level config, if provided', () => { it('should override default level config with project level config, if provided', () => {
loadTestFiles(packageWithConfigFiles('package-1', 'package-level-entry-point', '1.0.0'));
loadTestFiles([ loadTestFiles([
{ {
name: _Abs('/project-1/node_modules/package-1/ngcc.config.js'), name: _Abs('/project-1/node_modules/package-1/ngcc.config.js'),
@ -238,11 +296,13 @@ runInEachFileSystem(() => {
]); ]);
const configuration = new NgccConfiguration(fs, _Abs('/project-1')); const configuration = new NgccConfiguration(fs, _Abs('/project-1'));
const config = configuration.getConfig(_Abs('/project-1/node_modules/package-1')); const config =
configuration.getConfig(_Abs('/project-1/node_modules/package-1'), '1.0.0');
// Note that only the project-level-entry-point is left. // 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 // This is because overriding happens for packages as a whole and there is no attempt to
// merge entry-points. // merge entry-points.
expect(config).toEqual({ expect(config).toEqual({
versionRange: '*',
entryPoints: entryPoints:
{[_Abs('/project-1/node_modules/package-1/project-level-entry-point')]: {}} {[_Abs('/project-1/node_modules/package-1/project-level-entry-point')]: {}}
}); });
@ -250,4 +310,17 @@ runInEachFileSystem(() => {
}); });
}); });
}); });
function packageWithConfigFiles(packageName: string, entryPointName: string, version: string) {
return [
{
name: _Abs(`/project-1/node_modules/${packageName}/ngcc.config.js`),
contents: `module.exports = {entryPoints: { './${entryPointName}': {}}}`
},
{
name: _Abs(`/project-1/node_modules/${packageName}/package.json`),
contents: `{ "version": "${version}" }`
}
];
}
}); });

View File

@ -17,6 +17,7 @@
"convert-source-map": "^1.5.1", "convert-source-map": "^1.5.1",
"dependency-graph": "^0.7.2", "dependency-graph": "^0.7.2",
"magic-string": "^0.25.0", "magic-string": "^0.25.0",
"semver": "^6.3.0",
"source-map": "^0.6.1", "source-map": "^0.6.1",
"tslib": "^1.9.0", "tslib": "^1.9.0",
"yargs": "13.1.0" "yargs": "13.1.0"

View File

@ -877,6 +877,11 @@
resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-3.0.14.tgz#0b20a2370e6b1b8322c9c3dfcaa409e6c7c0c0a9" resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-3.0.14.tgz#0b20a2370e6b1b8322c9c3dfcaa409e6c7c0c0a9"
integrity sha512-4GbNCDs98uHCT/OMv40qQC/OpoPbYn9XdXeTiFwHBBFO6eJhYEPUu2zDKirXSbHlvDV8oZ9l8EQ+HrEx/YS9DQ== integrity sha512-4GbNCDs98uHCT/OMv40qQC/OpoPbYn9XdXeTiFwHBBFO6eJhYEPUu2zDKirXSbHlvDV8oZ9l8EQ+HrEx/YS9DQ==
"@types/semver@^6.0.2":
version "6.0.2"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-6.0.2.tgz#5e8b09f0e4af53034b1d0fb9977a277847836205"
integrity sha512-G1Ggy7/9Nsa1Jt2yiBR2riEuyK2DFNnqow6R7cromXPMNynackRY1vqFTLz/gwnef1LHokbXThcPhqMRjUbkpQ==
"@types/shelljs@^0.7.8": "@types/shelljs@^0.7.8":
version "0.7.9" version "0.7.9"
resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.9.tgz#3abecb72d9cad9cd4b0e7cb86ed10a97d93ba602" resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.9.tgz#3abecb72d9cad9cd4b0e7cb86ed10a97d93ba602"
@ -10057,11 +10062,6 @@ semver@5.1.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-5.1.0.tgz#85f2cf8550465c4df000cf7d86f6b054106ab9e5" resolved "https://registry.yarnpkg.com/semver/-/semver-5.1.0.tgz#85f2cf8550465c4df000cf7d86f6b054106ab9e5"
integrity sha1-hfLPhVBGXE3wAM99hvawVBBqueU= integrity sha1-hfLPhVBGXE3wAM99hvawVBBqueU=
semver@5.4.1:
version "5.4.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
integrity sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==
semver@5.5.0: semver@5.5.0:
version "5.5.0" version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
@ -10072,6 +10072,11 @@ semver@6.0.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.0.0.tgz#05e359ee571e5ad7ed641a6eec1e547ba52dea65" resolved "https://registry.yarnpkg.com/semver/-/semver-6.0.0.tgz#05e359ee571e5ad7ed641a6eec1e547ba52dea65"
integrity sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ== integrity sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==
semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@~5.0.1: semver@~5.0.1:
version "5.0.3" version "5.0.3"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a" resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a"