From dc9872e03609a342a00f8bc0947b14f983a7f939 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:33:55 +0000 Subject: [PATCH] feat(ng-dev): add `sync-module-bazel` command to update `pnpm` and `typescript` versions and integrity in `MODULE.bazel` This is helpful to ensure that these versions are always synced. --- MODULE.bazel | 3 +- MODULE.bazel.lock | 8 +- ng-dev/misc/cli.ts | 2 + ng-dev/misc/sync-module-bazel/cli.ts | 120 +++++++++++++++++++++++++++ renovate-presets/default.json5 | 17 +++- renovate.json | 1 + 6 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 ng-dev/misc/sync-module-bazel/cli.ts diff --git a/MODULE.bazel b/MODULE.bazel index 667e8aed6..d8675375c 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -68,7 +68,8 @@ use_repo(rules_angular, rules_angular_configurable_deps = "dev_infra_rules_angul pnpm = use_extension("@aspect_rules_js//npm:extensions.bzl", "pnpm") pnpm.pnpm( name = "pnpm", - pnpm_version = "10.16.1", + pnpm_version = "10.26.0", + pnpm_version_integrity = "sha512-Oz9scl6+cSUGwKsa1BM8+GsfS2h+/85iqbOLTXLjlUJC5kMZD8UfoWQpScc19APevUT1yw7dZXq+Y6i2p+HkAg==", ) use_repo(pnpm, "pnpm") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 3b8c6f87a..eece14a32 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -404,7 +404,7 @@ "@@aspect_rules_js+//npm:extensions.bzl%pnpm": { "general": { "bzlTransitiveDigest": "tQ+7EwLfQwqi/T4v5/N3NNHTmP6Wu/FqXxRDndEB2OU=", - "usagesDigest": "ZhbEw77ZyU/0//oi7ZG1g+T//EAz9+tI3MinUH//LSo=", + "usagesDigest": "yvNsPkPdz1NdbmMmM3BXRO+s1FJ8T1pFsHK6OLF1Zcs=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, @@ -413,11 +413,11 @@ "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_rule", "attributes": { "package": "pnpm", - "version": "10.16.1", + "version": "10.26.0", "root_package": "", "link_workspace": "", "link_packages": {}, - "integrity": "sha512-DhVaomKduGcrSehHXaYiaqS96oX9zf3BU1CHSUbU88kfqvZMvcSl0auAAvRz1cP87c0ZeYnPA5D5ut08BGeHBg==", + "integrity": "sha512-Oz9scl6+cSUGwKsa1BM8+GsfS2h+/85iqbOLTXLjlUJC5kMZD8UfoWQpScc19APevUT1yw7dZXq+Y6i2p+HkAg==", "url": "", "commit": "", "patch_args": [ @@ -440,7 +440,7 @@ "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_links", "attributes": { "package": "pnpm", - "version": "10.16.1", + "version": "10.26.0", "dev": false, "root_package": "", "link_packages": {}, diff --git a/ng-dev/misc/cli.ts b/ng-dev/misc/cli.ts index 79242f2ae..8c4f22e6e 100644 --- a/ng-dev/misc/cli.ts +++ b/ng-dev/misc/cli.ts @@ -7,6 +7,7 @@ */ import {Argv} from 'yargs'; +import {SyncModuleBazelModule} from './sync-module-bazel/cli.js'; import {BuildAndLinkCommandModule} from './build-and-link/cli.js'; import {GeneratedFilesModule} from './generated-files/cli.js'; import {GeneratedNodeJsToolchainModule} from './generate-nodejs-toolchain/cli.js'; @@ -16,6 +17,7 @@ export function buildMiscParser(localYargs: Argv) { return localYargs .help() .strict() + .command(SyncModuleBazelModule) .command(BuildAndLinkCommandModule) .command(GeneratedFilesModule) .command(GeneratedNodeJsToolchainModule); diff --git a/ng-dev/misc/sync-module-bazel/cli.ts b/ng-dev/misc/sync-module-bazel/cli.ts new file mode 100644 index 000000000..2b25ea0b5 --- /dev/null +++ b/ng-dev/misc/sync-module-bazel/cli.ts @@ -0,0 +1,120 @@ +/** + * @license + * Copyright Google LLC + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Argv, CommandModule} from 'yargs'; +import {readFileSync, writeFileSync} from 'node:fs'; +import {execSync} from 'node:child_process'; +import {join} from 'node:path'; +import {Log} from '../../utils/logging'; + +async function builder(argv: Argv) { + return argv; +} + +async function handler() { + const rootDir = process.cwd(); + const packageJsonPath = join(rootDir, 'package.json'); + const moduleBazelPath = join(rootDir, 'MODULE.bazel'); + + interface PackageJson { + engines?: { + pnpm?: string; + }; + dependencies?: { + typescript?: string; + }; + devDependencies?: { + typescript?: string; + }; + } + + // Read package.json + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as PackageJson; + const pnpmVersion = packageJson.engines?.pnpm; + const tsVersion = packageJson.dependencies?.typescript || packageJson.devDependencies?.typescript; + + if (!pnpmVersion) { + throw new Error('Could not find engines.pnpm in package.json'); + } + + if (!tsVersion) { + throw new Error('Could not find typescript in dependencies or devDependencies in package.json'); + } + + // Helper to get integrity + async function getIntegrity(pkg: string, version: string): Promise { + const response = await fetch(`https://registry.npmjs.org/${pkg}/${version}`); + if (!response.ok) { + throw new Error(`Failed to request ${pkg}@${version}: ${response.statusText}`); + } + + const {dist} = (await response.json()) as {dist: {integrity: string}}; + + return dist.integrity; + } + + // Read MODULE.bazel + let originalBazelContent = readFileSync(moduleBazelPath, 'utf8'); + let moduleBazelContent = originalBazelContent; + + if (moduleBazelContent.includes('pnpm_version')) { + console.log(`Resolving integrity for pnpm@${pnpmVersion}...`); + const pnpmIntegrity = await getIntegrity('pnpm', pnpmVersion); + + // Update pnpm version and integrity + moduleBazelContent = moduleBazelContent.replace( + /pnpm_version = ".*?"/, + `pnpm_version = "${pnpmVersion}"`, + ); + + if (moduleBazelContent.includes('pnpm_version_integrity =')) { + moduleBazelContent = moduleBazelContent.replace( + /pnpm_version_integrity = ".*?"/, + `pnpm_version_integrity = "${pnpmIntegrity}"`, + ); + } else { + moduleBazelContent = moduleBazelContent.replace( + /pnpm_version = ".*?"/, + `$&,\n pnpm_version_integrity = "${pnpmIntegrity}"`, + ); + } + } + + // Update typescript version and integrity + if (moduleBazelContent.includes('ts_version')) { + console.log(`Resolving integrity for typescript@${tsVersion}...`); + const tsIntegrity = await getIntegrity('typescript', tsVersion); + + moduleBazelContent = moduleBazelContent.replace( + /ts_version = ".*?"/, + `ts_version = "${tsVersion}"`, + ); + moduleBazelContent = moduleBazelContent.replace( + /ts_integrity = ".*?"/, + `ts_integrity = "${tsIntegrity}"`, + ); + + if (originalBazelContent !== moduleBazelContent) { + writeFileSync(moduleBazelPath, moduleBazelContent); + + try { + execSync('pnpm bazel mod deps --lockfile_mode=update', {stdio: 'inherit'}); + } catch (e) { + Log.debug(e); + } + } + } +} + +/** CLI command module. */ +export const SyncModuleBazelModule: CommandModule = { + builder, + handler, + command: 'sync-module-bazel', + describe: 'Sync pnpm and typescript versions in MODULE.bazel with package.json.', +}; diff --git a/renovate-presets/default.json5 b/renovate-presets/default.json5 index 9382156a0..723f6af37 100644 --- a/renovate-presets/default.json5 +++ b/renovate-presets/default.json5 @@ -48,6 +48,7 @@ // Workaround for https://github.com/renovatebot/renovate/issues/25557 { postUpgradeTasks: { + matchManagers: ['bazel', 'bazel-module', 'bazelisk'], commands: [ 'git restore .npmrc || true', // In case `.npmrc` avoid a hard error. 'bazel mod deps --lockfile_mode=update', @@ -56,7 +57,21 @@ // run when in the same branch there are mixtures of update types by different managers. executionMode: 'update', }, - matchManagers: ['bazel', 'bazel-module', 'bazelisk'], + }, + + // Enable 'postUpdateTasks' for changes that effect the typescript and pnpm versions. + // This is to ensure that the `MODULE.bazel` is updated with the correct versions. + { + matchManagers: ['npm'], + matchDepNames: ['pnpm', 'typescript'], + postUpgradeTasks: { + commands: [ + 'git restore .npmrc || true', // In case `.npmrc` avoid a hard error. + 'pnpm install --frozen-lockfile', + 'pnpm ng-dev misc sync-module-bazel || true', // TODO(alanagius): Remove || true once all repos update to the latest ng-dev. + ], + executionMode: 'branch', + }, }, // Rule to require manual approval for NPM updates on branches other than 'main'. diff --git a/renovate.json b/renovate.json index ec81023c1..9a4c85934 100644 --- a/renovate.json +++ b/renovate.json @@ -13,6 +13,7 @@ "commands": [ "pnpm install --frozen-lockfile", "pnpm bazel mod deps --lockfile_mode=update", + "pnpm ng-dev misc sync-module-bazel", "pnpm update-generated-files" ], "executionMode": "branch"