import type {ReporterEvent, Reporter} from '@atlaspack/types'; import type {WorkerApi} from '@atlaspack/workers'; import type {Bundle as InternalBundle, AtlaspackOptions} from './types'; import type {LoadedPlugin} from './AtlaspackConfig'; import invariant from 'assert'; import { bundleToInternalBundle, bundleToInternalBundleGraph, NamedBundle, } from './public/Bundle'; import WorkerFarm, {bus} from '@atlaspack/workers'; import logger, { patchConsole, unpatchConsole, PluginLogger, INTERNAL_ORIGINAL_CONSOLE, } from '@atlaspack/logger'; import PluginOptions from './public/PluginOptions'; import BundleGraph from './BundleGraph'; import {tracer, PluginTracer} from '@atlaspack/profiler'; import {anyToDiagnostic} from '@atlaspack/diagnostic'; type Opts = { options: AtlaspackOptions; reporters: Array>; workerFarm: WorkerFarm; }; const instances: Set = new Set(); export default class ReporterRunner { workerFarm: WorkerFarm; errors: Error[]; options: AtlaspackOptions; pluginOptions: PluginOptions; reporters: Array>; constructor(opts: Opts) { this.errors = []; this.options = opts.options; this.reporters = opts.reporters; this.workerFarm = opts.workerFarm; this.pluginOptions = new PluginOptions(this.options); logger.onLog((event) => this.report(event)); tracer.onTrace((event) => this.report(event)); bus.on('reporterEvent', this.eventHandler); instances.add(this); if (this.options.shouldPatchConsole) { patchConsole(); } else { unpatchConsole(); } } eventHandler: (arg1: ReporterEvent) => void = (event): void => { if ( event.type === 'buildProgress' && (event.phase === 'optimizing' || event.phase === 'packaging') && !(event.bundle instanceof NamedBundle) ) { // @ts-expect-error TS2339 let bundleGraphRef = event.bundleGraphRef; // @ts-expect-error TS2739 let bundle: InternalBundle = event.bundle; // Convert any internal bundles back to their public equivalents as reporting // is public api let bundleGraph = this.workerFarm.workerApi.getSharedReference(bundleGraphRef); invariant(bundleGraph instanceof BundleGraph); this.report({ ...event, bundle: NamedBundle.get(bundle, bundleGraph, this.options), }); return; } this.report(event); }; async report(unsanitisedEvent: ReporterEvent) { let event: ReporterEvent = unsanitisedEvent; // @ts-expect-error TS2339 if (event.diagnostics) { // Sanitise input before passing to reporters event = { ...event, // @ts-expect-error TS2322 diagnostics: anyToDiagnostic(event.diagnostics), }; } for (let reporter of this.reporters) { let measurement; try { // To avoid an infinite loop we don't measure trace events, as they'll // result in another trace! if (event.type !== 'trace') { measurement = tracer.createMeasurement(reporter.name, 'reporter'); } await reporter.plugin.report({ event, options: this.pluginOptions, logger: new PluginLogger({origin: reporter.name}), tracer: new PluginTracer({ origin: reporter.name, category: 'reporter', }), }); } catch (reportError: any) { if (event.type !== 'buildSuccess') { // This will be captured by consumers INTERNAL_ORIGINAL_CONSOLE.error(reportError); } this.errors.push(reportError); } finally { measurement && measurement.end(); } } } dispose() { bus.off('reporterEvent', this.eventHandler); instances.delete(this); } } export function reportWorker(workerApi: WorkerApi, event: ReporterEvent) { if ( event.type === 'buildProgress' && (event.phase === 'optimizing' || event.phase === 'packaging') ) { // Convert any public api bundles to their internal equivalents for // easy serialization bus.emit('reporterEvent', { ...event, bundle: bundleToInternalBundle(event.bundle), bundleGraphRef: workerApi.resolveSharedReference( bundleToInternalBundleGraph(event.bundle), ), }); return; } bus.emit('reporterEvent', event); } export async function report(event: ReporterEvent): Promise { await Promise.all([...instances].map((instance) => instance.report(event))); }