diff --git a/ng-dev/release/publish/BUILD.bazel b/ng-dev/release/publish/BUILD.bazel index c177cfb17..fd4d9d15a 100644 --- a/ng-dev/release/publish/BUILD.bazel +++ b/ng-dev/release/publish/BUILD.bazel @@ -8,6 +8,7 @@ ts_library( visibility = ["//ng-dev:__subpackages__"], deps = [ "//ng-dev/commit-message", + "//ng-dev/pr/common/labels", "//ng-dev/pr/merge", "//ng-dev/release/build", "//ng-dev/release/config", diff --git a/ng-dev/release/publish/actions.ts b/ng-dev/release/publish/actions.ts index 6ff982397..d3077f1f0 100644 --- a/ng-dev/release/publish/actions.ts +++ b/ng-dev/release/publish/actions.ts @@ -44,6 +44,8 @@ import {Prompt} from '../../utils/prompt.js'; import {glob} from 'fast-glob'; import {PnpmVersioning} from './pnpm-versioning.js'; import {Commit} from '../../utils/git/octokit-types.js'; +import {updateRenovateConfigTargetLabels} from './actions/renovate-config-updates.js'; +import {targetLabels} from '../../pr/common/labels/target.js'; /** Interface describing a Github repository. */ export interface GithubRepo { @@ -217,13 +219,27 @@ export abstract class ReleaseAction { } // Commit message for the release point. - const commitMessage = getCommitMessageForRelease(newVersion); const filesToCommit = [ workspaceRelativePackageJsonPath, workspaceRelativeChangelogPath, ...this.getAspectLockFiles(), ]; + if (newVersion.patch === 0 && !newVersion.prerelease) { + // Switch the renovate labels for `target: rc` to `target: patch` + const renovateConfigPath = await updateRenovateConfigTargetLabels( + this.projectDir, + targetLabels['TARGET_RC'].name, + targetLabels['TARGET_PATCH'].name, + ); + + if (renovateConfigPath) { + filesToCommit.push(renovateConfigPath); + } + } + + const commitMessage = getCommitMessageForRelease(newVersion); + // Create a release staging commit including changelog and version bump. await this.createCommit(commitMessage, filesToCommit); diff --git a/ng-dev/release/publish/actions/renovate-config-updates.ts b/ng-dev/release/publish/actions/renovate-config-updates.ts index 9ee6e28a2..7de2ad5ce 100644 --- a/ng-dev/release/publish/actions/renovate-config-updates.ts +++ b/ng-dev/release/publish/actions/renovate-config-updates.ts @@ -2,14 +2,16 @@ import {existsSync} from 'node:fs'; import {green, Log} from '../../../utils/logging.js'; import {join} from 'node:path'; import {writeFile, readFile} from 'node:fs/promises'; +import {targetLabels} from '../../../pr/common/labels/target.js'; /** * Updates the `renovate.json` configuration file to include a new base branch. + * It also updates specific target labels within the package rules. * - * @param projectDir - The project directory path. + * @param projectDir - The path to the project directory. * @param newBranchName - The name of the new branch to add to the base branches list. - * @returns A promise that resolves to an string containing the path to the modified `renovate.json` file, - * or null if config updating is disabled. + * @returns A promise that resolves to the path of the modified `renovate.json` file if updated, + * or `null` if the file was not found or the `baseBranches` array has an unexpected format. */ export async function updateRenovateConfig( projectDir: string, @@ -18,13 +20,13 @@ export async function updateRenovateConfig( const renovateConfigPath = join(projectDir, 'renovate.json'); if (!existsSync(renovateConfigPath)) { Log.warn(` ✘ Skipped updating Renovate config as it was not found.`); - return null; } const config = await readFile(renovateConfigPath, 'utf-8'); const configJson = JSON.parse(config) as Record; const baseBranches = configJson.baseBranches; + if (!Array.isArray(baseBranches) || baseBranches.length !== 2) { Log.warn( ` ✘ Skipped updating Renovate config: "baseBranches" must contain exactly 2 branches.`, @@ -34,8 +36,93 @@ export async function updateRenovateConfig( } configJson.baseBranches = ['main', newBranchName]; + + updateRenovateTargetLabel( + configJson, + targetLabels['TARGET_PATCH'].name, + targetLabels['TARGET_RC'].name, + ); await writeFile(renovateConfigPath, JSON.stringify(configJson, undefined, 2)); - Log.info(green(` ✓ Updated Renovate config.`)); + Log.info(green(` ✓ Updated Renovate config.`)); return renovateConfigPath; } + +/** + * Updates a specific target label in the `renovate.json` configuration file. + * This function specifically targets and replaces one label with another within the `packageRules`. + * + * @param projectDir - The path to the project directory. + * @param fromLabel - The label name to be replaced. + * @param toLabel - The new label name to replace `fromLabel` with. + * @returns A promise that resolves to the path of the modified `renovate.json` file if updated, + * or `null` if the file was not found or the `baseBranches` array has an unexpected format. + */ +export async function updateRenovateConfigTargetLabels( + projectDir: string, + fromLabel: string, + toLabel: string, +): Promise { + const renovateConfigPath = join(projectDir, 'renovate.json'); + if (!existsSync(renovateConfigPath)) { + Log.warn(` ✘ Skipped updating Renovate config as it was not found.`); + + return null; + } + + const config = await readFile(renovateConfigPath, 'utf-8'); + const configJson = JSON.parse(config) as Record; + + // Check baseBranches just in case, though this function's primary focus is labels + const baseBranches = configJson.baseBranches; + if (!Array.isArray(baseBranches) || baseBranches.length !== 2) { + Log.warn( + ` ✘ Skipped updating Renovate config: "baseBranches" must contain exactly 2 branches.`, + ); + + return null; + } + + if (updateRenovateTargetLabel(configJson, fromLabel, toLabel)) { + await writeFile(renovateConfigPath, JSON.stringify(configJson, undefined, 2)); + Log.info(green(` ✓ Updated target label in Renovate config.`)); + + return renovateConfigPath; + } else { + Log.info(green(` ✓ No changes to target labels in Renovate config.`)); + return null; + } +} + +/** + * Updates a specific target label within the `packageRules` of a Renovate configuration. + * + * @param configJson - The parsed JSON object of the Renovate configuration. + * @param fromLabel - The label name to be replaced. + * @param toLabel - The new label name to replace `fromLabel` with. + * @returns `true` is the label has been updated, otherwise `false`. + */ +function updateRenovateTargetLabel( + configJson: Record, + fromLabel: string, + toLabel: string, +): boolean { + if (!Array.isArray(configJson.packageRules)) { + return false; + } + + let updated = false; + for (const rule of configJson.packageRules) { + if (!Array.isArray(rule.addLabels)) { + continue; + } + + const idx = (rule.addLabels as string[]).findIndex((x) => x === fromLabel); + if (idx >= 0) { + rule.addLabels[idx] = toLabel; + updated = true; + } + } + + return updated; +}