Skip to content

Commit

Permalink
[rush-serve-plugin] Add WebSocket support
Browse files Browse the repository at this point in the history
  • Loading branch information
dmichon-msft committed Sep 14, 2023
1 parent 85ad03c commit 747a9ec
Show file tree
Hide file tree
Showing 10 changed files with 502 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@microsoft/rush",
"comment": "Add support for optional build status notifications over a web socket connection to `@rushstack/rush-serve-plugin`.",
"type": "none"
}
],
"packageName": "@microsoft/rush"
}
12 changes: 8 additions & 4 deletions common/config/rush/nonbrowser-approved-packages.json
Original file line number Diff line number Diff line change
Expand Up @@ -406,14 +406,14 @@
"name": "compression",
"allowedCategories": [ "libraries" ]
},
{
"name": "cors",
"allowedCategories": [ "libraries" ]
},
{
"name": "constructs",
"allowedCategories": [ "tests" ]
},
{
"name": "cors",
"allowedCategories": [ "libraries" ]
},
{
"name": "css-loader",
"allowedCategories": [ "libraries", "tests" ]
Expand Down Expand Up @@ -826,6 +826,10 @@
"name": "wordwrap",
"allowedCategories": [ "libraries" ]
},
{
"name": "ws",
"allowedCategories": [ "libraries" ]
},
{
"name": "xmldoc",
"allowedCategories": [ "libraries" ]
Expand Down
27 changes: 15 additions & 12 deletions common/config/rush/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion common/config/rush/repo-state.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
{
"pnpmShrinkwrapHash": "dbae06252c7f680ad367e1c99ccce18a2dc2edc7",
"pnpmShrinkwrapHash": "cd89d52eeac3e8b3a95f21cb20fdd6422c83084d",
"preferredVersionsHash": "1926a5b12ac8f4ab41e76503a0d1d0dccc9c0e06"
}
74 changes: 74 additions & 0 deletions rush-plugins/rush-serve-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,77 @@ What happens:
- Rush uses the configuration in the aforementioned files to configure an Express server to serve project outputs as static (but not cached) content
- When a change happens to a source file, Rush's normal watch-mode machinery will rebuild all affected project phases, resulting in new files on disk
- The next time one of these files is requested, Rush will serve the new version. Optionally, may support signals for automatic refresh.

## Live Build Status via Web Socket

This plugin also provides a web socket server that notifies clients of the build status in real time. To use the server, configure the `buildStatusWebSocketPath` option in `common/config/rush-plugins/rush-serve-plugin.json`. Specifying `/` will make the web socket server available at `wss://localhost:<port>/`.

The recommended way to connect to the web socket is to serve a static HTML page from the serve plugin using the `globalRouting` configuration.

To use the socket:
```ts
import type {
IWebSocketEventMessage,
IOperationInfo,
IRushSessionInfo,
ReadableOperationStatus
} from '@rushstack/rush-serve-plugin/api';

const socket: WebSocket = new WebSocket(`wss://${self.location.host}${buildStatusWebSocketPath}`);

const operationsByName: Map<string, IOperationInfo> = new Map();
let buildStatus: ReadableOperationStatus = 'Ready';

function updateOperations(operations): void {
for (const operation of operations) {
operationsByName.set(operation.name, operation);
}

for (const [operationName, operation] of operationsByName) {
// Do something with the operation
}
}

function updateSessionInfo(sessionInfo: IRushSessionInfo): void {
const { actionName, repositoryIdentifier } = sessionInfo;
}

function updateBuildStatus(newStatus: ReadableOperationStatus): void {
buildStatus = newStatus;
// Render
}

socket.addEventListener('message', (ev) => {
const message: IWebSocketEventMessage = JSON.parse(ev.data);

switch (message.event) {
case 'before-execute': {
const { operations } = message;
updateOperations(operations);
updateBuildStatus('Executing');
break;
}

case 'status-change': {
const { operations } = message;
updateOperations(operations);
break;
}

case 'after-execute': {
const { status } = message;
updateBuildStatus(status);
break;
}

case 'sync': {
operationsByName.clear();
const { operations, status, sessionInfo } = message;
updateOperations(operations);
updateSessionInfo(sessionInfo);
updateBuildStatus(status);
break;
}
}
});
```
27 changes: 24 additions & 3 deletions rush-plugins/rush-serve-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"type": "git",
"directory": "rush-plugins/rush-serve-plugin"
},
"main": "lib/index.js",
"main": "lib-commonjs/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "heft test --clean",
Expand All @@ -25,7 +25,8 @@
"compression": "~1.7.4",
"cors": "~2.8.5",
"express": "4.18.1",
"http2-express-bridge": "~1.0.7"
"http2-express-bridge": "~1.0.7",
"ws": "~8.14.1"
},
"devDependencies": {
"@rushstack/eslint-config": "workspace:*",
Expand All @@ -35,6 +36,26 @@
"@types/cors": "~2.8.12",
"@types/express": "4.17.13",
"@types/heft-jest": "1.0.1",
"@types/node": "14.18.36"
"@types/node": "14.18.36",
"@types/ws": "8.5.5"
},
"exports": {
".": {
"require": "./lib/index.js",
"types": "./dist/rush-serve-plugin.d.ts"
},
"./api": {
"types": "./lib/api.types.d.ts"
}
},
"typesVersions": {
"*": {
".": [
"dist/rush-serve-plugin.d.ts"
],
"api": [
"lib/api.types.d.ts"
]
}
}
}
14 changes: 13 additions & 1 deletion rush-plugins/rush-serve-plugin/src/RushServePlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,21 @@ export interface IRushServePluginOptions {
* The names of phased commands to which the plugin should be applied.
*/
phasedCommands: ReadonlyArray<string>;

/**
* The URL path at which to host the web socket connection for monitoring build status. If not specified, the web socket interface will not be enabled.
*/
buildStatusWebSocketPath?: string;

/**
* The name of a parameter that Rush is configured to use to pass a port number to underlying operations.
* If specified, the plugin will ensure the value is synchronized with the port used for its server.
*/
portParameterLongName?: string | undefined;

/**
* Routing rules for files that are associated with the entire workspace, rather than a single project (e.g. files output by Rush plugins).
*/
globalRouting?: IGlobalRoutingRuleJson[];
}

Expand All @@ -43,11 +52,13 @@ export class RushServePlugin implements IRushPlugin {
private readonly _phasedCommands: Set<string>;
private readonly _portParameterLongName: string | undefined;
private readonly _globalRoutingRules: IGlobalRoutingRuleJson[];
private readonly _buildStatusWebSocketPath: string | undefined;

public constructor(options: IRushServePluginOptions) {
this._phasedCommands = new Set(options.phasedCommands);
this._portParameterLongName = options.portParameterLongName;
this._globalRoutingRules = options.globalRouting ?? [];
this._buildStatusWebSocketPath = options.buildStatusWebSocketPath;
}

public apply(rushSession: RushSession, rushConfiguration: RushConfiguration): void {
Expand All @@ -73,7 +84,8 @@ export class RushServePlugin implements IRushPlugin {
rushConfiguration,
command,
portParameterLongName: this._portParameterLongName,
globalRoutingRules
globalRoutingRules,
buildStatusWebSocketPath: this._buildStatusWebSocketPath
});
};

Expand Down
Loading

0 comments on commit 747a9ec

Please sign in to comment.