Skip to content

Commit ef59d40

Browse files
committed
reporter updates to simplify things a bit
1 parent faf76cf commit ef59d40

File tree

10 files changed

+628
-602
lines changed

10 files changed

+628
-602
lines changed

incubator/reporter/eslint.config.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1 @@
1-
import config from "@rnx-kit/eslint-config";
2-
3-
// eslint-disable-next-line no-restricted-exports
4-
export default config;
1+
module.exports = require("@rnx-kit/eslint-config");

incubator/reporter/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"description": "EXPERIMENTAL - USE WITH CAUTION - reporter",
55
"homepage": "https://github.com/microsoft/rnx-kit/tree/main/incubator/reporter#readme",
66
"license": "MIT",
7+
"main": "lib/index.js",
8+
"types": "lib/index.d.ts",
79
"author": {
810
"name": "Microsoft Open Source",
911
"email": "microsoftopensource@users.noreply.github.com"

incubator/reporter/src/events.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { channel, subscribe, unsubscribe } from "node:diagnostics_channel";
2+
import type { ErrorEvent, SessionData } from "./types";
3+
4+
/** @internal */
5+
export const errorEvent = createEventHandler<ErrorEvent>("rnx-reporter:errors");
6+
7+
/** @internal */
8+
export const startEvent = createEventHandler<SessionData>("rnx-reporter:start");
9+
10+
/** @internal */
11+
export const finishEvent = createEventHandler<SessionData>("rnx-reporter:end");
12+
13+
/**
14+
* Typed wrapper around managing events sent through node's diagnostics channel
15+
* @param name friendly name of the event, will be turned into a symbol for containment
16+
* @returns a set of functions that can be used to listen for and send events
17+
*/
18+
export function createEventHandler<T>(name: string) {
19+
const eventName = Symbol(name);
20+
const eventChannel = channel(eventName);
21+
22+
return {
23+
subscribe: (callback: (event: T) => void) => {
24+
const handler = (event: unknown, name: symbol | string) => {
25+
if (name === eventName) {
26+
callback(event as T);
27+
}
28+
};
29+
subscribe(eventName, handler);
30+
return () => unsubscribe(eventName, handler);
31+
},
32+
hasSubscribers: () => eventChannel.hasSubscribers,
33+
publish: (event: T) => eventChannel.publish(event),
34+
};
35+
}
36+
37+
/**
38+
* Listen for reporter start events, sent when reporters or tasks are initiated
39+
* @param callback called when an error event is sent
40+
* @returns a function to unsubscribe from the event
41+
*/
42+
export function subscribeToStart(callback: (event: SessionData) => void) {
43+
return startEvent.subscribe(callback);
44+
}
45+
46+
/**
47+
* Listen for reporter finish events, sent when reporters or tasks are completed
48+
* @param callback called when an error event is sent
49+
* @returns a function to unsubscribe from the event
50+
*/
51+
export function subscribeToFinish(callback: (event: SessionData) => void) {
52+
return finishEvent.subscribe(callback);
53+
}
54+
55+
/**
56+
* Listen for reporter error events
57+
* @param callback called when an error event is sent
58+
* @returns a function to unsubscribe from the event
59+
*/
60+
export function subscribeToError(callback: (event: ErrorEvent) => void) {
61+
return errorEvent.subscribe(callback);
62+
}

incubator/reporter/src/formatting.ts

Lines changed: 147 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,153 @@
1-
import { stripVTControlCharacters } from "node:util";
1+
import chalk from "chalk";
2+
import {
3+
inspect,
4+
type InspectOptions,
5+
stripVTControlCharacters,
6+
} from "node:util";
7+
import { allLogLevels } from "./levels.ts";
28
import type {
39
ColorSettings,
410
DeepPartial,
511
FormatHelper,
6-
ReporterSettings,
12+
FormattingOptions,
13+
FormattingSettings,
714
} from "./types.ts";
815

916
export function noChange<T>(arg: T) {
1017
return arg;
1118
}
1219

13-
function asObject<T extends object>(item: unknown): T | undefined {
14-
if (item && typeof item === "object" && !Array.isArray(item)) {
15-
return item as T;
20+
export type Formatting = FormattingSettings & {
21+
format: FormatHelper;
22+
};
23+
24+
const defaultFormatSettings: FormattingSettings = {
25+
inspectOptions: {
26+
colors: true,
27+
depth: 2,
28+
compact: true,
29+
},
30+
colors: {
31+
error: { prefix: chalk.red.bold },
32+
warn: { prefix: chalk.yellowBright.bold },
33+
log: {},
34+
verbose: { text: chalk.dim },
35+
labels: chalk.bold,
36+
pkgName: chalk.bold.cyan,
37+
pkgScope: chalk.bold.blue,
38+
path: chalk.blue,
39+
duration: chalk.green,
40+
durationUnits: chalk.greenBright,
41+
},
42+
prefixes: {
43+
error: "ERROR: ⛔",
44+
warn: "WARNING: ⚠️",
45+
},
46+
};
47+
48+
const defaultFormatting: Formatting = {
49+
...defaultFormatSettings,
50+
format: createFormatHelper(defaultFormatSettings),
51+
};
52+
53+
export function getFormatting(
54+
overrides?: FormattingOptions,
55+
baseline: Formatting = defaultFormatting
56+
): Formatting {
57+
if (overrides) {
58+
const { colors, inspectOptions, prefixes } = overrides;
59+
const rebuildFormat = colors || inspectOptions;
60+
61+
// if settings have changed, create a new formatting object
62+
if (rebuildFormat || prefixes) {
63+
const result = {
64+
colors: mergeColors(baseline.colors, colors),
65+
inspectOptions: mergeInspectOptions(
66+
baseline.inspectOptions,
67+
inspectOptions
68+
),
69+
prefixes: mergePrefixes(baseline.prefixes, prefixes),
70+
format: baseline.format,
71+
};
72+
// update the format helper if needed, otherwise carry it forward
73+
if (rebuildFormat) {
74+
result.format = createFormatHelper(result);
75+
}
76+
return result;
77+
}
1678
}
17-
return undefined;
79+
return baseline;
1880
}
1981

20-
/**
21-
* Deep merges two objects, applying the source object properties to the target object.
22-
* @param target the object to merge into
23-
* @param source the object to merge from
24-
* @param immutable if true, the target object will not be modified, instead a new object will be returned
25-
* @returns either target with applied updates (if !immutable) or a new object with the merged settings
26-
*/
27-
export function mergeSettings<T extends object>(
28-
target: T,
29-
source?: DeepPartial<T>,
30-
immutable?: boolean
31-
): T {
32-
if (!source) {
33-
return target;
34-
}
35-
if (immutable) {
36-
target = { ...target };
82+
export function updateDefaultFormatting(options?: FormattingOptions) {
83+
const newDefault = getFormatting(options);
84+
if (newDefault !== defaultFormatting) {
85+
defaultFormatting.colors = newDefault.colors;
86+
defaultFormatting.inspectOptions = newDefault.inspectOptions;
87+
defaultFormatting.prefixes = newDefault.prefixes;
88+
defaultFormatting.format = newDefault.format;
3789
}
38-
for (const key in source) {
39-
if (source[key] !== undefined) {
40-
const objValue = asObject(source[key]);
41-
const objTarget = asObject(target[key]);
42-
if (objValue && objTarget) {
43-
target[key] = mergeSettings(
44-
objTarget,
45-
objValue,
46-
immutable
47-
) as T[Extract<keyof T, string>];
48-
} else {
49-
target[key] = source[key] as T[Extract<keyof T, string>];
90+
}
91+
92+
export function disableColors() {
93+
const disableMsgType = { text: stripVTControlCharacters };
94+
updateDefaultFormatting({
95+
inspectOptions: { colors: false },
96+
colors: {
97+
error: disableMsgType,
98+
warn: disableMsgType,
99+
log: disableMsgType,
100+
verbose: disableMsgType,
101+
},
102+
});
103+
}
104+
105+
export function defaultColors(): Readonly<ColorSettings> {
106+
return defaultFormatting.colors;
107+
}
108+
109+
export function defaultFormat(): Readonly<FormatHelper> {
110+
return defaultFormatting.format;
111+
}
112+
113+
function mergePrefixes(
114+
base: FormattingSettings["prefixes"],
115+
override?: Partial<FormattingSettings["prefixes"]>
116+
) {
117+
return override ? { ...base, ...override } : base;
118+
}
119+
120+
function mergeInspectOptions(
121+
base: InspectOptions,
122+
overrides?: Partial<InspectOptions>
123+
): InspectOptions {
124+
return overrides ? { ...base, ...overrides } : base;
125+
}
126+
127+
function mergeColors(
128+
base: ColorSettings,
129+
overrides?: DeepPartial<ColorSettings>
130+
): ColorSettings {
131+
if (overrides) {
132+
const result = { ...base, ...overrides };
133+
for (const level of allLogLevels) {
134+
if (overrides[level]) {
135+
result[level] = { ...base[level], ...overrides[level] };
50136
}
51137
}
52138
}
53-
return target;
139+
return base;
54140
}
55141

56-
const emptyMsgColors = { text: stripVTControlCharacters };
57-
58-
export const disableColorOptions: DeepPartial<ReporterSettings> = {
59-
inspectOptions: { colors: false },
60-
color: {
61-
message: {
62-
default: emptyMsgColors,
63-
error: emptyMsgColors,
64-
warn: emptyMsgColors,
65-
log: emptyMsgColors,
66-
verbose: emptyMsgColors,
67-
},
68-
},
69-
};
70-
71-
export function createFormatHelper(colorSettings: ColorSettings): FormatHelper {
72-
const { path: colorPath } = colorSettings;
142+
export function createFormatHelper(settings: FormattingSettings): FormatHelper {
143+
const { colors, inspectOptions } = settings;
73144
return {
74-
packageFull: (pkg: string) => formatPackageName(colorSettings, pkg),
145+
packageFull: (pkg: string) => formatPackageName(colors, pkg),
75146
packageParts: (name: string, scope?: string) =>
76-
formatPackageParts(colorSettings, name, scope),
77-
path: (pathValue: string) => colorPath(pathValue),
78-
duration: (time: number) => formatDuration(colorSettings, time),
147+
formatPackageParts(colors, name, scope),
148+
path: (pathValue: string) => colors.path(pathValue),
149+
duration: (time: number) => formatDuration(colors, time),
150+
serialize: (args: unknown[]) => serializeArgs(inspectOptions, args),
79151
};
80152
}
81153

@@ -123,3 +195,21 @@ function formatPackageParts(
123195
const { pkgName = noChange, pkgScope = noChange } = colors;
124196
return scope ? `${pkgScope(scope)}/${pkgName(name)}` : pkgName(name);
125197
}
198+
199+
/**
200+
* @param inspectOptions options for node:util.inspect, used to format the arguments, same as console.log
201+
* @param args args list to serialize
202+
* @returns a single string with arguments joined together with spaces, terminated with a newline
203+
*/
204+
export function serializeArgs(
205+
inspectOptions: InspectOptions,
206+
args: unknown[]
207+
): string {
208+
let msg = args
209+
.map((arg) =>
210+
typeof arg === "string" ? arg : inspect(arg, inspectOptions)
211+
)
212+
.join(" ");
213+
msg += "\n";
214+
return msg;
215+
}

incubator/reporter/src/index.ts

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,31 @@
11
import { checkPerformanceEnv } from "./performance.ts";
22
import { ReporterImpl } from "./reporter.ts";
3-
import type {
4-
DeepPartial,
5-
Reporter,
6-
ReporterOptions,
7-
ReporterSettings,
8-
} from "./types.ts";
3+
import type { CustomData, Reporter, ReporterOptions } from "./types.ts";
94

5+
export {
6+
subscribeToError,
7+
subscribeToFinish,
8+
subscribeToStart,
9+
} from "./events.ts";
10+
export {
11+
defaultColors,
12+
defaultFormat,
13+
disableColors,
14+
updateDefaultFormatting,
15+
} from "./formatting.ts";
1016
export { enablePerformanceTracing } from "./performance.ts";
11-
export { onCompleteEvent, onErrorEvent, onStartEvent } from "./reporter.ts";
1217
export type {
13-
CompleteEvent,
1418
ErrorEvent,
15-
EventSource,
19+
LogLevel,
1620
Reporter,
1721
ReporterOptions,
18-
ReporterSettings,
22+
SessionData,
1923
TaskOptions,
2024
} from "./types.ts";
2125

22-
export function createReporter(options: ReporterOptions): Reporter {
26+
export function createReporter<T extends CustomData = CustomData>(
27+
options: ReporterOptions<T>
28+
): Reporter<T> {
2329
checkPerformanceEnv();
24-
return new ReporterImpl(options);
25-
}
26-
27-
export function updateDefaultReporterSettings(
28-
options: DeepPartial<ReporterSettings>
29-
): void {
30-
ReporterImpl.updateDefaults(options);
31-
}
32-
33-
export function disableColors(): void {
34-
ReporterImpl.disableColors();
30+
return new ReporterImpl<T>(options);
3531
}

0 commit comments

Comments
 (0)