fix(ngcc): do not crash on entry-point that fails to compile (#36083)

Previously, when an entry-point contained code that caused its compilation
to fail, ngcc would exit in the middle of processing, possibly leaving other
entry-points in a corrupt state.

This change adds a new `errorOnFailedEntryPoint` option to `mainNgcc` that
specifies whether ngcc should exit immediately or log an error and continue
processing other entry-points.

The default is `false` so that ngcc will not error but continue processing
as much as possible. This is useful in post-install hooks, and async CLI
integration, where we do not have as much control over which entry-points
should be processed.

The option is forced to true if the `targetEntryPointPath` is provided,
such as the sync integration with the CLI, since in that case it is targeting
an entry-point that will actually be used in the current project so we do want
ngcc to exit with an error at that point.

PR Close #36083
This commit is contained in:
Pete Bacon Darwin
2020-03-16 15:08:47 +00:00
committed by Andrew Kushnir
parent 1790b63a5d
commit ff665b9e6a
10 changed files with 247 additions and 57 deletions

View File

@ -9,6 +9,7 @@
import {PartiallyOrderedTasks, TaskQueue} from '../../../../src/execution/tasks/api';
import {ParallelTaskQueue} from '../../../../src/execution/tasks/queues/parallel_task_queue';
import {computeTaskDependencies} from '../../../../src/execution/tasks/utils';
import {MockLogger} from '../../../helpers/mock_logger';
import {createTasksAndGraph} from '../../helpers';
describe('ParallelTaskQueue', () => {
@ -36,7 +37,7 @@ describe('ParallelTaskQueue', () => {
const {tasks, graph} =
createTasksAndGraph(entryPointCount, tasksPerEntryPointCount, entryPointDeps);
const dependencies = computeTaskDependencies(tasks, graph);
return {tasks, queue: new ParallelTaskQueue(tasks.slice(), dependencies)};
return {tasks, queue: new ParallelTaskQueue(new MockLogger(), tasks.slice(), dependencies)};
}
/**

View File

@ -11,6 +11,7 @@ import {PartiallyOrderedTasks, Task, TaskQueue} from '../../../../src/execution/
import {SerialTaskQueue} from '../../../../src/execution/tasks/queues/serial_task_queue';
import {computeTaskDependencies} from '../../../../src/execution/tasks/utils';
import {EntryPoint} from '../../../../src/packages/entry_point';
import {MockLogger} from '../../../helpers/mock_logger';
describe('SerialTaskQueue', () => {
@ -38,7 +39,7 @@ describe('SerialTaskQueue', () => {
graph.addNode(entryPoint.path);
}
const dependencies = computeTaskDependencies(tasks, graph);
return {tasks, queue: new SerialTaskQueue(tasks.slice(), dependencies)};
return {tasks, queue: new SerialTaskQueue(new MockLogger(), tasks.slice(), dependencies)};
};
/**

View File

@ -1085,45 +1085,130 @@ runInEachFileSystem(() => {
});
describe('diagnostics', () => {
it('should fail with formatted diagnostics when an error diagnostic is produced', () => {
loadTestFiles([
{
name: _('/node_modules/fatal-error/package.json'),
contents: '{"name": "fatal-error", "es2015": "./index.js", "typings": "./index.d.ts"}',
},
{name: _('/node_modules/fatal-error/index.metadata.json'), contents: 'DUMMY DATA'},
{
name: _('/node_modules/fatal-error/index.js'),
contents: `
it('should fail with formatted diagnostics when an error diagnostic is produced, if targetEntryPointPath is provided',
() => {
loadTestFiles([
{
name: _('/node_modules/fatal-error/package.json'),
contents:
'{"name": "fatal-error", "es2015": "./index.js", "typings": "./index.d.ts"}',
},
{name: _('/node_modules/fatal-error/index.metadata.json'), contents: 'DUMMY DATA'},
{
name: _('/node_modules/fatal-error/index.js'),
contents: `
import {Component} from '@angular/core';
export class FatalError {}
FatalError.decorators = [
{type: Component, args: [{selector: 'fatal-error'}]}
];
`,
},
{
name: _('/node_modules/fatal-error/index.d.ts'),
contents: `
},
{
name: _('/node_modules/fatal-error/index.d.ts'),
contents: `
export declare class FatalError {}
`,
},
]);
},
]);
try {
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: 'fatal-error',
propertiesToConsider: ['es2015']
});
fail('should have thrown');
} catch (e) {
expect(e.message).toContain(
'Failed to compile entry-point fatal-error (es2015 as esm2015) due to compilation errors:');
expect(e.message).toContain('NG2001');
expect(e.message).toContain('component is missing a template');
}
});
try {
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: 'fatal-error',
propertiesToConsider: ['es2015']
});
fail('should have thrown');
} catch (e) {
expect(e.message).toContain(
'Failed to compile entry-point fatal-error (es2015 as esm2015) due to compilation errors:');
expect(e.message).toContain('NG2001');
expect(e.message).toContain('component is missing a template');
}
});
it('should not fail but log an error with formatted diagnostics when an error diagnostic is produced, if targetEntryPoint is not provided and errorOnFailedEntryPoint is false',
() => {
loadTestFiles([
{
name: _('/node_modules/fatal-error/package.json'),
contents:
'{"name": "fatal-error", "es2015": "./index.js", "typings": "./index.d.ts"}',
},
{name: _('/node_modules/fatal-error/index.metadata.json'), contents: 'DUMMY DATA'},
{
name: _('/node_modules/fatal-error/index.js'),
contents: `
import {Component} from '@angular/core';
export class FatalError {}
FatalError.decorators = [
{type: Component, args: [{selector: 'fatal-error'}]}
];`,
},
{
name: _('/node_modules/fatal-error/index.d.ts'),
contents: `export declare class FatalError {}`,
},
{
name: _('/node_modules/dependent/package.json'),
contents: '{"name": "dependent", "es2015": "./index.js", "typings": "./index.d.ts"}',
},
{name: _('/node_modules/dependent/index.metadata.json'), contents: 'DUMMY DATA'},
{
name: _('/node_modules/dependent/index.js'),
contents: `
import {Component} from '@angular/core';
import {FatalError} from 'fatal-error';
export class Dependent {}
Dependent.decorators = [
{type: Component, args: [{selector: 'dependent', template: ''}]}
];`,
},
{
name: _('/node_modules/dependent/index.d.ts'),
contents: `export declare class Dependent {}`,
},
{
name: _('/node_modules/independent/package.json'),
contents:
'{"name": "independent", "es2015": "./index.js", "typings": "./index.d.ts"}',
},
{name: _('/node_modules/independent/index.metadata.json'), contents: 'DUMMY DATA'},
{
name: _('/node_modules/independent/index.js'),
contents: `
import {Component} from '@angular/core';
export class Independent {}
Independent.decorators = [
{type: Component, args: [{selector: 'independent', template: ''}]}
];`,
},
{
name: _('/node_modules/independent/index.d.ts'),
contents: `export declare class Independent {}`,
},
]);
const logger = new MockLogger();
mainNgcc({
basePath: '/node_modules',
propertiesToConsider: ['es2015'],
errorOnFailedEntryPoint: false, logger,
});
expect(logger.logs.error.length).toEqual(1);
const message = logger.logs.error[0][0];
expect(message).toContain(
'Failed to compile entry-point fatal-error (es2015 as esm2015) due to compilation errors:');
expect(message).toContain('NG2001');
expect(message).toContain('component is missing a template');
expect(hasBeenProcessed(loadPackage('fatal-error', _('/node_modules')), 'es2015'))
.toBe(false);
expect(hasBeenProcessed(loadPackage('dependent', _('/node_modules')), 'es2015'))
.toBe(false);
expect(hasBeenProcessed(loadPackage('independent', _('/node_modules')), 'es2015'))
.toBe(true);
});
});
describe('logger', () => {