refactor(ivy): ngcc - mark target entry-point as processed even if ngcc was a noop (#29092)

If `targetEntryPointPath` is provided to `mainNgcc` then we will now mark all
the `propertiesToConsider` for that entry-point if we determine that
it does not contain code that was compiled by Angular (for instance it has
no `...metadata.json` file).

The commit also renames `__modified_by_ngcc__` to `__processed_by_ivy_ngcc__`, since
there may be entry-points that are marked despite ngcc not actually compiling anything.

PR Close #29092
This commit is contained in:
Pete Bacon Darwin 2019-03-20 13:47:59 +00:00 committed by Matias Niemelä
parent 083fb99033
commit a827bc2e3a
7 changed files with 131 additions and 119 deletions

View File

@ -12,23 +12,23 @@ ivy-ngcc
# Did it add the appropriate build markers?
# - esm2015
grep '"__modified_by_ngcc__":[^}]*"esm2015":"' node_modules/@angular/common/package.json
grep '"__processed_by_ivy_ngcc__":[^}]*"esm2015":"' node_modules/@angular/common/package.json
if [[ $? != 0 ]]; then exit 1; fi
# - fesm2015
grep '"__modified_by_ngcc__":[^}]*"fesm2015":"' node_modules/@angular/common/package.json
grep '"__processed_by_ivy_ngcc__":[^}]*"fesm2015":"' node_modules/@angular/common/package.json
if [[ $? != 0 ]]; then exit 1; fi
grep '"__modified_by_ngcc__":[^}]*"es2015":"' node_modules/@angular/common/package.json
grep '"__processed_by_ivy_ngcc__":[^}]*"es2015":"' node_modules/@angular/common/package.json
if [[ $? != 0 ]]; then exit 1; fi
# - esm5
grep '"__modified_by_ngcc__":[^}]*"esm5":"' node_modules/@angular/common/package.json
grep '"__processed_by_ivy_ngcc__":[^}]*"esm5":"' node_modules/@angular/common/package.json
if [[ $? != 0 ]]; then exit 1; fi
# - fesm5
grep '"__modified_by_ngcc__":[^}]*"module":"' node_modules/@angular/common/package.json
grep '"__processed_by_ivy_ngcc__":[^}]*"module":"' node_modules/@angular/common/package.json
if [[ $? != 0 ]]; then exit 1; fi
grep '"__modified_by_ngcc__":[^}]*"fesm5":"' node_modules/@angular/common/package.json
grep '"__processed_by_ivy_ngcc__":[^}]*"fesm5":"' node_modules/@angular/common/package.json
if [[ $? != 0 ]]; then exit 1; fi
# Did it replace the PRE_R3 markers correctly?

View File

@ -6,5 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/
import {hasBeenProcessed as _hasBeenProcessed} from './src/packages/build_marker';
import {EntryPointJsonProperty, EntryPointPackageJson} from './src/packages/entry_point';
export {NgccOptions, mainNgcc as process} from './src/main';
export {hasBeenProcessed} from './src/packages/build_marker';
export function hasBeenProcessed(packageJson: object, format: string) {
// We are wrapping this function to hide the internal types.
return _hasBeenProcessed(packageJson as EntryPointPackageJson, format as EntryPointJsonProperty);
}

View File

@ -7,10 +7,11 @@
*/
import {resolve} from 'canonical-path';
import {readFileSync} from 'fs';
import {AbsoluteFsPath, PathSegment} from '../../src/ngtsc/path';
import {AbsoluteFsPath} from '../../src/ngtsc/path';
import {checkMarker, writeMarker} from './packages/build_marker';
import {hasBeenProcessed, markAsProcessed} from './packages/build_marker';
import {DependencyHost} from './packages/dependency_host';
import {DependencyResolver} from './packages/dependency_resolver';
import {EntryPointFormat, EntryPointJsonProperty, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat} from './packages/entry_point';
@ -67,22 +68,41 @@ export function mainNgcc({basePath, targetEntryPointPath,
undefined;
const {entryPoints} =
finder.findEntryPoints(AbsoluteFsPath.from(basePath), absoluteTargetEntryPointPath);
entryPoints.forEach(entryPoint => {
if (absoluteTargetEntryPointPath && entryPoints.every(entryPoint => {
return entryPoint.path !== absoluteTargetEntryPointPath;
})) {
// If we get here, then the requested entry-point did not contain anything compiled by
// the old Angular compiler. Therefore there is nothing for ngcc to do.
// So mark all formats in this entry-point as processed so that clients of ngcc can avoid
// triggering ngcc for this entry-point in the future.
const packageJsonPath =
AbsoluteFsPath.from(resolve(absoluteTargetEntryPointPath, 'package.json'));
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
propertiesToConsider.forEach(formatProperty => {
if (packageJson[formatProperty])
markAsProcessed(packageJson, packageJsonPath, formatProperty as EntryPointJsonProperty);
});
return;
}
entryPoints.forEach(entryPoint => {
// Are we compiling the Angular core?
const isCore = entryPoint.name === '@angular/core';
const compiledFormats = new Set<string>();
const entryPointPackageJson = entryPoint.packageJson;
const entryPointPackageJsonPath = AbsoluteFsPath.from(resolve(entryPoint.path, 'package.json'));
for (let i = 0; i < propertiesToConsider.length; i++) {
const property = propertiesToConsider[i] as EntryPointJsonProperty;
const formatPath = entryPoint.packageJson[property];
const formatPath = entryPointPackageJson[property];
const format = getEntryPointFormat(property);
// No format then this property is not supposed to be compiled.
if (!formatPath || !format || SUPPORTED_FORMATS.indexOf(format) === -1) continue;
if (checkMarker(entryPoint, property)) {
if (hasBeenProcessed(entryPointPackageJson, property)) {
compiledFormats.add(formatPath);
console.warn(`Skipping ${entryPoint.name} : ${property} (already compiled).`);
continue;
@ -109,7 +129,7 @@ export function mainNgcc({basePath, targetEntryPointPath,
// Either this format was just compiled or its underlying format was compiled because of a
// previous property.
if (compiledFormats.has(formatPath)) {
writeMarker(entryPoint, property);
markAsProcessed(entryPointPackageJson, entryPointPackageJsonPath, property);
}
}

View File

@ -6,10 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
import {resolve} from 'canonical-path';
import {writeFileSync} from 'fs';
import {EntryPoint, EntryPointJsonProperty} from './entry_point';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {EntryPointJsonProperty, EntryPointPackageJson} from './entry_point';
export const NGCC_VERSION = '0.0.0-PLACEHOLDER';
@ -25,36 +26,19 @@ export const NGCC_VERSION = '0.0.0-PLACEHOLDER';
* @throws Error if the `packageJson` property is not an object.
* @throws Error if the entry-point has already been processed with a different ngcc version.
*/
export function hasBeenProcessed(packageJson: any, format: string): boolean {
if (typeof packageJson !== 'object') {
throw new Error('`packageJson` parameter is invalid. It parameter must be an object.');
}
if (!packageJson.__modified_by_ngcc__) {
export function hasBeenProcessed(
packageJson: EntryPointPackageJson, format: EntryPointJsonProperty): boolean {
if (!packageJson.__processed_by_ivy_ngcc__) {
return false;
}
if (Object.keys(packageJson.__modified_by_ngcc__)
.some(property => packageJson.__modified_by_ngcc__[property] !== NGCC_VERSION)) {
if (Object.keys(packageJson.__processed_by_ivy_ngcc__)
.some(property => packageJson.__processed_by_ivy_ngcc__ ![property] !== NGCC_VERSION)) {
throw new Error(
'The ngcc compiler has changed since the last ngcc build.\n' +
'Please completely remove `node_modules` and try again.');
}
return packageJson.__modified_by_ngcc__[format] === NGCC_VERSION;
}
/**
* Check whether there is a marker for the given entry-point and format property, indicating that
* the given bundle has already been processed.
* @param entryPoint the entry-point to check for a marker.
* @param format the property in the package.json of the format for which we are checking for a
* marker.
* @returns true if the entry-point and format have already been processed with this ngcc version.
* @throws Error if the entry-point and format have already been processed with a different ngcc
* version.
*/
export function checkMarker(entryPoint: EntryPoint, format: EntryPointJsonProperty): boolean {
const pkg = entryPoint.packageJson;
return hasBeenProcessed(pkg, format);
return packageJson.__processed_by_ivy_ngcc__[format] === NGCC_VERSION;
}
/**
@ -64,9 +48,10 @@ export function checkMarker(entryPoint: EntryPoint, format: EntryPointJsonProper
* @param entryPoint the entry-point to write a marker.
* @param format the property in the package.json of the format for which we are writing the marker.
*/
export function writeMarker(entryPoint: EntryPoint, format: EntryPointJsonProperty) {
const pkg = entryPoint.packageJson;
if (!pkg.__modified_by_ngcc__) pkg.__modified_by_ngcc__ = {};
pkg.__modified_by_ngcc__[format] = NGCC_VERSION;
writeFileSync(resolve(entryPoint.path, 'package.json'), JSON.stringify(pkg), 'utf8');
export function markAsProcessed(
packageJson: EntryPointPackageJson, packageJsonPath: AbsoluteFsPath,
format: EntryPointJsonProperty) {
if (!packageJson.__processed_by_ivy_ngcc__) packageJson.__processed_by_ivy_ngcc__ = {};
packageJson.__processed_by_ivy_ngcc__[format] = NGCC_VERSION;
writeFileSync(packageJsonPath, JSON.stringify(packageJson), 'utf8');
}

View File

@ -51,7 +51,7 @@ interface PackageJsonFormatProperties {
*/
export interface EntryPointPackageJson extends PackageJsonFormatProperties {
name: string;
__modified_by_ngcc__?: {[key: string]: string};
__processed_by_ivy_ngcc__?: {[key: string]: string};
}
export type EntryPointJsonProperty = keyof(PackageJsonFormatProperties);

View File

@ -13,6 +13,7 @@ const Module = require('module');
import {mainNgcc} from '../../src/main';
import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from '../../../test/runfile_helpers';
import {EntryPointPackageJson} from '../../src/packages/entry_point';
describe('ngcc main()', () => {
beforeEach(createMockFileSystem);
@ -32,7 +33,7 @@ describe('ngcc main()', () => {
it('should only compile the given package entry-point (and its dependencies).', () => {
mainNgcc({basePath: '/node_modules', targetEntryPointPath: '@angular/common/http'});
expect(loadPackage('@angular/common/http').__modified_by_ngcc__).toEqual({
expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__).toEqual({
module: '0.0.0-PLACEHOLDER',
es2015: '0.0.0-PLACEHOLDER',
esm5: '0.0.0-PLACEHOLDER',
@ -41,7 +42,7 @@ describe('ngcc main()', () => {
fesm2015: '0.0.0-PLACEHOLDER',
});
// * `common` is a dependency of `common/http`, so is compiled.
expect(loadPackage('@angular/common').__modified_by_ngcc__).toEqual({
expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toEqual({
module: '0.0.0-PLACEHOLDER',
es2015: '0.0.0-PLACEHOLDER',
esm5: '0.0.0-PLACEHOLDER',
@ -50,7 +51,7 @@ describe('ngcc main()', () => {
fesm2015: '0.0.0-PLACEHOLDER',
});
// * `core` is a dependency of `common`, so is compiled.
expect(loadPackage('@angular/core').__modified_by_ngcc__).toEqual({
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
module: '0.0.0-PLACEHOLDER',
es2015: '0.0.0-PLACEHOLDER',
esm5: '0.0.0-PLACEHOLDER',
@ -60,7 +61,20 @@ describe('ngcc main()', () => {
});
// * `common/testing` is not a dependency of `common/http` so is not compiled.
expect(loadPackage('@angular/common/testing').__modified_by_ngcc__).toBeUndefined();
expect(loadPackage('@angular/common/testing').__processed_by_ivy_ngcc__).toBeUndefined();
});
it('should mark a non-Angular package target as processed', () => {
mainNgcc({basePath: '/node_modules', targetEntryPointPath: 'test-package'});
// `test-package` has no Angular but is marked as processed.
expect(loadPackage('test-package').__processed_by_ivy_ngcc__).toEqual({
es2015: '0.0.0-PLACEHOLDER',
});
// * `core` is a dependency of `test-package`, but it is not processed, since test-package
// was not processed.
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toBeUndefined();
});
});
@ -75,22 +89,22 @@ describe('ngcc main()', () => {
// * the `main` property is UMD, which is not yet supported.
// * none of the ES2015 formats are compiled as they are not on the `propertiesToConsider`
// list.
expect(loadPackage('@angular/core').__modified_by_ngcc__).toEqual({
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
esm5: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
fesm5: '0.0.0-PLACEHOLDER',
});
expect(loadPackage('@angular/common').__modified_by_ngcc__).toEqual({
expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toEqual({
esm5: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
fesm5: '0.0.0-PLACEHOLDER',
});
expect(loadPackage('@angular/common/testing').__modified_by_ngcc__).toEqual({
expect(loadPackage('@angular/common/testing').__processed_by_ivy_ngcc__).toEqual({
esm5: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
fesm5: '0.0.0-PLACEHOLDER',
});
expect(loadPackage('@angular/common/http').__modified_by_ngcc__).toEqual({
expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__).toEqual({
esm5: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
fesm5: '0.0.0-PLACEHOLDER',
@ -109,19 +123,19 @@ describe('ngcc main()', () => {
// * In the Angular packages fesm5 and module have the same underlying format,
// so both are marked as compiled.
// * The `esm5` is not compiled because we stopped after the `fesm5` format.
expect(loadPackage('@angular/core').__modified_by_ngcc__).toEqual({
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
fesm5: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
});
expect(loadPackage('@angular/common').__modified_by_ngcc__).toEqual({
expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toEqual({
fesm5: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
});
expect(loadPackage('@angular/common/testing').__modified_by_ngcc__).toEqual({
expect(loadPackage('@angular/common/testing').__processed_by_ivy_ngcc__).toEqual({
fesm5: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
});
expect(loadPackage('@angular/common/http').__modified_by_ngcc__).toEqual({
expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__).toEqual({
fesm5: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
});
@ -135,6 +149,13 @@ function createMockFileSystem() {
'/node_modules/@angular': loadAngularPackages(),
'/node_modules/rxjs': loadDirectory(resolveNpmTreeArtifact('rxjs', 'index.js')),
'/node_modules/tslib': loadDirectory(resolveNpmTreeArtifact('tslib', 'tslib.js')),
'/node_modules/test-package': {
'package.json': '{"name": "test-package", "es2015": "./index.js", "typings": "./index.d.ts"}',
'index.js':
'import {AppModule} from "@angular/common"; export class MyApp extends AppModule;',
'index.d.s':
'import {AppModule} from "@angular/common"; export declare class MyApp extends AppModule;',
}
});
spyOn(Module, '_resolveFilename').and.callFake(mockResolve);
}
@ -209,6 +230,6 @@ function mockResolve(request: string): string|null {
}
}
function loadPackage(packageName: string): any {
function loadPackage(packageName: string): EntryPointPackageJson {
return JSON.parse(readFileSync(`/node_modules/${packageName}/package.json`, 'utf8'));
}

View File

@ -10,7 +10,7 @@ import {readFileSync, writeFileSync} from 'fs';
import * as mockFs from 'mock-fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {checkMarker, hasBeenProcessed, writeMarker} from '../../src/packages/build_marker';
import {hasBeenProcessed, markAsProcessed} from '../../src/packages/build_marker';
import {EntryPoint} from '../../src/packages/entry_point';
function createMockFileSystem() {
@ -94,104 +94,83 @@ function restoreRealFileSystem() {
mockFs.restore();
}
function createEntryPoint(path: string): EntryPoint {
const absolutePath = AbsoluteFsPath.from(path);
return {
name: 'some-package',
path: absolutePath,
package: absolutePath,
typings: AbsoluteFsPath.from('/typings'),
packageJson: JSON.parse(readFileSync(path + '/package.json', 'utf8'))
};
}
describe('Marker files', () => {
beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem);
describe('writeMarker', () => {
const COMMON_PACKAGE_PATH = AbsoluteFsPath.from('/node_modules/@angular/common/package.json');
describe('markAsProcessed', () => {
it('should write a property in the package.json containing the version placeholder', () => {
let pkg = JSON.parse(readFileSync('/node_modules/@angular/common/package.json', 'utf8'));
expect(pkg.__modified_by_ngcc__).toBeUndefined();
expect(pkg.__modified_by_ngcc__).toBeUndefined();
let pkg = JSON.parse(readFileSync(COMMON_PACKAGE_PATH, 'utf8'));
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
writeMarker(createEntryPoint('/node_modules/@angular/common'), 'fesm2015');
pkg = JSON.parse(readFileSync('/node_modules/@angular/common/package.json', 'utf8'));
expect(pkg.__modified_by_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
expect(pkg.__modified_by_ngcc__.esm5).toBeUndefined();
markAsProcessed(pkg, COMMON_PACKAGE_PATH, 'fesm2015');
pkg = JSON.parse(readFileSync(COMMON_PACKAGE_PATH, 'utf8'));
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBeUndefined();
writeMarker(createEntryPoint('/node_modules/@angular/common'), 'esm5');
pkg = JSON.parse(readFileSync('/node_modules/@angular/common/package.json', 'utf8'));
expect(pkg.__modified_by_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
expect(pkg.__modified_by_ngcc__.esm5).toEqual('0.0.0-PLACEHOLDER');
});
});
describe('checkMarker', () => {
it('should return false if the marker property does not exist', () => {
expect(checkMarker(createEntryPoint('/node_modules/@angular/common'), 'fesm2015'))
.toBe(false);
markAsProcessed(pkg, COMMON_PACKAGE_PATH, 'esm5');
pkg = JSON.parse(readFileSync(COMMON_PACKAGE_PATH, 'utf8'));
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm5).toEqual('0.0.0-PLACEHOLDER');
});
it('should return true if the marker property exists and contains the correct version', () => {
const pkg = JSON.parse(readFileSync('/node_modules/@angular/common/package.json', 'utf8'));
pkg.__modified_by_ngcc__ = {fesm2015: '0.0.0-PLACEHOLDER'};
writeFileSync('/node_modules/@angular/common/package.json', JSON.stringify(pkg), 'utf8');
expect(checkMarker(createEntryPoint('/node_modules/@angular/common'), 'fesm2015')).toBe(true);
});
it('should throw if the marker property exists but contains the wrong version', () => {
const pkg = JSON.parse(readFileSync('/node_modules/@angular/common/package.json', 'utf8'));
pkg.__modified_by_ngcc__ = {fesm2015: 'WRONG_VERSION'};
writeFileSync('/node_modules/@angular/common/package.json', JSON.stringify(pkg), 'utf8');
expect(() => checkMarker(createEntryPoint('/node_modules/@angular/common'), 'fesm2015'))
.toThrowError(
'The ngcc compiler has changed since the last ngcc build.\n' +
'Please completely remove `node_modules` and try again.');
it('should update the packageJson object in-place', () => {
let pkg = JSON.parse(readFileSync(COMMON_PACKAGE_PATH, 'utf8'));
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
markAsProcessed(pkg, COMMON_PACKAGE_PATH, 'fesm2015');
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
});
});
describe('hasBeenProcessed', () => {
it('should return true if the marker exists for the given format property', () => {
expect(
hasBeenProcessed({__modified_by_ngcc__: {'fesm2015': '0.0.0-PLACEHOLDER'}}, 'fesm2015'))
expect(hasBeenProcessed(
{name: 'test', __processed_by_ivy_ngcc__: {'fesm2015': '0.0.0-PLACEHOLDER'}},
'fesm2015'))
.toBe(true);
});
it('should return false if the marker does not exist for the given format property', () => {
expect(hasBeenProcessed({__modified_by_ngcc__: {'fesm2015': '0.0.0-PLACEHOLDER'}}, 'module'))
expect(hasBeenProcessed(
{name: 'test', __processed_by_ivy_ngcc__: {'fesm2015': '0.0.0-PLACEHOLDER'}},
'module'))
.toBe(false);
});
it('should return false if the no markers exist',
() => { expect(hasBeenProcessed({}, 'module')).toBe(false); });
it('should throw an Error if the packageJson is not an object', () => {
expect(() => hasBeenProcessed(undefined, 'fesm2015'))
.toThrowError('`packageJson` parameter is invalid. It parameter must be an object.');
it('should return false if no markers exist',
() => { expect(hasBeenProcessed({name: 'test'}, 'module')).toBe(false); });
it('should throw an Error if the format has been compiled with a different version.', () => {
expect(
() => hasBeenProcessed(
'{"__modified_by_ngcc__": {"fesm2015": "0.0.0-PLACEHOLDER"}}', 'fesm2015'))
.toThrowError('`packageJson` parameter is invalid. It parameter must be an object.');
});
it('should throw an Error if the format has been compiled with a different version.', () => {
expect(() => hasBeenProcessed({__modified_by_ngcc__: {'fesm2015': '8.0.0'}}, 'fesm2015'))
{name: 'test', __processed_by_ivy_ngcc__: {'fesm2015': '8.0.0'}}, 'fesm2015'))
.toThrowError(
'The ngcc compiler has changed since the last ngcc build.\n' +
'Please completely remove `node_modules` and try again.');
});
it('should throw an Error if any format has been compiled with a different version.', () => {
expect(() => hasBeenProcessed({__modified_by_ngcc__: {'fesm2015': '8.0.0'}}, 'module'))
expect(
() => hasBeenProcessed(
{name: 'test', __processed_by_ivy_ngcc__: {'fesm2015': '8.0.0'}}, 'module'))
.toThrowError(
'The ngcc compiler has changed since the last ngcc build.\n' +
'Please completely remove `node_modules` and try again.');
expect(
() => hasBeenProcessed(
{__modified_by_ngcc__: {'module': '0.0.0-PLACEHOLDER', 'fesm2015': '8.0.0'}},
{
name: 'test',
__processed_by_ivy_ngcc__: {'module': '0.0.0-PLACEHOLDER', 'fesm2015': '8.0.0'}
},
'module'))
.toThrowError(
'The ngcc compiler has changed since the last ngcc build.\n' +
'Please completely remove `node_modules` and try again.');
expect(
() => hasBeenProcessed(
{__modified_by_ngcc__: {'module': '0.0.0-PLACEHOLDER', 'fesm2015': '8.0.0'}},
{
name: 'test',
__processed_by_ivy_ngcc__: {'module': '0.0.0-PLACEHOLDER', 'fesm2015': '8.0.0'}
},
'fesm2015'))
.toThrowError(
'The ngcc compiler has changed since the last ngcc build.\n' +