Skip to content

Commit aceb1fe

Browse files
authored
[rush] (BREAKING CHANGE) Overhaul watch-mode to facilitate orchestration (#5378)
## Summary Completely retools the watch engine in Rush to facilitate better interaction with plugins that wish to orchestrate the build process. Makes the Rush execution engine stateful across an entire Rush watch session. ### BREAKING CHANGES - `PhasedCommandHooks` now has only two hooks: `createOperationsAsync` and `onGraphCreatedAsync`. All other hooks have been moved to `OperationGraphHooks`, accessible via `operationGraph.hooks` inside the `onGraphCreatedAsync` callback. - `createOperations` is now `createOperationsAsync`, and the properties on the `ICreateOperationsContext` parameter have changed: `isInitial`, `projectsInUnknownState`, `phaseOriginal`, and `invalidateOperation` have been removed; `generateFullGraph` and `includePhaseDeps` have been added. - All manipulation of the runtime graph and taps into the build process are now in the `OperationGraphHooks` class. Tap `operationGraph.hooks` for: - `configureIteration` — synchronous hook to select which operations run in the next iteration - `beforeExecuteIterationAsync` — replaces `beforeExecuteOperationsAsync` - `afterExecuteIterationAsync` — replaces `afterExecuteOperationsAsync` - `beforeExecuteOperationAsync` — moved from `PhasedCommandHooks` - `afterExecuteOperationAsync` — moved from `PhasedCommandHooks` - `createEnvironmentForOperation` — moved from `PhasedCommandHooks` - `beforeLog` — moved from `PhasedCommandHooks`; now invoked by the graph before writing telemetry - `onIdle` — replaces `waitingForChanges` (moved from `PhasedCommandHooks`) - `onExecutionStatesUpdated`, `onEnableStatesChanged`, `onIterationScheduled`, `onGraphStateChanged`, `onInvalidateOperations` — new hooks - `IOperationRunnerContext` now includes `getInvalidateCallback()`, which returns a lightweight `(reason: string) => void` callback that marks the current operation for re-execution. This replaces the previous `invalidateOperation` on `ICreateOperationsContext` and removes the need for runners to capture an `IOperationGraph` reference. - `IOperationRunner.executeAsync` now takes an optional second parameter `lastState?: IOperationLastState`, providing the previous execution result to inform incremental behavior (e.g. choosing an initial vs. incremental command). - `IBaseOperationExecutionResult.getStateHashComponents()` now returns a structured `IOperationStateHashComponents` interface (`{ dependencies, local, config }`) instead of a flat `ReadonlyArray<string>`. - `IBaseOperationExecutionResult.metadataFolderPath` is now `string` (was `string | undefined`). ## Details The new lifecycle of a Rush phased command is that the command first invokes `createOperationsAsync` to create the session-long operation graph. This set of operations is then passed into `onGraphCreatedAsync`, which constructs an `IOperationGraph` that owns the lifecycle of the execution session. There is a new watch option `includeAllProjectsInWatchGraph` (in `command-line.json`) that, if set to true, will cause Rush to build the graph with all projects in `rush.json`, regardless of CLI selection parameters. Selected projects will only affect which projects are enabled for execution during the initial run. This also allows for a bare command, e.g. `rush start`, to select no projects. This feature is intended for use with plugins that offer the ability to alter the enabled/disabled states of operations in the graph while the session is ongoing. For an example, see `@rushstack/rush-serve-plugin`, which facilitates altering these states via Web Socket messages. ### Runner self-invalidation Long-lived runners (e.g. IPC processes, file watchers) can now request re-execution directly via `context.getInvalidateCallback()`. This returns a minimal closure that delegates to `IOperationGraph.invalidateOperations()`, without the runner needing access to the graph or `Operation` object. The `IPCOperationRunner` uses this internally to handle `requestRun` IPC messages from child processes. ### Error handling Operation runner errors are now uniformly caught and wrapped in `OperationError` by `OperationExecutionRecord.executeAsync`, rather than requiring each runner implementation to handle its own error wrapping. All in-repo plugins that interact with the Rush execution graph have also been updated. ## How it was tested Added unit tests for all functionality of the new `IOperationGraph` API contract (633 tests passing). Manual validation via the rushstack repo's `rush start` command for the CLI interaction (enable/disable debug or verbose, alter parallelism, pause/resume, kick a single build, invalidate, close runners). ## Impacted documentation All watch-mode documentation. Plugin documentation for phased commands. New docs added in this PR: - `docs/rush/phased-commands.md` — architecture reference for the phased command execution model, `OperationGraphHooks`, `IOperationGraph` API, and `IOperationRunner` contract - `docs/rush/plugin-migration-guide.md` — hook-by-hook migration guide for plugin authors upgrading from the previous API
1 parent ac09470 commit aceb1fe

80 files changed

Lines changed: 7543 additions & 4810 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/rush/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"@rushstack/rush-amazon-s3-build-cache-plugin": "workspace:*",
4949
"@rushstack/rush-azure-storage-build-cache-plugin": "workspace:*",
5050
"@rushstack/rush-http-build-cache-plugin": "workspace:*",
51+
"@rushstack/rush-serve-plugin": "workspace:*",
5152
"@types/semver": "7.7.1"
5253
},
5354
"exports": {

apps/rush/src/start-dev.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ function includePlugin(pluginName: string, pluginPackageName?: string): void {
2929
includePlugin('rush-amazon-s3-build-cache-plugin');
3030
includePlugin('rush-azure-storage-build-cache-plugin');
3131
includePlugin('rush-http-build-cache-plugin');
32+
includePlugin('rush-serve-plugin');
3233
// Including this here so that developers can reuse it without installing the plugin a second time
3334
includePlugin('rush-azure-interactive-auth-plugin', '@rushstack/rush-azure-storage-build-cache-plugin');
3435

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@microsoft/rush",
5+
"comment": "(PLUGIN BREAKING CHANGE) Overhaul watch-mode commands such that the graph is only created once at the start of command invocation, along with a stateful manager object. Plugins may now access the manager object and use it to orchestrate and tap into the build process.",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@microsoft/rush"
10+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush-serve-plugin-options.schema.json",
3+
"phasedCommands": ["start"],
4+
"portParameterLongName": "--port",
5+
"buildStatusWebSocketPath": "/ws",
6+
"logServePath": "/logs"
7+
}

common/config/rush/command-line.json

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,24 +245,25 @@
245245
// Used for very simple builds that don't support CLI arguments like `--production` or `--fix`
246246
"name": "_phase:lite-build",
247247
"dependencies": {
248-
"upstream": ["_phase:lite-build", "_phase:build"]
248+
"upstream": ["_phase:build"]
249249
},
250250
"missingScriptBehavior": "silent",
251251
"allowWarningsOnSuccess": false
252252
},
253253
{
254254
"name": "_phase:build",
255255
"dependencies": {
256-
"self": ["_phase:lite-build"],
257-
"upstream": ["_phase:build"]
256+
// Don't need to declare the dependency on _phase:build because it is transitive via _phase:lite-build
257+
"self": ["_phase:lite-build"]
258258
},
259259
"missingScriptBehavior": "log",
260260
"allowWarningsOnSuccess": false
261261
},
262262
{
263263
"name": "_phase:test",
264264
"dependencies": {
265-
"self": ["_phase:lite-build", "_phase:build"]
265+
// Dependency on _phase:lite-build is transitive via _phase:build
266+
"self": ["_phase:build"]
266267
},
267268
"missingScriptBehavior": "silent",
268269
"allowWarningsOnSuccess": false
@@ -494,6 +495,14 @@
494495
"associatedPhases": ["_phase:build", "_phase:test"],
495496
"associatedCommands": ["build", "rebuild", "test", "retest"]
496497
},
498+
{
499+
"longName": "--port",
500+
"parameterKind": "integer",
501+
"argumentName": "PORT",
502+
"description": "The port to use for the server",
503+
"associatedPhases": [],
504+
"associatedCommands": ["start"]
505+
},
497506
{
498507
"longName": "--update-snapshots",
499508
"parameterKind": "flag",

common/config/rush/experiments.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
* of `_phase:<name>` if they exist. The created child process will be provided with an IPC channel and expected to persist
8989
* across invocations.
9090
*/
91-
// "useIPCScriptsInWatchMode": true,
91+
"useIPCScriptsInWatchMode": true,
9292

9393
/**
9494
* (UNDER DEVELOPMENT) The Rush alerts feature provides a way to send announcements to engineers

common/config/rush/nonbrowser-approved-packages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,10 @@
382382
"name": "@rushstack/rush-sdk",
383383
"allowedCategories": [ "libraries", "tests", "vscode-extensions" ]
384384
},
385+
{
386+
"name": "@rushstack/rush-serve-plugin",
387+
"allowedCategories": [ "libraries" ]
388+
},
385389
{
386390
"name": "@rushstack/set-webpack-public-path-plugin",
387391
"allowedCategories": [ "libraries", "tests" ]

common/config/subspaces/default/pnpm-lock.yaml

Lines changed: 6 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)