Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions common/reviews/api/rush-lib.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,7 @@ export interface IGenerateCacheEntryIdOptions {
// @beta (undocumented)
export interface IGetChangedProjectsOptions {
enableFiltering: boolean;
excludeVersionOnlyChanges?: boolean;
includeExternalDependencies: boolean;
// (undocumented)
shouldFetch?: boolean;
Expand Down
4 changes: 3 additions & 1 deletion libraries/rush-lib/src/cli/actions/ChangeAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,9 @@ export class ChangeAction extends BaseRushAction {
// Not enabling, since this would be a breaking change
includeExternalDependencies: false,
// Since install may not have happened, cannot read rush-project.json
enableFiltering: false
enableFiltering: false,
// Exclude version-only changes to prevent 'rush version --bump' from triggering 'rush change --verify'
excludeVersionOnlyChanges: true
});
const projectHostMap: Map<RushConfigurationProject, string> = this._generateHostMap();

Expand Down
92 changes: 90 additions & 2 deletions libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ export interface IGetChangedProjectsOptions {
* and exclude matched files from change detection.
*/
enableFiltering: boolean;

/**
* If set to `true`, for each project, if the only change to the project is to its `package.json`,
* diffs the file against the base version to see if the only change is to the value of the "version" field,
* and if it is, ignores the change to the project.
*/
excludeVersionOnlyChanges?: boolean;
}

/**
Expand Down Expand Up @@ -83,8 +90,15 @@ export class ProjectChangeAnalyzer {
): Promise<Set<RushConfigurationProject>> {
const { _rushConfiguration: rushConfiguration } = this;

const { targetBranchName, terminal, includeExternalDependencies, enableFiltering, shouldFetch, variant } =
options;
const {
targetBranchName,
terminal,
includeExternalDependencies,
enableFiltering,
shouldFetch,
variant,
excludeVersionOnlyChanges
} = options;

const gitPath: string = this._git.getGitPathOrThrow();
const repoRoot: string = getRepoRoot(rushConfiguration.rushJsonFolder);
Expand Down Expand Up @@ -130,6 +144,36 @@ export class ProjectChangeAnalyzer {
}
}

// Check for version-only changes if excludeVersionOnlyChanges is enabled
if (excludeVersionOnlyChanges) {
Comment thread
dmichon-msft marked this conversation as resolved.
Outdated
const projectsToCheck: RushConfigurationProject[] = Array.from(changedProjects);
await Async.forEachAsync(
projectsToCheck,
async (project) => {
const projectChanges: Map<string, IFileDiffStatus> | undefined = changesByProject.get(project);
if (projectChanges && projectChanges.size === 1) {
// Check if the only change is to package.json
const packageJsonPath: string = Path.convertToSlashes(
path.relative(repoRoot, path.join(project.projectFolder, 'package.json'))
);
const diffStatus: IFileDiffStatus | undefined = projectChanges.get(packageJsonPath);
if (diffStatus) {
const isVersionOnlyChange: boolean = await this._isVersionOnlyChangeAsync(
project,
packageJsonPath,
diffStatus,
repoRoot
);
if (isVersionOnlyChange) {
changedProjects.delete(project);
}
}
}
},
{ concurrency: 10 }
);
}
Comment thread
dmichon-msft marked this conversation as resolved.
Outdated

// External dependency changes are not allowed to be filtered, so add these after filtering
if (includeExternalDependencies) {
// Even though changing the installed version of a nested dependency merits a change file,
Expand Down Expand Up @@ -395,6 +439,50 @@ export class ProjectChangeAnalyzer {
}
}

/**
* Checks if the only change to a package.json file is to the "version" field.
* @internal
*/
private async _isVersionOnlyChangeAsync(
project: RushConfigurationProject,
packageJsonPath: string,
diffStatus: IFileDiffStatus,
repoRoot: string
): Promise<boolean> {
try {
Comment thread
dmichon-msft marked this conversation as resolved.
Outdated
// Get the old version of package.json from Git using the blob id from IFileDiffStatus
const oldPackageJsonContent: string = await this._git.getBlobContentAsync({
blobSpec: diffStatus.oldhash,
repositoryRoot: repoRoot
});

// Get the current version of package.json
const currentPackageJsonPath: string = path.join(repoRoot, packageJsonPath);
const currentPackageJsonContent: string = await FileSystem.readFileAsync(currentPackageJsonPath);
Comment thread
dmichon-msft marked this conversation as resolved.
Outdated

// Parse both versions
const oldPackageJson: { version?: string } = JSON.parse(oldPackageJsonContent);
const currentPackageJson: { version?: string } = JSON.parse(currentPackageJsonContent);

// Create copies without the version field for comparison
const oldPackageJsonWithoutVersion: Record<string, unknown> = { ...oldPackageJson };
const currentPackageJsonWithoutVersion: Record<string, unknown> = { ...currentPackageJson };
delete oldPackageJsonWithoutVersion.version;
delete currentPackageJsonWithoutVersion.version;
Comment thread
dmichon-msft marked this conversation as resolved.
Outdated

// Check if the only difference is the version field
const oldVersionlessJson: string = JSON.stringify(oldPackageJsonWithoutVersion, null, 2);
const currentVersionlessJson: string = JSON.stringify(currentPackageJsonWithoutVersion, null, 2);

return (
oldVersionlessJson === currentVersionlessJson && oldPackageJson.version !== currentPackageJson.version
);
Comment thread
dmichon-msft marked this conversation as resolved.
Outdated
} catch (error) {
// If we can't read the file or parse it, assume it's not a version-only change
return false;
}
}

/**
* @internal
*/
Expand Down
Loading