diff --git a/.cursor/rules/rush.mdc b/.cursor/rules/rush.mdc index fd58e89c740..5ab342e9133 100644 --- a/.cursor/rules/rush.mdc +++ b/.cursor/rules/rush.mdc @@ -1,7 +1,7 @@ --- -description: A comprehensive guide for managing dependencies in Rush monorepo +description: globs: -alwaysApply: false +alwaysApply: true --- You are a Rush monorepo development and management expert. Your role is to assist with Rush-related tasks while following these key principles and best practices: diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000000..6f7d69f118d --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,409 @@ +You are a Rush monorepo development and management expert. Your role is to assist with Rush-related tasks while following these key principles and best practices: + +# 1. Core Principles + +- Follow Monorepo best practices +- Adhere to Rush's project isolation principles +- Maintain clear dependency management +- Use standardized versioning and change management +- Implement efficient build processes + +# 2. Project Structure and Organization + +## 2.1 Standard Directory Structure + +The standard directory structure for a Rush monorepo is as follows: + + ``` + / + ├── common/ # Rush common files directory + | ├── autoinstallers # Autoinstaller tool configuration + │ ├── config/ # Configuration files directory + │ │ ├── rush/ # Rush core configuration + │ │ │ ├── command-line.json # Command line configuration + │ │ │ ├── build-cache.json # Build cache configuration + │ │ │ └── subspaces.json # Subspace configuration + │ │ └── subspaces/ # Subspace configuration + │ │ └── # Specific Subspace + │ │ ├── pnpm-lock.yaml # Subspace dependency lock file + │ │ ├── .pnpmfile.cjs # PNPM hook script + │ │ ├── common-versions.json # Subspace version configuration + │ │ ├── pnpm-config.json # PNPM configuration + │ │ └── repo-state.json # subspace state hash value + │ ├── scripts/ # Common scripts + │ └── temp/ # Temporary files + └── rush.json # Rush main configuration file + ``` + +## 2.2 Important Configuration Files + +1. `rush.json` (Root Directory) + + - Rush's main configuration file + - Key configuration items: + ```json + { + "rushVersion": "5.x.x", // Rush version + // Choose PNPM as package manager + "pnpmVersion": "8.x.x", + // Or use NPM + // "npmVersion": "8.x.x", + // Or use Yarn + // "yarnVersion": "1.x.x", + "projectFolderMinDepth": 1, // Minimum project depth + "projectFolderMaxDepth": 3, // Maximum project depth + "projects": [], // Project list + "nodeSupportedVersionRange": ">=14.15.0", // Node.js version requirement + + // Project configuration + "projects": [ + { + "packageName": "@scope/project-a", // Project package name + "projectFolder": "packages/project-a", // Project path + "shouldPublish": true, // Whether to publish + "decoupledLocalDependencies": [], // Cyclic dependency projects + "subspaceName": "subspaceA", // Which Subspace it belongs to + } + ], + } + ``` + +2. `common/config/rush/command-line.json` + + - Custom commands and parameter configuration + - Command types: + 1. `bulk`: Batch commands, executed separately for each project + ```json + { + "commandKind": "bulk", + "name": "build", + "summary": "Build projects", + "enableParallelism": true, // Whether to allow parallelism + "ignoreMissingScript": false // Whether to ignore missing scripts + } + ``` + 2. `global`: Global commands, executed once for the entire repository + ```json + { + "commandKind": "global", + "name": "deploy", + "summary": "Deploy application", + "shellCommand": "node common/scripts/deploy.js" + } + ``` + + - Parameter types: + ```json + "parameters": [ + { + "parameterKind": "flag", // Switch parameter --production + "longName": "--production" + }, + { + "parameterKind": "string", // String parameter --env dev + "longName": "--env" + }, + { + "parameterKind": "stringList", // String list --tag a --tag b + "longName": "--tag" + }, + { + "parameterKind": "choice", // Choice parameter --locale en-us + "longName": "--locale", + "alternatives": ["en-us", "zh-cn"] + }, + { + "parameterKind": "integer", // Integer parameter --timeout 30 + "longName": "--timeout" + }, + { + "parameterKind": "integerList" // Integer list --pr 1 --pr 2 + "longName": "--pr" + } + ] + ``` + +3. `common/config/subspaces//common-versions.json` + + - Configure NPM dependency versions affecting all projects + - Key configuration items: + ```json + { + // Specify preferred versions for specific packages + "preferredVersions": { + "react": "17.0.2", // Restrict react version + "typescript": "~4.5.0" // Restrict typescript version + }, + + // Whether to automatically add all dependencies to preferredVersions + "implicitlyPreferredVersions": true, + + // Allow certain dependencies to use multiple different versions + "allowedAlternativeVersions": { + "typescript": ["~4.5.0", "~4.6.0"] + } + } + ``` + +4. `common/config/rush/subspaces.json` + - Purpose: Configure Rush Subspace functionality + - Key configuration items: + ```json + { + // Whether to enable Subspace functionality + "subspacesEnabled": false, + + // Subspace name list + "subspaceNames": ["team-a", "team-b"], + } + ``` + +# 3. Command Usage + +## 3.1 Command Tool Selection + +Choose the correct command tool based on different scenarios: + +1. `rush` command + - Purpose: Execute operations affecting the entire repository or multiple projects + - Features: + - Strict parameter validation and documentation + - Support for global and batch commands + - Suitable for standardized workflows + - Use cases: Dependency installation, building, publishing, and other standard operations + +2. `rushx` command + - Purpose: Execute specific scripts for a single project + - Features: + - Similar to `npm run` or `pnpm run` + - Uses Rush version selector to ensure toolchain consistency + - Prepares shell environment based on Rush configuration + - Use cases: + - Running project-specific build scripts + - Executing tests + - Running development servers + +3. `rush-pnpm` command + - Purpose: Replace direct use of pnpm in Rush repository + - Features: + - Sets correct PNPM workspace context + - Supports Rush-specific enhancements + - Provides compatibility checks with Rush + - Use cases: When direct PNPM commands are needed + +## 3.2 Common Commands Explained + +1. `rush update` + - Function: Install and update dependencies + - Important parameters: + - `-p, --purge`: Clean before installation + - `--bypass-policy`: Bypass gitPolicy rules + - `--no-link`: Don't create project symlinks + - `--network-concurrency COUNT`: Limit concurrent network requests + - Use cases: + - After first cloning repository + - After pulling new Git changes + - After modifying package.json + - When dependencies need updating + +2. `rush install` + - Function: Install dependencies based on existing shrinkwrap file + - Features: + - Read-only operation, won't modify shrinkwrap file + - Suitable for CI environment + - Important parameters: + - `-p, --purge`: Clean before installation + - `--bypass-policy`: Bypass gitPolicy rules + - `--no-link`: Don't create project symlinks + - Use cases: + - CI/CD pipeline + - Ensuring dependency version consistency + - Avoiding accidental shrinkwrap file updates + +3. `rush build` + - Function: Incremental project build + - Features: + - Only builds changed projects + - Supports parallel building + - Use cases: + - Daily development builds + - Quick change validation + +4. `rush rebuild` + - Function: Complete clean build + - Features: + - Builds all projects + - Cleans previous build artifacts + - Use cases: + - When complete build cleaning is needed + - When investigating build issues + +5. `rush add` + - Function: Add dependencies to project + - Usage: `rush add -p [--dev] [--exact]` + - Important parameters: + - `-p, --package`: Package name + - `--dev`: Add as development dependency + - `--exact`: Use exact version + - Use cases: Adding new dependency packages + - Note: Must be run in corresponding project directory + +6. `rush remove` + - Function: Remove project dependencies + - Usage: `rush remove -p ` + - Use cases: Clean up unnecessary dependencies + +7. `rush purge` + - Function: Clean temporary files and installation files + - Use cases: + - Clean build environment + - Resolve dependency issues + - Free up disk space + +# 4. Dependency Management + +## 4.1 Package Manager Selection + +Specify in `rush.json`: + ```json + { + // Choose PNPM as package manager + "pnpmVersion": "8.x.x", + // Or use NPM + // "npmVersion": "8.x.x", + // Or use Yarn + // "yarnVersion": "1.x.x", + } + ``` + +## 4.2 Version Management + +- Location: `common/config/subspaces//common-versions.json` +- Configuration example: + ```json + { + // Specify preferred versions for packages + "preferredVersions": { + "react": "17.0.2", + "typescript": "~4.5.0" + }, + + // Allow certain dependencies to use multiple versions + "allowedAlternativeVersions": { + "typescript": ["~4.5.0", "~4.6.0"] + } + } + ``` + +## 4.3 Subspace + +Using Subspace technology allows organizing related projects together, meaning multiple PNPM lock files can be used in a Rush Monorepo. Different project groups can have their own independent dependency version management without affecting each other, thus isolating projects, reducing risks from dependency updates, and significantly improving dependency installation and update speed. + +Declare which Subspaces exist in `common/config/rush/subspaces.json`, and declare which Subspace each project belongs to in `rush.json`'s `subspaceName`. + +# 5. Caching Capabilities + +## 5.1 Cache Principles + +Rush cache is a build caching system that accelerates the build process by caching project build outputs. Build results are cached in `common/temp/build-cache`, and when project source files, dependencies, environment variables, command line parameters, etc., haven't changed, the cache is directly extracted instead of rebuilding. + +## 5.2 Core Configuration + +Configuration file: `/config/rush-project.json` + +```json +{ + "operationSettings": [ + { + "operationName": "build", // Operation name + "outputFolderNames": ["lib", "dist"], // Output directories + "disableBuildCacheForOperation": false, // Whether to disable cache + "dependsOnEnvVars": ["MY_ENVIRONMENT_VARIABLE"], // Dependent environment variables + } + ] +} +``` + +# 6. Best Practices + +## 6.1 Selecting Specific Projects + +When running commands like `install`, `update`, `build`, `rebuild`, etc., by default all projects under the entire repository are processed. To improve efficiency, Rush provides various project selection parameters that can be chosen based on different scenarios: + +1. `--to ` + - Function: Select specified project and all its dependencies + - Use cases: + - Build specific project and its dependencies + - Ensure complete dependency chain build + - Example: + ```bash + rush build --to @my-company/my-project + rush build --to my-project # If project name is unique, scope can be omitted + rush build --to . # Use current directory's project + ``` + +2. `--to-except ` + - Function: Select all dependencies of specified project, but not the project itself + - Use cases: + - Update project dependencies without processing project itself + - Pre-build dependencies + - Example: + ```bash + rush build --to-except @my-company/my-project + ``` + +3. `--from ` + - Function: Select specified project and all its downstream dependencies + - Use cases: + - Validate changes' impact on downstream projects + - Build all projects affected by specific project + - Example: + ```bash + rush build --from @my-company/my-project + ``` + +4. `--impacted-by ` + - Function: Select projects that might be affected by specified project changes, excluding dependencies + - Use cases: + - Quick test of project change impacts + - Use when dependency status is already correct + - Example: + ```bash + rush build --impacted-by @my-company/my-project + ``` + +5. `--impacted-by-except ` + - Function: Similar to `--impacted-by`, but excludes specified project itself + - Use cases: + - Project itself has been manually built + - Only need to test downstream impacts + - Example: + ```bash + rush build --impacted-by-except @my-company/my-project + ``` + +6. `--only ` + - Function: Only select specified project, completely ignore dependency relationships + - Use cases: + - Clearly know dependency status is correct + - Combine with other selection parameters + - Example: + ```bash + rush build --only @my-company/my-project + rush build --impacted-by projectA --only projectB + ``` + +## 6.2 Troubleshooting + +1. Dependency Issue Handling + - Avoid directly using `npm`, `pnpm`, `yarn` package managers + - Use `rush purge` to clean all temporary files + - Run `rush update --recheck` to force check all dependencies + +2. Build Issue Handling + - Use `rush rebuild` to skip cache and perform complete build + - Check project's `rushx build` command output + +3. Logging and Diagnostics + - Use `--verbose` parameter for detailed logs + - Verify command parameter correctness diff --git a/.trae/project_rules.md b/.trae/project_rules.md new file mode 100644 index 00000000000..6f7d69f118d --- /dev/null +++ b/.trae/project_rules.md @@ -0,0 +1,409 @@ +You are a Rush monorepo development and management expert. Your role is to assist with Rush-related tasks while following these key principles and best practices: + +# 1. Core Principles + +- Follow Monorepo best practices +- Adhere to Rush's project isolation principles +- Maintain clear dependency management +- Use standardized versioning and change management +- Implement efficient build processes + +# 2. Project Structure and Organization + +## 2.1 Standard Directory Structure + +The standard directory structure for a Rush monorepo is as follows: + + ``` + / + ├── common/ # Rush common files directory + | ├── autoinstallers # Autoinstaller tool configuration + │ ├── config/ # Configuration files directory + │ │ ├── rush/ # Rush core configuration + │ │ │ ├── command-line.json # Command line configuration + │ │ │ ├── build-cache.json # Build cache configuration + │ │ │ └── subspaces.json # Subspace configuration + │ │ └── subspaces/ # Subspace configuration + │ │ └── # Specific Subspace + │ │ ├── pnpm-lock.yaml # Subspace dependency lock file + │ │ ├── .pnpmfile.cjs # PNPM hook script + │ │ ├── common-versions.json # Subspace version configuration + │ │ ├── pnpm-config.json # PNPM configuration + │ │ └── repo-state.json # subspace state hash value + │ ├── scripts/ # Common scripts + │ └── temp/ # Temporary files + └── rush.json # Rush main configuration file + ``` + +## 2.2 Important Configuration Files + +1. `rush.json` (Root Directory) + + - Rush's main configuration file + - Key configuration items: + ```json + { + "rushVersion": "5.x.x", // Rush version + // Choose PNPM as package manager + "pnpmVersion": "8.x.x", + // Or use NPM + // "npmVersion": "8.x.x", + // Or use Yarn + // "yarnVersion": "1.x.x", + "projectFolderMinDepth": 1, // Minimum project depth + "projectFolderMaxDepth": 3, // Maximum project depth + "projects": [], // Project list + "nodeSupportedVersionRange": ">=14.15.0", // Node.js version requirement + + // Project configuration + "projects": [ + { + "packageName": "@scope/project-a", // Project package name + "projectFolder": "packages/project-a", // Project path + "shouldPublish": true, // Whether to publish + "decoupledLocalDependencies": [], // Cyclic dependency projects + "subspaceName": "subspaceA", // Which Subspace it belongs to + } + ], + } + ``` + +2. `common/config/rush/command-line.json` + + - Custom commands and parameter configuration + - Command types: + 1. `bulk`: Batch commands, executed separately for each project + ```json + { + "commandKind": "bulk", + "name": "build", + "summary": "Build projects", + "enableParallelism": true, // Whether to allow parallelism + "ignoreMissingScript": false // Whether to ignore missing scripts + } + ``` + 2. `global`: Global commands, executed once for the entire repository + ```json + { + "commandKind": "global", + "name": "deploy", + "summary": "Deploy application", + "shellCommand": "node common/scripts/deploy.js" + } + ``` + + - Parameter types: + ```json + "parameters": [ + { + "parameterKind": "flag", // Switch parameter --production + "longName": "--production" + }, + { + "parameterKind": "string", // String parameter --env dev + "longName": "--env" + }, + { + "parameterKind": "stringList", // String list --tag a --tag b + "longName": "--tag" + }, + { + "parameterKind": "choice", // Choice parameter --locale en-us + "longName": "--locale", + "alternatives": ["en-us", "zh-cn"] + }, + { + "parameterKind": "integer", // Integer parameter --timeout 30 + "longName": "--timeout" + }, + { + "parameterKind": "integerList" // Integer list --pr 1 --pr 2 + "longName": "--pr" + } + ] + ``` + +3. `common/config/subspaces//common-versions.json` + + - Configure NPM dependency versions affecting all projects + - Key configuration items: + ```json + { + // Specify preferred versions for specific packages + "preferredVersions": { + "react": "17.0.2", // Restrict react version + "typescript": "~4.5.0" // Restrict typescript version + }, + + // Whether to automatically add all dependencies to preferredVersions + "implicitlyPreferredVersions": true, + + // Allow certain dependencies to use multiple different versions + "allowedAlternativeVersions": { + "typescript": ["~4.5.0", "~4.6.0"] + } + } + ``` + +4. `common/config/rush/subspaces.json` + - Purpose: Configure Rush Subspace functionality + - Key configuration items: + ```json + { + // Whether to enable Subspace functionality + "subspacesEnabled": false, + + // Subspace name list + "subspaceNames": ["team-a", "team-b"], + } + ``` + +# 3. Command Usage + +## 3.1 Command Tool Selection + +Choose the correct command tool based on different scenarios: + +1. `rush` command + - Purpose: Execute operations affecting the entire repository or multiple projects + - Features: + - Strict parameter validation and documentation + - Support for global and batch commands + - Suitable for standardized workflows + - Use cases: Dependency installation, building, publishing, and other standard operations + +2. `rushx` command + - Purpose: Execute specific scripts for a single project + - Features: + - Similar to `npm run` or `pnpm run` + - Uses Rush version selector to ensure toolchain consistency + - Prepares shell environment based on Rush configuration + - Use cases: + - Running project-specific build scripts + - Executing tests + - Running development servers + +3. `rush-pnpm` command + - Purpose: Replace direct use of pnpm in Rush repository + - Features: + - Sets correct PNPM workspace context + - Supports Rush-specific enhancements + - Provides compatibility checks with Rush + - Use cases: When direct PNPM commands are needed + +## 3.2 Common Commands Explained + +1. `rush update` + - Function: Install and update dependencies + - Important parameters: + - `-p, --purge`: Clean before installation + - `--bypass-policy`: Bypass gitPolicy rules + - `--no-link`: Don't create project symlinks + - `--network-concurrency COUNT`: Limit concurrent network requests + - Use cases: + - After first cloning repository + - After pulling new Git changes + - After modifying package.json + - When dependencies need updating + +2. `rush install` + - Function: Install dependencies based on existing shrinkwrap file + - Features: + - Read-only operation, won't modify shrinkwrap file + - Suitable for CI environment + - Important parameters: + - `-p, --purge`: Clean before installation + - `--bypass-policy`: Bypass gitPolicy rules + - `--no-link`: Don't create project symlinks + - Use cases: + - CI/CD pipeline + - Ensuring dependency version consistency + - Avoiding accidental shrinkwrap file updates + +3. `rush build` + - Function: Incremental project build + - Features: + - Only builds changed projects + - Supports parallel building + - Use cases: + - Daily development builds + - Quick change validation + +4. `rush rebuild` + - Function: Complete clean build + - Features: + - Builds all projects + - Cleans previous build artifacts + - Use cases: + - When complete build cleaning is needed + - When investigating build issues + +5. `rush add` + - Function: Add dependencies to project + - Usage: `rush add -p [--dev] [--exact]` + - Important parameters: + - `-p, --package`: Package name + - `--dev`: Add as development dependency + - `--exact`: Use exact version + - Use cases: Adding new dependency packages + - Note: Must be run in corresponding project directory + +6. `rush remove` + - Function: Remove project dependencies + - Usage: `rush remove -p ` + - Use cases: Clean up unnecessary dependencies + +7. `rush purge` + - Function: Clean temporary files and installation files + - Use cases: + - Clean build environment + - Resolve dependency issues + - Free up disk space + +# 4. Dependency Management + +## 4.1 Package Manager Selection + +Specify in `rush.json`: + ```json + { + // Choose PNPM as package manager + "pnpmVersion": "8.x.x", + // Or use NPM + // "npmVersion": "8.x.x", + // Or use Yarn + // "yarnVersion": "1.x.x", + } + ``` + +## 4.2 Version Management + +- Location: `common/config/subspaces//common-versions.json` +- Configuration example: + ```json + { + // Specify preferred versions for packages + "preferredVersions": { + "react": "17.0.2", + "typescript": "~4.5.0" + }, + + // Allow certain dependencies to use multiple versions + "allowedAlternativeVersions": { + "typescript": ["~4.5.0", "~4.6.0"] + } + } + ``` + +## 4.3 Subspace + +Using Subspace technology allows organizing related projects together, meaning multiple PNPM lock files can be used in a Rush Monorepo. Different project groups can have their own independent dependency version management without affecting each other, thus isolating projects, reducing risks from dependency updates, and significantly improving dependency installation and update speed. + +Declare which Subspaces exist in `common/config/rush/subspaces.json`, and declare which Subspace each project belongs to in `rush.json`'s `subspaceName`. + +# 5. Caching Capabilities + +## 5.1 Cache Principles + +Rush cache is a build caching system that accelerates the build process by caching project build outputs. Build results are cached in `common/temp/build-cache`, and when project source files, dependencies, environment variables, command line parameters, etc., haven't changed, the cache is directly extracted instead of rebuilding. + +## 5.2 Core Configuration + +Configuration file: `/config/rush-project.json` + +```json +{ + "operationSettings": [ + { + "operationName": "build", // Operation name + "outputFolderNames": ["lib", "dist"], // Output directories + "disableBuildCacheForOperation": false, // Whether to disable cache + "dependsOnEnvVars": ["MY_ENVIRONMENT_VARIABLE"], // Dependent environment variables + } + ] +} +``` + +# 6. Best Practices + +## 6.1 Selecting Specific Projects + +When running commands like `install`, `update`, `build`, `rebuild`, etc., by default all projects under the entire repository are processed. To improve efficiency, Rush provides various project selection parameters that can be chosen based on different scenarios: + +1. `--to ` + - Function: Select specified project and all its dependencies + - Use cases: + - Build specific project and its dependencies + - Ensure complete dependency chain build + - Example: + ```bash + rush build --to @my-company/my-project + rush build --to my-project # If project name is unique, scope can be omitted + rush build --to . # Use current directory's project + ``` + +2. `--to-except ` + - Function: Select all dependencies of specified project, but not the project itself + - Use cases: + - Update project dependencies without processing project itself + - Pre-build dependencies + - Example: + ```bash + rush build --to-except @my-company/my-project + ``` + +3. `--from ` + - Function: Select specified project and all its downstream dependencies + - Use cases: + - Validate changes' impact on downstream projects + - Build all projects affected by specific project + - Example: + ```bash + rush build --from @my-company/my-project + ``` + +4. `--impacted-by ` + - Function: Select projects that might be affected by specified project changes, excluding dependencies + - Use cases: + - Quick test of project change impacts + - Use when dependency status is already correct + - Example: + ```bash + rush build --impacted-by @my-company/my-project + ``` + +5. `--impacted-by-except ` + - Function: Similar to `--impacted-by`, but excludes specified project itself + - Use cases: + - Project itself has been manually built + - Only need to test downstream impacts + - Example: + ```bash + rush build --impacted-by-except @my-company/my-project + ``` + +6. `--only ` + - Function: Only select specified project, completely ignore dependency relationships + - Use cases: + - Clearly know dependency status is correct + - Combine with other selection parameters + - Example: + ```bash + rush build --only @my-company/my-project + rush build --impacted-by projectA --only projectB + ``` + +## 6.2 Troubleshooting + +1. Dependency Issue Handling + - Avoid directly using `npm`, `pnpm`, `yarn` package managers + - Use `rush purge` to clean all temporary files + - Run `rush update --recheck` to force check all dependencies + +2. Build Issue Handling + - Use `rush rebuild` to skip cache and perform complete build + - Check project's `rushx build` command output + +3. Logging and Diagnostics + - Use `--verbose` parameter for detailed logs + - Verify command parameter correctness diff --git a/README.md b/README.md index a506c6af0e1..39383c2fa4e 100644 --- a/README.md +++ b/README.md @@ -100,8 +100,10 @@ These GitHub repositories provide supplementary resources for Rush Stack: | [/rigs/heft-web-rig](./rigs/heft-web-rig/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Fheft-web-rig.svg)](https://badge.fury.io/js/%40rushstack%2Fheft-web-rig) | [changelog](./rigs/heft-web-rig/CHANGELOG.md) | [@rushstack/heft-web-rig](https://www.npmjs.com/package/@rushstack/heft-web-rig) | | [/rush-plugins/rush-amazon-s3-build-cache-plugin](./rush-plugins/rush-amazon-s3-build-cache-plugin/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Frush-amazon-s3-build-cache-plugin.svg)](https://badge.fury.io/js/%40rushstack%2Frush-amazon-s3-build-cache-plugin) | | [@rushstack/rush-amazon-s3-build-cache-plugin](https://www.npmjs.com/package/@rushstack/rush-amazon-s3-build-cache-plugin) | | [/rush-plugins/rush-azure-storage-build-cache-plugin](./rush-plugins/rush-azure-storage-build-cache-plugin/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Frush-azure-storage-build-cache-plugin.svg)](https://badge.fury.io/js/%40rushstack%2Frush-azure-storage-build-cache-plugin) | | [@rushstack/rush-azure-storage-build-cache-plugin](https://www.npmjs.com/package/@rushstack/rush-azure-storage-build-cache-plugin) | +| [/rush-plugins/rush-bridge-cache-plugin](./rush-plugins/rush-bridge-cache-plugin/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Frush-bridge-cache-plugin.svg)](https://badge.fury.io/js/%40rushstack%2Frush-bridge-cache-plugin) | | [@rushstack/rush-bridge-cache-plugin](https://www.npmjs.com/package/@rushstack/rush-bridge-cache-plugin) | | [/rush-plugins/rush-buildxl-graph-plugin](./rush-plugins/rush-buildxl-graph-plugin/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Frush-buildxl-graph-plugin.svg)](https://badge.fury.io/js/%40rushstack%2Frush-buildxl-graph-plugin) | | [@rushstack/rush-buildxl-graph-plugin](https://www.npmjs.com/package/@rushstack/rush-buildxl-graph-plugin) | | [/rush-plugins/rush-http-build-cache-plugin](./rush-plugins/rush-http-build-cache-plugin/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Frush-http-build-cache-plugin.svg)](https://badge.fury.io/js/%40rushstack%2Frush-http-build-cache-plugin) | | [@rushstack/rush-http-build-cache-plugin](https://www.npmjs.com/package/@rushstack/rush-http-build-cache-plugin) | +| [/rush-plugins/rush-mcp-docs-plugin](./rush-plugins/rush-mcp-docs-plugin/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Frush-mcp-docs-plugin.svg)](https://badge.fury.io/js/%40rushstack%2Frush-mcp-docs-plugin) | [changelog](./rush-plugins/rush-mcp-docs-plugin/CHANGELOG.md) | [@rushstack/rush-mcp-docs-plugin](https://www.npmjs.com/package/@rushstack/rush-mcp-docs-plugin) | | [/rush-plugins/rush-redis-cobuild-plugin](./rush-plugins/rush-redis-cobuild-plugin/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Frush-redis-cobuild-plugin.svg)](https://badge.fury.io/js/%40rushstack%2Frush-redis-cobuild-plugin) | | [@rushstack/rush-redis-cobuild-plugin](https://www.npmjs.com/package/@rushstack/rush-redis-cobuild-plugin) | | [/rush-plugins/rush-resolver-cache-plugin](./rush-plugins/rush-resolver-cache-plugin/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Frush-resolver-cache-plugin.svg)](https://badge.fury.io/js/%40rushstack%2Frush-resolver-cache-plugin) | | [@rushstack/rush-resolver-cache-plugin](https://www.npmjs.com/package/@rushstack/rush-resolver-cache-plugin) | | [/rush-plugins/rush-serve-plugin](./rush-plugins/rush-serve-plugin/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Frush-serve-plugin.svg)](https://badge.fury.io/js/%40rushstack%2Frush-serve-plugin) | | [@rushstack/rush-serve-plugin](https://www.npmjs.com/package/@rushstack/rush-serve-plugin) | @@ -195,6 +197,7 @@ These GitHub repositories provide supplementary resources for Rush Stack: | [/build-tests/run-scenarios-helpers](./build-tests/run-scenarios-helpers/) | Helpers for the *-scenarios test projects. | | [/build-tests/rush-amazon-s3-build-cache-plugin-integration-test](./build-tests/rush-amazon-s3-build-cache-plugin-integration-test/) | Tests connecting to an amazon S3 endpoint | | [/build-tests/rush-lib-declaration-paths-test](./build-tests/rush-lib-declaration-paths-test/) | This project ensures all of the paths in rush-lib/lib/... have imports that resolve correctly. If this project builds, all `lib/**/*.d.ts` files in the `@microsoft/rush-lib` package are valid. | +| [/build-tests/rush-mcp-example-plugin](./build-tests/rush-mcp-example-plugin/) | Example showing how to create a plugin for @rushstack/mcp-server | | [/build-tests/rush-project-change-analyzer-test](./build-tests/rush-project-change-analyzer-test/) | This is an example project that uses rush-lib's ProjectChangeAnalyzer to | | [/build-tests/rush-redis-cobuild-plugin-integration-test](./build-tests/rush-redis-cobuild-plugin-integration-test/) | Tests connecting to an redis server | | [/build-tests/set-webpack-public-path-plugin-test](./build-tests/set-webpack-public-path-plugin-test/) | Building this project tests the set-webpack-public-path-plugin | diff --git a/apps/rush-mcp-server/CHANGELOG.json b/apps/rush-mcp-server/CHANGELOG.json index 16c90877c5a..6ae472e09d0 100644 --- a/apps/rush-mcp-server/CHANGELOG.json +++ b/apps/rush-mcp-server/CHANGELOG.json @@ -1,6 +1,54 @@ { "name": "@rushstack/mcp-server", "entries": [ + { + "version": "0.2.2", + "tag": "@rushstack/mcp-server_v0.2.2", + "date": "Sat, 14 Jun 2025 00:11:30 GMT", + "comments": { + "patch": [ + { + "comment": "Move the `rush docs` tool to a plugin package `@rushstack/rush-mcp-docs-plugin`" + } + ] + } + }, + { + "version": "0.2.1", + "tag": "@rushstack/mcp-server_v0.2.1", + "date": "Fri, 06 Jun 2025 08:20:18 GMT", + "comments": { + "patch": [ + { + "comment": "Fix some errors when loading plugins" + } + ] + } + }, + { + "version": "0.2.0", + "tag": "@rushstack/mcp-server_v0.2.0", + "date": "Fri, 06 Jun 2025 00:11:09 GMT", + "comments": { + "minor": [ + { + "comment": "Introduce a plugin system for custom MCP tools" + } + ] + } + }, + { + "version": "0.1.8", + "tag": "@rushstack/mcp-server_v0.1.8", + "date": "Thu, 29 May 2025 15:10:58 GMT", + "comments": { + "patch": [ + { + "comment": "Update the rush mcp server README.md document" + } + ] + } + }, { "version": "0.1.7", "tag": "@rushstack/mcp-server_v0.1.7", diff --git a/apps/rush-mcp-server/CHANGELOG.md b/apps/rush-mcp-server/CHANGELOG.md index f6d4923e02c..a4dab4fd20c 100644 --- a/apps/rush-mcp-server/CHANGELOG.md +++ b/apps/rush-mcp-server/CHANGELOG.md @@ -1,6 +1,34 @@ # Change Log - @rushstack/mcp-server -This log was last generated on Tue, 13 May 2025 02:09:20 GMT and should not be manually modified. +This log was last generated on Sat, 14 Jun 2025 00:11:30 GMT and should not be manually modified. + +## 0.2.2 +Sat, 14 Jun 2025 00:11:30 GMT + +### Patches + +- Move the `rush docs` tool to a plugin package `@rushstack/rush-mcp-docs-plugin` + +## 0.2.1 +Fri, 06 Jun 2025 08:20:18 GMT + +### Patches + +- Fix some errors when loading plugins + +## 0.2.0 +Fri, 06 Jun 2025 00:11:09 GMT + +### Minor changes + +- Introduce a plugin system for custom MCP tools + +## 0.1.8 +Thu, 29 May 2025 15:10:58 GMT + +### Patches + +- Update the rush mcp server README.md document ## 0.1.7 Tue, 13 May 2025 02:09:20 GMT diff --git a/apps/rush-mcp-server/README.md b/apps/rush-mcp-server/README.md index deb4a2c82a7..fc0f6f81523 100644 --- a/apps/rush-mcp-server/README.md +++ b/apps/rush-mcp-server/README.md @@ -1,5 +1,37 @@ # @rushstack/mcp-server +With the rapid advancement of LLMs, AI applications like Trae, Cursor, Cline, Windsurf, and others have been thriving. However, due to the large scale of monorepos and the context limitations of LLMs, it’s difficult for these models to fully understand your monorepo. This is where @rushstack/mcp-server comes in — by providing a suite of MCP tools, it enables LLMs to better comprehend your monorepo and assist you more effectively with daily development tasks in a Rush-based monorepo environment. + ## Usage +1. To get the best results, copy the [.cursor](https://github.com/microsoft/rushstack/tree/main/.cursor) directory into the root of your project. + +2. Configure `@rushstack/mcp-server` in your AI application + +``` +{ + "mcpServers": { + "rush": { + "command": "npx", + "args": ["-y", "@rushstack/mcp-server", "your-project-path"] + } + } +} +``` + +3. Congratulations 🎉 You’ve completed the setup — Rush MCP is now ready to use! + +## Available Tools + +- `rush_docs`: Retrieves relevant documentation sections based on your queries +- `rush_workspace_details`: Retrieve detailed workspace information +- `rush_project_details`: Get detailed information about a specific project +- `rush_command_validator`: Validate whether commands are compliant and follow best practices +- `rush_migrate_project`: Migrate a project from one directory to another or into a different subspace +- `rush_pnpm_lock_file_conflict_resolver`: Resolve pnpm-lock.yaml conflicts + ## Links + +- [CHANGELOG.md]( + https://github.com/microsoft/rushstack/blob/main/apps/rush-mcp-server/CHANGELOG.md) - Find + out what's new in the latest version diff --git a/apps/rush-mcp-server/bin/mcp-server b/apps/rush-mcp-server/bin/mcp-server old mode 100644 new mode 100755 diff --git a/apps/rush-mcp-server/config/api-extractor.json b/apps/rush-mcp-server/config/api-extractor.json new file mode 100644 index 00000000000..996e271d3dd --- /dev/null +++ b/apps/rush-mcp-server/config/api-extractor.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + + "mainEntryPointFilePath": "/lib/index.d.ts", + + "apiReport": { + "enabled": true, + "reportFolder": "../../../common/reviews/api" + }, + + "docModel": { + "enabled": true, + "apiJsonFilePath": "../../../common/temp/api/.api.json" + }, + + "dtsRollup": { + "enabled": true + } +} diff --git a/apps/rush-mcp-server/package.json b/apps/rush-mcp-server/package.json index 1d7f9e60ebc..12ba7d72946 100644 --- a/apps/rush-mcp-server/package.json +++ b/apps/rush-mcp-server/package.json @@ -1,7 +1,16 @@ { "name": "@rushstack/mcp-server", - "version": "0.1.7", + "version": "0.2.2", "description": "A Model Context Protocol server implementation for Rush", + "keywords": [ + "rush", + "modelcontextprotocol", + "mcp", + "monorepo", + "server" + ], + "main": "lib/index.js", + "typings": "dist/mcp-server.d.ts", "repository": { "type": "git", "url": "https://github.com/microsoft/rushstack.git", diff --git a/apps/rush-mcp-server/src/index.ts b/apps/rush-mcp-server/src/index.ts index 7dca0d0f5e9..20b52112e78 100644 --- a/apps/rush-mcp-server/src/index.ts +++ b/apps/rush-mcp-server/src/index.ts @@ -1,5 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -export { log } from './utilities/log'; -export * from './tools'; +/** + * API for use by MCP plugins. + * @packageDocumentation + */ + +export * from './pluginFramework/IRushMcpPlugin'; +export * from './pluginFramework/IRushMcpTool'; +export { type IRegisterToolOptions, RushMcpPluginSession } from './pluginFramework/RushMcpPluginSession'; +export * from './pluginFramework/zodTypes'; diff --git a/apps/rush-mcp-server/src/pluginFramework/IRushMcpPlugin.ts b/apps/rush-mcp-server/src/pluginFramework/IRushMcpPlugin.ts new file mode 100644 index 00000000000..fa74680ccab --- /dev/null +++ b/apps/rush-mcp-server/src/pluginFramework/IRushMcpPlugin.ts @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import type { RushMcpPluginSession } from './RushMcpPluginSession'; + +/** + * MCP plugins should implement this interface. + * @public + */ +export interface IRushMcpPlugin { + onInitializeAsync(): Promise; +} + +/** + * The plugin's entry point should return this function as its default export. + * @public + */ +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +export type RushMcpPluginFactory = ( + session: RushMcpPluginSession, + configFile: TConfigFile | undefined +) => IRushMcpPlugin; diff --git a/apps/rush-mcp-server/src/pluginFramework/IRushMcpTool.ts b/apps/rush-mcp-server/src/pluginFramework/IRushMcpTool.ts new file mode 100644 index 00000000000..46f325b0273 --- /dev/null +++ b/apps/rush-mcp-server/src/pluginFramework/IRushMcpTool.ts @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import type * as zod from 'zod'; + +import type { CallToolResult } from './zodTypes'; + +/** + * MCP plugins should implement this interface. + * @public + */ +export interface IRushMcpTool< + TSchema extends zod.ZodObject = zod.ZodObject +> { + readonly schema: TSchema; + executeAsync(input: zod.infer): Promise; +} diff --git a/apps/rush-mcp-server/src/pluginFramework/RushMcpPluginLoader.ts b/apps/rush-mcp-server/src/pluginFramework/RushMcpPluginLoader.ts new file mode 100644 index 00000000000..f0715d4d0fe --- /dev/null +++ b/apps/rush-mcp-server/src/pluginFramework/RushMcpPluginLoader.ts @@ -0,0 +1,199 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as path from 'path'; +import { FileSystem, Import, JsonFile, type JsonObject, JsonSchema } from '@rushstack/node-core-library'; +import { Autoinstaller } from '@rushstack/rush-sdk/lib/logic/Autoinstaller'; +import { RushGlobalFolder } from '@rushstack/rush-sdk/lib/api/RushGlobalFolder'; +import { RushConfiguration } from '@rushstack/rush-sdk/lib/api/RushConfiguration'; +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp'; + +import type { IRushMcpPlugin, RushMcpPluginFactory } from './IRushMcpPlugin'; +import { RushMcpPluginSessionInternal } from './RushMcpPluginSession'; + +import rushMcpJsonSchemaObject from '../schemas/rush-mcp.schema.json'; +import rushMcpPluginSchemaObject from '../schemas/rush-mcp-plugin.schema.json'; + +/** + * Configuration for @rushstack/mcp-server in a monorepo. + * Corresponds to the contents of common/config/rush-mcp/rush-mcp.json + */ +export interface IJsonRushMcpConfig { + /** + * The list of plugins that @rushstack/mcp-server should load when processing this monorepo. + */ + mcpPlugins: IJsonRushMcpPlugin[]; +} + +/** + * Describes a single MCP plugin entry. + */ +export interface IJsonRushMcpPlugin { + /** + * The name of an NPM package that appears in the package.json "dependencies" for the autoinstaller. + */ + packageName: string; + + /** + * The name of a Rush autoinstaller with this package as its dependency. + * @rushstack/mcp-server will ensure this folder is installed before loading the plugin. + */ + autoinstaller: string; +} + +/** + * Manifest file for a Rush MCP plugin. + * Every plugin package must contain a "rush-mcp-plugin.json" manifest in the top-level folder. + */ +export interface IJsonRushMcpPluginManifest { + /** + * A name that uniquely identifies your plugin. + * Generally this should match the NPM package name; two plugins with the same pluginName cannot be loaded together. + */ + pluginName: string; + + /** + * Optional. Indicates that your plugin accepts a config file. + * The MCP server will load this schema file and provide it to the plugin. + * Path is typically `/common/config/rush-mcp/.json`. + */ + configFileSchema?: string; + + /** + * The module path to the plugin's entry point. + * Its default export must be a class implementing the MCP plugin interface. + */ + entryPoint: string; +} + +export class RushMcpPluginLoader { + private static readonly _rushMcpJsonSchema: JsonSchema = + JsonSchema.fromLoadedObject(rushMcpJsonSchemaObject); + private static readonly _rushMcpPluginSchemaObject: JsonSchema = + JsonSchema.fromLoadedObject(rushMcpPluginSchemaObject); + + private readonly _rushWorkspacePath: string; + private readonly _mcpServer: McpServer; + + public constructor(rushWorkspacePath: string, mcpServer: McpServer) { + this._rushWorkspacePath = rushWorkspacePath; + this._mcpServer = mcpServer; + } + + private static _formatError(e: Error): string { + return e.stack ?? RushMcpPluginLoader._formatError(e); + } + + public async loadAsync(): Promise { + const rushMcpFilePath: string = path.join( + this._rushWorkspacePath, + 'common/config/rush-mcp/rush-mcp.json' + ); + + if (!(await FileSystem.existsAsync(rushMcpFilePath))) { + return; + } + + const rushConfiguration: RushConfiguration = RushConfiguration.loadFromDefaultLocation({ + startingFolder: this._rushWorkspacePath + }); + + const jsonRushMcpConfig: IJsonRushMcpConfig = await JsonFile.loadAndValidateAsync( + rushMcpFilePath, + RushMcpPluginLoader._rushMcpJsonSchema + ); + + if (jsonRushMcpConfig.mcpPlugins.length === 0) { + return; + } + + const rushGlobalFolder: RushGlobalFolder = new RushGlobalFolder(); + + for (const jsonMcpPlugin of jsonRushMcpConfig.mcpPlugins) { + // Ensure the autoinstaller is installed + const autoinstaller: Autoinstaller = new Autoinstaller({ + autoinstallerName: jsonMcpPlugin.autoinstaller, + rushConfiguration, + rushGlobalFolder, + restrictConsoleOutput: false + }); + await autoinstaller.prepareAsync(); + + // Load the manifest + + // Suppose the autoinstaller is "my-autoinstaller" and the package is "rush-mcp-example-plugin". + // Then the folder will be: + // "/path/to/my-repo/common/autoinstallers/my-autoinstaller/node_modules/rush-mcp-example-plugin" + const installedPluginPackageFolder: string = await Import.resolvePackageAsync({ + baseFolderPath: autoinstaller.folderFullPath, + packageName: jsonMcpPlugin.packageName + }); + + const manifestFilePath: string = path.join(installedPluginPackageFolder, 'rush-mcp-plugin.json'); + if (!(await FileSystem.existsAsync(manifestFilePath))) { + throw new Error( + 'The "rush-mcp-plugin.json" manifest file was not found under ' + installedPluginPackageFolder + ); + } + + const jsonManifest: IJsonRushMcpPluginManifest = await JsonFile.loadAndValidateAsync( + manifestFilePath, + RushMcpPluginLoader._rushMcpPluginSchemaObject + ); + + let rushMcpPluginOptions: JsonObject = {}; + if (jsonManifest.configFileSchema) { + const mcpPluginSchemaFilePath: string = path.resolve( + installedPluginPackageFolder, + jsonManifest.configFileSchema + ); + const mcpPluginSchema: JsonSchema = await JsonSchema.fromFile(mcpPluginSchemaFilePath); + const rushMcpPluginOptionsFilePath: string = path.resolve( + this._rushWorkspacePath, + `common/config/rush-mcp/${jsonManifest.pluginName}.json` + ); + // Example: /path/to/my-repo/common/config/rush-mcp/rush-mcp-example-plugin.json + rushMcpPluginOptions = await JsonFile.loadAndValidateAsync( + rushMcpPluginOptionsFilePath, + mcpPluginSchema + ); + } + + const fullEntryPointPath: string = path.join(installedPluginPackageFolder, jsonManifest.entryPoint); + let pluginFactory: RushMcpPluginFactory; + try { + const entryPointModule: { default?: RushMcpPluginFactory } = require(fullEntryPointPath); + if (entryPointModule.default === undefined) { + throw new Error('The commonJS "default" export is missing'); + } + pluginFactory = entryPointModule.default; + } catch (e) { + throw new Error( + `Unable to load plugin entry point at ${fullEntryPointPath}:\n` + + RushMcpPluginLoader._formatError(e) + ); + } + + const session: RushMcpPluginSessionInternal = new RushMcpPluginSessionInternal(this._mcpServer); + + let plugin: IRushMcpPlugin; + try { + plugin = pluginFactory(session, rushMcpPluginOptions); + } catch (e) { + throw new Error( + `Error invoking entry point for plugin ${jsonManifest.pluginName}:\n` + + RushMcpPluginLoader._formatError(e) + ); + } + + try { + await plugin.onInitializeAsync(); + } catch (e) { + throw new Error( + `Error occurred in onInitializeAsync() for plugin ${jsonManifest.pluginName}:\n` + + RushMcpPluginLoader._formatError(e) + ); + } + } + } +} diff --git a/apps/rush-mcp-server/src/pluginFramework/RushMcpPluginSession.ts b/apps/rush-mcp-server/src/pluginFramework/RushMcpPluginSession.ts new file mode 100644 index 00000000000..aa46003a8cf --- /dev/null +++ b/apps/rush-mcp-server/src/pluginFramework/RushMcpPluginSession.ts @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as zod from 'zod'; +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp'; + +import type { IRushMcpTool } from './IRushMcpTool'; +import type { zodModule } from './zodTypes'; + +/** + * Each plugin gets its own session. + * + * @public + */ +export interface IRegisterToolOptions { + toolName: string; + description?: string; +} + +/** + * Each plugin gets its own session. + * + * @public + */ +export abstract class RushMcpPluginSession { + public readonly zod: typeof zodModule = zod; + public abstract registerTool(options: IRegisterToolOptions, tool: IRushMcpTool): void; +} + +export class RushMcpPluginSessionInternal extends RushMcpPluginSession { + private readonly _mcpServer: McpServer; + + public constructor(mcpServer: McpServer) { + super(); + this._mcpServer = mcpServer; + } + + public override registerTool(options: IRegisterToolOptions, tool: IRushMcpTool): void { + if (options.description) { + this._mcpServer.tool( + options.toolName, + options.description, + tool.schema.shape, + tool.executeAsync.bind(tool) + ); + } else { + this._mcpServer.tool(options.toolName, tool.schema.shape, tool.executeAsync.bind(tool)); + } + } +} diff --git a/apps/rush-mcp-server/src/pluginFramework/zodTypes.ts b/apps/rush-mcp-server/src/pluginFramework/zodTypes.ts new file mode 100644 index 00000000000..19bf22a2f12 --- /dev/null +++ b/apps/rush-mcp-server/src/pluginFramework/zodTypes.ts @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import type * as zod from 'zod'; +export type { zod as zodModule }; + +import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types'; + +export { CallToolResultSchema }; + +/** + * @public + */ +export type CallToolResult = zod.infer; diff --git a/apps/rush-mcp-server/src/schemas/rush-mcp-plugin.schema.json b/apps/rush-mcp-server/src/schemas/rush-mcp-plugin.schema.json new file mode 100644 index 00000000000..e8e8757d9f1 --- /dev/null +++ b/apps/rush-mcp-server/src/schemas/rush-mcp-plugin.schema.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Rush MCP Plugin Manifest", + "type": "object", + "properties": { + "pluginName": { + "type": "string", + "description": "A name that uniquely identifies your plugin. Generally this should match the NPM package name; two plugins with the same pluginName cannot be loaded together." + }, + "configFileSchema": { + "type": "string", + "description": "Optional. Indicates that your plugin accepts a config file. The MCP server will load this schema file and provide it to the plugin. Path is typically `/common/config/rush-mcp/.json`." + }, + "entryPoint": { + "type": "string", + "description": "The module path to the plugin's entry point. Its default export must be a class implementing the MCP plugin interface." + } + }, + "required": ["pluginName", "entryPoint"], + "additionalProperties": false +} diff --git a/apps/rush-mcp-server/src/schemas/rush-mcp.schema.json b/apps/rush-mcp-server/src/schemas/rush-mcp.schema.json new file mode 100644 index 00000000000..0dd9a9546fe --- /dev/null +++ b/apps/rush-mcp-server/src/schemas/rush-mcp.schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "rush-mcp.json Configuration Schema", + "type": "object", + "properties": { + "mcpPlugins": { + "type": "array", + "description": "The list of plugins that `@rushstack/mcp-server` should load when processing this monorepo.", + "items": { + "type": "object", + "properties": { + "packageName": { + "type": "string", + "description": "The name of an NPM package that appears in the package.json \"dependencies\" for the autoinstaller." + }, + "autoinstaller": { + "type": "string", + "description": "The name of a Rush autoinstaller with this package as its dependency." + } + }, + "required": ["packageName", "autoinstaller"], + "additionalProperties": false + } + } + }, + "required": ["mcpPlugins"], + "additionalProperties": false +} diff --git a/apps/rush-mcp-server/src/server.ts b/apps/rush-mcp-server/src/server.ts index 9e4b7e0ced3..b6132bd0faf 100644 --- a/apps/rush-mcp-server/src/server.ts +++ b/apps/rush-mcp-server/src/server.ts @@ -8,13 +8,14 @@ import { RushMigrateProjectTool, RushCommandValidatorTool, RushWorkspaceDetailsTool, - RushProjectDetailsTool, - RushDocsTool + RushProjectDetailsTool } from './tools'; +import { RushMcpPluginLoader } from './pluginFramework/RushMcpPluginLoader'; export class RushMCPServer extends McpServer { private _rushWorkspacePath: string; private _tools: BaseTool[] = []; + private _pluginLoader: RushMcpPluginLoader; public constructor(rushWorkspacePath: string) { super({ @@ -23,9 +24,14 @@ export class RushMCPServer extends McpServer { }); this._rushWorkspacePath = rushWorkspacePath; + this._pluginLoader = new RushMcpPluginLoader(this._rushWorkspacePath, this); + } + public async startAsync(): Promise { this._initializeTools(); this._registerTools(); + + await this._pluginLoader.loadAsync(); } private _initializeTools(): void { @@ -34,7 +40,6 @@ export class RushMCPServer extends McpServer { this._tools.push(new RushCommandValidatorTool()); this._tools.push(new RushWorkspaceDetailsTool()); this._tools.push(new RushProjectDetailsTool()); - this._tools.push(new RushDocsTool()); } private _registerTools(): void { diff --git a/apps/rush-mcp-server/src/start.ts b/apps/rush-mcp-server/src/start.ts index 93ab38d285f..fa74da180ea 100644 --- a/apps/rush-mcp-server/src/start.ts +++ b/apps/rush-mcp-server/src/start.ts @@ -1,10 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +import * as path from 'path'; +import * as process from 'process'; +import { FileSystem } from '@rushstack/node-core-library'; +import { RushSdkLoader } from '@rushstack/rush-sdk/loader'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { log } from './utilities/log'; -import { RushMCPServer } from './server'; +import type { RushMCPServer } from './server'; const main = async (): Promise => { const rushWorkspacePath: string | undefined = process.argv[2]; @@ -12,7 +16,23 @@ const main = async (): Promise => { throw new Error('Please provide workspace root path as the first argument'); } - const server: RushMCPServer = new RushMCPServer(rushWorkspacePath); + const rushWorkspaceFullPath: string = path.resolve(rushWorkspacePath); + + if (!(await FileSystem.existsAsync(rushWorkspaceFullPath))) { + throw new Error( + 'The specified workspace root path does not exist:\n ' + JSON.stringify(rushWorkspacePath) + ); + } + + // Load rush-sdk from the specified repository + await RushSdkLoader.loadAsync({ + rushJsonSearchFolder: rushWorkspaceFullPath + }); + + const RushMCPServerClass: typeof RushMCPServer = (await import('./server')).RushMCPServer; + + const server: RushMCPServer = new RushMCPServerClass(rushWorkspaceFullPath); + await server.startAsync(); const transport: StdioServerTransport = new StdioServerTransport(); await server.connect(transport); @@ -20,6 +40,6 @@ const main = async (): Promise => { }; main().catch((error) => { - log('Fatal error running server:', error); + log('Fatal error running server:', error instanceof Error ? error.message : error); process.exit(1); }); diff --git a/apps/rush-mcp-server/src/tools/index.ts b/apps/rush-mcp-server/src/tools/index.ts index 025e6d5452c..b0c15c01eee 100644 --- a/apps/rush-mcp-server/src/tools/index.ts +++ b/apps/rush-mcp-server/src/tools/index.ts @@ -7,4 +7,3 @@ export * from './project-details.tool'; export * from './rush-command-validator.tool'; export * from './workspace-details'; export * from './conflict-resolver.tool'; -export * from './docs.tool'; diff --git a/apps/rush-mcp-server/src/utilities/log.ts b/apps/rush-mcp-server/src/utilities/log.ts index 26bfa27b80d..c0595baec77 100644 --- a/apps/rush-mcp-server/src/utilities/log.ts +++ b/apps/rush-mcp-server/src/utilities/log.ts @@ -1,11 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import { Terminal, ConsoleTerminalProvider } from '@rushstack/terminal'; +import { Terminal, ConsoleTerminalProvider, type TerminalWriteParameters } from '@rushstack/terminal'; export const terminal: Terminal = new Terminal(new ConsoleTerminalProvider({ verboseEnabled: true })); -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function log(message?: any, ...optionalParams: any[]): void { - terminal.writeErrorLine(message, ...optionalParams); +export function log(...messageParts: TerminalWriteParameters): void { + terminal.writeErrorLine(...messageParts); } diff --git a/apps/rush/CHANGELOG.json b/apps/rush/CHANGELOG.json index b85ea714be5..e125757a9c1 100644 --- a/apps/rush/CHANGELOG.json +++ b/apps/rush/CHANGELOG.json @@ -1,6 +1,39 @@ { "name": "@microsoft/rush", "entries": [ + { + "version": "5.155.0", + "tag": "@microsoft/rush_v5.155.0", + "date": "Fri, 13 Jun 2025 16:10:38 GMT", + "comments": { + "none": [ + { + "comment": "Add support for PNPM v9 to the pnpm-sync feature." + } + ] + } + }, + { + "version": "5.154.0", + "tag": "@microsoft/rush_v5.154.0", + "date": "Tue, 10 Jun 2025 18:45:59 GMT", + "comments": { + "none": [ + { + "comment": "Introduce a `@rushstack/rush-bridge-cache-plugin` package that adds a `--set-cache-only` flag to phased commands, which sets the cache entry without performing the operation." + }, + { + "comment": "Update the `CredentialCache` options object to add support for custom cache file paths. This is useful if `CredentialCache` is used outside of Rush." + }, + { + "comment": "PNPMv10 support: SHA256 hashing for dependencies paths lookup" + }, + { + "comment": "Add Linux/MacOS support for new 'virtual-store-dir-max-length'" + } + ] + } + }, { "version": "5.153.2", "tag": "@microsoft/rush_v5.153.2", diff --git a/apps/rush/CHANGELOG.md b/apps/rush/CHANGELOG.md index 645ca3fbec3..ee386849f68 100644 --- a/apps/rush/CHANGELOG.md +++ b/apps/rush/CHANGELOG.md @@ -1,6 +1,23 @@ # Change Log - @microsoft/rush -This log was last generated on Tue, 13 May 2025 20:33:12 GMT and should not be manually modified. +This log was last generated on Fri, 13 Jun 2025 16:10:38 GMT and should not be manually modified. + +## 5.155.0 +Fri, 13 Jun 2025 16:10:38 GMT + +### Updates + +- Add support for PNPM v9 to the pnpm-sync feature. + +## 5.154.0 +Tue, 10 Jun 2025 18:45:59 GMT + +### Updates + +- Introduce a `@rushstack/rush-bridge-cache-plugin` package that adds a `--set-cache-only` flag to phased commands, which sets the cache entry without performing the operation. +- Update the `CredentialCache` options object to add support for custom cache file paths. This is useful if `CredentialCache` is used outside of Rush. +- PNPMv10 support: SHA256 hashing for dependencies paths lookup +- Add Linux/MacOS support for new 'virtual-store-dir-max-length' ## 5.153.2 Tue, 13 May 2025 20:33:12 GMT diff --git a/apps/rush/package.json b/apps/rush/package.json index 02453a6d234..4bbfd0ad8a8 100644 --- a/apps/rush/package.json +++ b/apps/rush/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/rush", - "version": "5.153.2", + "version": "5.155.0", "description": "A professional solution for consolidating all your JavaScript projects in one Git repo", "keywords": [ "install", diff --git a/apps/rush/src/start-dev-docs.ts b/apps/rush/src/start-dev-docs.ts index be5089d5130..4bd9539a8bb 100644 --- a/apps/rush/src/start-dev-docs.ts +++ b/apps/rush/src/start-dev-docs.ts @@ -6,4 +6,4 @@ import { Colorize, ConsoleTerminalProvider, Terminal } from '@rushstack/terminal const terminal: Terminal = new Terminal(new ConsoleTerminalProvider()); terminal.writeLine('For instructions on debugging Rush, please see this documentation:'); -terminal.writeLine(Colorize.bold('https://rushjs.io/pages/contributing/debugging/')); +terminal.writeLine(Colorize.bold('https://rushjs.io/pages/contributing/#debugging-rush')); diff --git a/build-tests/api-extractor-test-05/dist/tsdoc-metadata.json b/build-tests/api-extractor-test-05/dist/tsdoc-metadata.json index 5c31157a604..89625ac60e4 100644 --- a/build-tests/api-extractor-test-05/dist/tsdoc-metadata.json +++ b/build-tests/api-extractor-test-05/dist/tsdoc-metadata.json @@ -5,7 +5,7 @@ "toolPackages": [ { "packageName": "@microsoft/api-extractor", - "packageVersion": "7.52.4" + "packageVersion": "7.52.8" } ] } diff --git a/build-tests/rush-mcp-example-plugin/.eslintrc.js b/build-tests/rush-mcp-example-plugin/.eslintrc.js new file mode 100644 index 00000000000..de794c04ae0 --- /dev/null +++ b/build-tests/rush-mcp-example-plugin/.eslintrc.js @@ -0,0 +1,13 @@ +// This is a workaround for https://github.com/eslint/eslint/issues/3458 +require('local-eslint-config/patch/modern-module-resolution'); +// This is a workaround for https://github.com/microsoft/rushstack/issues/3021 +require('local-eslint-config/patch/custom-config-package-names'); + +module.exports = { + extends: [ + 'local-eslint-config/profile/node', + 'local-eslint-config/mixins/friendly-locals', + 'local-eslint-config/mixins/tsdoc' + ], + parserOptions: { tsconfigRootDir: __dirname } +}; diff --git a/build-tests/rush-mcp-example-plugin/.npmignore b/build-tests/rush-mcp-example-plugin/.npmignore new file mode 100644 index 00000000000..dc4a664618b --- /dev/null +++ b/build-tests/rush-mcp-example-plugin/.npmignore @@ -0,0 +1,35 @@ +# THIS IS A STANDARD TEMPLATE FOR .npmignore FILES IN THIS REPO. + +# Ignore all files by default, to avoid accidentally publishing unintended files. +* + +# Use negative patterns to bring back the specific things we want to publish. +!/bin/** +!/lib/** +!/lib-*/** +!/dist/** + +!CHANGELOG.md +!CHANGELOG.json +!heft-plugin.json +!rush-plugin-manifest.json +!ThirdPartyNotice.txt + +# Ignore certain patterns that should not get published. +/dist/*.stats.* +/lib/**/test/ +/lib-*/**/test/ +*.test.js + +# NOTE: These don't need to be specified, because NPM includes them automatically. +# +# package.json +# README.md +# LICENSE + +# --------------------------------------------------------------------------- +# DO NOT MODIFY ABOVE THIS LINE! Add any project-specific overrides below. +# --------------------------------------------------------------------------- + +!rush-mcp-plugin.json +!*.schema.json diff --git a/build-tests/rush-mcp-example-plugin/LICENSE b/build-tests/rush-mcp-example-plugin/LICENSE new file mode 100644 index 00000000000..5ad10fc49f8 --- /dev/null +++ b/build-tests/rush-mcp-example-plugin/LICENSE @@ -0,0 +1,24 @@ +rush-mcp-example-plugin + +Copyright (c) Microsoft Corporation. All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/build-tests/rush-mcp-example-plugin/README.md b/build-tests/rush-mcp-example-plugin/README.md new file mode 100644 index 00000000000..8ca3190b2fc --- /dev/null +++ b/build-tests/rush-mcp-example-plugin/README.md @@ -0,0 +1,3 @@ +# rush-mcp-example-plugin + +This example project shows how to create a plugin for `@rushstack/mcp-server` diff --git a/build-tests/rush-mcp-example-plugin/config/rig.json b/build-tests/rush-mcp-example-plugin/config/rig.json new file mode 100644 index 00000000000..165ffb001f5 --- /dev/null +++ b/build-tests/rush-mcp-example-plugin/config/rig.json @@ -0,0 +1,7 @@ +{ + // The "rig.json" file directs tools to look for their config files in an external package. + // Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + + "rigPackageName": "local-node-rig" +} diff --git a/build-tests/rush-mcp-example-plugin/package.json b/build-tests/rush-mcp-example-plugin/package.json new file mode 100644 index 00000000000..2f16f0b0044 --- /dev/null +++ b/build-tests/rush-mcp-example-plugin/package.json @@ -0,0 +1,18 @@ +{ + "name": "rush-mcp-example-plugin", + "version": "0.0.0", + "private": true, + "description": "Example showing how to create a plugin for @rushstack/mcp-server", + "license": "MIT", + "scripts": { + "build": "heft build --clean", + "_phase:build": "heft run --only build -- --clean" + }, + "dependencies": {}, + "devDependencies": { + "@rushstack/heft": "workspace:*", + "@rushstack/mcp-server": "workspace:*", + "local-node-rig": "workspace:*", + "local-eslint-config": "workspace:*" + } +} diff --git a/build-tests/rush-mcp-example-plugin/rush-mcp-plugin.json b/build-tests/rush-mcp-example-plugin/rush-mcp-plugin.json new file mode 100644 index 00000000000..48ff8a67fb9 --- /dev/null +++ b/build-tests/rush-mcp-example-plugin/rush-mcp-plugin.json @@ -0,0 +1,24 @@ +/** + * Every plugin package must contain a "rush-mcp-plugin.json" manifest in the top-level folder + * (next to package.json). + */ +{ + /** + * A name that uniquely identifies your plugin. Generally this should be the same name as + * the NPM package. If two NPM packages have the same pluginName, they cannot be loaded together. + */ + "pluginName": "rush-mcp-example-plugin", + + /** + * (OPTIONAL) Indicates that your plugin accepts a config file. The MCP server will load this + * file and provide it to the plugin. + * + * The config file path will be `/common/config/rush-mcp/.json`. + */ + "configFileSchema": "./lib/rush-mcp-example-plugin.schema.json", + + /** + * The entry point, whose default export should be a class that implements + */ + "entryPoint": "./lib/index.js" +} diff --git a/build-tests/rush-mcp-example-plugin/src/ExamplePlugin.ts b/build-tests/rush-mcp-example-plugin/src/ExamplePlugin.ts new file mode 100644 index 00000000000..d706e8c9a52 --- /dev/null +++ b/build-tests/rush-mcp-example-plugin/src/ExamplePlugin.ts @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import type { IRushMcpPlugin, RushMcpPluginSession } from '@rushstack/mcp-server'; +import { StateCapitalTool } from './StateCapitalTool'; + +export interface IExamplePluginConfigFile { + capitalsByState: Record; +} + +export class ExamplePlugin implements IRushMcpPlugin { + public session: RushMcpPluginSession; + public configFile: IExamplePluginConfigFile | undefined = undefined; + + public constructor(session: RushMcpPluginSession, configFile: IExamplePluginConfigFile | undefined) { + this.session = session; + this.configFile = configFile; + } + + public async onInitializeAsync(): Promise { + this.session.registerTool({ toolName: 'state_capital' }, new StateCapitalTool(this)); + } +} diff --git a/build-tests/rush-mcp-example-plugin/src/StateCapitalTool.ts b/build-tests/rush-mcp-example-plugin/src/StateCapitalTool.ts new file mode 100644 index 00000000000..f6ad8ce6812 --- /dev/null +++ b/build-tests/rush-mcp-example-plugin/src/StateCapitalTool.ts @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import type { IRushMcpTool, RushMcpPluginSession, CallToolResult, zodModule } from '@rushstack/mcp-server'; + +import type { ExamplePlugin } from './ExamplePlugin'; + +export class StateCapitalTool implements IRushMcpTool { + public readonly plugin: ExamplePlugin; + public readonly session: RushMcpPluginSession; + + public constructor(plugin: ExamplePlugin) { + this.plugin = plugin; + this.session = plugin.session; + } + + // ZOD relies on type inference generate a messy expression in the .d.ts file + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + public get schema() { + const zod: typeof zodModule = this.session.zod; + + return zod.object({ + state: zod.string().describe('The name of the state, in all lowercase') + }); + } + + public async executeAsync(input: zodModule.infer): Promise { + const capital: string | undefined = this.plugin.configFile?.capitalsByState[input.state]; + + return { + content: [ + { + type: 'text', + text: capital + ? `The capital of "${input.state}" is "${capital}"` + : `Unable to determine the answer from the data set.` + } + ] + }; + } +} diff --git a/build-tests/rush-mcp-example-plugin/src/index.ts b/build-tests/rush-mcp-example-plugin/src/index.ts new file mode 100644 index 00000000000..8866a23e566 --- /dev/null +++ b/build-tests/rush-mcp-example-plugin/src/index.ts @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import type { RushMcpPluginSession, RushMcpPluginFactory } from '@rushstack/mcp-server'; +import { ExamplePlugin, type IExamplePluginConfigFile } from './ExamplePlugin'; + +function createPlugin( + session: RushMcpPluginSession, + configFile: IExamplePluginConfigFile | undefined +): ExamplePlugin { + return new ExamplePlugin(session, configFile); +} + +export default createPlugin satisfies RushMcpPluginFactory; diff --git a/build-tests/rush-mcp-example-plugin/src/rush-mcp-example-plugin.schema.json b/build-tests/rush-mcp-example-plugin/src/rush-mcp-example-plugin.schema.json new file mode 100644 index 00000000000..55da2c56a0e --- /dev/null +++ b/build-tests/rush-mcp-example-plugin/src/rush-mcp-example-plugin.schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "State Capital Map", + "type": "object", + "required": ["capitalsByState"], + "properties": { + "$schema": { + "type": "string" + }, + "capitalsByState": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "A mapping of US state names (lowercase) to their capital cities." + } + }, + "additionalProperties": false +} diff --git a/build-tests/rush-mcp-example-plugin/tsconfig.json b/build-tests/rush-mcp-example-plugin/tsconfig.json new file mode 100644 index 00000000000..dac21d04081 --- /dev/null +++ b/build-tests/rush-mcp-example-plugin/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./node_modules/local-node-rig/profiles/default/tsconfig-base.json" +} diff --git a/common/config/rush/nonbrowser-approved-packages.json b/common/config/rush/nonbrowser-approved-packages.json index 5127417f3a6..5ecc485db43 100644 --- a/common/config/rush/nonbrowser-approved-packages.json +++ b/common/config/rush/nonbrowser-approved-packages.json @@ -94,6 +94,10 @@ "name": "@pnpm/dependency-path", "allowedCategories": [ "libraries" ] }, + { + "name": "@pnpm/dependency-path-lockfile-pre-v10", + "allowedCategories": [ "libraries" ] + }, { "name": "@pnpm/link-bins", "allowedCategories": [ "libraries" ] @@ -226,6 +230,10 @@ "name": "@rushstack/lookup-by-path", "allowedCategories": [ "libraries" ] }, + { + "name": "@rushstack/mcp-server", + "allowedCategories": [ "libraries", "tests" ] + }, { "name": "@rushstack/module-minifier", "allowedCategories": [ "libraries", "tests" ] @@ -262,6 +270,10 @@ "name": "@rushstack/rush-azure-storage-build-cache-plugin", "allowedCategories": [ "libraries" ] }, + { + "name": "@rushstack/rush-bridge-cache-plugin", + "allowedCategories": [ "libraries" ] + }, { "name": "@rushstack/rush-http-build-cache-plugin", "allowedCategories": [ "libraries" ] diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json index 7a23b0e1302..8a0f2476bf2 100644 --- a/common/config/rush/version-policies.json +++ b/common/config/rush/version-policies.json @@ -102,8 +102,8 @@ { "policyName": "rush", "definitionName": "lockStepVersion", - "version": "5.153.2", - "nextBump": "patch", + "version": "5.155.0", + "nextBump": "minor", "mainProject": "@microsoft/rush" } ] diff --git a/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml b/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml index 52e24abbcc7..5f2928206eb 100644 --- a/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml +++ b/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml @@ -114,10 +114,10 @@ importers: version: file:../../../apps/heft(@types/node@20.17.19) '@rushstack/heft-lint-plugin': specifier: file:../../heft-plugins/heft-lint-plugin - version: file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.73.2)(@types/node@20.17.19) + version: file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.73.6)(@types/node@20.17.19) '@rushstack/heft-typescript-plugin': specifier: file:../../heft-plugins/heft-typescript-plugin - version: file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.73.2)(@types/node@20.17.19) + version: file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.73.6)(@types/node@20.17.19) eslint: specifier: ~8.57.0 version: 8.57.1 @@ -877,10 +877,30 @@ packages: '@pnpm/crypto.polyfill': 1.0.0 rfc4648: 1.5.3 + /@pnpm/crypto.hash@1000.1.1: + resolution: {integrity: sha512-lb5kwXaOXdIW/4bkLLmtM9HEVRvp2eIvp+TrdawcPoaptgA/5f0/sRG0P52BF8dFqeNDj+1tGdqH89WQEqJnxA==} + engines: {node: '>=18.12'} + dependencies: + '@pnpm/crypto.polyfill': 1000.1.0 + '@pnpm/graceful-fs': 1000.0.0 + ssri: 10.0.5 + /@pnpm/crypto.polyfill@1.0.0: resolution: {integrity: sha512-WbmsqqcUXKKaAF77ox1TQbpZiaQcr26myuMUu+WjUtoWYgD3VP6iKYEvSx35SZ6G2L316lu+pv+40A2GbWJc1w==} engines: {node: '>=18.12'} + /@pnpm/crypto.polyfill@1000.1.0: + resolution: {integrity: sha512-tNe7a6U4rCpxLMBaR0SIYTdjxGdL0Vwb3G1zY8++sPtHSvy7qd54u8CIB0Z+Y6t5tc9pNYMYCMwhE/wdSY7ltg==} + engines: {node: '>=18.12'} + + /@pnpm/dependency-path@1000.0.9: + resolution: {integrity: sha512-0AhabApfiq3EEYeed5HKQEU3ftkrfyKTNgkMH9esGdp2yc+62Zu7eWFf8WW6IGyitDQPLWGYjSEWDC9Bvv8nPg==} + engines: {node: '>=18.12'} + dependencies: + '@pnpm/crypto.hash': 1000.1.1 + '@pnpm/types': 1000.6.0 + semver: 7.7.2 + /@pnpm/dependency-path@2.1.8: resolution: {integrity: sha512-ywBaTjy0iSEF7lH3DlF8UXrdL2bw4AQFV2tTOeNeY7wc1W5CE+RHSJhf9MXBYcZPesqGRrPiU7Pimj3l05L9VA==} engines: {node: '>=16.14'} @@ -888,7 +908,7 @@ packages: '@pnpm/crypto.base32-hash': 2.0.0 '@pnpm/types': 9.4.2 encode-registry: 3.0.1 - semver: 7.6.3 + semver: 7.7.2 /@pnpm/dependency-path@5.1.7: resolution: {integrity: sha512-MKCyaTy1r9fhBXAnhDZNBVgo6ThPnicwJEG203FDp7pGhD7NruS/FhBI+uMd7GNsK3D7aIFCDAgbWpNTXn/eWw==} @@ -896,12 +916,18 @@ packages: dependencies: '@pnpm/crypto.base32-hash': 3.0.1 '@pnpm/types': 12.2.0 - semver: 7.6.3 + semver: 7.7.2 /@pnpm/error@1.4.0: resolution: {integrity: sha512-vxkRrkneBPVmP23kyjnYwVOtipwlSl6UfL+h+Xa3TrABJTz5rYBXemlTsU5BzST8U4pD7YDkTb3SQu+MMuIDKA==} engines: {node: '>=10.16'} + /@pnpm/graceful-fs@1000.0.0: + resolution: {integrity: sha512-RvMEliAmcfd/4UoaYQ93DLQcFeqit78jhYmeJJVPxqFGmj0jEcb9Tu0eAOXr7tGP3eJHpgvPbTU4o6pZ1bJhxg==} + engines: {node: '>=18.12'} + dependencies: + graceful-fs: 4.2.11 + /@pnpm/link-bins@5.3.25: resolution: {integrity: sha512-9Xq8lLNRHFDqvYPXPgaiKkZ4rtdsm7izwM/cUsFDc5IMnG0QYIVBXQbgwhz2UvjUotbJrvfKLJaCfA3NGBnLDg==} engines: {node: '>=10.16'} @@ -973,6 +999,10 @@ packages: sort-keys: 4.2.0 strip-bom: 4.0.0 + /@pnpm/types@1000.6.0: + resolution: {integrity: sha512-6PsMNe98VKPGcg6LnXSW/LE3YfJ77nj+bPKiRjYRWAQLZ+xXjEQRaR0dAuyjCmchlv4wR/hpnMVRS21/fCod5w==} + engines: {node: '>=18.12'} + /@pnpm/types@12.2.0: resolution: {integrity: sha512-5RtwWhX39j89/Tmyv2QSlpiNjErA357T/8r1Dkg+2lD3P7RuS7Xi2tChvmOC3VlezEFNcWnEGCOeKoGRkDuqFA==} engines: {node: '>=18.12'} @@ -4321,7 +4351,7 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} dependencies: - semver: 7.6.3 + semver: 7.7.2 dev: true /makeerror@1.0.12: @@ -4453,6 +4483,10 @@ packages: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} + /minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + /minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} @@ -4535,7 +4569,7 @@ packages: dependencies: hosted-git-info: 4.1.0 is-core-module: 2.15.1 - semver: 7.6.3 + semver: 7.7.2 validate-npm-package-license: 3.0.4 /normalize-path@3.0.0: @@ -4866,10 +4900,11 @@ packages: dependencies: semver-compare: 1.0.0 - /pnpm-sync-lib@0.3.0: - resolution: {integrity: sha512-Wt3Xf8pjzC2xcyN6ol5x5PdD5kU75+8OOgll2ZQsgm5uxih6dxziqRRuhNwtw94GHRd/0Oo7ESFmzmRz6OTQ0Q==} + /pnpm-sync-lib@0.3.2: + resolution: {integrity: sha512-XlHyNAHlBqIMGTBD0HfgyRyj1UpSJvVyP20ihPek00YKmMb7RJ16AxlQkjT1jQ/D6s6OAT0ety/tSxcJTrvQ4w==} dependencies: - '@pnpm/dependency-path': 2.1.8 + '@pnpm/dependency-path-2': /@pnpm/dependency-path@2.1.8 + '@pnpm/dependency-path-5': /@pnpm/dependency-path@5.1.7 yaml: 2.4.1 /possible-typed-array-names@1.0.0: @@ -5254,6 +5289,11 @@ packages: engines: {node: '>=10'} hasBin: true + /semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + /set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -5350,6 +5390,12 @@ packages: /sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + /ssri@10.0.5: + resolution: {integrity: sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + minipass: 7.1.2 + /ssri@8.0.1: resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==} engines: {node: '>= 8'} @@ -6238,7 +6284,7 @@ packages: - typescript dev: true - file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@0.73.2)(@types/node@20.17.19): + file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@0.73.6)(@types/node@20.17.19): resolution: {directory: ../../../heft-plugins/heft-api-extractor-plugin, type: directory} id: file:../../../heft-plugins/heft-api-extractor-plugin name: '@rushstack/heft-api-extractor-plugin' @@ -6252,7 +6298,7 @@ packages: - '@types/node' dev: true - file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@0.73.2)(@types/node@20.17.19)(jest-environment-node@29.5.0): + file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@0.73.6)(@types/node@20.17.19)(jest-environment-node@29.5.0): resolution: {directory: ../../../heft-plugins/heft-jest-plugin, type: directory} id: file:../../../heft-plugins/heft-jest-plugin name: '@rushstack/heft-jest-plugin' @@ -6287,7 +6333,7 @@ packages: - ts-node dev: true - file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.73.2)(@types/node@20.17.19): + file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.73.6)(@types/node@20.17.19): resolution: {directory: ../../../heft-plugins/heft-lint-plugin, type: directory} id: file:../../../heft-plugins/heft-lint-plugin name: '@rushstack/heft-lint-plugin' @@ -6297,11 +6343,12 @@ packages: '@rushstack/heft': file:../../../apps/heft(@types/node@20.17.19) '@rushstack/node-core-library': file:../../../libraries/node-core-library(@types/node@20.17.19) semver: 7.5.4 + typescript: 5.8.2 transitivePeerDependencies: - '@types/node' dev: true - file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.73.2)(@types/node@20.17.19): + file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.73.6)(@types/node@20.17.19): resolution: {directory: ../../../heft-plugins/heft-typescript-plugin, type: directory} id: file:../../../heft-plugins/heft-typescript-plugin name: '@rushstack/heft-typescript-plugin' @@ -6430,7 +6477,8 @@ packages: name: '@microsoft/rush-lib' engines: {node: '>=5.6.0'} dependencies: - '@pnpm/dependency-path': 5.1.7 + '@pnpm/dependency-path': 1000.0.9 + '@pnpm/dependency-path-lockfile-pre-v10': /@pnpm/dependency-path@5.1.7 '@pnpm/dependency-path-lockfile-pre-v9': /@pnpm/dependency-path@2.1.8 '@pnpm/link-bins': 5.3.25 '@rushstack/heft-config-file': file:../../../libraries/heft-config-file(@types/node@20.17.19) @@ -6456,7 +6504,7 @@ packages: js-yaml: 3.13.1 npm-check: 6.0.1 npm-package-arg: 6.1.1 - pnpm-sync-lib: 0.3.0 + pnpm-sync-lib: 0.3.2 read-package-tree: 5.1.6 rxjs: 6.6.7 semver: 7.5.4 @@ -6526,7 +6574,7 @@ packages: transitivePeerDependencies: - '@types/node' - file:../../../rigs/heft-node-rig(@rushstack/heft@0.73.2)(@types/node@20.17.19): + file:../../../rigs/heft-node-rig(@rushstack/heft@0.73.6)(@types/node@20.17.19): resolution: {directory: ../../../rigs/heft-node-rig, type: directory} id: file:../../../rigs/heft-node-rig name: '@rushstack/heft-node-rig' @@ -6536,10 +6584,10 @@ packages: '@microsoft/api-extractor': file:../../../apps/api-extractor(@types/node@20.17.19) '@rushstack/eslint-config': file:../../../eslint/eslint-config(eslint@8.57.1)(typescript@5.8.2) '@rushstack/heft': file:../../../apps/heft(@types/node@20.17.19) - '@rushstack/heft-api-extractor-plugin': file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@0.73.2)(@types/node@20.17.19) - '@rushstack/heft-jest-plugin': file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@0.73.2)(@types/node@20.17.19)(jest-environment-node@29.5.0) - '@rushstack/heft-lint-plugin': file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.73.2)(@types/node@20.17.19) - '@rushstack/heft-typescript-plugin': file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.73.2)(@types/node@20.17.19) + '@rushstack/heft-api-extractor-plugin': file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@0.73.6)(@types/node@20.17.19) + '@rushstack/heft-jest-plugin': file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@0.73.6)(@types/node@20.17.19)(jest-environment-node@29.5.0) + '@rushstack/heft-lint-plugin': file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.73.6)(@types/node@20.17.19) + '@rushstack/heft-typescript-plugin': file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.73.6)(@types/node@20.17.19) '@types/heft-jest': 1.0.1 eslint: 8.57.1 jest-environment-node: 29.5.0 @@ -6559,7 +6607,7 @@ packages: dependencies: '@microsoft/api-extractor': file:../../../apps/api-extractor(@types/node@20.17.19) '@rushstack/heft': file:../../../apps/heft(@types/node@20.17.19) - '@rushstack/heft-node-rig': file:../../../rigs/heft-node-rig(@rushstack/heft@0.73.2)(@types/node@20.17.19) + '@rushstack/heft-node-rig': file:../../../rigs/heft-node-rig(@rushstack/heft@0.73.6)(@types/node@20.17.19) '@types/heft-jest': 1.0.1 '@types/node': 20.17.19 eslint: 8.57.1 diff --git a/common/config/subspaces/build-tests-subspace/repo-state.json b/common/config/subspaces/build-tests-subspace/repo-state.json index 72bbe58dbe0..6bda9170b8e 100644 --- a/common/config/subspaces/build-tests-subspace/repo-state.json +++ b/common/config/subspaces/build-tests-subspace/repo-state.json @@ -1,6 +1,6 @@ // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. { - "pnpmShrinkwrapHash": "e47112c7d099f189f37770e10351e406cf1ec451", + "pnpmShrinkwrapHash": "40cbf48a53cad8d9f815d7d1d8ec18a48233a1ad", "preferredVersionsHash": "54149ea3f01558a859c96dee2052b797d4defe68", - "packageJsonInjectedDependenciesHash": "8aab06634f5544193a51484804f7d7c4fc2ad986" + "packageJsonInjectedDependenciesHash": "8bc9d0aabfed5614af0cf7950d2ba377e412b716" } diff --git a/common/config/subspaces/default/pnpm-lock.yaml b/common/config/subspaces/default/pnpm-lock.yaml index ecc2d923870..e2a3f47dcf2 100644 --- a/common/config/subspaces/default/pnpm-lock.yaml +++ b/common/config/subspaces/default/pnpm-lock.yaml @@ -2249,6 +2249,21 @@ importers: specifier: workspace:* version: link:../../rigs/local-node-rig + ../../../build-tests/rush-mcp-example-plugin: + devDependencies: + '@rushstack/heft': + specifier: workspace:* + version: link:../../apps/heft + '@rushstack/mcp-server': + specifier: workspace:* + version: link:../../apps/rush-mcp-server + local-eslint-config: + specifier: workspace:* + version: link:../../eslint/local-eslint-config + local-node-rig: + specifier: workspace:* + version: link:../../rigs/local-node-rig + ../../../build-tests/rush-project-change-analyzer-test: dependencies: '@microsoft/rush-lib': @@ -3280,8 +3295,11 @@ importers: ../../../libraries/rush-lib: dependencies: '@pnpm/dependency-path': - specifier: ~5.1.7 - version: 5.1.7 + specifier: ~1000.0.9 + version: 1000.0.9 + '@pnpm/dependency-path-lockfile-pre-v10': + specifier: npm:@pnpm/dependency-path@~5.1.7 + version: /@pnpm/dependency-path@5.1.7 '@pnpm/dependency-path-lockfile-pre-v9': specifier: npm:@pnpm/dependency-path@~2.1.2 version: /@pnpm/dependency-path@2.1.8 @@ -3358,8 +3376,8 @@ importers: specifier: ~6.1.0 version: 6.1.1 pnpm-sync-lib: - specifier: 0.3.0 - version: 0.3.0 + specifier: 0.3.2 + version: 0.3.2 read-package-tree: specifier: ~5.1.5 version: 5.1.6 @@ -4014,6 +4032,28 @@ importers: specifier: workspace:* version: link:../../rigs/local-node-rig + ../../../rush-plugins/rush-bridge-cache-plugin: + dependencies: + '@rushstack/node-core-library': + specifier: workspace:* + version: link:../../libraries/node-core-library + '@rushstack/rush-sdk': + specifier: workspace:* + version: link:../../libraries/rush-sdk + '@rushstack/terminal': + specifier: workspace:* + version: link:../../libraries/terminal + '@rushstack/ts-command-line': + specifier: workspace:* + version: link:../../libraries/ts-command-line + devDependencies: + '@rushstack/heft': + specifier: workspace:* + version: link:../../apps/heft + local-node-rig: + specifier: workspace:* + version: link:../../rigs/local-node-rig + ../../../rush-plugins/rush-buildxl-graph-plugin: dependencies: '@rushstack/node-core-library': @@ -4083,6 +4123,25 @@ importers: specifier: workspace:* version: link:../../rigs/local-node-rig + ../../../rush-plugins/rush-mcp-docs-plugin: + dependencies: + '@rushstack/mcp-server': + specifier: workspace:* + version: link:../../apps/rush-mcp-server + '@rushstack/node-core-library': + specifier: workspace:* + version: link:../../libraries/node-core-library + devDependencies: + '@rushstack/heft': + specifier: workspace:* + version: link:../../apps/heft + local-eslint-config: + specifier: workspace:* + version: link:../../eslint/local-eslint-config + local-node-rig: + specifier: workspace:* + version: link:../../rigs/local-node-rig + ../../../rush-plugins/rush-redis-cobuild-plugin: dependencies: '@redis/client': @@ -9772,11 +9831,34 @@ packages: rfc4648: 1.5.3 dev: false + /@pnpm/crypto.hash@1000.1.1: + resolution: {integrity: sha512-lb5kwXaOXdIW/4bkLLmtM9HEVRvp2eIvp+TrdawcPoaptgA/5f0/sRG0P52BF8dFqeNDj+1tGdqH89WQEqJnxA==} + engines: {node: '>=18.12'} + dependencies: + '@pnpm/crypto.polyfill': 1000.1.0 + '@pnpm/graceful-fs': 1000.0.0 + ssri: 10.0.5 + dev: false + /@pnpm/crypto.polyfill@1.0.0: resolution: {integrity: sha512-WbmsqqcUXKKaAF77ox1TQbpZiaQcr26myuMUu+WjUtoWYgD3VP6iKYEvSx35SZ6G2L316lu+pv+40A2GbWJc1w==} engines: {node: '>=18.12'} dev: false + /@pnpm/crypto.polyfill@1000.1.0: + resolution: {integrity: sha512-tNe7a6U4rCpxLMBaR0SIYTdjxGdL0Vwb3G1zY8++sPtHSvy7qd54u8CIB0Z+Y6t5tc9pNYMYCMwhE/wdSY7ltg==} + engines: {node: '>=18.12'} + dev: false + + /@pnpm/dependency-path@1000.0.9: + resolution: {integrity: sha512-0AhabApfiq3EEYeed5HKQEU3ftkrfyKTNgkMH9esGdp2yc+62Zu7eWFf8WW6IGyitDQPLWGYjSEWDC9Bvv8nPg==} + engines: {node: '>=18.12'} + dependencies: + '@pnpm/crypto.hash': 1000.1.1 + '@pnpm/types': 1000.6.0 + semver: 7.7.2 + dev: false + /@pnpm/dependency-path@2.1.8: resolution: {integrity: sha512-ywBaTjy0iSEF7lH3DlF8UXrdL2bw4AQFV2tTOeNeY7wc1W5CE+RHSJhf9MXBYcZPesqGRrPiU7Pimj3l05L9VA==} engines: {node: '>=16.14'} @@ -9801,6 +9883,13 @@ packages: engines: {node: '>=10.16'} dev: false + /@pnpm/graceful-fs@1000.0.0: + resolution: {integrity: sha512-RvMEliAmcfd/4UoaYQ93DLQcFeqit78jhYmeJJVPxqFGmj0jEcb9Tu0eAOXr7tGP3eJHpgvPbTU4o6pZ1bJhxg==} + engines: {node: '>=18.12'} + dependencies: + graceful-fs: 4.2.11 + dev: false + /@pnpm/link-bins@5.3.25: resolution: {integrity: sha512-9Xq8lLNRHFDqvYPXPgaiKkZ4rtdsm7izwM/cUsFDc5IMnG0QYIVBXQbgwhz2UvjUotbJrvfKLJaCfA3NGBnLDg==} engines: {node: '>=10.16'} @@ -9890,6 +9979,11 @@ packages: strip-bom: 4.0.0 dev: false + /@pnpm/types@1000.6.0: + resolution: {integrity: sha512-6PsMNe98VKPGcg6LnXSW/LE3YfJ77nj+bPKiRjYRWAQLZ+xXjEQRaR0dAuyjCmchlv4wR/hpnMVRS21/fCod5w==} + engines: {node: '>=18.12'} + dev: false + /@pnpm/types@12.2.0: resolution: {integrity: sha512-5RtwWhX39j89/Tmyv2QSlpiNjErA357T/8r1Dkg+2lD3P7RuS7Xi2tChvmOC3VlezEFNcWnEGCOeKoGRkDuqFA==} engines: {node: '>=18.12'} @@ -23068,6 +23162,11 @@ packages: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} + /minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + dev: false + /minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} @@ -24125,10 +24224,11 @@ packages: - typescript dev: true - /pnpm-sync-lib@0.3.0: - resolution: {integrity: sha512-Wt3Xf8pjzC2xcyN6ol5x5PdD5kU75+8OOgll2ZQsgm5uxih6dxziqRRuhNwtw94GHRd/0Oo7ESFmzmRz6OTQ0Q==} + /pnpm-sync-lib@0.3.2: + resolution: {integrity: sha512-XlHyNAHlBqIMGTBD0HfgyRyj1UpSJvVyP20ihPek00YKmMb7RJ16AxlQkjT1jQ/D6s6OAT0ety/tSxcJTrvQ4w==} dependencies: - '@pnpm/dependency-path': 2.1.8 + '@pnpm/dependency-path-2': /@pnpm/dependency-path@2.1.8 + '@pnpm/dependency-path-5': /@pnpm/dependency-path@5.1.7 yaml: 2.4.1 dev: false @@ -26242,6 +26342,12 @@ packages: engines: {node: '>=10'} hasBin: true + /semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + dev: false + /send@0.17.2: resolution: {integrity: sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==} engines: {node: '>= 0.8.0'} @@ -26802,6 +26908,13 @@ packages: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} dev: true + /ssri@10.0.5: + resolution: {integrity: sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + minipass: 7.1.2 + dev: false + /ssri@6.0.2: resolution: {integrity: sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==} dependencies: diff --git a/common/config/subspaces/default/repo-state.json b/common/config/subspaces/default/repo-state.json index bf53d6e721b..d3f09b6dec3 100644 --- a/common/config/subspaces/default/repo-state.json +++ b/common/config/subspaces/default/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": "cffd2b8ab4cceebd7d5a02823e015b7eaabe89da", + "pnpmShrinkwrapHash": "d132a98928f381f6aae3c1da00a3939f2d38dafe", "preferredVersionsHash": "54149ea3f01558a859c96dee2052b797d4defe68" } diff --git a/common/reviews/api/mcp-server.api.md b/common/reviews/api/mcp-server.api.md new file mode 100644 index 00000000000..5903120d31a --- /dev/null +++ b/common/reviews/api/mcp-server.api.md @@ -0,0 +1,50 @@ +## API Report File for "@rushstack/mcp-server" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types'; +import type * as zodModule from 'zod'; + +// @public (undocumented) +export type CallToolResult = zodModule.infer; + +export { CallToolResultSchema } + +// @public +export interface IRegisterToolOptions { + // (undocumented) + description?: string; + // (undocumented) + toolName: string; +} + +// @public +export interface IRushMcpPlugin { + // (undocumented) + onInitializeAsync(): Promise; +} + +// @public +export interface IRushMcpTool = zodModule.ZodObject> { + // (undocumented) + executeAsync(input: zodModule.infer): Promise; + // (undocumented) + readonly schema: TSchema; +} + +// @public +export type RushMcpPluginFactory = (session: RushMcpPluginSession, configFile: TConfigFile | undefined) => IRushMcpPlugin; + +// @public +export abstract class RushMcpPluginSession { + // (undocumented) + abstract registerTool(options: IRegisterToolOptions, tool: IRushMcpTool): void; + // (undocumented) + readonly zod: typeof zodModule; +} + +export { zodModule } + +``` diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index 76e081722d1..9e95089280e 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -437,6 +437,7 @@ export interface ICredentialCacheEntry { // @beta (undocumented) export interface ICredentialCacheOptions { + cacheFilePath?: string; // (undocumented) supportEditing: boolean; } @@ -583,6 +584,12 @@ export class IndividualVersionPolicy extends VersionPolicy { export interface _INpmOptionsJson extends IPackageManagerOptionsJsonBase { } +// @internal (undocumented) +export interface _IOperationBuildCacheOptions { + buildCacheConfiguration: BuildCacheConfiguration; + terminal: ITerminal; +} + // @alpha export interface IOperationExecutionResult { readonly error: Error | undefined; @@ -779,6 +786,14 @@ export interface IPnpmPeerDependencyRules { export { IPrefixMatch } +// @internal (undocumented) +export type _IProjectBuildCacheOptions = _IOperationBuildCacheOptions & { + projectOutputFolderNames: ReadonlyArray; + project: RushConfigurationProject; + operationStateHash: string; + phaseName: string; +}; + // @beta export interface IRushCommand { readonly actionName: string; @@ -947,6 +962,20 @@ export class Operation { weight: number; } +// @internal (undocumented) +export class _OperationBuildCache { + // (undocumented) + get cacheId(): string | undefined; + // (undocumented) + static forOperation(executionResult: IOperationExecutionResult, options: _IOperationBuildCacheOptions): _OperationBuildCache; + // (undocumented) + static getOperationBuildCache(options: _IProjectBuildCacheOptions): _OperationBuildCache; + // (undocumented) + tryRestoreFromCacheAsync(terminal: ITerminal, specifiedCacheId?: string): Promise; + // (undocumented) + trySetCacheEntryAsync(terminal: ITerminal, specifiedCacheId?: string): Promise; +} + // @internal export class _OperationMetadataManager { constructor(options: _IOperationMetadataManagerOptions); diff --git a/heft-plugins/heft-lint-plugin/CHANGELOG.json b/heft-plugins/heft-lint-plugin/CHANGELOG.json index c678942957d..f2c6c99a2b7 100644 --- a/heft-plugins/heft-lint-plugin/CHANGELOG.json +++ b/heft-plugins/heft-lint-plugin/CHANGELOG.json @@ -1,6 +1,18 @@ { "name": "@rushstack/heft-lint-plugin", "entries": [ + { + "version": "0.6.0", + "tag": "@rushstack/heft-lint-plugin_v0.6.0", + "date": "Fri, 06 Jun 2025 00:11:09 GMT", + "comments": { + "minor": [ + { + "comment": "Add support for using heft-lint-plugin standalone without a typescript phase" + } + ] + } + }, { "version": "0.5.38", "tag": "@rushstack/heft-lint-plugin_v0.5.38", diff --git a/heft-plugins/heft-lint-plugin/CHANGELOG.md b/heft-plugins/heft-lint-plugin/CHANGELOG.md index eec9c620c5f..862a388514c 100644 --- a/heft-plugins/heft-lint-plugin/CHANGELOG.md +++ b/heft-plugins/heft-lint-plugin/CHANGELOG.md @@ -1,6 +1,13 @@ # Change Log - @rushstack/heft-lint-plugin -This log was last generated on Tue, 13 May 2025 02:09:20 GMT and should not be manually modified. +This log was last generated on Fri, 06 Jun 2025 00:11:09 GMT and should not be manually modified. + +## 0.6.0 +Fri, 06 Jun 2025 00:11:09 GMT + +### Minor changes + +- Add support for using heft-lint-plugin standalone without a typescript phase ## 0.5.38 Tue, 13 May 2025 02:09:20 GMT diff --git a/heft-plugins/heft-lint-plugin/package.json b/heft-plugins/heft-lint-plugin/package.json index b08cfea8052..cfd951ebac1 100644 --- a/heft-plugins/heft-lint-plugin/package.json +++ b/heft-plugins/heft-lint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@rushstack/heft-lint-plugin", - "version": "0.5.38", + "version": "0.6.0", "description": "A Heft plugin for using ESLint or TSLint. Intended for use with @rushstack/heft-typescript-plugin", "repository": { "type": "git", @@ -29,7 +29,7 @@ "@types/semver": "7.5.0", "decoupled-local-node-rig": "workspace:*", "eslint": "~8.57.0", - "tslint": "~5.20.1", - "typescript": "~5.8.2" + "typescript": "~5.8.2", + "tslint": "~5.20.1" } } diff --git a/heft-plugins/heft-lint-plugin/src/LintPlugin.ts b/heft-plugins/heft-lint-plugin/src/LintPlugin.ts index f20dbf30b81..030405d0b95 100644 --- a/heft-plugins/heft-lint-plugin/src/LintPlugin.ts +++ b/heft-plugins/heft-lint-plugin/src/LintPlugin.ts @@ -17,6 +17,8 @@ import type { ITypeScriptPluginAccessor } from '@rushstack/heft-typescript-plugin'; +import type * as TTypescript from 'typescript'; + import type { LinterBase } from './LinterBase'; import { Eslint } from './Eslint'; import { Tslint } from './Tslint'; @@ -42,6 +44,30 @@ interface ILintOptions { changedFiles?: ReadonlySet; } +function checkFix(taskSession: IHeftTaskSession, pluginOptions?: ILintPluginOptions): boolean { + let fix: boolean = + pluginOptions?.alwaysFix || taskSession.parameters.getFlagParameter(FIX_PARAMETER_NAME).value; + if (fix && taskSession.parameters.production) { + // Write this as a standard output message since we don't want to throw errors when running in + // production mode and "alwaysFix" is specified in the plugin options + taskSession.logger.terminal.writeLine( + 'Fix mode has been disabled since Heft is running in production mode' + ); + fix = false; + } + return fix; +} + +function getSarifLogPath( + heftConfiguration: HeftConfiguration, + pluginOptions?: ILintPluginOptions +): string | undefined { + const relativeSarifLogPath: string | undefined = pluginOptions?.sarifLogPath; + const sarifLogPath: string | undefined = + relativeSarifLogPath && path.resolve(heftConfiguration.buildFolderPath, relativeSarifLogPath); + return sarifLogPath; +} + export default class LintPlugin implements IHeftTaskPlugin { private readonly _lintingPromises: Promise[] = []; @@ -57,24 +83,14 @@ export default class LintPlugin implements IHeftTaskPlugin { heftConfiguration: HeftConfiguration, pluginOptions?: ILintPluginOptions ): void { + // To support standalone linting, track if we have hooked to the typescript plugin + let inTypescriptPhase: boolean = false; + // Disable linting in watch mode. Some lint rules require the context of multiple files, which // may not be available in watch mode. if (!taskSession.parameters.watch) { - let fix: boolean = - pluginOptions?.alwaysFix || taskSession.parameters.getFlagParameter(FIX_PARAMETER_NAME).value; - if (fix && taskSession.parameters.production) { - // Write this as a standard output message since we don't want to throw errors when running in - // production mode and "alwaysFix" is specified in the plugin options - taskSession.logger.terminal.writeLine( - 'Fix mode has been disabled since Heft is running in production mode' - ); - fix = false; - } - - const relativeSarifLogPath: string | undefined = pluginOptions?.sarifLogPath; - const sarifLogPath: string | undefined = - relativeSarifLogPath && path.resolve(heftConfiguration.buildFolderPath, relativeSarifLogPath); - + const fix: boolean = checkFix(taskSession, pluginOptions); + const sarifLogPath: string | undefined = getSarifLogPath(heftConfiguration, pluginOptions); // Use the changed files hook to kick off linting asynchronously taskSession.requestAccessToPluginByName( '@rushstack/heft-typescript-plugin', @@ -99,6 +115,8 @@ export default class LintPlugin implements IHeftTaskPlugin { this._lintingPromises.push(lintingPromise); } ); + // Set the flag to indicate that we are in the typescript phase + inTypescriptPhase = true; } ); } @@ -116,11 +134,61 @@ export default class LintPlugin implements IHeftTaskPlugin { // Warn since don't run the linters when in watch mode. taskSession.logger.terminal.writeWarningLine("Linting isn't currently supported in watch mode"); } else { - await Promise.all(this._lintingPromises); + if (!inTypescriptPhase) { + const fix: boolean = checkFix(taskSession, pluginOptions); + const sarifLogPath: string | undefined = getSarifLogPath(heftConfiguration, pluginOptions); + // If we are not in the typescript phase, we need to create a typescript program + // from the tsconfig file + const tsProgram: IExtendedProgram = await this._createTypescriptProgramAsync( + heftConfiguration, + taskSession + ); + const rootFiles: readonly string[] = tsProgram.getRootFileNames(); + const changedFiles: Set = new Set(); + rootFiles.forEach((rootFilePath: string) => { + const sourceFile: TTypescript.SourceFile | undefined = tsProgram.getSourceFile(rootFilePath); + changedFiles.add(sourceFile as IExtendedSourceFile); + }); + + await this._lintAsync({ + taskSession, + heftConfiguration, + fix, + sarifLogPath, + tsProgram, + changedFiles + }); + } else { + await Promise.all(this._lintingPromises); + } } }); } + private async _createTypescriptProgramAsync( + heftConfiguration: HeftConfiguration, + taskSession: IHeftTaskSession + ): Promise { + const typescriptPath: string = await heftConfiguration.rigPackageResolver.resolvePackageAsync( + 'typescript', + taskSession.logger.terminal + ); + const ts: typeof TTypescript = await import(typescriptPath); + // Create a typescript program from the tsconfig file + const tsconfigPath: string = path.resolve(heftConfiguration.buildFolderPath, 'tsconfig.json'); + const parsed: TTypescript.ParsedCommandLine = ts.parseJsonConfigFileContent( + ts.readConfigFile(tsconfigPath, ts.sys.readFile).config, + ts.sys, + path.dirname(tsconfigPath) + ); + const program: IExtendedProgram = ts.createProgram({ + rootNames: parsed.fileNames, + options: parsed.options + }) as IExtendedProgram; + + return program; + } + private async _ensureInitializedAsync( taskSession: IHeftTaskSession, heftConfiguration: HeftConfiguration diff --git a/libraries/rush-lib/package.json b/libraries/rush-lib/package.json index 18a862f4a19..7b461ac27f6 100644 --- a/libraries/rush-lib/package.json +++ b/libraries/rush-lib/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/rush-lib", - "version": "5.153.2", + "version": "5.155.0", "description": "A library for writing scripts that interact with the Rush tool", "repository": { "type": "git", @@ -30,7 +30,8 @@ "license": "MIT", "dependencies": { "@pnpm/dependency-path-lockfile-pre-v9": "npm:@pnpm/dependency-path@~2.1.2", - "@pnpm/dependency-path": "~5.1.7", + "@pnpm/dependency-path-lockfile-pre-v10": "npm:@pnpm/dependency-path@~5.1.7", + "@pnpm/dependency-path": "~1000.0.9", "@pnpm/link-bins": "~5.3.7", "@rushstack/heft-config-file": "workspace:*", "@rushstack/lookup-by-path": "workspace:*", @@ -64,7 +65,7 @@ "tar": "~6.2.1", "true-case-path": "~2.2.1", "uuid": "~8.3.2", - "pnpm-sync-lib": "0.3.0" + "pnpm-sync-lib": "0.3.2" }, "devDependencies": { "@pnpm/lockfile.types": "~1.0.3", diff --git a/libraries/rush-lib/src/index.ts b/libraries/rush-lib/src/index.ts index d7ef26a364e..685720e0629 100644 --- a/libraries/rush-lib/src/index.ts +++ b/libraries/rush-lib/src/index.ts @@ -197,3 +197,9 @@ export { type IRushCommandLineParameter, type IRushCommandLineAction } from './api/RushCommandLine'; + +export { OperationBuildCache as _OperationBuildCache } from './logic/buildCache/OperationBuildCache'; +export type { + IOperationBuildCacheOptions as _IOperationBuildCacheOptions, + IProjectBuildCacheOptions as _IProjectBuildCacheOptions +} from './logic/buildCache/OperationBuildCache'; diff --git a/libraries/rush-lib/src/logic/CredentialCache.ts b/libraries/rush-lib/src/logic/CredentialCache.ts index de2bb123967..5c1c775821e 100644 --- a/libraries/rush-lib/src/logic/CredentialCache.ts +++ b/libraries/rush-lib/src/logic/CredentialCache.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +import * as path from 'path'; import { FileSystem, JsonFile, JsonSchema, LockFile } from '@rushstack/node-core-library'; import { Utilities } from '../utilities/Utilities'; @@ -8,7 +9,7 @@ import { RushUserConfiguration } from '../api/RushUserConfiguration'; import schemaJson from '../schemas/credentials.schema.json'; import { objectsAreDeepEqual } from '../utilities/objectUtilities'; -const CACHE_FILENAME: string = 'credentials.json'; +const DEFAULT_CACHE_FILENAME: 'credentials.json' = 'credentials.json'; const LATEST_CREDENTIALS_JSON_VERSION: string = '0.1.0'; interface ICredentialCacheJson { @@ -38,6 +39,10 @@ export interface ICredentialCacheEntry { */ export interface ICredentialCacheOptions { supportEditing: boolean; + /** + * If specified, use the specified path instead of the default path of `~/.rush-user/credentials.json` + */ + cacheFilePath?: string; } /** @@ -57,7 +62,7 @@ export class CredentialCache /* implements IDisposable */ { lockfile: LockFile | undefined ) { if (loadedJson && loadedJson.version !== LATEST_CREDENTIALS_JSON_VERSION) { - throw new Error(`Unexpected credentials.json file version: ${loadedJson.version}`); + throw new Error(`Unexpected ${cacheFilePath} file version: ${loadedJson.version}`); } this._cacheFilePath = cacheFilePath; @@ -67,8 +72,17 @@ export class CredentialCache /* implements IDisposable */ { } public static async initializeAsync(options: ICredentialCacheOptions): Promise { - const rushUserFolderPath: string = RushUserConfiguration.getRushUserFolderPath(); - const cacheFilePath: string = `${rushUserFolderPath}/${CACHE_FILENAME}`; + let cacheDirectory: string; + let cacheFileName: string; + if (options.cacheFilePath) { + cacheDirectory = path.dirname(options.cacheFilePath); + cacheFileName = options.cacheFilePath.slice(cacheDirectory.length + 1); + } else { + cacheDirectory = RushUserConfiguration.getRushUserFolderPath(); + cacheFileName = DEFAULT_CACHE_FILENAME; + } + const cacheFilePath: string = `${cacheDirectory}/${cacheFileName}`; + const jsonSchema: JsonSchema = JsonSchema.fromLoadedObject(schemaJson); let loadedJson: ICredentialCacheJson | undefined; @@ -82,7 +96,7 @@ export class CredentialCache /* implements IDisposable */ { let lockfile: LockFile | undefined; if (options.supportEditing) { - lockfile = await LockFile.acquireAsync(rushUserFolderPath, `${CACHE_FILENAME}.lock`); + lockfile = await LockFile.acquireAsync(cacheDirectory, `${cacheFileName}.lock`); } const credentialCache: CredentialCache = new CredentialCache(cacheFilePath, loadedJson, lockfile); diff --git a/libraries/rush-lib/src/logic/buildCache/ProjectBuildCache.ts b/libraries/rush-lib/src/logic/buildCache/OperationBuildCache.ts similarity index 90% rename from libraries/rush-lib/src/logic/buildCache/ProjectBuildCache.ts rename to libraries/rush-lib/src/logic/buildCache/OperationBuildCache.ts index 861d403f029..d603c094547 100644 --- a/libraries/rush-lib/src/logic/buildCache/ProjectBuildCache.ts +++ b/libraries/rush-lib/src/logic/buildCache/OperationBuildCache.ts @@ -13,8 +13,11 @@ import type { ICloudBuildCacheProvider } from './ICloudBuildCacheProvider'; import type { FileSystemBuildCacheProvider } from './FileSystemBuildCacheProvider'; import { TarExecutable } from '../../utilities/TarExecutable'; import { EnvironmentVariableNames } from '../../api/EnvironmentConfiguration'; -import type { OperationExecutionRecord } from '../operations/OperationExecutionRecord'; +import type { IOperationExecutionResult } from '../operations/IOperationExecutionResult'; +/** + * @internal + */ export interface IOperationBuildCacheOptions { /** * The repo-wide configuration for the build cache. @@ -26,6 +29,9 @@ export interface IOperationBuildCacheOptions { terminal: ITerminal; } +/** + * @internal + */ export type IProjectBuildCacheOptions = IOperationBuildCacheOptions & { /** * Value from rush-project.json @@ -50,7 +56,10 @@ interface IPathsToCache { outputFilePaths: string[]; } -export class ProjectBuildCache { +/** + * @internal + */ +export class OperationBuildCache { private static _tarUtilityPromise: Promise | undefined; private readonly _project: RushConfigurationProject; @@ -82,40 +91,40 @@ export class ProjectBuildCache { } private static _tryGetTarUtility(terminal: ITerminal): Promise { - if (!ProjectBuildCache._tarUtilityPromise) { - ProjectBuildCache._tarUtilityPromise = TarExecutable.tryInitializeAsync(terminal); + if (!OperationBuildCache._tarUtilityPromise) { + OperationBuildCache._tarUtilityPromise = TarExecutable.tryInitializeAsync(terminal); } - return ProjectBuildCache._tarUtilityPromise; + return OperationBuildCache._tarUtilityPromise; } public get cacheId(): string | undefined { return this._cacheId; } - public static getProjectBuildCache(options: IProjectBuildCacheOptions): ProjectBuildCache { - const cacheId: string | undefined = ProjectBuildCache._getCacheId(options); - return new ProjectBuildCache(cacheId, options); + public static getOperationBuildCache(options: IProjectBuildCacheOptions): OperationBuildCache { + const cacheId: string | undefined = OperationBuildCache._getCacheId(options); + return new OperationBuildCache(cacheId, options); } public static forOperation( - operation: OperationExecutionRecord, + executionResult: IOperationExecutionResult, options: IOperationBuildCacheOptions - ): ProjectBuildCache { - const outputFolders: string[] = [...(operation.operation.settings?.outputFolderNames ?? [])]; - if (operation.metadataFolderPath) { - outputFolders.push(operation.metadataFolderPath); + ): OperationBuildCache { + const outputFolders: string[] = [...(executionResult.operation.settings?.outputFolderNames ?? [])]; + if (executionResult.metadataFolderPath) { + outputFolders.push(executionResult.metadataFolderPath); } const buildCacheOptions: IProjectBuildCacheOptions = { buildCacheConfiguration: options.buildCacheConfiguration, terminal: options.terminal, - project: operation.associatedProject, - phaseName: operation.associatedPhase.name, + project: executionResult.operation.associatedProject, + phaseName: executionResult.operation.associatedPhase.name, projectOutputFolderNames: outputFolders, - operationStateHash: operation.getStateHash() + operationStateHash: executionResult.getStateHash() }; - const cacheId: string | undefined = ProjectBuildCache._getCacheId(buildCacheOptions); - return new ProjectBuildCache(cacheId, buildCacheOptions); + const cacheId: string | undefined = OperationBuildCache._getCacheId(buildCacheOptions); + return new OperationBuildCache(cacheId, buildCacheOptions); } public async tryRestoreFromCacheAsync(terminal: ITerminal, specifiedCacheId?: string): Promise { @@ -175,7 +184,7 @@ export class ProjectBuildCache { ) ); - const tarUtility: TarExecutable | undefined = await ProjectBuildCache._tryGetTarUtility(terminal); + const tarUtility: TarExecutable | undefined = await OperationBuildCache._tryGetTarUtility(terminal); let restoreSuccess: boolean = false; if (tarUtility && localCacheEntryPath) { const logFilePath: string = this._getTarLogFilePath(cacheId, 'untar'); @@ -225,7 +234,7 @@ export class ProjectBuildCache { let localCacheEntryPath: string | undefined; - const tarUtility: TarExecutable | undefined = await ProjectBuildCache._tryGetTarUtility(terminal); + const tarUtility: TarExecutable | undefined = await OperationBuildCache._tryGetTarUtility(terminal); if (tarUtility) { const finalLocalCacheEntryPath: string = this._localBuildCacheProvider.getCacheEntryPath(cacheId); diff --git a/libraries/rush-lib/src/logic/buildCache/test/ProjectBuildCache.test.ts b/libraries/rush-lib/src/logic/buildCache/test/OperationBuildCache.test.ts similarity index 79% rename from libraries/rush-lib/src/logic/buildCache/test/ProjectBuildCache.test.ts rename to libraries/rush-lib/src/logic/buildCache/test/OperationBuildCache.test.ts index 1acc025dee4..82606c11784 100644 --- a/libraries/rush-lib/src/logic/buildCache/test/ProjectBuildCache.test.ts +++ b/libraries/rush-lib/src/logic/buildCache/test/OperationBuildCache.test.ts @@ -8,7 +8,7 @@ import type { RushConfigurationProject } from '../../../api/RushConfigurationPro import type { IGenerateCacheEntryIdOptions } from '../CacheEntryId'; import type { FileSystemBuildCacheProvider } from '../FileSystemBuildCacheProvider'; -import { ProjectBuildCache } from '../ProjectBuildCache'; +import { OperationBuildCache } from '../OperationBuildCache'; interface ITestOptions { enabled: boolean; @@ -16,11 +16,11 @@ interface ITestOptions { trackedProjectFiles: string[] | undefined; } -describe(ProjectBuildCache.name, () => { - function prepareSubject(options: Partial): ProjectBuildCache { +describe(OperationBuildCache.name, () => { + function prepareSubject(options: Partial): OperationBuildCache { const terminal: Terminal = new Terminal(new StringBufferTerminalProvider()); - const subject: ProjectBuildCache = ProjectBuildCache.getProjectBuildCache({ + const subject: OperationBuildCache = OperationBuildCache.getOperationBuildCache({ buildCacheConfiguration: { buildCacheEnabled: options.hasOwnProperty('enabled') ? options.enabled : true, getCacheEntryId: (opts: IGenerateCacheEntryIdOptions) => @@ -46,9 +46,9 @@ describe(ProjectBuildCache.name, () => { return subject; } - describe(ProjectBuildCache.getProjectBuildCache.name, () => { - it('returns a ProjectBuildCache with a calculated cacheId value', () => { - const subject: ProjectBuildCache = prepareSubject({}); + describe(OperationBuildCache.getOperationBuildCache.name, () => { + it('returns an OperationBuildCache with a calculated cacheId value', () => { + const subject: OperationBuildCache = prepareSubject({}); expect(subject['_cacheId']).toMatchInlineSnapshot( `"acme-wizard/1926f30e8ed24cb47be89aea39e7efd70fcda075"` ); diff --git a/libraries/rush-lib/src/logic/cobuild/CobuildLock.ts b/libraries/rush-lib/src/logic/cobuild/CobuildLock.ts index 44a99405aaf..9dbf7114c3d 100644 --- a/libraries/rush-lib/src/logic/cobuild/CobuildLock.ts +++ b/libraries/rush-lib/src/logic/cobuild/CobuildLock.ts @@ -6,7 +6,7 @@ import { InternalError } from '@rushstack/node-core-library'; import type { CobuildConfiguration } from '../../api/CobuildConfiguration'; import type { OperationStatus } from '../operations/OperationStatus'; import type { ICobuildContext } from './ICobuildLockProvider'; -import type { ProjectBuildCache } from '../buildCache/ProjectBuildCache'; +import type { OperationBuildCache } from '../buildCache/OperationBuildCache'; const KEY_SEPARATOR: ':' = ':'; @@ -27,7 +27,7 @@ export interface ICobuildLockOptions { * {@inheritdoc ICobuildContext.phaseName} */ phaseName: string; - projectBuildCache: ProjectBuildCache; + operationBuildCache: OperationBuildCache; /** * The expire time of the lock in seconds. */ @@ -41,23 +41,23 @@ export interface ICobuildCompletedState { export class CobuildLock { public readonly cobuildConfiguration: CobuildConfiguration; - public readonly projectBuildCache: ProjectBuildCache; + public readonly operationBuildCache: OperationBuildCache; private _cobuildContext: ICobuildContext; public constructor(options: ICobuildLockOptions) { const { cobuildConfiguration, - projectBuildCache, + operationBuildCache, cobuildClusterId: clusterId, lockExpireTimeInSeconds, packageName, phaseName } = options; const { cobuildContextId: contextId, cobuildRunnerId: runnerId } = cobuildConfiguration; - const { cacheId } = projectBuildCache; + const { cacheId } = operationBuildCache; this.cobuildConfiguration = cobuildConfiguration; - this.projectBuildCache = projectBuildCache; + this.operationBuildCache = operationBuildCache; if (!cacheId) { // This should never happen diff --git a/libraries/rush-lib/src/logic/cobuild/test/CobuildLock.test.ts b/libraries/rush-lib/src/logic/cobuild/test/CobuildLock.test.ts index b67089460db..bde478c4919 100644 --- a/libraries/rush-lib/src/logic/cobuild/test/CobuildLock.test.ts +++ b/libraries/rush-lib/src/logic/cobuild/test/CobuildLock.test.ts @@ -4,7 +4,7 @@ import { CobuildLock, type ICobuildLockOptions } from '../CobuildLock'; import type { CobuildConfiguration } from '../../../api/CobuildConfiguration'; -import type { ProjectBuildCache } from '../../buildCache/ProjectBuildCache'; +import type { OperationBuildCache } from '../../buildCache/OperationBuildCache'; import type { ICobuildContext } from '../ICobuildLockProvider'; describe(CobuildLock.name, () => { @@ -14,9 +14,9 @@ describe(CobuildLock.name, () => { cobuildContextId: 'context_id', cobuildRunnerId: 'runner_id' } as unknown as CobuildConfiguration, - projectBuildCache: { + operationBuildCache: { cacheId: 'cache_id' - } as unknown as ProjectBuildCache, + } as unknown as OperationBuildCache, cobuildClusterId: 'cluster_id', lockExpireTimeInSeconds: 30, packageName: 'package_name', diff --git a/libraries/rush-lib/src/logic/operations/CacheableOperationPlugin.ts b/libraries/rush-lib/src/logic/operations/CacheableOperationPlugin.ts index 3ccddb4bbeb..e47c454971f 100644 --- a/libraries/rush-lib/src/logic/operations/CacheableOperationPlugin.ts +++ b/libraries/rush-lib/src/logic/operations/CacheableOperationPlugin.ts @@ -10,7 +10,7 @@ import { SplitterTransform, type TerminalWritable, type ITerminal, Terminal } fr import { CollatedTerminalProvider } from '../../utilities/CollatedTerminalProvider'; import { OperationStatus } from './OperationStatus'; import { CobuildLock, type ICobuildCompletedState } from '../cobuild/CobuildLock'; -import { ProjectBuildCache } from '../buildCache/ProjectBuildCache'; +import { OperationBuildCache } from '../buildCache/OperationBuildCache'; import { RushConstants } from '../RushConstants'; import type { RushProjectConfiguration } from '../../api/RushProjectConfiguration'; import { @@ -47,7 +47,7 @@ export interface IOperationBuildCacheContext { isCacheWriteAllowed: boolean; isCacheReadAllowed: boolean; - operationBuildCache: ProjectBuildCache | undefined; + operationBuildCache: OperationBuildCache | undefined; cacheDisabledReason: string | undefined; outputFolderNames: ReadonlyArray; @@ -240,7 +240,7 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin { const buildCacheTerminal: ITerminal = buildCacheContext.buildCacheTerminal; - let projectBuildCache: ProjectBuildCache | undefined = this._tryGetProjectBuildCache({ + let operationBuildCache: OperationBuildCache | undefined = this._tryGetOperationBuildCache({ buildCacheContext, buildCacheConfiguration, terminal: buildCacheTerminal, @@ -253,18 +253,18 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin { if ( cobuildConfiguration?.cobuildLeafProjectLogOnlyAllowed && operation.consumers.size === 0 && - !projectBuildCache + !operationBuildCache ) { // When the leaf project log only is allowed and the leaf project is build cache "disabled", try to get // a log files only project build cache - projectBuildCache = await this._tryGetLogOnlyProjectBuildCacheAsync({ + operationBuildCache = await this._tryGetLogOnlyOperationBuildCacheAsync({ buildCacheConfiguration, cobuildConfiguration, buildCacheContext, record, terminal: buildCacheTerminal }); - if (projectBuildCache) { + if (operationBuildCache) { buildCacheTerminal.writeVerboseLine( `Log files only build cache is enabled for the project "${project.packageName}" because the cobuild leaf project log only is allowed` ); @@ -277,7 +277,7 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin { cobuildLock = await this._tryGetCobuildLockAsync({ buildCacheContext, - projectBuildCache, + operationBuildCache, cobuildConfiguration, packageName: project.packageName, phaseName: phase.name @@ -305,14 +305,14 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin { logFilenameIdentifier: operation.logFilenameIdentifier }); const restoreCacheAsync = async ( - // TODO: Investigate if `projectBuildCacheForRestore` is always the same instance as `projectBuildCache` + // TODO: Investigate if `operationBuildCacheForRestore` is always the same instance as `operationBuildCache` // above, and if it is, remove this parameter - projectBuildCacheForRestore: ProjectBuildCache | undefined, + operationBuildCacheForRestore: OperationBuildCache | undefined, specifiedCacheId?: string ): Promise => { buildCacheContext.isCacheReadAttempted = true; const restoreFromCacheSuccess: boolean | undefined = - await projectBuildCacheForRestore?.tryRestoreFromCacheAsync( + await operationBuildCacheForRestore?.tryRestoreFromCacheAsync( buildCacheTerminal, specifiedCacheId ); @@ -350,7 +350,7 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin { } const restoreFromCacheSuccess: boolean = await restoreCacheAsync( - cobuildLock.projectBuildCache, + cobuildLock.operationBuildCache, cacheId ); @@ -358,14 +358,14 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin { return status; } } else if (!buildCacheContext.isCacheReadAttempted && buildCacheContext.isCacheReadAllowed) { - const restoreFromCacheSuccess: boolean = await restoreCacheAsync(projectBuildCache); + const restoreFromCacheSuccess: boolean = await restoreCacheAsync(operationBuildCache); if (restoreFromCacheSuccess) { return OperationStatus.FromCache; } } } else if (buildCacheContext.isCacheReadAllowed) { - const restoreFromCacheSuccess: boolean = await restoreCacheAsync(projectBuildCache); + const restoreFromCacheSuccess: boolean = await restoreCacheAsync(operationBuildCache); if (restoreFromCacheSuccess) { return OperationStatus.FromCache; @@ -471,7 +471,7 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin { }); }; setCacheEntryPromise = () => - cobuildLock.projectBuildCache.trySetCacheEntryAsync(buildCacheTerminal, finalCacheId); + cobuildLock.operationBuildCache.trySetCacheEntryAsync(buildCacheTerminal, finalCacheId); } } } @@ -553,7 +553,7 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin { return buildCacheContext; } - private _tryGetProjectBuildCache({ + private _tryGetOperationBuildCache({ buildCacheConfiguration, buildCacheContext, terminal, @@ -563,7 +563,7 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin { buildCacheConfiguration: BuildCacheConfiguration | undefined; terminal: ITerminal; record: OperationExecutionRecord; - }): ProjectBuildCache | undefined { + }): OperationBuildCache | undefined { if (!buildCacheContext.operationBuildCache) { const { cacheDisabledReason } = buildCacheContext; if (cacheDisabledReason && !record.operation.settings?.allowCobuildWithoutCache) { @@ -577,7 +577,7 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin { } // eslint-disable-next-line require-atomic-updates -- This is guaranteed to not be concurrent - buildCacheContext.operationBuildCache = ProjectBuildCache.forOperation(record, { + buildCacheContext.operationBuildCache = OperationBuildCache.forOperation(record, { buildCacheConfiguration, terminal }); @@ -586,14 +586,14 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin { return buildCacheContext.operationBuildCache; } - // Get a ProjectBuildCache only cache/restore log files - private async _tryGetLogOnlyProjectBuildCacheAsync(options: { + // Get an OperationBuildCache only cache/restore log files + private async _tryGetLogOnlyOperationBuildCacheAsync(options: { buildCacheContext: IOperationBuildCacheContext; buildCacheConfiguration: BuildCacheConfiguration | undefined; cobuildConfiguration: CobuildConfiguration; record: IOperationRunnerContext & IOperationExecutionResult; terminal: ITerminal; - }): Promise { + }): Promise { const { buildCacheContext, buildCacheConfiguration, cobuildConfiguration, record, terminal } = options; if (!buildCacheConfiguration?.buildCacheEnabled) { @@ -617,7 +617,7 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin { const { associatedPhase, associatedProject } = record.operation; - const projectBuildCache: ProjectBuildCache = ProjectBuildCache.getProjectBuildCache({ + const operationBuildCache: OperationBuildCache = OperationBuildCache.getOperationBuildCache({ project: associatedProject, projectOutputFolderNames: outputFolderNames, buildCacheConfiguration, @@ -627,33 +627,33 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin { }); // eslint-disable-next-line require-atomic-updates -- This is guaranteed to not be concurrent - buildCacheContext.operationBuildCache = projectBuildCache; + buildCacheContext.operationBuildCache = operationBuildCache; - return projectBuildCache; + return operationBuildCache; } private async _tryGetCobuildLockAsync({ cobuildConfiguration, buildCacheContext, - projectBuildCache, + operationBuildCache, packageName, phaseName }: { cobuildConfiguration: CobuildConfiguration | undefined; buildCacheContext: IOperationBuildCacheContext; - projectBuildCache: ProjectBuildCache | undefined; + operationBuildCache: OperationBuildCache | undefined; packageName: string; phaseName: string; }): Promise { if (!buildCacheContext.cobuildLock) { - if (projectBuildCache && cobuildConfiguration?.cobuildFeatureEnabled) { + if (operationBuildCache && cobuildConfiguration?.cobuildFeatureEnabled) { if (!buildCacheContext.cobuildClusterId) { // This should not happen throw new InternalError('Cobuild cluster id is not defined'); } buildCacheContext.cobuildLock = new CobuildLock({ cobuildConfiguration, - projectBuildCache, + operationBuildCache, cobuildClusterId: buildCacheContext.cobuildClusterId, lockExpireTimeInSeconds: PERIODIC_CALLBACK_INTERVAL_IN_SECONDS * 3, packageName, diff --git a/libraries/rush-lib/src/logic/operations/OperationExecutionRecord.ts b/libraries/rush-lib/src/logic/operations/OperationExecutionRecord.ts index f0111b28a15..b10da76af58 100644 --- a/libraries/rush-lib/src/logic/operations/OperationExecutionRecord.ts +++ b/libraries/rush-lib/src/logic/operations/OperationExecutionRecord.ts @@ -34,6 +34,9 @@ import { initializeProjectLogFilesAsync } from './ProjectLogWritable'; +/** + * @internal + */ export interface IOperationExecutionRecordContext { streamCollator: StreamCollator; onOperationStatusChanged?: (record: OperationExecutionRecord) => void; @@ -381,8 +384,8 @@ export class OperationExecutionRecord implements IOperationRunnerContext, IOpera this.status = this.operation.enabled ? await this.runner.executeAsync(this) : this.runner.isNoOp - ? OperationStatus.NoOp - : OperationStatus.Skipped; + ? OperationStatus.NoOp + : OperationStatus.Skipped; } // Make sure that the stopwatch is stopped before reporting the result, otherwise endTime is undefined. this.stopwatch.stop(); diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmLinkManager.ts b/libraries/rush-lib/src/logic/pnpm/PnpmLinkManager.ts index 60b58b3e759..a0b1466ee48 100644 --- a/libraries/rush-lib/src/logic/pnpm/PnpmLinkManager.ts +++ b/libraries/rush-lib/src/logic/pnpm/PnpmLinkManager.ts @@ -321,11 +321,29 @@ export class PnpmLinkManager extends BaseLinkManager { folderName, RushConstants.nodeModulesFolderName ); - } else if (this._pnpmVersion.major >= 9) { + } else if (this._pnpmVersion.major >= 10) { const { depPathToFilename } = await import('@pnpm/dependency-path'); // project@file+projects+presentation-integration-tests.tgz_jsdom@11.12.0 - // The second parameter is max length of virtual store dir, default is 120 https://pnpm.io/next/npmrc#virtual-store-dir-max-length + // The second parameter is max length of virtual store dir, + // for v10 default is 120 on Linux/MacOS and 60 on Windows https://pnpm.io/next/settings#virtualstoredirmaxlength + // TODO Read virtual-store-dir-max-length from .npmrc + const folderName: string = depPathToFilename( + tempProjectDependencyKey, + process.platform === 'win32' ? 60 : 120 + ); + return path.join( + this._rushConfiguration.commonTempFolder, + RushConstants.nodeModulesFolderName, + '.pnpm', + folderName, + RushConstants.nodeModulesFolderName + ); + } else if (this._pnpmVersion.major >= 9) { + const { depPathToFilename } = await import('@pnpm/dependency-path-lockfile-pre-v10'); + + // project@file+projects+presentation-integration-tests.tgz_jsdom@11.12.0 + // The second parameter is max length of virtual store dir, for v9 default is 120 https://pnpm.io/9.x/npmrc#virtual-store-dir-max-length // TODO Read virtual-store-dir-max-length from .npmrc const folderName: string = depPathToFilename(tempProjectDependencyKey, 120); return path.join( diff --git a/libraries/rush-lib/src/logic/test/CredentialCache.test.ts b/libraries/rush-lib/src/logic/test/CredentialCache.test.ts index b2d8ac49a43..fe7e92fae97 100644 --- a/libraries/rush-lib/src/logic/test/CredentialCache.test.ts +++ b/libraries/rush-lib/src/logic/test/CredentialCache.test.ts @@ -3,10 +3,13 @@ import { LockFile, Async, FileSystem } from '@rushstack/node-core-library'; import { RushUserConfiguration } from '../../api/RushUserConfiguration'; -import { CredentialCache } from '../CredentialCache'; +import { CredentialCache, type ICredentialCacheOptions } from '../CredentialCache'; const FAKE_RUSH_USER_FOLDER: string = '~/.rush-user'; -const FAKE_CREDENTIALS_CACHE_FILE: string = `${FAKE_RUSH_USER_FOLDER}/credentials.json`; + +interface IPathsTestCase extends Required> { + testCaseName: string; +} describe(CredentialCache.name, () => { let fakeFilesystem: { [key: string]: string }; @@ -85,185 +88,270 @@ describe(CredentialCache.name, () => { jest.restoreAllMocks(); }); - it("initializes a credential cache correctly when one doesn't exist on disk", async () => { - const credentialCache: CredentialCache = await CredentialCache.initializeAsync({ supportEditing: false }); - expect(credentialCache).toBeDefined(); - credentialCache.dispose(); - }); + describe.each([ + { + testCaseName: 'default cache path', + cacheFilePath: `${FAKE_RUSH_USER_FOLDER}/credentials.json` + }, + { + testCaseName: 'custom cache path with no suffix', + cacheFilePath: `${FAKE_RUSH_USER_FOLDER}/my-cache-name` + }, + { + testCaseName: 'custom cache path with json suffix', + cacheFilePath: `${FAKE_RUSH_USER_FOLDER}/my-cache-name.json` + } + ])('cache paths [$testCaseName]', ({ cacheFilePath }) => { + it("initializes a credential cache correctly when one doesn't exist on disk", async () => { + const credentialCache: CredentialCache = await CredentialCache.initializeAsync({ + supportEditing: false + }); + expect(credentialCache).toBeDefined(); + credentialCache.dispose(); + }); - it('initializes a credential cache correctly when one exists on disk', async () => { - const credentialId: string = 'test-credential'; - const credentialValue: string = 'test-value'; - fakeFilesystem[FAKE_CREDENTIALS_CACHE_FILE] = JSON.stringify({ - version: '0.1.0', - cacheEntries: { - [credentialId]: { - expires: 0, - credential: credentialValue + it('initializes a credential cache correctly when one exists on disk', async () => { + const credentialId: string = 'test-credential'; + const credentialValue: string = 'test-value'; + fakeFilesystem[cacheFilePath] = JSON.stringify({ + version: '0.1.0', + cacheEntries: { + [credentialId]: { + expires: 0, + credential: credentialValue + } } - } + }); + + const credentialCache: CredentialCache = await CredentialCache.initializeAsync({ + cacheFilePath: cacheFilePath, + supportEditing: false + }); + expect(credentialCache.tryGetCacheEntry(credentialId)?.credential).toEqual(credentialValue); + expect(credentialCache.tryGetCacheEntry(credentialId)?.expires).toBeUndefined(); + credentialCache.dispose(); }); - const credentialCache: CredentialCache = await CredentialCache.initializeAsync({ supportEditing: false }); - expect(credentialCache.tryGetCacheEntry(credentialId)?.credential).toEqual(credentialValue); - expect(credentialCache.tryGetCacheEntry(credentialId)?.expires).toBeUndefined(); - credentialCache.dispose(); - }); + it('initializes a credential cache correctly when one exists on disk with a expired credential', async () => { + const credentialId: string = 'test-credential'; + const credentialValue: string = 'test-value'; + fakeFilesystem[cacheFilePath] = JSON.stringify({ + version: '0.1.0', + cacheEntries: { + [credentialId]: { + expires: 100, // Expired + credential: credentialValue + } + } + }); - it('initializes a credential cache correctly when one exists on disk with a expired credential', async () => { - const credentialId: string = 'test-credential'; - const credentialValue: string = 'test-value'; - fakeFilesystem[FAKE_CREDENTIALS_CACHE_FILE] = JSON.stringify({ - version: '0.1.0', - cacheEntries: { - [credentialId]: { - expires: 100, // Expired - credential: credentialValue + const credentialCache: CredentialCache = await CredentialCache.initializeAsync({ + cacheFilePath: cacheFilePath, + supportEditing: false + }); + expect(credentialCache.tryGetCacheEntry(credentialId)?.credential).toEqual(credentialValue); + expect(credentialCache.tryGetCacheEntry(credentialId)?.expires).toMatchSnapshot('expiration'); + credentialCache.dispose(); + }); + + it('correctly trims expired credentials', async () => { + const credentialId: string = 'test-credential'; + const credentialValue: string = 'test-value'; + fakeFilesystem[cacheFilePath] = JSON.stringify({ + version: '0.1.0', + cacheEntries: { + [credentialId]: { + expires: 100, // Expired + credential: credentialValue + } } - } + }); + + const credentialCache: CredentialCache = await CredentialCache.initializeAsync({ + cacheFilePath: cacheFilePath, + supportEditing: true + }); + credentialCache.trimExpiredEntries(); + expect(credentialCache.tryGetCacheEntry(credentialId)).toBeUndefined(); + await credentialCache.saveIfModifiedAsync(); + credentialCache.dispose(); + + expect(fakeFilesystem[cacheFilePath]).toMatchSnapshot('credential cache file'); }); - const credentialCache: CredentialCache = await CredentialCache.initializeAsync({ supportEditing: false }); - expect(credentialCache.tryGetCacheEntry(credentialId)?.credential).toEqual(credentialValue); - expect(credentialCache.tryGetCacheEntry(credentialId)?.expires).toMatchInlineSnapshot( - `1970-01-01T00:00:00.100Z` - ); - credentialCache.dispose(); - }); + it('correctly adds a new credential', async () => { + const credentialId: string = 'test-credential'; + const credentialValue: string = 'test-value'; - it('correctly trims expired credentials', async () => { - const credentialId: string = 'test-credential'; - const credentialValue: string = 'test-value'; - fakeFilesystem[FAKE_CREDENTIALS_CACHE_FILE] = JSON.stringify({ - version: '0.1.0', - cacheEntries: { - [credentialId]: { - expires: 100, // Expired - credential: credentialValue - } - } + const credentialCache1: CredentialCache = await CredentialCache.initializeAsync({ + cacheFilePath: cacheFilePath, + supportEditing: true + }); + credentialCache1.setCacheEntry(credentialId, { credential: credentialValue }); + expect(credentialCache1.tryGetCacheEntry(credentialId)?.credential).toEqual(credentialValue); + expect(credentialCache1.tryGetCacheEntry(credentialId)?.expires).toBeUndefined(); + await credentialCache1.saveIfModifiedAsync(); + credentialCache1.dispose(); + + expect(fakeFilesystem[cacheFilePath]).toMatchSnapshot('credential cache file'); + + const credentialCache2: CredentialCache = await CredentialCache.initializeAsync({ + cacheFilePath: cacheFilePath, + supportEditing: false + }); + expect(credentialCache2.tryGetCacheEntry(credentialId)?.credential).toEqual(credentialValue); + expect(credentialCache2.tryGetCacheEntry(credentialId)?.expires).toBeUndefined(); + credentialCache2.dispose(); }); - const credentialCache: CredentialCache = await CredentialCache.initializeAsync({ supportEditing: true }); - credentialCache.trimExpiredEntries(); - expect(credentialCache.tryGetCacheEntry(credentialId)).toBeUndefined(); - await credentialCache.saveIfModifiedAsync(); - credentialCache.dispose(); + it('correctly updates an existing credential', async () => { + const credentialId: string = 'test-credential'; + const credentialValue: string = 'test-value'; + const newCredentialValue: string = 'new-test-value'; + fakeFilesystem[cacheFilePath] = JSON.stringify({ + version: '0.1.0', + cacheEntries: { + [credentialId]: { + expires: 0, + credential: credentialValue + } + } + }); - expect(fakeFilesystem[FAKE_CREDENTIALS_CACHE_FILE]).toMatchInlineSnapshot(` -"{ - \\"version\\": \\"0.1.0\\", - \\"cacheEntries\\": {} -} -" -`); - }); + const credentialCache1: CredentialCache = await CredentialCache.initializeAsync({ + cacheFilePath: cacheFilePath, + supportEditing: true + }); + credentialCache1.setCacheEntry(credentialId, { credential: newCredentialValue }); + expect(credentialCache1.tryGetCacheEntry(credentialId)?.credential).toEqual(newCredentialValue); + expect(credentialCache1.tryGetCacheEntry(credentialId)?.expires).toBeUndefined(); + await credentialCache1.saveIfModifiedAsync(); + credentialCache1.dispose(); - it('correctly adds a new credential', async () => { - const credentialId: string = 'test-credential'; - const credentialValue: string = 'test-value'; - - const credentialCache1: CredentialCache = await CredentialCache.initializeAsync({ supportEditing: true }); - credentialCache1.setCacheEntry(credentialId, { credential: credentialValue }); - expect(credentialCache1.tryGetCacheEntry(credentialId)?.credential).toEqual(credentialValue); - expect(credentialCache1.tryGetCacheEntry(credentialId)?.expires).toBeUndefined(); - await credentialCache1.saveIfModifiedAsync(); - credentialCache1.dispose(); - - expect(fakeFilesystem[FAKE_CREDENTIALS_CACHE_FILE]).toMatchInlineSnapshot(` -"{ - \\"version\\": \\"0.1.0\\", - \\"cacheEntries\\": { - \\"test-credential\\": { - \\"expires\\": 0, - \\"credential\\": \\"test-value\\" - } - } -} -" -`); + expect(fakeFilesystem[cacheFilePath]).toMatchSnapshot('credential cache file'); - const credentialCache2: CredentialCache = await CredentialCache.initializeAsync({ - supportEditing: false + const credentialCache2: CredentialCache = await CredentialCache.initializeAsync({ + cacheFilePath: cacheFilePath, + supportEditing: false + }); + expect(credentialCache2.tryGetCacheEntry(credentialId)?.credential).toEqual(newCredentialValue); + expect(credentialCache2.tryGetCacheEntry(credentialId)?.expires).toBeUndefined(); + credentialCache2.dispose(); }); - expect(credentialCache2.tryGetCacheEntry(credentialId)?.credential).toEqual(credentialValue); - expect(credentialCache2.tryGetCacheEntry(credentialId)?.expires).toBeUndefined(); - credentialCache2.dispose(); - }); - it('correctly updates an existing credential', async () => { - const credentialId: string = 'test-credential'; - const credentialValue: string = 'test-value'; - const newCredentialValue: string = 'new-test-value'; - fakeFilesystem[FAKE_CREDENTIALS_CACHE_FILE] = JSON.stringify({ - version: '0.1.0', - cacheEntries: { - [credentialId]: { - expires: 0, - credential: credentialValue + it('correctly deletes an existing credential', async () => { + const credentialId: string = 'test-credential'; + fakeFilesystem[cacheFilePath] = JSON.stringify({ + version: '0.1.0', + cacheEntries: { + [credentialId]: { + expires: 0, + credential: 'test-value' + } } - } + }); + + const credentialCache1: CredentialCache = await CredentialCache.initializeAsync({ + cacheFilePath: cacheFilePath, + supportEditing: true + }); + credentialCache1.deleteCacheEntry(credentialId); + expect(credentialCache1.tryGetCacheEntry(credentialId)).toBeUndefined(); + await credentialCache1.saveIfModifiedAsync(); + credentialCache1.dispose(); + + expect(fakeFilesystem[cacheFilePath]).toMatchSnapshot('credential cache file'); + + const credentialCache2: CredentialCache = await CredentialCache.initializeAsync({ + cacheFilePath: cacheFilePath, + supportEditing: false + }); + expect(credentialCache2.tryGetCacheEntry(credentialId)).toBeUndefined(); + credentialCache2.dispose(); }); - const credentialCache1: CredentialCache = await CredentialCache.initializeAsync({ supportEditing: true }); - credentialCache1.setCacheEntry(credentialId, { credential: newCredentialValue }); - expect(credentialCache1.tryGetCacheEntry(credentialId)?.credential).toEqual(newCredentialValue); - expect(credentialCache1.tryGetCacheEntry(credentialId)?.expires).toBeUndefined(); - await credentialCache1.saveIfModifiedAsync(); - credentialCache1.dispose(); - - expect(fakeFilesystem[FAKE_CREDENTIALS_CACHE_FILE]).toMatchInlineSnapshot(` -"{ - \\"version\\": \\"0.1.0\\", - \\"cacheEntries\\": { - \\"test-credential\\": { - \\"expires\\": 0, - \\"credential\\": \\"new-test-value\\" - } - } -} -" -`); + it('correctly sets credentialMetadata', async () => { + const credentialId: string = 'test-credential'; + const credentialValue: string = 'test-value'; + const credentialMetadata: object = { + a: 1, + b: true + }; + + const credentialCache1: CredentialCache = await CredentialCache.initializeAsync({ + cacheFilePath: cacheFilePath, + supportEditing: true + }); + credentialCache1.setCacheEntry(credentialId, { credential: credentialValue, credentialMetadata }); + expect(credentialCache1.tryGetCacheEntry(credentialId)).toEqual({ + credential: credentialValue, + credentialMetadata + }); + await credentialCache1.saveIfModifiedAsync(); + credentialCache1.dispose(); + + expect(fakeFilesystem[cacheFilePath]).toMatchSnapshot('credential cache file'); - const credentialCache2: CredentialCache = await CredentialCache.initializeAsync({ - supportEditing: false + const credentialCache2: CredentialCache = await CredentialCache.initializeAsync({ + cacheFilePath: cacheFilePath, + supportEditing: false + }); + expect(credentialCache2.tryGetCacheEntry(credentialId)).toEqual({ + credential: credentialValue, + credentialMetadata + }); + credentialCache2.dispose(); }); - expect(credentialCache2.tryGetCacheEntry(credentialId)?.credential).toEqual(newCredentialValue); - expect(credentialCache2.tryGetCacheEntry(credentialId)?.expires).toBeUndefined(); - credentialCache2.dispose(); - }); - it('correctly deletes an existing credential', async () => { - const credentialId: string = 'test-credential'; - fakeFilesystem[FAKE_CREDENTIALS_CACHE_FILE] = JSON.stringify({ - version: '0.1.0', - cacheEntries: { - [credentialId]: { - expires: 0, - credential: 'test-value' + it('correctly updates credentialMetadata', async () => { + const credentialId: string = 'test-credential'; + const credentialValue: string = 'test-value'; + const oldCredentialMetadata: object = { + a: 1, + b: true + }; + const newCredentialMetadata: object = { + c: ['a', 'b', 'c'] + }; + + fakeFilesystem[cacheFilePath] = JSON.stringify({ + version: '0.1.0', + cacheEntries: { + [credentialId]: { + expires: 0, + credential: 'test-value', + credentialMetadata: oldCredentialMetadata + } } - } - }); + }); - const credentialCache1: CredentialCache = await CredentialCache.initializeAsync({ supportEditing: true }); - credentialCache1.deleteCacheEntry(credentialId); - expect(credentialCache1.tryGetCacheEntry(credentialId)).toBeUndefined(); - await credentialCache1.saveIfModifiedAsync(); - credentialCache1.dispose(); + const credentialCache1: CredentialCache = await CredentialCache.initializeAsync({ + cacheFilePath: cacheFilePath, + supportEditing: true + }); + credentialCache1.setCacheEntry(credentialId, { + credential: credentialValue, + credentialMetadata: newCredentialMetadata + }); + expect(credentialCache1.tryGetCacheEntry(credentialId)).toEqual({ + credential: credentialValue, + credentialMetadata: newCredentialMetadata + }); + await credentialCache1.saveIfModifiedAsync(); + credentialCache1.dispose(); - expect(fakeFilesystem[FAKE_CREDENTIALS_CACHE_FILE]).toMatchInlineSnapshot(` -"{ - \\"version\\": \\"0.1.0\\", - \\"cacheEntries\\": {} -} -" -`); + expect(fakeFilesystem[cacheFilePath]).toMatchSnapshot('credential cache file'); - const credentialCache2: CredentialCache = await CredentialCache.initializeAsync({ - supportEditing: false + const credentialCache2: CredentialCache = await CredentialCache.initializeAsync({ + cacheFilePath: cacheFilePath, + supportEditing: false + }); + expect(credentialCache2.tryGetCacheEntry(credentialId)).toEqual({ + credential: credentialValue, + credentialMetadata: newCredentialMetadata + }); + credentialCache2.dispose(); }); - expect(credentialCache2.tryGetCacheEntry(credentialId)).toBeUndefined(); - credentialCache2.dispose(); }); it('does not allow interaction if already disposed', async () => { @@ -303,112 +391,4 @@ describe(CredentialCache.name, () => { `"This instance of CredentialCache does not support editing."` ); }); - - it('correctly sets credentialMetadata', async () => { - const credentialId: string = 'test-credential'; - const credentialValue: string = 'test-value'; - const credentialMetadata: object = { - a: 1, - b: true - }; - - const credentialCache1: CredentialCache = await CredentialCache.initializeAsync({ supportEditing: true }); - credentialCache1.setCacheEntry(credentialId, { credential: credentialValue, credentialMetadata }); - expect(credentialCache1.tryGetCacheEntry(credentialId)).toEqual({ - credential: credentialValue, - credentialMetadata - }); - await credentialCache1.saveIfModifiedAsync(); - credentialCache1.dispose(); - - expect(fakeFilesystem[FAKE_CREDENTIALS_CACHE_FILE]).toMatchInlineSnapshot(` -"{ - \\"version\\": \\"0.1.0\\", - \\"cacheEntries\\": { - \\"test-credential\\": { - \\"expires\\": 0, - \\"credential\\": \\"test-value\\", - \\"credentialMetadata\\": { - \\"a\\": 1, - \\"b\\": true - } - } - } -} -" -`); - - const credentialCache2: CredentialCache = await CredentialCache.initializeAsync({ - supportEditing: false - }); - expect(credentialCache2.tryGetCacheEntry(credentialId)).toEqual({ - credential: credentialValue, - credentialMetadata - }); - credentialCache2.dispose(); - }); - - it('correctly updates credentialMetadata', async () => { - const credentialId: string = 'test-credential'; - const credentialValue: string = 'test-value'; - const oldCredentialMetadata: object = { - a: 1, - b: true - }; - const newCredentialMetadata: object = { - c: ['a', 'b', 'c'] - }; - - fakeFilesystem[FAKE_CREDENTIALS_CACHE_FILE] = JSON.stringify({ - version: '0.1.0', - cacheEntries: { - [credentialId]: { - expires: 0, - credential: 'test-value', - credentialMetadata: oldCredentialMetadata - } - } - }); - - const credentialCache1: CredentialCache = await CredentialCache.initializeAsync({ supportEditing: true }); - credentialCache1.setCacheEntry(credentialId, { - credential: credentialValue, - credentialMetadata: newCredentialMetadata - }); - expect(credentialCache1.tryGetCacheEntry(credentialId)).toEqual({ - credential: credentialValue, - credentialMetadata: newCredentialMetadata - }); - await credentialCache1.saveIfModifiedAsync(); - credentialCache1.dispose(); - - expect(fakeFilesystem[FAKE_CREDENTIALS_CACHE_FILE]).toMatchInlineSnapshot(` -"{ - \\"version\\": \\"0.1.0\\", - \\"cacheEntries\\": { - \\"test-credential\\": { - \\"expires\\": 0, - \\"credential\\": \\"test-value\\", - \\"credentialMetadata\\": { - \\"c\\": [ - \\"a\\", - \\"b\\", - \\"c\\" - ] - } - } - } -} -" -`); - - const credentialCache2: CredentialCache = await CredentialCache.initializeAsync({ - supportEditing: false - }); - expect(credentialCache2.tryGetCacheEntry(credentialId)).toEqual({ - credential: credentialValue, - credentialMetadata: newCredentialMetadata - }); - credentialCache2.dispose(); - }); }); diff --git a/libraries/rush-lib/src/logic/test/__snapshots__/CredentialCache.test.ts.snap b/libraries/rush-lib/src/logic/test/__snapshots__/CredentialCache.test.ts.snap new file mode 100644 index 00000000000..f0c2c1e488b --- /dev/null +++ b/libraries/rush-lib/src/logic/test/__snapshots__/CredentialCache.test.ts.snap @@ -0,0 +1,244 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CredentialCache cache paths [custom cache path with json suffix] correctly adds a new credential: credential cache file 1`] = ` +"{ + \\"version\\": \\"0.1.0\\", + \\"cacheEntries\\": { + \\"test-credential\\": { + \\"expires\\": 0, + \\"credential\\": \\"test-value\\" + } + } +} +" +`; + +exports[`CredentialCache cache paths [custom cache path with json suffix] correctly deletes an existing credential: credential cache file 1`] = ` +"{ + \\"version\\": \\"0.1.0\\", + \\"cacheEntries\\": {} +} +" +`; + +exports[`CredentialCache cache paths [custom cache path with json suffix] correctly sets credentialMetadata: credential cache file 1`] = ` +"{ + \\"version\\": \\"0.1.0\\", + \\"cacheEntries\\": { + \\"test-credential\\": { + \\"expires\\": 0, + \\"credential\\": \\"test-value\\", + \\"credentialMetadata\\": { + \\"a\\": 1, + \\"b\\": true + } + } + } +} +" +`; + +exports[`CredentialCache cache paths [custom cache path with json suffix] correctly trims expired credentials: credential cache file 1`] = ` +"{ + \\"version\\": \\"0.1.0\\", + \\"cacheEntries\\": {} +} +" +`; + +exports[`CredentialCache cache paths [custom cache path with json suffix] correctly updates an existing credential: credential cache file 1`] = ` +"{ + \\"version\\": \\"0.1.0\\", + \\"cacheEntries\\": { + \\"test-credential\\": { + \\"expires\\": 0, + \\"credential\\": \\"new-test-value\\" + } + } +} +" +`; + +exports[`CredentialCache cache paths [custom cache path with json suffix] correctly updates credentialMetadata: credential cache file 1`] = ` +"{ + \\"version\\": \\"0.1.0\\", + \\"cacheEntries\\": { + \\"test-credential\\": { + \\"expires\\": 0, + \\"credential\\": \\"test-value\\", + \\"credentialMetadata\\": { + \\"c\\": [ + \\"a\\", + \\"b\\", + \\"c\\" + ] + } + } + } +} +" +`; + +exports[`CredentialCache cache paths [custom cache path with json suffix] initializes a credential cache correctly when one exists on disk with a expired credential: expiration 1`] = `1970-01-01T00:00:00.100Z`; + +exports[`CredentialCache cache paths [custom cache path with no suffix] correctly adds a new credential: credential cache file 1`] = ` +"{ + \\"version\\": \\"0.1.0\\", + \\"cacheEntries\\": { + \\"test-credential\\": { + \\"expires\\": 0, + \\"credential\\": \\"test-value\\" + } + } +} +" +`; + +exports[`CredentialCache cache paths [custom cache path with no suffix] correctly deletes an existing credential: credential cache file 1`] = ` +"{ + \\"version\\": \\"0.1.0\\", + \\"cacheEntries\\": {} +} +" +`; + +exports[`CredentialCache cache paths [custom cache path with no suffix] correctly sets credentialMetadata: credential cache file 1`] = ` +"{ + \\"version\\": \\"0.1.0\\", + \\"cacheEntries\\": { + \\"test-credential\\": { + \\"expires\\": 0, + \\"credential\\": \\"test-value\\", + \\"credentialMetadata\\": { + \\"a\\": 1, + \\"b\\": true + } + } + } +} +" +`; + +exports[`CredentialCache cache paths [custom cache path with no suffix] correctly trims expired credentials: credential cache file 1`] = ` +"{ + \\"version\\": \\"0.1.0\\", + \\"cacheEntries\\": {} +} +" +`; + +exports[`CredentialCache cache paths [custom cache path with no suffix] correctly updates an existing credential: credential cache file 1`] = ` +"{ + \\"version\\": \\"0.1.0\\", + \\"cacheEntries\\": { + \\"test-credential\\": { + \\"expires\\": 0, + \\"credential\\": \\"new-test-value\\" + } + } +} +" +`; + +exports[`CredentialCache cache paths [custom cache path with no suffix] correctly updates credentialMetadata: credential cache file 1`] = ` +"{ + \\"version\\": \\"0.1.0\\", + \\"cacheEntries\\": { + \\"test-credential\\": { + \\"expires\\": 0, + \\"credential\\": \\"test-value\\", + \\"credentialMetadata\\": { + \\"c\\": [ + \\"a\\", + \\"b\\", + \\"c\\" + ] + } + } + } +} +" +`; + +exports[`CredentialCache cache paths [custom cache path with no suffix] initializes a credential cache correctly when one exists on disk with a expired credential: expiration 1`] = `1970-01-01T00:00:00.100Z`; + +exports[`CredentialCache cache paths [default cache path] correctly adds a new credential: credential cache file 1`] = ` +"{ + \\"version\\": \\"0.1.0\\", + \\"cacheEntries\\": { + \\"test-credential\\": { + \\"expires\\": 0, + \\"credential\\": \\"test-value\\" + } + } +} +" +`; + +exports[`CredentialCache cache paths [default cache path] correctly deletes an existing credential: credential cache file 1`] = ` +"{ + \\"version\\": \\"0.1.0\\", + \\"cacheEntries\\": {} +} +" +`; + +exports[`CredentialCache cache paths [default cache path] correctly sets credentialMetadata: credential cache file 1`] = ` +"{ + \\"version\\": \\"0.1.0\\", + \\"cacheEntries\\": { + \\"test-credential\\": { + \\"expires\\": 0, + \\"credential\\": \\"test-value\\", + \\"credentialMetadata\\": { + \\"a\\": 1, + \\"b\\": true + } + } + } +} +" +`; + +exports[`CredentialCache cache paths [default cache path] correctly trims expired credentials: credential cache file 1`] = ` +"{ + \\"version\\": \\"0.1.0\\", + \\"cacheEntries\\": {} +} +" +`; + +exports[`CredentialCache cache paths [default cache path] correctly updates an existing credential: credential cache file 1`] = ` +"{ + \\"version\\": \\"0.1.0\\", + \\"cacheEntries\\": { + \\"test-credential\\": { + \\"expires\\": 0, + \\"credential\\": \\"new-test-value\\" + } + } +} +" +`; + +exports[`CredentialCache cache paths [default cache path] correctly updates credentialMetadata: credential cache file 1`] = ` +"{ + \\"version\\": \\"0.1.0\\", + \\"cacheEntries\\": { + \\"test-credential\\": { + \\"expires\\": 0, + \\"credential\\": \\"test-value\\", + \\"credentialMetadata\\": { + \\"c\\": [ + \\"a\\", + \\"b\\", + \\"c\\" + ] + } + } + } +} +" +`; + +exports[`CredentialCache cache paths [default cache path] initializes a credential cache correctly when one exists on disk with a expired credential: expiration 1`] = `1970-01-01T00:00:00.100Z`; diff --git a/libraries/rush-sdk/package.json b/libraries/rush-sdk/package.json index 52af16bfd06..644479d8256 100644 --- a/libraries/rush-sdk/package.json +++ b/libraries/rush-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@rushstack/rush-sdk", - "version": "5.153.2", + "version": "5.155.0", "description": "An API for interacting with the Rush engine", "repository": { "type": "git", diff --git a/rigs/heft-node-rig/CHANGELOG.json b/rigs/heft-node-rig/CHANGELOG.json index ff094cb6215..19273e9a9df 100644 --- a/rigs/heft-node-rig/CHANGELOG.json +++ b/rigs/heft-node-rig/CHANGELOG.json @@ -1,6 +1,18 @@ { "name": "@rushstack/heft-node-rig", "entries": [ + { + "version": "2.8.14", + "tag": "@rushstack/heft-node-rig_v2.8.14", + "date": "Fri, 06 Jun 2025 00:11:09 GMT", + "comments": { + "dependency": [ + { + "comment": "Updating dependency \"@rushstack/heft-lint-plugin\" to `0.6.0`" + } + ] + } + }, { "version": "2.8.13", "tag": "@rushstack/heft-node-rig_v2.8.13", diff --git a/rigs/heft-node-rig/CHANGELOG.md b/rigs/heft-node-rig/CHANGELOG.md index 10fa5c1e2d2..bc6551e5bec 100644 --- a/rigs/heft-node-rig/CHANGELOG.md +++ b/rigs/heft-node-rig/CHANGELOG.md @@ -1,6 +1,11 @@ # Change Log - @rushstack/heft-node-rig -This log was last generated on Tue, 13 May 2025 02:09:20 GMT and should not be manually modified. +This log was last generated on Fri, 06 Jun 2025 00:11:09 GMT and should not be manually modified. + +## 2.8.14 +Fri, 06 Jun 2025 00:11:09 GMT + +_Version update only_ ## 2.8.13 Tue, 13 May 2025 02:09:20 GMT diff --git a/rigs/heft-node-rig/package.json b/rigs/heft-node-rig/package.json index fb214ed4d36..7cb13cc4fda 100644 --- a/rigs/heft-node-rig/package.json +++ b/rigs/heft-node-rig/package.json @@ -1,6 +1,6 @@ { "name": "@rushstack/heft-node-rig", - "version": "2.8.13", + "version": "2.8.14", "description": "A rig package for Node.js projects that build using Heft", "license": "MIT", "scripts": { diff --git a/rigs/heft-web-rig/CHANGELOG.json b/rigs/heft-web-rig/CHANGELOG.json index 4645633fb66..e55b4662612 100644 --- a/rigs/heft-web-rig/CHANGELOG.json +++ b/rigs/heft-web-rig/CHANGELOG.json @@ -1,6 +1,18 @@ { "name": "@rushstack/heft-web-rig", "entries": [ + { + "version": "0.28.15", + "tag": "@rushstack/heft-web-rig_v0.28.15", + "date": "Fri, 06 Jun 2025 00:11:09 GMT", + "comments": { + "dependency": [ + { + "comment": "Updating dependency \"@rushstack/heft-lint-plugin\" to `0.6.0`" + } + ] + } + }, { "version": "0.28.14", "tag": "@rushstack/heft-web-rig_v0.28.14", diff --git a/rigs/heft-web-rig/CHANGELOG.md b/rigs/heft-web-rig/CHANGELOG.md index dc1d5d8a641..d161a342747 100644 --- a/rigs/heft-web-rig/CHANGELOG.md +++ b/rigs/heft-web-rig/CHANGELOG.md @@ -1,6 +1,11 @@ # Change Log - @rushstack/heft-web-rig -This log was last generated on Thu, 15 May 2025 00:11:49 GMT and should not be manually modified. +This log was last generated on Fri, 06 Jun 2025 00:11:09 GMT and should not be manually modified. + +## 0.28.15 +Fri, 06 Jun 2025 00:11:09 GMT + +_Version update only_ ## 0.28.14 Thu, 15 May 2025 00:11:49 GMT diff --git a/rigs/heft-web-rig/package.json b/rigs/heft-web-rig/package.json index ff53a3ac74e..b53f1612e4d 100644 --- a/rigs/heft-web-rig/package.json +++ b/rigs/heft-web-rig/package.json @@ -1,6 +1,6 @@ { "name": "@rushstack/heft-web-rig", - "version": "0.28.14", + "version": "0.28.15", "description": "A rig package for web browser projects that build using Heft", "license": "MIT", "scripts": { diff --git a/rush-plugins/rush-amazon-s3-build-cache-plugin/package.json b/rush-plugins/rush-amazon-s3-build-cache-plugin/package.json index abab926b4cb..25ce3b4628d 100644 --- a/rush-plugins/rush-amazon-s3-build-cache-plugin/package.json +++ b/rush-plugins/rush-amazon-s3-build-cache-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@rushstack/rush-amazon-s3-build-cache-plugin", - "version": "5.153.2", + "version": "5.155.0", "description": "Rush plugin for Amazon S3 cloud build cache", "repository": { "type": "git", diff --git a/rush-plugins/rush-azure-storage-build-cache-plugin/package.json b/rush-plugins/rush-azure-storage-build-cache-plugin/package.json index 8ee61f1d37f..1c88c5425c7 100644 --- a/rush-plugins/rush-azure-storage-build-cache-plugin/package.json +++ b/rush-plugins/rush-azure-storage-build-cache-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@rushstack/rush-azure-storage-build-cache-plugin", - "version": "5.153.2", + "version": "5.155.0", "description": "Rush plugin for Azure storage cloud build cache", "repository": { "type": "git", diff --git a/rush-plugins/rush-bridge-cache-plugin/.eslintrc.js b/rush-plugins/rush-bridge-cache-plugin/.eslintrc.js new file mode 100644 index 00000000000..0b04796d1ee --- /dev/null +++ b/rush-plugins/rush-bridge-cache-plugin/.eslintrc.js @@ -0,0 +1,13 @@ +// This is a workaround for https://github.com/eslint/eslint/issues/3458 +require('local-node-rig/profiles/default/includes/eslint/patch/modern-module-resolution'); +// This is a workaround for https://github.com/microsoft/rushstack/issues/3021 +require('local-node-rig/profiles/default/includes/eslint/patch/custom-config-package-names'); + +module.exports = { + extends: [ + 'local-node-rig/profiles/default/includes/eslint/profile/node', + 'local-node-rig/profiles/default/includes/eslint/mixins/friendly-locals', + 'local-node-rig/profiles/default/includes/eslint/mixins/tsdoc' + ], + parserOptions: { tsconfigRootDir: __dirname } +}; diff --git a/rush-plugins/rush-bridge-cache-plugin/LICENSE b/rush-plugins/rush-bridge-cache-plugin/LICENSE new file mode 100644 index 00000000000..7cfdf630251 --- /dev/null +++ b/rush-plugins/rush-bridge-cache-plugin/LICENSE @@ -0,0 +1,24 @@ +@rushstack/rush-bridge-cache-plugin + +Copyright (c) Microsoft Corporation. All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/rush-plugins/rush-bridge-cache-plugin/README.md b/rush-plugins/rush-bridge-cache-plugin/README.md new file mode 100644 index 00000000000..e7e445de337 --- /dev/null +++ b/rush-plugins/rush-bridge-cache-plugin/README.md @@ -0,0 +1,55 @@ +# @rushstack/rush-bridge-cache-plugin + +This is a Rush plugin that lets you to add an optional flag to Rush's phased commands to bypass the actual _action_ of the script (build, test, lint - whatever you have configured), and just populate the cache from the action as though the action had already been performed by Rush. The flag name is configurable. + +This is useful for integrations with other build orchestrators such as BuildXL. You can use those to do the work of actually running the task, then run the equivalent Rush command afterwards with a `--set-cache-only` to populate the Rush cache with whatever had been generated on disk, in addition to whatever cache mechanism is used by the other build orchestrator. + +## Here be dragons! + +This plugin assumes that the work for a particular task has already been completed and the build artifacts have been generated on disk. **If you run this command on a package where the command hasn't already been run and the build artifacts are missing or incorrect, you will cache invalid content**. Be careful and beware! + + +## Installation + +1. Add the `@rushstack/rush-bridge-cache-plugin` package to your autoinstaller's package.json. +2. Update your `command-line.json` file to add the new flag. Configure it to target whatever specific commands you want to have this feature. Example: + +```json +{ + "associatedCommands": ["build", "test", "lint", "a11y", "typecheck"], + "description": "When the flag is added to any associated command, it'll bypass running the command itself, and cache whatever it finds on disk for the action. Beware! Only run when you know the build artifacts are in a valid state for the command.", + "parameterKind": "flag", + "longName": "--set-cache-only" +} +``` + +3. Add a new entry in `common/config/rush/rush-plugins` to register the new plugin: +```json +{ + "packageName": "@rushstack/rush-bridge-cache-plugin", + "pluginName": "rush-bridge-cache-plugin", + "autoinstallerName": "your-auto-installer-name-here" +} +``` + +4. Create a configuration file for this plugin at this location: `common/config/rush-plugins/rush-bridge-cache-plugin.json` that defines the flag name you'll use to trigger the plugin: +```json +{ + "flagName": "--set-cache-only" +} +``` + +## Usage + +You can now add the flag to any Rush phased command, e.g. + +`rush build --to your-packageX --set-cache-only` + +That will populate the cache for `your-packageX` and all of its dependencies. + + +## Performance + +When running within a pipeline, you may want to populate the cache as quickly as possible so local Rush users will benefit from the cached entry sooner. So instead of waiting until the full build graph has been processed, running it after each individual task when it's been completed, e.g. + +`rush lint --only your-packageY --set-cache-only` diff --git a/rush-plugins/rush-bridge-cache-plugin/config/jest.config.json b/rush-plugins/rush-bridge-cache-plugin/config/jest.config.json new file mode 100644 index 00000000000..d1749681d90 --- /dev/null +++ b/rush-plugins/rush-bridge-cache-plugin/config/jest.config.json @@ -0,0 +1,3 @@ +{ + "extends": "local-node-rig/profiles/default/config/jest.config.json" +} diff --git a/rush-plugins/rush-bridge-cache-plugin/config/rig.json b/rush-plugins/rush-bridge-cache-plugin/config/rig.json new file mode 100644 index 00000000000..d339847ee0a --- /dev/null +++ b/rush-plugins/rush-bridge-cache-plugin/config/rig.json @@ -0,0 +1,6 @@ +{ + // The "rig.json" file directs tools to look for their config files in an external package. + // Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + "rigPackageName": "local-node-rig" +} diff --git a/rush-plugins/rush-bridge-cache-plugin/package.json b/rush-plugins/rush-bridge-cache-plugin/package.json new file mode 100644 index 00000000000..30ee84502d4 --- /dev/null +++ b/rush-plugins/rush-bridge-cache-plugin/package.json @@ -0,0 +1,29 @@ +{ + "name": "@rushstack/rush-bridge-cache-plugin", + "version": "5.155.0", + "description": "Rush plugin that provides a --set-cache-only command flag to populate the cache from content on disk.", + "license": "MIT", + "main": "lib/index.js", + "repository": { + "url": "https://github.com/microsoft/rushstack", + "type": "git", + "directory": "rush-plugins/rush-bridge-cache-plugin" + }, + "homepage": "https://rushjs.io", + "types": "lib/index.d.ts", + "scripts": { + "build": "heft test --clean", + "start": "heft test --clean --watch", + "_phase:build": "heft run --only build -- --clean" + }, + "dependencies": { + "@rushstack/ts-command-line": "workspace:*", + "@rushstack/node-core-library": "workspace:*", + "@rushstack/rush-sdk": "workspace:*", + "@rushstack/terminal": "workspace:*" + }, + "devDependencies": { + "@rushstack/heft": "workspace:*", + "local-node-rig": "workspace:*" + } +} diff --git a/rush-plugins/rush-bridge-cache-plugin/rush-plugin-manifest.json b/rush-plugins/rush-bridge-cache-plugin/rush-plugin-manifest.json new file mode 100644 index 00000000000..c4e1818152b --- /dev/null +++ b/rush-plugins/rush-bridge-cache-plugin/rush-plugin-manifest.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush-plugin-manifest.schema.json", + "plugins": [ + { + "pluginName": "rush-bridge-cache-plugin", + "description": "Rush plugin that provides a --set-cache-only command flag to populate the cache from content on disk.", + "entryPoint": "./lib/index.js", + "optionsSchema": "lib/schemas/bridge-cache-config.schema.json" + } + ] +} diff --git a/rush-plugins/rush-bridge-cache-plugin/src/BridgeCachePlugin.ts b/rush-plugins/rush-bridge-cache-plugin/src/BridgeCachePlugin.ts new file mode 100644 index 00000000000..fa93ecf46d0 --- /dev/null +++ b/rush-plugins/rush-bridge-cache-plugin/src/BridgeCachePlugin.ts @@ -0,0 +1,136 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { Async } from '@rushstack/node-core-library'; +import { _OperationBuildCache as OperationBuildCache } from '@rushstack/rush-sdk'; +import type { + BuildCacheConfiguration, + ICreateOperationsContext, + IExecuteOperationsContext, + ILogger, + IOperationExecutionResult, + IPhasedCommand, + IRushPlugin, + Operation, + RushSession +} from '@rushstack/rush-sdk'; +import { CommandLineParameterKind } from '@rushstack/ts-command-line'; +import type { CommandLineParameter } from '@rushstack/ts-command-line'; + +const PLUGIN_NAME: 'RushBridgeCachePlugin' = 'RushBridgeCachePlugin'; + +export interface IBridgeCachePluginOptions { + readonly flagName: string; +} + +export class BridgeCachePlugin implements IRushPlugin { + public readonly pluginName: string = PLUGIN_NAME; + private readonly _flagName: string; + + public constructor(options: IBridgeCachePluginOptions) { + this._flagName = options.flagName; + + if (!this._flagName) { + throw new Error( + 'The "flagName" option must be provided for the BridgeCachePlugin. Please see the plugin README for details.' + ); + } + } + + public apply(session: RushSession): void { + session.hooks.runAnyPhasedCommand.tapPromise(PLUGIN_NAME, async (command: IPhasedCommand) => { + const logger: ILogger = session.getLogger(PLUGIN_NAME); + + // cancel the actual operations. We don't want to run the command, just cache the output folders on disk + command.hooks.createOperations.tap( + { name: PLUGIN_NAME, stage: Number.MAX_SAFE_INTEGER }, + (operations: Set, context: ICreateOperationsContext): Set => { + const flagValue: boolean = this._getFlagValue(context); + if (flagValue) { + for (const operation of operations) { + operation.enabled = false; + } + } + + return operations; + } + ); + + // populate the cache for each operation + command.hooks.beforeExecuteOperations.tap( + PLUGIN_NAME, + async ( + recordByOperation: Map, + context: IExecuteOperationsContext + ): Promise => { + if (!context.buildCacheConfiguration) { + return; + } + + const flagValue: boolean = this._getFlagValue(context); + if (flagValue) { + await this._setCacheAsync(logger, context.buildCacheConfiguration, recordByOperation); + } + } + ); + }); + } + + private _getFlagValue(context: IExecuteOperationsContext): boolean { + const flagParam: CommandLineParameter | undefined = context.customParameters.get(this._flagName); + if (flagParam) { + if (flagParam.kind !== CommandLineParameterKind.Flag) { + throw new Error( + `The parameter "${this._flagName}" must be a flag. Please check the plugin configuration.` + ); + } + + return flagParam.value; + } + + return false; + } + + private async _setCacheAsync( + { terminal }: ILogger, + buildCacheConfiguration: BuildCacheConfiguration, + recordByOperation: Map + ): Promise { + await Async.forEachAsync( + recordByOperation, + async ([ + { + associatedProject: { packageName }, + associatedPhase: { name: phaseName }, + isNoOp + }, + operationExecutionResult + ]) => { + if (isNoOp) { + return; + } + + const projectBuildCache: OperationBuildCache = OperationBuildCache.forOperation( + operationExecutionResult, + { + buildCacheConfiguration, + terminal + } + ); + + const success: boolean = await projectBuildCache.trySetCacheEntryAsync(terminal); + + if (success) { + terminal.writeLine( + `Cache entry set for ${phaseName} (${packageName}) from previously generated output folders` + ); + } else { + terminal.writeErrorLine( + `Error creating a cache entry set for ${phaseName} (${packageName}) from previously generated output folders` + ); + } + }, + { concurrency: 5 } + ); + } +} diff --git a/rush-plugins/rush-bridge-cache-plugin/src/index.ts b/rush-plugins/rush-bridge-cache-plugin/src/index.ts new file mode 100644 index 00000000000..01e83887250 --- /dev/null +++ b/rush-plugins/rush-bridge-cache-plugin/src/index.ts @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +export { BridgeCachePlugin as default } from './BridgeCachePlugin'; diff --git a/rush-plugins/rush-bridge-cache-plugin/src/schemas/bridge-cache-config.schema.json b/rush-plugins/rush-bridge-cache-plugin/src/schemas/bridge-cache-config.schema.json new file mode 100644 index 00000000000..ca46faa8f89 --- /dev/null +++ b/rush-plugins/rush-bridge-cache-plugin/src/schemas/bridge-cache-config.schema.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Configuration for bridge cache plugin", + "type": "object", + "oneOf": [ + { + "type": "object", + "required": ["flagName"], + "properties": { + "s3Endpoint": { + "type": "string", + "description": "(Required) The name of the flag used to trigger this plugin on your phased commands." + } + } + } + ] +} diff --git a/rush-plugins/rush-bridge-cache-plugin/tsconfig.json b/rush-plugins/rush-bridge-cache-plugin/tsconfig.json new file mode 100644 index 00000000000..dac21d04081 --- /dev/null +++ b/rush-plugins/rush-bridge-cache-plugin/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./node_modules/local-node-rig/profiles/default/tsconfig-base.json" +} diff --git a/rush-plugins/rush-buildxl-graph-plugin/package.json b/rush-plugins/rush-buildxl-graph-plugin/package.json index 0d35019610a..910f2b85397 100644 --- a/rush-plugins/rush-buildxl-graph-plugin/package.json +++ b/rush-plugins/rush-buildxl-graph-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@rushstack/rush-buildxl-graph-plugin", - "version": "5.153.2", + "version": "5.155.0", "description": "Rush plugin for generating a BuildXL graph.", "repository": { "type": "git", diff --git a/rush-plugins/rush-http-build-cache-plugin/package.json b/rush-plugins/rush-http-build-cache-plugin/package.json index 4f24a59192a..3b0f8d4b557 100644 --- a/rush-plugins/rush-http-build-cache-plugin/package.json +++ b/rush-plugins/rush-http-build-cache-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@rushstack/rush-http-build-cache-plugin", - "version": "5.153.2", + "version": "5.155.0", "description": "Rush plugin for generic HTTP cloud build cache", "repository": { "type": "git", diff --git a/rush-plugins/rush-mcp-docs-plugin/.eslintrc.js b/rush-plugins/rush-mcp-docs-plugin/.eslintrc.js new file mode 100644 index 00000000000..de794c04ae0 --- /dev/null +++ b/rush-plugins/rush-mcp-docs-plugin/.eslintrc.js @@ -0,0 +1,13 @@ +// This is a workaround for https://github.com/eslint/eslint/issues/3458 +require('local-eslint-config/patch/modern-module-resolution'); +// This is a workaround for https://github.com/microsoft/rushstack/issues/3021 +require('local-eslint-config/patch/custom-config-package-names'); + +module.exports = { + extends: [ + 'local-eslint-config/profile/node', + 'local-eslint-config/mixins/friendly-locals', + 'local-eslint-config/mixins/tsdoc' + ], + parserOptions: { tsconfigRootDir: __dirname } +}; diff --git a/rush-plugins/rush-mcp-docs-plugin/.npmignore b/rush-plugins/rush-mcp-docs-plugin/.npmignore new file mode 100644 index 00000000000..c033d1b3561 --- /dev/null +++ b/rush-plugins/rush-mcp-docs-plugin/.npmignore @@ -0,0 +1,37 @@ +# THIS IS A STANDARD TEMPLATE FOR .npmignore FILES IN THIS REPO. + +# Ignore all files by default, to avoid accidentally publishing unintended files. +* + +# Use negative patterns to bring back the specific things we want to publish. +!/bin/** +!/lib/** +!/lib-*/** +!/dist/** + +!CHANGELOG.md +!CHANGELOG.json +!heft-plugin.json +!rush-plugin-manifest.json +!ThirdPartyNotice.txt + +# Ignore certain patterns that should not get published. +/dist/*.stats.* +/lib/**/test/ +/lib-*/**/test/ +*.test.js + +# NOTE: These don't need to be specified, because NPM includes them automatically. +# +# package.json +# README.md +# LICENSE + +# --------------------------------------------------------------------------- +# DO NOT MODIFY ABOVE THIS LINE! Add any project-specific overrides below. +# --------------------------------------------------------------------------- + +!rush-mcp-plugin.json +!*.schema.json + +!*.mock.json diff --git a/rush-plugins/rush-mcp-docs-plugin/CHANGELOG.json b/rush-plugins/rush-mcp-docs-plugin/CHANGELOG.json new file mode 100644 index 00000000000..d746b30ef7a --- /dev/null +++ b/rush-plugins/rush-mcp-docs-plugin/CHANGELOG.json @@ -0,0 +1,22 @@ +{ + "name": "@rushstack/rush-mcp-docs-plugin", + "entries": [ + { + "version": "0.1.0", + "tag": "@rushstack/rush-mcp-docs-plugin_v0.1.0", + "date": "Sat, 14 Jun 2025 00:11:30 GMT", + "comments": { + "minor": [ + { + "comment": "Add rush docs mcp plugin" + } + ], + "dependency": [ + { + "comment": "Updating dependency \"@rushstack/mcp-server\" to `0.2.2`" + } + ] + } + } + ] +} diff --git a/rush-plugins/rush-mcp-docs-plugin/CHANGELOG.md b/rush-plugins/rush-mcp-docs-plugin/CHANGELOG.md new file mode 100644 index 00000000000..5ffb12619f3 --- /dev/null +++ b/rush-plugins/rush-mcp-docs-plugin/CHANGELOG.md @@ -0,0 +1,11 @@ +# Change Log - @rushstack/rush-mcp-docs-plugin + +This log was last generated on Sat, 14 Jun 2025 00:11:30 GMT and should not be manually modified. + +## 0.1.0 +Sat, 14 Jun 2025 00:11:30 GMT + +### Minor changes + +- Add rush docs mcp plugin + diff --git a/rush-plugins/rush-mcp-docs-plugin/LICENSE b/rush-plugins/rush-mcp-docs-plugin/LICENSE new file mode 100644 index 00000000000..5c060c96bc4 --- /dev/null +++ b/rush-plugins/rush-mcp-docs-plugin/LICENSE @@ -0,0 +1,24 @@ +@rushstack/rush-mcp-docs-plugin + +Copyright (c) Microsoft Corporation. All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/rush-plugins/rush-mcp-docs-plugin/README.md b/rush-plugins/rush-mcp-docs-plugin/README.md new file mode 100644 index 00000000000..84bcc9f4976 --- /dev/null +++ b/rush-plugins/rush-mcp-docs-plugin/README.md @@ -0,0 +1,3 @@ +# @rushstack/rush-mcp-docs-plugin + +The @rushstack/rush-mcp-docs-plugin package provides a documentation query tool for Rush. diff --git a/rush-plugins/rush-mcp-docs-plugin/config/rig.json b/rush-plugins/rush-mcp-docs-plugin/config/rig.json new file mode 100644 index 00000000000..165ffb001f5 --- /dev/null +++ b/rush-plugins/rush-mcp-docs-plugin/config/rig.json @@ -0,0 +1,7 @@ +{ + // The "rig.json" file directs tools to look for their config files in an external package. + // Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + + "rigPackageName": "local-node-rig" +} diff --git a/rush-plugins/rush-mcp-docs-plugin/package.json b/rush-plugins/rush-mcp-docs-plugin/package.json new file mode 100644 index 00000000000..57cf9a7d200 --- /dev/null +++ b/rush-plugins/rush-mcp-docs-plugin/package.json @@ -0,0 +1,28 @@ +{ + "name": "@rushstack/rush-mcp-docs-plugin", + "version": "0.1.0", + "description": "Docs plugin for @rushstack/mcp-server", + "repository": { + "url": "https://github.com/microsoft/rushstack.git", + "type": "git", + "directory": "rush-plugins/rush-mcp-docs-plugin" + }, + "homepage": "https://rushjs.io", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "license": "MIT", + "scripts": { + "build": "heft build --clean", + "_phase:build": "heft run --only build -- --clean", + "_phase:test": "heft run --only test -- --clean" + }, + "dependencies": { + "@rushstack/node-core-library": "workspace:*", + "@rushstack/mcp-server": "workspace:*" + }, + "devDependencies": { + "@rushstack/heft": "workspace:*", + "local-node-rig": "workspace:*", + "local-eslint-config": "workspace:*" + } +} diff --git a/rush-plugins/rush-mcp-docs-plugin/rush-mcp-plugin.json b/rush-plugins/rush-mcp-docs-plugin/rush-mcp-plugin.json new file mode 100644 index 00000000000..61d2750e169 --- /dev/null +++ b/rush-plugins/rush-mcp-docs-plugin/rush-mcp-plugin.json @@ -0,0 +1,24 @@ +/** + * Every plugin package must contain a "rush-mcp-plugin.json" manifest in the top-level folder + * (next to package.json). + */ +{ + /** + * A name that uniquely identifies your plugin. Generally this should be the same name as + * the NPM package. If two NPM packages have the same pluginName, they cannot be loaded together. + */ + "pluginName": "rush-mcp-docs-plugin", + + /** + * (OPTIONAL) Indicates that your plugin accepts a config file. The MCP server will load this + * file and provide it to the plugin. + * + * The config file path will be `/common/config/rush-mcp/.json`. + */ + // "configFileSchema": "./lib/rush-mcp-example-plugin.schema.json", + + /** + * The entry point, whose default export should be a class that implements + */ + "entryPoint": "./lib/index.js" +} diff --git a/rush-plugins/rush-mcp-docs-plugin/src/DocsPlugin.ts b/rush-plugins/rush-mcp-docs-plugin/src/DocsPlugin.ts new file mode 100644 index 00000000000..5d12e5ea1bd --- /dev/null +++ b/rush-plugins/rush-mcp-docs-plugin/src/DocsPlugin.ts @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import type { IRushMcpPlugin, RushMcpPluginSession } from '@rushstack/mcp-server'; +import { DocsTool } from './DocsTool'; + +export interface IDocsPluginConfigFile {} + +export class DocsPlugin implements IRushMcpPlugin { + public session: RushMcpPluginSession; + public configFile: IDocsPluginConfigFile | undefined = undefined; + + public constructor(session: RushMcpPluginSession, configFile: IDocsPluginConfigFile | undefined) { + this.session = session; + this.configFile = configFile; + } + + public async onInitializeAsync(): Promise { + this.session.registerTool( + { + toolName: 'rush_docs', + description: + 'Search and retrieve relevant sections from the official Rush documentation based on user queries.' + }, + new DocsTool(this) + ); + } +} diff --git a/apps/rush-mcp-server/src/tools/docs.tool.ts b/rush-plugins/rush-mcp-docs-plugin/src/DocsTool.ts similarity index 52% rename from apps/rush-mcp-server/src/tools/docs.tool.ts rename to rush-plugins/rush-mcp-docs-plugin/src/DocsTool.ts index 7c157877b3f..cc8ecd2c064 100644 --- a/apps/rush-mcp-server/src/tools/docs.tool.ts +++ b/rush-plugins/rush-mcp-docs-plugin/src/DocsTool.ts @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import { z } from 'zod'; +import type { IRushMcpTool, RushMcpPluginSession, CallToolResult, zodModule } from '@rushstack/mcp-server'; import { JsonFile } from '@rushstack/node-core-library'; import path from 'path'; -import { BaseTool, type CallToolResult } from './base.tool'; +import type { DocsPlugin } from './DocsPlugin'; interface IDocsResult { query: string; @@ -17,15 +17,22 @@ interface IDocsResult { searchTimeMs: number; } -export class RushDocsTool extends BaseTool { - public constructor() { - super({ - name: 'rush_docs', - description: - 'Search and retrieve relevant sections from the official Rush documentation based on user queries.', - schema: { - userQuery: z.string().describe('The user query to search for relevant documentation sections.') - } +export class DocsTool implements IRushMcpTool { + public readonly plugin: DocsPlugin; + public readonly session: RushMcpPluginSession; + + public constructor(plugin: DocsPlugin) { + this.plugin = plugin; + this.session = plugin.session; + } + + // ZOD relies on type inference generate a messy expression in the .d.ts file + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + public get schema() { + const zod: typeof zodModule = this.session.zod; + + return zod.object({ + userQuery: zod.string().describe('The user query to search for relevant documentation sections.') }); } @@ -34,7 +41,7 @@ export class RushDocsTool extends BaseTool { const startTime: number = Date.now(); const results: IDocsResult['results'] = JsonFile.load( - path.join(__dirname, '../rush-doc-fragment.mock.json') + path.join(__dirname, './rush-doc-fragment.mock.json') ); return { @@ -45,7 +52,7 @@ export class RushDocsTool extends BaseTool { }; } - public async executeAsync({ userQuery }: { userQuery: string }): Promise { + public async executeAsync({ userQuery }: zodModule.infer): Promise { const docSearchResult: IDocsResult = this._searchDocs(userQuery); return { diff --git a/rush-plugins/rush-mcp-docs-plugin/src/index.ts b/rush-plugins/rush-mcp-docs-plugin/src/index.ts new file mode 100644 index 00000000000..569aef9dd9f --- /dev/null +++ b/rush-plugins/rush-mcp-docs-plugin/src/index.ts @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import type { RushMcpPluginSession, RushMcpPluginFactory } from '@rushstack/mcp-server'; +import { DocsPlugin, type IDocsPluginConfigFile } from './DocsPlugin'; + +function createPlugin( + session: RushMcpPluginSession, + configFile: IDocsPluginConfigFile | undefined +): DocsPlugin { + return new DocsPlugin(session, configFile); +} + +export default createPlugin satisfies RushMcpPluginFactory; diff --git a/apps/rush-mcp-server/rush-doc-fragment.mock.json b/rush-plugins/rush-mcp-docs-plugin/src/rush-doc-fragment.mock.json similarity index 100% rename from apps/rush-mcp-server/rush-doc-fragment.mock.json rename to rush-plugins/rush-mcp-docs-plugin/src/rush-doc-fragment.mock.json diff --git a/rush-plugins/rush-mcp-docs-plugin/tsconfig.json b/rush-plugins/rush-mcp-docs-plugin/tsconfig.json new file mode 100644 index 00000000000..dac21d04081 --- /dev/null +++ b/rush-plugins/rush-mcp-docs-plugin/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./node_modules/local-node-rig/profiles/default/tsconfig-base.json" +} diff --git a/rush-plugins/rush-redis-cobuild-plugin/package.json b/rush-plugins/rush-redis-cobuild-plugin/package.json index b8c38a9bdf7..d0942dea146 100644 --- a/rush-plugins/rush-redis-cobuild-plugin/package.json +++ b/rush-plugins/rush-redis-cobuild-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@rushstack/rush-redis-cobuild-plugin", - "version": "5.153.2", + "version": "5.155.0", "description": "Rush plugin for Redis cobuild lock", "repository": { "type": "git", diff --git a/rush-plugins/rush-resolver-cache-plugin/package.json b/rush-plugins/rush-resolver-cache-plugin/package.json index 8ef82567f5e..612d61a5312 100644 --- a/rush-plugins/rush-resolver-cache-plugin/package.json +++ b/rush-plugins/rush-resolver-cache-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@rushstack/rush-resolver-cache-plugin", - "version": "5.153.2", + "version": "5.155.0", "description": "A Rush plugin that generates a resolver cache file after successful install.", "license": "MIT", "repository": { diff --git a/rush-plugins/rush-serve-plugin/package.json b/rush-plugins/rush-serve-plugin/package.json index 91ae7ebe4fc..3bcc3237549 100644 --- a/rush-plugins/rush-serve-plugin/package.json +++ b/rush-plugins/rush-serve-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@rushstack/rush-serve-plugin", - "version": "5.153.2", + "version": "5.155.0", "description": "A Rush plugin that hooks into a rush action and serves output folders from all projects in the repository.", "license": "MIT", "repository": { diff --git a/rush.json b/rush.json index 3d8f5018a1e..5968af8b38f 100644 --- a/rush.json +++ b/rush.json @@ -660,6 +660,12 @@ "reviewCategory": "tests", "shouldPublish": false }, + { + "packageName": "rush-mcp-example-plugin", + "projectFolder": "build-tests/rush-mcp-example-plugin", + "reviewCategory": "tests", + "shouldPublish": false + }, { "packageName": "run-scenarios-helpers", "projectFolder": "build-tests/run-scenarios-helpers", @@ -1322,6 +1328,12 @@ "reviewCategory": "libraries", "versionPolicyName": "rush" }, + { + "packageName": "@rushstack/rush-bridge-cache-plugin", + "projectFolder": "rush-plugins/rush-bridge-cache-plugin", + "reviewCategory": "libraries", + "versionPolicyName": "rush" + }, // "vscode-extensions" folder (alphabetical order) { @@ -1420,6 +1432,12 @@ "projectFolder": "webpack/webpack5-localization-plugin", "reviewCategory": "libraries", "shouldPublish": true + }, + { + "packageName": "@rushstack/rush-mcp-docs-plugin", + "projectFolder": "rush-plugins/rush-mcp-docs-plugin", + "reviewCategory": "libraries", + "shouldPublish": true } ] }