From 305adce878e3fecb2f890faab97ce53cb4065525 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Tue, 24 Mar 2026 20:01:50 -0400 Subject: [PATCH 1/9] Add the Rush plugin for recording published versions. Co-Authored-By: Claude Sonnet 4.6 --- common/autoinstallers/plugins/package.json | 8 + common/autoinstallers/plugins/pnpm-lock.yaml | 316 ++++++++++++++++++ .../rush-plugin-manifest.json | 12 + .../command-line.json | 22 ++ common/config/rush/rush-plugins.json | 5 + 5 files changed, 363 insertions(+) create mode 100644 common/autoinstallers/plugins/package.json create mode 100644 common/autoinstallers/plugins/pnpm-lock.yaml create mode 100644 common/autoinstallers/plugins/rush-plugins/@rushstack/rush-published-versions-json-plugin/rush-plugin-manifest.json create mode 100644 common/autoinstallers/plugins/rush-plugins/@rushstack/rush-published-versions-json-plugin/rush-published-versions-json-plugin/command-line.json diff --git a/common/autoinstallers/plugins/package.json b/common/autoinstallers/plugins/package.json new file mode 100644 index 00000000000..106dd139955 --- /dev/null +++ b/common/autoinstallers/plugins/package.json @@ -0,0 +1,8 @@ +{ + "name": "plugins", + "version": "1.0.0", + "private": true, + "dependencies": { + "@rushstack/rush-published-versions-json-plugin": "0.1.0" + } +} diff --git a/common/autoinstallers/plugins/pnpm-lock.yaml b/common/autoinstallers/plugins/pnpm-lock.yaml new file mode 100644 index 00000000000..3f722f3ce13 --- /dev/null +++ b/common/autoinstallers/plugins/pnpm-lock.yaml @@ -0,0 +1,316 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@rushstack/rush-published-versions-json-plugin': + specifier: 0.1.0 + version: 0.1.0 + +packages: + + '@pnpm/lockfile.types@900.0.0': + resolution: {integrity: sha512-/4+3CAu4uIjx0ln1DYXNdj0qKJ3wyRDY+RS+eFzV6OHjreaTKWsF2WcjigYp1M5mxL4kj2RsRGgBGEyKtCfEWg==} + engines: {node: '>=18.12'} + + '@pnpm/patching.types@900.0.0': + resolution: {integrity: sha512-A/3kgRD4Xy2tBMPjOBdx5ZdgmpUobphzWkqDB72S5SIB6gdyCg32AUV0/aO12DwMxpT7kyqMhkkynUOPBfdlUQ==} + engines: {node: '>=18.12'} + + '@pnpm/types@900.0.0': + resolution: {integrity: sha512-GucC9h/EVbU03Kl7M/FqVes1s5RCQaGCW2f41lFA7VqqHWQElR6k1q33iF6f6fXDUSCdzB1IUxSq9ghP2J+8Pw==} + engines: {node: '>=18.12'} + + '@rushstack/credential-cache@0.2.7': + resolution: {integrity: sha512-5vYUGreyf0ZvQ5lLFsQHLbJe1//hBZeI8G7BMoyDaLt4/uegfHNTmiXgYNXDQaqpnM9ImHyXSuOS/DrcbM3aMA==} + + '@rushstack/lookup-by-path@0.9.7': + resolution: {integrity: sha512-wlyXv5n0scQF7DEcv4KcFxsoVbSLXfZhVvS91S6gc6fOWpLoqIzSGANY9yIQtQpyJqrr0vwLUppa+zv/CB4sYw==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/node-core-library@5.20.3': + resolution: {integrity: sha512-95JgEPq2k7tHxhF9/OJnnyHDXfC9cLhhta0An/6MlkDsX2A6dTzDrTUG18vx4vjc280V0fi0xDH9iQczpSuWsw==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/package-deps-hash@4.7.7': + resolution: {integrity: sha512-nj7wspFmch+SmPr6RT46zO9aKGD6IjVBP8z3JMmiiJq5p7WgNyqEENOBWGZ42Hwpav9qjj9hWxMggHJiwLg3mA==} + + '@rushstack/problem-matcher@0.2.1': + resolution: {integrity: sha512-gulfhBs6n+I5b7DvjKRfhMGyUejtSgOHTclF/eONr8hcgF1APEDjhxIsfdUYYMzC3rvLwGluqLjbwCFZ8nxrog==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/rush-published-versions-json-plugin@0.1.0': + resolution: {integrity: sha512-LS3U3ChGkS+6zRknmz4DT8LjegPBRFLs8pM2D3+s5fOn8bPxAiAVRtIfpkde02HSXyNf7h9RV8xn8Z13gBF54w==} + + '@rushstack/rush-sdk@5.172.0': + resolution: {integrity: sha512-SC5txCn1pFKfY2ozXSvppM9YViETmqUwRb0cpwhzKs5BqMDT5YcQHPwHySRjmzGaTEkHLT2VuTJ5iuJ2dpJzNQ==} + + '@rushstack/terminal@0.22.3': + resolution: {integrity: sha512-gHC9pIMrUPzAbBiI4VZMU7Q+rsCzb8hJl36lFIulIzoceKotyKL3Rd76AZ2CryCTKEg+0bnTj406HE5YY5OQvw==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fs-extra@11.3.4: + resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} + engines: {node: '>=14.14'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + import-lazy@4.0.0: + resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} + engines: {node: '>=8'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + jju@1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + +snapshots: + + '@pnpm/lockfile.types@900.0.0': + dependencies: + '@pnpm/patching.types': 900.0.0 + '@pnpm/types': 900.0.0 + + '@pnpm/patching.types@900.0.0': {} + + '@pnpm/types@900.0.0': {} + + '@rushstack/credential-cache@0.2.7': + dependencies: + '@rushstack/node-core-library': 5.20.3 + transitivePeerDependencies: + - '@types/node' + + '@rushstack/lookup-by-path@0.9.7': {} + + '@rushstack/node-core-library@5.20.3': + dependencies: + ajv: 8.18.0 + ajv-draft-04: 1.0.0(ajv@8.18.0) + ajv-formats: 3.0.1(ajv@8.18.0) + fs-extra: 11.3.4 + import-lazy: 4.0.0 + jju: 1.4.0 + resolve: 1.22.11 + semver: 7.5.4 + + '@rushstack/package-deps-hash@4.7.7': + dependencies: + '@rushstack/node-core-library': 5.20.3 + transitivePeerDependencies: + - '@types/node' + + '@rushstack/problem-matcher@0.2.1': {} + + '@rushstack/rush-published-versions-json-plugin@0.1.0': + dependencies: + '@rushstack/node-core-library': 5.20.3 + '@rushstack/rush-sdk': 5.172.0 + '@rushstack/terminal': 0.22.3 + transitivePeerDependencies: + - '@types/node' + + '@rushstack/rush-sdk@5.172.0': + dependencies: + '@pnpm/lockfile.types-900': '@pnpm/lockfile.types@900.0.0' + '@rushstack/credential-cache': 0.2.7 + '@rushstack/lookup-by-path': 0.9.7 + '@rushstack/node-core-library': 5.20.3 + '@rushstack/package-deps-hash': 4.7.7 + '@rushstack/terminal': 0.22.3 + tapable: 2.2.1 + transitivePeerDependencies: + - '@types/node' + + '@rushstack/terminal@0.22.3': + dependencies: + '@rushstack/node-core-library': 5.20.3 + '@rushstack/problem-matcher': 0.2.1 + supports-color: 8.1.1 + + ajv-draft-04@1.0.0(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + + ajv-formats@3.0.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.0: {} + + fs-extra@11.3.4: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + function-bind@1.1.2: {} + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + import-lazy@4.0.0: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + jju@1.4.0: {} + + json-schema-traverse@1.0.0: {} + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + path-parse@1.0.7: {} + + require-from-string@2.0.2: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + semver@7.5.4: + dependencies: + lru-cache: 6.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tapable@2.2.1: {} + + universalify@2.0.1: {} + + yallist@4.0.0: {} diff --git a/common/autoinstallers/plugins/rush-plugins/@rushstack/rush-published-versions-json-plugin/rush-plugin-manifest.json b/common/autoinstallers/plugins/rush-plugins/@rushstack/rush-published-versions-json-plugin/rush-plugin-manifest.json new file mode 100644 index 00000000000..03631898b98 --- /dev/null +++ b/common/autoinstallers/plugins/rush-plugins/@rushstack/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/common/autoinstallers/plugins/rush-plugins/@rushstack/rush-published-versions-json-plugin/rush-published-versions-json-plugin/command-line.json b/common/autoinstallers/plugins/rush-plugins/@rushstack/rush-published-versions-json-plugin/rush-published-versions-json-plugin/command-line.json new file mode 100644 index 00000000000..248cc35030b --- /dev/null +++ b/common/autoinstallers/plugins/rush-plugins/@rushstack/rush-published-versions-json-plugin/rush-published-versions-json-plugin/command-line.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/command-line.schema.json", + "commands": [ + { + "commandKind": "globalPlugin", + "name": "record-published-versions", + "summary": "Generates a JSON file recording the version numbers of all published packages.", + "safeForSimultaneousRushProcesses": true + } + ], + "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/common/config/rush/rush-plugins.json b/common/config/rush/rush-plugins.json index bee0e46c491..ad93f5d3910 100644 --- a/common/config/rush/rush-plugins.json +++ b/common/config/rush/rush-plugins.json @@ -25,5 +25,10 @@ // */ // "autoinstallerName": "rush-plugins" // } + { + "packageName": "@rushstack/rush-published-versions-json-plugin", + "pluginName": "rush-published-versions-json-plugin", + "autoinstallerName": "plugins" + } ] } From 4485740a836eab425f97a7a0f80b68a5462fb995 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Tue, 24 Mar 2026 20:02:08 -0400 Subject: [PATCH 2/9] Replace record-versions step with Rush plugin in post-publish pipeline. Co-Authored-By: Claude Sonnet 4.6 --- common/config/azure-pipelines/templates/post-publish.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/common/config/azure-pipelines/templates/post-publish.yaml b/common/config/azure-pipelines/templates/post-publish.yaml index 0a6f7528ef9..eb22daa9d8b 100644 --- a/common/config/azure-pipelines/templates/post-publish.yaml +++ b/common/config/azure-pipelines/templates/post-publish.yaml @@ -1,7 +1,5 @@ steps: - - script: > - node repo-scripts/repo-toolbox/lib-commonjs/start.js record-versions - --out-file $(Build.ArtifactStagingDirectory)/published-versions/published-versions.json + - script: 'node common/scripts/install-run-rush.js record-published-versions --output-path $(Build.ArtifactStagingDirectory)/published-versions/published-versions.json' displayName: 'Record Published Versions' - script: > From 6478f358d6bf5649209e5ff698b11bac7c59773d Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Tue, 24 Mar 2026 20:02:29 -0400 Subject: [PATCH 3/9] Remove repo-toolbox record-versions action. Co-Authored-By: Claude Sonnet 4.6 --- .../src/cli/ToolboxCommandLine.ts | 2 - .../src/cli/actions/RecordVersionsAction.ts | 56 ------------------- 2 files changed, 58 deletions(-) delete mode 100644 repo-scripts/repo-toolbox/src/cli/actions/RecordVersionsAction.ts diff --git a/repo-scripts/repo-toolbox/src/cli/ToolboxCommandLine.ts b/repo-scripts/repo-toolbox/src/cli/ToolboxCommandLine.ts index bf98ea120da..6a79c0ce0c8 100644 --- a/repo-scripts/repo-toolbox/src/cli/ToolboxCommandLine.ts +++ b/repo-scripts/repo-toolbox/src/cli/ToolboxCommandLine.ts @@ -5,7 +5,6 @@ import { CommandLineParser } from '@rushstack/ts-command-line'; import { ConsoleTerminalProvider, type ITerminal, Terminal } from '@rushstack/terminal'; import { ReadmeAction } from './actions/ReadmeAction'; -import { RecordVersionsAction } from './actions/RecordVersionsAction'; import { BumpDecoupledLocalDependencies } from './actions/BumpDecoupledLocalDependencies'; import { CollectProjectFilesAction } from './actions/CollectProjectFilesAction'; @@ -19,7 +18,6 @@ export class ToolboxCommandLine extends CommandLineParser { const terminal: ITerminal = new Terminal(new ConsoleTerminalProvider()); this.addAction(new ReadmeAction(terminal)); - this.addAction(new RecordVersionsAction(terminal)); this.addAction(new BumpDecoupledLocalDependencies(terminal)); this.addAction(new CollectProjectFilesAction(terminal)); } diff --git a/repo-scripts/repo-toolbox/src/cli/actions/RecordVersionsAction.ts b/repo-scripts/repo-toolbox/src/cli/actions/RecordVersionsAction.ts deleted file mode 100644 index 57dc988bdd5..00000000000 --- a/repo-scripts/repo-toolbox/src/cli/actions/RecordVersionsAction.ts +++ /dev/null @@ -1,56 +0,0 @@ -// 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 { JsonFile } from '@rushstack/node-core-library'; -import type { ITerminal } from '@rushstack/terminal'; -import { RushConfiguration } from '@microsoft/rush-lib'; -import { CommandLineAction, type CommandLineStringParameter } from '@rushstack/ts-command-line'; - -export class RecordVersionsAction extends CommandLineAction { - private readonly _outFilePath: CommandLineStringParameter; - - private readonly _terminal: ITerminal; - - public constructor(terminal: ITerminal) { - super({ - actionName: 'record-versions', - summary: 'Generates a JSON file recording the version numbers of all published packages.', - documentation: '' - }); - - this._terminal = terminal; - - this._outFilePath = this.defineStringParameter({ - parameterLongName: '--out-file', - parameterShortName: '-o', - argumentName: 'FILE_PATH', - description: 'The path to the output file.', - required: true - }); - } - - protected override async onExecuteAsync(): Promise { - const terminal: ITerminal = this._terminal; - const rushConfig: RushConfiguration = RushConfiguration.loadFromDefaultLocation({ - startingFolder: process.cwd() - }); - - terminal.writeLine(`Found Rush configuration at ${rushConfig.rushJsonFile}`); - - const publishedPackageVersions: Record = {}; - for (const project of rushConfig.projects) { - if (project.shouldPublish || project.versionPolicy) { - publishedPackageVersions[project.packageName] = project.packageJson.version; - } - } - - const resolvedOutputPath: string = path.resolve(process.cwd(), this._outFilePath.value!); - await JsonFile.saveAsync(publishedPackageVersions, resolvedOutputPath, { - ensureFolderExists: true - }); - - terminal.writeLine(`Wrote file to ${resolvedOutputPath}`); - } -} From ddc3f314977dd2d111a0eb3880d922f8d1b689cc Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Tue, 24 Mar 2026 20:03:42 -0400 Subject: [PATCH 4/9] Update bump-decoupled-local-dependencies to update autoinstallers. Co-Authored-By: Claude Sonnet 4.6 --- .../azure-pipelines/npm-post-publish.yaml | 5 ++ .../actions/BumpDecoupledLocalDependencies.ts | 78 ++++++++++++++++++- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/common/config/azure-pipelines/npm-post-publish.yaml b/common/config/azure-pipelines/npm-post-publish.yaml index 08eadb59b57..f5ed50096af 100644 --- a/common/config/azure-pipelines/npm-post-publish.yaml +++ b/common/config/azure-pipelines/npm-post-publish.yaml @@ -119,6 +119,11 @@ extends: Arguments: 'update' DisplayName: 'Rush Update' + - template: /common/config/azure-pipelines/templates/install-run-rush.yaml@self + parameters: + Arguments: 'update-autoinstaller --name plugins' + DisplayName: 'Rush Update Autoinstaller (plugins)' + - bash: cp common/temp/install-run/@microsoft+rush@*/package-lock.json common/config/validation/rush-package-lock.json displayName: 'Update rush-package-lock.json' diff --git a/repo-scripts/repo-toolbox/src/cli/actions/BumpDecoupledLocalDependencies.ts b/repo-scripts/repo-toolbox/src/cli/actions/BumpDecoupledLocalDependencies.ts index eb9172aa678..2c28ed1c925 100644 --- a/repo-scripts/repo-toolbox/src/cli/actions/BumpDecoupledLocalDependencies.ts +++ b/repo-scripts/repo-toolbox/src/cli/actions/BumpDecoupledLocalDependencies.ts @@ -2,13 +2,19 @@ // See LICENSE in the project root for license information. import type { ChildProcess } from 'node:child_process'; +import * as path from 'node:path'; -import { Async, Executable, JsonFile } from '@rushstack/node-core-library'; +import { Async, Executable, FileSystem, JsonFile } from '@rushstack/node-core-library'; import type { ITerminal } from '@rushstack/terminal'; import { DependencyType, RushConfiguration } from '@microsoft/rush-lib'; import type { IRushConfigurationJson } from '@microsoft/rush-lib/lib/api/RushConfiguration'; import { CommandLineAction } from '@rushstack/ts-command-line'; +interface IPackageJson { + dependencies?: Record; + devDependencies?: Record; +} + async function _getLatestPublishedVersionAsync(terminal: ITerminal, packageName: string): Promise { return await new Promise((resolve: (result: string) => void, reject: (error: Error) => void) => { const childProcess: ChildProcess = Executable.spawn('npm', ['view', packageName, 'version'], { @@ -45,9 +51,10 @@ export class BumpDecoupledLocalDependencies extends CommandLineAction { protected override async onExecuteAsync(): Promise { const terminal: ITerminal = this._terminal; - const { projects, rushJsonFile } = RushConfiguration.loadFromDefaultLocation({ + const rushConfiguration: RushConfiguration = RushConfiguration.loadFromDefaultLocation({ startingFolder: process.cwd() }); + const { projects, rushJsonFile } = rushConfiguration; const cyclicDependencyNames: Set = new Set(); @@ -57,6 +64,52 @@ export class BumpDecoupledLocalDependencies extends CommandLineAction { } } + // Collect all package names published from this repo + const publishedPackageNames: Set = new Set(); + for (const project of projects) { + if (project.shouldPublish || project.versionPolicy) { + publishedPackageNames.add(project.packageName); + } + } + + // Scan autoinstaller package.json files for dependencies on packages published from this repo + const autoinstallersFolder: string = path.join(rushConfiguration.commonFolder, 'autoinstallers'); + // Map of autoinstaller name -> { packageJsonPath, packageJson } + const autoinstallerInfoByName: Map = + new Map(); + + let autoinstallerEntries: string[] = []; + try { + autoinstallerEntries = (await FileSystem.readFolderItemNamesAsync(autoinstallersFolder)).filter( + (entry) => !entry.startsWith('.') + ); + } catch (error) { + if (!FileSystem.isNotExistError(error as Error)) { + throw error; + } + } + + for (const autoinstallerName of autoinstallerEntries) { + const packageJsonPath: string = path.join(autoinstallersFolder, autoinstallerName, 'package.json'); + try { + const packageJson: IPackageJson = await JsonFile.loadAsync(packageJsonPath); + autoinstallerInfoByName.set(autoinstallerName, { packageJsonPath, packageJson }); + + for (const depName of [ + ...Object.keys(packageJson.dependencies ?? {}), + ...Object.keys(packageJson.devDependencies ?? {}) + ]) { + if (publishedPackageNames.has(depName)) { + cyclicDependencyNames.add(depName); + } + } + } catch (error) { + if (!FileSystem.isNotExistError(error as Error)) { + throw error; + } + } + } + const decoupledLocalDependencyVersionsByName: Map = new Map(); await Async.forEachAsync( Array.from(cyclicDependencyNames), @@ -110,6 +163,27 @@ export class BumpDecoupledLocalDependencies extends CommandLineAction { } } + // Update autoinstaller package.json files + for (const [autoinstallerName, { packageJsonPath, packageJson }] of autoinstallerInfoByName) { + let modified: boolean = false; + + for (const depSection of [packageJson.dependencies, packageJson.devDependencies]) { + if (!depSection) continue; + for (const depName of Object.keys(depSection)) { + const newVersion: string | undefined = decoupledLocalDependencyVersionsByName.get(depName); + if (newVersion && depSection[depName] !== newVersion) { + depSection[depName] = newVersion; + modified = true; + } + } + } + + if (modified) { + await JsonFile.saveAsync(packageJson, packageJsonPath, { updateExistingFile: true }); + terminal.writeLine(`Updated autoinstaller ${autoinstallerName}`); + } + } + terminal.writeLine(); // Update the Rush version in rush.json From cfd9d250f6d09ff2940a81b8773d274f4d5a05c9 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Tue, 24 Mar 2026 20:30:39 -0400 Subject: [PATCH 5/9] fixup! Update bump-decoupled-local-dependencies to update autoinstallers. --- .../actions/BumpDecoupledLocalDependencies.ts | 146 +++++++++--------- 1 file changed, 72 insertions(+), 74 deletions(-) diff --git a/repo-scripts/repo-toolbox/src/cli/actions/BumpDecoupledLocalDependencies.ts b/repo-scripts/repo-toolbox/src/cli/actions/BumpDecoupledLocalDependencies.ts index 2c28ed1c925..853e983f892 100644 --- a/repo-scripts/repo-toolbox/src/cli/actions/BumpDecoupledLocalDependencies.ts +++ b/repo-scripts/repo-toolbox/src/cli/actions/BumpDecoupledLocalDependencies.ts @@ -2,19 +2,20 @@ // See LICENSE in the project root for license information. import type { ChildProcess } from 'node:child_process'; -import * as path from 'node:path'; -import { Async, Executable, FileSystem, JsonFile } from '@rushstack/node-core-library'; +import { + Async, + Executable, + FileSystem, + FolderItem, + JsonFile, + type IPackageJson +} from '@rushstack/node-core-library'; import type { ITerminal } from '@rushstack/terminal'; -import { DependencyType, RushConfiguration } from '@microsoft/rush-lib'; +import { DependencyType, PackageJsonEditor, RushConfiguration, Subspace } from '@microsoft/rush-lib'; import type { IRushConfigurationJson } from '@microsoft/rush-lib/lib/api/RushConfiguration'; import { CommandLineAction } from '@rushstack/ts-command-line'; -interface IPackageJson { - dependencies?: Record; - devDependencies?: Record; -} - async function _getLatestPublishedVersionAsync(terminal: ITerminal, packageName: string): Promise { return await new Promise((resolve: (result: string) => void, reject: (error: Error) => void) => { const childProcess: ChildProcess = Executable.spawn('npm', ['view', packageName, 'version'], { @@ -36,6 +37,13 @@ async function _getLatestPublishedVersionAsync(terminal: ITerminal, packageName: }); } +interface IProjectLike { + packageName: string; + decoupledLocalDependencies: Iterable; + subspace: Subspace | undefined; + packageJsonEditor: PackageJsonEditor; +} + export class BumpDecoupledLocalDependencies extends CommandLineAction { private readonly _terminal: ITerminal; @@ -54,65 +62,80 @@ export class BumpDecoupledLocalDependencies extends CommandLineAction { const rushConfiguration: RushConfiguration = RushConfiguration.loadFromDefaultLocation({ startingFolder: process.cwd() }); - const { projects, rushJsonFile } = rushConfiguration; + const { projects, rushJsonFile, commonAutoinstallersFolder } = rushConfiguration; - const cyclicDependencyNames: Set = new Set(); + const projectsToUpdate: IProjectLike[] = []; - for (const { decoupledLocalDependencies } of projects) { + const allDecoupledLocalDependencyNames: Set = new Set(); + for (const project of projects) { + const { decoupledLocalDependencies } = project; for (const decoupledLocalDependency of decoupledLocalDependencies) { - cyclicDependencyNames.add(decoupledLocalDependency); + allDecoupledLocalDependencyNames.add(decoupledLocalDependency); } + + projectsToUpdate.push(project); } // Collect all package names published from this repo - const publishedPackageNames: Set = new Set(); - for (const project of projects) { - if (project.shouldPublish || project.versionPolicy) { - publishedPackageNames.add(project.packageName); + const publishedPackageNames: Set = new Set(); + for (const { shouldPublish, packageName } of projects) { + // Note that shouldPublish is also true here if the project is driven by a version policy + if (shouldPublish) { + publishedPackageNames.add(packageName); } } // Scan autoinstaller package.json files for dependencies on packages published from this repo - const autoinstallersFolder: string = path.join(rushConfiguration.commonFolder, 'autoinstallers'); // Map of autoinstaller name -> { packageJsonPath, packageJson } const autoinstallerInfoByName: Map = new Map(); - let autoinstallerEntries: string[] = []; + let autoinstallerEntries: FolderItem[] = []; try { - autoinstallerEntries = (await FileSystem.readFolderItemNamesAsync(autoinstallersFolder)).filter( - (entry) => !entry.startsWith('.') - ); + autoinstallerEntries = await FileSystem.readFolderItemsAsync(commonAutoinstallersFolder); } catch (error) { - if (!FileSystem.isNotExistError(error as Error)) { + if (!FileSystem.isNotExistError(error)) { throw error; } } - for (const autoinstallerName of autoinstallerEntries) { - const packageJsonPath: string = path.join(autoinstallersFolder, autoinstallerName, 'package.json'); - try { - const packageJson: IPackageJson = await JsonFile.loadAsync(packageJsonPath); - autoinstallerInfoByName.set(autoinstallerName, { packageJsonPath, packageJson }); - - for (const depName of [ - ...Object.keys(packageJson.dependencies ?? {}), - ...Object.keys(packageJson.devDependencies ?? {}) - ]) { - if (publishedPackageNames.has(depName)) { - cyclicDependencyNames.add(depName); + await Async.forEachAsync( + autoinstallerEntries, + async (folderItem) => { + if (folderItem.isDirectory()) { + const autoinstallerName: string = folderItem.name; + const packageJsonPath: string = `${commonAutoinstallersFolder}/${autoinstallerName}/package.json`; + try { + const packageJsonEditor: PackageJsonEditor = PackageJsonEditor.load(packageJsonPath); + + const { dependencyList, devDependencyList } = packageJsonEditor; + const decoupledLocalDependencies: Set = new Set(); + for (const { name } of [...dependencyList, ...devDependencyList]) { + if (publishedPackageNames.has(name)) { + allDecoupledLocalDependencyNames.add(name); + decoupledLocalDependencies.add(name); + } + } + + projectsToUpdate.push({ + packageName: `${autoinstallerName} (autoinstaller)`, + decoupledLocalDependencies, + subspace: undefined, + packageJsonEditor + }); + } catch (error) { + if (!FileSystem.isNotExistError(error)) { + throw error; + } } } - } catch (error) { - if (!FileSystem.isNotExistError(error as Error)) { - throw error; - } - } - } + }, + { concurrency: 10 } + ); - const decoupledLocalDependencyVersionsByName: Map = new Map(); + const decoupledLocalDependencyVersionsByName: Map = new Map(); await Async.forEachAsync( - Array.from(cyclicDependencyNames), + allDecoupledLocalDependencyNames, async (decoupledLocalDependencyName) => { const version: string = await _getLatestPublishedVersionAsync(terminal, decoupledLocalDependencyName); decoupledLocalDependencyVersionsByName.set(decoupledLocalDependencyName, version); @@ -124,21 +147,17 @@ export class BumpDecoupledLocalDependencies extends CommandLineAction { terminal.writeLine(); - for (const { - packageName, - decoupledLocalDependencies, - subspace, - packageJson: { dependencies, devDependencies }, - packageJsonEditor - } of projects) { - const { allowedAlternativeVersions } = subspace.getCommonVersions(); + for (const { packageName, decoupledLocalDependencies, subspace, packageJsonEditor } of projectsToUpdate) { + const { allowedAlternativeVersions } = subspace?.getCommonVersions() ?? {}; for (const cyclicDependencyProject of decoupledLocalDependencies) { - const existingVersion: string | undefined = - dependencies?.[cyclicDependencyProject] ?? devDependencies?.[cyclicDependencyProject]; + const { version: existingVersion } = + packageJsonEditor.tryGetDependency(cyclicDependencyProject) ?? + packageJsonEditor.tryGetDevDependency(cyclicDependencyProject) ?? + {}; if ( existingVersion && - allowedAlternativeVersions.get(cyclicDependencyProject)?.includes(existingVersion) + allowedAlternativeVersions?.get(cyclicDependencyProject)?.includes(existingVersion) ) { // Skip if the existing version is allowed by common-versions.json continue; @@ -163,27 +182,6 @@ export class BumpDecoupledLocalDependencies extends CommandLineAction { } } - // Update autoinstaller package.json files - for (const [autoinstallerName, { packageJsonPath, packageJson }] of autoinstallerInfoByName) { - let modified: boolean = false; - - for (const depSection of [packageJson.dependencies, packageJson.devDependencies]) { - if (!depSection) continue; - for (const depName of Object.keys(depSection)) { - const newVersion: string | undefined = decoupledLocalDependencyVersionsByName.get(depName); - if (newVersion && depSection[depName] !== newVersion) { - depSection[depName] = newVersion; - modified = true; - } - } - } - - if (modified) { - await JsonFile.saveAsync(packageJson, packageJsonPath, { updateExistingFile: true }); - terminal.writeLine(`Updated autoinstaller ${autoinstallerName}`); - } - } - terminal.writeLine(); // Update the Rush version in rush.json From cb7d0ed9bc6c01028f4bc9a4e0df1b19505eb367 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Tue, 24 Mar 2026 20:41:16 -0400 Subject: [PATCH 6/9] Clean up pipeline YAML. --- common/config/azure-pipelines/npm-post-publish.yaml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/common/config/azure-pipelines/npm-post-publish.yaml b/common/config/azure-pipelines/npm-post-publish.yaml index f5ed50096af..b81d2360aa4 100644 --- a/common/config/azure-pipelines/npm-post-publish.yaml +++ b/common/config/azure-pipelines/npm-post-publish.yaml @@ -111,7 +111,9 @@ extends: --verbose DisplayName: 'Rush Build (repo-toolbox)' - - script: 'node repo-scripts/repo-toolbox/lib-commonjs/start.js bump-decoupled-local-dependencies' + - script: > + node repo-scripts/repo-toolbox/lib-commonjs/start.js + bump-decoupled-local-dependencies displayName: 'Bump decoupled local dependencies' - template: /common/config/azure-pipelines/templates/install-run-rush.yaml@self @@ -211,7 +213,11 @@ extends: # Run api-documenter with the Docusaurus plugin from the # api.rushstack.io project directory so it picks up # config/api-documenter.json. - - script: 'npx @microsoft/api-documenter@latest generate --input-folder $(Pipeline.Workspace)/api --output-folder ./docs/pages' + - script: > + npx @microsoft/api-documenter@latest + generate + --input-folder $(Pipeline.Workspace)/api + --output-folder ./docs/pages displayName: 'Generate API documentation' workingDirectory: websites/api.rushstack.io From 66f1cbe87de8d8d34adb1b77404f4707bde64597 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Tue, 24 Mar 2026 20:42:34 -0400 Subject: [PATCH 7/9] fixup! Update bump-decoupled-local-dependencies to update autoinstallers. --- .../src/cli/actions/BumpDecoupledLocalDependencies.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/repo-scripts/repo-toolbox/src/cli/actions/BumpDecoupledLocalDependencies.ts b/repo-scripts/repo-toolbox/src/cli/actions/BumpDecoupledLocalDependencies.ts index 853e983f892..78b3c574fb0 100644 --- a/repo-scripts/repo-toolbox/src/cli/actions/BumpDecoupledLocalDependencies.ts +++ b/repo-scripts/repo-toolbox/src/cli/actions/BumpDecoupledLocalDependencies.ts @@ -7,12 +7,12 @@ import { Async, Executable, FileSystem, - FolderItem, + type FolderItem, JsonFile, type IPackageJson } from '@rushstack/node-core-library'; import type { ITerminal } from '@rushstack/terminal'; -import { DependencyType, PackageJsonEditor, RushConfiguration, Subspace } from '@microsoft/rush-lib'; +import { DependencyType, PackageJsonEditor, RushConfiguration, type Subspace } from '@microsoft/rush-lib'; import type { IRushConfigurationJson } from '@microsoft/rush-lib/lib/api/RushConfiguration'; import { CommandLineAction } from '@rushstack/ts-command-line'; @@ -85,11 +85,6 @@ export class BumpDecoupledLocalDependencies extends CommandLineAction { } } - // Scan autoinstaller package.json files for dependencies on packages published from this repo - // Map of autoinstaller name -> { packageJsonPath, packageJson } - const autoinstallerInfoByName: Map = - new Map(); - let autoinstallerEntries: FolderItem[] = []; try { autoinstallerEntries = await FileSystem.readFolderItemsAsync(commonAutoinstallersFolder); From 9bdc84979fc6a62676563f7f621d21a33bbe3683 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Tue, 24 Mar 2026 20:51:04 -0400 Subject: [PATCH 8/9] Address PR feedback: use install-run-rush.yaml template; add run-repo-toolbox.yaml template Co-Authored-By: Claude Sonnet 4.6 --- .../azure-pipelines/npm-post-publish.yaml | 8 ++--- .../templates/post-publish.yaml | 32 ++++++++++++------- .../templates/run-repo-toolbox.yaml | 10 ++++++ 3 files changed, 34 insertions(+), 16 deletions(-) create mode 100644 common/config/azure-pipelines/templates/run-repo-toolbox.yaml diff --git a/common/config/azure-pipelines/npm-post-publish.yaml b/common/config/azure-pipelines/npm-post-publish.yaml index b81d2360aa4..483168fa095 100644 --- a/common/config/azure-pipelines/npm-post-publish.yaml +++ b/common/config/azure-pipelines/npm-post-publish.yaml @@ -111,10 +111,10 @@ extends: --verbose DisplayName: 'Rush Build (repo-toolbox)' - - script: > - node repo-scripts/repo-toolbox/lib-commonjs/start.js - bump-decoupled-local-dependencies - displayName: 'Bump decoupled local dependencies' + - template: /common/config/azure-pipelines/templates/run-repo-toolbox.yaml@self + parameters: + Arguments: 'bump-decoupled-local-dependencies' + DisplayName: 'Bump decoupled local dependencies' - template: /common/config/azure-pipelines/templates/install-run-rush.yaml@self parameters: diff --git a/common/config/azure-pipelines/templates/post-publish.yaml b/common/config/azure-pipelines/templates/post-publish.yaml index eb22daa9d8b..a08e554a06e 100644 --- a/common/config/azure-pipelines/templates/post-publish.yaml +++ b/common/config/azure-pipelines/templates/post-publish.yaml @@ -1,15 +1,23 @@ steps: - - script: 'node common/scripts/install-run-rush.js record-published-versions --output-path $(Build.ArtifactStagingDirectory)/published-versions/published-versions.json' - displayName: 'Record Published Versions' + - template: /common/config/azure-pipelines/templates/install-run-rush.yaml@self + parameters: + Arguments: > + record-published-versions + --output-path $(Build.ArtifactStagingDirectory)/published-versions/published-versions.json + DisplayName: 'Record Published Versions' - - script: > - node repo-scripts/repo-toolbox/lib-commonjs/start.js collect-project-files - --subfolder temp/json-schemas - --output-path $(Build.ArtifactStagingDirectory)/json-schemas - displayName: 'Collect JSON Schemas' + - template: /common/config/azure-pipelines/templates/run-repo-toolbox.yaml@self + parameters: + Arguments: > + collect-project-files + --subfolder temp/json-schemas + --output-path $(Build.ArtifactStagingDirectory)/json-schemas + DisplayName: 'Collect JSON Schemas' - - script: > - node repo-scripts/repo-toolbox/lib-commonjs/start.js collect-project-files - --subfolder temp/api - --output-path $(Build.ArtifactStagingDirectory)/api - displayName: 'Collect API review files' + - template: /common/config/azure-pipelines/templates/run-repo-toolbox.yaml@self + parameters: + Arguments: > + collect-project-files + --subfolder temp/api + --output-path $(Build.ArtifactStagingDirectory)/api + DisplayName: 'Collect API review files' diff --git a/common/config/azure-pipelines/templates/run-repo-toolbox.yaml b/common/config/azure-pipelines/templates/run-repo-toolbox.yaml new file mode 100644 index 00000000000..7e8786d8866 --- /dev/null +++ b/common/config/azure-pipelines/templates/run-repo-toolbox.yaml @@ -0,0 +1,10 @@ +# Runs repo-toolbox with the specified CLI arguments. +parameters: + - name: Arguments + type: string + - name: DisplayName + type: string + +steps: + - script: 'node repo-scripts/repo-toolbox/lib-commonjs/start.js ${{ parameters.Arguments }}' + displayName: '${{ parameters.DisplayName }}' From 7f9c35ed1ff0698cd273f6b58a714f3e0094b0ba Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Tue, 24 Mar 2026 21:37:55 -0400 Subject: [PATCH 9/9] fixup! Update bump-decoupled-local-dependencies to update autoinstallers. --- .../src/cli/actions/BumpDecoupledLocalDependencies.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/repo-scripts/repo-toolbox/src/cli/actions/BumpDecoupledLocalDependencies.ts b/repo-scripts/repo-toolbox/src/cli/actions/BumpDecoupledLocalDependencies.ts index 78b3c574fb0..14657e0be70 100644 --- a/repo-scripts/repo-toolbox/src/cli/actions/BumpDecoupledLocalDependencies.ts +++ b/repo-scripts/repo-toolbox/src/cli/actions/BumpDecoupledLocalDependencies.ts @@ -3,14 +3,7 @@ import type { ChildProcess } from 'node:child_process'; -import { - Async, - Executable, - FileSystem, - type FolderItem, - JsonFile, - type IPackageJson -} from '@rushstack/node-core-library'; +import { Async, Executable, FileSystem, type FolderItem, JsonFile } from '@rushstack/node-core-library'; import type { ITerminal } from '@rushstack/terminal'; import { DependencyType, PackageJsonEditor, RushConfiguration, type Subspace } from '@microsoft/rush-lib'; import type { IRushConfigurationJson } from '@microsoft/rush-lib/lib/api/RushConfiguration';