Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions apps/rush/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ _(If you don't have a GitHub account set up, you can use `rush install --bypass-

For more details and support resources, please visit: https://rushjs.io

The repository also includes an
[experimental Rush reporter opt-in and demo guide](../../docs/rush/reporter.md). Rush 5 retains legacy
terminal output unless a reporter is explicitly enabled.

## Links

- [CHANGELOG.md](
Expand Down
12 changes: 12 additions & 0 deletions apps/rush/UPGRADING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Upgrade notes for @microsoft/rush

### Experimental Rush reporter opt-in

Rush 5 keeps the existing terminal output by default. Maintainers can evaluate the new reporter path for one
invocation with `--reporter=<name>` or for a repository with `"useRushReporter": true` in
`common/config/rush/experiments.json`.

Use `RUSH_REPORTER=legacy` for an immediate reporter-only rollback. Environment-based automatic AI selection
and the planned Rush 6 default change are not enabled yet.

See the [experimental Rush reporter guide](../../docs/rush/reporter.md) for the control precedence, stdout and
stderr contracts, full-log and privacy behavior, cross-version prerequisite, and reproducible demo.

### Rush 5.135.0

This release of Rush deprecates the `rush-project.json`'s `operationSettings.sharding.shardOperationSettings`
Expand Down
48 changes: 43 additions & 5 deletions apps/rush/src/test/sandbox/reporter-demo/README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
# Direct Rush reporter demo

Build the three reporter projects, then run the self-checking direct invocation demo:
For the complete control, compatibility, privacy, and troubleshooting reference, see the
[experimental Rush reporter guide](../../../../../../docs/rush/reporter.md).

From a clean checkout, install dependencies, build the reporter path, and run the self-checking direct
invocation demo:

```sh
rush build --to @microsoft/rush
node common/scripts/install-run-rush.js install
node common/scripts/install-run-rush.js build --to @microsoft/rush
node apps/rush/src/test/sandbox/reporter-demo/run.mjs
```

The script runs the same `rush build --only @rushstack/rush-reporter` operation stream through legacy,
plaintext, JSON, AI, file, and quiet modes, plus parser failure, help, and command-specific JSON cases.
It verifies payload-only machine stdout, one visible writer, ordered/lossless plaintext grouping from a
same-invocation JSON sidecar, final artifact completeness, owner-only log permissions, failure flushing,
AI parser-error context, command-JSON ownership, CI plaintext output, cache-path output, normalized
`RUSH_TEMP_FOLDER` log placement, matching purge-path selection, and the `RUSH_REPORTER=legacy` rollback
transcript. Captured stdout/stderr files are written to a temporary folder.
AI parser-error context, command-JSON ownership, exclusive sidecar destinations, and the
`RUSH_REPORTER=legacy` rollback transcript. Inherited `RUSH_REPORTER`, `RUSH_LOG_LEVEL`, and
`RUSH_QUIET_MODE` values are removed from the self-check matrix. It also verifies CI plaintext output,
cache-path output, a matching `RUSH_PREVIEW_VERSION`, normalized `RUSH_TEMP_FOLDER` log placement, and
matching purge-path selection. Captured stdout/stderr files are written to a temporary folder.

The final matrix case invokes `rush purge` with an isolated `RUSH_TEMP_FOLDER`. This also unlinks project
dependencies, so run the install command again before continuing development in the checkout.

For an individual invocation:

```sh
# Interactive TTY only
node apps/rush/bin/rush build --only @rushstack/rush-reporter --reporter=default

node apps/rush/bin/rush build --only @rushstack/rush-reporter --reporter=plaintext
node apps/rush/bin/rush build --only @rushstack/rush-reporter --reporter=json --log-level=debug
RUSH_PREVIEW_VERSION=$(node -p "require('./apps/rush/package.json').version") node apps/rush/bin/rush build --only @rushstack/rush-reporter --reporter=json
node apps/rush/bin/rush build --only @rushstack/rush-reporter --reporter=ai
node apps/rush/bin/rush build --only @rushstack/rush-reporter --reporter=file
RUSH_TEMP_FOLDER=./common/temp/reporter-demo-override node apps/rush/bin/rush build --only @rushstack/rush-reporter --reporter=file
node apps/rush/bin/rush build --only @rushstack/rush-reporter --reporter=plaintext --log-level=quiet
RUSH_REPORTER=legacy node apps/rush/bin/rush build --only @rushstack/rush-reporter --reporter=json
node apps/rush/bin/rush list --json --reporter=file
Expand All @@ -30,3 +45,26 @@ node apps/rush/bin/rush list --json --reporter=file
Repositories can opt in without a command-line flag by setting `"useRushReporter": true` in
`common/config/rush/experiments.json`. Remove that setting or use `RUSH_REPORTER=legacy` for immediate
rollback.

The expected output is shape-based:

- legacy keeps the existing Rush banner, operation blocks, and final status sections;
- `default` uses a width-aware three-row live region and leaves a short final summary;
- explicit `plaintext` groups ordered output under `project (phase)` and prints the absolute full-log path;
- JSON stdout contains only NDJSON event envelopes;
- a matching preview version keeps JSON stdout parseable and writes its warning to stderr;
- AI stdout contains `ai.status` and bounded `ai.final` records;
- file mode leaves stdout empty and writes the full-log path to stderr;
- `RUSH_TEMP_FOLDER` moves the full log and the matching purge removes that override;
- quiet mode retains only the final result and full-log path.

The intentional missing-project AI failure preserves an actionable diagnostic and complete log reference.
Structured remediation is included when the producing diagnostic supplies it; this parser failure does not
currently provide a remediation action.

After the demo exits, inspect the latest invocation log with:

```sh
ls -lt common/temp/rush-logs
sed -n '1,120p' common/temp/rush-logs/latest.log
```
34 changes: 33 additions & 1 deletion apps/rush/src/test/sandbox/reporter-demo/run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@ import { fileURLToPath } from 'node:url';
const scriptFolder = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(scriptFolder, '..', '..', '..', '..', '..', '..');
const rushBin = path.join(repoRoot, 'apps', 'rush', 'bin', 'rush');
const rushVersion = JSON.parse(
fs.readFileSync(path.join(repoRoot, 'apps', 'rush', 'package.json'), 'utf8')
).version;
const outputFolder = fs.mkdtempSync(path.join(os.tmpdir(), 'rush-reporter-demo-'));
const commonArgs = ['build', '--only', '@rushstack/rush-reporter'];
const baseEnv = { ...process.env };
delete baseEnv.RUSH_REPORTER;
delete baseEnv.RUSH_LOG_LEVEL;
delete baseEnv.RUSH_QUIET_MODE;

function run(name, args, env = {}, expectedStatus = 0) {
const result = spawnSync(process.execPath, [rushBin, ...args], {
cwd: repoRoot,
env: { ...process.env, ...env },
env: { ...baseEnv, ...env },
encoding: 'utf8'
});
fs.writeFileSync(path.join(outputFolder, `${name}.stdout`), result.stdout);
Expand All @@ -40,6 +47,9 @@ const plaintext = run('plaintext', [
`--output=json://${plaintextEventsPath}?logLevel=debug`
]).stdout;
const json = run('json', [...commonArgs, '--reporter=json', '--log-level=debug']).stdout;
const previewJson = run('preview-json', [...commonArgs, '--reporter=json'], {
RUSH_PREVIEW_VERSION: rushVersion
});
const ai = run('ai', [...commonArgs, '--reporter=ai']).stdout;
const file = run('file', [...commonArgs, '--reporter=file']);
const quiet = run('quiet', [...commonArgs, '--reporter=plaintext', '--log-level=quiet']).stdout;
Expand All @@ -62,6 +72,18 @@ const flagOffHelp = run('help-flag-off', ['--help']).stdout;
const help = run('help', ['--help', '--reporter=json'], { RUSH_REPORTER: 'legacy' }).stdout;
const commandJson = run('command-json', ['list', '--json', '--reporter=file']);
const commandJsonConflict = run('command-json-conflict', ['list', '--json', '--reporter=json'], {}, 1);
const duplicateOutputPath = path.join(outputFolder, 'duplicate-output.jsonl');
const outputConflict = run(
'output-conflict',
[
...commonArgs,
'--reporter=plaintext',
`--output=json://${duplicateOutputPath}`,
`--output=file://${duplicateOutputPath}`
],
{},
1
);
const tempOverride = path.join(outputFolder, 'rush-temp-override');
const tempOverrideFile = run('temp-override', [...commonArgs, '--reporter=file'], {
RUSH_TEMP_FOLDER: tempOverride
Expand Down Expand Up @@ -94,6 +116,7 @@ function parseNdjson(text, name) {
}

const jsonEvents = parseNdjson(json, 'json');
const previewJsonEvents = parseNdjson(previewJson.stdout, 'preview-json');
const aiRecords = parseNdjson(ai, 'ai');
const failureJsonEvents = parseNdjson(failureJson, 'failure-json');
const failureAiRecords = parseNdjson(failureAi, 'failure-ai');
Expand Down Expand Up @@ -169,6 +192,12 @@ const aiFinal = aiRecords.at(-1);
if (aiFinal?.kind !== 'ai.final' || aiFinal.log?.complete !== true) {
throw new Error('AI output did not include a complete full-log reference.');
}
if (
!previewJson.stderr.includes('RUSH_PREVIEW_VERSION') ||
!previewJsonEvents.some((event) => event.type === 'sessionCompleted')
) {
throw new Error('The matching preview version did not preserve payload-only reporter stdout.');
}
const failureAiFinal = failureAiRecords.at(-1);
if (
failureAiFinal?.result !== 'failed' ||
Expand Down Expand Up @@ -216,6 +245,9 @@ if (
) {
throw new Error('Command-specific JSON ownership arbitration failed.');
}
if (outputConflict.stdout !== '' || !outputConflict.stderr.includes('is already owned by another reporter')) {
throw new Error('Duplicate reporter output destinations were not rejected.');
}

console.log(`Reporter demo outputs: ${outputFolder}`);
console.log(`Full detail log: ${logMatch[1]}`);
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@microsoft/rush",
"comment": "Document the experimental reporter opt-in, rollback, output contracts, compatibility boundary, and reproducible demo.",
"type": "patch"
}
],
"packageName": "@microsoft/rush",
"email": "TheLarkInn@users.noreply.github.com"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@rushstack/rush-reporter",
"comment": "Add the opt-in reporter guide covering built-in renderers, full logs, privacy, and rollout safety.",
"type": "patch"
}
],
"packageName": "@rushstack/rush-reporter",
"email": "TheLarkInn@users.noreply.github.com"
}
Loading