diff --git a/packages/angular_devkit/architect/BUILD.bazel b/packages/angular_devkit/architect/BUILD.bazel index af9a6beba333..33f3e399983e 100644 --- a/packages/angular_devkit/architect/BUILD.bazel +++ b/packages/angular_devkit/architect/BUILD.bazel @@ -5,15 +5,13 @@ load("@npm//@angular/build-tooling/bazel/api-golden:index.bzl", "api_golden_test_npm_package") load("@npm//@bazel/jasmine:index.bzl", "jasmine_node_test") -load("//tools:defaults.bzl", "pkg_npm") -load("//tools:interop.bzl", "ts_project") +load("//tools:defaults2.bzl", "npm_package", "ts_project") load("//tools:ts_json_schema.bzl", "ts_json_schema") licenses(["notice"]) package(default_visibility = ["//visibility:public"]) -# @external_begin ts_json_schema( name = "builder_input_schema", src = "src/input-schema.json", @@ -38,7 +36,14 @@ ts_json_schema( name = "operator_schema", src = "builders/operator-schema.json", ) -# @external_end + +JSON_FILES = glob( + include = ["**/*.json"], + exclude = [ + # NB: we need to exclude the nested node_modules that is laid out by yarn workspaces + "node_modules/**", + ], +) ts_project( name = "architect", @@ -57,13 +62,8 @@ ts_project( "//packages/angular_devkit/architect:src/progress-schema.ts", "//packages/angular_devkit/architect:builders/operator-schema.ts", ], - data = glob( - include = ["**/*.json"], - exclude = [ - # NB: we need to exclude the nested node_modules that is laid out by yarn workspaces - "node_modules/**", - ], - ), + # Ensure tests can execute the output JS, relying on schemas/JSON files. + data = JSON_FILES, module_name = "@angular-devkit/architect", deps = [ "//:root_modules/@types/node", @@ -99,18 +99,18 @@ genrule( cmd = "cp $(execpath //:LICENSE) $@", ) -pkg_npm( - name = "npm_package", +npm_package( + name = "pkg", pkg_deps = [ "//packages/angular_devkit/core:package.json", ], tags = ["release-package"], - deps = [ - ":README.md", - ":architect", + deps = JSON_FILES + [ + "README.md", + ":architect_rjs", ":license", - "//packages/angular_devkit/architect/node", - "//packages/angular_devkit/architect/testing", + "//packages/angular_devkit/architect/node:node_rjs", + "//packages/angular_devkit/architect/testing:testing_rjs", ], ) diff --git a/scripts/build-packages-dist.mts b/scripts/build-packages-dist.mts index 8bb720a97f27..dcbe6c601a58 100644 --- a/scripts/build-packages-dist.mts +++ b/scripts/build-packages-dist.mts @@ -31,7 +31,7 @@ const bazelCmd = process.env.BAZEL || `yarn bazel`; /** Command that queries Bazel for all release package targets. */ const queryPackagesCmd = `${bazelCmd} query --output=label "attr('tags', '\\[.*${releaseTargetTag}.*\\]', //packages/...) ` + - `intersect kind('ng_package|pkg_npm', //packages/...)"`; + `intersect kind('ng_package|pkg_npm|^_npm_package rule$', //packages/...)"`; /** Path for the default distribution output directory. */ const defaultDistPath = join(projectDir, 'dist/releases'); diff --git a/tools/bazel/BUILD.bazel b/tools/bazel/BUILD.bazel new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tools/bazel/npm_package.bzl b/tools/bazel/npm_package.bzl new file mode 100644 index 000000000000..0d830c5f96b3 --- /dev/null +++ b/tools/bazel/npm_package.bzl @@ -0,0 +1,106 @@ +load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") +load("@aspect_bazel_lib//lib:expand_template.bzl", "expand_template") +load("@aspect_bazel_lib//lib:jq.bzl", "jq") +load("@aspect_bazel_lib//lib:utils.bzl", "to_label") +load("@aspect_rules_js//npm:defs.bzl", _npm_package = "npm_package") +load("@rules_pkg//:pkg.bzl", "pkg_tar") +load("//tools:link_package_json_to_tarballs.bzl", "link_package_json_to_tarballs") +load("//tools:snapshot_repo_filter.bzl", "SNAPSHOT_REPO_JQ_FILTER") +load("//tools:substitutions.bzl", "NO_STAMP_PACKAGE_SUBSTITUTIONS", "get_npm_package_substitutions_for_rjs") + +def npm_package( + name, + deps = [], + visibility = None, + pkg_deps = [], + pkg_json = "package.json", + **kwargs): + if name != "pkg": + fail("Expected npm_package to be named `pkg`. " + + "This is needed for pnpm workspace integration.") + + # Merge package.json with root package.json and perform various substitutions to + # prepare it for release. For jq docs, see https://stedolan.github.io/jq/manual/. + jq( + name = "basic_substitutions", + # Note: this jq filter relies on the order of the inputs + # buildifier: do not sort + srcs = ["//:package.json", pkg_json], + filter_file = "//tools:package_json_release_filter.jq", + args = ["--slurp"], + out = "substituted/package.json", + ) + + # Copy package.json files to bazel-out so we can use their bazel-out paths to determine + # the corresponding package npm package tgz path for substitutions. + copy_to_bin( + name = "package_json_copy", + srcs = [pkg_json], + ) + pkg_deps_copies = [] + for pkg_dep in pkg_deps: + pkg_label = to_label(pkg_dep) + if pkg_label.name != "package.json": + fail("ERROR: only package.json files allowed in pkg_deps of pkg_npm macro") + pkg_deps_copies.append("@%s//%s:package_json_copy" % (pkg_label.workspace_name, pkg_label.package)) + + # Substitute dependencies on other packages in this repo with tarballs. + link_package_json_to_tarballs( + name = "tar_substitutions", + src = "substituted/package.json", + pkg_deps = [":package_json_copy"] + pkg_deps_copies, + out = "substituted_with_tars/package.json", + ) + + # Substitute dependencies on other packages in this repo with snapshot repos. + jq( + name = "snapshot_repo_substitutions", + srcs = ["substituted/package.json"], + filter = SNAPSHOT_REPO_JQ_FILTER, + out = "substituted_with_snapshot_repos/package.json", + ) + + expand_template( + name = "final_package_json", + template = select({ + # Do local tar substitution if config_setting is true. + "//:package_json_use_tar_deps": "substituted_with_tars/package.json", + # Do snapshot repo substitution if config_setting is true. + "//:package_json_use_snapshot_repo_deps": "substituted_with_snapshot_repos/package.json", + "//conditions:default": "substituted/package.json", + }), + out = "substituted_final/package.json", + substitutions = NO_STAMP_PACKAGE_SUBSTITUTIONS, + stamp_substitutions = get_npm_package_substitutions_for_rjs(), + ) + + _npm_package( + name = "npm_package", + visibility = visibility, + # Note: Order matters here! Last file takes precedence after replaced prefixes. + srcs = deps + [":final_package_json"], + replace_prefixes = { + "substituted_final/": "", + "substituted_with_tars/": "", + "substituted_with_snapshot_repos/": "", + "substituted/": "", + }, + allow_overwrites = True, + **kwargs + ) + + # Note: For now, in hybrid mode with RNJS and RJS, we ensure + # both `:pkg` and `:npm_package` work. + native.alias( + name = "pkg", + actual = ":npm_package", + ) + + if pkg_json: + pkg_tar( + name = "npm_package_archive", + srcs = [":pkg"], + extension = "tgz", + strip_prefix = "./npm_package", + visibility = visibility, + ) diff --git a/tools/defaults.bzl b/tools/defaults.bzl index 598c3c667ceb..e7e64c2a3e4d 100644 --- a/tools/defaults.bzl +++ b/tools/defaults.bzl @@ -8,30 +8,13 @@ load("@build_bazel_rules_nodejs//:index.bzl", _js_library = "js_library", _pkg_n load("@npm//@angular/bazel:index.bzl", _ng_module = "ng_module", _ng_package = "ng_package") load("@npm//@angular/build-tooling/bazel:extract_js_module_output.bzl", "extract_js_module_output") load("@rules_pkg//:pkg.bzl", "pkg_tar") -load("//:constants.bzl", "RELEASE_ENGINES_NODE", "RELEASE_ENGINES_NPM", "RELEASE_ENGINES_YARN") load("//tools:link_package_json_to_tarballs.bzl", "link_package_json_to_tarballs") load("//tools:snapshot_repo_filter.bzl", "SNAPSHOT_REPO_JQ_FILTER") +load("//tools:substitutions.bzl", "NO_STAMP_PACKAGE_SUBSTITUTIONS", "NPM_PACKAGE_SUBSTITUTIONS") _DEFAULT_TSCONFIG_NG = "//:tsconfig-build-ng" _DEFAULT_TSCONFIG_TEST = "//:tsconfig-test.json" -NPM_PACKAGE_SUBSTITUTIONS = { - # Version of the local package being built, generated via the `--workspace_status_command` flag. - "0.0.0-PLACEHOLDER": "{STABLE_PROJECT_VERSION}", - "0.0.0-EXPERIMENTAL-PLACEHOLDER": "{STABLE_PROJECT_EXPERIMENTAL_VERSION}", - "BUILD_SCM_HASH-PLACEHOLDER": "{BUILD_SCM_ABBREV_HASH}", - "0.0.0-ENGINES-NODE": RELEASE_ENGINES_NODE, - "0.0.0-ENGINES-NPM": RELEASE_ENGINES_NPM, - "0.0.0-ENGINES-YARN": RELEASE_ENGINES_YARN, - # The below is needed for @angular/ssr FESM file. - "\\./(.+)/packages/angular/ssr/third_party/beasties": "../third_party/beasties/index.js", -} - -NO_STAMP_PACKAGE_SUBSTITUTIONS = dict(NPM_PACKAGE_SUBSTITUTIONS, **{ - "0.0.0-PLACEHOLDER": "0.0.0", - "0.0.0-EXPERIMENTAL-PLACEHOLDER": "0.0.0", -}) - def _default_module_name(testonly): """ Provide better defaults for package names. diff --git a/tools/defaults2.bzl b/tools/defaults2.bzl new file mode 100644 index 000000000000..aa2ff22e383a --- /dev/null +++ b/tools/defaults2.bzl @@ -0,0 +1,8 @@ +load("//tools:interop.bzl", _ts_project = "ts_project") +load("//tools/bazel:npm_package.bzl", _npm_package = "npm_package") + +def ts_project(**kwargs): + _ts_project(**kwargs) + +def npm_package(**kwargs): + _npm_package(**kwargs) diff --git a/tools/substitutions.bzl b/tools/substitutions.bzl new file mode 100644 index 000000000000..4b8b9c79c0af --- /dev/null +++ b/tools/substitutions.bzl @@ -0,0 +1,26 @@ +load("//:constants.bzl", "RELEASE_ENGINES_NODE", "RELEASE_ENGINES_NPM", "RELEASE_ENGINES_YARN") + +NPM_PACKAGE_SUBSTITUTIONS = { + # Version of the local package being built, generated via the `--workspace_status_command` flag. + "0.0.0-PLACEHOLDER": "{STABLE_PROJECT_VERSION}", + "0.0.0-EXPERIMENTAL-PLACEHOLDER": "{STABLE_PROJECT_EXPERIMENTAL_VERSION}", + "BUILD_SCM_HASH-PLACEHOLDER": "{BUILD_SCM_ABBREV_HASH}", + "0.0.0-ENGINES-NODE": RELEASE_ENGINES_NODE, + "0.0.0-ENGINES-NPM": RELEASE_ENGINES_NPM, + "0.0.0-ENGINES-YARN": RELEASE_ENGINES_YARN, + # The below is needed for @angular/ssr FESM file. + "\\./(.+)/packages/angular/ssr/third_party/beasties": "../third_party/beasties/index.js", +} + +NO_STAMP_PACKAGE_SUBSTITUTIONS = dict(NPM_PACKAGE_SUBSTITUTIONS, **{ + "0.0.0-PLACEHOLDER": "0.0.0", + "0.0.0-EXPERIMENTAL-PLACEHOLDER": "0.0.0", +}) + +def get_npm_package_substitutions_for_rjs(): + result = {} + for key, value in NPM_PACKAGE_SUBSTITUTIONS.items(): + # in `rules_js`, or `expand_template` from `bazel-lib`, stamp variables + # can only be retrieved via `{{X}}` syntax. + result[key] = value.replace("{", "{{").replace("}", "}}") + return result