diff --git a/.vscode/launch.json b/.vscode/launch.json
index 00b6f4ef11e..7736568abfe 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -44,6 +44,30 @@
"type": "node",
"request": "attach",
"port": 5858
+ },
+ {
+ "name": "Run Extension",
+ "type": "extensionHost",
+ "request": "launch",
+ "args": [
+ "--extensionDevelopmentPath=${workspaceFolder}/apps/vscode-extension"
+ ],
+ "outFiles": [
+ "${workspaceFolder}/apps/vscode-extension/dist/**/*.js"
+ ]
+ },
+ {
+ "name": "Extension Tests",
+ "type": "extensionHost",
+ "request": "launch",
+ "args": [
+ "--extensionDevelopmentPath=${workspaceFolder}/apps/vscode-extension",
+ "--extensionTestsPath=${workspaceFolder}/apps/vscode-extension/out/test/suite/index"
+ ],
+ "outFiles": [
+ "${workspaceFolder}/apps/vscode-extension/out/**/*.js",
+ "${workspaceFolder}/apps/vscode-extension/dist/**/*.js"
+ ]
}
]
}
diff --git a/apps/vscode-extension/.eslintrc.json b/apps/vscode-extension/.eslintrc.json
new file mode 100644
index 00000000000..5dfecab7e78
--- /dev/null
+++ b/apps/vscode-extension/.eslintrc.json
@@ -0,0 +1,18 @@
+{
+ "root": true,
+ "parser": "@typescript-eslint/parser",
+ "parserOptions": {
+ "ecmaVersion": 6,
+ "sourceType": "module"
+ },
+ "plugins": ["@typescript-eslint"],
+ "rules": {
+ "@typescript-eslint/naming-convention": "warn",
+ "@typescript-eslint/semi": "warn",
+ "curly": "warn",
+ "eqeqeq": "warn",
+ "no-throw-literal": "warn",
+ "semi": "off"
+ },
+ "ignorePatterns": ["out", "dist", "**/*.d.ts"]
+}
diff --git a/apps/vscode-extension/.vscodeignore b/apps/vscode-extension/.vscodeignore
new file mode 100644
index 00000000000..c6136798a58
--- /dev/null
+++ b/apps/vscode-extension/.vscodeignore
@@ -0,0 +1,13 @@
+.vscode/**
+.vscode-test/**
+out/**
+node_modules/**
+src/**
+.gitignore
+.yarnrc
+webpack.config.js
+vsc-extension-quickstart.md
+**/tsconfig.json
+**/.eslintrc.json
+**/*.map
+**/*.ts
diff --git a/apps/vscode-extension/CHANGELOG.md b/apps/vscode-extension/CHANGELOG.md
new file mode 100644
index 00000000000..4d8271f69ec
--- /dev/null
+++ b/apps/vscode-extension/CHANGELOG.md
@@ -0,0 +1,9 @@
+# Change Log
+
+All notable changes to the "rush" extension will be documented in this file.
+
+Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
+
+## [Unreleased]
+
+- Initial release
\ No newline at end of file
diff --git a/apps/vscode-extension/README.md b/apps/vscode-extension/README.md
new file mode 100644
index 00000000000..45fb53ed231
--- /dev/null
+++ b/apps/vscode-extension/README.md
@@ -0,0 +1,71 @@
+# rush README
+
+This is the README for your extension "rush". After writing up a brief description, we recommend including the following sections.
+
+## Features
+
+Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file.
+
+For example if there is an image subfolder under your extension project workspace:
+
+\!\[feature X\]\(images/feature-x.png\)
+
+> Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow.
+
+## Requirements
+
+If you have any requirements or dependencies, add a section describing those and how to install and configure them.
+
+## Extension Settings
+
+Include if your extension adds any VS Code settings through the `contributes.configuration` extension point.
+
+For example:
+
+This extension contributes the following settings:
+
+* `myExtension.enable`: Enable/disable this extension.
+* `myExtension.thing`: Set to `blah` to do something.
+
+## Known Issues
+
+Calling out known issues can help limit users opening duplicate issues against your extension.
+
+## Release Notes
+
+Users appreciate release notes as you update your extension.
+
+### 1.0.0
+
+Initial release of ...
+
+### 1.0.1
+
+Fixed issue #.
+
+### 1.1.0
+
+Added features X, Y, and Z.
+
+---
+
+## Following extension guidelines
+
+Ensure that you've read through the extensions guidelines and follow the best practices for creating your extension.
+
+* [Extension Guidelines](https://code.visualstudio.com/api/references/extension-guidelines)
+
+## Working with Markdown
+
+You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts:
+
+* Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux).
+* Toggle preview (`Shift+Cmd+V` on macOS or `Shift+Ctrl+V` on Windows and Linux).
+* Press `Ctrl+Space` (Windows, Linux, macOS) to see a list of Markdown snippets.
+
+## For more information
+
+* [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown)
+* [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/)
+
+**Enjoy!**
diff --git a/apps/vscode-extension/package.json b/apps/vscode-extension/package.json
new file mode 100644
index 00000000000..b0c2661b626
--- /dev/null
+++ b/apps/vscode-extension/package.json
@@ -0,0 +1,258 @@
+{
+ "name": "@rushstack/vscode-extension",
+ "displayName": "Rush",
+ "description": "Official extension for Rush",
+ "version": "0.0.1",
+ "engines": {
+ "vscode": "^1.71.0"
+ },
+ "categories": [
+ "Other"
+ ],
+ "activationEvents": [
+ "onView:rushProjects"
+ ],
+ "main": "./dist/extension.js",
+ "contributes": {
+ "commands": [
+ {
+ "command": "rush.revealProjectInExplorer",
+ "title": "Reveal in Explorer view",
+ "category": "Rush",
+ "icon": "$(folder)"
+ },
+ {
+ "command": "rush.activateProjectForResource",
+ "title": "Activate project in Rush",
+ "category": "Rush",
+ "icon": "$(add)"
+ },
+ {
+ "command": "rush.activateProject",
+ "title": "Activate",
+ "category": "Rush",
+ "icon": "$(add)"
+ },
+ {
+ "command": "rush.deactivateProject",
+ "title": "Deactivate",
+ "category": "Rush",
+ "icon": "$(remove)"
+ },
+ {
+ "command": "rush.enableWatch",
+ "title": "Enable watch mode",
+ "category": "Rush",
+ "icon": "$(eye)"
+ },
+ {
+ "command": "rush.disableWatch",
+ "title": "Disable watch mode",
+ "category": "Rush",
+ "icon": "$(eye-closed)"
+ },
+ {
+ "command": "rush.runAction",
+ "title": "Run",
+ "category": "Rush",
+ "icon": "$(run)"
+ },
+ {
+ "command": "rush.setWatchAction",
+ "title": "Set as watch command",
+ "category": "Rush",
+ "icon": "$(eye)"
+ },
+ {
+ "command": "rush.refreshProjects",
+ "title": "Refresh projects",
+ "category": "Rush",
+ "icon": "$(refresh)"
+ },
+ {
+ "command": "rush.refreshPhases",
+ "title": "Refresh phases",
+ "category": "Rush",
+ "icon": "$(refresh)"
+ }
+ ],
+ "configuration": {
+ "title": "Rush",
+ "properties": {
+ "rush.useWorkspaceRushVersion": {
+ "type": "boolean",
+ "default": true,
+ "description": "Specifies whether to use the version of Rush from the workspace"
+ }
+ }
+ },
+ "menus": {
+ "editor/title": [
+ {
+ "command": "rush.activateProjectForResource",
+ "when": "rush.enabled"
+ }
+ ],
+ "view/item/context": [
+ {
+ "command": "rush.revealProjectInExplorer",
+ "when": "view == rushProjects && viewItem =~ /^project:/",
+ "group": "inline"
+ },
+ {
+ "command": "rush.revealProjectInExplorer",
+ "when": "view == rushProjects && viewItem =~ /^project:/"
+ },
+ {
+ "command": "rush.activateProject",
+ "when": "view == rushProjects && viewItem =~ /^project:(Available|Included)/",
+ "group": "inline"
+ },
+ {
+ "command": "rush.deactivateProject",
+ "when": "view == rushProjects && viewItem =~ /^project:Active/",
+ "group": "inline"
+ },
+ {
+ "command": "rush.activateProject",
+ "when": "view == rushProjects && viewItem == group:Included && rush.includedProjects.count > 0",
+ "group": "inline"
+ },
+ {
+ "command": "rush.activateProject",
+ "when": "view == rushProjects && viewItem == group:Available && rush.availableProjects.count > 0",
+ "group": "inline"
+ },
+ {
+ "command": "rush.deactivateProject",
+ "when": "view == rushProjects && viewItem == group:Active && rush.activeProjects.count > 0",
+ "group": "inline"
+ },
+ {
+ "command": "rush.activateProject",
+ "when": "view == rushProjects && viewItem =~ /^project:(Available|Included)/"
+ },
+ {
+ "command": "rush.deactivateProject",
+ "when": "view == rushProjects && viewItem =~ /^project:Active/"
+ },
+ {
+ "command": "rush.activatePhase",
+ "when": "view == rushPhases && viewItem =~ /^phase:(Available|Included)/",
+ "group": "inline"
+ },
+ {
+ "command": "rush.deactivatePhase",
+ "when": "view == rushPhases && viewItem =~ /^phase:Active/",
+ "group": "inline"
+ },
+ {
+ "command": "rush.activatePhase",
+ "when": "view == rushPhases && viewItem == group:Included && rush.includedPhases.count > 0",
+ "group": "inline"
+ },
+ {
+ "command": "rush.activatePhase",
+ "when": "view == rushPhases && viewItem == group:Available && rush.availablePhases.count > 0",
+ "group": "inline"
+ },
+ {
+ "command": "rush.deactivatePhase",
+ "when": "view == rushPhases && viewItem == group:Active && rush.activePhases.count > 0",
+ "group": "inline"
+ }
+ ],
+ "view/title": [
+ {
+ "command": "rush.enableWatch",
+ "when": "view == rushProjects && rush.watcher == 'sleep' && rush.watchAction != ''",
+ "group": "navigation"
+ },
+ {
+ "command": "rush.disableWatch",
+ "when": "view == rushProjects && rush.watcher == 'starting'",
+ "group": "navigation"
+ },
+ {
+ "command": "rush.disableWatch",
+ "when": "view == rushProjects && rush.watcher == 'ready'",
+ "group": "navigation"
+ },
+ {
+ "command": "rush.refreshProjects",
+ "when": "view == rushProjects",
+ "group": "navigation"
+ },
+ {
+ "command": "rush.refreshPhases",
+ "when": "view == rushPhases",
+ "group": "navigation"
+ }
+ ]
+ },
+ "views": {
+ "rush": [
+ {
+ "id": "rushProjects",
+ "name": "Projects"
+ },
+ {
+ "id": "rushPhases",
+ "name": "Phases"
+ }
+ ]
+ },
+ "viewsWelcome": [
+ {
+ "view": "rushProjects",
+ "contents": "Welcome to Rush!\n\nIf this view does not load, make sure that this workspace is using Rush and that Rush has been installed by entering `rush install`. Then reload this window."
+ },
+ {
+ "view": "rushPhases",
+ "contents": "Available phases for your workspace's interactive build command will appear here."
+ }
+ ],
+ "viewsContainers": {
+ "activitybar": [
+ {
+ "id": "rush",
+ "title": "Rush",
+ "icon": "resources/rush.svg"
+ }
+ ]
+ }
+ },
+ "scripts": {
+ "vscode:prepublish": "pnpm run package",
+ "compile": "webpack",
+ "watch": "webpack --watch",
+ "package": "webpack --mode production --devtool hidden-source-map",
+ "compile-tests": "tsc -p . --outDir out",
+ "watch-tests": "tsc -p . -w --outDir out",
+ "pretest": "pnpm run compile-tests && pnpm run compile && pnpm run lint",
+ "lint": "eslint src --ext ts",
+ "test": "node ./out/test/runTest.js"
+ },
+ "dependencies": {
+ "@rushstack/rush-sdk": "workspace:*"
+ },
+ "devDependencies": {
+ "@types/vscode": "^1.71.0",
+ "@types/glob": "7.1.1",
+ "@types/mocha": "^9.1.1",
+ "@types/node": "12.20.24",
+ "@typescript-eslint/eslint-plugin": "~5.30.3",
+ "@typescript-eslint/parser": "~5.30.3",
+ "eslint": "~8.7.0",
+ "glob": "~7.0.5",
+ "mocha": "^10.0.0",
+ "typescript": "~4.7.4",
+ "ts-loader": "9.4.0",
+ "webpack": "~5.68.0",
+ "webpack-cli": "~4.10.0",
+ "@microsoft/rush": "workspace:*",
+ "@microsoft/rush-lib": "workspace:*",
+ "@rushstack/webpack-preserve-dynamic-require-plugin": "workspace:*",
+ "@vscode/test-electron": "^2.1.5"
+ }
+}
diff --git a/apps/vscode-extension/resources/rush.svg b/apps/vscode-extension/resources/rush.svg
new file mode 100644
index 00000000000..f63fa999ab2
--- /dev/null
+++ b/apps/vscode-extension/resources/rush.svg
@@ -0,0 +1,69 @@
+
+
+
+
\ No newline at end of file
diff --git a/apps/vscode-extension/src/dataProviders/phases/PhaseDataProvider.ts b/apps/vscode-extension/src/dataProviders/phases/PhaseDataProvider.ts
new file mode 100644
index 00000000000..3659a1e8dc5
--- /dev/null
+++ b/apps/vscode-extension/src/dataProviders/phases/PhaseDataProvider.ts
@@ -0,0 +1,291 @@
+import * as vscode from 'vscode';
+
+import type * as Rush from '@rushstack/rush-sdk';
+
+export type StateGroupName = 'Active' | 'Included' | 'Available';
+
+export interface IPhaseDataProviderParams {
+ workspaceRoot: string;
+ extensionContext: vscode.ExtensionContext;
+ rush: typeof Rush;
+}
+
+export class PhaseDataProvider implements vscode.TreeDataProvider {
+ private _workspaceRoot: string;
+ private _onDidChangeTreeData: vscode.EventEmitter<
+ PhaseStateGroup | Phase | (PhaseStateGroup | Phase)[] | undefined
+ >;
+
+ private _stateGroups: PhaseStateGroup[];
+ private _groupByState: Record;
+ private _rush: typeof Rush;
+
+ private _phasesByName: Map;
+ private _extensionContext: vscode.ExtensionContext;
+
+ constructor(params: IPhaseDataProviderParams) {
+ const { workspaceRoot, rush, extensionContext } = params;
+
+ this._workspaceRoot = workspaceRoot;
+ this._rush = rush;
+ this._extensionContext = extensionContext;
+
+ this._onDidChangeTreeData = new vscode.EventEmitter();
+
+ const groupByState: Record = {
+ Active: new PhaseStateGroup('Active', [
+ new Message('To get started, activate phases from this repository.')
+ ]),
+ Included: new PhaseStateGroup('Included', [
+ new Message('Dependencies of active phases will appear here.')
+ ]),
+ Available: new PhaseStateGroup('Available', [])
+ };
+
+ this._phasesByName = new Map();
+ this._groupByState = groupByState;
+
+ this._stateGroups = Object.values(groupByState);
+ }
+
+ public async refresh(): Promise {
+ this._phasesByName.clear();
+
+ await Promise.all([
+ vscode.commands.executeCommand('setContext', 'rush.activePhases.count', 0),
+ vscode.commands.executeCommand('setContext', 'rush.includedPhases.count', 0),
+ vscode.commands.executeCommand('setContext', 'rush.availablePhases.count', 0)
+ ]);
+
+ console.log('Updating tree data', 3, Date.now());
+ this._onDidChangeTreeData.fire(this._stateGroups);
+ console.log('Updated tree data', 3, Date.now());
+
+ if (!this._rush.RushCommandLineParser) {
+ return;
+ }
+
+ const commandLineParser = new this._rush.RushCommandLineParser({
+ cwd: this._workspaceRoot,
+ excludeDefaultActions: true
+ });
+
+ const command = commandLineParser.tryGetAction('start') as
+ | { _knownPhases: ReadonlyMap }
+ | undefined;
+ if (!command) {
+ console.warn(`This repository does not define an action named 'start'`);
+ return;
+ }
+
+ const phaseByRushPhase = new Map();
+ const sortedPhases: Phase[] = Array.from(command._knownPhases.values(), (rushPhase: Rush.IPhase) => {
+ const phase = new Phase(rushPhase);
+ phaseByRushPhase.set(rushPhase, phase);
+ return phase;
+ });
+
+ sortedPhases.sort(phaseNameSort);
+
+ for (const phase of sortedPhases) {
+ for (const dependency of phase.rushPhase.dependencies.self) {
+ const dependencyPhase = phaseByRushPhase.get(dependency);
+ if (dependencyPhase) {
+ phase.dependencies.add(dependencyPhase);
+ }
+ }
+ this._phasesByName.set(phase.rushPhase.name, phase);
+ }
+
+ const activePhases: Phase[] = [];
+
+ const activePhasesState =
+ this._extensionContext.workspaceState.get<{ [key: string]: true }>('rush.activePhases');
+
+ if (activePhasesState) {
+ for (const projectName of Object.keys(activePhasesState)) {
+ const phase = this._phasesByName.get(projectName);
+
+ if (phase) {
+ activePhases.push(phase);
+ }
+ }
+ }
+
+ await this.toggleActivePhases(activePhases, true);
+ }
+
+ public getPhasesByState(state: StateGroupName): ReadonlyArray {
+ return this._groupByState[state].children;
+ }
+
+ public async toggleActivePhases(togglePhases: Phase[], force?: boolean): Promise {
+ console.log('Toggling active phases', togglePhases.length, Date.now());
+
+ if (togglePhases.length === 0) {
+ return;
+ }
+
+ const destinationStateGroup =
+ force ?? togglePhases[0].stateGroupName !== 'Active' ? 'Active' : 'Available';
+ for (const phase of togglePhases) {
+ phase.stateGroupName = destinationStateGroup;
+ }
+
+ const activePhasesState: {
+ [key: string]: true;
+ } = {};
+
+ const queue: Set = new Set();
+ for (const phase of this._phasesByName.values()) {
+ if (phase.stateGroupName === 'Active') {
+ queue.add(phase);
+ activePhasesState[phase.rushPhase.name] = true;
+ } else if (phase.stateGroupName === 'Included') {
+ phase.stateGroupName = 'Available';
+ }
+ }
+
+ this._extensionContext.workspaceState.update('rush.activePhases', activePhasesState);
+
+ for (const phase of queue) {
+ if (phase.stateGroupName === 'Available') {
+ phase.stateGroupName = 'Included';
+ }
+
+ for (const dependency of phase.dependencies) {
+ queue.add(dependency);
+ }
+ }
+
+ const groupByState = this._groupByState;
+ for (const group of this._stateGroups) {
+ group.children.length = 0;
+ }
+
+ for (const phase of this._phasesByName.values()) {
+ groupByState[phase.stateGroupName].children.push(phase);
+ }
+
+ await Promise.all([
+ vscode.commands.executeCommand(
+ 'setContext',
+ 'rush.activePhases.count',
+ groupByState.Active.children.length
+ ),
+ vscode.commands.executeCommand(
+ 'setContext',
+ 'rush.includedPhases.count',
+ groupByState.Included.children.length
+ ),
+ vscode.commands.executeCommand(
+ 'setContext',
+ 'rush.availablePhases.count',
+ groupByState.Available.children.length
+ )
+ ]);
+
+ console.log('Updating tree data', 3, Date.now());
+ this._onDidChangeTreeData.fire(this._stateGroups);
+ console.log('Updated tree data', 3, Date.now());
+
+ console.log('Toggled active phases', togglePhases.length, Date.now());
+ }
+
+ public get onDidChangeTreeData(): vscode.Event<
+ PhaseStateGroup | Phase | (PhaseStateGroup | Phase)[] | undefined
+ > {
+ return this._onDidChangeTreeData.event;
+ }
+
+ public getChildren(element?: PhaseStateGroup | Phase | undefined): (PhaseStateGroup | Phase | Message)[] {
+ if (!element) {
+ return this._stateGroups;
+ } else if (element instanceof PhaseStateGroup) {
+ const children: Phase[] = element.children;
+
+ if (children.length === 0) {
+ return element.defaultChildren;
+ }
+
+ return children;
+ }
+
+ return [];
+ }
+
+ public getParent(element: Phase): vscode.ProviderResult {
+ return undefined;
+ }
+
+ public getTreeItem(element: Phase | PhaseStateGroup | Message): vscode.TreeItem {
+ return element.renderTreeItem();
+ }
+}
+
+export class Phase {
+ public readonly rushPhase: Rush.IPhase;
+ public readonly dependencies: Set;
+
+ public stateGroupName: StateGroupName;
+
+ constructor(rushPhase: Rush.IPhase) {
+ this.rushPhase = rushPhase;
+ this.dependencies = new Set();
+
+ this.stateGroupName = 'Available';
+ }
+
+ public renderTreeItem(): vscode.TreeItem {
+ const phaseName: string = this.rushPhase.name.replace(/^_phase:/, '');
+ const treeItem = new vscode.TreeItem(phaseName, vscode.TreeItemCollapsibleState.None);
+
+ treeItem.contextValue = `phase:${this.stateGroupName}`;
+
+ treeItem.id = `phase:${phaseName}`;
+
+ return treeItem;
+ }
+}
+
+export class PhaseStateGroup {
+ public readonly groupName: StateGroupName;
+ public readonly defaultChildren: Message[];
+ public readonly children: Phase[];
+
+ constructor(groupName: StateGroupName, defaultChildren: Message[]) {
+ this.groupName = groupName;
+ this.defaultChildren = defaultChildren;
+ this.children = [];
+ }
+
+ public renderTreeItem(): vscode.TreeItem {
+ const treeItem = new vscode.TreeItem(this.groupName, vscode.TreeItemCollapsibleState.Expanded);
+
+ treeItem.contextValue = `group:${this.groupName}`;
+
+ treeItem.id = `group:${this.groupName}`;
+
+ return treeItem;
+ }
+}
+
+export class Message {
+ public readonly label: string;
+
+ constructor(label: string) {
+ this.label = label;
+ }
+
+ public renderTreeItem(): vscode.TreeItem {
+ const treeItem = new vscode.TreeItem(this.label, vscode.TreeItemCollapsibleState.None);
+
+ treeItem.id = `message:${this.label}`;
+
+ return treeItem;
+ }
+}
+
+function phaseNameSort(a: Phase, b: Phase): number {
+ return a.rushPhase.name.localeCompare(b.rushPhase.name);
+}
diff --git a/apps/vscode-extension/src/dataProviders/projects/ProjectDataProvider.ts b/apps/vscode-extension/src/dataProviders/projects/ProjectDataProvider.ts
new file mode 100644
index 00000000000..291be5c0db6
--- /dev/null
+++ b/apps/vscode-extension/src/dataProviders/projects/ProjectDataProvider.ts
@@ -0,0 +1,639 @@
+import * as vscode from 'vscode';
+import type * as Rush from '@rushstack/rush-sdk';
+import * as path from 'path';
+
+export type StateGroupName = 'Active' | 'Included' | 'Available';
+
+export interface IProjectDataProviderParams {
+ workspaceRoot: string;
+ extensionContext: vscode.ExtensionContext;
+ rush: typeof Rush;
+}
+
+interface IStatusAndActive {
+ status: Rush.OperationStatus;
+ active: boolean;
+}
+
+export class ProjectDataProvider
+ implements
+ vscode.TreeDataProvider,
+ vscode.FileDecorationProvider
+{
+ private _workspaceRoot: string;
+ private _onDidChangeTreeData: vscode.EventEmitter<
+ StateGroup | Project | OperationPhase | (StateGroup | Project | OperationPhase)[] | undefined
+ >;
+ private _onDidChangeFileDecorations: vscode.EventEmitter;
+
+ private _stateGroups: StateGroup[];
+
+ private _activeGroup: StateGroup;
+ private _includedGroup: StateGroup;
+ private _availableGroup: StateGroup;
+ private _rush: typeof Rush;
+
+ private _projectsByName: Map;
+ private _extensionContext: vscode.ExtensionContext;
+
+ private _lookup: Rush.LookupByPath;
+
+ constructor(params: IProjectDataProviderParams) {
+ const { workspaceRoot, rush, extensionContext } = params;
+
+ this._workspaceRoot = workspaceRoot;
+ this._rush = rush;
+ this._extensionContext = extensionContext;
+
+ this._onDidChangeTreeData = new vscode.EventEmitter();
+ this._onDidChangeFileDecorations = new vscode.EventEmitter();
+
+ this._activeGroup = new StateGroup('Active');
+ this._includedGroup = new StateGroup('Included');
+ this._availableGroup = new StateGroup('Available');
+
+ this._projectsByName = new Map();
+ this._lookup = new rush.LookupByPath();
+
+ this._stateGroups = [this._activeGroup, this._includedGroup, this._availableGroup];
+ }
+
+ public async refresh(): Promise {
+ this._activeGroup.projects.clear();
+ this._includedGroup.projects.clear();
+ this._availableGroup.projects.clear();
+ this._projectsByName.clear();
+
+ await Promise.all([
+ vscode.commands.executeCommand('setContext', 'rush.activeProjects.count', 0),
+ vscode.commands.executeCommand('setContext', 'rush.includedProjects.count', 0),
+ vscode.commands.executeCommand('setContext', 'rush.availableProjects.count', 0)
+ ]);
+
+ console.log('Updating tree data', 3, Date.now());
+ this._onDidChangeTreeData.fire([this._activeGroup, this._includedGroup, this._availableGroup]);
+ console.log('Updated tree data', 3, Date.now());
+
+ if (!this._rush.RushConfiguration) {
+ return;
+ }
+
+ const rushConfigurationFile = this._rush.RushConfiguration.tryFindRushJsonLocation({
+ startingFolder: this._workspaceRoot
+ });
+
+ if (!rushConfigurationFile) {
+ return;
+ }
+
+ const rushConfiguration = this._rush.RushConfiguration.loadFromConfigurationFile(rushConfigurationFile);
+
+ if (!rushConfiguration) {
+ return;
+ }
+
+ this._lookup = new this._rush.LookupByPath(undefined, path.sep);
+
+ vscode.commands.executeCommand('setContext', 'rush.enabled', true);
+
+ for (const rushProject of rushConfiguration.projects) {
+ const project = new Project(rushProject);
+
+ this._projectsByName.set(rushProject.packageName, project);
+ this._availableGroup.projects.add(project);
+
+ this._lookup.setItem(vscode.Uri.file(rushProject.projectFolder).fsPath, project);
+ }
+
+ await Promise.all([
+ vscode.commands.executeCommand(
+ 'setContext',
+ 'rush.activeProjects.count',
+ this._activeGroup.projects.size
+ ),
+ vscode.commands.executeCommand(
+ 'setContext',
+ 'rush.includedProjects.count',
+ this._includedGroup.projects.size
+ ),
+ vscode.commands.executeCommand(
+ 'setContext',
+ 'rush.availableProjects.count',
+ this._availableGroup.projects.size
+ )
+ ]);
+
+ const activeProjects: Project[] = [];
+
+ const activeProjectsState =
+ this._extensionContext.workspaceState.get<{ [key: string]: true }>('rush.activeProjects');
+
+ if (activeProjectsState) {
+ for (const projectName of Object.keys(activeProjectsState)) {
+ const project = this._projectsByName.get(projectName);
+
+ if (project) {
+ activeProjects.push(project);
+ }
+ }
+ }
+
+ await this.toggleActiveProjects(activeProjects, true);
+ }
+
+ public getActiveProjects(): Project[] {
+ return Array.from(this._activeGroup.projects);
+ }
+
+ public getIncludedProjects(): Project[] {
+ return Array.from(this._includedGroup.projects);
+ }
+
+ public updateProjectPhases(operationStatuses: Rush.ITransferableOperationStatus[]): void {
+ console.log('Updating project phases', operationStatuses.length, Date.now());
+
+ const updatedProjects = new Set();
+
+ for (const operationStatus of operationStatuses) {
+ const {
+ operation: { project: projectName, phase }
+ } = operationStatus;
+
+ if (!projectName || !phase) {
+ continue;
+ }
+
+ const project = this._projectsByName.get(projectName);
+
+ if (!project) {
+ continue;
+ }
+
+ let operationPhase = project.phases.get(phase);
+
+ if (!operationPhase) {
+ operationPhase = new OperationPhase(project.rushProject, operationStatus);
+ project.phases.set(phase, operationPhase);
+ }
+
+ operationPhase.operationStatus = operationStatus;
+
+ updatedProjects.add(project);
+ }
+
+ console.log('Updating tree data', updatedProjects.size, Date.now());
+ if (updatedProjects.size > 10) {
+ this._onDidChangeTreeData.fire(undefined);
+ this._onDidChangeFileDecorations.fire(undefined);
+ } else {
+ this._onDidChangeTreeData.fire(Array.from(updatedProjects));
+
+ const resourceUris: vscode.Uri[] = [];
+
+ for (const operationStatus of operationStatuses) {
+ resourceUris.push(
+ vscode.Uri.parse(
+ `rush://${operationStatus.operation.project!}?${operationStatus.operation.phase!}`,
+ true
+ )
+ );
+ }
+
+ for (const project of updatedProjects) {
+ resourceUris.push(vscode.Uri.parse(`rush://${project.rushProject.packageName}`, true));
+ resourceUris.push(vscode.Uri.file(project.rushProject.projectFolder));
+ }
+
+ this._onDidChangeFileDecorations.fire(resourceUris);
+ }
+ console.log('Updated tree data', updatedProjects.size, Date.now());
+
+ console.log('Updated project phases', operationStatuses.length, Date.now());
+ }
+
+ public getProjectForResource(resource: vscode.Uri): Project | undefined {
+ return this._lookup.findChildPath(resource.fsPath);
+ }
+
+ public async toggleActiveProjects(toggleProjects: Project[], force?: boolean): Promise {
+ console.log('Toggling active projects', toggleProjects.length, Date.now());
+
+ if (toggleProjects.length === 0) {
+ return;
+ }
+
+ this._availableGroup.projects.clear();
+
+ for (const project of this._projectsByName.values()) {
+ project.stateGroupName = 'Available';
+ this._availableGroup.projects.add(project);
+ }
+
+ const direction = force ?? !this._activeGroup.projects.has(toggleProjects[0]);
+
+ if (direction) {
+ for (const toggleProject of toggleProjects) {
+ this._activeGroup.projects.add(toggleProject);
+ }
+ } else {
+ for (const toggleProject of toggleProjects) {
+ this._activeGroup.projects.delete(toggleProject);
+ }
+ }
+
+ const seenProjects = new Set();
+
+ const queue: Project[] = [];
+
+ const activeProjectsState: {
+ [key: string]: true;
+ } = {};
+
+ for (const activeProject of this._activeGroup.projects) {
+ activeProject.stateGroupName = 'Active';
+ seenProjects.add(activeProject.rushProject.packageName);
+ this._availableGroup.projects.delete(activeProject);
+ queue.push(activeProject);
+ activeProjectsState[activeProject.rushProject.packageName] = true;
+ }
+
+ this._extensionContext.workspaceState.update('rush.activeProjects', activeProjectsState);
+
+ this._includedGroup.projects.clear();
+
+ let included: Project | undefined;
+
+ while ((included = queue.shift())) {
+ for (const dependency of included.rushProject.dependencyProjects) {
+ if (!seenProjects.has(dependency.packageName)) {
+ const dependencyProject = this._projectsByName.get(dependency.packageName);
+
+ if (dependencyProject) {
+ dependencyProject.stateGroupName = 'Included';
+ this._includedGroup.projects.add(dependencyProject);
+ this._availableGroup.projects.delete(dependencyProject);
+ queue.push(dependencyProject);
+ }
+
+ seenProjects.add(dependency.packageName);
+ }
+ }
+ }
+
+ await Promise.all([
+ vscode.commands.executeCommand(
+ 'setContext',
+ 'rush.activeProjects.count',
+ this._activeGroup.projects.size
+ ),
+ vscode.commands.executeCommand(
+ 'setContext',
+ 'rush.includedProjects.count',
+ this._includedGroup.projects.size
+ ),
+ vscode.commands.executeCommand(
+ 'setContext',
+ 'rush.availableProjects.count',
+ this._availableGroup.projects.size
+ )
+ ]);
+
+ console.log('Updating tree data', 3, Date.now());
+ this._onDidChangeTreeData.fire([this._activeGroup, this._includedGroup, this._availableGroup]);
+ console.log('Updated tree data', 3, Date.now());
+
+ console.log('Toggled active projects', toggleProjects.length, Date.now());
+ }
+
+ public get onDidChangeTreeData(): vscode.Event<
+ StateGroup | Project | OperationPhase | (StateGroup | Project | OperationPhase)[] | undefined
+ > {
+ return this._onDidChangeTreeData.event;
+ }
+
+ public get onDidChangeFileDecorations(): vscode.Event {
+ return this._onDidChangeFileDecorations.event;
+ }
+
+ public provideFileDecoration(
+ uri: vscode.Uri,
+ token: vscode.CancellationToken
+ ): vscode.ProviderResult {
+ if (uri.scheme !== 'rush') {
+ return undefined;
+ }
+
+ const projectName = `${uri.authority}${uri.path}`;
+ const phaseName = uri.query;
+
+ const project = this._projectsByName.get(projectName);
+
+ if (project) {
+ const phase = phaseName ? project.phases.get(phaseName) : undefined;
+ const { status, active } = phase?.operationStatus ?? getOverallStatus(project.phases.values());
+
+ const { badge, color } = getStatusIndicators(status, active);
+
+ return new vscode.FileDecoration(badge, undefined, new vscode.ThemeColor(color));
+ }
+
+ return undefined;
+ }
+
+ public getChildren(
+ element?: StateGroup | Project | OperationPhase | undefined
+ ): (StateGroup | Project | OperationPhase | Message)[] {
+ if (!element) {
+ return this._stateGroups;
+ } else if (element instanceof StateGroup) {
+ if (element.groupName === 'Active') {
+ if (this._activeGroup.projects.size === 0) {
+ return [new Message('To get started, activate projects from this repository.')];
+ }
+
+ return Array.from(this._activeGroup.projects).sort((a: Project, b: Project) =>
+ a.rushProject.packageName.localeCompare(b.rushProject.packageName)
+ );
+ } else if (element.groupName === 'Included') {
+ if (this._includedGroup.projects.size === 0) {
+ return [new Message('Dependencies of active projects will appear here.')];
+ }
+ return Array.from(this._includedGroup.projects).sort((a: Project, b: Project) =>
+ a.rushProject.packageName.localeCompare(b.rushProject.packageName)
+ );
+ } else if (element.groupName === 'Available') {
+ return Array.from(this._availableGroup.projects).sort((a: Project, b: Project) =>
+ a.rushProject.packageName.localeCompare(b.rushProject.packageName)
+ );
+ }
+ } else if (element instanceof Project) {
+ return Array.from(element.phases.values());
+ }
+
+ return [];
+ }
+
+ public getParent(element: Project): vscode.ProviderResult {
+ return undefined;
+ }
+
+ public getTreeItem(element: Project | OperationPhase | StateGroup | Message): vscode.TreeItem {
+ if (element instanceof Message) {
+ const treeItem = new vscode.TreeItem(element.label, vscode.TreeItemCollapsibleState.None);
+
+ treeItem.id = `message:${element.label}`;
+
+ return treeItem;
+ } else if (element instanceof Project) {
+ const treeItem = new vscode.TreeItem(
+ element.rushProject.packageName,
+ element.phases.size > 0
+ ? vscode.TreeItemCollapsibleState.Collapsed
+ : vscode.TreeItemCollapsibleState.None
+ );
+
+ treeItem.contextValue = `project:${element.stateGroupName}`;
+ const packageName: string = element.rushProject.packageName;
+ treeItem.resourceUri = vscode.Uri.parse(`rush://${packageName}`, true);
+ treeItem.tooltip = element.rushProject.packageJson.description;
+
+ const status = getOverallStatus(element.phases.values());
+
+ const { icon, description, color } = getStatusIndicators(status.status, status.active);
+
+ treeItem.description = description;
+ treeItem.iconPath = new vscode.ThemeIcon(icon, new vscode.ThemeColor(color));
+
+ treeItem.id = `project:${element.rushProject.packageName}`;
+
+ return treeItem;
+ } else if (element instanceof OperationPhase) {
+ const packageName: string = element.rushProject.packageName;
+ const phaseName: string = element.operationStatus.operation.phase!;
+
+ const treeItem = new vscode.TreeItem(phaseName, vscode.TreeItemCollapsibleState.None);
+
+ treeItem.id = `phase:${packageName}/${phaseName}`;
+
+ const { icon, description, color } = getStatusIndicators(
+ element.operationStatus.status,
+ element.operationStatus.active
+ );
+
+ treeItem.iconPath = new vscode.ThemeIcon(icon, new vscode.ThemeColor(color));
+ treeItem.description = description;
+ treeItem.tooltip = `${element.operationStatus.hash}`;
+ if (element.operationStatus.operation.logFilePath) {
+ const uri = vscode.Uri.file(element.operationStatus.operation.logFilePath);
+ treeItem.resourceUri = vscode.Uri.parse(`rush://${packageName}?${phaseName}`, true);
+ treeItem.command = {
+ command: 'vscode.open',
+ title: 'Open',
+ arguments: [uri]
+ };
+ }
+
+ return treeItem;
+ } else if (element instanceof StateGroup) {
+ const treeItem = new vscode.TreeItem(element.groupName, vscode.TreeItemCollapsibleState.Expanded);
+
+ treeItem.contextValue = `group:${element.groupName}`;
+
+ treeItem.id = `group:${element.groupName}`;
+
+ return treeItem;
+ }
+
+ throw new Error('Unknown element type!');
+ }
+}
+
+export class Project {
+ public readonly rushProject: Rush.RushConfigurationProject;
+
+ public stateGroupName: StateGroupName;
+
+ public phases: Map;
+
+ constructor(rushProject: Rush.RushConfigurationProject) {
+ this.rushProject = rushProject;
+
+ this.stateGroupName = 'Available';
+
+ this.phases = new Map();
+ }
+}
+
+export class OperationPhase {
+ public readonly rushProject: Rush.RushConfigurationProject;
+ public operationStatus: Rush.ITransferableOperationStatus;
+
+ constructor(
+ rushProject: Rush.RushConfigurationProject,
+ operationStatus: Rush.ITransferableOperationStatus
+ ) {
+ this.operationStatus = operationStatus;
+ this.rushProject = rushProject;
+ }
+}
+
+export class StateGroup {
+ public readonly groupName: StateGroupName;
+
+ public readonly projects: Set;
+
+ constructor(groupName: StateGroupName) {
+ this.groupName = groupName;
+
+ this.projects = new Set();
+ }
+}
+
+export class Message {
+ public readonly label: string;
+
+ constructor(label: string) {
+ this.label = label;
+ }
+}
+
+function getOverallStatus(statuses: Iterable): IStatusAndActive {
+ const histogram: {
+ [P in Rush.OperationStatus]: number;
+ } = {
+ 'FROM CACHE': 0,
+ 'NO OP': 0,
+ 'SUCCESS WITH WARNINGS': 0,
+ BLOCKED: 0,
+ EXECUTING: 0,
+ FAILURE: 0,
+ READY: 0,
+ SKIPPED: 0,
+ SUCCESS: 0
+ };
+
+ let isActive: boolean = false;
+
+ for (const {
+ operationStatus: { status, active }
+ } of statuses) {
+ histogram[status]++;
+ if (active) {
+ isActive = true;
+ }
+ }
+
+ return {
+ status: mergeStatus(histogram),
+ active: isActive
+ };
+}
+
+function mergeStatus(histogram: { [P in Rush.OperationStatus]: number }): Rush.OperationStatus {
+ if (histogram.EXECUTING > 0) {
+ return 'EXECUTING' as Rush.OperationStatus;
+ } else if (histogram.READY > 0) {
+ return 'READY' as Rush.OperationStatus;
+ } else if (histogram.FAILURE > 0) {
+ return 'FAILURE' as Rush.OperationStatus;
+ } else if (histogram.BLOCKED > 0) {
+ return 'BLOCKED' as Rush.OperationStatus;
+ } else if (histogram['SUCCESS WITH WARNINGS'] > 0) {
+ return 'SUCCESS WITH WARNINGS' as Rush.OperationStatus;
+ } else if (histogram.SUCCESS > 0) {
+ return 'SUCCESS' as Rush.OperationStatus;
+ } else if (histogram['FROM CACHE'] > 0) {
+ return 'FROM CACHE' as Rush.OperationStatus;
+ } else if (histogram.SKIPPED > 0) {
+ return 'SKIPPED' as Rush.OperationStatus;
+ } else {
+ return 'NO OP' as Rush.OperationStatus;
+ }
+}
+
+function getStatusIndicators(
+ status: Rush.OperationStatus,
+ active: boolean
+): {
+ icon: string;
+ description: string;
+ color: string;
+ badge: string | undefined;
+} {
+ let icon: string;
+ let description: string;
+ let color: string;
+ let badge: string | undefined;
+
+ if (!active) {
+ return {
+ description: 'Out of scope',
+ icon: 'circle-outline',
+ color: 'disabledForeground',
+ badge: undefined
+ };
+ }
+
+ switch (status) {
+ case 'SUCCESS':
+ description = 'Succeeded';
+ icon = 'pass';
+ color = 'testing.iconPassed';
+ badge = 'S';
+ break;
+ case 'FROM CACHE':
+ description = 'Succeeded (from cache)';
+ icon = 'pass';
+ color = 'testing.iconPassed';
+ badge = 'SC';
+ break;
+ case 'NO OP':
+ description = 'Not configured';
+ icon = 'circle-outline';
+ color = 'disabledForeground';
+ break;
+ case 'EXECUTING':
+ description = 'Executing';
+ icon = 'sync~spin';
+ color = 'notebookStatusRunningIcon.foreground';
+ badge = 'E';
+ break;
+ case 'SUCCESS WITH WARNINGS':
+ description = 'Succeeded with warnings';
+ icon = 'warning';
+ color = 'testing.iconFailed';
+ badge = 'SW';
+ break;
+ case 'SKIPPED':
+ description = 'Skipped';
+ icon = 'testing-skipped-icon';
+ color = 'testing.iconSkipped';
+ break;
+ case 'FAILURE':
+ description = 'Failed';
+ icon = 'error';
+ color = 'testing.iconFailed';
+ badge = 'F';
+ break;
+ case 'BLOCKED':
+ description = 'Blocked';
+ icon = 'stop';
+ color = 'testing.iconQueued';
+ badge = 'B';
+ break;
+ default:
+ case 'READY':
+ description = 'Pending';
+ icon = 'clock';
+ color = 'notebookStatusRunningIcon.foreground';
+ badge = 'P';
+ break;
+ }
+
+ return {
+ icon,
+ description,
+ color,
+ badge
+ };
+}
diff --git a/apps/vscode-extension/src/extension.ts b/apps/vscode-extension/src/extension.ts
new file mode 100644
index 00000000000..2687d5f88d7
--- /dev/null
+++ b/apps/vscode-extension/src/extension.ts
@@ -0,0 +1,375 @@
+// The module 'vscode' contains the VS Code extensibility API
+// Import the module and reference it with the alias vscode in your code below
+import type * as Rush from '@rushstack/rush-sdk';
+import type * as RushLib from '@microsoft/rush-lib';
+import * as vscode from 'vscode';
+import {
+ ProjectDataProvider,
+ Project,
+ StateGroup,
+ OperationPhase
+} from './dataProviders/projects/ProjectDataProvider';
+import {
+ PhaseDataProvider,
+ Phase,
+ PhaseStateGroup as PhaseStateGroup
+} from './dataProviders/phases/PhaseDataProvider';
+import * as path from 'path';
+
+declare const global: NodeJS.Global &
+ typeof globalThis & {
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ ___rush___workingDirectory?: string;
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ ___rush___rushLibModule?: typeof RushLib;
+ };
+
+export async function activate(extensionContext: vscode.ExtensionContext) {
+ const workspaceRoot =
+ vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0
+ ? vscode.workspace.workspaceFolders[0].uri.fsPath
+ : undefined;
+
+ vscode.commands.executeCommand('setContext', 'rush.enabled', false);
+
+ if (!workspaceRoot) {
+ return;
+ }
+
+ const useWorkspaceRushVersion =
+ vscode.workspace.getConfiguration().get('rush.useWorkspaceRushVersion') ?? true;
+
+ if (useWorkspaceRushVersion) {
+ global.___rush___workingDirectory = workspaceRoot;
+ } else {
+ global.___rush___rushLibModule = await import('@microsoft/rush-lib');
+ }
+
+ const rush = await import('@rushstack/rush-sdk');
+
+ if (!rush.RushCommandLineParser || !rush.PhasedCommandWorkerController) {
+ // This version of Rush is not supported by VS Code.
+ return;
+ }
+
+ let worker: Rush.PhasedCommandWorkerController | undefined;
+
+ const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 50);
+ statusBarItem.command = {
+ command: 'rush.disableWatch',
+ title: 'Disable watch'
+ };
+
+ extensionContext.subscriptions.push(statusBarItem);
+
+ const rushDiagnostics = vscode.languages.createDiagnosticCollection('rush');
+ extensionContext.subscriptions.push(rushDiagnostics);
+
+ vscode.commands.executeCommand('setContext', 'rush.watcher', 'sleep');
+
+ function updateWorker(): void {
+ if (!worker) {
+ return;
+ }
+
+ rushDiagnostics.clear();
+
+ const activeProjects = projectDataProvider.getActiveProjects();
+ const activeProjectPhases = phaseDataProvider.getPhasesByState('Active');
+
+ const activeProjectNames = new Set(
+ activeProjects.map((project: Project) => project.rushProject.packageName)
+ );
+ const activePhaseNames = new Set(activeProjectPhases.map((phase: Phase) => phase.rushPhase.name));
+
+ const graph = worker.getGraph();
+
+ const activeOperations = graph.filter(
+ (operation: Rush.ITransferableOperation) =>
+ !!operation.project &&
+ activeProjectNames.has(operation.project) &&
+ !!operation.phase &&
+ activePhaseNames.has(operation.phase)
+ );
+
+ worker.update(activeOperations);
+ }
+
+ const projectDataProvider = new ProjectDataProvider({
+ workspaceRoot,
+ rush,
+ extensionContext: extensionContext
+ });
+
+ const phaseDataProvider = new PhaseDataProvider({
+ workspaceRoot,
+ rush,
+ extensionContext: extensionContext
+ });
+
+ const projectView = vscode.window.createTreeView('rushProjects', {
+ treeDataProvider: projectDataProvider,
+ canSelectMany: true,
+ showCollapseAll: true
+ });
+
+ extensionContext.subscriptions.push(projectView);
+
+ const phaseView = vscode.window.createTreeView('rushPhases', {
+ treeDataProvider: phaseDataProvider,
+ canSelectMany: true,
+ showCollapseAll: true
+ });
+
+ extensionContext.subscriptions.push(phaseView);
+
+ const decorationProvider = vscode.window.registerFileDecorationProvider(projectDataProvider);
+
+ extensionContext.subscriptions.push(decorationProvider);
+
+ const openProjectCommand = vscode.commands.registerCommand(
+ 'rush.revealProjectInExplorer',
+ (project: Project | StateGroup | OperationPhase) => {
+ if (project instanceof Project) {
+ vscode.commands.executeCommand(
+ 'revealInExplorer',
+ vscode.Uri.file(path.join(project.rushProject.projectFolder, 'package.json'))
+ );
+ }
+ }
+ );
+
+ extensionContext.subscriptions.push(openProjectCommand);
+
+ const activeProjectForResourceCommand = vscode.commands.registerCommand(
+ 'rush.activateProjectForResource',
+ async (resource: vscode.Uri) => {
+ const project = projectDataProvider.getProjectForResource(resource);
+
+ if (project) {
+ await projectDataProvider.toggleActiveProjects([project], true);
+ updateWorker();
+ }
+ }
+ );
+
+ extensionContext.subscriptions.push(activeProjectForResourceCommand);
+
+ const refreshProjectsCommand = vscode.commands.registerCommand('rush.refreshProjects', async () => {
+ await projectDataProvider.refresh();
+ });
+
+ extensionContext.subscriptions.push(refreshProjectsCommand);
+
+ const refreshPhasesCommand = vscode.commands.registerCommand('rush.refreshPhases', async () => {
+ await phaseDataProvider.refresh();
+ });
+
+ extensionContext.subscriptions.push(refreshPhasesCommand);
+
+ const activateProjectCommand = vscode.commands.registerCommand(
+ 'rush.activateProject',
+ async (
+ contextProject: Project | StateGroup | OperationPhase,
+ selectedProjects: (Project | StateGroup | OperationPhase)[] = [contextProject]
+ ) => {
+ if (contextProject instanceof Project) {
+ await projectDataProvider.toggleActiveProjects(selectedProjects as Project[], true);
+ } else if (contextProject instanceof StateGroup) {
+ if (contextProject.groupName === 'Included' || contextProject.groupName === 'Available') {
+ await projectDataProvider.toggleActiveProjects(Array.from(contextProject.projects), true);
+ }
+ }
+
+ updateWorker();
+ }
+ );
+
+ extensionContext.subscriptions.push(activateProjectCommand);
+
+ const deactivateProjectCommand = vscode.commands.registerCommand(
+ 'rush.deactivateProject',
+ async (
+ contextProject: Project | StateGroup | OperationPhase,
+ selectedProjects: (Project | StateGroup | OperationPhase)[] = [contextProject]
+ ) => {
+ if (contextProject instanceof Project) {
+ await projectDataProvider.toggleActiveProjects(selectedProjects as Project[], false);
+ } else if (contextProject instanceof StateGroup) {
+ if (contextProject.groupName === 'Active') {
+ await projectDataProvider.toggleActiveProjects(Array.from(contextProject.projects), false);
+ }
+ }
+
+ updateWorker();
+ }
+ );
+
+ extensionContext.subscriptions.push(deactivateProjectCommand);
+
+ const activatePhaseCommand = vscode.commands.registerCommand(
+ 'rush.activateProject',
+ async (
+ contextPhase: Phase | PhaseStateGroup,
+ selectedPhases: (Phase | PhaseStateGroup)[] = [contextPhase]
+ ) => {
+ if (contextPhase instanceof Phase) {
+ await phaseDataProvider.toggleActivePhases(selectedPhases as Phase[], true);
+ } else if (contextPhase instanceof StateGroup) {
+ if (contextPhase.groupName === 'Included' || contextPhase.groupName === 'Available') {
+ await phaseDataProvider.toggleActivePhases(contextPhase.children, true);
+ }
+ }
+
+ updateWorker();
+ }
+ );
+
+ extensionContext.subscriptions.push(activatePhaseCommand);
+
+ const deactivatePhaseCommand = vscode.commands.registerCommand(
+ 'rush.deactivateProject',
+ async (
+ contextPhase: Phase | PhaseStateGroup,
+ selectedPhases: (Phase | PhaseStateGroup)[] = [contextPhase]
+ ) => {
+ if (contextPhase instanceof Phase) {
+ await phaseDataProvider.toggleActivePhases(selectedPhases as Phase[], false);
+ } else if (contextPhase instanceof StateGroup) {
+ if (contextPhase.groupName === 'Active') {
+ await phaseDataProvider.toggleActivePhases(contextPhase.children, false);
+ }
+ }
+
+ updateWorker();
+ }
+ );
+
+ extensionContext.subscriptions.push(deactivatePhaseCommand);
+
+ const enableWatchCommand = vscode.commands.registerCommand('rush.enableWatch', async () => {
+ vscode.window.showInformationMessage('Initializing the Rush watcher.');
+ vscode.commands.executeCommand('setContext', 'rush.watcher', 'starting');
+
+ statusBarItem.text = `$(sync~spin) Rush: initializing watcher`;
+ statusBarItem.show();
+
+ worker = new rush.PhasedCommandWorkerController(['--debug', 'start'], {
+ cwd: workspaceRoot,
+ onStatusUpdates: (statuses: Rush.ITransferableOperationStatus[]) => {
+ projectDataProvider.updateProjectPhases(statuses);
+ if (worker?.state === 'executing') {
+ const { activeOperationCount, pendingOperationCount } = worker;
+ statusBarItem.text = `$(sync~spin) Rush: running start (${
+ activeOperationCount - pendingOperationCount
+ }/${activeOperationCount})`;
+ }
+
+ const diagnosticsByPath: Map = new Map();
+ for (const { diagnostics } of statuses) {
+ if (diagnostics?.length) {
+ for (const { file, line, column, message, severity, tag } of diagnostics) {
+ let collection = diagnosticsByPath.get(file);
+ if (!collection) {
+ diagnosticsByPath.set(file, (collection = []));
+ }
+ const diagnostic = new vscode.Diagnostic(
+ new vscode.Range(line - 1, column - 1, line - 1, column),
+ `${tag} ${message}`,
+ severity === 'warning' ? vscode.DiagnosticSeverity.Warning : vscode.DiagnosticSeverity.Error
+ );
+ collection.push(diagnostic);
+ }
+ }
+ }
+
+ for (const [file, diagnostics] of diagnosticsByPath) {
+ rushDiagnostics.set(vscode.Uri.file(`${workspaceRoot}/${file}`), diagnostics);
+ }
+ },
+ onStateChanged: (state: Rush.PhasedCommandWorkerState) => {
+ switch (state) {
+ case 'initializing':
+ statusBarItem.text = `$(sync~spin) Rush: initializing watcher`;
+ statusBarItem.show();
+ break;
+ case 'updating':
+ statusBarItem.text = `$(sync~spin) Rush: detecting changes`;
+ break;
+ case 'waiting':
+ statusBarItem.text = `$(eye) Rush: watching`;
+ break;
+ case 'executing':
+ const { activeOperationCount, pendingOperationCount } = worker!;
+ statusBarItem.text = `$(sync~spin) Rush: running (${
+ activeOperationCount - pendingOperationCount
+ }/${activeOperationCount})`;
+ break;
+ case 'exiting':
+ statusBarItem.text = `$(sync~spin) Rush: shutting down watcher`;
+ break;
+ case 'exited':
+ statusBarItem.hide();
+ break;
+ }
+ }
+ });
+
+ try {
+ const graph: Rush.ITransferableOperation[] = await worker.getGraphAsync();
+
+ console.log(`Graph: `, graph);
+ projectDataProvider.updateProjectPhases(
+ graph.map((operation: Rush.ITransferableOperation) => {
+ return {
+ operation,
+ status: 'NO OP' as Rush.OperationStatus,
+ duration: 0,
+ hash: '',
+ active: false
+ };
+ })
+ );
+ updateWorker();
+
+ vscode.window.showInformationMessage('Initialized the Rush watcher.');
+ vscode.commands.executeCommand('setContext', 'rush.watcher', 'ready');
+ } catch {
+ vscode.window.showErrorMessage('Failed to initialize the Rush watcher.');
+ vscode.commands.executeCommand('setContext', 'rush.watcher', 'sleep');
+ worker.shutdownAsync(true);
+ }
+ });
+
+ extensionContext.subscriptions.push(enableWatchCommand);
+
+ const disableWatchCommand = vscode.commands.registerCommand('rush.disableWatch', async () => {
+ if (worker) {
+ vscode.window.showInformationMessage('Shutting down the Rush watcher.');
+ try {
+ await worker.shutdownAsync();
+ vscode.window.showInformationMessage('Shut down the Rush watcher.');
+ } catch {
+ vscode.window.showErrorMessage('Failed to shut down the Rush watcher.');
+ // Swallow error.
+ }
+ }
+
+ statusBarItem.hide();
+
+ vscode.commands.executeCommand('setContext', 'rush.watcher', 'sleep');
+ });
+
+ extensionContext.subscriptions.push(disableWatchCommand);
+
+ extensionContext.subscriptions.push(
+ vscode.workspace.onDidSaveTextDocument((e: vscode.TextDocument) => {
+ updateWorker();
+ })
+ );
+
+ await Promise.all([projectDataProvider.refresh(), phaseDataProvider.refresh()]);
+}
+
+// this method is called when your extension is deactivated
+export function deactivate() {}
diff --git a/apps/vscode-extension/src/test/runTest.ts b/apps/vscode-extension/src/test/runTest.ts
new file mode 100644
index 00000000000..014c3a28f7a
--- /dev/null
+++ b/apps/vscode-extension/src/test/runTest.ts
@@ -0,0 +1,23 @@
+import * as path from 'path';
+
+import { runTests } from '@vscode/test-electron';
+
+async function main() {
+ try {
+ // The folder containing the Extension Manifest package.json
+ // Passed to `--extensionDevelopmentPath`
+ const extensionDevelopmentPath = path.resolve(__dirname, '../../');
+
+ // The path to test runner
+ // Passed to --extensionTestsPath
+ const extensionTestsPath = path.resolve(__dirname, './suite/index');
+
+ // Download VS Code, unzip it and run the integration test
+ await runTests({ extensionDevelopmentPath, extensionTestsPath });
+ } catch (err) {
+ console.error('Failed to run tests');
+ process.exit(1);
+ }
+}
+
+main();
diff --git a/apps/vscode-extension/src/test/suite/extension.test.ts b/apps/vscode-extension/src/test/suite/extension.test.ts
new file mode 100644
index 00000000000..17e2eab2ae2
--- /dev/null
+++ b/apps/vscode-extension/src/test/suite/extension.test.ts
@@ -0,0 +1,15 @@
+import * as assert from 'assert';
+
+// You can import and use all API from the 'vscode' module
+// as well as import your extension to test it
+import * as vscode from 'vscode';
+// import * as myExtension from '../../extension';
+
+suite('Extension Test Suite', () => {
+ vscode.window.showInformationMessage('Start all tests.');
+
+ test('Sample test', () => {
+ assert.strictEqual(-1, [1, 2, 3].indexOf(5));
+ assert.strictEqual(-1, [1, 2, 3].indexOf(0));
+ });
+});
diff --git a/apps/vscode-extension/src/test/suite/index.ts b/apps/vscode-extension/src/test/suite/index.ts
new file mode 100644
index 00000000000..f584ab03928
--- /dev/null
+++ b/apps/vscode-extension/src/test/suite/index.ts
@@ -0,0 +1,38 @@
+import * as path from 'path';
+import * as Mocha from 'mocha';
+import * as glob from 'glob';
+
+export function run(): Promise {
+ // Create the mocha test
+ const mocha = new Mocha({
+ ui: 'tdd',
+ color: true
+ });
+
+ const testsRoot = path.resolve(__dirname, '..');
+
+ return new Promise((c, e) => {
+ glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
+ if (err) {
+ return e(err);
+ }
+
+ // Add files to the test suite
+ files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f)));
+
+ try {
+ // Run the mocha test
+ mocha.run((failures) => {
+ if (failures > 0) {
+ e(new Error(`${failures} tests failed.`));
+ } else {
+ c();
+ }
+ });
+ } catch (err) {
+ console.error(err);
+ e(err);
+ }
+ });
+ });
+}
diff --git a/apps/vscode-extension/tsconfig.json b/apps/vscode-extension/tsconfig.json
new file mode 100644
index 00000000000..e5599fb204d
--- /dev/null
+++ b/apps/vscode-extension/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "module": "esnext",
+ "moduleResolution": "node",
+ "target": "ES2020",
+ "lib": ["ES2020"],
+ "sourceMap": true,
+ "rootDir": "src",
+ "strict": true /* enable all strict type-checking options */
+ /* Additional Checks */
+ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
+ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
+ // "noUnusedParameters": true, /* Report errors on unused parameters. */
+ }
+}
diff --git a/apps/vscode-extension/vsc-extension-quickstart.md b/apps/vscode-extension/vsc-extension-quickstart.md
new file mode 100644
index 00000000000..b2eb4a435ce
--- /dev/null
+++ b/apps/vscode-extension/vsc-extension-quickstart.md
@@ -0,0 +1,47 @@
+# Welcome to your VS Code Extension
+
+## What's in the folder
+
+* This folder contains all of the files necessary for your extension.
+* `package.json` - this is the manifest file in which you declare your extension and command.
+ * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin.
+* `src/extension.ts` - this is the main file where you will provide the implementation of your command.
+ * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`.
+ * We pass the function containing the implementation of the command as the second parameter to `registerCommand`.
+
+## Setup
+
+* install the recommended extensions (amodio.tsl-problem-matcher and dbaeumer.vscode-eslint)
+
+
+## Get up and running straight away
+
+* Press `F5` to open a new window with your extension loaded.
+* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`.
+* Set breakpoints in your code inside `src/extension.ts` to debug your extension.
+* Find output from your extension in the debug console.
+
+## Make changes
+
+* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`.
+* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
+
+
+## Explore the API
+
+* You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`.
+
+## Run tests
+
+* Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`.
+* Press `F5` to run the tests in a new window with your extension loaded.
+* See the output of the test result in the debug console.
+* Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder.
+ * The provided test runner will only consider files matching the name pattern `**.test.ts`.
+ * You can create folders inside the `test` folder to structure your tests any way you want.
+
+## Go further
+
+* Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension).
+* [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace.
+* Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).
diff --git a/apps/vscode-extension/webpack.config.js b/apps/vscode-extension/webpack.config.js
new file mode 100644
index 00000000000..f9de6813f81
--- /dev/null
+++ b/apps/vscode-extension/webpack.config.js
@@ -0,0 +1,53 @@
+//@ts-check
+
+'use strict';
+
+const path = require('path');
+// eslint-disable-next-line @typescript-eslint/naming-convention
+const { PreserveDynamicRequireWebpackPlugin } = require('@rushstack/webpack-preserve-dynamic-require-plugin');
+
+//@ts-check
+/** @typedef {import('webpack').Configuration} WebpackConfig **/
+
+/** @type WebpackConfig */
+const extensionConfig = {
+ target: 'node', // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
+ mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
+
+ entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
+ output: {
+ // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
+ path: path.resolve(__dirname, 'dist'),
+ filename: 'extension.js',
+ libraryTarget: 'commonjs2'
+ },
+ externals: {
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ '@microsoft/rush-lib': 'commonjs @microsoft/rush-lib',
+ vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
+ // modules added here also need to be added in the .vscodeignore file
+ },
+ resolve: {
+ // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
+ extensions: ['.ts', '.js']
+ },
+ module: {
+ rules: [
+ {
+ test: /\.ts$/,
+ exclude: /node_modules/,
+ use: [
+ {
+ loader: 'ts-loader'
+ }
+ ]
+ }
+ ]
+ },
+ devtool: 'source-map',
+ infrastructureLogging: {
+ level: 'log' // enables logging required for problem matchers
+ },
+ plugins: [new PreserveDynamicRequireWebpackPlugin()]
+};
+module.exports = [extensionConfig];
diff --git a/common/config/rush/browser-approved-packages.json b/common/config/rush/browser-approved-packages.json
index 1c11b12bef0..0922411ddaa 100644
--- a/common/config/rush/browser-approved-packages.json
+++ b/common/config/rush/browser-approved-packages.json
@@ -2,6 +2,26 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/approved-packages.schema.json",
"packages": [
+ {
+ "name": "@microsoft/rush",
+ "allowedCategories": [ "libraries" ]
+ },
+ {
+ "name": "@rushstack/rush-lib",
+ "allowedCategories": [ "libraries" ]
+ },
+ {
+ "name": "@rushstack/webpack-preserve-dynamic-require-plugin",
+ "allowedCategories": [ "libraries" ]
+ },
+ {
+ "name": "@vscode/test-electron",
+ "allowedCategories": [ "libraries" ]
+ },
+ {
+ "name": "mocha",
+ "allowedCategories": [ "libraries" ]
+ },
{
"name": "react",
"allowedCategories": [ "tests" ]
diff --git a/common/config/rush/common-versions.json b/common/config/rush/common-versions.json
index 47bcc551057..041eaecc26f 100644
--- a/common/config/rush/common-versions.json
+++ b/common/config/rush/common-versions.json
@@ -88,6 +88,8 @@
"style-loader": ["~2.0.0"],
"terser-webpack-plugin": ["~3.0.8"],
"terser": ["~4.8.0"],
- "webpack": ["~4.44.2"]
+ "ts-loader": ["9.4.0"],
+ "webpack": ["~4.44.2"],
+ "webpack-cli": ["~4.10.0"]
}
}
diff --git a/common/config/rush/nonbrowser-approved-packages.json b/common/config/rush/nonbrowser-approved-packages.json
index abcd21c0205..c029450a645 100644
--- a/common/config/rush/nonbrowser-approved-packages.json
+++ b/common/config/rush/nonbrowser-approved-packages.json
@@ -676,7 +676,7 @@
},
{
"name": "ts-loader",
- "allowedCategories": [ "tests" ]
+ "allowedCategories": [ "libraries", "tests" ]
},
{
"name": "tslint",
@@ -704,7 +704,7 @@
},
{
"name": "webpack-cli",
- "allowedCategories": [ "tests" ]
+ "allowedCategories": [ "libraries", "tests" ]
},
{
"name": "webpack-dev-server",
diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml
index b4bca7613bf..8eea13010eb 100644
--- a/common/config/rush/pnpm-lock.yaml
+++ b/common/config/rush/pnpm-lock.yaml
@@ -192,6 +192,47 @@ importers:
'@types/node': 12.20.24
'@types/semver': 7.3.5
+ ../../apps/vscode-extension:
+ specifiers:
+ '@microsoft/rush': workspace:*
+ '@microsoft/rush-lib': workspace:*
+ '@rushstack/rush-sdk': workspace:*
+ '@rushstack/webpack-preserve-dynamic-require-plugin': workspace:*
+ '@types/glob': 7.1.1
+ '@types/mocha': ^9.1.1
+ '@types/node': 12.20.24
+ '@types/vscode': ^1.71.0
+ '@typescript-eslint/eslint-plugin': ~5.30.3
+ '@typescript-eslint/parser': ~5.30.3
+ '@vscode/test-electron': ^2.1.5
+ eslint: ~8.7.0
+ glob: ~7.0.5
+ mocha: ^10.0.0
+ ts-loader: 9.4.0
+ typescript: ~4.7.4
+ webpack: ~5.68.0
+ webpack-cli: ~4.10.0
+ dependencies:
+ '@rushstack/rush-sdk': link:../../libraries/rush-sdk
+ devDependencies:
+ '@microsoft/rush': link:../rush
+ '@microsoft/rush-lib': link:../../libraries/rush-lib
+ '@rushstack/webpack-preserve-dynamic-require-plugin': link:../../webpack/preserve-dynamic-require-plugin
+ '@types/glob': 7.1.1
+ '@types/mocha': 9.1.1
+ '@types/node': 12.20.24
+ '@types/vscode': 1.71.0
+ '@typescript-eslint/eslint-plugin': 5.30.3_exlp6dxqua5sxvf2mpdocyqr3m
+ '@typescript-eslint/parser': 5.30.3_valmiib6gbzc7jhcbpocdsabay
+ '@vscode/test-electron': 2.1.5
+ eslint: 8.7.0
+ glob: 7.0.6
+ mocha: 10.0.0
+ ts-loader: 9.4.0_y6z6q2r6y5t2cxg26ednarkyyq
+ typescript: 4.7.4
+ webpack: 5.68.0_webpack-cli@4.10.0
+ webpack-cli: 4.10.0_webpack@5.68.0
+
../../build-tests-samples/heft-node-basic-tutorial:
specifiers:
'@rushstack/eslint-config': workspace:*
@@ -7080,6 +7121,10 @@ packages:
'@types/node': 12.20.24
dev: true
+ /@types/mocha/9.1.1:
+ resolution: {integrity: sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==}
+ dev: true
+
/@types/node-fetch/1.6.9:
resolution: {integrity: sha512-n2r6WLoY7+uuPT7pnEtKJCmPUGyJ+cbyBR8Avnu4+m1nzz7DwBVuyIvvlBzCZ/nrpC7rIgb3D6pNavL7rFEa9g==}
dependencies:
@@ -7268,6 +7313,10 @@ packages:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
dev: true
+ /@types/vscode/1.71.0:
+ resolution: {integrity: sha512-nB50bBC9H/x2CpwW9FzRRRDrTZ7G0/POttJojvN/LiVfzTGfLyQIje1L1QRMdFXK9G41k5UJN/1B9S4of7CSzA==}
+ dev: true
+
/@types/webpack-env/1.13.0:
resolution: {integrity: sha512-0BANcVFVqkAD1i7/fWy9Vu6KjB9whuAmkfFX0GFwNzubu2i0qXDsLvGZSbU1QimJHWH4rqjJDQ/PX9v5OVepEA==}
@@ -7344,7 +7393,6 @@ packages:
typescript: 4.7.4
transitivePeerDependencies:
- supports-color
- dev: false
/@typescript-eslint/eslint-plugin/5.30.3_wnypca4zi2m2ykuw256e4kq76m:
resolution: {integrity: sha512-QEgE1uahnDbWEkZlidq7uKB630ny1NN8KbLPmznX+8hYsYpoV1/quG1Nzvs141FVuumuS7O0EpqYw3RB4AVzRg==}
@@ -7483,7 +7531,6 @@ packages:
typescript: 4.7.4
transitivePeerDependencies:
- supports-color
- dev: false
/@typescript-eslint/types/5.30.3_typescript@4.7.4:
resolution: {integrity: sha512-vshU3pjSTgBPNgfd55JLYngHkXuwQP68fxYFUAg1Uq+JrR3xG/XjvL9Dmv28CpOERtqwkaR4QQ3mD0NLZcE2Xw==}
@@ -7547,7 +7594,6 @@ packages:
transitivePeerDependencies:
- supports-color
- typescript
- dev: false
/@typescript-eslint/visitor-keys/5.30.3_typescript@4.7.4:
resolution: {integrity: sha512-ep2xtHOhnSRt6fDP9DSSxrA/FqZhdMF7/Y9fYsxrKss2uWJMbzJyBJ/We1fKc786BJ10pHwrzUlhvpz8i7XzBg==}
@@ -7558,6 +7604,22 @@ packages:
transitivePeerDependencies:
- typescript
+ /@ungap/promise-all-settled/1.1.2:
+ resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==}
+ dev: true
+
+ /@vscode/test-electron/2.1.5:
+ resolution: {integrity: sha512-O/ioqFpV+RvKbRykX2ItYPnbcZ4Hk5V0rY4uhQjQTLhGL9WZUvS7exzuYQCCI+ilSqJpctvxq2llTfGXf9UnnA==}
+ engines: {node: '>=8.9.3'}
+ dependencies:
+ http-proxy-agent: 4.0.1
+ https-proxy-agent: 5.0.1
+ rimraf: 3.0.2
+ unzipper: 0.10.11
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@webassemblyjs/ast/1.11.1:
resolution: {integrity: sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==}
dependencies:
@@ -7758,6 +7820,37 @@ packages:
'@webassemblyjs/wast-parser': 1.9.0
'@xtuc/long': 4.2.2
+ /@webpack-cli/configtest/1.2.0_rwfyai4fxq6tnmgv4kfhytp6ay:
+ resolution: {integrity: sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==}
+ peerDependencies:
+ webpack: 4.x.x || 5.x.x
+ webpack-cli: 4.x.x
+ dependencies:
+ webpack: 5.68.0_webpack-cli@4.10.0
+ webpack-cli: 4.10.0_webpack@5.68.0
+ dev: true
+
+ /@webpack-cli/info/1.5.0_webpack-cli@4.10.0:
+ resolution: {integrity: sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==}
+ peerDependencies:
+ webpack-cli: 4.x.x
+ dependencies:
+ envinfo: 7.8.1
+ webpack-cli: 4.10.0_webpack@5.68.0
+ dev: true
+
+ /@webpack-cli/serve/1.7.0_webpack-cli@4.10.0:
+ resolution: {integrity: sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==}
+ peerDependencies:
+ webpack-cli: 4.x.x
+ webpack-dev-server: '*'
+ peerDependenciesMeta:
+ webpack-dev-server:
+ optional: true
+ dependencies:
+ webpack-cli: 4.10.0_webpack@5.68.0
+ dev: true
+
/@xtuc/ieee754/1.2.0:
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
@@ -7965,6 +8058,11 @@ packages:
engines: {node: '>=6'}
dev: true
+ /ansi-colors/4.1.1:
+ resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==}
+ engines: {node: '>=6'}
+ dev: true
+
/ansi-colors/4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'}
@@ -8703,6 +8801,11 @@ packages:
is-windows: 1.0.2
dev: false
+ /big-integer/1.6.51:
+ resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==}
+ engines: {node: '>=0.6'}
+ dev: true
+
/big.js/3.2.0:
resolution: {integrity: sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==}
dev: false
@@ -8719,6 +8822,13 @@ packages:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
+ /binary/0.3.0:
+ resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==}
+ dependencies:
+ buffers: 0.1.1
+ chainsaw: 0.1.0
+ dev: true
+
/bindings/1.5.0:
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
requiresBuild: true
@@ -8734,6 +8844,10 @@ packages:
readable-stream: 3.6.0
dev: true
+ /bluebird/3.4.7:
+ resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==}
+ dev: true
+
/bluebird/3.7.2:
resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
@@ -8798,6 +8912,12 @@ packages:
balanced-match: 1.0.2
concat-map: 0.0.1
+ /brace-expansion/2.0.1:
+ resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+ dependencies:
+ balanced-match: 1.0.2
+ dev: true
+
/braces/2.3.2:
resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==}
engines: {node: '>=0.10.0'}
@@ -8825,6 +8945,10 @@ packages:
/browser-process-hrtime/1.0.0:
resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==}
+ /browser-stdout/1.3.1:
+ resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==}
+ dev: true
+
/browserify-aes/1.2.0:
resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==}
dependencies:
@@ -8901,6 +9025,11 @@ packages:
/buffer-from/1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+ /buffer-indexof-polyfill/1.0.2:
+ resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==}
+ engines: {node: '>=0.10'}
+ dev: true
+
/buffer-xor/1.0.3:
resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==}
@@ -8918,6 +9047,11 @@ packages:
ieee754: 1.2.1
dev: true
+ /buffers/0.1.1:
+ resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==}
+ engines: {node: '>=0.2.0'}
+ dev: true
+
/builtin-modules/1.1.1:
resolution: {integrity: sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==}
engines: {node: '>=0.10.0'}
@@ -9129,6 +9263,12 @@ packages:
yargs: 16.2.0
dev: true
+ /chainsaw/0.1.0:
+ resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==}
+ dependencies:
+ traverse: 0.3.9
+ dev: true
+
/chalk/1.1.3:
resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==}
engines: {node: '>=0.10.0'}
@@ -9526,7 +9666,7 @@ packages:
dev: true
/concat-map/0.0.1:
- resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
/concat-stream/1.6.2:
resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==}
@@ -10068,6 +10208,19 @@ packages:
dependencies:
ms: 2.1.2
+ /debug/4.3.4_supports-color@8.1.1:
+ resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.1.2
+ supports-color: 8.1.1
+ dev: true
+
/debuglog/1.0.1:
resolution: {integrity: sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==}
dev: false
@@ -10084,6 +10237,11 @@ packages:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
+ /decamelize/4.0.0:
+ resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==}
+ engines: {node: '>=10'}
+ dev: true
+
/decamelize/5.0.1:
resolution: {integrity: sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==}
engines: {node: '>=10'}
@@ -10413,6 +10571,12 @@ packages:
/duplexer/0.1.2:
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
+ /duplexer2/0.1.4:
+ resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==}
+ dependencies:
+ readable-stream: 2.3.7
+ dev: true
+
/duplexer3/0.1.4:
resolution: {integrity: sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA==}
dev: true
@@ -11531,6 +11695,11 @@ packages:
/fast-safe-stringify/2.1.1:
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
+ /fastest-levenshtein/1.0.16:
+ resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==}
+ engines: {node: '>= 4.9.1'}
+ dev: true
+
/fastify-error/0.3.1:
resolution: {integrity: sha512-oCfpcsDndgnDVgiI7bwFKAun2dO+4h84vBlkWsWnz/OUK9Reff5UFoFl241xTiLeHWX/vU9zkDVXqYUxjOwHcQ==}
dev: false
@@ -11747,6 +11916,11 @@ packages:
flatted: 3.2.5
rimraf: 3.0.2
+ /flat/5.0.2:
+ resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
+ hasBin: true
+ dev: true
+
/flatstr/1.0.12:
resolution: {integrity: sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==}
dev: false
@@ -11974,6 +12148,16 @@ packages:
requiresBuild: true
optional: true
+ /fstream/1.0.12:
+ resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==}
+ engines: {node: '>=0.6'}
+ dependencies:
+ graceful-fs: 4.2.10
+ inherits: 2.0.4
+ mkdirp: 0.5.6
+ rimraf: 2.7.1
+ dev: true
+
/ftp/0.3.10:
resolution: {integrity: sha512-faFVML1aBx2UoDStmLwv2Wptt4vw5x03xxX172nhA5Y5HBshW5JweqQ2W4xL4dezQTG8inJsuYcpPHHU3X5OTQ==}
engines: {node: '>=0.8.0'}
@@ -12192,6 +12376,17 @@ packages:
path-is-absolute: 1.0.1
dev: false
+ /glob/7.2.0:
+ resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==}
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.1.2
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+ dev: true
+
/glob/7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
dependencies:
@@ -13348,6 +13543,11 @@ packages:
/is-typedarray/1.0.0:
resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==}
+ /is-unicode-supported/0.1.0:
+ resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
+ engines: {node: '>=10'}
+ dev: true
+
/is-weakref/1.0.2:
resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
dependencies:
@@ -14474,6 +14674,10 @@ packages:
/lines-and-columns/1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+ /listenercount/1.0.1:
+ resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==}
+ dev: true
+
/load-json-file/6.2.0:
resolution: {integrity: sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ==}
engines: {node: '>=8'}
@@ -14620,6 +14824,14 @@ packages:
/lodash/4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+ /log-symbols/4.1.0:
+ resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
+ engines: {node: '>=10'}
+ dependencies:
+ chalk: 4.1.2
+ is-unicode-supported: 0.1.0
+ dev: true
+
/log4js/6.5.2:
resolution: {integrity: sha512-DXtpNtt+KDOMT7RHUDIur/WsSA3rntlUh9Zg4XCdV42wUuMmbFkl38+LZ92Z5QvQA7mD5kAVkLiBSEH/tvUB8A==}
engines: {node: '>=8.0'}
@@ -14993,6 +15205,13 @@ packages:
dependencies:
brace-expansion: 1.1.11
+ /minimatch/5.0.1:
+ resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==}
+ engines: {node: '>=10'}
+ dependencies:
+ brace-expansion: 2.0.1
+ dev: true
+
/minimist-options/4.1.0:
resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
engines: {node: '>= 6'}
@@ -15090,6 +15309,35 @@ packages:
engines: {node: '>=10'}
hasBin: true
+ /mocha/10.0.0:
+ resolution: {integrity: sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==}
+ engines: {node: '>= 14.0.0'}
+ hasBin: true
+ dependencies:
+ '@ungap/promise-all-settled': 1.1.2
+ ansi-colors: 4.1.1
+ browser-stdout: 1.3.1
+ chokidar: 3.5.3
+ debug: 4.3.4_supports-color@8.1.1
+ diff: 5.0.0
+ escape-string-regexp: 4.0.0
+ find-up: 5.0.0
+ glob: 7.2.0
+ he: 1.2.0
+ js-yaml: 4.1.0
+ log-symbols: 4.1.0
+ minimatch: 5.0.1
+ ms: 2.1.3
+ nanoid: 3.3.3
+ serialize-javascript: 6.0.0
+ strip-json-comments: 3.1.1
+ supports-color: 8.1.1
+ workerpool: 6.2.1
+ yargs: 16.2.0
+ yargs-parser: 20.2.4
+ yargs-unparser: 2.0.0
+ dev: true
+
/move-concurrently/1.0.1:
resolution: {integrity: sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ==}
dependencies:
@@ -15138,6 +15386,12 @@ packages:
/nan/2.16.0:
resolution: {integrity: sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==}
+ /nanoid/3.3.3:
+ resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+ dev: true
+
/nanoid/3.3.4:
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -17168,6 +17422,13 @@ packages:
resolve: 1.17.0
dev: true
+ /rechoir/0.7.1:
+ resolution: {integrity: sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==}
+ engines: {node: '>= 0.10'}
+ dependencies:
+ resolve: 1.17.0
+ dev: true
+
/redent/3.0.0:
resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
engines: {node: '>=8'}
@@ -18914,6 +19175,10 @@ packages:
dependencies:
punycode: 2.1.1
+ /traverse/0.3.9:
+ resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==}
+ dev: true
+
/trim-newlines/3.0.1:
resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==}
engines: {node: '>=8'}
@@ -18959,6 +19224,21 @@ packages:
typescript: 4.7.4
dev: false
+ /ts-loader/9.4.0_y6z6q2r6y5t2cxg26ednarkyyq:
+ resolution: {integrity: sha512-0G3UMhk1bjgsgiwF4rnZRAeTi69j9XMDtmDDMghGSqlWESIAS3LFgJe//GYfE4vcjbyzuURLB9Us2RZIWp2clQ==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ typescript: '*'
+ webpack: ^5.0.0
+ dependencies:
+ chalk: 4.1.2
+ enhanced-resolve: 5.9.3
+ micromatch: 4.0.5
+ semver: 7.3.7
+ typescript: 4.7.4
+ webpack: 5.68.0_webpack-cli@4.10.0
+ dev: true
+
/ts-pnp/1.2.0_typescript@4.7.4:
resolution: {integrity: sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==}
engines: {node: '>=6'}
@@ -19294,6 +19574,21 @@ packages:
has-value: 0.3.1
isobject: 3.0.1
+ /unzipper/0.10.11:
+ resolution: {integrity: sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==}
+ dependencies:
+ big-integer: 1.6.51
+ binary: 0.3.0
+ bluebird: 3.4.7
+ buffer-indexof-polyfill: 1.0.2
+ duplexer2: 0.1.4
+ fstream: 1.0.12
+ graceful-fs: 4.2.10
+ listenercount: 1.0.1
+ readable-stream: 2.3.7
+ setimmediate: 1.0.5
+ dev: true
+
/upath/1.2.0:
resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==}
engines: {node: '>=4'}
@@ -19654,6 +19949,41 @@ packages:
yargs: 13.3.2
dev: false
+ /webpack-cli/4.10.0_webpack@5.68.0:
+ resolution: {integrity: sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==}
+ engines: {node: '>=10.13.0'}
+ hasBin: true
+ peerDependencies:
+ '@webpack-cli/generators': '*'
+ '@webpack-cli/migrate': '*'
+ webpack: 4.x.x || 5.x.x
+ webpack-bundle-analyzer: '*'
+ webpack-dev-server: '*'
+ peerDependenciesMeta:
+ '@webpack-cli/generators':
+ optional: true
+ '@webpack-cli/migrate':
+ optional: true
+ webpack-bundle-analyzer:
+ optional: true
+ webpack-dev-server:
+ optional: true
+ dependencies:
+ '@discoveryjs/json-ext': 0.5.7
+ '@webpack-cli/configtest': 1.2.0_rwfyai4fxq6tnmgv4kfhytp6ay
+ '@webpack-cli/info': 1.5.0_webpack-cli@4.10.0
+ '@webpack-cli/serve': 1.7.0_webpack-cli@4.10.0
+ colorette: 2.0.19
+ commander: 7.2.0
+ cross-spawn: 7.0.3
+ fastest-levenshtein: 1.0.16
+ import-local: 3.1.0
+ interpret: 2.2.0
+ rechoir: 0.7.1
+ webpack: 5.68.0_webpack-cli@4.10.0
+ webpack-merge: 5.8.0
+ dev: true
+
/webpack-dev-middleware/3.7.3_2jhnw6fokymnjfoumvhvkjoyjq:
resolution: {integrity: sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ==}
engines: {node: '>= 6'}
@@ -19919,7 +20249,6 @@ packages:
dependencies:
clone-deep: 4.0.1
wildcard: 2.0.0
- dev: false
/webpack-sources/1.4.3:
resolution: {integrity: sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==}
@@ -20052,6 +20381,47 @@ packages:
- esbuild
- uglify-js
+ /webpack/5.68.0_webpack-cli@4.10.0:
+ resolution: {integrity: sha512-zUcqaUO0772UuuW2bzaES2Zjlm/y3kRBQDVFVCge+s2Y8mwuUTdperGaAv65/NtRL/1zanpSJOq/MD8u61vo6g==}
+ engines: {node: '>=10.13.0'}
+ hasBin: true
+ peerDependencies:
+ webpack-cli: '*'
+ peerDependenciesMeta:
+ webpack-cli:
+ optional: true
+ dependencies:
+ '@types/eslint-scope': 3.7.3
+ '@types/estree': 0.0.50
+ '@webassemblyjs/ast': 1.11.1
+ '@webassemblyjs/wasm-edit': 1.11.1
+ '@webassemblyjs/wasm-parser': 1.11.1
+ acorn: 8.7.1
+ acorn-import-assertions: 1.8.0_acorn@8.7.1
+ browserslist: 4.20.4
+ chrome-trace-event: 1.0.3
+ enhanced-resolve: 5.9.3
+ es-module-lexer: 0.9.3
+ eslint-scope: 5.1.1
+ events: 3.3.0
+ glob-to-regexp: 0.4.1
+ graceful-fs: 4.2.10
+ json-parse-better-errors: 1.0.2
+ loader-runner: 4.3.0
+ mime-types: 2.1.35
+ neo-async: 2.6.2
+ schema-utils: 3.1.1
+ tapable: 2.2.1
+ terser-webpack-plugin: 5.3.3_webpack@5.68.0
+ watchpack: 2.4.0
+ webpack-cli: 4.10.0_webpack@5.68.0
+ webpack-sources: 3.2.3
+ transitivePeerDependencies:
+ - '@swc/core'
+ - esbuild
+ - uglify-js
+ dev: true
+
/websocket-driver/0.7.4:
resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==}
engines: {node: '>=0.8.0'}
@@ -20125,7 +20495,6 @@ packages:
/wildcard/2.0.0:
resolution: {integrity: sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==}
- dev: false
/word-wrap/1.2.3:
resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==}
@@ -20145,6 +20514,10 @@ packages:
microevent.ts: 0.1.1
dev: true
+ /workerpool/6.2.1:
+ resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==}
+ dev: true
+
/wrap-ansi/5.1.0:
resolution: {integrity: sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==}
engines: {node: '>=6'}
@@ -20316,10 +20689,25 @@ packages:
decamelize: 1.2.0
dev: true
+ /yargs-parser/20.2.4:
+ resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==}
+ engines: {node: '>=10'}
+ dev: true
+
/yargs-parser/20.2.9:
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
engines: {node: '>=10'}
+ /yargs-unparser/2.0.0:
+ resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==}
+ engines: {node: '>=10'}
+ dependencies:
+ camelcase: 6.3.0
+ decamelize: 4.0.0
+ flat: 5.0.2
+ is-plain-obj: 2.1.0
+ dev: true
+
/yargs/13.3.2:
resolution: {integrity: sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==}
dependencies:
diff --git a/common/config/rush/repo-state.json b/common/config/rush/repo-state.json
index a8d1d5b071f..eae34bf678e 100644
--- a/common/config/rush/repo-state.json
+++ b/common/config/rush/repo-state.json
@@ -1,5 +1,5 @@
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
{
- "pnpmShrinkwrapHash": "9f318b7f81c50e7dd8f20cb296676b3e05f6a40b",
+ "pnpmShrinkwrapHash": "0287692314f45d724adc9578462265497be117a5",
"preferredVersionsHash": "d15f901c51f5b82ae0cc4cc0a2cdc9a5e451367c"
}
diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md
index b7567025f1c..e4563b6ea6d 100644
--- a/common/reviews/api/rush-lib.api.md
+++ b/common/reviews/api/rush-lib.api.md
@@ -11,11 +11,14 @@ import { AsyncSeriesHook } from 'tapable';
import { AsyncSeriesWaterfallHook } from 'tapable';
import type { CollatedWriter } from '@rushstack/stream-collator';
import type { CommandLineParameter } from '@rushstack/ts-command-line';
+import { CommandLineParser } from '@rushstack/ts-command-line';
import { HookMap } from 'tapable';
+import { IDiagnostic } from '@rushstack/terminal';
import { IPackageJson } from '@rushstack/node-core-library';
import { ITerminal } from '@rushstack/node-core-library';
import { ITerminalProvider } from '@rushstack/node-core-library';
import { JsonObject } from '@rushstack/node-core-library';
+import { JsonSchema } from '@rushstack/node-core-library';
import { PackageNameParser } from '@rushstack/node-core-library';
import type { StdioSummarizer } from '@rushstack/terminal';
import { SyncHook } from 'tapable';
@@ -283,6 +286,8 @@ export interface ICredentialCacheOptions {
supportEditing: boolean;
}
+export { IDiagnostic }
+
// @beta (undocumented)
export interface IEnvironmentConfigurationInitializeOptions {
// (undocumented)
@@ -372,6 +377,7 @@ export interface _INpmOptionsJson extends IPackageManagerOptionsJsonBase {
// @alpha
export interface IOperationExecutionResult {
readonly error: Error | undefined;
+ readonly silent: boolean;
readonly status: OperationStatus;
readonly stdioSummarizer: StdioSummarizer;
readonly stopwatch: IStopwatchResult;
@@ -399,8 +405,11 @@ export interface IOperationRunner {
export interface IOperationRunnerContext {
collatedWriter: CollatedWriter;
debugMode: boolean;
+ isCacheWriteAllowed: boolean;
quietMode: boolean;
+ stateHash: string | undefined;
stdioSummarizer: StdioSummarizer;
+ trackedFileHashes: ReadonlyMap | undefined;
}
// @public
@@ -428,6 +437,13 @@ export interface IPhasedCommand extends IRushCommand {
readonly hooks: PhasedCommandHooks;
}
+// @alpha (undocumented)
+export interface IPhasedCommandWorkerOptions {
+ cwd?: string;
+ onStateChanged?: (state: PhasedCommandWorkerState) => void;
+ onStatusUpdates?: (operationStatus: ITransferableOperationStatus[]) => void;
+}
+
// @internal
export interface _IPnpmOptionsJson extends IPackageManagerOptionsJsonBase {
pnpmStore?: PnpmStoreOptions;
@@ -504,6 +520,34 @@ export interface ITelemetryOperationResult {
startTimestampMs?: number;
}
+// @alpha (undocumented)
+export interface ITransferableOperation {
+ // (undocumented)
+ logFilePath?: string;
+ // (undocumented)
+ name?: string;
+ // (undocumented)
+ phase?: string;
+ // (undocumented)
+ project?: string;
+}
+
+// @alpha (undocumented)
+export interface ITransferableOperationStatus {
+ // (undocumented)
+ active: boolean;
+ // (undocumented)
+ diagnostics?: IDiagnostic[];
+ // (undocumented)
+ duration: number;
+ // (undocumented)
+ hash: string | undefined;
+ // (undocumented)
+ operation: ITransferableOperation;
+ // (undocumented)
+ status: OperationStatus;
+}
+
// @public
export interface ITryFindRushJsonLocationOptions {
showVerbose?: boolean;
@@ -654,6 +698,30 @@ export class PhasedCommandHooks {
readonly waitingForChanges: SyncHook;
}
+// @alpha
+export class PhasedCommandWorkerController {
+ constructor(args: string[], options?: IPhasedCommandWorkerOptions);
+ abort(): void;
+ // (undocumented)
+ get activeOperationCount(): number;
+ getGraph(): ITransferableOperation[];
+ // (undocumented)
+ getGraphAsync(): Promise;
+ // (undocumented)
+ getStatuses(): Iterable;
+ onStateChanged: (state: PhasedCommandWorkerState) => void;
+ onStatusUpdates: (statuses: ITransferableOperationStatus[]) => void;
+ // (undocumented)
+ get pendingOperationCount(): number;
+ shutdownAsync(force?: boolean): Promise;
+ // (undocumented)
+ get state(): PhasedCommandWorkerState;
+ update(operations: ITransferableOperation[]): void;
+}
+
+// @alpha (undocumented)
+export type PhasedCommandWorkerState = 'initializing' | 'waiting' | 'updating' | 'executing' | 'exiting' | 'exited';
+
// @public
export class PnpmOptionsConfiguration extends PackageManagerOptionsConfigurationBase {
// @internal
@@ -678,6 +746,10 @@ export class ProjectChangeAnalyzer {
// (undocumented)
_filterProjectDataAsync(project: RushConfigurationProject, unfilteredProjectData: Map, rootDir: string, terminal: ITerminal): Promise