Skip to content

Commit 86aaf31

Browse files
Propagate reporter errors
1 parent 8f07a1c commit 86aaf31

File tree

7 files changed

+87
-62
lines changed

7 files changed

+87
-62
lines changed

packages/core/core/src/Parcel.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,8 @@ export default class Parcel {
152152
this.#disposable.add(() => this.#watchEvents.dispose());
153153

154154
this.#reporterRunner = new ReporterRunner({
155-
config: this.#config,
156155
options: resolvedOptions,
156+
reporters: await this.#config.getReporters(),
157157
workerFarm: this.#farm,
158158
});
159159
this.#disposable.add(this.#reporterRunner);
@@ -313,7 +313,7 @@ export default class Parcel {
313313
if (options.shouldTrace) {
314314
tracer.enable();
315315
}
316-
this.#reporterRunner.report({
316+
await this.#reporterRunner.report({
317317
type: 'buildStart',
318318
});
319319

@@ -401,6 +401,11 @@ export default class Parcel {
401401
createValidationRequest({optionsRef: this.#optionsRef, assetRequests}),
402402
{force: assetRequests.length > 0},
403403
);
404+
405+
if (this.#reporterRunner.errors.length) {
406+
throw this.#reporterRunner.errors;
407+
}
408+
404409
return event;
405410
} catch (e) {
406411
if (e instanceof BuildAbortError) {

packages/core/core/src/ReporterRunner.js

+28-34
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,24 @@ import BundleGraph from './BundleGraph';
2424
import {tracer, PluginTracer} from '@parcel/profiler';
2525

2626
type Opts = {|
27-
config: ParcelConfig,
2827
options: ParcelOptions,
28+
reporters: Array<LoadedPlugin<Reporter>>,
2929
workerFarm: WorkerFarm,
3030
|};
3131

3232
const instances: Set<ReporterRunner> = new Set();
3333

3434
export default class ReporterRunner {
3535
workerFarm: WorkerFarm;
36-
config: ParcelConfig;
36+
errors: Error[];
3737
options: ParcelOptions;
3838
pluginOptions: PluginOptions;
3939
reporters: Array<LoadedPlugin<Reporter>>;
4040

4141
constructor(opts: Opts) {
42-
this.config = opts.config;
42+
this.errors = [];
4343
this.options = opts.options;
44+
this.reporters = opts.reporters;
4445
this.workerFarm = opts.workerFarm;
4546
this.pluginOptions = new PluginOptions(this.options);
4647

@@ -86,40 +87,33 @@ export default class ReporterRunner {
8687
};
8788

8889
async report(event: ReporterEvent) {
89-
// We should catch all errors originating from reporter plugins to prevent infinite loops
90-
try {
91-
let reporters = this.reporters;
92-
if (!reporters) {
93-
this.reporters = await this.config.getReporters();
94-
reporters = this.reporters;
95-
}
96-
97-
for (let reporter of this.reporters) {
98-
let measurement;
99-
try {
100-
// To avoid an infinite loop we don't measure trace events, as they'll
101-
// result in another trace!
102-
if (event.type !== 'trace') {
103-
measurement = tracer.createMeasurement(reporter.name, 'reporter');
104-
}
105-
await reporter.plugin.report({
106-
event,
107-
options: this.pluginOptions,
108-
logger: new PluginLogger({origin: reporter.name}),
109-
tracer: new PluginTracer({
110-
origin: reporter.name,
111-
category: 'reporter',
112-
}),
113-
});
114-
} catch (reportError) {
90+
for (let reporter of this.reporters) {
91+
let measurement;
92+
try {
93+
// To avoid an infinite loop we don't measure trace events, as they'll
94+
// result in another trace!
95+
if (event.type !== 'trace') {
96+
measurement = tracer.createMeasurement(reporter.name, 'reporter');
97+
}
98+
await reporter.plugin.report({
99+
event,
100+
options: this.pluginOptions,
101+
logger: new PluginLogger({origin: reporter.name}),
102+
tracer: new PluginTracer({
103+
origin: reporter.name,
104+
category: 'reporter',
105+
}),
106+
});
107+
} catch (reportError) {
108+
if (event.type !== 'buildSuccess') {
109+
// This will be captured by consumers
115110
INTERNAL_ORIGINAL_CONSOLE.error(reportError);
116-
process.exitCode = 1;
117-
} finally {
118-
measurement && measurement.end();
119111
}
112+
113+
this.errors.push(reportError);
114+
} finally {
115+
measurement && measurement.end();
120116
}
121-
} catch (err) {
122-
INTERNAL_ORIGINAL_CONSOLE.error(err);
123117
}
124118
}
125119

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "@parcel/config-default",
3+
"reporters": ["./test-reporter"]
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export function main() {}

packages/core/integration-tests/test/integration/reporters-load-failure/yarn.lock

Whitespace-only changes.

packages/core/integration-tests/test/reporters.js

+46-25
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {execSync} from 'child_process';
55
import path from 'path';
66

77
import {INTERNAL_ORIGINAL_CONSOLE} from '@parcel/logger';
8-
import {bundle} from '@parcel/test-utils';
8+
import {bundler} from '@parcel/test-utils';
99
import sinon from 'sinon';
1010

1111
describe('reporters', () => {
@@ -16,7 +16,14 @@ describe('reporters', () => {
1616
'index.js',
1717
);
1818

19-
let failingEntry = path.join(
19+
let loadReporterFailureEntry = path.join(
20+
__dirname,
21+
'integration',
22+
'reporters-load-failure',
23+
'index.js',
24+
);
25+
26+
let failingReporterEntry = path.join(
2027
__dirname,
2128
'integration',
2229
'reporters-failure',
@@ -32,42 +39,56 @@ describe('reporters', () => {
3239
);
3340
});
3441

35-
it('exit with an error code when an error is emitted', () => {
42+
it('exit with an error code when a reporter fails to load', () => {
3643
assert.throws(() =>
37-
execSync(`parcel build --no-cache ${failingEntry}`, {stdio: 'ignore'}),
44+
execSync(`parcel build --no-cache ${loadReporterFailureEntry}`, {
45+
stdio: 'ignore',
46+
}),
47+
);
48+
});
49+
50+
it('exit with an error code when a reporter emits an error', () => {
51+
assert.throws(() =>
52+
execSync(`parcel build --no-cache ${failingReporterEntry}`, {
53+
stdio: 'ignore',
54+
}),
3855
);
3956
});
4057
});
4158

4259
describe('running on the programmatic api', () => {
43-
let consoleError;
44-
let processExitCode;
60+
it('resolves when no errors are emitted', async () => {
61+
let buildEvent = await bundler(successfulEntry).run();
4562

46-
beforeEach(() => {
47-
processExitCode = process.exitCode;
48-
consoleError = sinon.stub(INTERNAL_ORIGINAL_CONSOLE, 'error');
63+
assert.equal(buildEvent.type, 'buildSuccess');
4964
});
5065

51-
afterEach(() => {
52-
process.exitCode = processExitCode;
53-
sinon.restore();
54-
});
55-
56-
it('exit successfully when no errors are emitted', async () => {
57-
await bundle(successfulEntry);
66+
it('rejects when a reporter fails to load', async () => {
67+
try {
68+
let buildEvent = await bundler(loadReporterFailureEntry).run();
5869

59-
assert(!process.exitCode);
70+
throw new Error(buildEvent);
71+
} catch (err) {
72+
assert.equal(err.name, 'Error');
73+
assert.deepEqual(
74+
err.diagnostics.map(d => d.message),
75+
['Cannot find Parcel plugin "./test-reporter"'],
76+
);
77+
}
6078
});
6179

62-
it('exit with an error code when an error is emitted', async () => {
63-
await bundle(failingEntry);
80+
it('rejects when a reporter emits an error', async () => {
81+
try {
82+
let buildEvent = await bundler(failingReporterEntry).run();
6483

65-
assert.equal(process.exitCode, 1);
66-
assert(
67-
consoleError.calledWithMatch({
68-
message: 'Failed to report buildSuccess',
69-
}),
70-
);
84+
throw new Error(buildEvent);
85+
} catch (err) {
86+
assert.equal(err.name, 'BuildError');
87+
assert.deepEqual(
88+
err.diagnostics.map(d => d.message),
89+
['Failed to report buildSuccess'],
90+
);
91+
}
7192
});
7293
});
7394
});

packages/core/parcel/src/cli.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ async function run(
404404
await exit(1);
405405
}
406406

407-
await exit(process.exitCode);
407+
await exit();
408408
}
409409
}
410410

0 commit comments

Comments
 (0)