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:
parent
916762440c
commit
90007e97ca
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
],
|
],
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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}" }`
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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"
|
||||||
|
|
15
yarn.lock
15
yarn.lock
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue