From d0c56ca23919f31ea70fcf3cc6980c1fdf29afa5 Mon Sep 17 00:00:00 2001 From: Joey Perrott Date: Wed, 27 Aug 2025 21:49:15 +0000 Subject: [PATCH 1/2] feat(bazel): provide a validation macro to ensure that the package.json and MODULE.bazel.lock file versions of typescript match --- bazel/validation/BUILD.bazel | 11 ++++ bazel/validation/defs.bzl | 15 ++++++ bazel/validation/verify-typescript.mjs | 69 ++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 bazel/validation/BUILD.bazel create mode 100644 bazel/validation/defs.bzl create mode 100644 bazel/validation/verify-typescript.mjs diff --git a/bazel/validation/BUILD.bazel b/bazel/validation/BUILD.bazel new file mode 100644 index 000000000..0b7e7eb7b --- /dev/null +++ b/bazel/validation/BUILD.bazel @@ -0,0 +1,11 @@ +load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") + +copy_to_bin( + name = "verify-typescript", + srcs = [ + "verify-typescript.mjs", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/bazel/validation/defs.bzl b/bazel/validation/defs.bzl new file mode 100644 index 000000000..6647a5c78 --- /dev/null +++ b/bazel/validation/defs.bzl @@ -0,0 +1,15 @@ +load("@aspect_rules_js//js:defs.bzl", "js_test") + +def validate_ts_version_matching(package_json, module_lock_file): + js_test( + name = "validate_ts_version_match", + data = [ + package_json, + module_lock_file, + ], + entry_point = "@devinfra//bazel/validation:verify-typescript", + fixed_args = [ + "$(rlocationpath %s)" % package_json, + "$(rlocationpath %s)" % module_lock_file, + ], + ) diff --git a/bazel/validation/verify-typescript.mjs b/bazel/validation/verify-typescript.mjs new file mode 100644 index 000000000..c44752be2 --- /dev/null +++ b/bazel/validation/verify-typescript.mjs @@ -0,0 +1,69 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {readFile} from 'node:fs/promises'; +import {join} from 'node:path'; + +/** The runfiles directory for the script. */ +const runfiles = process.env['RUNFILES']; + +async function main([packageJsonPath, moduleLockFilePath]) { + /** The json contents of the BAZEL.module.lock file. */ + const moduleLock = JSON.parse(await readFile(join(runfiles, moduleLockFilePath), 'utf8')); + /** The json contents of the package.json file. */ + const packageJson = JSON.parse(await readFile(join(runfiles, packageJsonPath), 'utf8')); + /** The version of typescript extracted from the package.json file. */ + let packageJsonVersion; + try { + packageJsonVersion = + packageJson['dependencies']?.['typescript'] || packageJson['devDependencies']?.['typescript']; + } catch { + console.error('Unable to find the typescript version within the package.json file.'); + } + + /** The version of typescript extracted from the BAZEL.module.lock file. */ + let lockfileVersion; + try { + // The path to the generated repo specs is static based on the location of the extension + // used. The name of the generated repo is determined by the user so we instead need to take + // the first value/item from the `generaredRepoSpecs` property and get the version from the + // attributes there. + const generatedRepoSpecs = + moduleLock['moduleExtensions']?.['@@aspect_rules_ts~//ts:extensions.bzl%ext']?.['general']?.[ + 'generatedRepoSpecs' + ]; + lockfileVersion = + Object.values(generatedRepoSpecs || {})[0]?.['attributes']?.['version'] || 'unknown'; + } catch { + console.error('Unable to find the typescript version within the MODULE.bazel.lock file.'); + } + + // If either version is undefined, the comparison is invalid and we should exit. + if (packageJsonVersion === undefined || lockfileVersion === undefined) { + process.exitCode = 1; + return; + } + + // If the versions don't match, exit as a failure. + if (packageJsonVersion !== lockfileVersion) { + console.error( + `Typescript version mismatch between MODULE.bazel (${lockfileVersion}) and package.json (${packageJsonVersion})`, + ); + process.exitCode = 1; + return; + } + + console.info( + `Typescript version matches between MODULE.bazel and package.json: ${lockfileVersion}`, + ); +} + +main(process.argv.slice(2)).catch((e) => { + console.error(e); + process.exit(2); +}); From 35073a4785cf792fef7fbf48e2822374d8a389a1 Mon Sep 17 00:00:00 2001 From: Joey Perrott Date: Wed, 27 Aug 2025 21:49:41 +0000 Subject: [PATCH 2/2] build: use version directly in typescript version for module.bazel and validate it with testing --- BUILD.bazel | 7 ++++++- MODULE.bazel | 2 +- MODULE.bazel.lock | 6 ++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/BUILD.bazel b/BUILD.bazel index 539bbb76e..15fcd3245 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,6 +1,7 @@ load("@aspect_rules_js//js:defs.bzl", "js_binary") load("@aspect_rules_ts//ts:defs.bzl", "ts_config") load("@devinfra_npm//:defs.bzl", "npm_link_all_packages") +load("//bazel/validation:defs.bzl", "validate_ts_version_matching") npm_link_all_packages() @@ -12,7 +13,6 @@ ts_config( exports_files([ "package.json", - ".yarnrc.yml", ]) js_binary( @@ -28,3 +28,8 @@ js_binary( entry_point = ".yarn/releases/yarn-1.22.17.cjs", visibility = ["//bazel/integration/tests:__subpackages__"], ) + +validate_ts_version_matching( + module_lock_file = "MODULE.bazel.lock", + package_json = "package.json", +) diff --git a/MODULE.bazel b/MODULE.bazel index cc636ca03..e28dbbd69 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -49,7 +49,7 @@ rules_ts_ext = use_extension("@aspect_rules_ts//ts:extensions.bzl", "ext") rules_ts_ext.deps( # Obtained by: curl --silent https://registry.npmjs.org/typescript/5.9.2 | jq -r '.dist.integrity' ts_integrity = "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", - ts_version_from = "//bazel:package.json", + ts_version = "5.9.2", ) use_repo(rules_ts_ext, "npm_typescript") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index a4a184f80..7017994d9 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -542,9 +542,8 @@ "@@aspect_rules_ts~//ts:extensions.bzl%ext": { "general": { "bzlTransitiveDigest": "9IJp6IlB/FMHFBJe4MX/DQM4zi3oArC8yqYE/+NyPwk=", - "usagesDigest": "ebMkyO2v+TgtLWj5q0NfGsbQ3Z4ubfib4KPBeQnPty4=", + "usagesDigest": "XrTgiAyKY3z4XZ1MADJpyq4VUa30V5Bpu++qpMM1QBM=", "recordedFileInputs": { - "@@//bazel/package.json": "f90ae656882e652c88b59c7b94880416dc4a90ef01e8763fd04e8c6f8c2bb6e6", "@@rules_browsers~//package.json": "45572077938c7a4916e4aaedf7db7ce8425854ab92f35348cff02a2134023bb8" }, "recordedDirentsInputs": {}, @@ -554,8 +553,7 @@ "bzlFile": "@@aspect_rules_ts~//ts/private:npm_repositories.bzl", "ruleClassName": "http_archive_version", "attributes": { - "version": "", - "version_from": "@@//bazel:package.json", + "version": "5.9.2", "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "urls": [ "https://registry.npmjs.org/typescript/-/typescript-{}.tgz"