From d097f783429f0c33a181cb71e620cfac0cdd900c Mon Sep 17 00:00:00 2001 From: David Michon Date: Tue, 19 Aug 2025 21:33:02 +0000 Subject: [PATCH 1/2] [rush] Fix version bump removing policy fields --- ...ersion-policy-exempt_2025-08-19-21-32.json | 10 ++ common/reviews/api/rush-lib.api.md | 36 +++--- libraries/rush-lib/src/api/VersionPolicy.ts | 111 +++++++----------- .../src/api/test/VersionPolicy.test.ts | 44 ++++++- 4 files changed, 113 insertions(+), 88 deletions(-) create mode 100644 common/changes/@microsoft/rush/version-policy-exempt_2025-08-19-21-32.json diff --git a/common/changes/@microsoft/rush/version-policy-exempt_2025-08-19-21-32.json b/common/changes/@microsoft/rush/version-policy-exempt_2025-08-19-21-32.json new file mode 100644 index 00000000000..610e4cf2210 --- /dev/null +++ b/common/changes/@microsoft/rush/version-policy-exempt_2025-08-19-21-32.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Ensure that `rush version` and `rush publish` preserve all fields in `version-policies-json`.", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index 798296124b0..842692bb49f 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -575,15 +575,15 @@ export interface ILogger { // @public export class IndividualVersionPolicy extends VersionPolicy { - // Warning: (ae-forgotten-export) The symbol "IIndividualVersionJson" needs to be exported by the entry point index.d.ts - // // @internal constructor(versionPolicyJson: IIndividualVersionJson); bump(bumpType?: BumpType, identifier?: string): void; ensure(project: IPackageJson, force?: boolean): IPackageJson | undefined; - // @internal - get _json(): IIndividualVersionJson; - readonly lockedMajor: number | undefined; + // Warning: (ae-forgotten-export) The symbol "IIndividualVersionJson" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly _json: IIndividualVersionJson; + get lockedMajor(): number | undefined; validate(versionString: string, packageName: string): void; } @@ -929,16 +929,16 @@ export interface _IYarnOptionsJson extends IPackageManagerOptionsJsonBase { // @public export class LockStepVersionPolicy extends VersionPolicy { - // Warning: (ae-forgotten-export) The symbol "ILockStepVersionJson" needs to be exported by the entry point index.d.ts - // // @internal constructor(versionPolicyJson: ILockStepVersionJson); bump(bumpType?: BumpType, identifier?: string): void; ensure(project: IPackageJson, force?: boolean): IPackageJson | undefined; - // @internal - get _json(): ILockStepVersionJson; - readonly mainProject: string | undefined; - readonly nextBump: BumpType | undefined; + // Warning: (ae-forgotten-export) The symbol "ILockStepVersionJson" needs to be exported by the entry point index.d.ts + // + // (undocumented) + _json: ILockStepVersionJson; + get mainProject(): string | undefined; + get nextBump(): BumpType | undefined; update(newVersionString: string): boolean; validate(versionString: string, packageName: string): void; get version(): string; @@ -1603,21 +1603,21 @@ export class SubspacesConfiguration { // @public export abstract class VersionPolicy { - // Warning: (ae-forgotten-export) The symbol "IVersionPolicyJson" needs to be exported by the entry point index.d.ts - // // @internal constructor(versionPolicyJson: IVersionPolicyJson); abstract bump(bumpType?: BumpType, identifier?: string): void; - readonly definitionName: VersionPolicyDefinitionName; + get definitionName(): VersionPolicyDefinitionName; abstract ensure(project: IPackageJson, force?: boolean): IPackageJson | undefined; - readonly exemptFromRushChange: boolean; - readonly includeEmailInChangeFile: boolean; + get exemptFromRushChange(): boolean; + get includeEmailInChangeFile(): boolean; get isLockstepped(): boolean; + // Warning: (ae-forgotten-export) The symbol "IVersionPolicyJson" needs to be exported by the entry point index.d.ts + // // @internal - abstract get _json(): IVersionPolicyJson; + readonly _json: IVersionPolicyJson; // @internal static load(versionPolicyJson: IVersionPolicyJson): VersionPolicy | undefined; - readonly policyName: string; + get policyName(): string; setDependenciesBeforeCommit(packageName: string, configuration: RushConfiguration): void; setDependenciesBeforePublish(packageName: string, configuration: RushConfiguration): void; abstract validate(versionString: string, packageName: string): void; diff --git a/libraries/rush-lib/src/api/VersionPolicy.ts b/libraries/rush-lib/src/api/VersionPolicy.ts index 2b1986c7943..e3f3b92ee65 100644 --- a/libraries/rush-lib/src/api/VersionPolicy.ts +++ b/libraries/rush-lib/src/api/VersionPolicy.ts @@ -9,8 +9,7 @@ import { type ILockStepVersionJson, type IIndividualVersionJson, VersionFormatForCommit, - VersionFormatForPublish, - type IVersionPolicyDependencyJson + VersionFormatForPublish } from './VersionPolicyConfiguration'; import type { PackageJsonEditor } from './PackageJsonEditor'; import type { RushConfiguration } from './RushConfiguration'; @@ -57,42 +56,54 @@ export enum VersionPolicyDefinitionName { * @public */ export abstract class VersionPolicy { - private _versionFormatForCommit: VersionFormatForCommit; - private _versionFormatForPublish: VersionFormatForPublish; + /** + * Serialized json for the policy + * + * @internal + */ + public readonly _json: IVersionPolicyJson; + + private get _versionFormatForCommit(): VersionFormatForCommit { + return this._json.dependencies?.versionFormatForCommit ?? VersionFormatForCommit.original; + } + + private get _versionFormatForPublish(): VersionFormatForPublish { + return this._json.dependencies?.versionFormatForPublish ?? VersionFormatForPublish.original; + } /** * Version policy name */ - public readonly policyName: string; + public get policyName(): string { + return this._json.policyName; + } /** * Version policy definition name */ - public readonly definitionName: VersionPolicyDefinitionName; + public get definitionName(): VersionPolicyDefinitionName { + return Enum.getValueByKey(VersionPolicyDefinitionName, this._json.definitionName); + } /** * Determines if a version policy wants to opt out of changelog files. */ - public readonly exemptFromRushChange: boolean; + public get exemptFromRushChange(): boolean { + return this._json.exemptFromRushChange ?? false; + } /** * Determines if a version policy wants to opt in to including email. */ - public readonly includeEmailInChangeFile: boolean; + public get includeEmailInChangeFile(): boolean { + return this._json.includeEmailInChangeFile ?? false; + } /** * @internal */ public constructor(versionPolicyJson: IVersionPolicyJson) { - this.policyName = versionPolicyJson.policyName; - this.definitionName = Enum.getValueByKey(VersionPolicyDefinitionName, versionPolicyJson.definitionName); - this.exemptFromRushChange = versionPolicyJson.exemptFromRushChange || false; - this.includeEmailInChangeFile = versionPolicyJson.includeEmailInChangeFile || false; - - const jsonDependencies: IVersionPolicyDependencyJson = versionPolicyJson.dependencies || {}; - this._versionFormatForCommit = jsonDependencies.versionFormatForCommit || VersionFormatForCommit.original; - this._versionFormatForPublish = - jsonDependencies.versionFormatForPublish || VersionFormatForPublish.original; + this._json = versionPolicyJson; } /** @@ -140,13 +151,6 @@ export abstract class VersionPolicy { */ public abstract bump(bumpType?: BumpType, identifier?: string): void; - /** - * Serialized json for the policy - * - * @internal - */ - public abstract get _json(): IVersionPolicyJson; - /** * Validates the specified version and throws if the version does not satisfy the policy. * @@ -211,6 +215,7 @@ export abstract class VersionPolicy { * @public */ export class LockStepVersionPolicy extends VersionPolicy { + public declare _json: ILockStepVersionJson; private _version: semver.SemVer; /** @@ -218,7 +223,9 @@ export class LockStepVersionPolicy extends VersionPolicy { */ // nextBump is probably not needed. It can be prerelease only. // Other types of bumps can be passed in as a parameter to bump method, so can identifier. - public readonly nextBump: BumpType | undefined; + public get nextBump(): BumpType | undefined { + return this._json.nextBump !== undefined ? Enum.getValueByKey(BumpType, this._json.nextBump) : undefined; + } /** * The main project for the version policy. @@ -226,7 +233,9 @@ export class LockStepVersionPolicy extends VersionPolicy { * If the value is provided, change logs will only be generated in that project. * If the value is not provided, change logs will be hosted in each project associated with the policy. */ - public readonly mainProject: string | undefined; + public get mainProject(): string | undefined { + return this._json.mainProject; + } /** * @internal @@ -234,11 +243,6 @@ export class LockStepVersionPolicy extends VersionPolicy { public constructor(versionPolicyJson: ILockStepVersionJson) { super(versionPolicyJson); this._version = new semver.SemVer(versionPolicyJson.version); - this.nextBump = - versionPolicyJson.nextBump !== undefined - ? Enum.getValueByKey(BumpType, versionPolicyJson.nextBump) - : undefined; - this.mainProject = versionPolicyJson.mainProject; } /** @@ -248,26 +252,6 @@ export class LockStepVersionPolicy extends VersionPolicy { return this._version.format(); } - /** - * Serialized json for this policy - * - * @internal - */ - public get _json(): ILockStepVersionJson { - const json: ILockStepVersionJson = { - policyName: this.policyName, - definitionName: VersionPolicyDefinitionName[this.definitionName], - version: this.version - }; - if (this.nextBump !== undefined) { - json.nextBump = BumpType[this.nextBump]; - } - if (this.mainProject !== undefined) { - json.mainProject = this.mainProject; - } - return json; - } - /** * Returns an updated package json that satisfies the version policy. * @@ -303,6 +287,7 @@ export class LockStepVersionPolicy extends VersionPolicy { } this._version.inc(this._getReleaseType(nextBump), identifier); + this._json.version = this.version; } /** @@ -315,6 +300,7 @@ export class LockStepVersionPolicy extends VersionPolicy { return false; } this._version = newVersion; + this._json.version = this.version; return true; } @@ -348,33 +334,20 @@ export class LockStepVersionPolicy extends VersionPolicy { * @public */ export class IndividualVersionPolicy extends VersionPolicy { + public declare readonly _json: IIndividualVersionJson; + /** * The major version that has been locked */ - public readonly lockedMajor: number | undefined; + public get lockedMajor(): number | undefined { + return this._json.lockedMajor; + } /** * @internal */ public constructor(versionPolicyJson: IIndividualVersionJson) { super(versionPolicyJson); - this.lockedMajor = versionPolicyJson.lockedMajor; - } - - /** - * Serialized json for this policy - * - * @internal - */ - public get _json(): IIndividualVersionJson { - const json: IIndividualVersionJson = { - policyName: this.policyName, - definitionName: VersionPolicyDefinitionName[this.definitionName] - }; - if (this.lockedMajor !== undefined) { - json.lockedMajor = this.lockedMajor; - } - return json; } /** diff --git a/libraries/rush-lib/src/api/test/VersionPolicy.test.ts b/libraries/rush-lib/src/api/test/VersionPolicy.test.ts index 5371bcff7b1..edc0bbcedd0 100644 --- a/libraries/rush-lib/src/api/test/VersionPolicy.test.ts +++ b/libraries/rush-lib/src/api/test/VersionPolicy.test.ts @@ -3,7 +3,13 @@ import type { IPackageJson } from '@rushstack/node-core-library'; -import { VersionPolicyConfiguration } from '../VersionPolicyConfiguration'; +import { + type ILockStepVersionJson, + VersionFormatForCommit, + VersionFormatForPublish, + VersionPolicyConfiguration, + type IIndividualVersionJson +} from '../VersionPolicyConfiguration'; import { VersionPolicy, LockStepVersionPolicy, IndividualVersionPolicy, BumpType } from '../VersionPolicy'; describe(VersionPolicy.name, () => { @@ -113,6 +119,25 @@ describe(VersionPolicy.name, () => { lockStepVersionPolicy.update(newVersion); expect(lockStepVersionPolicy.version).toEqual(newVersion); }); + + it('preserves fields', () => { + const originalJson: ILockStepVersionJson = { + definitionName: 'lockStepVersion', + policyName: 'test', + dependencies: { + versionFormatForCommit: VersionFormatForCommit.original, + versionFormatForPublish: VersionFormatForPublish.original + }, + exemptFromRushChange: true, + includeEmailInChangeFile: true, + version: '1.1.0', + mainProject: 'main-project', + nextBump: 'major' + }; + + const nextJson: ILockStepVersionJson = new LockStepVersionPolicy(originalJson)._json; + expect(nextJson).toMatchObject(originalJson); + }); }); describe(IndividualVersionPolicy.name, () => { @@ -159,5 +184,22 @@ describe(VersionPolicy.name, () => { individualVersionPolicy.ensure(originalPackageJson); }).toThrow(); }); + + it('preserves fields', () => { + const originalJson: IIndividualVersionJson = { + definitionName: 'individualVersion', + policyName: 'test', + dependencies: { + versionFormatForCommit: VersionFormatForCommit.original, + versionFormatForPublish: VersionFormatForPublish.original + }, + exemptFromRushChange: true, + includeEmailInChangeFile: true, + lockedMajor: 3 + }; + + const nextJson: IIndividualVersionJson = new IndividualVersionPolicy(originalJson)._json; + expect(nextJson).toMatchObject(originalJson); + }); }); }); From 5fc82ca84e1f0150db2d8301ddae7575a85200f5 Mon Sep 17 00:00:00 2001 From: David Michon Date: Wed, 20 Aug 2025 00:21:52 +0000 Subject: [PATCH 2/2] Fix issues --- common/reviews/api/rush-lib.api.md | 44 +++++++++++++++---- libraries/rush-lib/src/api/VersionPolicy.ts | 24 ++++++---- .../src/api/VersionPolicyConfiguration.ts | 38 ++++++++++++---- .../src/api/test/VersionPolicy.test.ts | 10 ++--- libraries/rush-lib/src/index.ts | 7 ++- 5 files changed, 90 insertions(+), 33 deletions(-) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index 842692bb49f..f3ffee7a543 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -538,6 +538,12 @@ export interface IGetChangedProjectsOptions { export interface IGlobalCommand extends IRushCommand { } +// @public +export interface IIndividualVersionJson extends IVersionPolicyJson { + // (undocumented) + lockedMajor?: number; +} + // @beta export interface IInputsSnapshot { getOperationOwnStateHash(project: IRushConfigurationProjectForSnapshot, operationName?: string): string; @@ -556,6 +562,16 @@ export interface ILaunchOptions { terminalProvider?: ITerminalProvider; } +// @public +export interface ILockStepVersionJson extends IVersionPolicyJson { + // (undocumented) + mainProject?: string; + // (undocumented) + nextBump?: string; + // (undocumented) + version: string; +} + // @alpha export interface ILogFilePaths { error: string; @@ -579,9 +595,7 @@ export class IndividualVersionPolicy extends VersionPolicy { constructor(versionPolicyJson: IIndividualVersionJson); bump(bumpType?: BumpType, identifier?: string): void; ensure(project: IPackageJson, force?: boolean): IPackageJson | undefined; - // Warning: (ae-forgotten-export) The symbol "IIndividualVersionJson" needs to be exported by the entry point index.d.ts - // - // (undocumented) + // @internal (undocumented) readonly _json: IIndividualVersionJson; get lockedMajor(): number | undefined; validate(versionString: string, packageName: string): void; @@ -922,6 +936,22 @@ export interface ITryFindRushJsonLocationOptions { startingFolder?: string; } +// @public +export interface IVersionPolicyJson { + // (undocumented) + definitionName: string; + // Warning: (ae-forgotten-export) The symbol "IVersionPolicyDependencyJson" needs to be exported by the entry point index.d.ts + // + // (undocumented) + dependencies?: IVersionPolicyDependencyJson; + // (undocumented) + exemptFromRushChange?: boolean; + // (undocumented) + includeEmailInChangeFile?: boolean; + // (undocumented) + policyName: string; +} + // @internal export interface _IYarnOptionsJson extends IPackageManagerOptionsJsonBase { ignoreEngines?: boolean; @@ -933,10 +963,8 @@ export class LockStepVersionPolicy extends VersionPolicy { constructor(versionPolicyJson: ILockStepVersionJson); bump(bumpType?: BumpType, identifier?: string): void; ensure(project: IPackageJson, force?: boolean): IPackageJson | undefined; - // Warning: (ae-forgotten-export) The symbol "ILockStepVersionJson" needs to be exported by the entry point index.d.ts - // - // (undocumented) - _json: ILockStepVersionJson; + // @internal (undocumented) + readonly _json: ILockStepVersionJson; get mainProject(): string | undefined; get nextBump(): BumpType | undefined; update(newVersionString: string): boolean; @@ -1611,8 +1639,6 @@ export abstract class VersionPolicy { get exemptFromRushChange(): boolean; get includeEmailInChangeFile(): boolean; get isLockstepped(): boolean; - // Warning: (ae-forgotten-export) The symbol "IVersionPolicyJson" needs to be exported by the entry point index.d.ts - // // @internal readonly _json: IVersionPolicyJson; // @internal diff --git a/libraries/rush-lib/src/api/VersionPolicy.ts b/libraries/rush-lib/src/api/VersionPolicy.ts index e3f3b92ee65..f2a18967836 100644 --- a/libraries/rush-lib/src/api/VersionPolicy.ts +++ b/libraries/rush-lib/src/api/VersionPolicy.ts @@ -4,10 +4,10 @@ import * as semver from 'semver'; import { type IPackageJson, Enum } from '@rushstack/node-core-library'; -import { - type IVersionPolicyJson, - type ILockStepVersionJson, - type IIndividualVersionJson, +import type { + IVersionPolicyJson, + ILockStepVersionJson, + IIndividualVersionJson, VersionFormatForCommit, VersionFormatForPublish } from './VersionPolicyConfiguration'; @@ -64,11 +64,11 @@ export abstract class VersionPolicy { public readonly _json: IVersionPolicyJson; private get _versionFormatForCommit(): VersionFormatForCommit { - return this._json.dependencies?.versionFormatForCommit ?? VersionFormatForCommit.original; + return this._json.dependencies?.versionFormatForCommit ?? 'original'; } private get _versionFormatForPublish(): VersionFormatForPublish { - return this._json.dependencies?.versionFormatForPublish ?? VersionFormatForPublish.original; + return this._json.dependencies?.versionFormatForPublish ?? 'original'; } /** @@ -164,7 +164,7 @@ export abstract class VersionPolicy { * to values used for publishing. */ public setDependenciesBeforePublish(packageName: string, configuration: RushConfiguration): void { - if (this._versionFormatForPublish === VersionFormatForPublish.exact) { + if (this._versionFormatForPublish === 'exact') { const project: RushConfigurationProject = configuration.getProjectByName(packageName)!; const packageJsonEditor: PackageJsonEditor = project.packageJsonEditor; @@ -190,7 +190,7 @@ export abstract class VersionPolicy { * to values used for checked-in source. */ public setDependenciesBeforeCommit(packageName: string, configuration: RushConfiguration): void { - if (this._versionFormatForCommit === VersionFormatForCommit.wildcard) { + if (this._versionFormatForCommit === 'wildcard') { const project: RushConfigurationProject = configuration.getProjectByName(packageName)!; const packageJsonEditor: PackageJsonEditor = project.packageJsonEditor; @@ -215,7 +215,10 @@ export abstract class VersionPolicy { * @public */ export class LockStepVersionPolicy extends VersionPolicy { - public declare _json: ILockStepVersionJson; + /** + * @internal + */ + public declare readonly _json: ILockStepVersionJson; private _version: semver.SemVer; /** @@ -334,6 +337,9 @@ export class LockStepVersionPolicy extends VersionPolicy { * @public */ export class IndividualVersionPolicy extends VersionPolicy { + /** + * @internal + */ public declare readonly _json: IIndividualVersionJson; /** diff --git a/libraries/rush-lib/src/api/VersionPolicyConfiguration.ts b/libraries/rush-lib/src/api/VersionPolicyConfiguration.ts index 01382f02852..eff69dc2faa 100644 --- a/libraries/rush-lib/src/api/VersionPolicyConfiguration.ts +++ b/libraries/rush-lib/src/api/VersionPolicyConfiguration.ts @@ -7,6 +7,12 @@ import { VersionPolicy, type BumpType, type LockStepVersionPolicy } from './Vers import type { RushConfigurationProject } from './RushConfigurationProject'; import schemaJson from '../schemas/version-policies.schema.json'; +/** + * This interface represents the raw version policy JSON object which allows repo + * maintainers to define how different groups of projects will be published by Rush, + * and how their version numbers will be determined. + * @public + */ export interface IVersionPolicyJson { policyName: string; definitionName: string; @@ -15,26 +21,42 @@ export interface IVersionPolicyJson { includeEmailInChangeFile?: boolean; } +/** + * This interface represents the raw lock-step version policy JSON object which extends the base version policy + * with additional fields specific to lock-step versioning. + * @public + */ export interface ILockStepVersionJson extends IVersionPolicyJson { version: string; nextBump?: string; mainProject?: string; } +/** + * This interface represents the raw individual version policy JSON object which extends the base version policy + * with additional fields specific to individual versioning. + * @public + */ export interface IIndividualVersionJson extends IVersionPolicyJson { lockedMajor?: number; } -export enum VersionFormatForPublish { - original = 'original', - exact = 'exact' -} +/** + * @public + */ +export type VersionFormatForPublish = 'original' | 'exact'; -export enum VersionFormatForCommit { - wildcard = 'wildcard', - original = 'original' -} +/** + * @public + */ +export type VersionFormatForCommit = 'wildcard' | 'original'; +/** + * This interface represents the `dependencies` field in a version policy JSON object, + * allowing repo maintainers to specify how dependencies' versions should be handled + * during publishing and committing. + * @public + */ export interface IVersionPolicyDependencyJson { versionFormatForPublish?: VersionFormatForPublish; versionFormatForCommit?: VersionFormatForCommit; diff --git a/libraries/rush-lib/src/api/test/VersionPolicy.test.ts b/libraries/rush-lib/src/api/test/VersionPolicy.test.ts index edc0bbcedd0..738798be560 100644 --- a/libraries/rush-lib/src/api/test/VersionPolicy.test.ts +++ b/libraries/rush-lib/src/api/test/VersionPolicy.test.ts @@ -5,8 +5,6 @@ import type { IPackageJson } from '@rushstack/node-core-library'; import { type ILockStepVersionJson, - VersionFormatForCommit, - VersionFormatForPublish, VersionPolicyConfiguration, type IIndividualVersionJson } from '../VersionPolicyConfiguration'; @@ -125,8 +123,8 @@ describe(VersionPolicy.name, () => { definitionName: 'lockStepVersion', policyName: 'test', dependencies: { - versionFormatForCommit: VersionFormatForCommit.original, - versionFormatForPublish: VersionFormatForPublish.original + versionFormatForCommit: 'original', + versionFormatForPublish: 'original' }, exemptFromRushChange: true, includeEmailInChangeFile: true, @@ -190,8 +188,8 @@ describe(VersionPolicy.name, () => { definitionName: 'individualVersion', policyName: 'test', dependencies: { - versionFormatForCommit: VersionFormatForCommit.original, - versionFormatForPublish: VersionFormatForPublish.original + versionFormatForCommit: 'wildcard', + versionFormatForPublish: 'exact' }, exemptFromRushChange: true, includeEmailInChangeFile: true, diff --git a/libraries/rush-lib/src/index.ts b/libraries/rush-lib/src/index.ts index 685720e0629..85a59f9ad77 100644 --- a/libraries/rush-lib/src/index.ts +++ b/libraries/rush-lib/src/index.ts @@ -107,7 +107,12 @@ export { VersionPolicy } from './api/VersionPolicy'; -export { VersionPolicyConfiguration } from './api/VersionPolicyConfiguration'; +export { + VersionPolicyConfiguration, + type ILockStepVersionJson, + type IIndividualVersionJson, + type IVersionPolicyJson +} from './api/VersionPolicyConfiguration'; export { type ILaunchOptions, Rush } from './api/Rush'; export { RushInternals as _RushInternals } from './api/RushInternals';