refactor(ngcc): add `packageName` property to `EntryPoint` interface (#37040)

This commit adds a `packageName` property to the `EntryPoint` interface.
In a subsequent commit this will be used to retrieve the correct ngcc
configuration for each package, regardless of its path.

PR Close #37040
This commit is contained in:
George Kalpakas 2020-06-08 22:04:35 +03:00 committed by Misko Hevery
parent 829ddf95e5
commit 8f3695e20e
7 changed files with 222 additions and 11 deletions

View File

@ -28,6 +28,11 @@ export interface EntryPoint extends JsonObject {
name: string;
/** The path to this entry point. */
path: AbsoluteFsPath;
/**
* The name of the package that contains this entry-point (e.g. `@angular/core` or
* `@angular/common`).
*/
packageName: string;
/** The path to the package that contains this entry-point. */
packagePath: AbsoluteFsPath;
/** The parsed package.json file for this entry-point. */
@ -131,7 +136,8 @@ export function getEntryPointInfo(
const loadedEntryPointPackageJson = (packagePackageJsonPath === entryPointPackageJsonPath) ?
loadedPackagePackageJson :
loadPackageJson(fs, entryPointPackageJsonPath);
const packageVersion = getPackageVersion(loadedPackagePackageJson);
const {packageName, packageVersion} = getPackageNameAndVersion(
fs, packagePath, loadedPackagePackageJson, loadedEntryPointPackageJson);
const entryPointConfig =
config.getPackageConfig(packagePath, packageVersion).entryPoints[entryPointPath];
let entryPointPackageJson: EntryPointPackageJson;
@ -173,6 +179,7 @@ export function getEntryPointInfo(
const entryPointInfo: EntryPoint = {
name: entryPointPackageJson.name,
path: entryPointPath,
packageName,
packagePath,
packageJson: entryPointPackageJson,
typings: resolve(entryPointPath, typings),
@ -304,15 +311,44 @@ function guessTypingsFromPackageJson(
}
/**
* Find the version of the package at `packageJsonPath`.
* Find or infer the name and version of a package.
*
* The version is read off of the `version` property of the package's `package.json` file (if
* available).
* - The name is computed based on the `name` property of the package's or the entry-point's
* `package.json` file (if available) or inferred from the package's path.
* - The version is read off of the `version` property of the package's `package.json` file (if
* available).
*
* @param packageJson the parsed `package.json` of the package (if available).
* @returns the version string or `null` if the `pckage.json` file is missing or doesn't contain a
* version.
* @param fs The `FileSystem` instance to use for parsing `packagePath` (if needed).
* @param packagePath the absolute path to the package.
* @param packagePackageJson the parsed `package.json` of the package (if available).
* @param entryPointPackageJson the parsed `package.json` of an entry-point (if available).
* @returns the computed name and version of the package.
*/
function getPackageVersion(packageJson: EntryPointPackageJson|null): string|null {
return packageJson?.version ?? null;
function getPackageNameAndVersion(
fs: FileSystem, packagePath: AbsoluteFsPath, packagePackageJson: EntryPointPackageJson|null,
entryPointPackageJson: EntryPointPackageJson|
null): {packageName: string, packageVersion: string|null} {
let packageName: string;
if (packagePackageJson !== null) {
// We have a valid `package.json` for the package: Get the package name from that.
packageName = packagePackageJson.name;
} else if (entryPointPackageJson !== null) {
// We have a valid `package.json` for the entry-point: Get the package name from that.
// This might be a secondary entry-point, so make sure we only keep the main package's name
// (e.g. only keep `@angular/common` from `@angular/common/http`).
packageName = /^(?:@[^/]+\/)?[^/]*/.exec(entryPointPackageJson.name)![0];
} else {
// We don't have a valid `package.json`: Infer the package name from the package's path.
const lastSegment = fs.basename(packagePath);
const secondLastSegment = fs.basename(fs.dirname(packagePath));
packageName =
secondLastSegment.startsWith('@') ? `${secondLastSegment}/${lastSegment}` : lastSegment;
}
return {
packageName,
packageVersion: packagePackageJson?.version ?? null,
};
}

View File

@ -60,6 +60,7 @@ runInEachFileSystem(() => {
first = {
name: 'first',
path: _('/first'),
packageName: 'first',
packagePath: _('/first'),
packageJson: {esm5: './index.js'},
typings: _('/first/index.d.ts'),
@ -67,7 +68,9 @@ runInEachFileSystem(() => {
ignoreMissingDependencies: false,
} as EntryPoint;
second = {
name: 'second',
path: _('/second'),
packageName: 'second',
packagePath: _('/second'),
packageJson: {esm2015: './sub/index.js'},
typings: _('/second/sub/index.d.ts'),
@ -75,7 +78,9 @@ runInEachFileSystem(() => {
ignoreMissingDependencies: false,
} as EntryPoint;
third = {
name: 'third',
path: _('/third'),
packageName: 'third',
packagePath: _('/third'),
packageJson: {fesm5: './index.js'},
typings: _('/third/index.d.ts'),
@ -83,7 +88,9 @@ runInEachFileSystem(() => {
ignoreMissingDependencies: false,
} as EntryPoint;
fourth = {
name: 'fourth',
path: _('/fourth'),
packageName: 'fourth',
packagePath: _('/fourth'),
packageJson: {fesm2015: './sub2/index.js'},
typings: _('/fourth/sub2/index.d.ts'),
@ -91,7 +98,9 @@ runInEachFileSystem(() => {
ignoreMissingDependencies: false,
} as EntryPoint;
fifth = {
name: 'fifth',
path: _('/fifth'),
packageName: 'fifth',
packagePath: _('/fifth'),
packageJson: {module: './index.js'},
typings: _('/fifth/index.d.ts'),
@ -100,7 +109,9 @@ runInEachFileSystem(() => {
} as EntryPoint;
sixthIgnoreMissing = {
name: 'sixth',
path: _('/sixth'),
packageName: 'sixth',
packagePath: _('/sixth'),
packageJson: {module: './index.js'},
typings: _('/sixth/index.d.ts'),
@ -291,6 +302,7 @@ runInEachFileSystem(() => {
const testEntryPoint = {
name: 'test-package',
path: _('/project/node_modules/test-package'),
packageName: 'test-package',
packagePath: _('/project/node_modules/test-package'),
packageJson: {esm5: './index.js'},
typings: _('/project/node_modules/test-package/index.d.ts'),

View File

@ -22,6 +22,7 @@ export function makeTestEntryPoint(
return {
name: entryPointName,
path: absoluteFrom(`/node_modules/${entryPointName}`),
packageName,
packagePath: absoluteFrom(`/node_modules/${packageName}`),
packageJson: {name: entryPointName},
typings: absoluteFrom(`/node_modules/${entryPointName}/index.d.ts`),

View File

@ -1572,7 +1572,7 @@ runInEachFileSystem(() => {
},
{
name: _('/node_modules/deep_import/package.json'),
contents: '{"name": "deep-import", "es2015": "./index.js", "typings": "./index.d.ts"}',
contents: '{"name": "deep_import", "es2015": "./index.js", "typings": "./index.d.ts"}',
},
{
name: _('/node_modules/deep_import/entry_point.js'),

View File

@ -168,6 +168,7 @@ runInEachFileSystem(() => {
const entryPoint: EntryPoint = {
name: 'test',
path: absoluteFrom('/node_modules/test'),
packageName: 'test',
packagePath: absoluteFrom('/node_modules/test'),
packageJson: {name: 'test'},
typings: absoluteFrom('/node_modules/test/index.d.ts'),
@ -218,6 +219,7 @@ runInEachFileSystem(() => {
const entryPoint: EntryPoint = {
name: 'test',
path: absoluteFrom('/node_modules/test'),
packageName: 'test',
packagePath: absoluteFrom('/node_modules/test'),
packageJson: {name: 'test'},
typings: absoluteFrom('/node_modules/test/index.d.ts'),
@ -240,6 +242,7 @@ runInEachFileSystem(() => {
const entryPoint: EntryPoint = {
name: 'internal',
path: absoluteFrom('/node_modules/internal'),
packageName: 'internal',
packagePath: absoluteFrom('/node_modules/internal'),
packageJson: {name: 'internal'},
typings: absoluteFrom('/node_modules/internal/index.d.ts'),
@ -262,6 +265,7 @@ runInEachFileSystem(() => {
const entryPoint: EntryPoint = {
name: 'test',
path: absoluteFrom('/node_modules/test'),
packageName: 'test',
packagePath: absoluteFrom('/node_modules/test'),
packageJson: {name: 'test'},
typings: absoluteFrom('/node_modules/test/index.d.ts'),
@ -285,6 +289,7 @@ runInEachFileSystem(() => {
const entryPoint: EntryPoint = {
name: 'secondary',
path: absoluteFrom('/node_modules/primary/secondary'),
packageName: 'primary',
packagePath: absoluteFrom('/node_modules/primary'),
packageJson: {name: 'secondary'},
typings: absoluteFrom('/node_modules/primary/secondary/index.d.ts'),

View File

@ -149,6 +149,7 @@ runInEachFileSystem(() => {
entryPoint: {
name: 'some_package/valid_entry_point',
path: _Abs('/project/node_modules/some_package/valid_entry_point'),
packageName: 'some_package',
packagePath: _Abs('/project/node_modules/some_package'),
packageJson: jasmine.any(Object),
typings:

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem, join} from '../../../src/ngtsc/file_system';
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';
@ -45,6 +45,7 @@ runInEachFileSystem(() => {
expect(entryPoint).toEqual({
name: 'some_package/valid_entry_point',
path: _('/project/node_modules/some_package/valid_entry_point'),
packageName: 'some_package',
packagePath: SOME_PACKAGE,
packageJson: loadPackageJson(fs, '/project/node_modules/some_package/valid_entry_point'),
typings:
@ -194,6 +195,7 @@ runInEachFileSystem(() => {
expect(entryPoint).toEqual({
name: 'some_package/valid_entry_point',
path: _('/project/node_modules/some_package/valid_entry_point'),
packageName: 'some_package',
packagePath: SOME_PACKAGE,
packageJson: overriddenPackageJson,
typings: _('/project/node_modules/some_package/valid_entry_point/some_other.d.ts'),
@ -242,6 +244,7 @@ runInEachFileSystem(() => {
expect(entryPoint).toEqual({
name: 'some_package/missing_package_json',
path: _('/project/node_modules/some_package/missing_package_json'),
packageName: 'some_package',
packagePath: SOME_PACKAGE,
packageJson: {name: 'some_package/missing_package_json', ...override},
typings: _(
@ -252,6 +255,154 @@ runInEachFileSystem(() => {
});
});
[false, true].forEach(isScoped => {
const nameWithScope = (baseName: string) => `${isScoped ? '@some-scope/' : ''}${baseName}`;
const getPackageName = (packagePath: AbsoluteFsPath, entryPointPath: AbsoluteFsPath) => {
const config = new NgccConfiguration(fs, _('/project'));
const logger = new MockLogger();
const entryPoint = getEntryPointInfo(fs, config, logger, packagePath, entryPointPath);
if (!isEntryPoint(entryPoint)) {
return fail(`Expected an entry point but got ${entryPoint}`);
}
return entryPoint.packageName;
};
const setUpPackageWithEntryPointPackageJson =
(entryPointName: string, entryPointPath: AbsoluteFsPath) => {
// Ensure a `package.json` exists for the entry-point (containing `entryPointName`).
loadTestFiles([
{
name: join(entryPointPath, 'package.json'),
contents: JSON.stringify({name: entryPointName, typings: './index.d.ts'}),
},
]);
};
const setUpPackageWithoutEntryPointPackageJson =
(packagePath: AbsoluteFsPath, entryPointPath: AbsoluteFsPath) => {
// Ensure there is an ngcc config for the entry-point providing a `typings` field to
// avoid returning `INCOMPATIBLE_ENTRY_POINT` (since there is no `package.json`).
loadTestFiles([
{
name: join(packagePath, 'ngcc.config.js'),
contents: `
module.exports = {
entryPoints: {
'${relative(packagePath, entryPointPath)}': {
override: {typings: './index.d.ts'},
},
},
};
`,
},
]);
};
describe(`should compute the containing ${isScoped ? 'scoped ' : ''}package's name`, () => {
it('for a primary entry-point with a `package.json`', () => {
const packagePath = _(`/project/node_modules/${nameWithScope('on-disk-package-name')}`);
const entryPointPath = packagePath;
const expectedPackageName = nameWithScope('package-json-package-name');
setUpPackageWithEntryPointPackageJson(expectedPackageName, entryPointPath);
expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName);
});
it('for a primary entry-point without a `package.json`', () => {
const packagePath = _(`/project/node_modules/${nameWithScope('on-disk-package-name')}`);
const entryPointPath = packagePath;
const expectedPackageName = nameWithScope('on-disk-package-name');
setUpPackageWithoutEntryPointPackageJson(packagePath, entryPointPath);
expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName);
});
it('for a secondary entry-point with a `package.json`', () => {
const packagePath = _(`/project/node_modules/${nameWithScope('on-disk-package-name')}`);
const entryPointPath = join(packagePath, 'some-entry-point');
const expectedPackageName = nameWithScope('package-json-package-name');
setUpPackageWithEntryPointPackageJson(
`${expectedPackageName}/some-entry-point`, entryPointPath);
expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName);
});
it('for a secondary entry-point without a `package.json`', () => {
const packagePath = _(`/project/node_modules/${nameWithScope('on-disk-package-name')}`);
const entryPointPath = join(packagePath, 'some-entry-point');
const expectedPackageName = nameWithScope('on-disk-package-name');
setUpPackageWithoutEntryPointPackageJson(packagePath, entryPointPath);
expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName);
});
it('for a primary entry-point without a `package.json` in nested `node_modules/`', () => {
const packagePath = _(`/project/node_modules/other-package/node_modules/${
nameWithScope('on-disk-package-name')}`);
const entryPointPath = packagePath;
const expectedPackageName = nameWithScope('on-disk-package-name');
setUpPackageWithoutEntryPointPackageJson(packagePath, entryPointPath);
expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName);
});
it('for a secondary entry-point without a `package.json` in nested `node_modules/`', () => {
const packagePath = _(`/project/node_modules/other-package/node_modules/${
nameWithScope('on-disk-package-name')}`);
const entryPointPath = join(packagePath, 'some-entry-point');
const expectedPackageName = nameWithScope('on-disk-package-name');
setUpPackageWithoutEntryPointPackageJson(packagePath, entryPointPath);
expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName);
});
it('for a primary entry-point with a `package.json` outside `node_modules/`', () => {
const packagePath = _(`/project/libs/${nameWithScope('on-disk-package-name')}`);
const entryPointPath = packagePath;
const expectedPackageName = nameWithScope('package-json-package-name');
setUpPackageWithEntryPointPackageJson(expectedPackageName, entryPointPath);
expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName);
});
it('for a primary entry-point without a `package.json` outside `node_modules/`', () => {
const packagePath = _(`/project/libs/${nameWithScope('on-disk-package-name')}`);
const entryPointPath = packagePath;
const expectedPackageName = nameWithScope('on-disk-package-name');
setUpPackageWithoutEntryPointPackageJson(packagePath, entryPointPath);
expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName);
});
it('for a secondary entry-point with a `package.json` outside `node_modules/`', () => {
const packagePath = _(`/project/libs/${nameWithScope('on-disk-package-name')}`);
const entryPointPath = join(packagePath, 'some-entry-point');
const expectedPackageName = nameWithScope('package-json-package-name');
setUpPackageWithEntryPointPackageJson(expectedPackageName, entryPointPath);
expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName);
});
it('for a secondary entry-point without a `package.json` outside `node_modules/`', () => {
const packagePath = _(`/project/libs/${nameWithScope('on-disk-package-name')}`);
const entryPointPath = join(packagePath, 'some-entry-point');
const expectedPackageName = nameWithScope('on-disk-package-name');
setUpPackageWithoutEntryPointPackageJson(packagePath, entryPointPath);
expect(getPackageName(packagePath, entryPointPath)).toBe(expectedPackageName);
});
});
});
it('should return `INCOMPATIBLE_ENTRY_POINT` if there is no typings or types field in the package.json',
() => {
@ -323,6 +474,7 @@ runInEachFileSystem(() => {
expect(entryPoint).toEqual({
name: 'some_package/missing_typings',
path: _('/project/node_modules/some_package/missing_typings'),
packageName: 'some_package',
packagePath: SOME_PACKAGE,
packageJson: loadPackageJson(fs, '/project/node_modules/some_package/missing_typings'),
typings: _(`/project/node_modules/some_package/missing_typings/${typingsPath}.d.ts`),
@ -348,6 +500,7 @@ runInEachFileSystem(() => {
expect(entryPoint).toEqual({
name: 'some_package/missing_metadata',
path: _('/project/node_modules/some_package/missing_metadata'),
packageName: 'some_package',
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`),
@ -377,6 +530,7 @@ runInEachFileSystem(() => {
expect(entryPoint).toEqual({
name: 'some_package/missing_metadata',
path: _('/project/node_modules/some_package/missing_metadata'),
packageName: 'some_package',
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'),
@ -405,6 +559,7 @@ runInEachFileSystem(() => {
expect(entryPoint).toEqual({
name: 'some_package/types_rather_than_typings',
path: _('/project/node_modules/some_package/types_rather_than_typings'),
packageName: 'some_package',
packagePath: SOME_PACKAGE,
packageJson:
loadPackageJson(fs, '/project/node_modules/some_package/types_rather_than_typings'),
@ -440,6 +595,7 @@ runInEachFileSystem(() => {
expect(entryPoint).toEqual({
name: 'some_package/material_style',
path: _('/project/node_modules/some_package/material_style'),
packageName: 'some_package',
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`),