diff --git a/README.md b/README.md index e49d4c1c729..001b4c7d696 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,7 @@ These GitHub repositories provide supplementary resources for Rush Stack: | [/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-published-versions-json-plugin](./rush-plugins/rush-published-versions-json-plugin/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Frush-published-versions-json-plugin.svg)](https://badge.fury.io/js/%40rushstack%2Frush-published-versions-json-plugin) | [changelog](./rush-plugins/rush-published-versions-json-plugin/CHANGELOG.md) | [@rushstack/rush-published-versions-json-plugin](https://www.npmjs.com/package/@rushstack/rush-published-versions-json-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) | diff --git a/common/changes/@microsoft/rush/rush-published-versions-plugin_2026-03-15-04-22.json b/common/changes/@microsoft/rush/rush-published-versions-plugin_2026-03-15-04-22.json new file mode 100644 index 00000000000..ba6f7227095 --- /dev/null +++ b/common/changes/@microsoft/rush/rush-published-versions-plugin_2026-03-15-04-22.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Add `customParametersByLongName` and `setHandled()` to `IGlobalCommand`, enabling Rush plugins to implement native global commands. Plugins can now define global commands with an empty `shellCommand`, handle execution via the `runGlobalCustomCommand` hook, and access parsed command-line parameters.", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file diff --git a/common/changes/@rushstack/rush-published-versions-json-plugin/rush-published-versions-plugin_2026-03-15-04-27.json b/common/changes/@rushstack/rush-published-versions-json-plugin/rush-published-versions-plugin_2026-03-15-04-27.json new file mode 100644 index 00000000000..9a39d326bf4 --- /dev/null +++ b/common/changes/@rushstack/rush-published-versions-json-plugin/rush-published-versions-plugin_2026-03-15-04-27.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/rush-published-versions-json-plugin", + "comment": "Initial release.", + "type": "minor" + } + ], + "packageName": "@rushstack/rush-published-versions-json-plugin" +} \ No newline at end of file diff --git a/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml b/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml index fbce8df8843..4acd9ce2d55 100644 --- a/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml +++ b/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml @@ -836,7 +836,7 @@ packages: '@rushstack/heft-api-extractor-plugin@file:../../../heft-plugins/heft-api-extractor-plugin': resolution: {directory: ../../../heft-plugins/heft-api-extractor-plugin, type: directory} peerDependencies: - '@rushstack/heft': 1.2.6 + '@rushstack/heft': 1.2.7 '@rushstack/heft-config-file@file:../../../libraries/heft-config-file': resolution: {directory: ../../../libraries/heft-config-file, type: directory} @@ -845,7 +845,7 @@ packages: '@rushstack/heft-jest-plugin@file:../../../heft-plugins/heft-jest-plugin': resolution: {directory: ../../../heft-plugins/heft-jest-plugin, type: directory} peerDependencies: - '@rushstack/heft': ^1.2.6 + '@rushstack/heft': ^1.2.7 jest-environment-jsdom: ^29.5.0 jest-environment-node: ^29.5.0 peerDependenciesMeta: @@ -857,17 +857,17 @@ packages: '@rushstack/heft-lint-plugin@file:../../../heft-plugins/heft-lint-plugin': resolution: {directory: ../../../heft-plugins/heft-lint-plugin, type: directory} peerDependencies: - '@rushstack/heft': 1.2.6 + '@rushstack/heft': 1.2.7 '@rushstack/heft-node-rig@file:../../../rigs/heft-node-rig': resolution: {directory: ../../../rigs/heft-node-rig, type: directory} peerDependencies: - '@rushstack/heft': ^1.2.6 + '@rushstack/heft': ^1.2.7 '@rushstack/heft-typescript-plugin@file:../../../heft-plugins/heft-typescript-plugin': resolution: {directory: ../../../heft-plugins/heft-typescript-plugin, type: directory} peerDependencies: - '@rushstack/heft': 1.2.6 + '@rushstack/heft': 1.2.7 '@rushstack/heft@file:../../../apps/heft': resolution: {directory: ../../../apps/heft, type: directory} diff --git a/common/config/subspaces/build-tests-subspace/repo-state.json b/common/config/subspaces/build-tests-subspace/repo-state.json index 8d3940352aa..0b3d7c37322 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": "c395a90b30bd67a31beb1d1b08be9aecb02de265", + "pnpmShrinkwrapHash": "58bd145b905637c52bb733fb9455b6d83e3a890e", "preferredVersionsHash": "550b4cee0bef4e97db6c6aad726df5149d20e7d9", "packageJsonInjectedDependenciesHash": "fa90a0a032a0046e646e8751bbc6d0be86a4dda1" } diff --git a/common/config/subspaces/default/pnpm-lock.yaml b/common/config/subspaces/default/pnpm-lock.yaml index dd1378b412d..1577d5d8e87 100644 --- a/common/config/subspaces/default/pnpm-lock.yaml +++ b/common/config/subspaces/default/pnpm-lock.yaml @@ -5089,6 +5089,31 @@ importers: specifier: workspace:* version: link:../../rigs/local-node-rig + ../../../rush-plugins/rush-published-versions-json-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 + devDependencies: + '@rushstack/heft': + specifier: workspace:* + version: link:../../apps/heft + '@rushstack/ts-command-line': + specifier: workspace:* + version: link:../../libraries/ts-command-line + eslint: + specifier: ~9.37.0 + version: 9.37.0 + local-node-rig: + specifier: workspace:* + version: link:../../rigs/local-node-rig + ../../../rush-plugins/rush-redis-cobuild-plugin: dependencies: '@redis/client': diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index e7004ddbbc2..57fd0c1191a 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -512,6 +512,8 @@ export interface IGetChangedProjectsOptions { // @beta export interface IGlobalCommand extends IRushCommand { + getCustomParametersByLongName(longName: string): TParameter; + setHandled(): void; } // @public @@ -1500,7 +1502,7 @@ export class RushLifecycleHooks { variant: string | undefined ]>; readonly beforeInstall: AsyncSeriesHook<[ - command: IGlobalCommand, + command: IRushCommand, subspace: Subspace, variant: string | undefined ]>; diff --git a/libraries/rush-lib/src/cli/scriptActions/GlobalScriptAction.ts b/libraries/rush-lib/src/cli/scriptActions/GlobalScriptAction.ts index 9b35156f188..920ff0d4ba9 100644 --- a/libraries/rush-lib/src/cli/scriptActions/GlobalScriptAction.ts +++ b/libraries/rush-lib/src/cli/scriptActions/GlobalScriptAction.ts @@ -5,6 +5,7 @@ import * as path from 'node:path'; import type { AsyncSeriesHook } from 'tapable'; +import type { CommandLineParameter } from '@rushstack/ts-command-line'; import { FileSystem, type IPackageJson, @@ -45,6 +46,9 @@ export class GlobalScriptAction extends BaseScriptAction { private readonly _autoinstallerName: string; private readonly _autoinstallerFullPath: string; + private _customParametersByLongName: ReadonlyMap | undefined; + private _isHandled: boolean = false; + public constructor(options: IGlobalScriptActionOptions) { super(options); this._shellCommand = options.shellCommand; @@ -93,6 +97,37 @@ export class GlobalScriptAction extends BaseScriptAction { this.defineScriptParameters(); } + /** + * {@inheritDoc IGlobalCommand.setHandled} + */ + public setHandled(): void { + this._isHandled = true; + } + + /** + * {@inheritDoc IGlobalCommand.getCustomParametersByLongName} + */ + public getCustomParametersByLongName( + longName: string + ): TParameter { + if (!this._customParametersByLongName) { + const map: Map = new Map(); + for (const [parameterJson, parameter] of this.customParameters) { + map.set(parameterJson.longName, parameter); + } + this._customParametersByLongName = map; + } + + const parameter: CommandLineParameter | undefined = this._customParametersByLongName.get(longName); + if (!parameter) { + throw new Error( + `The command "${this.actionName}" does not have a custom parameter with long name "${longName}".` + ); + } + + return parameter as TParameter; + } + private async _prepareAutoinstallerNameAsync(): Promise { const autoInstaller: Autoinstaller = new Autoinstaller({ autoinstallerName: this._autoinstallerName, @@ -117,6 +152,20 @@ export class GlobalScriptAction extends BaseScriptAction { await hookForAction.promise(this); } + // If a plugin hook called setHandled(), the command has been fully handled. + // Skip the default shell command execution. + if (this._isHandled) { + return; + } + + if (this._shellCommand === '') { + throw new Error( + `The custom command "${this.actionName}" has an empty "shellCommand" value, but no plugin ` + + 'called setHandled() for this command. An empty "shellCommand" is intended for global ' + + 'commands whose implementation is provided entirely by a Rush plugin.' + ); + } + const additionalPathFolders: string[] = this.commandLineConfiguration?.additionalPathFolders.slice() || []; diff --git a/libraries/rush-lib/src/pluginFramework/RushLifeCycle.ts b/libraries/rush-lib/src/pluginFramework/RushLifeCycle.ts index 76e51d8e17d..d5f68d63ffa 100644 --- a/libraries/rush-lib/src/pluginFramework/RushLifeCycle.ts +++ b/libraries/rush-lib/src/pluginFramework/RushLifeCycle.ts @@ -3,6 +3,8 @@ import { AsyncParallelHook, AsyncSeriesHook, HookMap } from 'tapable'; +import type { CommandLineParameter } from '@rushstack/ts-command-line'; + import type { ITelemetryData } from '../logic/Telemetry'; import type { PhasedCommandHooks } from './PhasedCommandHooks'; import type { Subspace } from '../api/Subspace'; @@ -23,7 +25,17 @@ export interface IRushCommand { * @beta */ export interface IGlobalCommand extends IRushCommand { - // Nothing added. + /** + * Get a parameter by its long name (e.g. "--output-path") that was defined in command-line.json for this command. + * If the parameter was not defined or not provided on the command line, this will throw. + */ + getCustomParametersByLongName(longName: string): TParameter; + + /** + * Call this from a plugin hook to indicate that the command has been fully handled + * by the plugin. When set, the default shell command execution will be skipped. + */ + setHandled(): void; } /** @@ -94,7 +106,7 @@ export class RushLifecycleHooks { * The hook to run between preparing the common/temp folder and invoking the package manager during "rush install" or "rush update". */ public readonly beforeInstall: AsyncSeriesHook< - [command: IGlobalCommand, subspace: Subspace, variant: string | undefined] + [command: IRushCommand, subspace: Subspace, variant: string | undefined] > = new AsyncSeriesHook(['command', 'subspace', 'variant'], 'beforeInstall'); /** diff --git a/rush-plugins/rush-published-versions-json-plugin/.npmignore b/rush-plugins/rush-published-versions-json-plugin/.npmignore new file mode 100644 index 00000000000..f7a40e10213 --- /dev/null +++ b/rush-plugins/rush-published-versions-json-plugin/.npmignore @@ -0,0 +1,36 @@ +# 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/** +!/includes/** + +!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 +*.test.[cm]js +*.test.d.ts +*.test.d.[cm]ts + +# 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. +# --------------------------------------------------------------------------- diff --git a/rush-plugins/rush-published-versions-json-plugin/LICENSE b/rush-plugins/rush-published-versions-json-plugin/LICENSE new file mode 100644 index 00000000000..da9e82e6c70 --- /dev/null +++ b/rush-plugins/rush-published-versions-json-plugin/LICENSE @@ -0,0 +1,24 @@ +@rushstack/rush-published-versions-json-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-published-versions-json-plugin/README.md b/rush-plugins/rush-published-versions-json-plugin/README.md new file mode 100644 index 00000000000..bf975210cf7 --- /dev/null +++ b/rush-plugins/rush-published-versions-json-plugin/README.md @@ -0,0 +1,71 @@ +# @rushstack/rush-published-versions-json-plugin + +A Rush plugin that generates a JSON file recording the version numbers of all published packages in a Rush monorepo. + +## Installation + +1. Add the plugin package to an autoinstaller (e.g. `common/autoinstallers/rush-plugins/package.json`): + + ``` + rush init-autoinstaller --name rush-plugins + ``` + + ```bash + cd common/autoinstallers/rush-plugins + pnpm add @rushstack/rush-published-versions-json-plugin + rush update-autoinstaller --name rush-plugins + ``` + +2. Register the plugin in `common/config/rush/rush-plugins.json`: + + ```json + { + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush-plugins.schema.json", + "plugins": [ + { + "packageName": "@rushstack/rush-published-versions-json-plugin", + "pluginName": "rush-published-versions-json-plugin", + "autoinstallerName": "rush-plugins" + } + ] + } + ``` + +3. Run `rush update` to install the plugin. + +## Usage + +```bash +rush record-published-versions --output-path +``` + +### Parameters + +| Parameter | Short | Required | Description | +| --- | --- | --- | --- | +| `--output-path` | `-o` | Yes | The path to the output JSON file. Relative paths are resolved from the repo root. | + +### Example + +```bash +rush record-published-versions --output-path common/config/published-versions.json +``` + +### Output format + +The output is a JSON object mapping published package names to their current versions: + +```json +{ + "@my-scope/my-library": "1.2.3", + "@my-scope/my-app": "0.5.0" +} +``` + +A package is included if it has `shouldPublish` set to `true` or has a `versionPolicy` assigned in +**rush.json**. + +## Links + +- [CHANGELOG.md](./CHANGELOG.md) +- [Rush: Using rush plugins](https://rushjs.io/pages/maintainer/using_rush_plugins/) diff --git a/rush-plugins/rush-published-versions-json-plugin/command-line.json b/rush-plugins/rush-published-versions-json-plugin/command-line.json new file mode 100644 index 00000000000..212a0ad8b6b --- /dev/null +++ b/rush-plugins/rush-published-versions-json-plugin/command-line.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/command-line.schema.json", + "commands": [ + { + "commandKind": "global", + "name": "record-published-versions", + "summary": "Generates a JSON file recording the version numbers of all published packages.", + "safeForSimultaneousRushProcesses": true, + // The command is handled by the plugin, so we don't want Rush to attempt to execute anything. + "shellCommand": "" + } + ], + "parameters": [ + { + "parameterKind": "string", + "longName": "--output-path", + "shortName": "-o", + "argumentName": "FILE_PATH", + "description": "The path to the output JSON file. Relative paths are resolved from the repo root.", + "associatedCommands": ["record-published-versions"], + "required": true + } + ] +} diff --git a/rush-plugins/rush-published-versions-json-plugin/config/rig.json b/rush-plugins/rush-published-versions-json-plugin/config/rig.json new file mode 100644 index 00000000000..165ffb001f5 --- /dev/null +++ b/rush-plugins/rush-published-versions-json-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-published-versions-json-plugin/eslint.config.js b/rush-plugins/rush-published-versions-json-plugin/eslint.config.js new file mode 100644 index 00000000000..87132f43292 --- /dev/null +++ b/rush-plugins/rush-published-versions-json-plugin/eslint.config.js @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +const nodeProfile = require('local-node-rig/profiles/default/includes/eslint/flat/profile/node'); +const friendlyLocalsMixin = require('local-node-rig/profiles/default/includes/eslint/flat/mixins/friendly-locals'); +const tsdocMixin = require('local-node-rig/profiles/default/includes/eslint/flat/mixins/tsdoc'); + +module.exports = [ + ...nodeProfile, + ...friendlyLocalsMixin, + ...tsdocMixin, + { + files: ['**/*.ts', '**/*.tsx'], + languageOptions: { + parserOptions: { + tsconfigRootDir: __dirname + } + } + } +]; diff --git a/rush-plugins/rush-published-versions-json-plugin/package.json b/rush-plugins/rush-published-versions-json-plugin/package.json new file mode 100644 index 00000000000..6ef2132d72c --- /dev/null +++ b/rush-plugins/rush-published-versions-json-plugin/package.json @@ -0,0 +1,49 @@ +{ + "name": "@rushstack/rush-published-versions-json-plugin", + "version": "0.0.0", + "description": "A Rush plugin for generating a JSON file containing the versions of all published packages in the monorepo.", + "license": "MIT", + "repository": { + "url": "https://github.com/microsoft/rushstack.git", + "type": "git", + "directory": "rush-plugins/rush-published-versions-json-plugin" + }, + "scripts": { + "build": "heft test --clean", + "_phase:build": "heft run --only build -- --clean" + }, + "dependencies": { + "@rushstack/node-core-library": "workspace:*", + "@rushstack/rush-sdk": "workspace:*", + "@rushstack/terminal": "workspace:*" + }, + "devDependencies": { + "@rushstack/heft": "workspace:*", + "@rushstack/ts-command-line": "workspace:*", + "eslint": "~9.37.0", + "local-node-rig": "workspace:*" + }, + "main": "./lib-commonjs/index.js", + "module": "./lib-esm/index.js", + "types": "./lib-dts/index.d.ts", + "exports": { + ".": { + "types": "./lib-dts/index.d.ts", + "import": "./lib-esm/index.js", + "require": "./lib-commonjs/index.js" + }, + "./lib/*": { + "types": "./lib-dts/*.d.ts", + "import": "./lib-esm/*.js", + "require": "./lib-commonjs/*.js" + }, + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "lib/*": [ + "lib-dts/*" + ] + } + } +} diff --git a/rush-plugins/rush-published-versions-json-plugin/rush-plugin-manifest.json b/rush-plugins/rush-published-versions-json-plugin/rush-plugin-manifest.json new file mode 100644 index 00000000000..03631898b98 --- /dev/null +++ b/rush-plugins/rush-published-versions-json-plugin/rush-plugin-manifest.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush-plugin-manifest.schema.json", + "plugins": [ + { + "pluginName": "rush-published-versions-json-plugin", + "description": "A Rush plugin for generating a JSON file containing the versions of all published packages in the monorepo.", + "entryPoint": "./lib-commonjs/index.js", + "associatedCommands": ["record-published-versions"], + "commandLineJsonFilePath": "./command-line.json" + } + ] +} diff --git a/rush-plugins/rush-published-versions-json-plugin/src/PublishedVersionsJsonPlugin.ts b/rush-plugins/rush-published-versions-json-plugin/src/PublishedVersionsJsonPlugin.ts new file mode 100644 index 00000000000..6cba126c385 --- /dev/null +++ b/rush-plugins/rush-published-versions-json-plugin/src/PublishedVersionsJsonPlugin.ts @@ -0,0 +1,62 @@ +// 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 'node:path'; + +import type { IRequiredCommandLineStringParameter } from '@rushstack/ts-command-line'; +import { JsonFile } from '@rushstack/node-core-library'; +import type { + IGlobalCommand, + ILogger, + IRushPlugin, + RushConfiguration, + RushSession +} from '@rushstack/rush-sdk'; + +const PLUGIN_NAME: 'PublishedVersionsJsonPlugin' = 'PublishedVersionsJsonPlugin'; + +/** + * A Rush plugin for generating a JSON file containing the versions of all published packages in the monorepo. + * @public + */ +export class PublishedVersionsJsonPlugin implements IRushPlugin { + public readonly pluginName: string = PLUGIN_NAME; + + public apply(session: RushSession, rushConfiguration: RushConfiguration): void { + session.hooks.runGlobalCustomCommand + .for('record-published-versions') + .tapPromise(PLUGIN_NAME, async (command: IGlobalCommand) => { + if (typeof command.setHandled !== 'function') { + throw new Error( + `${PLUGIN_NAME} requires Rush version 5.171.0 or newer. ` + + 'Please upgrade your Rush installation.' + ); + } + command.setHandled(); + + const { terminal }: ILogger = session.getLogger(PLUGIN_NAME); + + const outputPathParameter: IRequiredCommandLineStringParameter = + command.getCustomParametersByLongName('--output-path'); + + const publishedPackageVersions: Record = {}; + for (const { + shouldPublish, + packageName, + packageJson: { version } + } of rushConfiguration.projects) { + if (shouldPublish) { + // Note that `shouldPublish` is also `true` when publishing is driven by a version policy. + publishedPackageVersions[packageName] = version; + } + } + + const resolvedOutputPath: string = path.resolve(process.cwd(), outputPathParameter.value); + await JsonFile.saveAsync(publishedPackageVersions, resolvedOutputPath, { + ensureFolderExists: true + }); + + terminal.writeLine(`Wrote file to ${resolvedOutputPath}`); + }); + } +} diff --git a/rush-plugins/rush-published-versions-json-plugin/src/index.ts b/rush-plugins/rush-published-versions-json-plugin/src/index.ts new file mode 100644 index 00000000000..7b2de678a0e --- /dev/null +++ b/rush-plugins/rush-published-versions-json-plugin/src/index.ts @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { PublishedVersionsJsonPlugin } from './PublishedVersionsJsonPlugin'; +export default PublishedVersionsJsonPlugin; diff --git a/rush-plugins/rush-published-versions-json-plugin/tsconfig.json b/rush-plugins/rush-published-versions-json-plugin/tsconfig.json new file mode 100644 index 00000000000..dac21d04081 --- /dev/null +++ b/rush-plugins/rush-published-versions-json-plugin/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./node_modules/local-node-rig/profiles/default/tsconfig-base.json" +} diff --git a/rush.json b/rush.json index c1aef29a869..2edd7ac5ed7 100644 --- a/rush.json +++ b/rush.json @@ -1588,6 +1588,12 @@ "reviewCategory": "libraries", "shouldPublish": true }, + { + "packageName": "@rushstack/rush-published-versions-json-plugin", + "projectFolder": "rush-plugins/rush-published-versions-json-plugin", + "reviewCategory": "libraries", + "shouldPublish": true + }, { "packageName": "@rushstack/rush-pnpm-kit-v8", "projectFolder": "libraries/rush-pnpm-kit-v8",