@reporters/mux 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ ## [1.1.0](https://github.com/MoLow/reporters/compare/mux-v1.0.0...mux-v1.1.0) (2026-07-02)
4
+
5
+
6
+ ### Features
7
+
8
+ * **mux:** start every sink before reporters consume events ([#221](https://github.com/MoLow/reporters/issues/221)) ([c5ef14f](https://github.com/MoLow/reporters/commit/c5ef14fbe0357a4f756fb170f69876396806abd6))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **web:** stay a pure emitter under mux via symbol-declared default options ([#217](https://github.com/MoLow/reporters/issues/217)) ([96a7d14](https://github.com/MoLow/reporters/commit/96a7d14e82f69c6dc6d8d363645f94c09733fbda))
14
+
15
+ ## 1.0.0 (2026-07-02)
16
+
17
+
18
+ ### Features
19
+
20
+ * @reporters/mux environment-aware reporter routing + web NDJSON redesign ([#203](https://github.com/MoLow/reporters/issues/203)) ([82d1e20](https://github.com/MoLow/reporters/commit/82d1e205949e6daa90b13aec5838ddb0c7e931a5))
21
+ * @reporters/sink — gist + s3 delivery sinks for viewing CI runs ([#211](https://github.com/MoLow/reporters/issues/211)) ([4b254b6](https://github.com/MoLow/reporters/commit/4b254b629236393b25c5903a976dbebfaf5ebc1a))
22
+
23
+
24
+ ### Documentation
25
+
26
+ * flag-order fixes and the gh collapsible actions log ([#216](https://github.com/MoLow/reporters/issues/216)) ([131f410](https://github.com/MoLow/reporters/commit/131f4107712d6d0b899d3b8ca7a39496b6e05276))
27
+ * rewrite package READMEs with demos and clearer positioning ([#209](https://github.com/MoLow/reporters/issues/209)) ([e1265e5](https://github.com/MoLow/reporters/commit/e1265e5f6b8a0f34b80ef7ee725cede4ed16b6da))
28
+
29
+
30
+ ### Miscellaneous Chores
31
+
32
+ * route this repo's tests through @reporters/mux ([#207](https://github.com/MoLow/reporters/issues/207)) ([85a5973](https://github.com/MoLow/reporters/commit/85a59737bc3d631b0293edf343b77fd82e0c459d))
package/README.md CHANGED
@@ -60,7 +60,12 @@ Transform-stream reporters (like `@reporters/gh`) are supported as route reporte
60
60
  `@reporters/web` accepts `{ open: boolean }`); most reporters need none.
61
61
  - `sink` — `'stdout'`, `'stderr'`, a file path, or a `Sink` object.
62
62
  - `open` — open the sink's viewer URL in a browser. Defaults to on locally, off
63
- in CI. `open: false` opts out; `REPORTERS_OPEN=1|0` forces it.
63
+ in CI. `open: false` opts out; `REPORTERS_OPEN=1|0` forces it. This is the
64
+ only open gate: a reporter can declare default options for when it runs under
65
+ mux — attached to the reporter function under
66
+ `Symbol.for('reporters.mux.defaultOptions')`, merged beneath the route's
67
+ `options` — which is how reporters that self-open standalone (like
68
+ `@reporters/web`, declaring `{ open: false }`) stay pure emitters here.
64
69
 
65
70
  ### Profile resolution
66
71
 
@@ -95,3 +100,22 @@ Built in: `'stdout'`, `'stderr'`, and file paths. Beyond those:
95
100
 
96
101
  When a sink exposes a `viewerUrl`, mux prints `report at <url>` to stderr and —
97
102
  on GitHub Actions — adds a **View report** link to the job summary.
103
+
104
+ Reporters can consume a sink's viewer URL too: keep the sink instance and pass
105
+ a lazy getter into another route's `options`. Every sink in the profile has
106
+ started before any reporter receives events, so the getter is live from the
107
+ first event on (note `options` reach function reporters only — stream
108
+ reporters have no options channel):
109
+
110
+ ```js
111
+ import { s3 } from '@reporters/sink';
112
+
113
+ const upload = s3({ bucket: 'ci-runs' });
114
+
115
+ export default {
116
+ ci: [
117
+ { reporter: '@reporters/web', sink: upload },
118
+ { reporter: './my-reporter.mjs', options: { viewerUrl: () => upload.viewerUrl() }, sink: 'stdout' },
119
+ ],
120
+ };
121
+ ```
package/dist/index.d.ts CHANGED
@@ -40,7 +40,12 @@ interface Sink {
40
40
  flush?(): Promise<void>;
41
41
  /** The run ended; release resources. */
42
42
  close(): Promise<void>;
43
- /** How a human views what was written, if anything. */
43
+ /**
44
+ * How a human views what was written, if anything. Valid once `start()` has
45
+ * resolved; under mux, every sink in the profile has started before any
46
+ * reporter receives events, so a getter wired into another route's
47
+ * `options` is live from the first event.
48
+ */
44
49
  viewerUrl?(): string | undefined;
45
50
  }
46
51
  type SinkSpec = string | Sink;
@@ -61,6 +66,13 @@ interface Route {
61
66
  /** Map of profile name -> the routes active under that profile. */
62
67
  type MuxConfig = Record<string, Route[]>;
63
68
 
69
+ /**
70
+ * Well-known key under which a reporter function declares the options it wants
71
+ * by default when driven through mux (a route's own `options` win key-by-key).
72
+ * Lets a reporter behave differently under mux without env coupling — e.g.
73
+ * `@reporters/web` declares `{ open: false }` so the sink owns viewing.
74
+ */
75
+ declare const MUX_DEFAULT_OPTIONS: unique symbol;
64
76
  /** A reporter function, a stream instance, or a module specifier to `import()`. */
65
77
  declare function resolveReporter(reporter: Reporter | Duplex | string): Promise<Reporter | Duplex>;
66
78
  /**
@@ -76,4 +88,4 @@ declare function runRoutes(source: AsyncIterable<TestEvent>, config: MuxConfig,
76
88
  */
77
89
  declare function mux(source: AsyncIterable<TestEvent>): AsyncGenerator<string>;
78
90
 
79
- export { type MuxConfig, type Reporter, type Route, type Sink, type SinkSpec, mux as default, resolveReporter, runRoutes };
91
+ export { MUX_DEFAULT_OPTIONS, type MuxConfig, type Reporter, type Route, type Sink, type SinkSpec, mux as default, resolveReporter, runRoutes };
package/dist/index.js CHANGED
@@ -183,6 +183,12 @@ function announce(url, env = process.env) {
183
183
  var internals = { openInBrowser, announce };
184
184
 
185
185
  // src/index.ts
186
+ var MUX_DEFAULT_OPTIONS = /* @__PURE__ */ Symbol.for("reporters.mux.defaultOptions");
187
+ function routeOptions(reporter, route) {
188
+ const defaults = reporter[MUX_DEFAULT_OPTIONS];
189
+ if (!defaults) return route.options;
190
+ return { ...defaults, ...route.options };
191
+ }
186
192
  function isStreamReporter(value) {
187
193
  return typeof value === "object" && value !== null && typeof value.pipe === "function";
188
194
  }
@@ -196,12 +202,15 @@ async function resolveReporter(reporter) {
196
202
  async function runRoutes(source, config, env = process.env, open = internals.openInBrowser) {
197
203
  const routes = resolveProfile(config, env);
198
204
  const streams = broadcast(source, routes.length);
199
- await Promise.all(routes.map(async (route, i) => {
200
- const reporter = await resolveReporter(route.reporter);
201
- const sink = resolveSink(route.sink);
202
- const stage = typeof reporter === "function" ? (src) => reporter(src, route.options) : reporter;
205
+ const sinks = routes.map((route) => resolveSink(route.sink));
206
+ const starts = sinks.map((sink) => sink.start?.());
207
+ await Promise.allSettled(starts);
208
+ const results = await Promise.allSettled(routes.map(async (route, i) => {
209
+ const sink = sinks[i];
203
210
  try {
204
- if (sink.start) await sink.start();
211
+ await starts[i];
212
+ const reporter = await resolveReporter(route.reporter);
213
+ const stage = typeof reporter === "function" ? (src) => reporter(src, routeOptions(reporter, route)) : reporter;
205
214
  const url = sink.viewerUrl?.();
206
215
  if (url) {
207
216
  internals.announce(url, env);
@@ -214,12 +223,15 @@ async function runRoutes(source, config, env = process.env, open = internals.ope
214
223
  await sink.close();
215
224
  }
216
225
  }));
226
+ const failure = results.find((r) => r.status === "rejected");
227
+ if (failure) throw failure.reason;
217
228
  }
218
229
  async function* mux(source) {
219
230
  const config = await loadConfig();
220
231
  await runRoutes(source, config);
221
232
  }
222
233
  export {
234
+ MUX_DEFAULT_OPTIONS,
223
235
  mux as default,
224
236
  resolveReporter,
225
237
  runRoutes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reporters/mux",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Environment-aware routing reporter for `node:test`: tee the event stream to multiple reporters and sinks",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -29,7 +29,7 @@
29
29
  "test": "node --test-reporter=@reporters/mux --test"
30
30
  },
31
31
  "devDependencies": {
32
- "@reporters/tree-core": "workspace:^",
32
+ "@reporters/tree-core": "^0.0.0",
33
33
  "tsup": "^8.5.0",
34
34
  "typescript": "^5.7.0"
35
35
  },
@@ -40,4 +40,4 @@
40
40
  "repository": "https://github.com/MoLow/reporters.git",
41
41
  "author": "Moshe Atlow",
42
42
  "license": "MIT"
43
- }
43
+ }