George Kalpakas 8197557fcf refactor(ngcc): rename EntryPoint#package to EntryPoint#packagePath (#37040)
Rename the `package` property to `packagePath` on the `EntryPoint`
interface. This makes it more clear that the `packagePath` property
holds the absolute path to the containing package (similar to how `path`
holds the path to the entry-point). This will also align with the
`packageName` property that will be added in a subsequent commit.

This commit also re-orders the `EntryPoint` properties to group related
properties together and to match the order of properties on instances
with that on the interface.

PR Close #37040
2020-06-11 18:58:36 -07:00

541 lines
23 KiB
TypeScript

/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* 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
*/
import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem} 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 {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';
runInEachFileSystem(() => {
describe('getEntryPointInfo()', () => {
let SOME_PACKAGE: AbsoluteFsPath;
let _: typeof absoluteFrom;
let fs: FileSystem;
beforeEach(() => {
_ = absoluteFrom;
SOME_PACKAGE = _('/project/node_modules/some_package');
fs = getFileSystem();
});
it('should return an object containing absolute paths to the formats of the specified entry-point',
() => {
loadTestFiles([
{
name: _('/project/node_modules/some_package/valid_entry_point/package.json'),
contents: createPackageJson('valid_entry_point')
},
{
name: _(
'/project/node_modules/some_package/valid_entry_point/valid_entry_point.metadata.json'),
contents: 'some meta data'
},
]);
const config = new NgccConfiguration(fs, _('/project'));
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/valid_entry_point'));
expect(entryPoint).toEqual({
name: 'some_package/valid_entry_point',
path: _('/project/node_modules/some_package/valid_entry_point'),
packagePath: SOME_PACKAGE,
packageJson: loadPackageJson(fs, '/project/node_modules/some_package/valid_entry_point'),
typings:
_(`/project/node_modules/some_package/valid_entry_point/valid_entry_point.d.ts`),
compiledByAngular: true,
ignoreMissingDependencies: false,
generateDeepReexports: false,
});
});
it('should return `IGNORED_ENTRY_POINT` if configured to ignore the specified entry-point', () => {
loadTestFiles([
{
name: _('/project/node_modules/some_package/valid_entry_point/package.json'),
contents: createPackageJson('valid_entry_point'),
},
{
name: _(
'/project/node_modules/some_package/valid_entry_point/valid_entry_point.metadata.json'),
contents: 'some meta data',
},
]);
const config = new NgccConfiguration(fs, _('/project'));
spyOn(config, 'getPackageConfig').and.returnValue({
entryPoints: {[_('/project/node_modules/some_package/valid_entry_point')]: {ignore: true}}
} as any);
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/valid_entry_point'));
expect(entryPoint).toBe(IGNORED_ENTRY_POINT);
});
it('should override the properties on package.json if the entry-point is configured', () => {
loadTestFiles([
{
name: _('/project/node_modules/some_package/valid_entry_point/package.json'),
contents: createPackageJson('valid_entry_point'),
},
{
name: _(
'/project/node_modules/some_package/valid_entry_point/valid_entry_point.metadata.json'),
contents: 'some meta data',
},
]);
const config = new NgccConfiguration(fs, _('/project'));
const override = {
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: '*'
});
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/valid_entry_point'));
const overriddenPackageJson = {
...loadPackageJson(fs, '/project/node_modules/some_package/valid_entry_point'),
...override
};
expect(entryPoint).toEqual({
name: 'some_package/valid_entry_point',
path: _('/project/node_modules/some_package/valid_entry_point'),
packagePath: SOME_PACKAGE,
packageJson: overriddenPackageJson,
typings: _('/project/node_modules/some_package/valid_entry_point/some_other.d.ts'),
compiledByAngular: true,
ignoreMissingDependencies: false,
generateDeepReexports: false,
});
});
it('should return `NO_ENTRY_POINT` if there is no package.json at the entry-point path', () => {
loadTestFiles([
{
name: _(
'/project/node_modules/some_package/missing_package_json/missing_package_json.metadata.json'),
contents: 'some meta data'
},
]);
const config = new NgccConfiguration(fs, _('/project'));
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/missing_package_json'));
expect(entryPoint).toBe(NO_ENTRY_POINT);
});
it('should return a configured entry-point if there is no package.json at the entry-point path',
() => {
loadTestFiles([
// no package.json!
{
name: _(
'/project/node_modules/some_package/missing_package_json/missing_package_json.metadata.json'),
contents: 'some meta data',
},
]);
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: '*'
});
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/missing_package_json'));
expect(entryPoint).toEqual({
name: 'some_package/missing_package_json',
path: _('/project/node_modules/some_package/missing_package_json'),
packagePath: SOME_PACKAGE,
packageJson: {name: 'some_package/missing_package_json', ...override},
typings: _(
'/project/node_modules/some_package/missing_package_json/missing_package_json.d.ts'),
compiledByAngular: true,
ignoreMissingDependencies: false,
generateDeepReexports: false,
});
});
it('should return `INCOMPATIBLE_ENTRY_POINT` if there is no typings or types field in the package.json',
() => {
loadTestFiles([
{
name: _('/project/node_modules/some_package/missing_typings/package.json'),
contents: createPackageJson('missing_typings', {excludes: ['typings']})
},
{
name: _(
'/project/node_modules/some_package/missing_typings/missing_typings.metadata.json'),
contents: 'some meta data'
},
]);
const config = new NgccConfiguration(fs, _('/project'));
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/missing_typings'));
expect(entryPoint).toBe(INCOMPATIBLE_ENTRY_POINT);
});
it('should return `INCOMPATIBLE_ENTRY_POINT` if the typings or types field is not a string in the package.json',
() => {
loadTestFiles([
{
name: _('/project/node_modules/some_package/typings_array/package.json'),
contents: createPackageJson('typings_array', {typingsIsArray: true})
},
{
name: _(
'/project/node_modules/some_package/typings_array/missing_typings.metadata.json'),
contents: 'some meta data'
},
]);
const config = new NgccConfiguration(fs, _('/project'));
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/typings_array'));
expect(entryPoint).toBe(INCOMPATIBLE_ENTRY_POINT);
});
for (let prop of SUPPORTED_FORMAT_PROPERTIES) {
// Ignore the UMD format
if (prop === 'main' || prop === 'browser') continue;
// Let's give 'module' a specific path, otherwise compute it based on the property.
const typingsPath = prop === 'module' ? 'index' : `${prop}/missing_typings`;
it(`should return an object if it can guess the typings path from the "${prop}" field`,
() => {
loadTestFiles([
{
name: _('/project/node_modules/some_package/missing_typings/package.json'),
contents: createPackageJson('missing_typings', {excludes: ['typings']}),
},
{
name: _(`/project/node_modules/some_package/missing_typings/${
typingsPath}.metadata.json`),
contents: 'some meta data',
},
{
name: _(`/project/node_modules/some_package/missing_typings/${typingsPath}.d.ts`),
contents: '// some typings file',
},
]);
const config = new NgccConfiguration(fs, _('/project'));
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/missing_typings'));
expect(entryPoint).toEqual({
name: 'some_package/missing_typings',
path: _('/project/node_modules/some_package/missing_typings'),
packagePath: SOME_PACKAGE,
packageJson: loadPackageJson(fs, '/project/node_modules/some_package/missing_typings'),
typings: _(`/project/node_modules/some_package/missing_typings/${typingsPath}.d.ts`),
compiledByAngular: true,
ignoreMissingDependencies: false,
generateDeepReexports: false,
});
});
}
it('should return an object with `compiledByAngular` set to false if there is no metadata.json file next to the typing file',
() => {
loadTestFiles([
{
name: _('/project/node_modules/some_package/missing_metadata/package.json'),
contents: createPackageJson('missing_metadata')
},
]);
const config = new NgccConfiguration(fs, _('/project'));
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/missing_metadata'));
expect(entryPoint).toEqual({
name: 'some_package/missing_metadata',
path: _('/project/node_modules/some_package/missing_metadata'),
packagePath: SOME_PACKAGE,
packageJson: loadPackageJson(fs, '/project/node_modules/some_package/missing_metadata'),
typings: _(`/project/node_modules/some_package/missing_metadata/missing_metadata.d.ts`),
compiledByAngular: false,
ignoreMissingDependencies: false,
generateDeepReexports: false,
});
});
it('should return an object with `compiledByAngular` set to true if there is no metadata.json file but the entry-point has a configuration',
() => {
loadTestFiles([
{
name: _('/project/node_modules/some_package/missing_metadata/package.json'),
contents: createPackageJson('missing_metadata'),
},
// no metadata.json!
]);
const config = new NgccConfiguration(fs, _('/project'));
spyOn(config, 'getPackageConfig').and.returnValue({
entryPoints: {[_('/project/node_modules/some_package/missing_metadata')]: {}},
versionRange: '*'
});
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/missing_metadata'));
expect(entryPoint).toEqual({
name: 'some_package/missing_metadata',
path: _('/project/node_modules/some_package/missing_metadata'),
packagePath: SOME_PACKAGE,
packageJson: loadPackageJson(fs, '/project/node_modules/some_package/missing_metadata'),
typings: _('/project/node_modules/some_package/missing_metadata/missing_metadata.d.ts'),
compiledByAngular: true,
ignoreMissingDependencies: false,
generateDeepReexports: false,
});
});
it('should work if the typings field is named `types', () => {
loadTestFiles([
{
name: _('/project/node_modules/some_package/types_rather_than_typings/package.json'),
contents: createPackageJson('types_rather_than_typings', {typingsProp: 'types'})
},
{
name: _(
'/project/node_modules/some_package/types_rather_than_typings/types_rather_than_typings.metadata.json'),
contents: 'some meta data'
},
]);
const config = new NgccConfiguration(fs, _('/project'));
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/types_rather_than_typings'));
expect(entryPoint).toEqual({
name: 'some_package/types_rather_than_typings',
path: _('/project/node_modules/some_package/types_rather_than_typings'),
packagePath: SOME_PACKAGE,
packageJson:
loadPackageJson(fs, '/project/node_modules/some_package/types_rather_than_typings'),
typings: _(
`/project/node_modules/some_package/types_rather_than_typings/types_rather_than_typings.d.ts`),
compiledByAngular: true,
ignoreMissingDependencies: false,
generateDeepReexports: false,
});
});
it('should work with Angular Material style package.json', () => {
loadTestFiles([
{
name: _('/project/node_modules/some_package/material_style/package.json'),
contents: `{
"name": "some_package/material_style",
"typings": "./material_style.d.ts",
"main": "./bundles/material_style.umd.js",
"module": "./esm5/material_style.es5.js",
"es2015": "./esm2015/material_style.js"
}`
},
{
name: _('/project/node_modules/some_package/material_style/material_style.metadata.json'),
contents: 'some meta data'
},
]);
const config = new NgccConfiguration(fs, _('/project'));
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/material_style'));
expect(entryPoint).toEqual({
name: 'some_package/material_style',
path: _('/project/node_modules/some_package/material_style'),
packagePath: SOME_PACKAGE,
packageJson: loadPackageJson(fs, '/project/node_modules/some_package/material_style'),
typings: _(`/project/node_modules/some_package/material_style/material_style.d.ts`),
compiledByAngular: true,
ignoreMissingDependencies: false,
generateDeepReexports: false,
});
});
it('should return `INCOMPATIBLE_ENTRY_POINT` if the package.json is not valid JSON', () => {
loadTestFiles([
// package.json might not be a valid JSON
// for example, @schematics/angular contains a package.json blueprint
// with unexpected symbols
{
name: _('/project/node_modules/some_package/unexpected_symbols/package.json'),
contents: '{"devDependencies": {<% if (!minimal) { %>"@types/jasmine": "~2.8.8" <% } %>}}'
},
]);
const config = new NgccConfiguration(fs, _('/project'));
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/unexpected_symbols'));
expect(entryPoint).toBe(INCOMPATIBLE_ENTRY_POINT);
});
});
describe('getEntryPointFormat()', () => {
let SOME_PACKAGE: AbsoluteFsPath;
let _: typeof absoluteFrom;
let fs: FileSystem;
let entryPoint: EntryPoint;
beforeEach(() => {
_ = absoluteFrom;
SOME_PACKAGE = _('/project/node_modules/some_package');
fs = getFileSystem();
loadTestFiles([{
name: _('/project/node_modules/some_package/valid_entry_point/package.json'),
contents: createPackageJson('valid_entry_point')
}]);
const config = new NgccConfiguration(fs, _('/project'));
const result = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/valid_entry_point'));
if (!isEntryPoint(result)) {
return fail(`Expected an entry point but got ${result}`);
}
entryPoint = result;
});
it('should return `esm2015` format for `fesm2015` property', () => {
expect(getEntryPointFormat(fs, entryPoint, 'fesm2015')).toBe('esm2015');
});
it('should return `esm5` format for `fesm5` property', () => {
expect(getEntryPointFormat(fs, entryPoint, 'fesm5')).toBe('esm5');
});
it('should return `esm2015` format for `es2015` property', () => {
expect(getEntryPointFormat(fs, entryPoint, 'es2015')).toBe('esm2015');
});
it('should return `esm2015` format for `esm2015` property', () => {
expect(getEntryPointFormat(fs, entryPoint, 'esm2015')).toBe('esm2015');
});
it('should return `esm5` format for `esm5` property', () => {
expect(getEntryPointFormat(fs, entryPoint, 'esm5')).toBe('esm5');
});
it('should return `esm5` format for `module` property', () => {
expect(getEntryPointFormat(fs, entryPoint, 'module')).toBe('esm5');
});
it('should return `esm2015` format for `module` property if it points to esm2015 output',
() => {
entryPoint.packageJson['module'] = '../fesm2015/valid-entry-point.js';
expect(getEntryPointFormat(fs, entryPoint, 'module')).toBe('esm2015');
});
(['browser', 'main'] as EntryPointJsonProperty[]).forEach(browserOrMain => {
it('should return `esm5` for `' + browserOrMain +
'` if the file contains import or export statements',
() => {
const name = _(
'/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js');
loadTestFiles([{name, contents: `import * as core from '@angular/core;`}]);
expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('esm5');
loadTestFiles([{name, contents: `import {Component} from '@angular/core;`}]);
expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('esm5');
loadTestFiles([{name, contents: `export function foo() {}`}]);
expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('esm5');
loadTestFiles([{name, contents: `export * from 'abc';`}]);
expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('esm5');
});
it('should return `umd` for `' + browserOrMain +
'` if the file contains a UMD wrapper function',
() => {
loadTestFiles([{
name: _(
'/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js'),
contents: `
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) :
typeof define === 'function' && define.amd ? define('@angular/common', ['exports', '@angular/core'], factory) :
(global = global || self, factory((global.ng = global.ng || {}, global.ng.common = {}), global.ng.core));
}(this, function (exports, core) { 'use strict'; }));
`
}]);
expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('umd');
});
it('should return `commonjs` for `' + browserOrMain +
'` if the file does not contain a UMD wrapper function',
() => {
loadTestFiles([{
name: _(
'/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js'),
contents: `
const core = require('@angular/core);
module.exports = {};
`
}]);
expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('commonjs');
});
it('should resolve the format path with suitable postfixes', () => {
loadTestFiles([{
name: _(
'/project/node_modules/some_package/valid_entry_point/bundles/valid_entry_point/index.js'),
contents: `
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) :
typeof define === 'function' && define.amd ? define('@angular/common', ['exports', '@angular/core'], factory) :
(global = global || self, factory((global.ng = global.ng || {}, global.ng.common = {}), global.ng.core));
}(this, function (exports, core) { 'use strict'; }));
`
}]);
entryPoint.packageJson.main = './bundles/valid_entry_point/index';
expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('umd');
entryPoint.packageJson.main = './bundles/valid_entry_point';
expect(getEntryPointFormat(fs, entryPoint, browserOrMain)).toBe('umd');
});
});
it('should return `undefined` if the `browser` property is not a string', () => {
entryPoint.packageJson.browser = {} as any;
expect(getEntryPointFormat(fs, entryPoint, 'browser')).toBeUndefined();
});
});
});
export function createPackageJson(
packageName: string,
{excludes, typingsProp = 'typings', typingsIsArray}:
{excludes?: string[], typingsProp?: string, typingsIsArray?: boolean} = {}): string {
const packageJson: any = {
name: `some_package/${packageName}`,
[typingsProp]: typingsIsArray ? [`./${packageName}.d.ts`] : `./${packageName}.d.ts`,
fesm2015: `./fesm2015/${packageName}.js`,
esm2015: `./esm2015/${packageName}.js`,
es2015: `./es2015/${packageName}.js`,
fesm5: `./fesm5/${packageName}.js`,
esm5: `./esm5/${packageName}.js`,
main: `./bundles/${packageName}/index.js`,
browser: `./bundles/${packageName}/index.js`,
module: './index.js',
};
if (excludes) {
excludes.forEach(exclude => delete packageJson[exclude]);
}
return JSON.stringify(packageJson);
}
export function loadPackageJson(fs: FileSystem, packagePath: string) {
return JSON.parse(fs.readFile(fs.resolve(packagePath + '/package.json')));
}