1import type { ReportableEvent } from 'metro';
2import type { TerminalReportableEvent } from 'metro/src/lib/TerminalReporter';
3import type { Terminal } from 'metro-core';
4
5export type GlobalCacheDisabledReason = 'too_many_errors' | 'too_many_misses';
6
7export type BundleDetails = {
8  buildID?: string;
9  bundleType: string;
10  dev: boolean;
11  entryFile: string;
12  minify: boolean;
13  platform: string | null | undefined;
14  customTransformOptions?: { environment?: 'node' };
15  runtimeBytecodeVersion: number | null | undefined;
16};
17
18export type BundleProgress = {
19  bundleDetails: BundleDetails;
20  transformedFileCount: number;
21  totalFileCount: number;
22  ratio: number;
23};
24
25export { TerminalReportableEvent };
26
27export type BuildPhase = 'in_progress' | 'done' | 'failed';
28
29/**
30 * Code across the application takes a reporter as an option and calls the
31 * update whenever one of the ReportableEvent happens. Code does not directly
32 * write to the standard output, because a build would be:
33 *
34 *   1. ad-hoc, embedded into another tool, in which case we do not want to
35 *   pollute that tool's own output. The tool is free to present the
36 *   warnings/progress we generate any way they want, by specifying a custom
37 *   reporter.
38 *   2. run as a background process from another tool, in which case we want
39 *   to expose updates in a way that is easily machine-readable, for example
40 *   a JSON-stream. We don't want to pollute it with textual messages.
41 *
42 * We centralize terminal reporting into a single place because we want the
43 * output to be robust and consistent. The most common reporter is
44 * TerminalReporter, that should be the only place in the application should
45 * access the `terminal` module (nor the `console`).
46 */
47export type Reporter = { update(event: ReportableEvent): void };
48
49export interface SnippetError extends Error {
50  code?: string;
51  filename?: string;
52  snippet?: string;
53
54  /** Module that failed to load ex 'fs' */
55  targetModuleName?: string;
56  originModulePath?: string;
57
58  errors?: any[];
59}
60
61export interface TerminalReporterInterface {
62  new (terminal: Terminal): TerminalReporterInterface;
63
64  /**
65   * The bundle builds for which we are actively maintaining the status on the
66   * terminal, ie. showing a progress bar. There can be several bundles being
67   * built at the same time.
68   */
69  _activeBundles: Map<string, BundleProgress>;
70
71  _scheduleUpdateBundleProgress: {
72    (data: { buildID: string; transformedFileCount: number; totalFileCount: number }): void;
73    cancel(): void;
74  };
75
76  /** Set in super type */
77  terminal: Terminal;
78
79  /**
80   * Construct a message that represents the progress of a
81   * single bundle build, for example:
82   *
83   *     BUNDLE path/to/bundle.js ▓▓▓▓▓░░░░░░░░░░░ 36.6% (4790/7922)
84   */
85  _getBundleStatusMessage(
86    {
87      bundleDetails: { entryFile, bundleType, runtimeBytecodeVersion },
88      transformedFileCount,
89      totalFileCount,
90      ratio,
91    }: BundleProgress,
92    phase: BuildPhase
93  ): string;
94
95  /**
96   * This function is only concerned with logging and should not do state
97   * or terminal status updates.
98   */
99  _log(event: TerminalReportableEvent): void;
100
101  _logCacheDisabled(reason: GlobalCacheDisabledReason): void;
102
103  _logBundleBuildDone(buildID: string): void;
104
105  _logBundleBuildFailed(buildID: string): void;
106
107  _logInitializing(port: number, hasReducedPerformance: boolean): void;
108
109  _logInitializingFailed(port: number, error: SnippetError): void;
110
111  /**
112   * We do not want to log the whole stacktrace for bundling error, because
113   * these are operational errors, not programming errors, and the stacktrace
114   * is not actionable to end users.
115   */
116  _logBundlingError(error: SnippetError): void;
117
118  /**
119   * We use Math.pow(ratio, 2) to as a conservative measure of progress because
120   * we know the `totalCount` is going to progressively increase as well. We
121   * also prevent the ratio from going backwards.
122   */
123  _updateBundleProgress({
124    buildID,
125    transformedFileCount,
126    totalFileCount,
127  }: {
128    buildID: string;
129    transformedFileCount: number;
130    totalFileCount: number;
131  }): void;
132
133  /**
134   * This function is exclusively concerned with updating the internal state.
135   * No logging or status updates should be done at this point.
136   */
137  _updateState(event: TerminalReportableEvent): void;
138  /**
139   * Return a status message that is always consistent with the current state
140   * of the application. Having this single function ensures we don't have
141   * different call sites overriding each other status messages.
142   */
143  _getStatusMessage(): string;
144
145  _logHmrClientError(e: Error): void;
146
147  /**
148   * Single entry point for reporting events. That allows us to implement the
149   * corresponding JSON reporter easily and have a consistent reporting.
150   */
151  update(event: TerminalReportableEvent): void;
152}
153