Skip to content

[rush] Initial implementation of Rush alerts feature #4801

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a0f79ea
feat: rush alerts - print alerts
g-chao Jun 19, 2024
b23b0e5
feat: rush alerts prototype
g-chao Jun 23, 2024
190bd8d
chore: rush change
g-chao Jun 23, 2024
b21836d
chore: fix bugs
g-chao Jun 23, 2024
dc52f2d
Simplify experiment name
octogonz Jul 1, 2024
3a43c58
Rename alerts-config.json to rush-alerts.json
octogonz Jul 1, 2024
9cce646
Draft of `rush init` template
octogonz Jul 1, 2024
39fdcbb
feat: register rushAlerts template in rush init action
g-chao Jul 1, 2024
f8e970f
feat: add rush alerts schema
g-chao Jul 3, 2024
fd65498
Merge remote-tracking branch 'remotes/origin/main' into chao/rush-not…
octogonz Jul 16, 2024
427644c
Fix an issue where rush-alerts.json could not be loaded when the "$sc…
octogonz Jul 17, 2024
f02cd42
Use node-core-library APIs which provide better error handling
octogonz Jul 17, 2024
25cd43f
Fix a bug where RushAlerts did not work correctly when optional setti…
octogonz Jul 17, 2024
cb69ea9
Invoke the conditionScript with robust error-checking and report its …
octogonz Jul 17, 2024
70e3739
Improve error reporting for various script mistakes
octogonz Jul 17, 2024
e830cbe
More debugging
octogonz Jul 17, 2024
b21b210
Add a failing test
octogonz Jul 17, 2024
1157c12
Fix a word-wrapping edge case
octogonz Jul 17, 2024
eb7f918
Fix a bug where the "message" setting didn't accept both a string and…
octogonz Jul 17, 2024
5885220
Improve formatting of messages
octogonz Jul 17, 2024
a9074de
Improve changelog wording and prepare to publish a MINOR release of Rush
octogonz Jul 17, 2024
4bcf85b
rush change
octogonz Jul 17, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@microsoft/rush",
"comment": "(EXPERIMENTAL) Initial implementation of Rush alerts feature",
"type": "none"
}
],
"packageName": "@microsoft/rush"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/terminal",
"comment": "Improve the PrintUtilities API to handle an edge case when word-wrapping a final line",
"type": "patch"
}
],
"packageName": "@rushstack/terminal"
}
2 changes: 1 addition & 1 deletion common/config/rush/version-policies.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
"policyName": "rush",
"definitionName": "lockStepVersion",
"version": "5.129.7",
"nextBump": "patch",
"nextBump": "minor",
"mainProject": "@microsoft/rush"
}
]
3 changes: 3 additions & 0 deletions common/reviews/api/rush-lib.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ export interface IExperimentsJson {
noChmodFieldInTarHeaderNormalization?: boolean;
omitImportersFromPreventManualShrinkwrapChanges?: boolean;
printEventHooksOutputToConsole?: boolean;
rushAlerts?: boolean;
useIPCScriptsInWatchMode?: boolean;
usePnpmFrozenLockfileForRushInstall?: boolean;
usePnpmLockfileOnlyThenFrozenLockfileForRushUpdate?: boolean;
Expand Down Expand Up @@ -1357,6 +1358,8 @@ export class RushConstants {
static readonly projectShrinkwrapFilename: 'shrinkwrap-deps.json';
static readonly rebuildCommandName: 'rebuild';
static readonly repoStateFilename: 'repo-state.json';
static readonly rushAlertsConfigFilename: 'rush-alerts.json';
static readonly rushAlertsStateFilename: 'rush-alerts-state.json';
static readonly rushJsonFilename: 'rush.json';
static readonly rushLogsFolderName: 'rush-logs';
static readonly rushPackageName: '@microsoft/rush';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,19 @@
* If set to true, Rush will generate a `project-impact-graph.yaml` file in the repository root during `rush update`.
*/
/*[LINE "HYPOTHETICAL"]*/ "generateProjectImpactGraphDuringRushUpdate": true,

/**
* If true, when running in watch mode, Rush will check for phase scripts named `_phase:<name>:ipc` and run them instead
* of `_phase:<name>` if they exist. The created child process will be provided with an IPC channel and expected to persist
* across invocations.
*/
/*[LINE "HYPOTHETICAL"]*/ "useIPCScriptsInWatchMode": true
/*[LINE "HYPOTHETICAL"]*/ "useIPCScriptsInWatchMode": true,

/**
* (UNDER DEVELOPMENT) The Rush alerts feature provides a way to send announcements to engineers
* working in the monorepo, by printing directly in the user's shell window when they invoke Rush commands.
* This ensures that important notices will be seen by anyone doing active development, since people often
* ignore normal discussion group messages or don't know to subscribe.
*/
/*[LINE "HYPOTHETICAL"]*/ "rushAlerts": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* This configuration file manages the Rush alerts feature.
* More documentation is available on the Rush website: https://rushjs.io
*/
{
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush-alerts.schema.json",

/**
* Settings such as `startTime` and `endTime` will use this timezone.
* If omitted, the default timezone is UTC (`+00:00`).
*/
"timezone": "-08:00",

/**
* An array of alert messages and conditions for triggering them.
*/
"alerts": [
/*[BEGIN "DEMO"]*/
{
/**
* When the alert is displayed, this title will appear at the top of the message box.
* It should be a single line of text, as concise as possible.
*/
"title": "Node.js upgrade soon!",

/**
* When the alert is displayed, this text appears in the message box. To make the
* JSON file more readable, if the text is longer than one line, you can instead provide
* an array of strings that will be concatenated. Your text may contain newline characters,
* but generally this is unnecessary because word-wrapping is automatically applied.
*/
"message": [
"This Thursday, we will complete the Node.js version upgrade. Any pipelines that",
" still have not upgraded will be temporarily disabled."
],

/**
* (OPTIONAL) To avoid spamming users, the `title` and `message` settings should be kept
* as concise as possible. If you need to provide more detail, use this setting to
* print a hyperlink to a web page with further guidance.
*/
/*[LINE "HYPOTHETICAL"]*/ "detailsUrl": "https://contoso.com/team-wiki/2024-01-01-migration",

/**
* (OPTIONAL) If `startTime` is specified, then this alert will not be shown prior to
* that time.
*
* Keep in mind that the alert is not guaranteed to be shown at this time, or at all:
* Alerts are only displayed after a Rush command has triggered fetching of the
* latest rush-alerts.json configuration. Also, display of alerts is throttled to
* avoid spamming the user with too many messages. If you need to test your alert,
* set the environment variable `RUSH_ALERTS_DEBUG=1` to disable throttling.
*
* The `startTime` should be specified as `YYYY-MM-DD HH:MM` using 24 hour time format,
* or else `YYYY-MM-DD` in which case the time part will be `00:00` (start of that day).
* The time zone is obtained from the `timezone` setting above.
*/
/*[LINE "HYPOTHETICAL"]*/ "startTime": "2024-01-01 15:00",

/**
* (OPTIONAL) This alert will not be shown if the current time is later than `endTime`.
* The format is the same as `startTime`.
*/
/*[LINE "HYPOTHETICAL"]*/ "endTime": "2024-01-05",

/**
* (OPTIONAL) The filename of a script that determines whether this alert can be shown,
* found in the "common/config/rush/alert-scripts" folder. The script must define
* a CommonJS export named `canShowAlert` that returns a boolean value, for example:
*
* ```
* module.exports.canShowAlert = function () {
* // (your logic goes here)
* return true;
* }
* ```
*
* Rush will invoke this script with the working directory set to the monorepo root folder,
* with no guarantee that `rush install` has been run. To ensure up-to-date alerts, Rush
* may fetch and checkout the "common/config/rush-alerts" folder in an unpredictable temporary
* path. Therefore, your script should avoid importing dependencies from outside its folder,
* generally be kept as simple and reliable and quick as possible. For more complex conditions,
* we suggest to design some other process that prepares a data file or environment variable
* that can be cheaply checked by your condition script.
*/
/*[LINE "HYPOTHETICAL"]*/ "conditionScript": "rush-alexrt-node-upgrade.js"
}
/*[END "DEMO"]*/
]
}
8 changes: 8 additions & 0 deletions libraries/rush-lib/src/api/ExperimentsConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ export interface IExperimentsJson {
* across invocations.
*/
useIPCScriptsInWatchMode?: boolean;

/**
* (UNDER DEVELOPMENT) The Rush alerts feature provides a way to send announcements to engineers
* working in the monorepo, by printing directly in the user's shell window when they invoke Rush commands.
* This ensures that important notices will be seen by anyone doing active development, since people often
* ignore normal discussion group messages or don't know to subscribe.
*/
rushAlerts?: boolean;
}

const _EXPERIMENTS_JSON_SCHEMA: JsonSchema = JsonSchema.fromLoadedObject(schemaJson);
Expand Down
3 changes: 2 additions & 1 deletion libraries/rush-lib/src/api/RushConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ const knownRushConfigFilenames: string[] = [
RushConstants.versionPoliciesFilename,
RushConstants.rushPluginsConfigFilename,
RushConstants.pnpmConfigFilename,
RushConstants.subspacesConfigFilename
RushConstants.subspacesConfigFilename,
RushConstants.rushAlertsConfigFilename
];

/**
Expand Down
28 changes: 28 additions & 0 deletions libraries/rush-lib/src/cli/RushCommandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import { RushSession } from '../pluginFramework/RushSession';
import { PhasedScriptAction } from './scriptActions/PhasedScriptAction';
import type { IBuiltInPluginConfiguration } from '../pluginFramework/PluginLoader/BuiltInPluginLoader';
import { InitSubspaceAction } from './actions/InitSubspaceAction';
import { RushAlerts } from '../utilities/RushAlerts';

/**
* Options for `RushCommandLineParser`.
Expand Down Expand Up @@ -222,6 +223,33 @@ export class RushCommandLineParser extends CommandLineParser {

try {
await this._wrapOnExecuteAsync();

try {
const { configuration: experiments } = this.rushConfiguration.experimentsConfiguration;
if (experiments.rushAlerts) {
this._terminal.writeDebugLine('Checking Rush alerts...');
// Print out alerts if have after each successful command actions
const rushAlerts: RushAlerts = new RushAlerts({
rushConfiguration: this.rushConfiguration,
terminal: this._terminal
});
if (await rushAlerts.isAlertsStateUpToDateAsync()) {
await rushAlerts.printAlertsAsync();
} else {
await rushAlerts.retrieveAlertsAsync();
}
}
} catch (error) {
if (error instanceof AlreadyReportedError) {
throw error;
}
// Generally the RushAlerts implementation should handle its own error reporting; if not,
// clarify the source, since the Rush Alerts behavior is nondeterministic and may not repro easily:
this._terminal.writeErrorLine(`\nAn unexpected error was encountered by the Rush alerts feature:`);
this._terminal.writeErrorLine(error.message);
throw new AlreadyReportedError();
}

// If we make it here, everything went fine, so reset the exit code back to 0
process.exitCode = 0;
} catch (error) {
Expand Down
3 changes: 2 additions & 1 deletion libraries/rush-lib/src/cli/actions/InitAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ export class InitAction extends BaseConfiglessRushAction {
];

const experimentalTemplateFilePaths: string[] = [
`common/config/rush/${RushConstants.subspacesConfigFilename}`
`common/config/rush/${RushConstants.subspacesConfigFilename}`,
'common/config/rush/rush-alerts.json'
];

if (this._experimentsParameter.value) {
Expand Down
10 changes: 10 additions & 0 deletions libraries/rush-lib/src/logic/RushConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,4 +325,14 @@ export class RushConstants {
* The filename for the last link flag
*/
public static readonly lastLinkFlagFilename: 'last-link' = 'last-link';

/**
* The filename for the Rush alerts config file.
*/
public static readonly rushAlertsConfigFilename: 'rush-alerts.json' = 'rush-alerts.json';

/**
* The filename for the machine-generated file that tracks state for Rush alerts.
*/
public static readonly rushAlertsStateFilename: 'rush-alerts-state.json' = 'rush-alerts-state.json';
}
4 changes: 4 additions & 0 deletions libraries/rush-lib/src/schemas/experiments.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@
"useIPCScriptsInWatchMode": {
"description": "If true, when running in watch mode, Rush will check for phase scripts named `_phase:<name>:ipc` and run them instead of `_phase:<name>` if they exist. The created child process will be provided with an IPC channel and expected to persist across invocations.",
"type": "boolean"
},
"rushAlerts": {
"description": "(UNDER DEVELOPMENT) The Rush alerts feature provides a way to send announcements to engineers working in the monorepo, by printing directly in the user's shell window when they invoke Rush commands. This ensures that important notices will be seen by anyone doing active development, since people often ignore normal discussion group messages or don't know to subscribe.",
"type": "boolean"
}
},
"additionalProperties": false
Expand Down
67 changes: 67 additions & 0 deletions libraries/rush-lib/src/schemas/rush-alerts.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Rush rush-alerts.json file",
"description": "This configuration file provides settings to rush alerts feature.",
"type": "object",
"additionalProperties": false,
"properties": {
"$schema": {
"description": "Part of the JSON Schema standard, this optional keyword declares the URL of the schema that the file conforms to. Editors may download the schema and use it to perform syntax highlighting.",
"type": "string"
},

"timezone": {
"description": "Settings such as `startTime` and `endTime` will use this timezone.\n\nIf omitted, the default timezone is UTC (`+00:00`).",
"type": "string"
},
"alerts": {
"description": "An array of alert messages and conditions for triggering them.",
"items": {
"$ref": "#/definitions/IAlert"
},
"type": "array"
}
},
"definitions": {
"IAlert": {
"type": "object",
"properties": {
"title": {
"description": "When the alert is displayed, this title will appear at the top of the message box. It should be a single line of text, as concise as possible.",
"type": "string"
},
"message": {
"description": "When the alert is displayed, this text appears in the message box.\n\nTo make the JSON file more readable, if the text is longer than one line, you can instead provide an array of strings that will be concatenated.\n\nYour text may contain newline characters, but generally this is unnecessary because word-wrapping is automatically applied.",
"oneOf": [
{
"type": "string"
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
},
"detailsUrl": {
"description": "(OPTIONAL) To avoid spamming users, the `title` and `message` settings should be kept as concise as possible.\n\nIf you need to provide more detail, use this setting to print a hyperlink to a web page with further guidance.",
"type": "string"
},
"startTime": {
"description": "(OPTIONAL) If `startTime` is specified, then this alert will not be shown prior to that time.\n\nKeep in mind that the alert is not guaranteed to be shown at this time, or at all. Alerts are only displayed after a Rush command has triggered fetching of the latest rush-alerts.json configuration.\n\nAlso, display of alerts is throttled to avoid spamming the user with too many messages.\n\nIf you need to test your alert, set the environment variable `RUSH_ALERTS_DEBUG=1` to disable throttling.\n\nThe `startTime` should be specified as `YYYY-MM-DD HH:MM` using 24 hour time format, or else `YYYY-MM-DD` in which case the time part will be `00:00` (start of that day). The time zone is obtained from the `timezone` setting above.",
"type": "string"
},
"endTime": {
"description": "(OPTIONAL) This alert will not be shown if the current time is later than `endTime`.\n\nThe format is the same as `startTime`.",
"type": "string"
},
"conditionScript": {
"description": "(OPTIONAL) The filename of a script that determines whether this alert can be shown, found in the 'common/config/rush/alert-scripts' folder.\n\nThe script must define a CommonJS export named `canShowAlert` that returns a boolean value, for example:\n\n`module.exports.canShowAlert = function () { // (your logic goes here) return true; }`.\n\nRush will invoke this script with the working directory set to the monorepo root folder, with no guarantee that `rush install` has been run.\n\nTo ensure up-to-date alerts, Rush may fetch and checkout the 'common/config/rush-alerts' folder in an unpredictable temporary path. Therefore, your script should avoid importing dependencies from outside its folder, generally be kept as simple and reliable and quick as possible.\n\nFor more complex conditions, we suggest to design some other process that prepares a data file or environment variable that can be cheaply checked by your condition script.",
"type": "string"
}
},
"required": ["title", "message"]
}
}
}
Loading
Loading