| """Adapt npm repository rules to be called from MODULE.bazel |
| See https://bazel.build/docs/bzlmod#extension-definition |
| """ |
| |
| load("@aspect_bazel_lib//lib:repo_utils.bzl", "repo_utils") |
| load("@aspect_bazel_lib//lib:utils.bzl", bazel_lib_utils = "utils") |
| load("@bazel_features//:features.bzl", "bazel_features") |
| load("//npm:repositories.bzl", "npm_import", "pnpm_repository", _DEFAULT_PNPM_VERSION = "DEFAULT_PNPM_VERSION", _LATEST_PNPM_VERSION = "LATEST_PNPM_VERSION") |
| load("//npm/private:npm_import.bzl", "npm_import_lib", "npm_import_links_lib") |
| load("//npm/private:npm_translate_lock.bzl", "npm_translate_lock_lib", "npm_translate_lock_rule") |
| load("//npm/private:npm_translate_lock_helpers.bzl", npm_translate_lock_helpers = "helpers") |
| load("//npm/private:npm_translate_lock_macro_helpers.bzl", macro_helpers = "helpers") |
| load("//npm/private:npm_translate_lock_state.bzl", "npm_translate_lock_state") |
| load("//npm/private:npmrc.bzl", "parse_npmrc") |
| load("//npm/private:pnpm_extension.bzl", "DEFAULT_PNPM_REPO_NAME", "resolve_pnpm_repositories") |
| load("//npm/private:tar.bzl", "detect_system_tar") |
| load("//npm/private:transitive_closure.bzl", "translate_to_transitive_closure") |
| |
| DEFAULT_PNPM_VERSION = _DEFAULT_PNPM_VERSION |
| LATEST_PNPM_VERSION = _LATEST_PNPM_VERSION |
| |
| def _npm_extension_impl(module_ctx): |
| if not bazel_lib_utils.is_bazel_6_or_greater(): |
| # ctx.actions.declare_symlink was added in Bazel 6 |
| fail("A minimum version of Bazel 6 required to use rules_js") |
| |
| for mod in module_ctx.modules: |
| for attr in mod.tags.npm_translate_lock: |
| _npm_translate_lock_bzlmod(attr) |
| |
| # We cannot read the pnpm_lock file before it has been bootstrapped. |
| # See comment in e2e/update_pnpm_lock_with_import/test.sh. |
| if attr.pnpm_lock: |
| if hasattr(module_ctx, "watch"): |
| module_ctx.watch(attr.pnpm_lock) |
| _npm_lock_imports_bzlmod(module_ctx, attr) |
| |
| for i in mod.tags.npm_import: |
| _npm_import_bzlmod(i) |
| |
| if bazel_features.external_deps.extension_metadata_has_reproducible: |
| return module_ctx.extension_metadata( |
| reproducible = True, |
| ) |
| return module_ctx.extension_metadata() |
| |
| def _npm_translate_lock_bzlmod(attr): |
| npm_translate_lock_rule( |
| name = attr.name, |
| bins = attr.bins, |
| custom_postinstalls = attr.custom_postinstalls, |
| data = attr.data, |
| dev = attr.dev, |
| external_repository_action_cache = attr.external_repository_action_cache, |
| generate_bzl_library_targets = attr.generate_bzl_library_targets, |
| link_workspace = attr.link_workspace, |
| no_optional = attr.no_optional, |
| npmrc = attr.npmrc, |
| npm_package_lock = attr.npm_package_lock, |
| npm_package_target_name = attr.npm_package_target_name, |
| package_visibility = attr.package_visibility, |
| patches = attr.patches, |
| patch_args = attr.patch_args, |
| pnpm_lock = attr.pnpm_lock, |
| use_pnpm = attr.use_pnpm, |
| preupdate = attr.preupdate, |
| prod = attr.prod, |
| public_hoist_packages = attr.public_hoist_packages, |
| quiet = attr.quiet, |
| replace_packages = attr.replace_packages, |
| root_package = attr.root_package, |
| update_pnpm_lock = attr.update_pnpm_lock, |
| use_home_npmrc = attr.use_home_npmrc, |
| verify_node_modules_ignored = attr.verify_node_modules_ignored, |
| verify_patches = attr.verify_patches, |
| yarn_lock = attr.yarn_lock, |
| bzlmod = True, |
| ) |
| |
| def _npm_lock_imports_bzlmod(module_ctx, attr): |
| state = npm_translate_lock_state.new(attr.name, module_ctx, attr, True) |
| |
| importers, packages = translate_to_transitive_closure( |
| state.importers(), |
| state.packages(), |
| attr.prod, |
| attr.dev, |
| attr.no_optional, |
| ) |
| |
| registries = {} |
| npm_auth = {} |
| if attr.npmrc: |
| npmrc = parse_npmrc(module_ctx.read(attr.npmrc)) |
| (registries, npm_auth) = npm_translate_lock_helpers.get_npm_auth(npmrc, module_ctx.path(attr.npmrc), module_ctx.os.environ) |
| |
| if attr.use_home_npmrc: |
| home_directory = repo_utils.get_home_directory(module_ctx) |
| if home_directory: |
| home_npmrc_path = "{}/{}".format(home_directory, ".npmrc") |
| home_npmrc = parse_npmrc(module_ctx.read(home_npmrc_path)) |
| |
| (registries2, npm_auth2) = npm_translate_lock_helpers.get_npm_auth(home_npmrc, home_npmrc_path, module_ctx.os.environ) |
| registries.update(registries2) |
| npm_auth.update(npm_auth2) |
| else: |
| # buildifier: disable=print |
| print(""" |
| WARNING: Cannot determine home directory in order to load home `.npmrc` file in module extension `npm_translate_lock(name = "{attr_name}")`. |
| """.format(attr_name = attr.name)) |
| |
| lifecycle_hooks, lifecycle_hooks_execution_requirements, lifecycle_hooks_use_default_shell_env = macro_helpers.macro_lifecycle_args_to_rule_attrs( |
| lifecycle_hooks = attr.lifecycle_hooks, |
| lifecycle_hooks_exclude = attr.lifecycle_hooks_exclude, |
| run_lifecycle_hooks = attr.run_lifecycle_hooks, |
| lifecycle_hooks_no_sandbox = attr.lifecycle_hooks_no_sandbox, |
| lifecycle_hooks_execution_requirements = attr.lifecycle_hooks_execution_requirements, |
| lifecycle_hooks_use_default_shell_env = attr.lifecycle_hooks_use_default_shell_env, |
| ) |
| imports = npm_translate_lock_helpers.get_npm_imports( |
| importers = importers, |
| packages = packages, |
| patched_dependencies = state.patched_dependencies(), |
| only_built_dependencies = state.only_built_dependencies(), |
| root_package = attr.pnpm_lock.package, |
| rctx_name = attr.name, |
| attr = attr, |
| all_lifecycle_hooks = lifecycle_hooks, |
| all_lifecycle_hooks_execution_requirements = lifecycle_hooks_execution_requirements, |
| all_lifecycle_hooks_use_default_shell_env = lifecycle_hooks_use_default_shell_env, |
| registries = registries, |
| default_registry = state.default_registry(), |
| npm_auth = npm_auth, |
| ) |
| |
| system_tar = detect_system_tar(module_ctx) |
| |
| for i in imports: |
| npm_import( |
| name = i.name, |
| bins = i.bins, |
| commit = i.commit, |
| custom_postinstall = i.custom_postinstall, |
| deps = i.deps, |
| dev = i.dev, |
| integrity = i.integrity, |
| generate_bzl_library_targets = attr.generate_bzl_library_targets, |
| lifecycle_hooks = i.lifecycle_hooks if i.lifecycle_hooks else [], |
| lifecycle_hooks_env = i.lifecycle_hooks_env, |
| lifecycle_hooks_execution_requirements = i.lifecycle_hooks_execution_requirements, |
| lifecycle_hooks_use_default_shell_env = i.lifecycle_hooks_use_default_shell_env, |
| link_packages = i.link_packages, |
| # attr.pnpm_lock.repo_name is a canonical repository name, so it needs to be qualified with an extra '@'. |
| link_workspace = attr.link_workspace if attr.link_workspace else "@" + attr.pnpm_lock.repo_name, |
| npm_auth = i.npm_auth, |
| npm_auth_basic = i.npm_auth_basic, |
| npm_auth_password = i.npm_auth_password, |
| npm_auth_username = i.npm_auth_username, |
| package = i.package, |
| package_visibility = i.package_visibility, |
| patch_tool = i.patch_tool, |
| patch_args = i.patch_args, |
| patches = i.patches, |
| exclude_package_contents = i.exclude_package_contents, |
| replace_package = i.replace_package, |
| root_package = i.root_package, |
| transitive_closure = i.transitive_closure, |
| system_tar = system_tar, |
| url = i.url, |
| version = i.version, |
| ) |
| |
| def _npm_import_bzlmod(i): |
| npm_import( |
| name = i.name, |
| bins = i.bins, |
| commit = i.commit, |
| custom_postinstall = i.custom_postinstall, |
| deps = i.deps, |
| dev = i.dev, |
| extra_build_content = i.extra_build_content, |
| integrity = i.integrity, |
| lifecycle_hooks = i.lifecycle_hooks, |
| lifecycle_hooks_env = i.lifecycle_hooks_env, |
| lifecycle_hooks_execution_requirements = i.lifecycle_hooks_execution_requirements, |
| lifecycle_hooks_use_default_shell_env = i.lifecycle_hooks_use_default_shell_env, |
| link_packages = i.link_packages, |
| link_workspace = i.link_workspace, |
| npm_auth = i.npm_auth, |
| npm_auth_basic = i.npm_auth_basic, |
| npm_auth_username = i.npm_auth_username, |
| npm_auth_password = i.npm_auth_password, |
| package = i.package, |
| package_visibility = i.package_visibility, |
| patch_tool = i.patch_tool, |
| patch_args = i.patch_args, |
| patches = i.patches, |
| exclude_package_contents = i.exclude_package_contents, |
| replace_package = i.replace_package, |
| root_package = i.root_package, |
| transitive_closure = i.transitive_closure, |
| url = i.url, |
| version = i.version, |
| ) |
| |
| def _npm_translate_lock_attrs(): |
| attrs = dict(**npm_translate_lock_lib.attrs) |
| |
| # Add macro attrs that aren't in the rule attrs. |
| attrs["name"] = attr.string() |
| attrs["lifecycle_hooks_exclude"] = attr.string_list(default = []) |
| attrs["lifecycle_hooks_no_sandbox"] = attr.bool(default = True) |
| attrs["run_lifecycle_hooks"] = attr.bool(default = True) |
| |
| # Args defaulted differently by the macro |
| attrs["npm_package_target_name"] = attr.string(default = "pkg") |
| attrs["patch_args"] = attr.string_list_dict(default = {"*": ["-p0"]}) |
| |
| # Args not supported or unnecessary in bzlmod |
| attrs.pop("repositories_bzl_filename") |
| |
| return attrs |
| |
| def _npm_import_attrs(): |
| attrs = dict(**npm_import_lib.attrs) |
| attrs.update(**npm_import_links_lib.attrs) |
| |
| # Add macro attrs that aren't in the rule attrs. |
| attrs["name"] = attr.string() |
| attrs["lifecycle_hooks_no_sandbox"] = attr.bool(default = False) |
| attrs["run_lifecycle_hooks"] = attr.bool(default = False) |
| |
| # Args defaulted differently by the macro |
| attrs["lifecycle_hooks_execution_requirements"] = attr.string_list(default = ["no-sandbox"]) |
| attrs["patch_args"] = attr.string_list(default = ["-p0"]) |
| attrs["package_visibility"] = attr.string_list(default = ["//visibility:public"]) |
| |
| return attrs |
| |
| npm = module_extension( |
| implementation = _npm_extension_impl, |
| tag_classes = { |
| "npm_translate_lock": tag_class(attrs = _npm_translate_lock_attrs()), |
| "npm_import": tag_class(attrs = _npm_import_attrs()), |
| }, |
| ) |
| |
| def _pnpm_extension_impl(module_ctx): |
| resolved = resolve_pnpm_repositories(module_ctx.modules) |
| |
| for note in resolved.notes: |
| # buildifier: disable=print |
| print(note) |
| |
| for name, pnpm_version in resolved.repositories.items(): |
| pnpm_repository( |
| name = name, |
| pnpm_version = pnpm_version, |
| ) |
| |
| pnpm = module_extension( |
| implementation = _pnpm_extension_impl, |
| tag_classes = { |
| "pnpm": tag_class( |
| attrs = { |
| "name": attr.string( |
| doc = """Name of the generated repository, allowing more than one pnpm version to be registered. |
| Overriding the default is only permitted in the root module.""", |
| default = DEFAULT_PNPM_REPO_NAME, |
| ), |
| "pnpm_version": attr.string( |
| doc = "pnpm version to use. The string `latest` will be resolved to LATEST_PNPM_VERSION.", |
| default = DEFAULT_PNPM_VERSION, |
| ), |
| "pnpm_version_integrity": attr.string(), |
| }, |
| ), |
| }, |
| ) |