Skip to content

Commit 0bd291a

Browse files
authored
[rush-serve-plugin] Allow configuring operation enable/disable states via websocket (#5200)
* [rush] Fix telemetry for `--changed-projects-only` * [rush-serve] Support enable/disable via websocket * Stop mutating parameter * [rush-serve] Allow setting enabled state to 'changed' * [rush-serve] Support invalidation via websocket * fix setting changedProjectsOnly --------- Co-authored-by: David Michon <[email protected]>
1 parent 44ddff4 commit 0bd291a

File tree

6 files changed

+133
-8
lines changed

6 files changed

+133
-8
lines changed
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": "Fix telemetry for \"--changed-projects-only\" when toggled in watch mode.",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@microsoft/rush"
10+
}
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": "(rush-serve-plugin) Support websocket message to enable/disable operations.",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@microsoft/rush"
10+
}

libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> {
146146
private readonly _alwaysInstall: boolean | undefined;
147147
private readonly _knownPhases: ReadonlyMap<string, IPhase>;
148148
private readonly _terminal: ITerminal;
149+
private _changedProjectsOnly: boolean;
149150

150151
private readonly _changedProjectsOnlyParameter: CommandLineFlagParameter | undefined;
151152
private readonly _selectionParameters: SelectionParameterSet;
@@ -162,8 +163,6 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> {
162163
private readonly _debugBuildCacheIdsParameter: CommandLineFlagParameter;
163164
private readonly _includePhaseDeps: CommandLineFlagParameter | undefined;
164165

165-
private _changedProjectsOnly: boolean;
166-
167166
public constructor(options: IPhasedScriptActionOptions) {
168167
super(options);
169168
this._enableParallelism = options.enableParallelism;
@@ -845,7 +844,7 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> {
845844
// Account for consumer relationships
846845
const executeOperationsContext: IExecuteOperationsContext = {
847846
...initialCreateOperationsContext,
848-
changedProjectsOnly: this._changedProjectsOnly,
847+
changedProjectsOnly: !!this._changedProjectsOnly,
849848
isInitial: false,
850849
inputsSnapshot: state,
851850
projectsInUnknownState: changedProjects,
@@ -961,6 +960,13 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> {
961960
countNoOp: 0
962961
};
963962

963+
const { _changedProjectsOnlyParameter: changedProjectsOnlyParameter } = this;
964+
if (changedProjectsOnlyParameter) {
965+
// Overwrite this value since we allow changing it at runtime.
966+
extraData[changedProjectsOnlyParameter.scopedLongName ?? changedProjectsOnlyParameter.longName] =
967+
this._changedProjectsOnly;
968+
}
969+
964970
if (result) {
965971
const { operationResults } = result;
966972

libraries/rush-lib/src/logic/operations/PhasedOperationPlugin.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ const PLUGIN_NAME: 'PhasedOperationPlugin' = 'PhasedOperationPlugin';
2121
export class PhasedOperationPlugin implements IPhasedCommandPlugin {
2222
public apply(hooks: PhasedCommandHooks): void {
2323
hooks.createOperations.tap(PLUGIN_NAME, createOperations);
24+
// Configure operations later.
25+
hooks.createOperations.tap(
26+
{
27+
name: `${PLUGIN_NAME}.Configure`,
28+
stage: 1000
29+
},
30+
configureOperations
31+
);
2432
}
2533
}
2634

@@ -40,8 +48,6 @@ function createOperations(
4048
}
4149
}
4250

43-
configureOperations(existingOperations, context);
44-
4551
return existingOperations;
4652

4753
// Binds phaseSelection, projectSelection, operations via closure
@@ -88,7 +94,7 @@ function createOperations(
8894
}
8995
}
9096

91-
function configureOperations(operations: ReadonlySet<Operation>, context: ICreateOperationsContext): void {
97+
function configureOperations(operations: Set<Operation>, context: ICreateOperationsContext): Set<Operation> {
9298
const {
9399
changedProjectsOnly,
94100
projectsInUnknownState: changedProjects,
@@ -149,6 +155,8 @@ function configureOperations(operations: ReadonlySet<Operation>, context: ICreat
149155
operation.enabled &&= phaseSelection.has(associatedPhase) && projectSelection.has(associatedProject);
150156
}
151157
}
158+
159+
return operations;
152160
}
153161

154162
// Convert the [IPhase, RushConfigurationProject] into a value suitable for use as a Map key

rush-plugins/rush-serve-plugin/src/api.types.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,31 @@ export interface IWebSocketSyncCommandMessage {
151151
command: 'sync';
152152
}
153153

154+
/**
155+
* Message received from a WebSocket client to request invalidation of one or more operations.
156+
*/
157+
export interface IWebSocketInvalidateCommandMessage {
158+
command: 'invalidate';
159+
operationNames: string[];
160+
}
161+
162+
/**
163+
* The set of possible operation enabled states.
164+
*/
165+
export type OperationEnabledState = 'never' | 'changed' | 'affected';
166+
167+
/**
168+
* Message received from a WebSocket client to change the enabled states of operations.
169+
*/
170+
export interface IWebSocketSetEnabledStatesCommandMessage {
171+
command: 'set-enabled-states';
172+
enabledStateByOperationName: Record<string, OperationEnabledState>;
173+
}
174+
154175
/**
155176
* The set of possible messages received from a WebSocket client.
156177
*/
157-
export type IWebSocketCommandMessage = IWebSocketSyncCommandMessage;
178+
export type IWebSocketCommandMessage =
179+
| IWebSocketSyncCommandMessage
180+
| IWebSocketInvalidateCommandMessage
181+
| IWebSocketSetEnabledStatesCommandMessage;

rush-plugins/rush-serve-plugin/src/phasedCommandHandler.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ import type {
4545
ReadableOperationStatus,
4646
IWebSocketCommandMessage,
4747
IRushSessionInfo,
48-
ILogFileURLs
48+
ILogFileURLs,
49+
OperationEnabledState
4950
} from './api.types';
5051

5152
export interface IPhasedCommandHandlerOptions {
@@ -389,6 +390,51 @@ function tryEnableBuildStatusWebSocketServer(
389390

390391
const { hooks } = command;
391392

393+
let invalidateOperation: ((operation: Operation, reason: string) => void) | undefined;
394+
395+
const operationEnabledStates: Map<string, OperationEnabledState> = new Map();
396+
hooks.createOperations.tap(
397+
{
398+
name: PLUGIN_NAME,
399+
stage: Infinity
400+
},
401+
(operations: Set<Operation>, context: ICreateOperationsContext) => {
402+
const potentiallyAffectedOperations: Set<Operation> = new Set();
403+
for (const operation of operations) {
404+
const { associatedProject } = operation;
405+
if (context.projectsInUnknownState.has(associatedProject)) {
406+
potentiallyAffectedOperations.add(operation);
407+
}
408+
}
409+
for (const operation of potentiallyAffectedOperations) {
410+
for (const consumer of operation.consumers) {
411+
potentiallyAffectedOperations.add(consumer);
412+
}
413+
414+
const { name } = operation;
415+
const expectedState: OperationEnabledState | undefined = operationEnabledStates.get(name);
416+
switch (expectedState) {
417+
case 'affected':
418+
operation.enabled = true;
419+
break;
420+
case 'never':
421+
operation.enabled = false;
422+
break;
423+
case 'changed':
424+
operation.enabled = context.projectsInUnknownState.has(operation.associatedProject);
425+
break;
426+
case undefined:
427+
// Use the original value.
428+
break;
429+
}
430+
}
431+
432+
invalidateOperation = context.invalidateOperation;
433+
434+
return operations;
435+
}
436+
);
437+
392438
hooks.beforeExecuteOperations.tap(
393439
PLUGIN_NAME,
394440
(operationsToExecute: Map<Operation, IOperationExecutionResult>): void => {
@@ -452,6 +498,27 @@ function tryEnableBuildStatusWebSocketServer(
452498
break;
453499
}
454500

501+
case 'set-enabled-states': {
502+
const { enabledStateByOperationName } = parsedMessage;
503+
for (const [name, state] of Object.entries(enabledStateByOperationName)) {
504+
operationEnabledStates.set(name, state);
505+
}
506+
break;
507+
}
508+
509+
case 'invalidate': {
510+
const { operationNames } = parsedMessage;
511+
const operationNameSet: Set<string> = new Set(operationNames);
512+
if (invalidateOperation && operationStates) {
513+
for (const operation of operationStates.keys()) {
514+
if (operationNameSet.has(operation.name)) {
515+
invalidateOperation(operation, 'WebSocket');
516+
}
517+
}
518+
}
519+
break;
520+
}
521+
455522
default: {
456523
// Unknown message. Ignore.
457524
}

0 commit comments

Comments
 (0)