Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
129 commits
Select commit Hold shift + click to select a range
8c6b049
Add Rush reporter repository configuration
TheLarkInn Aug 28, 2026
b4f0fc1
Add Rush reporter frontend controls
TheLarkInn Aug 28, 2026
2450d5f
Fix reporter frontend integration
TheLarkInn Aug 28, 2026
c6b0349
Emit shadow Rush lifecycle events
TheLarkInn Aug 28, 2026
b80a277
Add feature-flagged operation event adapter
TheLarkInn Aug 28, 2026
481b4f5
Fix operation reporter stream edge cases
TheLarkInn Aug 28, 2026
cbff953
Harden reporter demo integration
TheLarkInn Aug 28, 2026
8131011
Harden Heft negotiation fallback
TheLarkInn Aug 28, 2026
717e14d
Fix structured Heft output archival
TheLarkInn Aug 28, 2026
0ad2e30
Fix reporter frontend argument and close lifecycle
TheLarkInn Aug 28, 2026
4cb2448
Preserve Rush CLI reporter compatibility
TheLarkInn Aug 28, 2026
00f16e1
Refine reporter flag ownership
TheLarkInn Aug 28, 2026
a5f1f7c
Tolerate value-less custom reporter flags
TheLarkInn Aug 28, 2026
e83fa59
Fix operation stream review findings
TheLarkInn Aug 28, 2026
5aa1d2c
Fix reporter demo review findings
TheLarkInn Aug 28, 2026
965e0db
Fix reporter watch and lock handling
TheLarkInn Aug 28, 2026
ae5c475
Fix overlapping reporter watch cycles
TheLarkInn Aug 28, 2026
7964dd8
Fix Heft child reporter review findings
TheLarkInn Aug 28, 2026
2220a63
Fix acknowledgement close test race
TheLarkInn Aug 28, 2026
9f8e6af
Preserve fallback on acknowledgement failure
TheLarkInn Aug 28, 2026
94c9d09
Archive structured Heft diagnostics
TheLarkInn Aug 28, 2026
cc81ba4
Harden Heft child trust boundary
TheLarkInn Aug 29, 2026
f093fbf
Stop execution after parser initialization failure
TheLarkInn Sep 3, 2026
7f47181
Isolate overlapping reporter watch cycles
TheLarkInn Sep 3, 2026
a87994b
Preserve reporter cycles across stream callbacks
TheLarkInn Sep 3, 2026
8431367
Redact secret human reporter messages
TheLarkInn Sep 3, 2026
61a3e54
Force nonzero parser failure exits
TheLarkInn Sep 3, 2026
23de51a
Expose scoped RushSession reporter producers
TheLarkInn Aug 28, 2026
023cf7e
Emit registrations for collapsed iterations
TheLarkInn Sep 3, 2026
04d04cc
Keep operation sink callbacks compatible
TheLarkInn Sep 3, 2026
2139b32
Keep operation stream callbacks compatible
TheLarkInn Sep 3, 2026
745843b
Align reporter completion with failure exit
TheLarkInn Sep 3, 2026
853470e
Complete shadow reporter parity coverage
TheLarkInn Aug 28, 2026
ccd34f3
Forward daemon operation completion events
TheLarkInn Sep 3, 2026
0ef86fa
Finalize machine privacy and zero-work cycles
TheLarkInn Sep 3, 2026
0debf23
Complete collapsed daemon operation cycles
TheLarkInn Sep 3, 2026
6af34ca
Add direct Rush reporter demo path
TheLarkInn Aug 28, 2026
384eab3
Preserve iteration callback compatibility
TheLarkInn Sep 3, 2026
9e90abf
Protect secret logs and callback compatibility
TheLarkInn Sep 3, 2026
38c0ea3
Integrate negotiated Heft child reporting
TheLarkInn Aug 28, 2026
9d58b50
Use resolved iteration for matcher cleanup
TheLarkInn Sep 3, 2026
3b005ee
Isolate reporter configuration test fixtures from shared cleanup
TheLarkInn Sep 7, 2026
7b07603
Fix Heft reporter pipe detection on Windows
TheLarkInn Sep 7, 2026
7e9a1cd
Add Rush reporter frontend controls
TheLarkInn Aug 28, 2026
43908fe
Fix reporter frontend integration
TheLarkInn Aug 28, 2026
b0056ee
Fix reporter frontend argument and close lifecycle
TheLarkInn Aug 28, 2026
c3dc2c1
Preserve Rush CLI reporter compatibility
TheLarkInn Aug 28, 2026
352aa37
Refine reporter flag ownership
TheLarkInn Aug 28, 2026
034f22d
Tolerate value-less custom reporter flags
TheLarkInn Aug 28, 2026
2c1d786
Stop execution after parser initialization failure
TheLarkInn Sep 3, 2026
c8637e5
Expose scoped RushSession reporter producers
TheLarkInn Aug 28, 2026
32562c8
Emit shadow Rush lifecycle events
TheLarkInn Aug 28, 2026
fde0436
Isolate overlapping reporter watch cycles
TheLarkInn Sep 3, 2026
1bc68af
Keep operation sink callbacks compatible
TheLarkInn Sep 3, 2026
3776ee9
Align reporter completion with failure exit
TheLarkInn Sep 3, 2026
707a019
Complete shadow reporter parity coverage
TheLarkInn Aug 28, 2026
8d665d9
Add feature-flagged operation event adapter
TheLarkInn Aug 28, 2026
3055cfb
Fix operation reporter stream edge cases
TheLarkInn Aug 28, 2026
daec75c
Fix operation stream review findings
TheLarkInn Aug 28, 2026
bf2379e
Preserve reporter cycles across stream callbacks
TheLarkInn Sep 3, 2026
e94c944
Emit registrations for collapsed iterations
TheLarkInn Sep 3, 2026
66110fa
Keep operation stream callbacks compatible
TheLarkInn Sep 3, 2026
4375f9f
Forward daemon operation completion events
TheLarkInn Sep 3, 2026
e38163f
Complete collapsed daemon operation cycles
TheLarkInn Sep 3, 2026
1c2a87f
Add direct Rush reporter demo path
TheLarkInn Aug 28, 2026
7c0fffd
Harden reporter demo integration
TheLarkInn Aug 28, 2026
52c50f8
Force nonzero parser failure exits
TheLarkInn Sep 3, 2026
7fb53ea
Fix reporter demo review findings
TheLarkInn Aug 28, 2026
fbbaae7
Fix reporter watch and lock handling
TheLarkInn Aug 28, 2026
b1eaa85
Fix overlapping reporter watch cycles
TheLarkInn Aug 28, 2026
cce4eb1
Redact secret human reporter messages
TheLarkInn Sep 3, 2026
4293aa3
Finalize machine privacy and zero-work cycles
TheLarkInn Sep 3, 2026
898b55f
Preserve iteration callback compatibility
TheLarkInn Sep 3, 2026
4d141b8
Protect secret logs and callback compatibility
TheLarkInn Sep 3, 2026
ba6531b
Sync reporter PR #6000 with its restacked parent
TheLarkInn Sep 7, 2026
5bba19c
Add the missing reporter change note for R2B JSON controls
TheLarkInn Sep 7, 2026
e3bf1e7
Expose scoped RushSession reporter producers
TheLarkInn Aug 28, 2026
ce66517
Emit shadow Rush lifecycle events
TheLarkInn Aug 28, 2026
cd2e907
Isolate overlapping reporter watch cycles
TheLarkInn Sep 3, 2026
a5fd68e
Keep operation sink callbacks compatible
TheLarkInn Sep 3, 2026
b556aec
Align reporter completion with failure exit
TheLarkInn Sep 3, 2026
cc02a14
Complete shadow reporter parity coverage
TheLarkInn Aug 28, 2026
32bf313
Add feature-flagged operation event adapter
TheLarkInn Aug 28, 2026
6ee64ef
Fix operation reporter stream edge cases
TheLarkInn Aug 28, 2026
0ee3e23
Fix operation stream review findings
TheLarkInn Aug 28, 2026
17d5263
Preserve reporter cycles across stream callbacks
TheLarkInn Sep 3, 2026
ccad744
Emit registrations for collapsed iterations
TheLarkInn Sep 3, 2026
5ce23dd
Keep operation stream callbacks compatible
TheLarkInn Sep 3, 2026
f26743e
Forward daemon operation completion events
TheLarkInn Sep 3, 2026
f741114
Complete collapsed daemon operation cycles
TheLarkInn Sep 3, 2026
8e50a42
Add direct Rush reporter demo path
TheLarkInn Aug 28, 2026
8981d22
Harden reporter demo integration
TheLarkInn Aug 28, 2026
09a9fa1
Fix reporter demo review findings
TheLarkInn Aug 28, 2026
306fa70
Fix reporter watch and lock handling
TheLarkInn Aug 28, 2026
fb15ddc
Fix overlapping reporter watch cycles
TheLarkInn Aug 28, 2026
2d80a11
Redact secret human reporter messages
TheLarkInn Sep 3, 2026
41816e7
Finalize machine privacy and zero-work cycles
TheLarkInn Sep 3, 2026
2bc9601
Preserve iteration callback compatibility
TheLarkInn Sep 3, 2026
1c84401
Protect secret logs and callback compatibility
TheLarkInn Sep 3, 2026
7c01056
Sync reporter PR #6000 with parent release metadata
TheLarkInn Sep 7, 2026
d57e66b
Expose scoped RushSession reporter producers
TheLarkInn Aug 28, 2026
e64ea11
Emit shadow Rush lifecycle events
TheLarkInn Aug 28, 2026
6dcfc56
Isolate overlapping reporter watch cycles
TheLarkInn Sep 3, 2026
21f0f0b
Keep operation sink callbacks compatible
TheLarkInn Sep 3, 2026
f7909be
Align reporter completion with failure exit
TheLarkInn Sep 3, 2026
a575d19
Complete shadow reporter parity coverage
TheLarkInn Aug 28, 2026
220da37
Add feature-flagged operation event adapter
TheLarkInn Aug 28, 2026
a1fa10d
Fix operation reporter stream edge cases
TheLarkInn Aug 28, 2026
87b8f81
Fix operation stream review findings
TheLarkInn Aug 28, 2026
e18f03f
Preserve reporter cycles across stream callbacks
TheLarkInn Sep 3, 2026
8e1e7fd
Emit registrations for collapsed iterations
TheLarkInn Sep 3, 2026
4efd6f5
Keep operation stream callbacks compatible
TheLarkInn Sep 3, 2026
646cb72
Forward daemon operation completion events
TheLarkInn Sep 3, 2026
7aa321f
Complete collapsed daemon operation cycles
TheLarkInn Sep 3, 2026
3cbffdf
Record Reporter operation forwarding release note
TheLarkInn Sep 7, 2026
ec08144
Add direct Rush reporter demo path
TheLarkInn Aug 28, 2026
562fbb0
Harden reporter demo integration
TheLarkInn Aug 28, 2026
1fe6931
Fix reporter demo review findings
TheLarkInn Aug 28, 2026
daa5ddd
Fix reporter watch and lock handling
TheLarkInn Aug 28, 2026
d6ff4f1
Fix overlapping reporter watch cycles
TheLarkInn Aug 28, 2026
aa6d91f
Redact secret human reporter messages
TheLarkInn Sep 3, 2026
7b96732
Finalize machine privacy and zero-work cycles
TheLarkInn Sep 3, 2026
9dab133
Preserve iteration callback compatibility
TheLarkInn Sep 3, 2026
26c2ac0
Protect secret logs and callback compatibility
TheLarkInn Sep 3, 2026
dba92fe
Merge aligned Reporter parent into R7A
TheLarkInn Sep 7, 2026
a674350
Merge corrected R5B parent into R7
TheLarkInn Sep 9, 2026
7955774
Align the owned R7 slice with corrected R5B
TheLarkInn Sep 9, 2026
f8ea35b
Adapt R7 transport and demo fixes to the owning PR
TheLarkInn Sep 9, 2026
cd97510
Preserve secret-alias privacy in Heft reporter output
TheLarkInn Sep 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions apps/heft/src/cli/HeftActionRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type {
import type { InternalHeftSession } from '../pluginFramework/InternalHeftSession';
import type { HeftConfiguration } from '../configuration/HeftConfiguration';
import type { LoggingManager } from '../pluginFramework/logging/LoggingManager';
import type { HeftChildReporter } from '../pluginFramework/logging/HeftChildReporter';
import type { MetricsCollector } from '../metrics/MetricsCollector';
import { HeftParameterManager } from '../pluginFramework/HeftParameterManager';
import { TaskOperationRunner } from '../operations/runners/TaskOperationRunner';
Expand Down Expand Up @@ -71,9 +72,13 @@ export function initializeHeft(
): void {
// Ensure that verbose is enabled on the terminal if requested. terminalProvider.verboseEnabled
// should already be `true` if the `--debug` flag was provided. This is set in HeftCommandLineParser
if (heftConfiguration.terminalProvider instanceof ConsoleTerminalProvider) {
heftConfiguration.terminalProvider.verboseEnabled =
heftConfiguration.terminalProvider.verboseEnabled || isVerbose;
if (
heftConfiguration.terminalProvider instanceof ConsoleTerminalProvider ||
'verboseEnabled' in heftConfiguration.terminalProvider
) {
const terminalProvider: ConsoleTerminalProvider | HeftChildReporter =
heftConfiguration.terminalProvider as ConsoleTerminalProvider | HeftChildReporter;
terminalProvider.verboseEnabled = terminalProvider.verboseEnabled || isVerbose;
}

// Log some information about the execution
Expand Down
38 changes: 30 additions & 8 deletions apps/heft/src/cli/HeftCommandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import {
type CommandLineAction
} from '@rushstack/ts-command-line';
import { InternalError, AlreadyReportedError } from '@rushstack/node-core-library';
import { Terminal, ConsoleTerminalProvider, type ITerminal } from '@rushstack/terminal';
import {
Terminal,
ConsoleTerminalProvider,
type ITerminal,
type ITerminalProvider
} from '@rushstack/terminal';

import { MetricsCollector } from '../metrics/MetricsCollector';
import { HeftConfiguration } from '../configuration/HeftConfiguration';
Expand All @@ -23,6 +28,7 @@ import type { IHeftActionOptions } from './actions/IHeftAction';
import { AliasAction } from './actions/AliasAction';
import { getToolParameterNamesFromArgs } from '../utilities/CliUtilities';
import { Constants } from '../utilities/Constants';
import { HeftChildReporter } from '../pluginFramework/logging/HeftChildReporter';

/**
* This interfaces specifies values for parameters that must be parsed before the CLI
Expand All @@ -41,7 +47,8 @@ export class HeftCommandLineParser extends CommandLineParser {
private readonly _debugFlag: CommandLineFlagParameter;
private readonly _unmanagedFlag: CommandLineFlagParameter;
private readonly _debug: boolean;
private readonly _terminalProvider: ConsoleTerminalProvider;
private readonly _terminalProvider: ITerminalProvider;
private readonly _childReporter: HeftChildReporter | undefined;
private readonly _loggingManager: LoggingManager;
private readonly _metricsCollector: MetricsCollector;
private readonly _heftConfiguration: HeftConfiguration;
Expand Down Expand Up @@ -77,12 +84,22 @@ export class HeftCommandLineParser extends CommandLineParser {
this._debug = !!preInitializationArgumentValues.debug;

// Enable debug and verbose logging if the "--debug" flag is set
this._terminalProvider = new ConsoleTerminalProvider({
debugEnabled: this._debug,
verboseEnabled: this._debug
});
this._childReporter = HeftChildReporter.tryInitialize();
this._terminalProvider =
this._childReporter ??
new ConsoleTerminalProvider({
debugEnabled: this._debug,
verboseEnabled: this._debug
});
if (this._debug && this._childReporter) {
this._childReporter.debugEnabled = true;
this._childReporter.verboseEnabled = true;
}
this.globalTerminal = new Terminal(this._terminalProvider);
this._loggingManager = new LoggingManager({ terminalProvider: this._terminalProvider });
this._loggingManager = new LoggingManager({
terminalProvider: this._terminalProvider,
childReporter: this._childReporter
});
if (this._debug) {
// Enable printing stacktraces if the "--debug" flag is set
this._loggingManager.enablePrintStacks();
Expand Down Expand Up @@ -197,6 +214,7 @@ export class HeftCommandLineParser extends CommandLineParser {
commandName,
unaliasedCommandName
};
this._childReporter?.setCommandName(commandName);
await super.onExecuteAsync();
} catch (e) {
await this._reportErrorAndSetExitCodeAsync(e as Error);
Expand Down Expand Up @@ -241,7 +259,11 @@ export class HeftCommandLineParser extends CommandLineParser {

private async _reportErrorAndSetExitCodeAsync(error: Error): Promise<void> {
if (!(error instanceof AlreadyReportedError)) {
this.globalTerminal.writeErrorLine(error.toString());
if (this._childReporter) {
this._childReporter.emitDiagnostic(Constants.heftPackageName, error, 'error');
} else {
this.globalTerminal.writeErrorLine(error.toString());
}
}

if (this._debug) {
Expand Down
275 changes: 275 additions & 0 deletions apps/heft/src/pluginFramework/logging/HeftChildReporter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import * as childProcess from 'node:child_process';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import type { Readable, Writable } from 'node:stream';

import { PackageJsonLookup } from '@rushstack/node-core-library';

import { HeftChildReporter } from './HeftChildReporter';

describe(HeftChildReporter.name, () => {
it('preserves standalone behavior when no parent descriptors are present', () => {
expect(HeftChildReporter.tryInitialize({})).toBeUndefined();
});

it('does not write to or close descriptors that are not pipes', () => {
const folderPath: string = fs.mkdtempSync(path.join(os.tmpdir(), 'heft-child-reporter-'));
const eventPath: string = path.join(folderPath, 'event');
const acknowledgementPath: string = path.join(folderPath, 'ack');
const eventFd: number = fs.openSync(eventPath, 'w+');
const acknowledgementFd: number = fs.openSync(acknowledgementPath, 'w+');
try {
expect(
HeftChildReporter.tryInitialize({
_RUSH_REPORTER_CHILD_FD: String(eventFd),
_RUSH_REPORTER_CHILD_ACK_FD: String(acknowledgementFd)
})
).toBeUndefined();
expect(fs.readFileSync(eventPath, 'utf8')).toBe('');
expect(() => fs.writeSync(acknowledgementFd, 'still open')).not.toThrow();
} finally {
fs.closeSync(eventFd);
fs.closeSync(acknowledgementFd);
fs.rmSync(folderPath, { recursive: true });
}
});

it('recognizes pipe mode bits when platform predicates return false', () => {
const fsModule: typeof fs = jest.requireActual('node:fs');
const stats: fs.Stats = fs.statSync(__filename);
stats.mode = fs.constants.S_IFIFO;
stats.isFIFO = () => false;
stats.isSocket = () => false;
const acknowledgement: Buffer = Buffer.from(
`${JSON.stringify({
kind: 'helloAck',
protocolVersion: { major: 1, minor: 2 },
acceptedCapabilities: ['heft-child-events-v1'],
rejectedRequiredFeatures: []
})}\n`
);
const statSpy: jest.SpyInstance = jest.spyOn(fsModule, 'fstatSync').mockReturnValue(stats);
const packageSpy: jest.SpyInstance = jest
.spyOn(PackageJsonLookup.instance, 'tryLoadPackageJsonFor')
.mockReturnValue({ name: '@rushstack/heft', version: '1.0.0' });
const writeSpy: jest.SpyInstance = jest.spyOn(fsModule, 'writeSync').mockReturnValue(1);
const readSpy: jest.SpyInstance = jest.spyOn(fsModule, 'readSync').mockImplementation((fd, buffer) => {
expect(fd).toBe(4);
if (!Buffer.isBuffer(buffer)) {
throw new Error('Expected the acknowledgement read buffer.');
}
return acknowledgement.copy(buffer);
});
const closeSpy: jest.SpyInstance = jest.spyOn(fsModule, 'closeSync').mockImplementation(() => {});

try {
const reporter: HeftChildReporter | undefined = HeftChildReporter.tryInitialize({
_RUSH_REPORTER_CHILD_FD: '3',
_RUSH_REPORTER_CHILD_ACK_FD: '4'
});

expect(reporter).toBeDefined();
expect(reporter?.parentReporterName).toBe('plaintext');
expect(writeSpy).toHaveBeenCalledWith(3, expect.stringContaining('"kind":"hello"'));
expect(closeSpy).toHaveBeenCalledWith(4);
expect(closeSpy).not.toHaveBeenCalledWith(3);
} finally {
closeSpy.mockRestore();
readSpy.mockRestore();
writeSpy.mockRestore();
packageSpy.mockRestore();
statSpy.mockRestore();
}
});

it('negotiates context and emits ordered structured output and diagnostics', async () => {
const modulePath: string = require.resolve('./HeftChildReporter');
const childScript: string = `
const { HeftChildReporter } = require(process.argv[1]);
const reporter = HeftChildReporter.tryInitialize(process.env);
if (!reporter) {
process.stdout.write('fallback');
process.exit(2);
}
if (reporter.parentReporterName !== 'json' || reporter.terminalWidth !== 132) process.exit(3);
reporter.setCommandName('build');
reporter.write('visible output\\n', 0);
reporter.write('hidden verbose output\\n', 3);
reporter.emitDiagnostic('typescript', new Error('structured failure'), 'error');
`;
const child: childProcess.ChildProcess = childProcess.spawn(
process.execPath,
['-e', childScript, modulePath],
{
env: {
...process.env,
_RUSH_REPORTER_CHILD_FD: '3',
_RUSH_REPORTER_CHILD_ACK_FD: '4'
},
stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe']
}
);
const descriptor: Readable = child.stdio[3] as Readable;
const acknowledgement: Writable = child.stdio[4] as Writable;
let descriptorText: string = '';
let acknowledgementSent: boolean = false;
descriptor.setEncoding('utf8');
descriptor.on('data', (chunk: string) => {
descriptorText += chunk;
if (!acknowledgementSent && descriptorText.includes('\n')) {
acknowledgementSent = true;
acknowledgement.end(
`${JSON.stringify({
kind: 'helloAck',
protocolVersion: { major: 1, minor: 2 },
acceptedCapabilities: ['heft-child-events-v1', 'reporter-context-v1'],
rejectedRequiredFeatures: [],
context: {
reporter: 'json',
logLevel: 'normal',
color: false,
terminalWidth: 132
}
})}\n`
);
}
});

let stdout: string = '';
child.stdout?.setEncoding('utf8').on('data', (chunk: string) => {
stdout += chunk;
});
const exitCode: number | null = await new Promise((resolve, reject) => {
child.once('error', reject);
child.once('close', resolve);
});

expect(exitCode).toBe(0);
expect(stdout).toBe('');
const records: Array<Record<string, unknown>> = descriptorText
.trim()
.split('\n')
.map((line: string) => JSON.parse(line) as Record<string, unknown>);
expect(records[0].kind).toBe('hello');
expect(records.slice(1).map((record) => record.type)).toEqual(['externalOutput', 'diagnosticEmitted']);
expect(records.slice(1).map((record) => record.sequence)).toEqual([1, 2]);
expect((records[1].scope as { commandName?: string }).commandName).toBe('build');
expect((records[1].payload as { text?: string }).text).toBe('visible output\n');
expect((records[2].payload as { severity?: string }).severity).toBe('error');
});

it('uses safe context defaults when the accepted context capability has no payload', async () => {
const modulePath: string = require.resolve('./HeftChildReporter');
const childScript: string = `
const { HeftChildReporter } = require(process.argv[1]);
const reporter = HeftChildReporter.tryInitialize(process.env);
if (!reporter) process.exit(2);
if (reporter.parentReporterName !== 'plaintext' || reporter.terminalWidth !== 80) process.exit(3);
reporter.write('structured with defaults\\n', 0);
`;
const child: childProcess.ChildProcess = childProcess.spawn(
process.execPath,
['-e', childScript, modulePath],
{
env: {
...process.env,
_RUSH_REPORTER_CHILD_FD: '3',
_RUSH_REPORTER_CHILD_ACK_FD: '4'
},
stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe']
}
);
const descriptor: Readable = child.stdio[3] as Readable;
const acknowledgement: Writable = child.stdio[4] as Writable;
let descriptorText: string = '';
descriptor.setEncoding('utf8');
descriptor.on('data', (chunk: string) => {
descriptorText += chunk;
if (descriptorText.split('\n').length === 2) {
acknowledgement.end(
`${JSON.stringify({
kind: 'helloAck',
protocolVersion: { major: 1, minor: 2 },
acceptedCapabilities: ['heft-child-events-v1', 'reporter-context-v1'],
rejectedRequiredFeatures: []
})}\n`
);
}
});

const exitCode: number | null = await new Promise((resolve, reject) => {
child.once('error', reject);
child.once('close', resolve);
});

expect(exitCode).toBe(0);
expect(descriptorText).toContain('structured with defaults');
});

it.each([0, 'wide'])(
'falls back safely for invalid parent terminal width %p',
async (terminalWidth: unknown) => {
const modulePath: string = require.resolve('./HeftChildReporter');
const childScript: string = `
const { HeftChildReporter } = require(process.argv[1]);
const reporter = HeftChildReporter.tryInitialize(process.env);
if (reporter) process.exit(2);
process.stdout.write('context fallback');
`;
const child: childProcess.ChildProcess = childProcess.spawn(
process.execPath,
['-e', childScript, modulePath],
{
env: {
...process.env,
_RUSH_REPORTER_CHILD_FD: '3',
_RUSH_REPORTER_CHILD_ACK_FD: '4'
},
stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe']
}
);
const descriptor: Readable = child.stdio[3] as Readable;
const acknowledgement: Writable = child.stdio[4] as Writable;
let descriptorText: string = '';
let acknowledgementSent: boolean = false;
descriptor.setEncoding('utf8');
descriptor.on('data', (chunk: string) => {
descriptorText += chunk;
if (!acknowledgementSent && descriptorText.includes('\n')) {
acknowledgementSent = true;
acknowledgement.end(
`${JSON.stringify({
kind: 'helloAck',
protocolVersion: { major: 1, minor: 2 },
acceptedCapabilities: ['heft-child-events-v1', 'reporter-context-v1'],
rejectedRequiredFeatures: [],
context: {
reporter: 'json',
logLevel: 'normal',
color: false,
terminalWidth
}
})}\n`
);
}
});
let stdout: string = '';
child.stdout?.setEncoding('utf8').on('data', (chunk: string) => {
stdout += chunk;
});

const exitCode: number | null = await new Promise((resolve, reject) => {
child.once('error', reject);
child.once('close', resolve);
});

expect(exitCode).toBe(0);
expect(acknowledgementSent).toBe(true);
expect(stdout).toBe('context fallback');
}
);
});
Loading