Skip to content

Commit a249deb

Browse files
Exit process with error code when reporters fail (#9735)
* Exit process when reporters fail * Add cli handling * Propagate reporter errors
1 parent e825d59 commit a249deb

File tree

16 files changed

+166
-36
lines changed

16 files changed

+166
-36
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
@@ -12,7 +12,6 @@ import {
1212
NamedBundle,
1313
} from './public/Bundle';
1414
import WorkerFarm, {bus} from '@parcel/workers';
15-
import ParcelConfig from './ParcelConfig';
1615
import logger, {
1716
patchConsole,
1817
unpatchConsole,
@@ -24,23 +23,24 @@ import BundleGraph from './BundleGraph';
2423
import {tracer, PluginTracer} from '@parcel/profiler';
2524

2625
type Opts = {|
27-
config: ParcelConfig,
2826
options: ParcelOptions,
27+
reporters: Array<LoadedPlugin<Reporter>>,
2928
workerFarm: WorkerFarm,
3029
|};
3130

3231
const instances: Set<ReporterRunner> = new Set();
3332

3433
export default class ReporterRunner {
3534
workerFarm: WorkerFarm;
36-
config: ParcelConfig;
35+
errors: Error[];
3736
options: ParcelOptions;
3837
pluginOptions: PluginOptions;
3938
reporters: Array<LoadedPlugin<Reporter>>;
4039

4140
constructor(opts: Opts) {
42-
this.config = opts.config;
41+
this.errors = [];
4342
this.options = opts.options;
43+
this.reporters = opts.reporters;
4444
this.workerFarm = opts.workerFarm;
4545
this.pluginOptions = new PluginOptions(this.options);
4646

@@ -86,39 +86,33 @@ export default class ReporterRunner {
8686
};
8787

8888
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) {
89+
for (let reporter of this.reporters) {
90+
let measurement;
91+
try {
92+
// To avoid an infinite loop we don't measure trace events, as they'll
93+
// result in another trace!
94+
if (event.type !== 'trace') {
95+
measurement = tracer.createMeasurement(reporter.name, 'reporter');
96+
}
97+
await reporter.plugin.report({
98+
event,
99+
options: this.pluginOptions,
100+
logger: new PluginLogger({origin: reporter.name}),
101+
tracer: new PluginTracer({
102+
origin: reporter.name,
103+
category: 'reporter',
104+
}),
105+
});
106+
} catch (reportError) {
107+
if (event.type !== 'buildSuccess') {
108+
// This will be captured by consumers
115109
INTERNAL_ORIGINAL_CONSOLE.error(reportError);
116-
} finally {
117-
measurement && measurement.end();
118110
}
111+
112+
this.errors.push(reportError);
113+
} finally {
114+
measurement && measurement.end();
119115
}
120-
} catch (err) {
121-
INTERNAL_ORIGINAL_CONSOLE.error(err);
122116
}
123117
}
124118

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() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const { Reporter } = require('@parcel/plugin');
2+
3+
module.exports = new Reporter({
4+
async report({ event }) {
5+
if (event.type === 'buildSuccess') {
6+
throw new Error('Failed to report buildSuccess');
7+
}
8+
}
9+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "test-reporter",
3+
"version": "1.0.0",
4+
"main": "index.js"
5+
}

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

Whitespace-only changes.
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.
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() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const { Reporter } = require('@parcel/plugin');
2+
3+
module.exports = new Reporter({
4+
async report({ event }) {}
5+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "test-reporter",
3+
"version": "1.0.0",
4+
"main": "index.js"
5+
}

packages/core/integration-tests/test/integration/reporters-success/yarn.lock

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// @flow
2+
3+
import assert from 'assert';
4+
import {execSync} from 'child_process';
5+
import path from 'path';
6+
7+
import {bundler} from '@parcel/test-utils';
8+
9+
describe('reporters', () => {
10+
let successfulEntry = path.join(
11+
__dirname,
12+
'integration',
13+
'reporters-success',
14+
'index.js',
15+
);
16+
17+
let loadReporterFailureEntry = path.join(
18+
__dirname,
19+
'integration',
20+
'reporters-load-failure',
21+
'index.js',
22+
);
23+
24+
let failingReporterEntry = path.join(
25+
__dirname,
26+
'integration',
27+
'reporters-failure',
28+
'index.js',
29+
);
30+
31+
describe('running on the cli', () => {
32+
it('exit successfully when no errors are emitted', () => {
33+
assert.doesNotThrow(() =>
34+
execSync(`parcel build --no-cache ${successfulEntry}`, {
35+
stdio: 'ignore',
36+
}),
37+
);
38+
});
39+
40+
it('exit with an error code when a reporter fails to load', () => {
41+
assert.throws(() =>
42+
execSync(`parcel build --no-cache ${loadReporterFailureEntry}`, {
43+
stdio: 'ignore',
44+
}),
45+
);
46+
});
47+
48+
it('exit with an error code when a reporter emits an error', () => {
49+
assert.throws(() =>
50+
execSync(`parcel build --no-cache ${failingReporterEntry}`, {
51+
stdio: 'ignore',
52+
}),
53+
);
54+
});
55+
});
56+
57+
describe('running on the programmatic api', () => {
58+
it('resolves when no errors are emitted', async () => {
59+
let buildEvent = await bundler(successfulEntry).run();
60+
61+
assert.equal(buildEvent.type, 'buildSuccess');
62+
});
63+
64+
it('rejects when a reporter fails to load', async () => {
65+
try {
66+
let buildEvent = await bundler(loadReporterFailureEntry).run();
67+
68+
throw new Error(buildEvent);
69+
} catch (err) {
70+
assert.equal(err.name, 'Error');
71+
assert.deepEqual(
72+
err.diagnostics.map(d => d.message),
73+
['Cannot find Parcel plugin "./test-reporter"'],
74+
);
75+
}
76+
});
77+
78+
it('rejects when a reporter emits an error', async () => {
79+
try {
80+
let buildEvent = await bundler(failingReporterEntry).run();
81+
82+
throw new Error(buildEvent);
83+
} catch (err) {
84+
assert.equal(err.name, 'BuildError');
85+
assert.deepEqual(
86+
err.diagnostics.map(d => d.message),
87+
['Failed to report buildSuccess'],
88+
);
89+
}
90+
});
91+
});
92+
});

0 commit comments

Comments
 (0)