Merge branch 'stable-3.14' * stable-3.14: Bump JGit submodule to the tip of stable-7.5 Bazel: Move Gerrit off JGit servlet-4 via EE8 bridge Bazel: Move Gerrit off JGit servlet-4 via EE8 bridge Update git submodules Update git submodules Update git submodules Release-Notes: skip Change-Id: Ifd33646b1c399eb2ee8d54d082b3b74d232c4824
diff --git a/.agents/rules.json b/.agents/rules.json new file mode 100644 index 0000000..d8db5f0 --- /dev/null +++ b/.agents/rules.json
@@ -0,0 +1,7 @@ +{ + "entries": [ + { + "path": "/google/src/files/head/depot/google3/learning/gemini/agents/skills/gerrit/references/rules/upload_rationale.md" + } + ] +}
diff --git a/.agents/skills.json b/.agents/skills.json new file mode 100644 index 0000000..88387c1 --- /dev/null +++ b/.agents/skills.json
@@ -0,0 +1,7 @@ +{ + "entries": [ + { + "path": "/google/src/files/head/depot/google3/learning/gemini/agents/skills/gerrit" + } + ] +}
diff --git a/.bazelproject b/.bazelproject index 86e568d..b27b85d 100644 --- a/.bazelproject +++ b/.bazelproject
@@ -25,5 +25,4 @@ ts_config_rules: //tools/node_tools/node_modules_licenses:tsconfig_editor - //tools/node_tools/polygerrit_app_preprocessor:preprocessor_tsconfig.json //polygerrit-ui/app/node_modules_licenses:tsconfig_editor
diff --git a/.bazelrc b/.bazelrc index 7a2ed39..b864a3b 100644 --- a/.bazelrc +++ b/.bazelrc
@@ -1,44 +1,29 @@ -# TODO(davido): Migrate all dependencies from WORKSPACE to MODULE.bazel -# https://issues.gerritcodereview.com/issues/303819949 -# Remove hybrid mode option once bzlmod migration is completed -common --enable_workspace -common --enable_bzlmod --lockfile_mode=error -common --incompatible_enable_proto_toolchain_resolution -common --@protobuf//bazel/toolchains:prefer_prebuilt_protoc +common --lockfile_mode=error # Enable Gerrit-tree-only plugin checks (standalone plugin builds skip them). common --@com_googlesource_gerrit_bazlets//flags:in_gerrit_tree=true common --incompatible_disallow_struct_provider_syntax=false common --incompatible_disallow_empty_glob=false +common --@aspect_rules_ts//ts:default_to_tsc_transpiler build --workspace_status_command="python3 ./tools/workspace_status.py" build --repository_cache=~/.gerritcodereview/bazel-cache/repository build --action_env=PATH build --disk_cache=~/.gerritcodereview/bazel-cache/cas -# Define configuration using remotejdk_21, executes using remotejdk_21 or local_jdk -build:build_shared --java_language_version=21 -build:build_shared --java_runtime_version=remotejdk_21 -build:build_shared --tool_java_language_version=21 -build:build_shared --tool_java_runtime_version=remotejdk_21 +# Builds using remotejdk_25, executes using remotejdk_25 or local_jdk +build --java_language_version=25 +build --java_runtime_version=remotejdk_25 +build --tool_java_language_version=25 +build --tool_java_runtime_version=remotejdk_25 -# Builds using remotejdk_21, executes using remotejdk_21 or local_jdk -# Avoid warnings for non default configurations: -# build --config=build_shared -build --java_language_version=21 -build --java_runtime_version=remotejdk_21 -build --tool_java_language_version=21 -build --tool_java_runtime_version=remotejdk_21 - -# Builds and executes on RBE using remotejdk_21 +# Enables builds and execution on RBE build:remote --config=config_gcp -build:remote --config=build_shared -# Define remote21 configuration alias +# Define remote_gcp configuration alias build:remote_gcp --config=remote -# Builds and executes on BuildBuddy RBE using remotejdk_21 +# Enables builds and execution on BuildBuddy RBE build:remote_bb --config=config_bb -build:remote_bb --config=build_shared # Builds using remotejdk_21, executes using remotejdk_21 or local_jdk build:build_java21_shared --java_language_version=21 @@ -49,16 +34,35 @@ build:java21 --config=build_java21_shared # Builds and executes on RBE using remotejdk_21 -build:remote21 --config=config_gcp +build:remote21 --config=remote build:remote21 --config=build_java21_shared # Define remote21 configuration alias build:remote21_gcp --config=remote21 # Builds and executes on BuildBuddy RBE using remotejdk_21 -build:remote21_bb --config=config_bb +build:remote21_bb --config=remote_bb build:remote21_bb --config=build_java21_shared +# Builds using remotejdk_25, executes using remotejdk_25 or local_jdk +build:build_java25_shared --java_language_version=25 +build:build_java25_shared --java_runtime_version=remotejdk_25 +build:build_java25_shared --tool_java_language_version=25 +build:build_java25_shared --tool_java_runtime_version=remotejdk_25 + +build:java25 --config=build_java25_shared + +# Builds and executes on RBE using remotejdk_25 +build:remote25 --config=remote +build:remote25 --config=build_java25_shared + +# Define remote25 configuration alias +build:remote25_gcp --config=remote25 + +# Builds and executes on BuildBuddy RBE using remotejdk_25 +build:remote25_bb --config=remote_bb +build:remote25_bb --config=build_java25_shared + # Enable strict_action_env flag to. For more information on this feature see # https://groups.google.com/forum/#!topic/bazel-discuss/_VmRfMyyHBk. # This will be the new default behavior at some point (and the flag was flipped
diff --git a/.bazelversion b/.bazelversion index acd405b..47da986 100644 --- a/.bazelversion +++ b/.bazelversion
@@ -1 +1 @@ -8.6.0 +9.1.0
diff --git a/.gemini/settings.json b/.gemini/settings.json new file mode 100644 index 0000000..5e535b2 --- /dev/null +++ b/.gemini/settings.json
@@ -0,0 +1,5 @@ +{ + "context": { + "fileName": ["AGENTS.md", "GEMINI.md"] + } +}
diff --git a/.gitignore b/.gitignore index 64e0e49..2d146c8 100644 --- a/.gitignore +++ b/.gitignore
@@ -10,6 +10,7 @@ .DS_Store CLAUDE.md js-to-ts.sh +/.aspect/ /.apt_generated /.apt_generated_tests /.bazel_path @@ -43,8 +44,10 @@ /polygerrit-ui/screenshots/Chromium/failed/ !/plugins/.eslintignore !/plugins/.eslintrc.js +!/plugins/eslint-plugin.config.js !/plugins/.prettierrc.js !/plugins/package.json +!/plugins/pnpm-lock.yaml !/plugins/rollup.config.js !/plugins/tsconfig.json !/plugins/tsconfig-plugins-base.json @@ -54,7 +57,6 @@ !/plugins/commit-message-length-validator !/plugins/delete-project !/plugins/download-commands -!/plugins/external_plugin_deps.bzl !/plugins/gitiles !/plugins/hooks !/plugins/plugin-manager @@ -65,7 +67,6 @@ /test_site /tools/format /tools/maven/gerrit-*_pom.xml.asc -/tools/node_tools /tools/polygerrit-updater /.ts-out/* !/.ts-out/README.md
diff --git a/.gitmodules b/.gitmodules index 7579477..2170834 100644 --- a/.gitmodules +++ b/.gitmodules
@@ -61,7 +61,3 @@ url = ../plugins/webhooks branch = . -[submodule "polymer-bridges"] - path = polymer-bridges - url = ../polymer-bridges - branch = .
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 6703ebc..5d9819c 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs
@@ -11,9 +11,9 @@ org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=21 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=25 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=21 +org.eclipse.jdt.core.compiler.compliance=25 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -130,4 +130,4 @@ org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.processAnnotations=enabled org.eclipse.jdt.core.compiler.release=enabled -org.eclipse.jdt.core.compiler.source=21 +org.eclipse.jdt.core.compiler.source=25
diff --git a/.zuul.yaml b/.zuul.yaml index e0e92fa..2979c1f 100644 --- a/.zuul.yaml +++ b/.zuul.yaml
@@ -31,7 +31,6 @@ - plugins/reviewnotes - plugins/singleusergroup - plugins/webhooks - - polymer-bridges - project: check:
diff --git a/GEMINI.md b/AGENTS.md similarity index 83% rename from GEMINI.md rename to AGENTS.md index 9d27731..be46d98 100644 --- a/GEMINI.md +++ b/AGENTS.md
@@ -1,12 +1,18 @@ -# Gemini Project Profile: gerrit +# AI Agent Project Profile: gerrit ## Project Overview -Gerrit is a web-based code review system for Git. Backend is Java 21, frontend is TypeScript/Lit components, built with Bazel 7.6.1. +Gerrit is a web-based code review system for Git. Backend is Java 21, frontend is TypeScript/Lit components, built with Bazel. +The bazel version to be used can be read from the `.bazelversion` file. ## Sub-projects -- **`polygerrit-ui`**: The frontend web application. See `polygerrit-ui/GEMINI.md` for details on the frontend development environment. +- **`polygerrit-ui`**: The frontend web application. See `polygerrit-ui/AGENTS.md` for details on the frontend development environment. + +## Build tool selection + +If `bazelisk` is installed, use `bazelisk` instead of `bazel` for all build +commands. ## Build Commands @@ -104,6 +110,8 @@ - Java: Google Java Style Guide, use `./tools/gjf.sh run` before committing - Commit messages: max 72 chars/line, present tense, include Change-Id (added by git hook) - **Release-Notes footer required**: Every commit must have `Release-Notes:` footer. Use `Release-Notes: skip` for small fixes/refactorings, or add a summary for notable changes +- Optional Google-Bug-Id footer: Commits might have a `Google-Bug-Id: b/<number>` footer. If your context provides a bug number, use it. +- **Copyright headers**: All new Java files must include the Apache 2.0 license header with the current year (e.g., `// Copyright (C) 2026 The Android Open Source Project`) ## Key Patterns
diff --git a/BUILD b/BUILD index 0c10d76..2b76335 100644 --- a/BUILD +++ b/BUILD
@@ -1,6 +1,9 @@ -load("//tools/bzl:genrule2.bzl", "genrule2") +load("@com_googlesource_gerrit_bazlets//tools:genrule2.bzl", "genrule2") +load("@npm//:defs.bzl", "npm_link_all_packages") load("//tools/bzl:pkg_war.bzl", "pkg_war") +npm_link_all_packages(name = "node_modules") + package(default_visibility = ["//visibility:public"]) genrule(
diff --git a/Documentation/BUILD b/Documentation/BUILD index 9ab713a..9a8451b 100644 --- a/Documentation/BUILD +++ b/Documentation/BUILD
@@ -1,3 +1,4 @@ +load("@rules_shell//shell:sh_test.bzl", "sh_test") load("//tools/bzl:asciidoc.bzl", "documentation_attributes", "genasciidoc", "genasciidoc_zip") load("//tools/bzl:license.bzl", "license_map") load("//tools/bzl:war_checks.bzl", "war_jars_allowlist_test")
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt index 6b6ab24..e65a367 100644 --- a/Documentation/access-control.txt +++ b/Documentation/access-control.txt
@@ -1837,7 +1837,7 @@ When determining access, first "read = DENY group A" on "refs/a" is encountered. The following rule to consider is "ALLOW read group A" on "refs/a". The latter rule applies to the same (permission, -ref-pattern, group) tuple, so it it is ignored. +ref-pattern, group) tuple, so it is ignored. The DENY rule does not affect the last rule for "refs/*", since that has a different ref pattern and a different group. If group B is a
diff --git a/Documentation/cmd-show-caches.txt b/Documentation/cmd-show-caches.txt index 65f05b1..92e8963 100644 --- a/Documentation/cmd-show-caches.txt +++ b/Documentation/cmd-show-caches.txt
@@ -8,11 +8,17 @@ -- _ssh_ -p <port> <host> _gerrit show-caches_ [--show-jvm] + [--cache <NAME> ...] -- == DESCRIPTION Display statistics about the size and hit ratio of in-memory caches. +By default, the command displays statistics for all registered caches. +Because collecting data for numerous large caches on a busy server +can cause delays, the --cache option can be used (one or more times) +to limit the output to specific caches. + == OPTIONS --show-jvm:: List the name and version of the Java virtual machine, host @@ -22,6 +28,15 @@ --show-threads:: Show detailed counts for Gerrit specific threads. +--cache <NAME>:: + Show statistics only for the cache called <NAME>. May be + supplied more than once to display multiple caches in a single + command execution. Cache names are matched case-insensitively + against the full registered cache name (including any plugin + prefix). Unknown cache names are silently ignored. The + summary sections (SSH, tasks, memory, threads, JVM) are not + affected by this option. + --width:: -w:: Width of the output table. @@ -84,6 +99,12 @@ Threads: 4 CPUs available, 371 threads ---- +Show statistics for only the "accounts" and "projects" caches: + +---- +$ ssh -p 29418 review.example.com gerrit show-caches --cache accounts --cache projects +---- + == SEE ALSO * link:cmd-flush-caches.html[gerrit flush-caches]
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt index f45da29..f48b90c 100644 --- a/Documentation/config-gerrit.txt +++ b/Documentation/config-gerrit.txt
@@ -932,6 +932,19 @@ Valid values are 0, and positive integers. Setting this to 0 will cause the filter to never be rebuilt. + +[[cache.startupThreads]]cache.startupThreads:: ++ +Number of threads used to open persistent caches in parallel during startup. +Each persistent cache is backed by its own H2 database file and builds a +BloomFilter by reading all keys from disk when it is opened. With the default +of 1, caches are opened sequentially. Increasing this value allows the +BloomFilter build phase to run concurrently across caches. ++ +Setting this value greater than 1 creates threads in a dedicated pool, separate +from the H2 thread pool (`DiskCache-Store`) that handles cache updates. ++ +Default is 1. + [[cache.openFiles]]cache.openFiles:: + The number of file descriptors to add to the limit set by the Gerrit daemon. @@ -6414,6 +6427,45 @@ By default false. + +[[submitRequirement.executionTimeout]]submitRequirement.executionTimeout:: +Maximum time allowed for evaluating a submit requirement while +accessing a change. +Supports time units (e.g. 500ms, 2s etc). +If the evaluation exceeds this timeout, the evaluation is aborted and the +submit requirement is marked as `TIMEOUT`. +This helps to prevent long-running or expensive submit requirement expressions +evaluation. +- If set to 0 or config is missing, there is no timeout and the evaluation +will run until evaluation is completed, regardless of how long it takes. +- If set to to a positive value, evaluation will timeout the SR and change +cannot be submitted. ++ +By default 0. ++ +---- +Note: +Timeout configured is applicable per Submit Requirement i.e. it +applies collectively to the total time needed to evaluate all of these +expressions: applicableIf, submittableIf, overrideIf. + +If submitRequirement.evaluationThreads is set to negative value +submitRequirement.executionTimeout is not applicable and the evaluation will +run until evaluation is completed. +---- + +[[submitRequirement.evaluationThreads]]submitRequirement.evaluationThreads:: +Maximum number of threads to be use for evaluating submit requirements for a +change. +- If the config property is set to 0, then the number of threads is set to the +number of available CPUs. +- If the value is negative, then direct executor is used and the evaluation is +executed in the same thread as the request. +- If the value is positive, then the specified number of threads will be used +for evaluation. ++ +By default -1. ++ + [[suggest]] === Section suggest
diff --git a/Documentation/config-mail.txt b/Documentation/config-mail.txt index 49ec3f4..c440eb3 100644 --- a/Documentation/config-mail.txt +++ b/Documentation/config-mail.txt
@@ -226,7 +226,7 @@ $change.shortOriginalSubject:: + -The original subject limited to 72 characters, with an ellipsis if it exceeds +The original subject limited to 72 characters, with an ellipsis if it exceeds. that. $change.sizeBucket:: @@ -251,7 +251,7 @@ $instanceAndProjectName:: + -The Gerrit instance name, followed by the short project name +The Gerrit instance name, followed by the short project name. $addInstanceNameInSubject:: +
diff --git a/Documentation/config-submit-requirements.txt b/Documentation/config-submit-requirements.txt index 239ccf6..336b8d6 100644 --- a/Documentation/config-submit-requirements.txt +++ b/Documentation/config-submit-requirements.txt
@@ -505,7 +505,7 @@ + 'label:Code-Review=MAX,users=human_reviewers' can be used to implement "Want-Code-Review-From-All" functionality, see -link#require-code-review-approvals-from-all-human-reviewers-example[examples +link:#require-code-review-approvals-from-all-human-reviewers-example[examples below]. [[operator_is_true]]
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt index 3361ebc..f48b2b66 100644 --- a/Documentation/dev-bazel.txt +++ b/Documentation/dev-bazel.txt
@@ -18,7 +18,7 @@ To build Gerrit from source, you need: * A Linux or macOS system (Windows is not supported) -* A JDK for Java 21 +* A JDK for Java 25 * Python 3 * link:https://github.com/nodesource/distributions/blob/master/README.md[Node.js (including npm),role=external,window=_blank] * Yarn (`npm install -g yarn`) @@ -54,10 +54,10 @@ `java -version` -[[java-21]] -==== Java 21 support +[[java-25]] +==== Java 25 support -To build Gerrit with Java 21 language level, run: +To build Gerrit with Java 25 language level, run: ``` $ bazelisk build :release @@ -202,7 +202,7 @@ bazelisk build plugins/<name> ---- -The output JAR file will be be placed in: +The output JAR file will be placed in: ---- bazel-bin/plugins/<name>/<name>.jar @@ -519,34 +519,6 @@ bazelisk test //plugins/replication/... ---- -[[known-issues]] -=== Known Issues - -[[byte-buddy-not-initialized-or-unavailable]] -==== The Byte Buddy agent is not initialized or unavailable - -If running tests that make use of mocks fail with the exception below, set the -`sandbox_tmpfs_path` flag for running tests in `.bazelrc` as described in this -link:https://github.com/mockito/mockito/issues/1879#issuecomment-922459131[ -issue], e.g. add this line: `test --sandbox_tmpfs_path=/tmp` - -.Exception: ----- -... -Caused by: org.mockito.exceptions.base.MockitoInitializationException: -Could not initialize inline Byte Buddy mock maker. - -It appears as if your JDK does not supply a working agent attachment mechanism. -... -Caused by: java.lang.IllegalStateException: The Byte Buddy agent is not initialized or unavailable -at net.bytebuddy.agent.ByteBuddyAgent.getInstrumentation(ByteBuddyAgent.java:230) -at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:617) -at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:568) -at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:545) -at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.<clinit>(InlineDelegateByteBuddyMockMaker.java:115) -... 47 more ----- - [[debugging-tests]] == Debugging Unit Tests In some cases it may be necessary to debug a test while running it in bazel. For example, when we @@ -809,15 +781,15 @@ --disk-size=200 ``` -Note, that we are using Ubuntu2204 docker image from bazel project: +Note, that we are using Ubuntu2404 docker image from bazel project: ``` -docker pull gcr.io/bazel-public/ubuntu2204-java17@sha256:ffe37746a34537d8e73cef5a20ccd3a4e3ec7af3e7410cba87387ba97c0e520f +docker pull gcr.io/bazel-public/ubuntu2404@sha256:a5e969f2208ae4ab3cbe18cd85971f1f602345a650495674736a66b3ef9a55d1 ``` Re-build rbe_autoconfig project, conduct a new release and switch to using it -in `WORKSPACE` file. For more details see this +in `MODULE.bazel` file. For more details see this link:https://github.com/davido/rbe_autoconfig[repository,role=external,window=_blank] Note, to authenticate to the gcr.io registry, the following command must be
diff --git a/Documentation/dev-build-plugins.txt b/Documentation/dev-build-plugins.txt index c0cf52e..cc43706 100644 --- a/Documentation/dev-build-plugins.txt +++ b/Documentation/dev-build-plugins.txt
@@ -83,13 +83,6 @@ === Plugins with external dependencies === -[NOTE] -As of Gerrit 3.14 using the `external_plugin_deps.bzl` file for adding external -dependencies of plugins to the build has been deprecated. This feature will be -removed with Gerrit 3.15. Please migrate to using Bazel modules as described -below. The documentation of the deprecated `external_plugin_deps.bzl` -functionality has been moved to a dedicated section below. - If a plugin requires external Java dependencies, it can install them in its `MODULE.bazel` using `rules_jvm_external`. The Maven repository containing the plugin runtime dependencies must be plugin-scoped (for example @@ -269,51 +262,6 @@ bazel build reviewers ---- -== Managing external dependencies using `external_plugin_deps.bzl` == - -[NOTE] -This functionality has been deprecated. - -If the plugin has external dependencies, then they can be included from Gerrit's -own WORKSPACE file. This can be achieved by including them in `external_plugin_deps.bzl`. -During the build in Gerrit tree, this file must be copied over the dummy one in -`plugins` directory. - -Example for content of `external_plugin_deps.bzl` file: - ----- -load("//tools/bzl:maven_jar.bzl", "maven_jar") - -def external_plugin_deps(): - maven_jar( - name = 'org_apache_tika_tika_core', - artifact = 'org.apache.tika:tika-core:1.12', - sha1 = '5ab95580d22fe1dee79cffbcd98bb509a32da09b', - ) ----- - -If the plugin(s) being bundled in the release have external dependencies, include them -in `plugins/external_plugin_deps`. Create symbolic link from plugin's own -`external_plugin_deps()` file in plugins directory and prefix the file with -plugin name, e.g.: - ----- - $ cd plugins - $ ln -s oauth/external_plugin_deps.bzl oauth_external_plugin_deps.bzl - $ ln -s uploadvalidator/external_plugin_deps.bzl uploadvalidator_external_plugin_deps.bzl ----- - -Now the plugin specific dependency files can be imported: - ----- -load(":oauth_external_plugin_deps.bzl", oauth_deps="external_plugin_deps") -load(":uploadvalidator_external_plugin_deps.bzl", uploadvalidator_deps="external_plugin_deps") - -def external_plugin_deps(): - oauth_deps() - uploadvalidator_deps() ----- - GERRIT ------ Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-crafting-changes.txt b/Documentation/dev-crafting-changes.txt index 9a97aad..a00cdca 100644 --- a/Documentation/dev-crafting-changes.txt +++ b/Documentation/dev-crafting-changes.txt
@@ -147,9 +147,9 @@ To format Java source code, Gerrit uses the link:https://github.com/google/google-java-format[`google-java-format`,role=external,window=_blank] -tool (version 1.24.0), and to format Bazel BUILD, WORKSPACE and .bzl files the +tool (version 1.35.0), and to format Bazel BUILD, MODULE.bazel and .bzl files the link:https://github.com/bazelbuild/buildtools/tree/master/buildifier[`buildifier`,role=external,window=_blank] -tool (version 4.0.0). Unused dependencies are found and removed using the +tool (version 8.0.0). Unused dependencies are found and removed using the link:https://github.com/bazelbuild/buildtools/tree/master/unused_deps[`unused_deps`,role=external,window=_blank] build tool, a sibling of `buildifier`.
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt index 3d4563b..bf26fc2 100644 --- a/Documentation/dev-eclipse.txt +++ b/Documentation/dev-eclipse.txt
@@ -131,7 +131,7 @@ To format source code, Gerrit uses the link:https://github.com/google/google-java-format[`google-java-format`,role=external,window=_blank] -tool (version 1.24.0), which automatically formats code to follow the +tool (version 1.35.0), which automatically formats code to follow the style guide. See link:dev-crafting-changes.html#style[Code Style] for the instruction how to set up command line tool that uses this formatter. The Eclipse plugin is provided that allows to format with the same
diff --git a/Documentation/dev-plugins-lifecycle.txt b/Documentation/dev-plugins-lifecycle.txt index 7933743..5be6faa 100644 --- a/Documentation/dev-plugins-lifecycle.txt +++ b/Documentation/dev-plugins-lifecycle.txt
@@ -209,6 +209,65 @@ requesting to be more involved and making them maintainers of his plugin, adding them to the list of the project owners. +[[branching_strategy]] +== Branching Strategy + +Plugin maintainers are encouraged to adopt a minimal-branching approach that +balances compatibility across Gerrit core versions with sustainable maintenance +overhead. + +[[development_branch_strategy]] +=== Development Branch Strategy + +The plugin `master` branch is the primary development branch when no stable +branches exist. The goal is to keep `master` compatible with the current +Gerrit core release for as long as possible, avoiding premature branch +proliferation. + +When stable branches exist, new features should be developed on the oldest +supported stable branch that can support them, then merged forward through +any intermediate stable branches and finally into `master`. This ensures the +feature is immediately available to users on older Gerrit versions without +requiring a separate backport. + +[[reactive_branching]] +=== Just-in-Time Branching + +Plugin branches are created reactively, not proactively. There is no +requirement to create a plugin branch for every Gerrit core release. + +The decision flow is: + +. Develop on plugin `master`. +. Test plugin `master` against the target Gerrit core version. +. If it builds and works → continue using `master`; no new branch is needed. +. If it breaks: + .. Identify the last commit on `master` that was compatible. + .. Create a stable branch from that commit. + .. Continue development for the new Gerrit core version on `master`. + +A single plugin branch may remain compatible with multiple consecutive Gerrit +core versions. Branches should only diverge when API or behavioral +incompatibilities make it unavoidable. + +[[core_version_relationship]] +=== Relationship to Gerrit Core Versions + +There is no required one-to-one mapping between plugin branches and Gerrit +core versions. A single plugin branch can cover multiple Gerrit releases as +long as no incompatible API changes are introduced. Creating a dedicated branch +for every Gerrit release is discouraged, as it increases backport complexity +and maintenance overhead. + +[[ci_limitations]] +=== CI Limitations and Expectations + +Plugins are not continuously built against multiple Gerrit core versions +simultaneously. CI coverage does not guarantee early detection of +incompatibilities between a plugin and a given Gerrit core version. Some +breakages may only surface at runtime. This is an accepted trade-off given the +overhead of maintaining full multi-version CI pipelines. + [[plugin_release]] == Plugin Release
diff --git a/Documentation/dev-processes.txt b/Documentation/dev-processes.txt index 2e87150..52fa75a 100644 --- a/Documentation/dev-processes.txt +++ b/Documentation/dev-processes.txt
@@ -52,8 +52,8 @@ [[steering-committee-election]] === Election of non-Google steering committee members -The election of the non-Google steering committee members happens once -a year in June. Non-Google link:dev-roles.html#maintainer[maintainers] +The election of the non-Google steering committee members and the community managers +happens once a year in June. Non-Google link:dev-roles.html#maintainer[maintainers] can nominate themselves by posting an informal application on the non-public mailto:gerritcodereview-community-managers@googlegroups.com[ community manager mailing list] when the call for nominations is sent to
diff --git a/Documentation/dev-roles.txt b/Documentation/dev-roles.txt index d1fee3f..a3101cf 100644 --- a/Documentation/dev-roles.txt +++ b/Documentation/dev-roles.txt
@@ -175,7 +175,7 @@ link:dev-design-docs.html[design docs] and raising concerns during the design review * serving as link:#mentor[mentor] -* doing releases (see link#release-manager[release manager]) +* doing releases (see link:#release-manager[release manager]) Maintainers can:
diff --git a/Documentation/intro-gerrit-walkthrough.txt b/Documentation/intro-gerrit-walkthrough.txt index 345eb1c..61aae4f5 100644 --- a/Documentation/intro-gerrit-walkthrough.txt +++ b/Documentation/intro-gerrit-walkthrough.txt
@@ -176,7 +176,7 @@ She uses the *Cover Message* text box to provide Max with some additional feedback. When she is satisfied with her review, Hannah clicks the *SEND* button. At this point, her vote and cover message become -visible to to all users. +visible to all users. == Reworking the Change
diff --git a/Documentation/intro-project-owner.txt b/Documentation/intro-project-owner.txt index eed18fb..fa3855d 100644 --- a/Documentation/intro-project-owner.txt +++ b/Documentation/intro-project-owner.txt
@@ -721,7 +721,7 @@ request the deletion of your project. Instead of deleting a project you may set the -link:project-configuration.html#project-state[project state] to `ReadOnly` or +link:config-project-config.html#project-section[project state] to `ReadOnly` or `Hidden`. [[project-rename]]
diff --git a/Documentation/js_licenses.txt b/Documentation/js_licenses.txt index 9cfed09..dd9559c 100644 --- a/Documentation/js_licenses.txt +++ b/Documentation/js_licenses.txt
@@ -755,7 +755,6 @@ Polymer-2015 * @polymer/font-roboto-local - only the following file(s): -** README.md ** bower.json ** demo/index.d.ts ** demo/index.html @@ -766,6 +765,7 @@ ** package.json ** roboto.js ** update-fonts.sh +* @webcomponents/shadycss [[Polymer-2015_license]] ---- @@ -807,100 +807,6 @@ ---- -[[Polymer-2017]] -Polymer-2017 - -* @polymer/decorators -* @polymer/polymer -* @webcomponents/shadycss - -[[Polymer-2017_license]] ----- -Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - -This code may only be used under the BSD style license found at -http://polymer.github.io/LICENSE.txt The complete set of authors may be found at -http://polymer.github.io/AUTHORS.txt The complete set of contributors may be -found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as -part of the polymer project is also subject to an additional IP rights grant -found at http://polymer.github.io/PATENTS.txt - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - - -[[Polymer-2018]] -Polymer-2018 - -* @webcomponents/webcomponentsjs -* polymer-bridges -* polymer-resin - -[[Polymer-2018_license]] ----- -Copyright (c) 2018 The Polymer Project Authors. All rights reserved. - -This code may only be used under the BSD style license found at -http://polymer.github.io/LICENSE.txt The complete set of authors may be found at -http://polymer.github.io/AUTHORS.txt The complete set of contributors may be -found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as -part of the polymer project is also subject to an additional IP rights grant -found at http://polymer.github.io/PATENTS.txt - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - - [[font-roboto-local-fonts-roboto]] font-roboto-local-fonts-roboto
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt index 6774ba2..7a3c608 100644 --- a/Documentation/licenses.txt +++ b/Documentation/licenses.txt
@@ -2883,7 +2883,6 @@ Polymer-2015 * @polymer/font-roboto-local - only the following file(s): -** README.md ** bower.json ** demo/index.d.ts ** demo/index.html @@ -2894,6 +2893,7 @@ ** package.json ** roboto.js ** update-fonts.sh +* @webcomponents/shadycss [[Polymer-2015_license]] ---- @@ -2935,100 +2935,6 @@ ---- -[[Polymer-2017]] -Polymer-2017 - -* @polymer/decorators -* @polymer/polymer -* @webcomponents/shadycss - -[[Polymer-2017_license]] ----- -Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - -This code may only be used under the BSD style license found at -http://polymer.github.io/LICENSE.txt The complete set of authors may be found at -http://polymer.github.io/AUTHORS.txt The complete set of contributors may be -found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as -part of the polymer project is also subject to an additional IP rights grant -found at http://polymer.github.io/PATENTS.txt - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - - -[[Polymer-2018]] -Polymer-2018 - -* @webcomponents/webcomponentsjs -* polymer-bridges -* polymer-resin - -[[Polymer-2018_license]] ----- -Copyright (c) 2018 The Polymer Project Authors. All rights reserved. - -This code may only be used under the BSD style license found at -http://polymer.github.io/LICENSE.txt The complete set of authors may be found at -http://polymer.github.io/AUTHORS.txt The complete set of contributors may be -found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as -part of the polymer project is also subject to an additional IP rights grant -found at http://polymer.github.io/PATENTS.txt - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----- - - [[font-roboto-local-fonts-roboto]] font-roboto-local-fonts-roboto
diff --git a/Documentation/pg-plugin-endpoints.txt b/Documentation/pg-plugin-endpoints.txt index 8e6873c..ed63caa 100644 --- a/Documentation/pg-plugin-endpoints.txt +++ b/Documentation/pg-plugin-endpoints.txt
@@ -68,6 +68,20 @@ current revision displayed, an instance of link:rest-api-changes.html#revision-info[RevisionInfo] +=== change-view-commit-aside +The `change-view-commit-aside` extension point is located on the change view +page, next to the commit message above the related changes list. + +* `change` ++ +current change displayed, an instance of +link:rest-api-changes.html#change-info[ChangeInfo] + +* `revision` ++ +current revision displayed, an instance of +link:rest-api-changes.html#revision-info[RevisionInfo] + === change-metadata-item The `change-metadata-item` extension point is located on the change view left panel, below the `Submit Requirements` and `Links` sections by default. @@ -124,6 +138,21 @@ The end point contains the `<gr-formatted-text>` element holding the `CheckResult.message` (if any was set). +=== check-result-feedback +The `check-result-feedback` extension point is located at the bottom of the +result details in the expanded state of the +link:pg-plugin-checks-api.html[ChecksAPI]. It wraps the default feedback area +(thumbs up/down controls). This can be used by plugins to fully customize or +replace the result-level interaction mechanisms. + +In addition to default parameters, the following are available: + +* `result` ++ +The `CheckResult` object for the currently expanded result row. + +The end point contains the default feedback controls. + === repo-command This endpoint is situated among the repository commands.
diff --git a/Documentation/quota.txt b/Documentation/quota.txt index 3f2f37d..287f961 100644 --- a/Documentation/quota.txt +++ b/Documentation/quota.txt
@@ -22,9 +22,6 @@ to specific endpoints while knowing the general context (user and top-level entity such as change, project or account). -If the quota enforcer wants to throttle HTTP requests, they should use -link:quota.html#http-requests[HTTP Requests] instead. - The quota groups used for checking follow the exact definition of the endpoint in the REST API, but remove all IDs. The schema is:
diff --git a/Documentation/rest-api-access.txt b/Documentation/rest-api-access.txt index 2e1bc43..a154052 100644 --- a/Documentation/rest-api-access.txt +++ b/Documentation/rest-api-access.txt
@@ -352,7 +352,7 @@ |`rules` || The rules assigned for this permission as a map that maps the UUIDs of the groups for which the permission are assigned to -link:#permission-info[PermissionRuleInfo] entities. +link:#permission-rule-info[PermissionRuleInfo] entities. |================================== [[permission-rule-info]]
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt index 341b485..a1fa859 100644 --- a/Documentation/rest-api-changes.txt +++ b/Documentation/rest-api-changes.txt
@@ -9846,7 +9846,8 @@ * `PASS` - expression was evaluated and result is true. + * `FAIL` - expression was evaluated and result is false. + * `ERROR` - an error occurred while evaluating the expression. + - * `NOT_EVALUATED` - expression was not evaluated. + * `NOT_EVALUATED` - expression was not evaluated. + + * `TIMEOUT` - expression was TIMEOUT while evaluating. |`passing_atoms`|optional| A list of passing atoms as strings. For the above expression, `passing_atoms` can contain ["branch:refs/heads/foo"] if the branch predicate is @@ -9903,7 +9904,7 @@ |`status`|| Status describing the result of evaluating the submit requirement. The status is one of (`SATISFIED`, `UNSATISFIED`, `OVERRIDDEN`, `NOT_APPLICABLE`, `ERROR`, -`FORCED`). +`FORCED`, `TIMEOUT`). |`is_legacy`|| If true, this submit requirement result was created from a legacy link:#submit-record[SubmitRecord]. Otherwise, it was created by evaluating a
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt index 8c3c972..b86d732 100644 --- a/Documentation/rest-api-config.txt +++ b/Documentation/rest-api-config.txt
@@ -1548,8 +1548,7 @@ { "changes": [ "foo~101", - "bar~202", - "303" + "bar~202" ], "delete_missing": "true" } @@ -1561,8 +1560,13 @@ Content-Disposition: attachment ---- -When `delete_missing` is set to `true` changes to be reindexed which are missing in NoteDb -will be deleted in the index. +[NOTE] +When `delete_missing` is set to `true`, only Change-IDs in the format +project~changeNumber are accepted. Changes to be reindexed which are +missing in NoteDb will be deleted in the index. If any of the Change-IDs +are in an incorrect format, the server will return a 400 - Bad Request, +and none of the operations will be executed. + [[list-indexes]] === List Indexes @@ -2573,11 +2577,11 @@ |================================ |Field Name ||Description |`changes` || -List of link:rest-api-changes.html#change-id[change-ids] +List of link:rest-api-changes.html#change-id[change-ids]. When `delete_missing` is `true`, each entry must be in `project~changeNumber` format. |`delete_missing` |optional| Delete changes which are missing in NoteDb from the index. This can be used to get rid of stale index entries. Possible values are `true` and `false`. -By default set to `false`. +By default set to `false`. When `true`, all entries in `changes` must be in `project~changeNumber` format; otherwise the request fails with `400 Bad Request` and no operations are executed. |================================ [[jvm-summary-info]]
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt index e265eaa..824a757 100644 --- a/Documentation/rest-api-projects.txt +++ b/Documentation/rest-api-projects.txt
@@ -1669,6 +1669,113 @@ ---- +[[diff-commits]] +=== Diff Between Commits +-- +'GET /projects/link:#project-name[\{project-name\}]/commits/link:#commit-id[\{commit-id\}]/diff' +-- + +Lists the files that differ between two commits. This is useful for comparing +commits across multiple changes (similar to a pull request diff). + +The `base` query parameter is required and must be a 40-character +SHA-1 commit ID. The commits must be in an ancestor/descendant relationship. +All commits in the path between the base commit and the target commit must be +visible to the caller. +Private changes that the caller cannot access will result in a 404 error. + +The response format is identical to the +link:rest-api-changes.html#list-files[List Files] endpoint for changes. + +.Request +---- + GET /projects/my-project/commits/6d2a3adb10e844c33617fc948dbeb88e868d396e/diff?base=a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96 HTTP/1.0 +---- + +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json;charset=UTF-8 + + )]}' + { + "/COMMIT_MSG": { + "status": "A", + "lines_inserted": 15, + "size_delta": 450, + "size": 450 + }, + "src/main/java/Example.java": { + "lines_inserted": 10, + "lines_deleted": 5, + "size_delta": 150, + "size": 2500 + } + } +---- + +[[diff-file-content]] +=== Get Diff for File +-- +'GET /projects/link:#project-name[\{project-name\}]/commits/link:#commit-id[\{commit-id\}]/files/\{file-id\}/diff' +-- + +Gets the diff for a single file between two commits. The `base` +query parameter is required (same as <<diff-commits,Diff Between Commits>>). + +The response format is identical to the +link:rest-api-changes.html#get-diff[Get Diff] endpoint for changes. + +.Request +---- + GET /projects/my-project/commits/6d2a3adb10e844c33617fc948dbeb88e868d396e/files/src%2Fmain%2Fjava%2FExample.java/diff?base=a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96 HTTP/1.0 +---- + +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json;charset=UTF-8 + + )]}' + { + "meta_a": { + "name": "src/main/java/Example.java", + "content_type": "text/x-java", + "lines": 50 + }, + "meta_b": { + "name": "src/main/java/Example.java", + "content_type": "text/x-java", + "lines": 55 + }, + "change_type": "MODIFIED", + "content": [ + { + "ab": [ + "// Copyright header", + "package com.example;" + ] + }, + { + "a": [ + " private int oldField;" + ], + "b": [ + " private int newField;", + " private String additionalField;" + ] + } + ] + } +---- + +.Options +* `--whitespace`: Controls whitespace handling. Valid values are `IGNORE_NONE`, + `IGNORE_TRAILING`, `IGNORE_LEADING_AND_TRAILING` (default), and `IGNORE_ALL`. +* `--intraline`: If set, includes intra-line diffs. + [[branch-endpoints]] == Branch Endpoints @@ -4190,7 +4297,7 @@ .Request ---- - POST /projects/testproj/migrate-labels HTTP/1.0 + POST /projects/testproj/migrate-labels:review HTTP/1.0 Content-Type: application/json; charset=UTF-8 ----
diff --git a/Documentation/user-upload.txt b/Documentation/user-upload.txt index 16ab0d7..02ee785 100644 --- a/Documentation/user-upload.txt +++ b/Documentation/user-upload.txt
@@ -25,10 +25,12 @@ value of link:#auth.gitBasicAuthPolicy[auth.gitBasicAuthPolicy], credentials are validated using: -* The randomly generated HTTP password on the `HTTP Password` tab - in the user settings page if `gitBasicAuthPolicy` is `HTTP`. +* Randomly generated HTTP authentication tokens on the `HTTP Credentials` tab + in the user settings page if `gitBasicAuthPolicy` is `HTTP`. Administrators + can limit the maximum lifetime of HTTP authentication tokens by setting + the option link:config-gerrit.html#auth.maxAuthTokenLifetime[auth.maxAuthTokenLifetime]. * The LDAP password if `gitBasicAuthPolicy` is `LDAP` -* Both, the HTTP and the LDAP passwords (in this order) if `gitBasicAuthPolicy` +* Both, the HTTP and the LDAP credentials (in this order) if `gitBasicAuthPolicy` is `HTTP_LDAP`. When gitBasicAuthPolicy is set to `LDAP` or `HTTP_LDAP` and the user
diff --git a/MODULE.bazel b/MODULE.bazel index c530bce..037a989 100644 --- a/MODULE.bazel +++ b/MODULE.bazel
@@ -1,14 +1,21 @@ -# TODO(davido): Migrate all dependencies from WORKSPACE to MODULE.bazel -# https://issues.gerritcodereview.com/issues/303819949 module(name = "gerrit") # Core Bazel deps. -bazel_dep(name = "bazel_features", version = "1.39.0") +bazel_dep(name = "bazel_features", version = "1.45.0") bazel_dep(name = "platforms", version = "1.0.0") -bazel_dep(name = "rules_jvm_external", version = "6.10") +bazel_dep(name = "rules_jvm_external", version = "7.0") # Language rules. -bazel_dep(name = "rules_java", version = "8.16.1") +bazel_dep(name = "aspect_bazel_lib", version = "2.22.5") +bazel_dep(name = "aspect_rules_js", version = "3.1.2") +bazel_dep(name = "aspect_rules_rollup", version = "2.0.1") +bazel_dep(name = "aspect_rules_ts", version = "3.8.8") +bazel_dep(name = "rules_android", version = "0.7.2") +bazel_dep(name = "rules_go", version = "0.60.0") +bazel_dep(name = "rules_java", version = "9.3.0") +bazel_dep(name = "rules_nodejs", version = "6.7.4") +bazel_dep(name = "rules_python", version = "2.0.2") +bazel_dep(name = "rules_shell", version = "0.8.0") # Pin rules_java to Gerrit's chosen version. Transitive bzlmod modules can # otherwise raise it through MVS, which changes the Java toolchain inputs @@ -16,15 +23,11 @@ # but update the version to the target branch's chosen rules_java version. single_version_override( module_name = "rules_java", - version = "8.16.1", + version = "9.3.0", ) -bazel_dep(name = "rules_proto", version = "7.1.0") -bazel_dep(name = "rules_python", version = "1.8.0-rc1") -bazel_dep(name = "rules_shell", version = "0.6.1") - # Libraries / toolchains. -bazel_dep(name = "protobuf", version = "33.4") +bazel_dep(name = "protobuf", version = "35.0") # In-tree modules. bazel_dep(name = "jgit") @@ -37,7 +40,7 @@ bazel_dep(name = "rbe_autoconfig") git_override( module_name = "rbe_autoconfig", - commit = "c4d733d0399ebf2ee1ffb897be5fbaba23738e04", + commit = "eb944ce4fc29a1608eae9adf0b0e0df2f9e05e33", remote = "https://github.com/davido/rbe_autoconfig.git", ) @@ -52,5 +55,9 @@ # External dependency wiring is split out. include("//tools:java_deps.MODULE.bazel") +# PolyGerrit dependency wiring is split out. +include("//polygerrit-ui:polygerrit.MODULE.bazel") + # Wiring for external dependencies contributed by in-tree plugins. include("//plugins:external_plugin_deps.MODULE.bazel") +
diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index d47d4ab..7ea3e69 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock
@@ -1,5 +1,5 @@ { - "lockFileVersion": 24, + "lockFileVersion": 26, "registryFileHashes": { "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", @@ -12,16 +12,38 @@ "https://bcr.bazel.build/modules/abseil-cpp/20250127.0/MODULE.bazel": "d1086e248cda6576862b4b3fe9ad76a214e08c189af5b42557a6e1888812c5d5", "https://bcr.bazel.build/modules/abseil-cpp/20250127.1/MODULE.bazel": "c4a89e7ceb9bf1e25cf84a9f830ff6b817b72874088bf5141b314726e46a57c1", "https://bcr.bazel.build/modules/abseil-cpp/20250512.1/MODULE.bazel": "d209fdb6f36ffaf61c509fcc81b19e81b411a999a934a032e10cd009a0226215", - "https://bcr.bazel.build/modules/abseil-cpp/20250814.0/MODULE.bazel": "c43c16ca2c432566cdb78913964497259903ebe8fb7d9b57b38e9f1425b427b8", - "https://bcr.bazel.build/modules/abseil-cpp/20250814.0/source.json": "b88bff599ceaf0f56c264c749b1606f8485cec3b8c38ba30f88a4df9af142861", + "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/MODULE.bazel": "51f2312901470cdab0dbdf3b88c40cd21c62a7ed58a3de45b365ddc5b11bcab2", + "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/source.json": "cea3901d7e299da7320700abbaafe57a65d039f10d0d7ea601c4a66938ea4b0c", "https://bcr.bazel.build/modules/abseil-py/2.1.0/MODULE.bazel": "5ebe5bf853769c65707e5c28f216798f7a4b1042015e6a36e6d03094d94bec8a", "https://bcr.bazel.build/modules/abseil-py/2.1.0/source.json": "0e8fc4f088ce07099c1cd6594c20c7ddbb48b4b3c0849b7d94ba94be88ff042b", "https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896", "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", - "https://bcr.bazel.build/modules/apple_support/1.23.1/MODULE.bazel": "53763fed456a968cf919b3240427cf3a9d5481ec5466abc9d5dc51bc70087442", - "https://bcr.bazel.build/modules/apple_support/1.23.1/source.json": "d888b44312eb0ad2c21a91d026753f330caa48a25c9b2102fae75eb2b0dcfdd2", + "https://bcr.bazel.build/modules/apple_support/1.21.0/MODULE.bazel": "ac1824ed5edf17dee2fdd4927ada30c9f8c3b520be1b5fd02a5da15bc10bff3e", + "https://bcr.bazel.build/modules/apple_support/1.21.1/MODULE.bazel": "5809fa3efab15d1f3c3c635af6974044bac8a4919c62238cce06acee8a8c11f1", + "https://bcr.bazel.build/modules/apple_support/1.24.1/MODULE.bazel": "f46e8ddad60aef170ee92b2f3d00ef66c147ceafea68b6877cb45bd91737f5f8", + "https://bcr.bazel.build/modules/apple_support/1.24.2/MODULE.bazel": "0e62471818affb9f0b26f128831d5c40b074d32e6dda5a0d3852847215a41ca4", + "https://bcr.bazel.build/modules/apple_support/2.3.0/MODULE.bazel": "d48f824ae8eeea5f837eb3038cef3615075d996d3e82eb9187192c2820605a81", + "https://bcr.bazel.build/modules/apple_support/2.3.0/source.json": "d99b0a50918c4484856d773026b2fd60a694668c70fc0e19d592786dc7e5e469", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.11.0/MODULE.bazel": "cb1ba9f9999ed0bc08600c221f532c1ddd8d217686b32ba7d45b0713b5131452", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "2b31ffcc9bdc8295b2167e07a757dbbc9ac8906e7028e5170a3708cecaac119f", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.2/MODULE.bazel": "30dfabbfae0139b1f0036e01c201dd4c0167da3017f0b7ef3820d78e07622989", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.3/MODULE.bazel": "253d739ba126f62a5767d832765b12b59e9f8d2bc88cc1572f4a73e46eb298ca", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/MODULE.bazel": "004ba890363d05372a97248c37205ae64b6fa31047629cd2c0895a9d0c7779e8", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/source.json": "ac2c3213df8f985785f1d0aeb7f0f73d5324e6e67d593d9b9470fb74a25d4a9b", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.7/MODULE.bazel": "491f8681205e31bb57892d67442ce448cda4f472a8e6b3dc062865e29a64f89c", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838", + "https://bcr.bazel.build/modules/aspect_rules_js/2.0.0/MODULE.bazel": "b45b507574aa60a92796e3e13c195cd5744b3b8aff516a9c0cb5ae6a048161c5", + "https://bcr.bazel.build/modules/aspect_rules_js/3.1.2/MODULE.bazel": "e3685502155d3cc65f3bf98e714f7435de67d7f8f355d63478a80197310311fc", + "https://bcr.bazel.build/modules/aspect_rules_js/3.1.2/source.json": "a32ab71831452b945f3f83a1b1feb9402007e600bce55ac76e15ef0c1e08b520", + "https://bcr.bazel.build/modules/aspect_rules_rollup/2.0.1/MODULE.bazel": "296e3a053658c2af989ba9bd62a205e6d1fa84bdd6dd5249196546e6b84770ec", + "https://bcr.bazel.build/modules/aspect_rules_rollup/2.0.1/source.json": "2fe8ac1ccb4de74bf884761e070010280b272d94e3997205b361b91c75409726", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.8.8/MODULE.bazel": "b52b929a948438665809d49af610f58d1b14f63d6d21ab748f47b6050be4c1f6", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.8.8/source.json": "5414530b761a45ab7ca6c49f0a2a9cf8dc0da772f5037cf05ca18aaa64bb1b19", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.3.3/MODULE.bazel": "37c764292861c2f70314efa9846bb6dbb44fc0308903b3285da6528305450183", + "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.3.3/source.json": "605086bbc197743a0d360f7ddc550a1d4dfa0441bc807236e17170f636153348", "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", + "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", "https://bcr.bazel.build/modules/bazel_features/1.13.0/MODULE.bazel": "c14c33c7c3c730612bdbe14ebbb5e61936b6f11322ea95a6e91cd1ba962f94df", "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", @@ -30,15 +52,28 @@ "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", "https://bcr.bazel.build/modules/bazel_features/1.23.0/MODULE.bazel": "fd1ac84bc4e97a5a0816b7fd7d4d4f6d837b0047cf4cbd81652d616af3a6591a", + "https://bcr.bazel.build/modules/bazel_features/1.25.0/MODULE.bazel": "e2e60a10a6da64bbf533f15ca652bf61a033e41c2ed734d79a9a08ba87f68c1a", "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65", "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9", "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", + "https://bcr.bazel.build/modules/bazel_features/1.32.0/MODULE.bazel": "095d67022a58cb20f7e20e1aefecfa65257a222c18a938e2914fd257b5f1ccdc", "https://bcr.bazel.build/modules/bazel_features/1.33.0/MODULE.bazel": "8b8dc9d2a4c88609409c3191165bccec0e4cb044cd7a72ccbe826583303459f6", + "https://bcr.bazel.build/modules/bazel_features/1.34.0/MODULE.bazel": "e8475ad7c8965542e0c7aac8af68eb48c4af904be3d614b6aa6274c092c2ea1e", + "https://bcr.bazel.build/modules/bazel_features/1.36.0/MODULE.bazel": "596cb62090b039caf1cad1d52a8bc35cf188ca9a4e279a828005e7ee49a1bec3", "https://bcr.bazel.build/modules/bazel_features/1.39.0/MODULE.bazel": "28739425c1fc283c91931619749c832b555e60bcd1010b40d8441ce0a5cf726d", - "https://bcr.bazel.build/modules/bazel_features/1.39.0/source.json": "f63cbeb4c602098484d57001e5a07d31cb02bbccde9b5e2c9bf0b29d05283e93", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.41.0/MODULE.bazel": "6e0f87fafed801273c371d41e22a15a6f8abf83fdd7f87d5e44ad317b94433d0", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/MODULE.bazel": "275a59b5406ff18c01739860aa70ad7ccb3cfb474579411decca11c93b951080", + "https://bcr.bazel.build/modules/bazel_features/1.45.0/MODULE.bazel": "7daec6d87ab0703417486d4cb948af0b06f55d4d7c08cbb5978c80e79b538edf", + "https://bcr.bazel.build/modules/bazel_features/1.45.0/source.json": "635e4536e09ff125b8972e0fa239c135fde5f18701f7d5115680560651dfb41d", + "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0-rc.0/MODULE.bazel": "d6e00979a98ac14ada5e31c8794708b41434d461e7e7ca39b59b765e6d233b18", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0/MODULE.bazel": "22b70b80ac89ad3f3772526cd9feee2fa412c2b01933fea7ed13238a448d370d", + "https://bcr.bazel.build/modules/bazel_lib/3.1.0/MODULE.bazel": "6809765c14e3c766a9b9286c7b0ec56ed87a73326e48fe01749f0c0fdcfe3287", + "https://bcr.bazel.build/modules/bazel_lib/3.2.2/MODULE.bazel": "e2c890c8a515d6bca9c66d47718aa9e44b458fde64ec7204b8030bf2d349058c", + "https://bcr.bazel.build/modules/bazel_lib/3.2.2/source.json": "9e84e115c20e14652c5c21401ae85ff4daa8702e265b5c0b3bf89353f17aa212", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", @@ -55,18 +90,26 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/MODULE.bazel": "72997b29dfd95c3fa0d0c48322d05590418edef451f8db8db5509c57875fb4b7", "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/source.json": "7ad77c1e8c1b84222d9b3f3cae016a76639435744c19330b0b37c0a3c9da7dc0", "https://bcr.bazel.build/modules/bazel_worker_api/0.0.1/MODULE.bazel": "02a13b77321773b2042e70ee5e4c5e099c8ddee4cf2da9cd420442c36938d4bd", + "https://bcr.bazel.build/modules/bazel_worker_api/0.0.10/MODULE.bazel": "a426df551b40c3997c351d05f00f0d1f86b618ddb646012f1e9c72efce8ed939", + "https://bcr.bazel.build/modules/bazel_worker_api/0.0.10/source.json": "7f220d3edfeba5d1f61535fd8400338df74f8bdeb241ebe660534c6926a5a645", "https://bcr.bazel.build/modules/bazel_worker_api/0.0.4/MODULE.bazel": "460aa12d01231a80cce03c548287b433b321d205b0028ae596728c35e5ee442e", - "https://bcr.bazel.build/modules/bazel_worker_api/0.0.4/source.json": "d353c410d47a8b65d09fa98e83d57ebec257a2c2b9c6e42d6fda1cb25e5464a5", + "https://bcr.bazel.build/modules/bazel_worker_api/0.0.8/MODULE.bazel": "396c1ef53835aafe3d42ce6619080531ee770648303731f16cfaa33fa056bf0c", + "https://bcr.bazel.build/modules/bazel_worker_java/0.0.10/MODULE.bazel": "538f21f715cab81c4a43f73e1a91a0c1f8accd9b263dd2a831952805fcfc1a62", + "https://bcr.bazel.build/modules/bazel_worker_java/0.0.10/source.json": "1646d3aaf5a4bc0d587ecb33d81e93ea9284ff96f5ef579006a9c054945bbe7a", "https://bcr.bazel.build/modules/bazel_worker_java/0.0.4/MODULE.bazel": "82494a01018bb7ef06d4a17ec4cd7a758721f10eb8b6c820a818e70d669500db", - "https://bcr.bazel.build/modules/bazel_worker_java/0.0.4/source.json": "a2d30458fd86cf022c2b6331e652526fa08e17573b2f5034a9dbcacdf9c2583c", - "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", - "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/bazel_worker_java/0.0.8/MODULE.bazel": "e76479eae70bd4e8f5f4c2dfc5d03ab971cfb18750246c7b3f3454c5c2ee6629", + "https://bcr.bazel.build/modules/buildozer/8.5.1/MODULE.bazel": "a35d9561b3fc5b18797c330793e99e3b834a473d5fbd3d7d7634aafc9bdb6f8f", + "https://bcr.bazel.build/modules/buildozer/8.5.1/source.json": "e3386e6ff4529f2442800dee47ad28d3e6487f36a1f75ae39ae56c70f0cd2fbd", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/MODULE.bazel": "cdf8cbe5ee750db04b78878c9633cc76e80dcf4416cbe982ac3a9222f80713c8", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.3/MODULE.bazel": "f1b7bb2dd53e8f2ef984b39485ec8a44e9076dda5c4b8efd2fb4c6a6e856a31d", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.3/source.json": "ebe931bfe362e4b41e59ee00a528db6074157ff2ced92eb9e970acab2e1089c9", "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0", "https://bcr.bazel.build/modules/gazelle/0.40.0/MODULE.bazel": "42ba5378ebe845fca43989a53186ab436d956db498acde790685fe0e8f9c6146", - "https://bcr.bazel.build/modules/gazelle/0.40.0/source.json": "1e5ef6e4d8b9b6836d93273c781e78ff829ea2e077afef7a57298040fa4f010a", + "https://bcr.bazel.build/modules/gazelle/0.47.0/MODULE.bazel": "b61bb007c4efad134aa30ee7f4a8e2a39b22aa5685f005edaa022fbd1de43ebc", + "https://bcr.bazel.build/modules/gazelle/0.47.0/source.json": "aeb2e5df14b7fb298625d75d08b9c65bdb0b56014c5eb89da9e5dd0572280ae6", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", @@ -74,12 +117,18 @@ "https://bcr.bazel.build/modules/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108", "https://bcr.bazel.build/modules/googletest/1.17.0/MODULE.bazel": "dbec758171594a705933a29fcf69293d2468c49ec1f2ebca65c36f504d72df46", "https://bcr.bazel.build/modules/googletest/1.17.0/source.json": "38e4454b25fc30f15439c0378e57909ab1fd0a443158aa35aec685da727cd713", + "https://bcr.bazel.build/modules/jq.bzl/0.1.0/MODULE.bazel": "2ce69b1af49952cd4121a9c3055faa679e748ce774c7f1fda9657f936cae902f", + "https://bcr.bazel.build/modules/jq.bzl/0.4.0/MODULE.bazel": "a7b39b37589f2b0dad53fd6c1ccaabbdb290330caa920d7ef3e6aad068cd4ab2", + "https://bcr.bazel.build/modules/jq.bzl/0.4.0/source.json": "52ec7530c4618e03f634b30ff719814a68d7d39c235938b7aa2abbfe1eb1c52c", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", + "https://bcr.bazel.build/modules/jsoncpp/1.9.6.bcr.2/MODULE.bazel": "64f508885f907ac2518039b3d0c5c1703a8f6137d9f5d635067ba93d33c2670a", + "https://bcr.bazel.build/modules/jsoncpp/1.9.6.bcr.2/source.json": "13199fa0a267ca46814e9e6ab313a71890c8f1822e57376fdc23a2d2cd384051", "https://bcr.bazel.build/modules/jsoncpp/1.9.6/MODULE.bazel": "2f8d20d3b7d54143213c4dfc3d98225c42de7d666011528dc8fe91591e2e17b0", - "https://bcr.bazel.build/modules/jsoncpp/1.9.6/source.json": "a04756d367a2126c3541682864ecec52f92cdee80a35735a3cb249ce015ca000", "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74", - "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9", + "https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92", + "https://bcr.bazel.build/modules/package_metadata/0.0.3/MODULE.bazel": "77890552ecea9e284b5424c9de827a58099348763a4359e975c359a83d4faa83", + "https://bcr.bazel.build/modules/package_metadata/0.0.5/MODULE.bazel": "ef4f9439e3270fdd6b9fd4dbc3d2f29d13888e44c529a1b243f7a31dfbc2e8e4", "https://bcr.bazel.build/modules/package_metadata/0.0.7/MODULE.bazel": "7adb03933fc8401f495800cf4eafcff0edc6da0ff55c7db223ef69d19f689486", "https://bcr.bazel.build/modules/package_metadata/0.0.7/source.json": "50639625e937b56115012674c797cca7a05a96b4878c87d803c13dc2b31de8a0", "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", @@ -98,17 +147,21 @@ "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", "https://bcr.bazel.build/modules/protobuf/27.2/MODULE.bazel": "32450b50673882e4c8c3d10a83f3bc82161b213ed2f80d17e38bece8f165c295", + "https://bcr.bazel.build/modules/protobuf/29.0-rc2.bcr.1/MODULE.bazel": "52f4126f63a2f0bbf36b99c2a87648f08467a4eaf92ba726bc7d6a500bbf770c", "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", "https://bcr.bazel.build/modules/protobuf/29.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95", + "https://bcr.bazel.build/modules/protobuf/29.3/MODULE.bazel": "77480eea5fb5541903e49683f24dc3e09f4a79e0eea247414887bb9fc0066e94", "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", "https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573", "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", "https://bcr.bazel.build/modules/protobuf/31.1/MODULE.bazel": "379a389bb330b7b8c1cdf331cc90bf3e13de5614799b3b52cdb7c6f389f6b38e", "https://bcr.bazel.build/modules/protobuf/32.1/MODULE.bazel": "89cd2866a9cb07fee9ff74c41ceace11554f32e0d849de4e23ac55515cfada4d", + "https://bcr.bazel.build/modules/protobuf/33.1/MODULE.bazel": "982c8a0cceab4d790076f72b7677faf836b0dfadc2b66a34aab7232116c4ae39", "https://bcr.bazel.build/modules/protobuf/33.4/MODULE.bazel": "114775b816b38b6d0ca620450d6b02550c60ceedfdc8d9a229833b34a223dc42", - "https://bcr.bazel.build/modules/protobuf/33.4/source.json": "555f8686b4c7d6b5ba731fbea13bf656b4bfd9a7ff629c1d9d3f6e1d6155de79", + "https://bcr.bazel.build/modules/protobuf/35.0/MODULE.bazel": "ee97170c013d94c366bbb5fe8a97b0650477cbc00e5e0f6a2c297484bc34d81b", + "https://bcr.bazel.build/modules/protobuf/35.0/source.json": "2162e80adef862606ba85ae42e0df85eb519376fd6ab49caddee6fb776a1281e", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34", "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/source.json": "6900fdc8a9e95866b8c0d4ad4aba4d4236317b5c1cd04c502df3f0d33afed680", @@ -117,13 +170,15 @@ "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/source.json": "2ff292be6ef3340325ce8a045ecc326e92cbfab47c7cbab4bd85d28971b97ac4", "https://bcr.bazel.build/modules/re2/2024-07-02/MODULE.bazel": "0eadc4395959969297cbcf31a249ff457f2f1d456228c67719480205aa306daa", "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", + "https://bcr.bazel.build/modules/rules_android/0.6.4/MODULE.bazel": "b4cde12d506dd65d82b2be39761f49f5797303343a3d5b4ee191c0cdf9ef387c", "https://bcr.bazel.build/modules/rules_android/0.6.6/MODULE.bazel": "b0fb569752aab65ab1a9db0a8f6cfaf5aa1754965e17e95dcf0e4d88e192a68d", - "https://bcr.bazel.build/modules/rules_android/0.6.6/source.json": "a9d8dc2d5a102dc03269a94acc886a4cab82cdcb9ccbc77b0f665d6d17a6ae09", + "https://bcr.bazel.build/modules/rules_android/0.7.1/MODULE.bazel": "a806fc382a774252f228a40e3b11b9fcc6276f8778c7fb33e9f72937c6258363", + "https://bcr.bazel.build/modules/rules_android/0.7.2/MODULE.bazel": "0862d727582c8d117ac09cd3451b5e93d8b91033f0f13bb61a4a10334958130d", + "https://bcr.bazel.build/modules/rules_android/0.7.2/source.json": "bf2dfc8dc72ffd2b58643c9d547f1b7faea4de65aa28ac4894687fa6afa09861", "https://bcr.bazel.build/modules/rules_apple/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a", - "https://bcr.bazel.build/modules/rules_apple/3.16.0/source.json": "d8b5fe461272018cc07cfafce11fe369c7525330804c37eec5a82f84cd475366", + "https://bcr.bazel.build/modules/rules_apple/4.1.0/MODULE.bazel": "76e10fd4a48038d3fc7c5dc6e63b7063bbf5304a2e3bd42edda6ec660eebea68", "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", - "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", @@ -132,10 +187,16 @@ "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", + "https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0", "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", "https://bcr.bazel.build/modules/rules_cc/0.2.0/MODULE.bazel": "b5c17f90458caae90d2ccd114c81970062946f49f355610ed89bebf954f5783c", + "https://bcr.bazel.build/modules/rules_cc/0.2.13/MODULE.bazel": "eecdd666eda6be16a8d9dc15e44b5c75133405e820f620a234acc4b1fdc5aa37", "https://bcr.bazel.build/modules/rules_cc/0.2.14/MODULE.bazel": "353c99ed148887ee89c54a17d4100ae7e7e436593d104b668476019023b58df8", - "https://bcr.bazel.build/modules/rules_cc/0.2.14/source.json": "55d0a4587c5592fad350f6e698530f4faf0e7dd15e69d43f8d87e220c78bea54", + "https://bcr.bazel.build/modules/rules_cc/0.2.15/MODULE.bazel": "6a0a4a75a57aa6dc888300d848053a58c6b12a29f89d4304e1c41448514ec6e8", + "https://bcr.bazel.build/modules/rules_cc/0.2.16/MODULE.bazel": "9242fa89f950c6ef7702801ab53922e99c69b02310c39fb6e62b2bd30df2a1d4", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", + "https://bcr.bazel.build/modules/rules_cc/0.2.4/MODULE.bazel": "1ff1223dfd24f3ecf8f028446d4a27608aa43c3f41e346d22838a4223980b8cc", "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", @@ -144,29 +205,40 @@ "https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd", "https://bcr.bazel.build/modules/rules_go/0.50.1/MODULE.bazel": "b91a308dc5782bb0a8021ad4330c81fea5bda77f96b9e4c117b9b9c8f6665ee0", "https://bcr.bazel.build/modules/rules_go/0.51.0-rc2/MODULE.bazel": "edfc3a9cea7bedb0eaaff37b0d7817c1a4bf72b3c615580b0ffcee6c52690fd4", - "https://bcr.bazel.build/modules/rules_go/0.51.0-rc2/source.json": "6b5cd0b3da2bd0e6949580851db990a04af0a285f072b9a0f059424457cd8cc9", - "https://bcr.bazel.build/modules/rules_java/8.16.1/MODULE.bazel": "0f20b1cecaa8e52f60a8f071e59a20b4e3b9a67f6c56c802ea256f6face692d3", - "https://bcr.bazel.build/modules/rules_java/8.16.1/source.json": "072f8d11264edc499621be2dc9ea01d6395db5aa6f8799c034ae01a3e857f2e4", + "https://bcr.bazel.build/modules/rules_go/0.53.0/MODULE.bazel": "a4ed760d3ac0dbc0d7b967631a9a3fd9100d28f7d9fcf214b4df87d4bfff5f9a", + "https://bcr.bazel.build/modules/rules_go/0.59.0/MODULE.bazel": "b7e43e7414a3139a7547d1b4909b29085fbe5182b6c58cbe1ed4c6272815aeae", + "https://bcr.bazel.build/modules/rules_go/0.60.0/MODULE.bazel": "4a57ff2ffc2a3570e3c5646575c5a4b07287e91bcdac5d1f72383d51502b48cb", + "https://bcr.bazel.build/modules/rules_go/0.60.0/source.json": "1e21368c5e0c3013a110bd79a8fcff8ca46b5bcb2b561713a7273cbfcff7c464", + "https://bcr.bazel.build/modules/rules_java/9.3.0/MODULE.bazel": "f657c72d65ac449caae9abf2e68e66c0d36f9416848c4c4903d0b3234229e7f2", + "https://bcr.bazel.build/modules/rules_java/9.3.0/source.json": "59ae7e662c3c7042b88bbb42ad12483523e234c65ebe4c51611baa43e85cb248", "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", "https://bcr.bazel.build/modules/rules_jvm_external/6.10/MODULE.bazel": "33e636ca6bc9ee0fa090a38aa33c631ded2d8cf6fead4124181d1b35dc474f7c", - "https://bcr.bazel.build/modules/rules_jvm_external/6.10/source.json": "c191249787625db72616a3fb3cc2786ab57355a2e3b615402b8b3b66b0f995b7", "https://bcr.bazel.build/modules/rules_jvm_external/6.2/MODULE.bazel": "36a6e52487a855f33cb960724eb56547fa87e2c98a0474c3acad94339d7f8e99", "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", "https://bcr.bazel.build/modules/rules_jvm_external/6.6/MODULE.bazel": "153042249c7060536dc95b6bb9f9bb8063b8a0b0cb7acdb381bddbc2374aed55", "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd", + "https://bcr.bazel.build/modules/rules_jvm_external/6.9/MODULE.bazel": "07c5db05527db7744a54fcffd653e1550d40e0540207a7f7e6d0a4de5bef8274", + "https://bcr.bazel.build/modules/rules_jvm_external/7.0/MODULE.bazel": "421482bdbcf05709f933c96b867a599deb517f2804ceb3e74511880610cfbf71", + "https://bcr.bazel.build/modules/rules_jvm_external/7.0/source.json": "714cd003eadf5be5c83268311fe8e951db39f802babeaddc536b3560dc8f6faf", "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", "https://bcr.bazel.build/modules/rules_kotlin/1.9.5/MODULE.bazel": "043a16a572f610558ec2030db3ff0c9938574e7dd9f58bded1bb07c0192ef025", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", "https://bcr.bazel.build/modules/rules_kotlin/2.1.3/MODULE.bazel": "ce7def6d576aa8d3a9c6d10e13b4d157296229674371f67dbf788dae0afae3d5", - "https://bcr.bazel.build/modules/rules_kotlin/2.1.3/source.json": "0b0dc9400f14b5fbb13d278ad3bf0413cdbaf0da0db337e055b855e35b878a3b", + "https://bcr.bazel.build/modules/rules_kotlin/2.2.2/MODULE.bazel": "00d39c5e0fa78cd86193946265bb849e7878c24e44260f9525108428852b315c", + "https://bcr.bazel.build/modules/rules_kotlin/2.3.20/MODULE.bazel": "3443d53d275e14fecfebd0b491f01d06ea3883c04a1b3336e7ae9d5ec9066bef", + "https://bcr.bazel.build/modules/rules_kotlin/2.3.20/source.json": "5a5553cffea43f2c5156c8ad0de4a14ad95413ceb39cd4d08f50e2aea86927e8", "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", + "https://bcr.bazel.build/modules/rules_nodejs/6.2.0/MODULE.bazel": "ec27907f55eb34705adb4e8257952162a2d4c3ed0f0b3b4c3c1aad1fac7be35e", + "https://bcr.bazel.build/modules/rules_nodejs/6.7.3/MODULE.bazel": "c22a48b2a0dbf05a9dc5f83837bbc24c226c1f6e618de3c3a610044c9f336056", + "https://bcr.bazel.build/modules/rules_nodejs/6.7.4/MODULE.bazel": "e6a241a55c82e999145553d2e00a08fc6ebadf62b63d108fb5e984696ffd0bd2", + "https://bcr.bazel.build/modules/rules_nodejs/6.7.4/source.json": "34e7a8a3b4c8d630ac0e0492b3fed9dba41fe008a0edf220b7d88fa38ac53698", "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", @@ -187,26 +259,31 @@ "https://bcr.bazel.build/modules/rules_python/0.37.1/MODULE.bazel": "3faeb2d9fa0a81f8980643ee33f212308f4d93eea4b9ce6f36d0b742e71e9500", "https://bcr.bazel.build/modules/rules_python/0.37.2/MODULE.bazel": "b5ffde91410745750b6c13be1c5dc4555ef5bc50562af4a89fd77807fdde626a", "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", - "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", "https://bcr.bazel.build/modules/rules_python/1.0.0/MODULE.bazel": "898a3d999c22caa585eb062b600f88654bf92efb204fa346fb55f6f8edffca43", + "https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13", "https://bcr.bazel.build/modules/rules_python/1.4.1/MODULE.bazel": "8991ad45bdc25018301d6b7e1d3626afc3c8af8aaf4bc04f23d0b99c938b73a6", "https://bcr.bazel.build/modules/rules_python/1.6.0/MODULE.bazel": "7e04ad8f8d5bea40451cf80b1bd8262552aa73f841415d20db96b7241bd027d8", "https://bcr.bazel.build/modules/rules_python/1.7.0/MODULE.bazel": "d01f995ecd137abf30238ad9ce97f8fc3ac57289c8b24bd0bf53324d937a14f8", - "https://bcr.bazel.build/modules/rules_python/1.8.0-rc1/MODULE.bazel": "d5348333fd8be9589c3ea8d9110fd00b1c7d84bc7b505668307cbe9f105e9c8f", - "https://bcr.bazel.build/modules/rules_python/1.8.0-rc1/source.json": "d26719d5b92a569eaa387bad9667a042d4810193f279ea64590c9d76ae38f3ba", + "https://bcr.bazel.build/modules/rules_python/2.0.2/MODULE.bazel": "9d26ecb43f248c2663fb29f3c9b833aa86c31e736096e58c7077f80038f8d32f", + "https://bcr.bazel.build/modules/rules_python/2.0.2/source.json": "36a74dedc1498bfbf72855568a24e6aa30c70b5ac7e114bc244c62b3a58b47fc", "https://bcr.bazel.build/modules/rules_robolectric/4.14.1.2/MODULE.bazel": "d44fec647d0aeb67b9f3b980cf68ba634976f3ae7ccd6c07d790b59b87a4f251", "https://bcr.bazel.build/modules/rules_robolectric/4.14.1.2/source.json": "37c10335f2361c337c5c1f34ed36d2da70534c23088062b33a8bdaab68aa9dea", + "https://bcr.bazel.build/modules/rules_rust/0.69.0/MODULE.bazel": "4326fec48f2fef0d514de46346f7f77e200c82936dd08b91c9ef039fbdad5c10", + "https://bcr.bazel.build/modules/rules_rust/0.69.0/source.json": "0d094307d690cc18b3ab003998697be8070a206f65592c5c8476999796f11c4b", "https://bcr.bazel.build/modules/rules_shell/0.1.2/MODULE.bazel": "66e4ca3ce084b04af0b9ff05ff14cab4e5df7503973818bb91cbc6cda08d32fc", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", "https://bcr.bazel.build/modules/rules_shell/0.4.1/MODULE.bazel": "00e501db01bbf4e3e1dd1595959092c2fadf2087b2852d3f553b5370f5633592", "https://bcr.bazel.build/modules/rules_shell/0.6.1/MODULE.bazel": "72e76b0eea4e81611ef5452aa82b3da34caca0c8b7b5c0c9584338aa93bae26b", - "https://bcr.bazel.build/modules/rules_shell/0.6.1/source.json": "20ec05cd5e592055e214b2da8ccb283c7f2a421ea0dc2acbf1aa792e11c03d0c", + "https://bcr.bazel.build/modules/rules_shell/0.8.0/MODULE.bazel": "f6a89f1d6a669a26f28fe814503857055d76306b79cfc11d12399af08d0b80ae", + "https://bcr.bazel.build/modules/rules_shell/0.8.0/source.json": "eb53cc815bc503c6683c5fe12d943f98883f81fc22f51403ec8a95610cba4195", "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca", "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046", - "https://bcr.bazel.build/modules/rules_swift/2.1.1/source.json": "40fc69dfaac64deddbb75bd99cdac55f4427d9ca0afbe408576a65428427a186", + "https://bcr.bazel.build/modules/rules_swift/2.4.0/MODULE.bazel": "1639617eb1ede28d774d967a738b4a68b0accb40650beadb57c21846beab5efd", + "https://bcr.bazel.build/modules/rules_swift/3.1.2/MODULE.bazel": "72c8f5cf9d26427cee6c76c8e3853eb46ce6b0412a081b2b6db6e8ad56267400", "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", + "https://bcr.bazel.build/modules/stardoc/0.5.4/MODULE.bazel": "6569966df04610b8520957cb8e97cf2e9faac2c0309657c537ab51c16c18a2a4", "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", "https://bcr.bazel.build/modules/stardoc/0.6.2/MODULE.bazel": "7060193196395f5dd668eda046ccbeacebfd98efc77fed418dbe2b82ffaa39fd", "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", @@ -214,9 +291,18 @@ "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91", - "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "75aab2373a4bbe2a1260b9bf2a1ebbdbf872d3bd36f80bff058dccd82e89422f", + "https://bcr.bazel.build/modules/tar.bzl/0.10.4/MODULE.bazel": "e8f9ff79199e8d9eaad7f1b0a77ad74b30bb82d794b87d8ca942bead5de83ae9", + "https://bcr.bazel.build/modules/tar.bzl/0.10.4/source.json": "20143442376c03426f6135292ba02d825cb75308aa47e6bf42dd4cc5a435c2ff", + "https://bcr.bazel.build/modules/tar.bzl/0.2.1/MODULE.bazel": "52d1c00a80a8cc67acbd01649e83d8dd6a9dc426a6c0b754a04fe8c219c76468", + "https://bcr.bazel.build/modules/tar.bzl/0.5.1/MODULE.bazel": "7c2eb3dcfc53b0f3d6f9acdfd911ca803eaf92aadf54f8ca6e4c1f3aee288351", + "https://bcr.bazel.build/modules/toml.bzl/0.3.0/MODULE.bazel": "5016e5dd1ad2200e119a4b28b2b3935e276c4b480f2fe3e952bea7eeba88f578", + "https://bcr.bazel.build/modules/toml.bzl/0.3.0/source.json": "0cf7c878c419b37ddb55f3dd93dd7c0c409bd7c4efacb3da504e0748780b2fa9", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", "https://bcr.bazel.build/modules/upb/0.0.0-20230516-61a97ef/MODULE.bazel": "c0df5e35ad55e264160417fd0875932ee3c9dda63d9fccace35ac62f45e1b6f9", + "https://bcr.bazel.build/modules/yq.bzl/0.1.1/MODULE.bazel": "9039681f9bcb8958ee2c87ffc74bdafba9f4369096a2b5634b88abc0eaefa072", + "https://bcr.bazel.build/modules/yq.bzl/0.3.4/MODULE.bazel": "d3a270662f5d766cd7229732d65a5a5bc485240c3007343dd279edfb60c9ae27", + "https://bcr.bazel.build/modules/yq.bzl/0.3.4/source.json": "786dafdc2843722da3416e4343ee1a05237227f068590779a6e8496a2064c0f9", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", @@ -225,15 +311,75 @@ }, "selectedYankedVersions": {}, "moduleExtensions": { + "@@aspect_rules_ts+//ts:extensions.bzl%ext": { + "general": { + "bzlTransitiveDigest": "znGIWhsWB2BLftYOmjVGiqrZuccFABBogiS+i4Kg3tU=", + "usagesDigest": "wmTY4QJrwyD3VUSOHaA0OPkvX0ywR/XPM2EecaJ8yGk=", + "recordedInputs": [ + "REPO_MAPPING:aspect_rules_ts+,aspect_rules_ts aspect_rules_ts+", + "REPO_MAPPING:aspect_rules_ts+,bazel_tools bazel_tools", + "FILE:@@//package.json f230bdb8c76c59a925ec0727a1decc727388d47d4b20d95b4e5bcf328edc0c79" + ], + "generatedRepoSpecs": { + "npm_typescript": { + "repoRuleId": "@@aspect_rules_ts+//ts/private:npm_repositories.bzl%http_archive_version", + "attributes": { + "version": "", + "version_from": "@@//:package.json", + "integrity": "", + "urls": [ + "https://registry.npmjs.org/typescript/-/typescript-{}.tgz" + ] + } + } + } + } + }, + "@@aspect_tools_telemetry+//:extension.bzl%telemetry": { + "general": { + "bzlTransitiveDigest": "cl5A2O84vDL6Tt+Qga8FCj1DUDGqn+e7ly5rZ+4xvcc=", + "usagesDigest": "VyFtm1DFYJGrU6mU+xj6UF6QCpzpFzBONF0/8BC6ZC8=", + "recordedInputs": [ + "REPO_MAPPING:aspect_tools_telemetry+,bazel_lib bazel_lib+", + "REPO_MAPPING:aspect_tools_telemetry+,bazel_skylib bazel_skylib+" + ], + "generatedRepoSpecs": { + "aspect_tools_telemetry_report": { + "repoRuleId": "@@aspect_tools_telemetry+//:extension.bzl%tel_repository", + "attributes": { + "deps": { + "aspect_rules_js": "3.1.2", + "aspect_rules_ts": "3.8.8", + "aspect_tools_telemetry": "0.3.3" + } + } + } + } + } + }, + "@@protobuf+//python/dist:system_python.bzl%system_python_extension": { + "general": { + "bzlTransitiveDigest": "qh0n9IrXU/xS94wxKQrG1J63zrLkA1Wy2Y3BQxptPcI=", + "usagesDigest": "tCi55FyqtOJ2jXh9vcjrHCl4ov3kpWiwKl103nA9BOI=", + "recordedInputs": [], + "generatedRepoSpecs": { + "system_python": { + "repoRuleId": "@@protobuf+//python/dist:system_python.bzl%system_python", + "attributes": { + "minimum_python_version": "3.9" + } + } + } + } + }, "@@pybind11_bazel+//:internal_configure.bzl%internal_configure_extension": { "general": { - "bzlTransitiveDigest": "NFQjcZF+fAvf5fDH+pqsx4JrfzP9PuHBz6S6ZutIbnw=", + "bzlTransitiveDigest": "b+RP7Sgl8KN0VHamrgTqzGLuYPcQ/Mo4ptNkkHUIIlA=", "usagesDigest": "D1r3lfzMuUBFxgG8V6o0bQTLMk3GkaGOaPzw53wrwyw=", - "recordedFileInputs": { - "@@pybind11_bazel+//MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34" - }, - "recordedDirentsInputs": {}, - "envVariables": {}, + "recordedInputs": [ + "REPO_MAPPING:pybind11_bazel+,bazel_tools bazel_tools", + "FILE:@@pybind11_bazel+//MODULE.bazel e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34" + ], "generatedRepoSpecs": { "pybind11": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", @@ -245,525 +391,491 @@ ] } } - }, - "recordedRepoMappingEntries": [ - [ - "pybind11_bazel+", - "bazel_tools", - "bazel_tools" - ] - ] + } } }, "@@rules_android+//bzlmod_extensions:apksig.bzl%apksig_extension": { "general": { - "bzlTransitiveDigest": "By9qVNN7G4oL1vYOJXye7Dp/CbR2ar9oxAW8WXAVcVw=", - "usagesDigest": "xq6OVkELeJvOgYo3oY/sUBsGFbcqdV+9BYiNgSPV/po=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, + "bzlTransitiveDigest": "O/gCjP4/VVnaP+zTRGN3DFrkkxLE5GMbE4M1HG3noGQ=", + "usagesDigest": "S8lLnnZxdeYUYq3kIGhVMk0wQ9Fd6elmCskvn+SL6iw=", + "recordedInputs": [ + "REPO_MAPPING:rules_android+,bazel_tools bazel_tools" + ], "generatedRepoSpecs": { "apksig": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "url": "https://android.googlesource.com/platform/tools/apksig/+archive/24e3075e68ebe17c0b529bb24bfda819db5e2f3b.tar.gz", + "urls": [ + "https://mirror.bazel.build/android.googlesource.com/platform/tools/apksig/+archive/24e3075e68ebe17c0b529bb24bfda819db5e2f3b.tar.gz" + ], + "sha256": "12e44fdbd219c5e1cc62099c2a01d775957603d2d4f693f8285f9d95d9a04e77", "build_file": "@@rules_android+//bzlmod_extensions:apksig.BUILD" } } - }, - "recordedRepoMappingEntries": [ - [ - "rules_android+", - "bazel_tools", - "bazel_tools" - ] - ] + } } }, "@@rules_android+//bzlmod_extensions:com_android_dex.bzl%com_android_dex_extension": { "general": { - "bzlTransitiveDigest": "rvWbJQc8jInfIAaXIMhSOqUlwM9HVeLey6q0ISvg08Y=", - "usagesDigest": "toF8IFMu98H/VU2p1sfVC5fVXVYJunpbbmtM6tOsQXY=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, + "bzlTransitiveDigest": "fVTI/3B6KjJ93jRf+pOa6ExSALb1hgRndpTPBrJKoZQ=", + "usagesDigest": "0hluQmaWiWak6sVMP5L4wXhNyIwv9fw0y5JJ8lnPb1c=", + "recordedInputs": [ + "REPO_MAPPING:rules_android+,bazel_tools bazel_tools" + ], "generatedRepoSpecs": { "com_android_dex": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "url": "https://android.googlesource.com/platform/dalvik/+archive/5a81c499a569731e2395f7c8d13c0e0d4e17a2b6.tar.gz", - "build_file": "@@rules_android+//bzlmod_extensions:com_android_dex.BUILD" + "urls": [ + "https://mirror.bazel.build/android.googlesource.com/platform/dalvik/+archive/5a81c499a569731e2395f7c8d13c0e0d4e17a2b6.tar.gz" + ], + "build_file": "@@rules_android+//bzlmod_extensions:com_android_dex.BUILD", + "sha256": "86b4848c038bf687fadc812239cb01fb8d1d15cef3125b480a0448360992b95d" } } - }, - "recordedRepoMappingEntries": [ - [ - "rules_android+", - "bazel_tools", - "bazel_tools" - ] - ] + } } }, "@@rules_android+//rules/android_sdk_repository:rule.bzl%android_sdk_repository_extension": { "general": { - "bzlTransitiveDigest": "NAy+0M15JNVEBb8Tny6t7j3lKqTnsAMjoBB6LJ+C370=", - "usagesDigest": "g9Ur6X6qhf9a8MmY9qXU/jFjkyk/aZVBegI0yVMF0z4=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, + "bzlTransitiveDigest": "qHbR00gVzVzkxX+PRtv4UGcUFMtBz7TK9CNYUWH8nIE=", + "usagesDigest": "tTIw/WMyb1t/LzacDY8lDjUznzxQ3MyXW6474WIS3WQ=", + "recordedInputs": [], "generatedRepoSpecs": { "androidsdk": { "repoRuleId": "@@rules_android+//rules/android_sdk_repository:rule.bzl%_android_sdk_repository", "attributes": {} } - }, - "recordedRepoMappingEntries": [] + } } }, - "@@rules_apple+//apple:apple.bzl%provisioning_profile_repository_extension": { + "@@rules_nodejs+//nodejs:extensions.bzl%node": { "general": { - "bzlTransitiveDigest": "DBjF8z9KnkAVkDon8si62fhfjje60FibbeIt+zE+BWw=", - "usagesDigest": "vsJl8Rw5NL+5Ag2wdUDoTeRF/5klkXO8545Iy7U1Q08=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, + "bzlTransitiveDigest": "oZFClfRhTTwsYzpxVPkOpOt/r0+OzEfEV37au0jFZ0s=", + "usagesDigest": "rmuj3zuYtKpBbldwwQvwLpB0VwZVHNwpx5rco2PvuLY=", + "recordedInputs": [], "generatedRepoSpecs": { - "local_provisioning_profiles": { - "repoRuleId": "@@rules_apple+//apple/internal:local_provisioning_profiles.bzl%provisioning_profile_repository", - "attributes": {} - } - }, - "recordedRepoMappingEntries": [ - [ - "apple_support+", - "bazel_skylib", - "bazel_skylib+" - ], - [ - "bazel_tools", - "rules_cc", - "rules_cc+" - ], - [ - "rules_apple+", - "bazel_skylib", - "bazel_skylib+" - ], - [ - "rules_apple+", - "bazel_tools", - "bazel_tools" - ], - [ - "rules_apple+", - "build_bazel_apple_support", - "apple_support+" - ], - [ - "rules_apple+", - "build_bazel_rules_swift", - "rules_swift+" - ], - [ - "rules_cc+", - "bazel_tools", - "bazel_tools" - ], - [ - "rules_cc+", - "cc_compatibility_proxy", - "rules_cc++compatibility_proxy+cc_compatibility_proxy" - ], - [ - "rules_cc+", - "rules_cc", - "rules_cc+" - ], - [ - "rules_cc++compatibility_proxy+cc_compatibility_proxy", - "rules_cc", - "rules_cc+" - ], - [ - "rules_swift+", - "bazel_skylib", - "bazel_skylib+" - ], - [ - "rules_swift+", - "bazel_tools", - "bazel_tools" - ], - [ - "rules_swift+", - "build_bazel_apple_support", - "apple_support+" - ], - [ - "rules_swift+", - "build_bazel_rules_swift", - "rules_swift+" - ], - [ - "rules_swift+", - "build_bazel_rules_swift_local_config", - "rules_swift++non_module_deps+build_bazel_rules_swift_local_config" - ] - ] - } - }, - "@@rules_apple+//apple:extensions.bzl%non_module_deps": { - "general": { - "bzlTransitiveDigest": "4xtddSlWIQdtVNVuvOI62fJfQVETHZCVWFvYYwQHMR4=", - "usagesDigest": "M3VqFpeTCo4qmrNKGZw0dxBHvTYDrfV3cscGzlSAhQ4=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "xctestrunner": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "nodejs_linux_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", "attributes": { - "urls": [ - "https://github.com/google/xctestrunner/archive/b7698df3d435b6491b4b4c0f9fc7a63fbed5e3a6.tar.gz" + "node_download_auth": {}, + "node_repositories": { + "24.14.1-darwin_arm64": [ + "node-v24.14.1-darwin-arm64.tar.gz", + "node-v24.14.1-darwin-arm64", + "25495ff85bd89e2d8a24d88566d7e2f827c6b0d3d872b2cebf75371f93fcb1fe" + ], + "24.14.1-darwin_amd64": [ + "node-v24.14.1-darwin-x64.tar.gz", + "node-v24.14.1-darwin-x64", + "2526230ad7d922be82d4fdb1e7ee1e84303e133e3b4b0ec4c2897ab31de0253d" + ], + "24.14.1-linux_arm64": [ + "node-v24.14.1-linux-arm64.tar.xz", + "node-v24.14.1-linux-arm64", + "71e427e28b78846f201d4d5ecc30cb13d1508ca099ef3871889a1256c7d6f67e" + ], + "24.14.1-linux_ppc64le": [ + "node-v24.14.1-linux-ppc64le.tar.xz", + "node-v24.14.1-linux-ppc64le", + "95bf0c8dbb73144edb79a57399f03c70af6995b78e1c632926e53e6404662ef5" + ], + "24.14.1-linux_s390x": [ + "node-v24.14.1-linux-s390x.tar.xz", + "node-v24.14.1-linux-s390x", + "ed3bfbc0ff418b0ec4633f23d53a12a691717a34b041c3fbdb296c8774e5a98a" + ], + "24.14.1-linux_amd64": [ + "node-v24.14.1-linux-x64.tar.xz", + "node-v24.14.1-linux-x64", + "84d38715d449447117d05c3e71acd78daa49d5b1bfa8aacf610303920c3322be" + ], + "24.14.1-windows_amd64": [ + "node-v24.14.1-win-x64.zip", + "node-v24.14.1-win-x64", + "6e50ce5498c0cebc20fd39ab3ff5df836ed2f8a31aa093cecad8497cff126d70" + ] + }, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" ], - "strip_prefix": "xctestrunner-b7698df3d435b6491b4b4c0f9fc7a63fbed5e3a6", - "sha256": "ae3a063c985a8633cb7eb566db21656f8db8eb9a0edb8c182312c7f0db53730d" + "node_version": "24.14.1", + "include_headers": false, + "platform": "linux_amd64" + } + }, + "nodejs_linux_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": { + "24.14.1-darwin_arm64": [ + "node-v24.14.1-darwin-arm64.tar.gz", + "node-v24.14.1-darwin-arm64", + "25495ff85bd89e2d8a24d88566d7e2f827c6b0d3d872b2cebf75371f93fcb1fe" + ], + "24.14.1-darwin_amd64": [ + "node-v24.14.1-darwin-x64.tar.gz", + "node-v24.14.1-darwin-x64", + "2526230ad7d922be82d4fdb1e7ee1e84303e133e3b4b0ec4c2897ab31de0253d" + ], + "24.14.1-linux_arm64": [ + "node-v24.14.1-linux-arm64.tar.xz", + "node-v24.14.1-linux-arm64", + "71e427e28b78846f201d4d5ecc30cb13d1508ca099ef3871889a1256c7d6f67e" + ], + "24.14.1-linux_ppc64le": [ + "node-v24.14.1-linux-ppc64le.tar.xz", + "node-v24.14.1-linux-ppc64le", + "95bf0c8dbb73144edb79a57399f03c70af6995b78e1c632926e53e6404662ef5" + ], + "24.14.1-linux_s390x": [ + "node-v24.14.1-linux-s390x.tar.xz", + "node-v24.14.1-linux-s390x", + "ed3bfbc0ff418b0ec4633f23d53a12a691717a34b041c3fbdb296c8774e5a98a" + ], + "24.14.1-linux_amd64": [ + "node-v24.14.1-linux-x64.tar.xz", + "node-v24.14.1-linux-x64", + "84d38715d449447117d05c3e71acd78daa49d5b1bfa8aacf610303920c3322be" + ], + "24.14.1-windows_amd64": [ + "node-v24.14.1-win-x64.zip", + "node-v24.14.1-win-x64", + "6e50ce5498c0cebc20fd39ab3ff5df836ed2f8a31aa093cecad8497cff126d70" + ] + }, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "24.14.1", + "include_headers": false, + "platform": "linux_arm64" + } + }, + "nodejs_linux_s390x": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": { + "24.14.1-darwin_arm64": [ + "node-v24.14.1-darwin-arm64.tar.gz", + "node-v24.14.1-darwin-arm64", + "25495ff85bd89e2d8a24d88566d7e2f827c6b0d3d872b2cebf75371f93fcb1fe" + ], + "24.14.1-darwin_amd64": [ + "node-v24.14.1-darwin-x64.tar.gz", + "node-v24.14.1-darwin-x64", + "2526230ad7d922be82d4fdb1e7ee1e84303e133e3b4b0ec4c2897ab31de0253d" + ], + "24.14.1-linux_arm64": [ + "node-v24.14.1-linux-arm64.tar.xz", + "node-v24.14.1-linux-arm64", + "71e427e28b78846f201d4d5ecc30cb13d1508ca099ef3871889a1256c7d6f67e" + ], + "24.14.1-linux_ppc64le": [ + "node-v24.14.1-linux-ppc64le.tar.xz", + "node-v24.14.1-linux-ppc64le", + "95bf0c8dbb73144edb79a57399f03c70af6995b78e1c632926e53e6404662ef5" + ], + "24.14.1-linux_s390x": [ + "node-v24.14.1-linux-s390x.tar.xz", + "node-v24.14.1-linux-s390x", + "ed3bfbc0ff418b0ec4633f23d53a12a691717a34b041c3fbdb296c8774e5a98a" + ], + "24.14.1-linux_amd64": [ + "node-v24.14.1-linux-x64.tar.xz", + "node-v24.14.1-linux-x64", + "84d38715d449447117d05c3e71acd78daa49d5b1bfa8aacf610303920c3322be" + ], + "24.14.1-windows_amd64": [ + "node-v24.14.1-win-x64.zip", + "node-v24.14.1-win-x64", + "6e50ce5498c0cebc20fd39ab3ff5df836ed2f8a31aa093cecad8497cff126d70" + ] + }, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "24.14.1", + "include_headers": false, + "platform": "linux_s390x" + } + }, + "nodejs_linux_ppc64le": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": { + "24.14.1-darwin_arm64": [ + "node-v24.14.1-darwin-arm64.tar.gz", + "node-v24.14.1-darwin-arm64", + "25495ff85bd89e2d8a24d88566d7e2f827c6b0d3d872b2cebf75371f93fcb1fe" + ], + "24.14.1-darwin_amd64": [ + "node-v24.14.1-darwin-x64.tar.gz", + "node-v24.14.1-darwin-x64", + "2526230ad7d922be82d4fdb1e7ee1e84303e133e3b4b0ec4c2897ab31de0253d" + ], + "24.14.1-linux_arm64": [ + "node-v24.14.1-linux-arm64.tar.xz", + "node-v24.14.1-linux-arm64", + "71e427e28b78846f201d4d5ecc30cb13d1508ca099ef3871889a1256c7d6f67e" + ], + "24.14.1-linux_ppc64le": [ + "node-v24.14.1-linux-ppc64le.tar.xz", + "node-v24.14.1-linux-ppc64le", + "95bf0c8dbb73144edb79a57399f03c70af6995b78e1c632926e53e6404662ef5" + ], + "24.14.1-linux_s390x": [ + "node-v24.14.1-linux-s390x.tar.xz", + "node-v24.14.1-linux-s390x", + "ed3bfbc0ff418b0ec4633f23d53a12a691717a34b041c3fbdb296c8774e5a98a" + ], + "24.14.1-linux_amd64": [ + "node-v24.14.1-linux-x64.tar.xz", + "node-v24.14.1-linux-x64", + "84d38715d449447117d05c3e71acd78daa49d5b1bfa8aacf610303920c3322be" + ], + "24.14.1-windows_amd64": [ + "node-v24.14.1-win-x64.zip", + "node-v24.14.1-win-x64", + "6e50ce5498c0cebc20fd39ab3ff5df836ed2f8a31aa093cecad8497cff126d70" + ] + }, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "24.14.1", + "include_headers": false, + "platform": "linux_ppc64le" + } + }, + "nodejs_darwin_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": { + "24.14.1-darwin_arm64": [ + "node-v24.14.1-darwin-arm64.tar.gz", + "node-v24.14.1-darwin-arm64", + "25495ff85bd89e2d8a24d88566d7e2f827c6b0d3d872b2cebf75371f93fcb1fe" + ], + "24.14.1-darwin_amd64": [ + "node-v24.14.1-darwin-x64.tar.gz", + "node-v24.14.1-darwin-x64", + "2526230ad7d922be82d4fdb1e7ee1e84303e133e3b4b0ec4c2897ab31de0253d" + ], + "24.14.1-linux_arm64": [ + "node-v24.14.1-linux-arm64.tar.xz", + "node-v24.14.1-linux-arm64", + "71e427e28b78846f201d4d5ecc30cb13d1508ca099ef3871889a1256c7d6f67e" + ], + "24.14.1-linux_ppc64le": [ + "node-v24.14.1-linux-ppc64le.tar.xz", + "node-v24.14.1-linux-ppc64le", + "95bf0c8dbb73144edb79a57399f03c70af6995b78e1c632926e53e6404662ef5" + ], + "24.14.1-linux_s390x": [ + "node-v24.14.1-linux-s390x.tar.xz", + "node-v24.14.1-linux-s390x", + "ed3bfbc0ff418b0ec4633f23d53a12a691717a34b041c3fbdb296c8774e5a98a" + ], + "24.14.1-linux_amd64": [ + "node-v24.14.1-linux-x64.tar.xz", + "node-v24.14.1-linux-x64", + "84d38715d449447117d05c3e71acd78daa49d5b1bfa8aacf610303920c3322be" + ], + "24.14.1-windows_amd64": [ + "node-v24.14.1-win-x64.zip", + "node-v24.14.1-win-x64", + "6e50ce5498c0cebc20fd39ab3ff5df836ed2f8a31aa093cecad8497cff126d70" + ] + }, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "24.14.1", + "include_headers": false, + "platform": "darwin_amd64" + } + }, + "nodejs_darwin_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": { + "24.14.1-darwin_arm64": [ + "node-v24.14.1-darwin-arm64.tar.gz", + "node-v24.14.1-darwin-arm64", + "25495ff85bd89e2d8a24d88566d7e2f827c6b0d3d872b2cebf75371f93fcb1fe" + ], + "24.14.1-darwin_amd64": [ + "node-v24.14.1-darwin-x64.tar.gz", + "node-v24.14.1-darwin-x64", + "2526230ad7d922be82d4fdb1e7ee1e84303e133e3b4b0ec4c2897ab31de0253d" + ], + "24.14.1-linux_arm64": [ + "node-v24.14.1-linux-arm64.tar.xz", + "node-v24.14.1-linux-arm64", + "71e427e28b78846f201d4d5ecc30cb13d1508ca099ef3871889a1256c7d6f67e" + ], + "24.14.1-linux_ppc64le": [ + "node-v24.14.1-linux-ppc64le.tar.xz", + "node-v24.14.1-linux-ppc64le", + "95bf0c8dbb73144edb79a57399f03c70af6995b78e1c632926e53e6404662ef5" + ], + "24.14.1-linux_s390x": [ + "node-v24.14.1-linux-s390x.tar.xz", + "node-v24.14.1-linux-s390x", + "ed3bfbc0ff418b0ec4633f23d53a12a691717a34b041c3fbdb296c8774e5a98a" + ], + "24.14.1-linux_amd64": [ + "node-v24.14.1-linux-x64.tar.xz", + "node-v24.14.1-linux-x64", + "84d38715d449447117d05c3e71acd78daa49d5b1bfa8aacf610303920c3322be" + ], + "24.14.1-windows_amd64": [ + "node-v24.14.1-win-x64.zip", + "node-v24.14.1-win-x64", + "6e50ce5498c0cebc20fd39ab3ff5df836ed2f8a31aa093cecad8497cff126d70" + ] + }, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "24.14.1", + "include_headers": false, + "platform": "darwin_arm64" + } + }, + "nodejs_windows_amd64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": { + "24.14.1-darwin_arm64": [ + "node-v24.14.1-darwin-arm64.tar.gz", + "node-v24.14.1-darwin-arm64", + "25495ff85bd89e2d8a24d88566d7e2f827c6b0d3d872b2cebf75371f93fcb1fe" + ], + "24.14.1-darwin_amd64": [ + "node-v24.14.1-darwin-x64.tar.gz", + "node-v24.14.1-darwin-x64", + "2526230ad7d922be82d4fdb1e7ee1e84303e133e3b4b0ec4c2897ab31de0253d" + ], + "24.14.1-linux_arm64": [ + "node-v24.14.1-linux-arm64.tar.xz", + "node-v24.14.1-linux-arm64", + "71e427e28b78846f201d4d5ecc30cb13d1508ca099ef3871889a1256c7d6f67e" + ], + "24.14.1-linux_ppc64le": [ + "node-v24.14.1-linux-ppc64le.tar.xz", + "node-v24.14.1-linux-ppc64le", + "95bf0c8dbb73144edb79a57399f03c70af6995b78e1c632926e53e6404662ef5" + ], + "24.14.1-linux_s390x": [ + "node-v24.14.1-linux-s390x.tar.xz", + "node-v24.14.1-linux-s390x", + "ed3bfbc0ff418b0ec4633f23d53a12a691717a34b041c3fbdb296c8774e5a98a" + ], + "24.14.1-linux_amd64": [ + "node-v24.14.1-linux-x64.tar.xz", + "node-v24.14.1-linux-x64", + "84d38715d449447117d05c3e71acd78daa49d5b1bfa8aacf610303920c3322be" + ], + "24.14.1-windows_amd64": [ + "node-v24.14.1-win-x64.zip", + "node-v24.14.1-win-x64", + "6e50ce5498c0cebc20fd39ab3ff5df836ed2f8a31aa093cecad8497cff126d70" + ] + }, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "24.14.1", + "include_headers": false, + "platform": "windows_amd64" + } + }, + "nodejs_windows_arm64": { + "repoRuleId": "@@rules_nodejs+//nodejs:repositories.bzl%_nodejs_repositories", + "attributes": { + "node_download_auth": {}, + "node_repositories": { + "24.14.1-darwin_arm64": [ + "node-v24.14.1-darwin-arm64.tar.gz", + "node-v24.14.1-darwin-arm64", + "25495ff85bd89e2d8a24d88566d7e2f827c6b0d3d872b2cebf75371f93fcb1fe" + ], + "24.14.1-darwin_amd64": [ + "node-v24.14.1-darwin-x64.tar.gz", + "node-v24.14.1-darwin-x64", + "2526230ad7d922be82d4fdb1e7ee1e84303e133e3b4b0ec4c2897ab31de0253d" + ], + "24.14.1-linux_arm64": [ + "node-v24.14.1-linux-arm64.tar.xz", + "node-v24.14.1-linux-arm64", + "71e427e28b78846f201d4d5ecc30cb13d1508ca099ef3871889a1256c7d6f67e" + ], + "24.14.1-linux_ppc64le": [ + "node-v24.14.1-linux-ppc64le.tar.xz", + "node-v24.14.1-linux-ppc64le", + "95bf0c8dbb73144edb79a57399f03c70af6995b78e1c632926e53e6404662ef5" + ], + "24.14.1-linux_s390x": [ + "node-v24.14.1-linux-s390x.tar.xz", + "node-v24.14.1-linux-s390x", + "ed3bfbc0ff418b0ec4633f23d53a12a691717a34b041c3fbdb296c8774e5a98a" + ], + "24.14.1-linux_amd64": [ + "node-v24.14.1-linux-x64.tar.xz", + "node-v24.14.1-linux-x64", + "84d38715d449447117d05c3e71acd78daa49d5b1bfa8aacf610303920c3322be" + ], + "24.14.1-windows_amd64": [ + "node-v24.14.1-win-x64.zip", + "node-v24.14.1-win-x64", + "6e50ce5498c0cebc20fd39ab3ff5df836ed2f8a31aa093cecad8497cff126d70" + ] + }, + "node_urls": [ + "https://nodejs.org/dist/v{version}/{filename}" + ], + "node_version": "24.14.1", + "include_headers": false, + "platform": "windows_arm64" + } + }, + "nodejs": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "nodejs" + } + }, + "nodejs_host": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_repo_host_os_alias.bzl%nodejs_repo_host_os_alias", + "attributes": { + "user_node_repository_name": "nodejs" + } + }, + "nodejs_toolchains": { + "repoRuleId": "@@rules_nodejs+//nodejs/private:nodejs_toolchains_repo.bzl%nodejs_toolchains_repo", + "attributes": { + "user_node_repository_name": "nodejs" } } - }, - "recordedRepoMappingEntries": [ - [ - "rules_apple+", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, - "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { - "general": { - "bzlTransitiveDigest": "HJP3wKFbPhB1mSYjJS6kbXEiP+OQxvsBdqpvyJN6I3s=", - "usagesDigest": "qTwqmKKUfWcPdvM0waG+CPWrxsbeAWVeUxavm7tEk9E=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "com_github_jetbrains_kotlin_git": { - "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", - "attributes": { - "urls": [ - "https://github.com/JetBrains/kotlin/releases/download/v2.1.0/kotlin-compiler-2.1.0.zip" - ], - "sha256": "b6698d5728ad8f9edcdd01617d638073191d8a03139cc538a391b4e3759ad297" - } - }, - "com_github_jetbrains_kotlin": { - "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", - "attributes": { - "git_repository_name": "com_github_jetbrains_kotlin_git", - "compiler_version": "2.1.0" - } - }, - "com_github_google_ksp": { - "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", - "attributes": { - "urls": [ - "https://github.com/google/ksp/releases/download/2.1.0-1.0.28/artifacts.zip" - ], - "sha256": "fc27b08cadc061a4a989af01cbeccb613feef1995f4aad68f2be0f886a3ee251", - "strip_version": "2.1.0-1.0.28" - } - }, - "com_github_pinterest_ktlint": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", - "attributes": { - "sha256": "a9f923be58fbd32670a17f0b729b1df804af882fa57402165741cb26e5440ca1", - "urls": [ - "https://github.com/pinterest/ktlint/releases/download/1.3.1/ktlint" - ], - "executable": true - } - }, - "kotlinx_serialization_core_jvm": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar", - "attributes": { - "sha256": "29c821a8d4e25cbfe4f2ce96cdd4526f61f8f4e69a135f9612a34a81d93b65f1", - "urls": [ - "https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.6.3/kotlinx-serialization-core-jvm-1.6.3.jar" - ] - } - }, - "kotlinx_serialization_json": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar", - "attributes": { - "sha256": "8c0016890a79ab5980dd520a5ab1a6738023c29aa3b6437c482e0e5fdc06dab1", - "urls": [ - "https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-json/1.6.3/kotlinx-serialization-json-1.6.3.jar" - ] - } - }, - "kotlinx_serialization_json_jvm": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar", - "attributes": { - "sha256": "d3234179bcff1886d53d67c11eca47f7f3cf7b63c349d16965f6db51b7f3dd9a", - "urls": [ - "https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-json-jvm/1.6.3/kotlinx-serialization-json-jvm-1.6.3.jar" - ] - } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_kotlin+", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, - "@@rules_python+//python/extensions:config.bzl%config": { - "general": { - "bzlTransitiveDigest": "TRGIl0CDmorwyNiblOYyhWuyKzi/kWFHT2uIofq7o9Y=", - "usagesDigest": "tIEieEA/gbsjNF3L/Oouyg6UdqGOVxFsPqBjFxkTAKM=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "rules_python_internal": { - "repoRuleId": "@@rules_python+//python/private:internal_config_repo.bzl%internal_config_repo", - "attributes": { - "transition_setting_generators": {}, - "transition_settings": [] - } - }, - "pypi__build": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/e2/03/f3c8ba0a6b6e30d7d18c40faab90807c9bb5e9a1e3b2fe2008af624a9c97/build-1.2.1-py3-none-any.whl", - "sha256": "75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__click": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", - "sha256": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__colorama": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", - "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__importlib_metadata": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/2d/0a/679461c511447ffaf176567d5c496d1de27cbe34a87df6677d7171b2fbd4/importlib_metadata-7.1.0-py3-none-any.whl", - "sha256": "30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__installer": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl", - "sha256": "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__more_itertools": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/50/e2/8e10e465ee3987bb7c9ab69efb91d867d93959095f4807db102d07995d94/more_itertools-10.2.0-py3-none-any.whl", - "sha256": "686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__packaging": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", - "sha256": "2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pep517": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/25/6e/ca4a5434eb0e502210f591b97537d322546e4833dcb4d470a48c375c5540/pep517-0.13.1-py3-none-any.whl", - "sha256": "31b206f67165b3536dd577c5c3f1518e8fbaf38cbc57efff8369a392feff1721", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pip": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl", - "sha256": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pip_tools": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/0d/dc/38f4ce065e92c66f058ea7a368a9c5de4e702272b479c0992059f7693941/pip_tools-7.4.1-py3-none-any.whl", - "sha256": "4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pyproject_hooks": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/ae/f3/431b9d5fe7d14af7a32340792ef43b8a714e7726f1d7b69cc4e8e7a3f1d7/pyproject_hooks-1.1.0-py3-none-any.whl", - "sha256": "7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__setuptools": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/90/99/158ad0609729111163fc1f674a5a42f2605371a4cf036d0441070e2f7455/setuptools-78.1.1-py3-none-any.whl", - "sha256": "c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__tomli": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", - "sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__wheel": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/7d/cd/d7460c9a869b16c3dd4e1e403cce337df165368c71d6af229a74699622ce/wheel-0.43.0-py3-none-any.whl", - "sha256": "55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__zipp": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/da/55/a03fd7240714916507e1fcf7ae355bd9d9ed2e6db492595f1a67f61681be/zipp-3.18.2-py3-none-any.whl", - "sha256": "dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_python+", - "bazel_tools", - "bazel_tools" - ], - [ - "rules_python+", - "pypi__build", - "rules_python++config+pypi__build" - ], - [ - "rules_python+", - "pypi__click", - "rules_python++config+pypi__click" - ], - [ - "rules_python+", - "pypi__colorama", - "rules_python++config+pypi__colorama" - ], - [ - "rules_python+", - "pypi__importlib_metadata", - "rules_python++config+pypi__importlib_metadata" - ], - [ - "rules_python+", - "pypi__installer", - "rules_python++config+pypi__installer" - ], - [ - "rules_python+", - "pypi__more_itertools", - "rules_python++config+pypi__more_itertools" - ], - [ - "rules_python+", - "pypi__packaging", - "rules_python++config+pypi__packaging" - ], - [ - "rules_python+", - "pypi__pep517", - "rules_python++config+pypi__pep517" - ], - [ - "rules_python+", - "pypi__pip", - "rules_python++config+pypi__pip" - ], - [ - "rules_python+", - "pypi__pip_tools", - "rules_python++config+pypi__pip_tools" - ], - [ - "rules_python+", - "pypi__pyproject_hooks", - "rules_python++config+pypi__pyproject_hooks" - ], - [ - "rules_python+", - "pypi__setuptools", - "rules_python++config+pypi__setuptools" - ], - [ - "rules_python+", - "pypi__tomli", - "rules_python++config+pypi__tomli" - ], - [ - "rules_python+", - "pypi__wheel", - "rules_python++config+pypi__wheel" - ], - [ - "rules_python+", - "pypi__zipp", - "rules_python++config+pypi__zipp" - ] - ] + } } }, "@@rules_python+//python/uv:uv.bzl%uv": { "general": { - "bzlTransitiveDigest": "ijW9KS7qsIY+yBVvJ+Nr1mzwQox09j13DnE3iIwaeTM=", - "usagesDigest": "s63+dBGiTSbvuV/QBtGNrbYox+e7K5QXThW1NgBreis=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, + "bzlTransitiveDigest": "I8FPZMevE2oI/peSpMBRVIN++WOtfjtJVjbPsBZQ87A=", + "usagesDigest": "R0pij/Kri0Z/7GcM2UBot6XmOqgduje0j8y9wNMWjLA=", + "recordedInputs": [ + "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_python+,platforms platforms" + ], "generatedRepoSpecs": { "uv": { "repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo", @@ -783,180 +895,1166 @@ "toolchain_target_settings": {} } } - }, - "recordedRepoMappingEntries": [ - [ - "rules_python+", - "bazel_tools", - "bazel_tools" - ], - [ - "rules_python+", - "platforms", - "platforms" - ] - ] + } } }, - "@@rules_swift+//swift:extensions.bzl%non_module_deps": { + "@@rules_rust+//crate_universe:extension.bzl%crate": { "general": { - "bzlTransitiveDigest": "6axDCXf6fQoPav8hojnUBxGA0FAMqLvtpC1cRsisCdw=", - "usagesDigest": "mhACFnrdMv9Wi0Mt67bxocJqviRkDSV+Ee5Mqdj5akA=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, + "bzlTransitiveDigest": "4ZxdDHM5dGVbcpGNyPxSWkXiz1DTw/ldJgFC1a5SPOI=", + "usagesDigest": "EuFUqVKVHF263jHTWOHXs4tFACdRNVOhwpoytdk19bs=", + "recordedInputs": [ + "ENV:CARGO_BAZEL_DEBUG \\0", + "ENV:CARGO_BAZEL_GENERATOR_SHA256 \\0", + "ENV:CARGO_BAZEL_GENERATOR_URL \\0", + "ENV:CARGO_BAZEL_ISOLATED \\0", + "ENV:CARGO_BAZEL_REPIN \\0", + "ENV:CARGO_BAZEL_REPIN_ONLY \\0", + "ENV:CARGO_BAZEL_TIMEOUT \\0", + "ENV:REPIN \\0", + "REPO_MAPPING:bazel_features+,bazel_features_globals bazel_features++version_extension+bazel_features_globals", + "REPO_MAPPING:bazel_features+,bazel_features_version bazel_features++version_extension+bazel_features_version", + "REPO_MAPPING:rules_cc+,bazel_skylib bazel_skylib+", + "REPO_MAPPING:rules_cc+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_cc+,cc_compatibility_proxy rules_cc++compatibility_proxy+cc_compatibility_proxy", + "REPO_MAPPING:rules_cc+,platforms platforms", + "REPO_MAPPING:rules_cc+,rules_cc rules_cc+", + "REPO_MAPPING:rules_cc++compatibility_proxy+cc_compatibility_proxy,rules_cc rules_cc+", + "REPO_MAPPING:rules_rust+,bazel_features bazel_features+", + "REPO_MAPPING:rules_rust+,bazel_skylib bazel_skylib+", + "REPO_MAPPING:rules_rust+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_rust+,rules_cc rules_cc+", + "REPO_MAPPING:rules_rust+,rules_rust rules_rust+" + ], "generatedRepoSpecs": { - "com_github_apple_swift_protobuf": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "crates": { + "repoRuleId": "@@rules_rust+//crate_universe:extensions.bzl%_generate_repo", "attributes": { - "urls": [ - "https://github.com/apple/swift-protobuf/archive/1.20.2.tar.gz" - ], - "sha256": "3fb50bd4d293337f202d917b6ada22f9548a0a0aed9d9a4d791e6fbd8a246ebb", - "strip_prefix": "swift-protobuf-1.20.2/", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_protobuf/BUILD.overlay" + "contents": { + "BUILD.bazel": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\npackage(default_visibility = [\"//visibility:public\"])\n\nexports_files(\n [\n \"cargo-bazel.json\",\n \"crates.bzl\",\n \"defs.bzl\",\n ] + glob(\n allow_empty = True,\n include = [\"*.bazel\"],\n ),\n)\n\nfilegroup(\n name = \"srcs\",\n srcs = glob(\n allow_empty = True,\n include = [\n \"*.bazel\",\n \"*.bzl\",\n ],\n ),\n)\n\n# Workspace Member Dependencies\nalias(\n name = \"googletest-0.14.2\",\n actual = \"@crates__googletest-0.14.2//:googletest\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"googletest\",\n actual = \"@crates__googletest-0.14.2//:googletest\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"linkme-0.3.36\",\n actual = \"@crates__linkme-0.3.36//:linkme\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"linkme\",\n actual = \"@crates__linkme-0.3.36//:linkme\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"paste-1.0.15\",\n actual = \"@crates__paste-1.0.15//:paste\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"paste\",\n actual = \"@crates__paste-1.0.15//:paste\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"quote-1.0.45\",\n actual = \"@crates__quote-1.0.45//:quote\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"quote\",\n actual = \"@crates__quote-1.0.45//:quote\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"syn-2.0.117\",\n actual = \"@crates__syn-2.0.117//:syn\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"syn\",\n actual = \"@crates__syn-2.0.117//:syn\",\n tags = [\"manual\"],\n)\n", + "alias_rules.bzl": "\"\"\"Alias that transitions its target to `compilation_mode=opt`. Use `transition_alias=\"opt\"` to enable.\"\"\"\n\nload(\"@rules_cc//cc:defs.bzl\", \"CcInfo\")\nload(\"@rules_rust//rust:rust_common.bzl\", \"COMMON_PROVIDERS\")\n\ndef _transition_alias_impl(ctx):\n # `ctx.attr.actual` is a list of 1 item due to the transition\n providers = [ctx.attr.actual[0][provider] for provider in COMMON_PROVIDERS]\n if CcInfo in ctx.attr.actual[0]:\n providers.append(ctx.attr.actual[0][CcInfo])\n return providers\n\ndef _change_compilation_mode(compilation_mode):\n def _change_compilation_mode_impl(_settings, _attr):\n return {\n \"//command_line_option:compilation_mode\": compilation_mode,\n }\n\n return transition(\n implementation = _change_compilation_mode_impl,\n inputs = [],\n outputs = [\n \"//command_line_option:compilation_mode\",\n ],\n )\n\ndef _transition_alias_rule(compilation_mode):\n return rule(\n implementation = _transition_alias_impl,\n provides = COMMON_PROVIDERS,\n attrs = {\n \"actual\": attr.label(\n mandatory = True,\n doc = \"`rust_library()` target to transition to `compilation_mode=opt`.\",\n providers = COMMON_PROVIDERS,\n cfg = _change_compilation_mode(compilation_mode),\n ),\n \"_allowlist_function_transition\": attr.label(\n default = \"@bazel_tools//tools/allowlists/function_transition_allowlist\",\n ),\n },\n doc = \"Transitions a Rust library crate to the `compilation_mode=opt`.\",\n )\n\ntransition_alias_dbg = _transition_alias_rule(\"dbg\")\ntransition_alias_fastbuild = _transition_alias_rule(\"fastbuild\")\ntransition_alias_opt = _transition_alias_rule(\"opt\")\n", + "defs.bzl": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\"\"\"\n# `crates_repository` API\n\n- [aliases](#aliases)\n- [crate_deps](#crate_deps)\n- [all_crate_deps](#all_crate_deps)\n- [crate_repositories](#crate_repositories)\n\n\"\"\"\n\nload(\"@bazel_tools//tools/build_defs/repo:git.bzl\", \"new_git_repository\")\nload(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\nload(\"@bazel_tools//tools/build_defs/repo:utils.bzl\", \"maybe\")\nload(\"@bazel_skylib//lib:selects.bzl\", \"selects\")\nload(\"@rules_rust//crate_universe/private:local_crate_mirror.bzl\", \"local_crate_mirror\")\n\n###############################################################################\n# MACROS API\n###############################################################################\n\n# An identifier that represent common dependencies (unconditional).\n_COMMON_CONDITION = \"\"\n\ndef _flatten_dependency_maps(all_dependency_maps):\n \"\"\"Flatten a list of dependency maps into one dictionary.\n\n Dependency maps have the following structure:\n\n ```python\n DEPENDENCIES_MAP = {\n # The first key in the map is a Bazel package\n # name of the workspace this file is defined in.\n \"workspace_member_package\": {\n\n # Not all dependencies are supported for all platforms.\n # the condition key is the condition required to be true\n # on the host platform.\n \"condition\": {\n\n # An alias to a crate target. # The label of the crate target the\n # Aliases are only crate names. # package name refers to.\n \"package_name\": \"@full//:label\",\n }\n }\n }\n ```\n\n Args:\n all_dependency_maps (list): A list of dicts as described above\n\n Returns:\n dict: A dictionary as described above\n \"\"\"\n dependencies = {}\n\n for workspace_deps_map in all_dependency_maps:\n for pkg_name, conditional_deps_map in workspace_deps_map.items():\n if pkg_name not in dependencies:\n non_frozen_map = dict()\n for key, values in conditional_deps_map.items():\n non_frozen_map.update({key: dict(values.items())})\n dependencies.setdefault(pkg_name, non_frozen_map)\n continue\n\n for condition, deps_map in conditional_deps_map.items():\n # If the condition has not been recorded, do so and continue\n if condition not in dependencies[pkg_name]:\n dependencies[pkg_name].setdefault(condition, dict(deps_map.items()))\n continue\n\n # Alert on any miss-matched dependencies\n inconsistent_entries = []\n for crate_name, crate_label in deps_map.items():\n existing = dependencies[pkg_name][condition].get(crate_name)\n if existing and existing != crate_label:\n inconsistent_entries.append((crate_name, existing, crate_label))\n dependencies[pkg_name][condition].update({crate_name: crate_label})\n\n return dependencies\n\ndef crate_deps(deps, package_name = None):\n \"\"\"Finds the fully qualified label of the requested crates for the package where this macro is called.\n\n Args:\n deps (list): The desired list of crate targets.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()`.\n\n Returns:\n list: A list of labels to generated rust targets (str)\n \"\"\"\n\n if not deps:\n return []\n\n if package_name == None:\n package_name = native.package_name()\n\n # Join both sets of dependencies\n dependencies = _flatten_dependency_maps([\n _NORMAL_DEPENDENCIES,\n _NORMAL_DEV_DEPENDENCIES,\n _PROC_MACRO_DEPENDENCIES,\n _PROC_MACRO_DEV_DEPENDENCIES,\n _BUILD_DEPENDENCIES,\n _BUILD_PROC_MACRO_DEPENDENCIES,\n ]).pop(package_name, {})\n\n # Combine all conditional packages so we can easily index over a flat list\n # TODO: Perhaps this should actually return select statements and maintain\n # the conditionals of the dependencies\n flat_deps = {}\n for deps_set in dependencies.values():\n for crate_name, crate_label in deps_set.items():\n flat_deps.update({crate_name: crate_label})\n\n missing_crates = []\n crate_targets = []\n for crate_target in deps:\n if crate_target not in flat_deps:\n missing_crates.append(crate_target)\n else:\n crate_targets.append(flat_deps[crate_target])\n\n if missing_crates:\n fail(\"Could not find crates `{}` among dependencies of `{}`. Available dependencies were `{}`\".format(\n missing_crates,\n package_name,\n dependencies,\n ))\n\n return crate_targets\n\ndef all_crate_deps(\n normal = False, \n normal_dev = False, \n proc_macro = False, \n proc_macro_dev = False,\n build = False,\n build_proc_macro = False,\n package_name = None):\n \"\"\"Finds the fully qualified label of all requested direct crate dependencies \\\n for the package where this macro is called.\n\n If no parameters are set, all normal dependencies are returned. Setting any one flag will\n otherwise impact the contents of the returned list.\n\n Args:\n normal (bool, optional): If True, normal dependencies are included in the\n output list.\n normal_dev (bool, optional): If True, normal dev dependencies will be\n included in the output list.\n proc_macro (bool, optional): If True, proc_macro dependencies are included\n in the output list.\n proc_macro_dev (bool, optional): If True, dev proc_macro dependencies are\n included in the output list.\n build (bool, optional): If True, build dependencies are included\n in the output list.\n build_proc_macro (bool, optional): If True, build proc_macro dependencies are\n included in the output list.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()` when unset.\n\n Returns:\n list: A list of labels to generated rust targets (str)\n \"\"\"\n\n if package_name == None:\n package_name = native.package_name()\n\n # Determine the relevant maps to use\n all_dependency_maps = []\n if normal:\n all_dependency_maps.append(_NORMAL_DEPENDENCIES)\n if normal_dev:\n all_dependency_maps.append(_NORMAL_DEV_DEPENDENCIES)\n if proc_macro:\n all_dependency_maps.append(_PROC_MACRO_DEPENDENCIES)\n if proc_macro_dev:\n all_dependency_maps.append(_PROC_MACRO_DEV_DEPENDENCIES)\n if build:\n all_dependency_maps.append(_BUILD_DEPENDENCIES)\n if build_proc_macro:\n all_dependency_maps.append(_BUILD_PROC_MACRO_DEPENDENCIES)\n\n # Default to always using normal dependencies\n if not all_dependency_maps:\n all_dependency_maps.append(_NORMAL_DEPENDENCIES)\n\n dependencies = _flatten_dependency_maps(all_dependency_maps).pop(package_name, None)\n\n if not dependencies:\n if dependencies == None:\n fail(\"Tried to get all_crate_deps for package \" + package_name + \" but that package had no Cargo.toml file\")\n else:\n return []\n\n crate_deps = list(dependencies.pop(_COMMON_CONDITION, {}).values())\n for condition, deps in dependencies.items():\n crate_deps += selects.with_or({\n tuple(_CONDITIONS[condition]): deps.values(),\n \"//conditions:default\": [],\n })\n\n return crate_deps\n\ndef aliases(\n normal = False,\n normal_dev = False,\n proc_macro = False,\n proc_macro_dev = False,\n build = False,\n build_proc_macro = False,\n package_name = None):\n \"\"\"Produces a map of Crate alias names to their original label\n\n If no dependency kinds are specified, `normal` and `proc_macro` are used by default.\n Setting any one flag will otherwise determine the contents of the returned dict.\n\n Args:\n normal (bool, optional): If True, normal dependencies are included in the\n output list.\n normal_dev (bool, optional): If True, normal dev dependencies will be\n included in the output list..\n proc_macro (bool, optional): If True, proc_macro dependencies are included\n in the output list.\n proc_macro_dev (bool, optional): If True, dev proc_macro dependencies are\n included in the output list.\n build (bool, optional): If True, build dependencies are included\n in the output list.\n build_proc_macro (bool, optional): If True, build proc_macro dependencies are\n included in the output list.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()` when unset.\n\n Returns:\n dict: The aliases of all associated packages\n \"\"\"\n if package_name == None:\n package_name = native.package_name()\n\n # Determine the relevant maps to use\n all_aliases_maps = []\n if normal:\n all_aliases_maps.append(_NORMAL_ALIASES)\n if normal_dev:\n all_aliases_maps.append(_NORMAL_DEV_ALIASES)\n if proc_macro:\n all_aliases_maps.append(_PROC_MACRO_ALIASES)\n if proc_macro_dev:\n all_aliases_maps.append(_PROC_MACRO_DEV_ALIASES)\n if build:\n all_aliases_maps.append(_BUILD_ALIASES)\n if build_proc_macro:\n all_aliases_maps.append(_BUILD_PROC_MACRO_ALIASES)\n\n # Default to always using normal aliases\n if not all_aliases_maps:\n all_aliases_maps.append(_NORMAL_ALIASES)\n all_aliases_maps.append(_PROC_MACRO_ALIASES)\n\n aliases = _flatten_dependency_maps(all_aliases_maps).pop(package_name, None)\n\n if not aliases:\n return dict()\n\n common_items = aliases.pop(_COMMON_CONDITION, {}).items()\n\n # If there are only common items in the dictionary, immediately return them\n if not len(aliases.keys()) == 1:\n return dict(common_items)\n\n # Build a single select statement where each conditional has accounted for the\n # common set of aliases.\n crate_aliases = {\"//conditions:default\": dict(common_items)}\n for condition, deps in aliases.items():\n condition_triples = _CONDITIONS[condition]\n for triple in condition_triples:\n if triple in crate_aliases:\n crate_aliases[triple].update(deps)\n else:\n crate_aliases.update({triple: dict(deps.items() + common_items)})\n\n return select(crate_aliases)\n\n###############################################################################\n# WORKSPACE MEMBER DEPS AND ALIASES\n###############################################################################\n\n_NORMAL_DEPENDENCIES = {\n \"\": {\n _COMMON_CONDITION: {\n \"googletest\": Label(\"@crates//:googletest-0.14.2\"),\n \"linkme\": Label(\"@crates//:linkme-0.3.36\"),\n \"quote\": Label(\"@crates//:quote-1.0.45\"),\n \"syn\": Label(\"@crates//:syn-2.0.117\"),\n },\n },\n}\n\n\n_NORMAL_ALIASES = {\n \"\": {\n _COMMON_CONDITION: {\n },\n },\n}\n\n\n_NORMAL_DEV_DEPENDENCIES = {\n \"\": {\n },\n}\n\n\n_NORMAL_DEV_ALIASES = {\n \"\": {\n },\n}\n\n\n_PROC_MACRO_DEPENDENCIES = {\n \"\": {\n _COMMON_CONDITION: {\n \"paste\": Label(\"@crates//:paste-1.0.15\"),\n },\n },\n}\n\n\n_PROC_MACRO_ALIASES = {\n \"\": {\n },\n}\n\n\n_PROC_MACRO_DEV_DEPENDENCIES = {\n \"\": {\n },\n}\n\n\n_PROC_MACRO_DEV_ALIASES = {\n \"\": {\n },\n}\n\n\n_BUILD_DEPENDENCIES = {\n \"\": {\n },\n}\n\n\n_BUILD_ALIASES = {\n \"\": {\n },\n}\n\n\n_BUILD_PROC_MACRO_DEPENDENCIES = {\n \"\": {\n },\n}\n\n\n_BUILD_PROC_MACRO_ALIASES = {\n \"\": {\n },\n}\n\n\n_CONDITIONS = {\n \"aarch64-apple-darwin\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"aarch64-unknown-linux-gnu\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n \"wasm32-unknown-unknown\": [\"@rules_rust//rust/platform:wasm32-unknown-unknown\"],\n \"wasm32-wasip1\": [\"@rules_rust//rust/platform:wasm32-wasip1\"],\n \"x86_64-pc-windows-msvc\": [\"@rules_rust//rust/platform:x86_64-pc-windows-msvc\"],\n \"x86_64-unknown-linux-gnu\": [\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n \"x86_64-unknown-nixos-gnu\": [\"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\"],\n}\n\n###############################################################################\n\ndef crate_repositories():\n \"\"\"A macro for defining repositories for all generated crates.\n\n Returns:\n A list of repos visible to the module through the module extension.\n \"\"\"\n maybe(\n http_archive,\n name = \"crates__aho-corasick-1.1.4\",\n sha256 = \"ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aho-corasick/1.1.4/download\"],\n strip_prefix = \"aho-corasick-1.1.4\",\n build_file = Label(\"@crates//crates:BUILD.aho-corasick-1.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__autocfg-1.5.1\",\n sha256 = \"f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/autocfg/1.5.1/download\"],\n strip_prefix = \"autocfg-1.5.1\",\n build_file = Label(\"@crates//crates:BUILD.autocfg-1.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__googletest-0.14.2\",\n sha256 = \"06597b7d02ee58b9a37f522785ac15b9e18c6b178747c4439a6c03fbb35ea753\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/googletest/0.14.2/download\"],\n strip_prefix = \"googletest-0.14.2\",\n build_file = Label(\"@crates//crates:BUILD.googletest-0.14.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__googletest_macro-0.14.2\",\n sha256 = \"c31d9f07c9c19b855faebf71637be3b43f8e13a518aece5d61a3beee7710b4ef\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/googletest_macro/0.14.2/download\"],\n strip_prefix = \"googletest_macro-0.14.2\",\n build_file = Label(\"@crates//crates:BUILD.googletest_macro-0.14.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__linkme-0.3.36\",\n sha256 = \"e83272d46373fb8decca684579ac3e7c8f3d71d4cc3aa693df8759e260ae41cf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/linkme/0.3.36/download\"],\n strip_prefix = \"linkme-0.3.36\",\n build_file = Label(\"@crates//crates:BUILD.linkme-0.3.36.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__linkme-impl-0.3.36\",\n sha256 = \"32d59e20403c7d08fe62b4376edfe5c7fb2ef1e6b1465379686d0f21c8df444b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/linkme-impl/0.3.36/download\"],\n strip_prefix = \"linkme-impl-0.3.36\",\n build_file = Label(\"@crates//crates:BUILD.linkme-impl-0.3.36.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__memchr-2.8.1\",\n sha256 = \"6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/memchr/2.8.1/download\"],\n strip_prefix = \"memchr-2.8.1\",\n build_file = Label(\"@crates//crates:BUILD.memchr-2.8.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__num-traits-0.2.19\",\n sha256 = \"071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/num-traits/0.2.19/download\"],\n strip_prefix = \"num-traits-0.2.19\",\n build_file = Label(\"@crates//crates:BUILD.num-traits-0.2.19.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__paste-1.0.15\",\n sha256 = \"57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/paste/1.0.15/download\"],\n strip_prefix = \"paste-1.0.15\",\n build_file = Label(\"@crates//crates:BUILD.paste-1.0.15.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__proc-macro2-1.0.106\",\n sha256 = \"8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/proc-macro2/1.0.106/download\"],\n strip_prefix = \"proc-macro2-1.0.106\",\n build_file = Label(\"@crates//crates:BUILD.proc-macro2-1.0.106.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__quote-1.0.45\",\n sha256 = \"41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/quote/1.0.45/download\"],\n strip_prefix = \"quote-1.0.45\",\n build_file = Label(\"@crates//crates:BUILD.quote-1.0.45.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__regex-1.12.3\",\n sha256 = \"e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/regex/1.12.3/download\"],\n strip_prefix = \"regex-1.12.3\",\n build_file = Label(\"@crates//crates:BUILD.regex-1.12.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__regex-automata-0.4.14\",\n sha256 = \"6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/regex-automata/0.4.14/download\"],\n strip_prefix = \"regex-automata-0.4.14\",\n build_file = Label(\"@crates//crates:BUILD.regex-automata-0.4.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__regex-syntax-0.8.10\",\n sha256 = \"dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/regex-syntax/0.8.10/download\"],\n strip_prefix = \"regex-syntax-0.8.10\",\n build_file = Label(\"@crates//crates:BUILD.regex-syntax-0.8.10.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__rustversion-1.0.22\",\n sha256 = \"b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustversion/1.0.22/download\"],\n strip_prefix = \"rustversion-1.0.22\",\n build_file = Label(\"@crates//crates:BUILD.rustversion-1.0.22.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__syn-2.0.117\",\n sha256 = \"e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/syn/2.0.117/download\"],\n strip_prefix = \"syn-2.0.117\",\n build_file = Label(\"@crates//crates:BUILD.syn-2.0.117.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"crates__unicode-ident-1.0.24\",\n sha256 = \"e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicode-ident/1.0.24/download\"],\n strip_prefix = \"unicode-ident-1.0.24\",\n build_file = Label(\"@crates//crates:BUILD.unicode-ident-1.0.24.bazel\"),\n )\n\n return [\n struct(repo=\"crates__googletest-0.14.2\", is_dev_dep = False),\n struct(repo=\"crates__linkme-0.3.36\", is_dev_dep = False),\n struct(repo=\"crates__paste-1.0.15\", is_dev_dep = False),\n struct(repo=\"crates__quote-1.0.45\", is_dev_dep = False),\n struct(repo=\"crates__syn-2.0.117\", is_dev_dep = False),\n ]\n" + } } }, - "com_github_grpc_grpc_swift": { + "crates__aho-corasick-1.1.4": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { + "remote_patch_strip": 1, + "sha256": "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301", + "type": "tar.gz", "urls": [ - "https://github.com/grpc/grpc-swift/archive/1.16.0.tar.gz" + "https://static.crates.io/crates/aho-corasick/1.1.4/download" ], - "sha256": "58b60431d0064969f9679411264b82e40a217ae6bd34e17096d92cc4e47556a5", - "strip_prefix": "grpc-swift-1.16.0/", - "build_file": "@@rules_swift+//third_party:com_github_grpc_grpc_swift/BUILD.overlay" + "strip_prefix": "aho-corasick-1.1.4", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"aho_corasick\",\n deps = [\n \"@crates__memchr-2.8.1//:memchr\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"perf-literal\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=aho-corasick\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.1.4\",\n)\n" } }, - "com_github_apple_swift_docc_symbolkit": { + "crates__autocfg-1.5.1": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { + "remote_patch_strip": 1, + "sha256": "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53", + "type": "tar.gz", "urls": [ - "https://github.com/apple/swift-docc-symbolkit/archive/refs/tags/swift-5.10-RELEASE.tar.gz" + "https://static.crates.io/crates/autocfg/1.5.1/download" ], - "sha256": "de1d4b6940468ddb53b89df7aa1a81323b9712775b0e33e8254fa0f6f7469a97", - "strip_prefix": "swift-docc-symbolkit-swift-5.10-RELEASE", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_docc_symbolkit/BUILD.overlay" + "strip_prefix": "autocfg-1.5.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"autocfg\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2015\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=autocfg\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.5.1\",\n)\n" } }, - "com_github_apple_swift_nio": { + "crates__googletest-0.14.2": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { + "remote_patch_strip": 1, + "sha256": "06597b7d02ee58b9a37f522785ac15b9e18c6b178747c4439a6c03fbb35ea753", + "type": "tar.gz", "urls": [ - "https://github.com/apple/swift-nio/archive/2.42.0.tar.gz" + "https://static.crates.io/crates/googletest/0.14.2/download" ], - "sha256": "e3304bc3fb53aea74a3e54bd005ede11f6dc357117d9b1db642d03aea87194a0", - "strip_prefix": "swift-nio-2.42.0/", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_nio/BUILD.overlay" + "strip_prefix": "googletest-0.14.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"googletest\",\n deps = [\n \"@crates__num-traits-0.2.19//:num_traits\",\n \"@crates__regex-1.12.3//:regex\",\n ],\n proc_macro_deps = [\n \"@crates__googletest_macro-0.14.2//:googletest_macro\",\n \"@crates__rustversion-1.0.22//:rustversion\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=googletest\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.2\",\n)\n" } }, - "com_github_apple_swift_nio_http2": { + "crates__googletest_macro-0.14.2": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { + "remote_patch_strip": 1, + "sha256": "c31d9f07c9c19b855faebf71637be3b43f8e13a518aece5d61a3beee7710b4ef", + "type": "tar.gz", "urls": [ - "https://github.com/apple/swift-nio-http2/archive/1.26.0.tar.gz" + "https://static.crates.io/crates/googletest_macro/0.14.2/download" ], - "sha256": "f0edfc9d6a7be1d587e5b403f2d04264bdfae59aac1d74f7d974a9022c6d2b25", - "strip_prefix": "swift-nio-http2-1.26.0/", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_nio_http2/BUILD.overlay" + "strip_prefix": "googletest_macro-0.14.2", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"googletest_macro\",\n deps = [\n \"@crates__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates__quote-1.0.45//:quote\",\n \"@crates__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=googletest_macro\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.14.2\",\n)\n" } }, - "com_github_apple_swift_nio_transport_services": { + "crates__linkme-0.3.36": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { + "remote_patch_strip": 1, + "sha256": "e83272d46373fb8decca684579ac3e7c8f3d71d4cc3aa693df8759e260ae41cf", + "type": "tar.gz", "urls": [ - "https://github.com/apple/swift-nio-transport-services/archive/1.15.0.tar.gz" + "https://static.crates.io/crates/linkme/0.3.36/download" ], - "sha256": "f3498dafa633751a52b9b7f741f7ac30c42bcbeb3b9edca6d447e0da8e693262", - "strip_prefix": "swift-nio-transport-services-1.15.0/", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_nio_transport_services/BUILD.overlay" + "strip_prefix": "linkme-0.3.36", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"linkme\",\n deps = [\n \"@crates__linkme-0.3.36//:build_script_build\",\n ],\n proc_macro_deps = [\n \"@crates__linkme-impl-0.3.36//:linkme_impl\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=linkme\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.36\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"linkme\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=linkme\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.3.36\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" } }, - "com_github_apple_swift_nio_extras": { + "crates__linkme-impl-0.3.36": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { + "remote_patch_strip": 1, + "sha256": "32d59e20403c7d08fe62b4376edfe5c7fb2ef1e6b1465379686d0f21c8df444b", + "type": "tar.gz", "urls": [ - "https://github.com/apple/swift-nio-extras/archive/1.4.0.tar.gz" + "https://static.crates.io/crates/linkme-impl/0.3.36/download" ], - "sha256": "4684b52951d9d9937bb3e8ccd6b5daedd777021ef2519ea2f18c4c922843b52b", - "strip_prefix": "swift-nio-extras-1.4.0/", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_nio_extras/BUILD.overlay" + "strip_prefix": "linkme-impl-0.3.36", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"linkme_impl\",\n deps = [\n \"@crates__linkme-impl-0.3.36//:build_script_build\",\n \"@crates__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates__quote-1.0.45//:quote\",\n \"@crates__syn-2.0.117//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=linkme-impl\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.36\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"linkme-impl\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=linkme-impl\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.3.36\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" } }, - "com_github_apple_swift_log": { + "crates__memchr-2.8.1": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { + "remote_patch_strip": 1, + "sha256": "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8", + "type": "tar.gz", "urls": [ - "https://github.com/apple/swift-log/archive/1.4.4.tar.gz" + "https://static.crates.io/crates/memchr/2.8.1/download" ], - "sha256": "48fe66426c784c0c20031f15dc17faf9f4c9037c192bfac2f643f65cb2321ba0", - "strip_prefix": "swift-log-1.4.4/", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_log/BUILD.overlay" + "strip_prefix": "memchr-2.8.1", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"memchr\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=memchr\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.8.1\",\n)\n" } }, - "com_github_apple_swift_nio_ssl": { + "crates__num-traits-0.2.19": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { + "remote_patch_strip": 1, + "sha256": "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841", + "type": "tar.gz", "urls": [ - "https://github.com/apple/swift-nio-ssl/archive/2.23.0.tar.gz" + "https://static.crates.io/crates/num-traits/0.2.19/download" ], - "sha256": "4787c63f61dd04d99e498adc3d1a628193387e41efddf8de19b8db04544d016d", - "strip_prefix": "swift-nio-ssl-2.23.0/", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_nio_ssl/BUILD.overlay" + "strip_prefix": "num-traits-0.2.19", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"num_traits\",\n deps = [\n \"@crates__num-traits-0.2.19//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=num-traits\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.19\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n deps = [\n \"@crates__autocfg-1.5.1//:autocfg\",\n ],\n edition = \"2021\",\n pkg_name = \"num-traits\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=num-traits\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"0.2.19\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" } }, - "com_github_apple_swift_collections": { + "crates__paste-1.0.15": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { + "remote_patch_strip": 1, + "sha256": "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a", + "type": "tar.gz", "urls": [ - "https://github.com/apple/swift-collections/archive/1.0.4.tar.gz" + "https://static.crates.io/crates/paste/1.0.15/download" ], - "sha256": "d9e4c8a91c60fb9c92a04caccbb10ded42f4cb47b26a212bc6b39cc390a4b096", - "strip_prefix": "swift-collections-1.0.4/", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_collections/BUILD.overlay" + "strip_prefix": "paste-1.0.15", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"paste\",\n deps = [\n \"@crates__paste-1.0.15//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=paste\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.15\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"paste\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=paste\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.15\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" } }, - "com_github_apple_swift_atomics": { + "crates__proc-macro2-1.0.106": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { + "remote_patch_strip": 1, + "sha256": "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934", + "type": "tar.gz", "urls": [ - "https://github.com/apple/swift-atomics/archive/1.1.0.tar.gz" + "https://static.crates.io/crates/proc-macro2/1.0.106/download" ], - "sha256": "1bee7f469f7e8dc49f11cfa4da07182fbc79eab000ec2c17bfdce468c5d276fb", - "strip_prefix": "swift-atomics-1.1.0/", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_atomics/BUILD.overlay" + "strip_prefix": "proc-macro2-1.0.106", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"proc_macro2\",\n deps = [\n \"@crates__proc-macro2-1.0.106//:build_script_build\",\n \"@crates__unicode-ident-1.0.24//:unicode_ident\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"proc-macro\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"default\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"default\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"default\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"default\", # x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=proc-macro2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.106\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"proc-macro\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"default\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"default\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"default\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"default\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"default\", # x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"proc-macro2\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=proc-macro2\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.106\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" } }, - "build_bazel_rules_swift_index_import": { + "crates__quote-1.0.45": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "build_file": "@@rules_swift+//third_party:build_bazel_rules_swift_index_import/BUILD.overlay", - "canonical_id": "index-import-5.8", + "remote_patch_strip": 1, + "sha256": "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924", + "type": "tar.gz", "urls": [ - "https://github.com/MobileNativeFoundation/index-import/releases/download/5.8.0.1/index-import.tar.gz" + "https://static.crates.io/crates/quote/1.0.45/download" ], - "sha256": "28c1ffa39d99e74ed70623899b207b41f79214c498c603915aef55972a851a15" + "strip_prefix": "quote-1.0.45", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"quote\",\n deps = [\n \"@crates__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates__quote-1.0.45//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"proc-macro\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=quote\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.45\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"proc-macro\",\n ],\n crate_name = \"build_script_build\",\n crate_root = \"build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2021\",\n pkg_name = \"quote\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=quote\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.45\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" } }, - "build_bazel_rules_swift_local_config": { - "repoRuleId": "@@rules_swift+//swift/internal:swift_autoconfiguration.bzl%swift_autoconfiguration", - "attributes": {} + "crates__regex-1.12.3": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/regex/1.12.3/download" + ], + "strip_prefix": "regex-1.12.3", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"regex\",\n deps = [\n \"@crates__aho-corasick-1.1.4//:aho_corasick\",\n \"@crates__memchr-2.8.1//:memchr\",\n \"@crates__regex-automata-0.4.14//:regex_automata\",\n \"@crates__regex-syntax-0.8.10//:regex_syntax\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"perf\",\n \"perf-backtrack\",\n \"perf-cache\",\n \"perf-dfa\",\n \"perf-inline\",\n \"perf-literal\",\n \"perf-onepass\",\n \"std\",\n \"unicode\",\n \"unicode-age\",\n \"unicode-bool\",\n \"unicode-case\",\n \"unicode-gencat\",\n \"unicode-perl\",\n \"unicode-script\",\n \"unicode-segment\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=regex\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.12.3\",\n)\n" + } + }, + "crates__regex-automata-0.4.14": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/regex-automata/0.4.14/download" + ], + "strip_prefix": "regex-automata-0.4.14", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"regex_automata\",\n deps = [\n \"@crates__aho-corasick-1.1.4//:aho_corasick\",\n \"@crates__memchr-2.8.1//:memchr\",\n \"@crates__regex-syntax-0.8.10//:regex_syntax\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"alloc\",\n \"dfa-onepass\",\n \"hybrid\",\n \"meta\",\n \"nfa-backtrack\",\n \"nfa-pikevm\",\n \"nfa-thompson\",\n \"perf-inline\",\n \"perf-literal\",\n \"perf-literal-multisubstring\",\n \"perf-literal-substring\",\n \"std\",\n \"syntax\",\n \"unicode\",\n \"unicode-age\",\n \"unicode-bool\",\n \"unicode-case\",\n \"unicode-gencat\",\n \"unicode-perl\",\n \"unicode-script\",\n \"unicode-segment\",\n \"unicode-word-boundary\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=regex-automata\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.4.14\",\n)\n" + } + }, + "crates__regex-syntax-0.8.10": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/regex-syntax/0.8.10/download" + ], + "strip_prefix": "regex-syntax-0.8.10", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"regex_syntax\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"default\",\n \"std\",\n \"unicode\",\n \"unicode-age\",\n \"unicode-bool\",\n \"unicode-case\",\n \"unicode-gencat\",\n \"unicode-perl\",\n \"unicode-script\",\n \"unicode-segment\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=regex-syntax\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.8.10\",\n)\n" + } + }, + "crates__rustversion-1.0.22": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/rustversion/1.0.22/download" + ], + "strip_prefix": "rustversion-1.0.22", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\nload(\n \"@rules_rust//cargo:defs.bzl\",\n \"cargo_build_script\",\n \"cargo_toml_env_vars\",\n)\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"rustversion\",\n deps = [\n \"@crates__rustversion-1.0.22//:build_script_build\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustversion\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.22\",\n)\n\ncargo_build_script(\n name = \"_bs\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \"**/*.rs\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_name = \"build_script_build\",\n crate_root = \"build/build.rs\",\n data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n edition = \"2018\",\n pkg_name = \"rustversion\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=rustversion\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n version = \"1.0.22\",\n visibility = [\"//visibility:private\"],\n)\n\nalias(\n name = \"build_script_build\",\n actual = \":_bs\",\n tags = [\"manual\"],\n)\n" + } + }, + "crates__syn-2.0.117": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/syn/2.0.117/download" + ], + "strip_prefix": "syn-2.0.117", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"syn\",\n deps = [\n \"@crates__proc-macro2-1.0.106//:proc_macro2\",\n \"@crates__quote-1.0.45//:quote\",\n \"@crates__unicode-ident-1.0.24//:unicode_ident\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"clone-impls\",\n \"default\",\n \"derive\",\n \"parsing\",\n \"printing\",\n \"proc-macro\",\n ] + select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [\n \"extra-traits\", # aarch64-apple-darwin\n \"full\", # aarch64-apple-darwin\n ],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [\n \"extra-traits\", # aarch64-unknown-linux-gnu\n \"full\", # aarch64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [\n \"extra-traits\", # x86_64-pc-windows-msvc\n \"full\", # x86_64-pc-windows-msvc\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [\n \"extra-traits\", # x86_64-unknown-linux-gnu\n \"full\", # x86_64-unknown-linux-gnu\n ],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [\n \"extra-traits\", # x86_64-unknown-nixos-gnu\n \"full\", # x86_64-unknown-nixos-gnu\n ],\n \"//conditions:default\": [],\n }),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=syn\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"2.0.117\",\n)\n" + } + }, + "crates__unicode-ident-1.0.24": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "remote_patch_strip": 1, + "sha256": "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/unicode-ident/1.0.24/download" + ], + "strip_prefix": "unicode-ident-1.0.24", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'protobuf'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"unicode_ident\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=unicode-ident\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:wasm32-unknown-unknown\": [],\n \"@rules_rust//rust/platform:wasm32-wasip1\": [],\n \"@rules_rust//rust/platform:x86_64-pc-windows-msvc\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"1.0.24\",\n)\n" + } + } + } + } + }, + "@@rules_rust+//crate_universe/private:internal_extensions.bzl%cu_nr": { + "general": { + "bzlTransitiveDigest": "2RrHa9GqLSgJmFPwKD1wONow/2x/PGTGakiYDTBQJ88=", + "usagesDigest": "tG3p3Nb5XxC7vWY/bcKdb//g0HoAxpxxH3F5/jBVlk4=", + "recordedInputs": [ + "REPO_MAPPING:bazel_features+,bazel_features_globals bazel_features++version_extension+bazel_features_globals", + "REPO_MAPPING:bazel_features+,bazel_features_version bazel_features++version_extension+bazel_features_version", + "REPO_MAPPING:rules_cc+,bazel_skylib bazel_skylib+", + "REPO_MAPPING:rules_cc+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_cc+,cc_compatibility_proxy rules_cc++compatibility_proxy+cc_compatibility_proxy", + "REPO_MAPPING:rules_cc+,platforms platforms", + "REPO_MAPPING:rules_cc+,rules_cc rules_cc+", + "REPO_MAPPING:rules_cc++compatibility_proxy+cc_compatibility_proxy,rules_cc rules_cc+", + "REPO_MAPPING:rules_rust+,bazel_features bazel_features+", + "REPO_MAPPING:rules_rust+,bazel_skylib bazel_skylib+", + "REPO_MAPPING:rules_rust+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_rust+,cui rules_rust++cu+cui", + "REPO_MAPPING:rules_rust+,rrc rules_rust++i2+rrc", + "REPO_MAPPING:rules_rust+,rules_cc rules_cc+", + "REPO_MAPPING:rules_rust+,rules_rust rules_rust+" + ], + "generatedRepoSpecs": { + "cargo_bazel_bootstrap": { + "repoRuleId": "@@rules_rust+//cargo/private:cargo_bootstrap.bzl%cargo_bootstrap_repository", + "attributes": { + "srcs": [ + "@@rules_rust+//crate_universe:src/api.rs", + "@@rules_rust+//crate_universe:src/api/lockfile.rs", + "@@rules_rust+//crate_universe:src/cli.rs", + "@@rules_rust+//crate_universe:src/cli/generate.rs", + "@@rules_rust+//crate_universe:src/cli/query.rs", + "@@rules_rust+//crate_universe:src/cli/render.rs", + "@@rules_rust+//crate_universe:src/cli/splice.rs", + "@@rules_rust+//crate_universe:src/cli/vendor.rs", + "@@rules_rust+//crate_universe:src/config.rs", + "@@rules_rust+//crate_universe:src/context.rs", + "@@rules_rust+//crate_universe:src/context/crate_context.rs", + "@@rules_rust+//crate_universe:src/context/platforms.rs", + "@@rules_rust+//crate_universe:src/lib.rs", + "@@rules_rust+//crate_universe:src/lockfile.rs", + "@@rules_rust+//crate_universe:src/main.rs", + "@@rules_rust+//crate_universe:src/metadata.rs", + "@@rules_rust+//crate_universe:src/metadata/cargo_bin.rs", + "@@rules_rust+//crate_universe:src/metadata/cargo_tree_resolver.rs", + "@@rules_rust+//crate_universe:src/metadata/cargo_tree_rustc_wrapper.bat", + "@@rules_rust+//crate_universe:src/metadata/cargo_tree_rustc_wrapper.sh", + "@@rules_rust+//crate_universe:src/metadata/dependency.rs", + "@@rules_rust+//crate_universe:src/metadata/metadata_annotation.rs", + "@@rules_rust+//crate_universe:src/rendering.rs", + "@@rules_rust+//crate_universe:src/rendering/template_engine.rs", + "@@rules_rust+//crate_universe:src/rendering/templates/module_bzl.j2", + "@@rules_rust+//crate_universe:src/rendering/templates/partials/header.j2", + "@@rules_rust+//crate_universe:src/rendering/templates/partials/module/aliases_map.j2", + "@@rules_rust+//crate_universe:src/rendering/templates/partials/module/deps_map.j2", + "@@rules_rust+//crate_universe:src/rendering/templates/partials/module/repo_git.j2", + "@@rules_rust+//crate_universe:src/rendering/templates/partials/module/repo_http.j2", + "@@rules_rust+//crate_universe:src/rendering/templates/vendor_module.j2", + "@@rules_rust+//crate_universe:src/rendering/verbatim/alias_rules.bzl", + "@@rules_rust+//crate_universe:src/select.rs", + "@@rules_rust+//crate_universe:src/splicing.rs", + "@@rules_rust+//crate_universe:src/splicing/cargo_config.rs", + "@@rules_rust+//crate_universe:src/splicing/crate_index_lookup.rs", + "@@rules_rust+//crate_universe:src/splicing/splicer.rs", + "@@rules_rust+//crate_universe:src/test.rs", + "@@rules_rust+//crate_universe:src/utils.rs", + "@@rules_rust+//crate_universe:src/utils/starlark.rs", + "@@rules_rust+//crate_universe:src/utils/starlark/glob.rs", + "@@rules_rust+//crate_universe:src/utils/starlark/label.rs", + "@@rules_rust+//crate_universe:src/utils/starlark/select.rs", + "@@rules_rust+//crate_universe:src/utils/starlark/select_dict.rs", + "@@rules_rust+//crate_universe:src/utils/starlark/select_list.rs", + "@@rules_rust+//crate_universe:src/utils/starlark/select_scalar.rs", + "@@rules_rust+//crate_universe:src/utils/starlark/select_set.rs", + "@@rules_rust+//crate_universe:src/utils/starlark/serialize.rs", + "@@rules_rust+//crate_universe:src/utils/starlark/target_compatible_with.rs", + "@@rules_rust+//crate_universe:src/utils/symlink.rs", + "@@rules_rust+//crate_universe:src/utils/target_triple.rs" + ], + "binary": "cargo-bazel", + "cargo_lockfile": "@@rules_rust+//crate_universe:Cargo.lock", + "cargo_toml": "@@rules_rust+//crate_universe:Cargo.toml", + "version": "1.93.1", + "timeout": 900, + "rust_toolchain_cargo_template": "@rust_host_tools//:bin/{tool}", + "rust_toolchain_rustc_template": "@rust_host_tools//:bin/{tool}", + "compressed_windows_toolchain_names": false + } } }, - "recordedRepoMappingEntries": [ - [ - "rules_swift+", - "bazel_tools", - "bazel_tools" + "moduleExtensionMetadata": { + "explicitRootModuleDirectDeps": [ + "cargo_bazel_bootstrap" ], - [ - "rules_swift+", - "build_bazel_rules_swift", - "rules_swift+" - ] - ] + "explicitRootModuleDirectDevDeps": [], + "useAllRepos": "NO", + "reproducible": false + } + } + }, + "@@yq.bzl+//yq:extensions.bzl%yq": { + "general": { + "bzlTransitiveDigest": "tDqk+ntWTdxNAWPDjRY1uITgHbti2jcXR5ZdinltBs0=", + "usagesDigest": "Pz33iK5QtkPFivaljfReN4KzpAqM2DgY4vwgyZSNoAM=", + "recordedInputs": [], + "generatedRepoSpecs": { + "yq_darwin_amd64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "darwin_amd64", + "version": "4.45.2" + } + }, + "yq_darwin_arm64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "darwin_arm64", + "version": "4.45.2" + } + }, + "yq_linux_amd64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "linux_amd64", + "version": "4.45.2" + } + }, + "yq_linux_arm64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "linux_arm64", + "version": "4.45.2" + } + }, + "yq_linux_s390x": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "linux_s390x", + "version": "4.45.2" + } + }, + "yq_linux_riscv64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "linux_riscv64", + "version": "4.45.2" + } + }, + "yq_linux_ppc64le": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "linux_ppc64le", + "version": "4.45.2" + } + }, + "yq_windows_amd64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "windows_amd64", + "version": "4.45.2" + } + }, + "yq_windows_arm64": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo", + "attributes": { + "platform": "windows_arm64", + "version": "4.45.2" + } + }, + "yq_toolchains": { + "repoRuleId": "@@yq.bzl+//yq/toolchain:toolchain.bzl%yq_toolchains_repo", + "attributes": { + "user_repository_name": "yq" + } + } + } } } }, - "facts": {} + "facts": { + "@@rules_go+//go:extensions.bzl%go_sdk": { + "1.22.4": { + "aix_ppc64": [ + "go1.22.4.aix-ppc64.tar.gz", + "b9647fa9fc83a0cc5d4f092a19eaeaecf45f063a5aa7d4962fde65aeb7ae6ce1" + ], + "darwin_amd64": [ + "go1.22.4.darwin-amd64.tar.gz", + "c95967f50aa4ace34af0c236cbdb49a9a3e80ee2ad09d85775cb4462a5c19ed3" + ], + "darwin_arm64": [ + "go1.22.4.darwin-arm64.tar.gz", + "242b78dc4c8f3d5435d28a0d2cec9b4c1aa999b601fb8aa59fb4e5a1364bf827" + ], + "dragonfly_amd64": [ + "go1.22.4.dragonfly-amd64.tar.gz", + "f2fbb51af4719d3616efb482d6ed2b96579b474156f85a7ddc6f126764feec4b" + ], + "freebsd_386": [ + "go1.22.4.freebsd-386.tar.gz", + "7c54884bb9f274884651d41e61d1bc12738863ad1497e97ea19ad0e9aa6bf7b5" + ], + "freebsd_amd64": [ + "go1.22.4.freebsd-amd64.tar.gz", + "88d44500e1701dd35797619774d6dd51bf60f45a8338b0a82ddc018e4e63fb78" + ], + "freebsd_arm64": [ + "go1.22.4.freebsd-arm64.tar.gz", + "726dc093cf020277be45debf03c3b02b43c2efb3e2a5d4fba8f52579d65327dc" + ], + "freebsd_armv6l": [ + "go1.22.4.freebsd-arm.tar.gz", + "3d9efe47db142a22679aba46b1772e3900b0d87ae13bd2b3bc80dbf2ac0b2cd6" + ], + "freebsd_riscv64": [ + "go1.22.4.freebsd-riscv64.tar.gz", + "5f6b67e5e32f1d6ccb2d4dcb44934a5e2e870a877ba7443d86ec43cfc28afa71" + ], + "illumos_amd64": [ + "go1.22.4.illumos-amd64.tar.gz", + "d56ecc2f85b6418a21ef83879594d0c42ab4f65391a676bb12254870e6690d63" + ], + "linux_386": [ + "go1.22.4.linux-386.tar.gz", + "47a2a8d249a91eb8605c33bceec63aedda0441a43eac47b4721e3975ff916cec" + ], + "linux_amd64": [ + "go1.22.4.linux-amd64.tar.gz", + "ba79d4526102575196273416239cca418a651e049c2b099f3159db85e7bade7d" + ], + "linux_arm64": [ + "go1.22.4.linux-arm64.tar.gz", + "a8e177c354d2e4a1b61020aca3562e27ea3e8f8247eca3170e3fa1e0c2f9e771" + ], + "linux_armv6l": [ + "go1.22.4.linux-armv6l.tar.gz", + "e2b143fbacbc9cbd448e9ef41ac3981f0488ce849af1cf37e2341d09670661de" + ], + "linux_loong64": [ + "go1.22.4.linux-loong64.tar.gz", + "e2ff9436e4b34bf6926b06d97916e26d67a909a2effec17967245900f0816f1d" + ], + "linux_mips": [ + "go1.22.4.linux-mips.tar.gz", + "73f0dcc60458c4770593b05a7bc01cc0d31fc98f948c0c2334812c7a1f2fc3f1" + ], + "linux_mips64": [ + "go1.22.4.linux-mips64.tar.gz", + "417af97fc2630a647052375768be4c38adcc5af946352ea5b28613ea81ca5d45" + ], + "linux_mips64le": [ + "go1.22.4.linux-mips64le.tar.gz", + "7486e2d7dd8c98eb44df815ace35a7fe7f30b7c02326e3741bd934077508139b" + ], + "linux_mipsle": [ + "go1.22.4.linux-mipsle.tar.gz", + "69479c8aad301e459a8365b40cad1074a0dbba5defb9291669f94809c4c4be6e" + ], + "linux_ppc64": [ + "go1.22.4.linux-ppc64.tar.gz", + "dd238847e65bc3e2745caca475a5db6522a2fcf85cf6c38fc36a06642b19efd7" + ], + "linux_ppc64le": [ + "go1.22.4.linux-ppc64le.tar.gz", + "a3e5834657ef92523f570f798fed42f1f87bc18222a16815ec76b84169649ec4" + ], + "linux_riscv64": [ + "go1.22.4.linux-riscv64.tar.gz", + "56a827ff7dc6245bcd7a1e9288dffaa1d8b0fd7468562264c1523daf3b4f1b4a" + ], + "linux_s390x": [ + "go1.22.4.linux-s390x.tar.gz", + "7590c3e278e2dc6040aae0a39da3ca1eb2e3921673a7304cc34d588c45889eec" + ], + "netbsd_386": [ + "go1.22.4.netbsd-386.tar.gz", + "ddd2eebe34471a2502de6c5dad04ab27c9fc80cbde7a9ad5b3c66ecec4504e1d" + ], + "netbsd_amd64": [ + "go1.22.4.netbsd-amd64.tar.gz", + "33af79f6f935f6fbacc5d23876450b3567b79348fc065beef8e64081127dd234" + ], + "netbsd_arm64": [ + "go1.22.4.netbsd-arm64.tar.gz", + "c9a2971dec9f6d320c6f2b049b2353c6d0a2d35e87b8a4b2d78a2f0d62545f8e" + ], + "netbsd_armv6l": [ + "go1.22.4.netbsd-arm.tar.gz", + "fa3550ebd5375a70b3bcd342b5a71f4bd271dcbbfaf4eabefa2144ab5d8924b6" + ], + "openbsd_386": [ + "go1.22.4.openbsd-386.tar.gz", + "d21af022331bfdc2b5b161d616c3a1a4573d33cf7a30416ee509a8f3641deb47" + ], + "openbsd_amd64": [ + "go1.22.4.openbsd-amd64.tar.gz", + "72c0094c43f7e5722ec49c2a3e9dfa7a1123ac43a5f3a63eecf3e3795d3ff0ae" + ], + "openbsd_arm64": [ + "go1.22.4.openbsd-arm64.tar.gz", + "a7ab8d4e0b02bf06ed144ba42c61c0e93ee00f2b433415dfd4ad4b6e79f31650" + ], + "openbsd_armv6l": [ + "go1.22.4.openbsd-arm.tar.gz", + "1096831ea3c5ea3ca57d14251d9eda3786889531eb40d7d6775dcaa324d4b065" + ], + "openbsd_ppc64": [ + "go1.22.4.openbsd-ppc64.tar.gz", + "9716327c8a628358798898dc5148c49dbbeb5196bf2cbf088e550721a6e4f60b" + ], + "plan9_386": [ + "go1.22.4.plan9-386.tar.gz", + "a8dd4503c95c32a502a616ab78870a19889c9325fe9bd31eb16dd69346e4bfa8" + ], + "plan9_amd64": [ + "go1.22.4.plan9-amd64.tar.gz", + "5423a25808d76fe5aca8607a2e5ac5673abf45446b168cb5e9d8519ee9fe39a1" + ], + "plan9_armv6l": [ + "go1.22.4.plan9-arm.tar.gz", + "6af939ad583f5c85c09c53728ab7d38c3cc2b39167562d6c18a07c5c6608b370" + ], + "solaris_amd64": [ + "go1.22.4.solaris-amd64.tar.gz", + "e8cabe69c03085725afdb32a6f9998191a3e55a747b270d835fd05000d56abba" + ], + "windows_386": [ + "go1.22.4.windows-386.zip", + "aca4e2c37278a10f1c70dd0df142f7d66b50334fcee48978d409202d308d6d25" + ], + "windows_amd64": [ + "go1.22.4.windows-amd64.zip", + "26321c4d945a0035d8a5bc4a1965b0df401ff8ceac66ce2daadabf9030419a98" + ], + "windows_arm64": [ + "go1.22.4.windows-arm64.zip", + "8a2daa9ea28cbdafddc6171aefed384f4e5b6e714fb52116fe9ed25a132f37ed" + ], + "windows_armv6l": [ + "go1.22.4.windows-arm.zip", + "5fcd0671a49cecf39b41021621ee1b6e7aa1370f37122b72e80d4fd4185833b6" + ] + }, + "1.25.0": { + "aix_ppc64": [ + "go1.25.0.aix-ppc64.tar.gz", + "e5234a7dac67bc86c528fe9752fc9d63557918627707a733ab4cac1a6faed2d4" + ], + "darwin_amd64": [ + "go1.25.0.darwin-amd64.tar.gz", + "5bd60e823037062c2307c71e8111809865116714d6f6b410597cf5075dfd80ef" + ], + "darwin_arm64": [ + "go1.25.0.darwin-arm64.tar.gz", + "544932844156d8172f7a28f77f2ac9c15a23046698b6243f633b0a0b00c0749c" + ], + "dragonfly_amd64": [ + "go1.25.0.dragonfly-amd64.tar.gz", + "5ed3cf9a810a1483822538674f1336c06b51aa1b94d6d545a1a0319a48177120" + ], + "freebsd_386": [ + "go1.25.0.freebsd-386.tar.gz", + "abea5d5c6697e6b5c224731f2158fe87c602996a2a233ac0c4730cd57bf8374e" + ], + "freebsd_amd64": [ + "go1.25.0.freebsd-amd64.tar.gz", + "86e6fe0a29698d7601c4442052dac48bd58d532c51cccb8f1917df648138730b" + ], + "freebsd_arm": [ + "go1.25.0.freebsd-arm.tar.gz", + "d90b78e41921f72f30e8bbc81d9dec2cff7ff384a33d8d8debb24053e4336bfe" + ], + "freebsd_arm64": [ + "go1.25.0.freebsd-arm64.tar.gz", + "451d0da1affd886bfb291b7c63a6018527b269505db21ce6e14724f22ab0662e" + ], + "freebsd_riscv64": [ + "go1.25.0.freebsd-riscv64.tar.gz", + "7b565f76bd8bda46549eeaaefe0e53b251e644c230577290c0f66b1ecdb3cdbe" + ], + "illumos_amd64": [ + "go1.25.0.illumos-amd64.tar.gz", + "b1e1fdaab1ad25aa1c08d7a36c97d45d74b98b89c3f78c6d2145f77face54a2c" + ], + "linux_386": [ + "go1.25.0.linux-386.tar.gz", + "8c602dd9d99bc9453b3995d20ce4baf382cc50855900a0ece5de9929df4a993a" + ], + "linux_amd64": [ + "go1.25.0.linux-amd64.tar.gz", + "2852af0cb20a13139b3448992e69b868e50ed0f8a1e5940ee1de9e19a123b613" + ], + "linux_arm64": [ + "go1.25.0.linux-arm64.tar.gz", + "05de75d6994a2783699815ee553bd5a9327d8b79991de36e38b66862782f54ae" + ], + "linux_armv6l": [ + "go1.25.0.linux-armv6l.tar.gz", + "a5a8f8198fcf00e1e485b8ecef9ee020778bf32a408a4e8873371bfce458cd09" + ], + "linux_loong64": [ + "go1.25.0.linux-loong64.tar.gz", + "cab86b1cf761b1cb3bac86a8877cfc92e7b036fc0d3084123d77013d61432afc" + ], + "linux_mips": [ + "go1.25.0.linux-mips.tar.gz", + "d66b6fb74c3d91b9829dc95ec10ca1f047ef5e89332152f92e136cf0e2da5be1" + ], + "linux_mips64": [ + "go1.25.0.linux-mips64.tar.gz", + "4082e4381a8661bc2a839ff94ba3daf4f6cde20f8fb771b5b3d4762dc84198a2" + ], + "linux_mips64le": [ + "go1.25.0.linux-mips64le.tar.gz", + "70002c299ec7f7175ac2ef673b1b347eecfa54ae11f34416a6053c17f855afcc" + ], + "linux_mipsle": [ + "go1.25.0.linux-mipsle.tar.gz", + "b00a3a39eff099f6df9f1c7355bf28e4589d0586f42d7d4a394efb763d145a73" + ], + "linux_ppc64": [ + "go1.25.0.linux-ppc64.tar.gz", + "df166f33bd98160662560a72ff0b4ba731f969a80f088922bddcf566a88c1ec1" + ], + "linux_ppc64le": [ + "go1.25.0.linux-ppc64le.tar.gz", + "0f18a89e7576cf2c5fa0b487a1635d9bcbf843df5f110e9982c64df52a983ad0" + ], + "linux_riscv64": [ + "go1.25.0.linux-riscv64.tar.gz", + "c018ff74a2c48d55c8ca9b07c8e24163558ffec8bea08b326d6336905d956b67" + ], + "linux_s390x": [ + "go1.25.0.linux-s390x.tar.gz", + "34e5a2e19f2292fbaf8783e3a241e6e49689276aef6510a8060ea5ef54eee408" + ], + "netbsd_386": [ + "go1.25.0.netbsd-386.tar.gz", + "f8586cdb7aa855657609a5c5f6dbf523efa00c2bbd7c76d3936bec80aa6c0aba" + ], + "netbsd_amd64": [ + "go1.25.0.netbsd-amd64.tar.gz", + "ae8dc1469385b86a157a423bb56304ba45730de8a897615874f57dd096db2c2a" + ], + "netbsd_arm": [ + "go1.25.0.netbsd-arm.tar.gz", + "1ff7e4cc764425fc9dd6825eaee79d02b3c7cafffbb3691687c8d672ade76cb7" + ], + "netbsd_arm64": [ + "go1.25.0.netbsd-arm64.tar.gz", + "e1b310739f26724216aa6d7d7208c4031f9ff54c9b5b9a796ddc8bebcb4a5f16" + ], + "openbsd_386": [ + "go1.25.0.openbsd-386.tar.gz", + "4802a9b20e533da91adb84aab42e94aa56cfe3e5475d0550bed3385b182e69d8" + ], + "openbsd_amd64": [ + "go1.25.0.openbsd-amd64.tar.gz", + "c016cd984bebe317b19a4f297c4f50def120dc9788490540c89f28e42f1dabe1" + ], + "openbsd_arm": [ + "go1.25.0.openbsd-arm.tar.gz", + "a1e31d0bf22172ddde42edf5ec811ef81be43433df0948ece52fecb247ccfd8d" + ], + "openbsd_arm64": [ + "go1.25.0.openbsd-arm64.tar.gz", + "343ea8edd8c218196e15a859c6072d0dd3246fbbb168481ab665eb4c4140458d" + ], + "openbsd_ppc64": [ + "go1.25.0.openbsd-ppc64.tar.gz", + "694c14da1bcaeb5e3332d49bdc2b6d155067648f8fe1540c5de8f3cf8e157154" + ], + "openbsd_riscv64": [ + "go1.25.0.openbsd-riscv64.tar.gz", + "aa510ad25cf54c06cd9c70b6d80ded69cb20188ac6e1735655eef29ff7e7885f" + ], + "plan9_386": [ + "go1.25.0.plan9-386.tar.gz", + "46f8cef02086cf04bf186c5912776b56535178d4cb319cd19c9fdbdd29231986" + ], + "plan9_amd64": [ + "go1.25.0.plan9-amd64.tar.gz", + "29b34391d84095e44608a228f63f2f88113a37b74a79781353ec043dfbcb427b" + ], + "plan9_arm": [ + "go1.25.0.plan9-arm.tar.gz", + "0a047107d13ebe7943aaa6d54b1d7bbd2e45e68ce449b52915a818da715799c2" + ], + "solaris_amd64": [ + "go1.25.0.solaris-amd64.tar.gz", + "9977f9e4351984364a3b2b78f8b88bfd1d339812356d5237678514594b7d3611" + ], + "windows_386": [ + "go1.25.0.windows-386.zip", + "df9f39db82a803af0db639e3613a36681ab7a42866b1384b3f3a1045663961a7" + ], + "windows_amd64": [ + "go1.25.0.windows-amd64.zip", + "89efb4f9b30812eee083cc1770fdd2913c14d301064f6454851428f9707d190b" + ], + "windows_arm64": [ + "go1.25.0.windows-arm64.zip", + "27bab004c72b3d7bd05a69b6ec0fc54a309b4b78cc569dd963d8b3ec28bfdb8c" + ] + } + }, + "@@rules_python+//python/extensions:pip.bzl%pip": { + "dist_hashes": { + "https://pypi.org/simple": { + "backports-tarfile": { + "https://files.pythonhosted.org/packages/86/72/cd9b395f25e290e633655a100af28cb253e4393396264a98bd5f5951d50f/backports_tarfile-1.2.0.tar.gz": "d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991", + "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl": "77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34" + }, + "certifi": { + "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz": "47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", + "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl": "0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de" + }, + "cffi": { + "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl": "5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", + "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl": "3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", + "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl": "5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", + "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl": "b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", + "https://files.pythonhosted.org/packages/15/12/a7a79bd0df4c3bff744b2d7e52cc1b68d5e7e427b384252c42366dc1ecbc/cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl": "3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", + "https://files.pythonhosted.org/packages/1f/74/cc4096ce66f5939042ae094e2e96f53426a979864aa1f96a621ad128be27/cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl": "61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", + "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl": "cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", + "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl": "7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", + "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl": "737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", + "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl": "c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", + "https://files.pythonhosted.org/packages/2b/e7/7c769804eb75e4c4b35e658dba01de1640a351a9653c3d49ca89d16ccc91/cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl": "89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", + "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl": "7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", + "https://files.pythonhosted.org/packages/32/f2/81b63e288295928739d715d00952c8c6034cb6c6a516b17d37e0c8be5600/cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl": "cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", + "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl": "b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", + "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl": "6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", + "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl": "19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", + "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl": "81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", + "https://files.pythonhosted.org/packages/3d/de/38d9726324e127f727b4ecc376bc85e505bfe61ef130eaf3f290c6847dd4/cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl": "de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", + "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl": "9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", + "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl": "087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", + "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl": "a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", + "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl": "afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", + "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl": "8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", + "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl": "45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", + "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl": "00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", + "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl": "3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", + "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl": "2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", + "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl": "53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", + "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl": "d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", + "https://files.pythonhosted.org/packages/54/8f/a1e836f82d8e32a97e6b29cc8f641779181ac7363734f12df27db803ebda/cffi-2.0.0-cp39-cp39-win_amd64.whl": "b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", + "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl": "c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", + "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl": "e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", + "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl": "3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", + "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl": "da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", + "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl": "9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", + "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl": "fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", + "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl": "0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", + "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl": "4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", + "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl": "c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", + "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl": "fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", + "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl": "c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", + "https://files.pythonhosted.org/packages/9b/13/c92e36358fbcc39cf0962e83223c9522154ee8630e1df7c0b3a39a8124e2/cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl": "4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", + "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl": "f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", + "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl": "dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", + "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl": "9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", + "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl": "1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", + "https://files.pythonhosted.org/packages/a3/ad/5c51c1c7600bdd7ed9a24a203ec255dccdd0ebf4527f7b922a0bde2fb6ed/cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl": "e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", + "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl": "d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", + "https://files.pythonhosted.org/packages/aa/d9/6218d78f920dcd7507fc16a766b5ef8f3b913cc7aa938e7fc80b9978d089/cffi-2.0.0-cp39-cp39-win32.whl": "2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", + "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl": "94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", + "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl": "0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", + "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl": "66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", + "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl": "07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", + "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl": "baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", + "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl": "12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", + "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl": "2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", + "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl": "730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", + "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl": "203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", + "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl": "d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", + "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl": "7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", + "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl": "d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", + "https://files.pythonhosted.org/packages/c0/cc/08ed5a43f2996a16b462f64a7055c6e962803534924b9b2f1371d8c00b7b/cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl": "fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf", + "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl": "1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", + "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl": "38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", + "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl": "256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", + "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl": "dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", + "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl": "28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", + "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl": "b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", + "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl": "24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", + "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl": "8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", + "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl": "b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", + "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl": "6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", + "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl": "8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", + "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl": "92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", + "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl": "6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", + "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl": "1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", + "https://files.pythonhosted.org/packages/e8/be/f6424d1dc46b1091ffcc8964fa7c0ab0cd36839dd2761b49c90481a6ba1b/cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl": "0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", + "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl": "6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", + "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz": "44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", + "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl": "74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", + "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl": "f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", + "https://files.pythonhosted.org/packages/f7/e0/dda537c2309817edf60109e39265f24f24aa7f050767e22c98c53fe7f48b/cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl": "1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", + "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl": "da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", + "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl": "21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe" + }, + "charset-normalizer": { + "https://files.pythonhosted.org/packages/00/bd/ef9c88464b126fa176f4ef4a317ad9b6f4d30b2cffbc43386062367c3e2c/charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl": "8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9", + "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl": "ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", + "https://files.pythonhosted.org/packages/04/9a/914d294daa4809c57667b77470533e65def9c0be1ef8b4c1183a99170e9d/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl": "fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7", + "https://files.pythonhosted.org/packages/05/35/bb59b1cd012d7196fc81c2f5879113971efc226a63812c9cf7f89fe97c40/charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl": "5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40", + "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl": "42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", + "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl": "30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", + "https://files.pythonhosted.org/packages/0c/52/8b0c6c3e53f7e546a5e49b9edb876f379725914e1130297f3b423c7b71c5/charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl": "c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b", + "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl": "1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", + "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl": "2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", + "https://files.pythonhosted.org/packages/1a/79/ae516e678d6e32df2e7e740a7be51dc80b700e2697cb70054a0f1ac2c955/charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl": "3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b", + "https://files.pythonhosted.org/packages/20/30/5f64fe3981677fe63fa987b80e6c01042eb5ff653ff7cec1b7bd9268e54e/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl": "2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312", + "https://files.pythonhosted.org/packages/21/40/5188be1e3118c82dcb7c2a5ba101b783822cfb413a0268ed3be0468532de/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl": "cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe", + "https://files.pythonhosted.org/packages/22/82/63a45bfc36f73efe46731a3a71cb84e2112f7e0b049507025ce477f0f052/charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl": "0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c", + "https://files.pythonhosted.org/packages/2a/91/26c3036e62dfe8de8061182d33be5025e2424002125c9500faff74a6735e/charset_normalizer-3.4.3-cp310-cp310-win32.whl": "d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f", + "https://files.pythonhosted.org/packages/2f/36/77da9c6a328c54d17b960c89eccacfab8271fdaaa228305330915b88afa9/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl": "1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", + "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl": "cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", + "https://files.pythonhosted.org/packages/33/9e/eca49d35867ca2db336b6ca27617deed4653b97ebf45dfc21311ce473c37/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl": "78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a", + "https://files.pythonhosted.org/packages/37/60/5d0d74bc1e1380f0b72c327948d9c2aca14b46a9efd87604e724260f384c/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl": "07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601", + "https://files.pythonhosted.org/packages/39/c6/99271dc37243a4f925b09090493fb96c9333d7992c6187f5cfe5312008d2/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl": "23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e", + "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl": "86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", + "https://files.pythonhosted.org/packages/3a/a4/b3b6c76e7a635748c4421d2b92c7b8f90a432f98bda5082049af37ffc8e3/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl": "00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", + "https://files.pythonhosted.org/packages/3b/38/20a1f44e4851aa1c9105d6e7110c9d020e093dfa5836d712a5f074a12bf7/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl": "4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0", + "https://files.pythonhosted.org/packages/45/8c/dcef87cfc2b3f002a6478f38906f9040302c68aebe21468090e39cde1445/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl": "88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34", + "https://files.pythonhosted.org/packages/4c/92/27dbe365d34c68cfe0ca76f1edd70e8705d82b378cb54ebbaeabc2e3029d/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl": "939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", + "https://files.pythonhosted.org/packages/50/10/c117806094d2c956ba88958dab680574019abc0c02bcf57b32287afca544/charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl": "a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b", + "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl": "fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", + "https://files.pythonhosted.org/packages/59/c0/a74f3bd167d311365e7973990243f32c35e7a94e45103125275b9e6c479f/charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl": "252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4", + "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl": "1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", + "https://files.pythonhosted.org/packages/61/c5/dc3ba772489c453621ffc27e8978a98fe7e41a93e787e5e5bde797f1dddb/charset_normalizer-3.4.3-cp38-cp38-win32.whl": "ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557", + "https://files.pythonhosted.org/packages/61/f1/190d9977e0084d3f1dc169acd060d479bbbc71b90bf3e7bf7b9927dec3eb/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl": "96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", + "https://files.pythonhosted.org/packages/63/86/9cbd533bd37883d467fcd1bd491b3547a3532d0fbb46de2b99feeebf185e/charset_normalizer-3.4.3-cp39-cp39-win32.whl": "16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432", + "https://files.pythonhosted.org/packages/64/d1/f9d141c893ef5d4243bc75c130e95af8fd4bc355beff06e9b1e941daad6e/charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl": "5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a", + "https://files.pythonhosted.org/packages/64/d4/9eb4ff2c167edbbf08cdd28e19078bf195762e9bd63371689cab5ecd3d0d/charset_normalizer-3.4.3-cp311-cp311-win32.whl": "6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", + "https://files.pythonhosted.org/packages/65/1a/7425c952944a6521a9cfa7e675343f83fd82085b8af2b1373a2409c683dc/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl": "d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2", + "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl": "14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", + "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl": "18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", + "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl": "41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", + "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl": "d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", + "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl": "fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", + "https://files.pythonhosted.org/packages/7a/03/cbb6fac9d3e57f7e07ce062712ee80d80a5ab46614684078461917426279/charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl": "d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb", + "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl": "0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", + "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl": "53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", + "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl": "416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", + "https://files.pythonhosted.org/packages/7f/b5/991245018615474a60965a7c9cd2b4efbaabd16d582a5547c47ee1c7730b/charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl": "b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", + "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl": "3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", + "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz": "6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", + "https://files.pythonhosted.org/packages/85/9a/d891f63722d9158688de58d050c59dc3da560ea7f04f4c53e769de5140f5/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl": "74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c", + "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl": "30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", + "https://files.pythonhosted.org/packages/87/df/b7737ff046c974b183ea9aa111b74185ac8c3a326c6262d413bd5a1b8c69/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl": "0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", + "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl": "ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", + "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl": "3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", + "https://files.pythonhosted.org/packages/99/04/baae2a1ea1893a01635d475b9261c889a18fd48393634b6270827869fa34/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl": "fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", + "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl": "cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", + "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl": "18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", + "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl": "6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", + "https://files.pythonhosted.org/packages/a4/fa/384d2c0f57edad03d7bec3ebefb462090d8905b4ff5a2d2525f3bb711fac/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl": "02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0", + "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl": "027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", + "https://files.pythonhosted.org/packages/b0/a8/6f5bcf1bcf63cb45625f7c5cadca026121ff8a6c8a3256d8d8cd59302663/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl": "257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7", + "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl": "c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", + "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl": "320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", + "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl": "6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", + "https://files.pythonhosted.org/packages/c2/ca/9a0983dd5c8e9733565cf3db4df2b0a2e9a82659fd8aa2a868ac6e4a991f/charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl": "70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05", + "https://files.pythonhosted.org/packages/c4/72/d3d0e9592f4e504f9dea08b8db270821c909558c353dc3b457ed2509f2fb/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl": "1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19", + "https://files.pythonhosted.org/packages/c5/35/9c99739250742375167bc1b1319cd1cec2bf67438a70d84b2e1ec4c9daa3/charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl": "b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942", + "https://files.pythonhosted.org/packages/c7/2a/ae245c41c06299ec18262825c1569c5d3298fc920e4ddf56ab011b417efd/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl": "13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", + "https://files.pythonhosted.org/packages/ce/d6/7e805c8e5c46ff9729c49950acc4ee0aeb55efb8b3a56687658ad10c3216/charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl": "d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca", + "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl": "b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", + "https://files.pythonhosted.org/packages/d6/98/f3b8013223728a99b908c9344da3aa04ee6e3fa235f19409033eda92fb78/charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl": "fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72", + "https://files.pythonhosted.org/packages/e1/ef/dd08b2cac9284fd59e70f7d97382c33a3d0a926e45b15fc21b3308324ffd/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl": "511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc", + "https://files.pythonhosted.org/packages/e2/c6/f05db471f81af1fa01839d44ae2a8bfeec8d2a8b4590f16c4e7393afd323/charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl": "c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669", + "https://files.pythonhosted.org/packages/e2/e6/63bb0e10f90a8243c5def74b5b105b3bbbfb3e7bb753915fe333fb0c11ea/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl": "585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", + "https://files.pythonhosted.org/packages/e4/69/132eab043356bba06eb333cc2cc60c6340857d0a2e4ca6dc2b51312886b3/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl": "34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99", + "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl": "e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", + "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl": "73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", + "https://files.pythonhosted.org/packages/f0/c9/a2c9c2a355a8594ce2446085e2ec97fd44d323c684ff32042e2a6b718e1d/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl": "c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0", + "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl": "8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", + "https://files.pythonhosted.org/packages/f4/9c/996a4a028222e7761a96634d1820de8a744ff4327a00ada9c8942033089b/charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl": "31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", + "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl": "bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", + "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl": "c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018" + }, + "cryptography": { + "https://files.pythonhosted.org/packages/01/41/3a578f7fd5c70611c0aacba52cd13cb364a5dee895a5c1d467208a9380b0/cryptography-46.0.6-cp314-cp314t-macosx_10_9_universal2.whl": "2ef9e69886cbb137c2aef9772c2e7138dc581fad4fcbcf13cc181eb5a3ab6275", + "https://files.pythonhosted.org/packages/01/59/562be1e653accee4fdad92c7a2e88fced26b3fdfce144047519bbebc299e/cryptography-46.0.6-cp311-abi3-manylinux_2_31_armv7l.whl": "760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c", + "https://files.pythonhosted.org/packages/01/b3/0796998056a66d1973fd52ee89dc1bb3b6581960a91ad4ac705f182d398f/cryptography-46.0.6-cp38-abi3-manylinux_2_31_armv7l.whl": "02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70", + "https://files.pythonhosted.org/packages/09/0a/4fe7a8d25fed74419f91835cf5829ade6408fd1963c9eae9c4bce390ecbb/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_aarch64.whl": "8e7304c4f4e9490e11efe56af6713983460ee0780f16c63f219984dab3af9d2d", + "https://files.pythonhosted.org/packages/0a/09/ddc5f630cc32287d2c953fc5d32705e63ec73e37308e5120955316f53827/cryptography-46.0.6-cp38-abi3-win32.whl": "7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c", + "https://files.pythonhosted.org/packages/0f/a8/976acdd4f0f30df7b25605f4b9d3d89295351665c2091d18224f7ad5cdbf/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_ppc64le.whl": "3c21d92ed15e9cfc6eb64c1f5a0326db22ca9c2566ca46d845119b45b4400361", + "https://files.pythonhosted.org/packages/10/38/cd7864d79aa1d92ef6f1a584281433419b955ad5a5ba8d1eb6c872165bcb/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_x86_64.whl": "69cf0056d6947edc6e6760e5f17afe4bea06b56a9ac8a06de9d2bd6b532d4f3a", + "https://files.pythonhosted.org/packages/16/0b/b239701eb946523e4e9f329336e4ff32b1247e109cbab32d1a7b61da8ed7/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_aarch64.whl": "aad75154a7ac9039936d50cf431719a2f8d4ed3d3c277ac03f3339ded1a5e707", + "https://files.pythonhosted.org/packages/19/69/732a736d12c2631e140be2348b4ad3d226302df63ef64d30dfdb8db7ad1c/cryptography-46.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl": "9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a", + "https://files.pythonhosted.org/packages/1a/89/843b53614b47f97fe1abc13f9a86efa5ec9e275292c457af1d4a60dc80e0/cryptography-46.0.6-pp311-pypy311_pp73-win_amd64.whl": "6728c49e3b2c180ef26f8e9f0a883a2c585638db64cf265b49c9ba10652d430e", + "https://files.pythonhosted.org/packages/1b/82/ca4893968aeb2709aacfb57a30dec6fa2ab25b10fa9f064b8882ce33f599/cryptography-46.0.6-cp38-abi3-win_amd64.whl": "79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f", + "https://files.pythonhosted.org/packages/1d/5c/f6c3596a1430cec6f949085f0e1a970638d76f81c3ea56d93d564d04c340/cryptography-46.0.6-cp311-abi3-musllinux_1_2_aarch64.whl": "2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c", + "https://files.pythonhosted.org/packages/21/5e/19f3260ed1e95bced52ace7501fabcd266df67077eeb382b79c81729d2d3/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_ppc64le.whl": "ed418c37d095aeddf5336898a132fba01091f0ac5844e3e8018506f014b6d2c4", + "https://files.pythonhosted.org/packages/2e/84/7ccff00ced5bac74b775ce0beb7d1be4e8637536b522b5df9b73ada42da2/cryptography-46.0.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl": "2ea0f37e9a9cf0df2952893ad145fd9627d326a59daec9b0802480fa3bcd2ead", + "https://files.pythonhosted.org/packages/2f/97/daba0f5d2dc6d855e2dcb70733c812558a7977a55dd4a6722756628c44d1/cryptography-46.0.6-cp38-abi3-manylinux_2_28_aarch64.whl": "8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290", + "https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl": "22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97", + "https://files.pythonhosted.org/packages/44/46/466269e833f1c4718d6cd496ffe20c56c9c8d013486ff66b4f69c302a68d/cryptography-46.0.6-cp38-abi3-musllinux_1_2_x86_64.whl": "6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72", + "https://files.pythonhosted.org/packages/47/23/9285e15e3bc57325b0a72e592921983a701efc1ee8f91c06c5f0235d86d9/cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl": "64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8", + "https://files.pythonhosted.org/packages/49/b3/dc27efd8dcc4bff583b3f01d4a3943cd8b5821777a58b3a6a5f054d61b79/cryptography-46.0.6-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl": "639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8", + "https://files.pythonhosted.org/packages/5b/ba/d5e27f8d68c24951b0a484924a84c7cdaed7502bac9f18601cd357f8b1d2/cryptography-46.0.6-cp311-abi3-manylinux_2_28_ppc64le.whl": "d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463", + "https://files.pythonhosted.org/packages/5f/a0/7d738944eac6513cd60a8da98b65951f4a3b279b93479a7e8926d9cd730b/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_x86_64.whl": "b928a3ca837c77a10e81a814a693f2295200adb3352395fad024559b7be7a736", + "https://files.pythonhosted.org/packages/60/f8/e61f8f13950ab6195b31913b42d39f0f9afc7d93f76710f299b5ec286ae6/cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl": "26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30", + "https://files.pythonhosted.org/packages/7e/c9/9f9cea13ee2dbde070424e0c4f621c091a91ffcc504ffea5e74f0e1daeff/cryptography-46.0.6-cp311-abi3-musllinux_1_2_x86_64.whl": "380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f", + "https://files.pythonhosted.org/packages/89/06/fe1fce39a37ac452e58d04b43b0855261dac320a2ebf8f5260dd55b201a9/cryptography-46.0.6-cp38-abi3-manylinux_2_28_ppc64le.whl": "b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410", + "https://files.pythonhosted.org/packages/8b/65/5bf43286d566f8171917cae23ac6add941654ccf085d739195a4eacf1674/cryptography-46.0.6-cp38-abi3-manylinux_2_34_x86_64.whl": "341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58", + "https://files.pythonhosted.org/packages/91/e0/207fb177c3a9ef6a8108f234208c3e9e76a6aa8cf20d51932916bd43bda0/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_aarch64.whl": "c89eb37fae9216985d8734c1afd172ba4927f5a05cfd9bf0e4863c6d5465b013", + "https://files.pythonhosted.org/packages/9e/c5/e1594c4eec66a567c3ac4400008108a415808be2ce13dcb9a9045c92f1a0/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl": "90e5f0a7b3be5f40c3a0a0eafb32c681d8d2c181fc2a1bdabe9b3f611d9f6b1a", + "https://files.pythonhosted.org/packages/a4/ba/04b1bd4218cbc58dc90ce967106d51582371b898690f3ae0402876cc4f34/cryptography-46.0.6.tar.gz": "27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759", + "https://files.pythonhosted.org/packages/aa/a8/0a90c4f0b0871e0e3d1ed126aed101328a8a57fd9fd17f00fb67e82a51ca/cryptography-46.0.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl": "d24c13369e856b94892a89ddf70b332e0b70ad4a5c43cf3e9cb71d6d7ffa1f7b", + "https://files.pythonhosted.org/packages/ad/b5/1895bc0821226f129bc74d00eccfc6a5969e2028f8617c09790bf89c185e/cryptography-46.0.6-cp311-abi3-win32.whl": "bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2", + "https://files.pythonhosted.org/packages/b1/1b/bf0e01a88efd0e59679b69f42d4afd5bced8700bb5e80617b2d63a3741af/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_x86_64.whl": "4668298aef7cddeaf5c6ecc244c2302a2b8e40f384255505c22875eebb47888b", + "https://files.pythonhosted.org/packages/bb/8b/11df86de2ea389c65aa1806f331cae145f2ed18011f30234cc10ca253de8/cryptography-46.0.6-cp314-cp314t-manylinux_2_31_armv7l.whl": "8ce35b77aaf02f3b59c90b2c8a05c73bac12cea5b4e8f3fbece1f5fddea5f0ca", + "https://files.pythonhosted.org/packages/bc/1f/4c926f50df7749f000f20eede0c896769509895e2648db5da0ed55db711d/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl": "a3e84d5ec9ba01f8fd03802b2147ba77f0c8f2617b2aff254cedd551844209c8", + "https://files.pythonhosted.org/packages/c3/f8/c9bcbf0d3e6ad288b9d9aa0b1dee04b063d19e8c4f871855a03ab3a297ab/cryptography-46.0.6-cp311-abi3-win_amd64.whl": "6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124", + "https://files.pythonhosted.org/packages/c4/cc/f330e982852403da79008552de9906804568ae9230da8432f7496ce02b71/cryptography-46.0.6-cp38-abi3-macosx_10_9_universal2.whl": "12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a", + "https://files.pythonhosted.org/packages/c5/3d/db200af5a4ffd08918cd55c08399dc6c9c50b0bc72c00a3246e099d3a849/cryptography-46.0.6-cp38-abi3-manylinux_2_34_aarch64.whl": "7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d", + "https://files.pythonhosted.org/packages/c6/65/707be3ffbd5f786028665c3223e86e11c4cda86023adbc56bd72b1b6bab5/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl": "12f0fa16cc247b13c43d56d7b35287ff1569b5b1f4c5e87e92cc4fcc00cd10c0", + "https://files.pythonhosted.org/packages/c9/57/fe4a23eb549ac9d903bd4698ffda13383808ef0876cc912bcb2838799ece/cryptography-46.0.6-cp314-cp314t-win_amd64.whl": "c797e2517cb7880f8297e2c0f43bb910e91381339336f75d2c1c2cbf811b70b4", + "https://files.pythonhosted.org/packages/cb/f1/c2326781ca05208845efca38bf714f76939ae446cd492d7613808badedf1/cryptography-46.0.6-cp314-cp314t-win32.whl": "97c8115b27e19e592a05c45d0dd89c57f81f841cc9880e353e0d3bf25b2139ed", + "https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl": "67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175", + "https://files.pythonhosted.org/packages/d6/8b/b1ebfeb788bf4624d36e45ed2662b8bd43a05ff62157093c1539c1288a18/cryptography-46.0.6-cp311-abi3-manylinux_2_34_aarch64.whl": "3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507", + "https://files.pythonhosted.org/packages/d7/18/61acfd5b414309d74ee838be321c636fe71815436f53c9f0334bf19064fa/cryptography-46.0.6-cp38-abi3-manylinux_2_34_ppc64le.whl": "456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa", + "https://files.pythonhosted.org/packages/dd/52/a005f8eabdb28df57c20f84c44d397a755782d6ff6d455f05baa2785bd91/cryptography-46.0.6-cp311-abi3-manylinux_2_34_ppc64le.whl": "cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19", + "https://files.pythonhosted.org/packages/e0/25/7e49c0fa7205cf3597e525d156a6bce5b5c9de1fd7e8cb01120e459f205a/cryptography-46.0.6-cp38-abi3-musllinux_1_2_aarch64.whl": "9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb", + "https://files.pythonhosted.org/packages/e6/05/e8d0e6eb4f0d83365b3cb0e00eb3c484f7348db0266652ccd84632a3d58d/cryptography-46.0.6-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl": "ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77", + "https://files.pythonhosted.org/packages/ec/4d/8e7d7245c79c617d08724e2efa397737715ca0ec830ecb3c91e547302555/cryptography-46.0.6-cp311-abi3-manylinux_2_34_x86_64.whl": "d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738", + "https://files.pythonhosted.org/packages/f3/6d/73557ed0ef7d73d04d9aba745d2c8e95218213687ee5e76b7d236a5030fc/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl": "50575a76e2951fe7dbd1f56d181f8c5ceeeb075e9ff88e7ad997d2f42af06e7b", + "https://files.pythonhosted.org/packages/fa/87/887f35a6fca9dde90cad08e0de0c89263a8e59b2d2ff904fd9fcd8025b6f/cryptography-46.0.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl": "7f417f034f91dcec1cb6c5c35b07cdbb2ef262557f701b4ecd803ee8cefed4f4", + "https://files.pythonhosted.org/packages/ff/8a/b14f3101fe9c3592603339eb5d94046c3ce5f7fc76d6512a2d40efd9724e/cryptography-46.0.6-cp38-abi3-manylinux_2_28_x86_64.whl": "063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d" + }, + "docutils": { + "https://files.pythonhosted.org/packages/4a/c0/89fe6215b443b919cb98a5002e107cb5026854ed1ccb6b5833e0768419d1/docutils-0.22.2.tar.gz": "9fdb771707c8784c8f2728b67cb2c691305933d68137ef95a75db5f4dfbc213d", + "https://files.pythonhosted.org/packages/66/dd/f95350e853a4468ec37478414fc04ae2d61dad7a947b3015c3dcc51a09b9/docutils-0.22.2-py3-none-any.whl": "b0e98d679283fc3bb0ead8a5da7f501baa632654e7056e9c5846842213d674d8" + }, + "idna": { + "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl": "946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", + "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz": "12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9" + }, + "importlib-metadata": { + "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl": "e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", + "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz": "d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000" + }, + "jaraco-classes": { + "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz": "47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", + "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl": "f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790" + }, + "jaraco-context": { + "https://files.pythonhosted.org/packages/df/ad/f3777b81bf0b6e7bc7514a1656d3e637b2e8e15fab2ce3235730b3e7a4e6/jaraco_context-6.0.1.tar.gz": "9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", + "https://files.pythonhosted.org/packages/ff/db/0c52c4cf5e4bd9f5d7135ec7669a3a767af21b3a308e1ed3674881e52b62/jaraco.context-6.0.1-py3-none-any.whl": "f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4" + }, + "jaraco-functools": { + "https://files.pythonhosted.org/packages/b4/09/726f168acad366b11e420df31bf1c702a54d373a83f968d94141a8c3fde0/jaraco_functools-4.3.0-py3-none-any.whl": "227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8", + "https://files.pythonhosted.org/packages/f7/ed/1aa2d585304ec07262e1a83a9889880701079dde796ac7b1d1826f40c63d/jaraco_functools-4.3.0.tar.gz": "cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294" + }, + "jeepney": { + "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz": "cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", + "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl": "97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683" + }, + "keyring": { + "https://files.pythonhosted.org/packages/70/09/d904a6e96f76ff214be59e7aa6ef7190008f52a0ab6689760a98de0bf37d/keyring-25.6.0.tar.gz": "0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66", + "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl": "552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd" + }, + "markdown-it-py": { + "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz": "cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", + "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl": "87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147" + }, + "mdurl": { + "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl": "84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz": "bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" + }, + "more-itertools": { + "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl": "52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", + "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz": "f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd" + }, + "nh3": { + "https://files.pythonhosted.org/packages/0c/e0/cf1543e798ba86d838952e8be4cb8d18e22999be2a24b112a671f1c04fd6/nh3-0.3.0-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl": "ec6cfdd2e0399cb79ba4dcffb2332b94d9696c52272ff9d48a630c5dca5e325a", + "https://files.pythonhosted.org/packages/10/71/2fb1834c10fab6d9291d62c95192ea2f4c7518bd32ad6c46aab5d095cb87/nh3-0.3.0-cp313-cp313t-musllinux_1_2_i686.whl": "0649464ac8eee018644aacbc103874ccbfac80e3035643c3acaab4287e36e7f5", + "https://files.pythonhosted.org/packages/23/1e/80a8c517655dd40bb13363fc4d9e66b2f13245763faab1a20f1df67165a7/nh3-0.3.0-cp313-cp313t-win_amd64.whl": "423201bbdf3164a9e09aa01e540adbb94c9962cc177d5b1cbb385f5e1e79216e", + "https://files.pythonhosted.org/packages/2f/d6/f1c6e091cbe8700401c736c2bc3980c46dca770a2cf6a3b48a175114058e/nh3-0.3.0-cp313-cp313t-win32.whl": "7275fdffaab10cc5801bf026e3c089d8de40a997afc9e41b981f7ac48c5aa7d5", + "https://files.pythonhosted.org/packages/33/c1/8f8ccc2492a000b6156dce68a43253fcff8b4ce70ab4216d08f90a2ac998/nh3-0.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl": "1adeb1062a1c2974bc75b8d1ecb014c5fd4daf2df646bbe2831f7c23659793f9", + "https://files.pythonhosted.org/packages/39/2c/6394301428b2017a9d5644af25f487fa557d06bc8a491769accec7524d9a/nh3-0.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl": "f416c35efee3e6a6c9ab7716d9e57aa0a49981be915963a82697952cba1353e1", + "https://files.pythonhosted.org/packages/4c/3c/cba7b26ccc0ef150c81646478aa32f9c9535234f54845603c838a1dc955c/nh3-0.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl": "80fe20171c6da69c7978ecba33b638e951b85fb92059259edd285ff108b82a6d", + "https://files.pythonhosted.org/packages/4e/9a/344b9f9c4bd1c2413a397f38ee6a3d5db30f1a507d4976e046226f12b297/nh3-0.3.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl": "37d3003d98dedca6cd762bf88f2e70b67f05100f6b949ffe540e189cc06887f9", + "https://files.pythonhosted.org/packages/5b/76/3165e84e5266d146d967a6cc784ff2fbf6ddd00985a55ec006b72bc39d5d/nh3-0.3.0-cp38-abi3-win_arm64.whl": "d97d3efd61404af7e5721a0e74d81cdbfc6e5f97e11e731bb6d090e30a7b62b2", + "https://files.pythonhosted.org/packages/5c/86/a96b1453c107b815f9ab8fac5412407c33cc5c7580a4daf57aabeb41b774/nh3-0.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl": "ce5e7185599f89b0e391e2f29cc12dc2e206167380cea49b33beda4891be2fe1", + "https://files.pythonhosted.org/packages/63/da/c5fd472b700ba37d2df630a9e0d8cc156033551ceb8b4c49cc8a5f606b68/nh3-0.3.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl": "ba0caa8aa184196daa6e574d997a33867d6d10234018012d35f86d46024a2a95", + "https://files.pythonhosted.org/packages/66/3f/cd37f76c8ca277b02a84aa20d7bd60fbac85b4e2cbdae77cb759b22de58b/nh3-0.3.0-cp38-abi3-musllinux_1_2_aarch64.whl": "634e34e6162e0408e14fb61d5e69dbaea32f59e847cfcfa41b66100a6b796f62", + "https://files.pythonhosted.org/packages/6a/1b/b15bd1ce201a1a610aeb44afd478d55ac018b4475920a3118ffd806e2483/nh3-0.3.0-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl": "e9e6a7e4d38f7e8dda9edd1433af5170c597336c1a74b4693c5cb75ab2b30f2a", + "https://files.pythonhosted.org/packages/8c/ae/324b165d904dc1672eee5f5661c0a68d4bab5b59fbb07afb6d8d19a30b45/nh3-0.3.0-cp38-abi3-win_amd64.whl": "bae63772408fd63ad836ec569a7c8f444dd32863d0c67f6e0b25ebbd606afa95", + "https://files.pythonhosted.org/packages/8f/14/079670fb2e848c4ba2476c5a7a2d1319826053f4f0368f61fca9bb4227ae/nh3-0.3.0-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl": "7852f038a054e0096dac12b8141191e02e93e0b4608c4b993ec7d4ffafea4e49", + "https://files.pythonhosted.org/packages/97/03/03f79f7e5178eb1ad5083af84faff471e866801beb980cc72943a4397368/nh3-0.3.0-cp38-abi3-musllinux_1_2_i686.whl": "c7a32a7f0d89f7d30cb8f4a84bdbd56d1eb88b78a2434534f62c71dac538c450", + "https://files.pythonhosted.org/packages/97/33/11e7273b663839626f714cb68f6eb49899da5a0d9b6bc47b41fe870259c2/nh3-0.3.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl": "389d93d59b8214d51c400fb5b07866c2a4f79e4e14b071ad66c92184fec3a392", + "https://files.pythonhosted.org/packages/9a/e0/af86d2a974c87a4ba7f19bc3b44a8eaa3da480de264138fec82fe17b340b/nh3-0.3.0-cp313-cp313t-win_arm64.whl": "16f8670201f7e8e0e05ed1a590eb84bfa51b01a69dd5caf1d3ea57733de6a52f", + "https://files.pythonhosted.org/packages/a3/e5/ac7fc565f5d8bce7f979d1afd68e8cb415020d62fa6507133281c7d49f91/nh3-0.3.0-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl": "af5aa8127f62bbf03d68f67a956627b1bd0469703a35b3dad28d0c1195e6c7fb", + "https://files.pythonhosted.org/packages/ad/7f/7c6b8358cf1222921747844ab0eef81129e9970b952fcb814df417159fb9/nh3-0.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl": "7c915060a2c8131bef6a29f78debc29ba40859b6dbe2362ef9e5fd44f11487c2", + "https://files.pythonhosted.org/packages/b4/11/340b7a551916a4b2b68c54799d710f86cf3838a4abaad8e74d35360343bb/nh3-0.3.0-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl": "a537ece1bf513e5a88d8cff8a872e12fe8d0f42ef71dd15a5e7520fecd191bbb", + "https://files.pythonhosted.org/packages/c3/a4/96cff0977357f60f06ec4368c4c7a7a26cccfe7c9fcd54f5378bf0428fd3/nh3-0.3.0.tar.gz": "d8ba24cb31525492ea71b6aac11a4adac91d828aadeff7c4586541bf5dc34d2f", + "https://files.pythonhosted.org/packages/c9/50/76936ec021fe1f3270c03278b8af5f2079038116b5d0bfe8538ffe699d69/nh3-0.3.0-cp38-abi3-win32.whl": "6d68fa277b4a3cf04e5c4b84dd0c6149ff7d56c12b3e3fab304c525b850f613d", + "https://files.pythonhosted.org/packages/ce/55/1974bcc16884a397ee699cebd3914e1f59be64ab305533347ca2d983756f/nh3-0.3.0-cp38-abi3-musllinux_1_2_x86_64.whl": "3f1b4f8a264a0c86ea01da0d0c390fe295ea0bcacc52c2103aca286f6884f518", + "https://files.pythonhosted.org/packages/ee/db/7aa11b44bae4e7474feb1201d8dee04fabe5651c7cb51409ebda94a4ed67/nh3-0.3.0-cp38-abi3-musllinux_1_2_armv7l.whl": "b0612ccf5de8a480cf08f047b08f9d3fecc12e63d2ee91769cb19d7290614c23", + "https://files.pythonhosted.org/packages/f3/ba/59e204d90727c25b253856e456ea61265ca810cda8ee802c35f3fadaab00/nh3-0.3.0-cp313-cp313t-musllinux_1_2_armv7l.whl": "e90883f9f85288f423c77b3f5a6f4486375636f25f793165112679a7b6363b35" + }, + "pkginfo": { + "https://files.pythonhosted.org/packages/24/03/e26bf3d6453b7fda5bd2b84029a426553bb373d6277ef6b5ac8863421f87/pkginfo-1.12.1.2.tar.gz": "5cd957824ac36f140260964eba3c6be6442a8359b8c48f4adf90210f33a04b7b", + "https://files.pythonhosted.org/packages/fa/3d/f4f2ba829efb54b6cd2d91349c7463316a9cc55a43fc980447416c88540f/pkginfo-1.12.1.2-py3-none-any.whl": "c783ac885519cab2c34927ccfa6bf64b5a704d7c69afaea583dd9b7afe969343" + }, + "pycparser": { + "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl": "e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", + "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz": "78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2" + }, + "pygments": { + "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz": "636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl": "86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + }, + "pywin32-ctypes": { + "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz": "d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", + "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl": "8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8" + }, + "readme-renderer": { + "https://files.pythonhosted.org/packages/5a/a9/104ec9234c8448c4379768221ea6df01260cd6c2ce13182d4eac531c8342/readme_renderer-44.0.tar.gz": "8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1", + "https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl": "2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151" + }, + "requests": { + "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz": "c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", + "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl": "3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b" + }, + "requests-toolbelt": { + "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl": "cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", + "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz": "7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6" + }, + "rfc3986": { + "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz": "97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", + "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl": "50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd" + }, + "rich": { + "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl": "536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", + "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz": "e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8" + }, + "secretstorage": { + "https://files.pythonhosted.org/packages/53/a4/f48c9d79cb507ed1373477dbceaba7401fd8a23af63b837fa61f1dcd3691/SecretStorage-3.3.3.tar.gz": "2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", + "https://files.pythonhosted.org/packages/54/24/b4293291fa1dd830f353d2cb163295742fa87f179fcc8a20a306a81978b7/SecretStorage-3.3.3-py3-none-any.whl": "f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99" + }, + "twine": { + "https://files.pythonhosted.org/packages/5d/ec/00f9d5fd040ae29867355e559a94e9a8429225a0284a3f5f091a3878bfc0/twine-5.1.1-py3-none-any.whl": "215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997", + "https://files.pythonhosted.org/packages/77/68/bd982e5e949ef8334e6f7dcf76ae40922a8750aa2e347291ae1477a4782b/twine-5.1.1.tar.gz": "9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db" + }, + "urllib3": { + "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl": "bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", + "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz": "1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed" + }, + "zipp": { + "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl": "071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", + "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz": "a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166" + } + } + }, + "fact_version": "v1", + "index_urls": { + "https://pypi.org/simple/": { + "backports_tarfile": "/simple/backports-tarfile/", + "certifi": "/simple/certifi/", + "cffi": "/simple/cffi/", + "charset_normalizer": "/simple/charset-normalizer/", + "cryptography": "/simple/cryptography/", + "docutils": "/simple/docutils/", + "idna": "/simple/idna/", + "importlib_metadata": "/simple/importlib-metadata/", + "jaraco_classes": "/simple/jaraco-classes/", + "jaraco_context": "/simple/jaraco-context/", + "jaraco_functools": "/simple/jaraco-functools/", + "jeepney": "/simple/jeepney/", + "keyring": "/simple/keyring/", + "markdown_it_py": "/simple/markdown-it-py/", + "mdurl": "/simple/mdurl/", + "more_itertools": "/simple/more-itertools/", + "nh3": "/simple/nh3/", + "pkginfo": "/simple/pkginfo/", + "pycparser": "/simple/pycparser/", + "pygments": "/simple/pygments/", + "pywin32_ctypes": "/simple/pywin32-ctypes/", + "readme_renderer": "/simple/readme-renderer/", + "requests": "/simple/requests/", + "requests_toolbelt": "/simple/requests-toolbelt/", + "rfc3986": "/simple/rfc3986/", + "rich": "/simple/rich/", + "secretstorage": "/simple/secretstorage/", + "twine": "/simple/twine/", + "urllib3": "/simple/urllib3/", + "zipp": "/simple/zipp/" + } + } + } + } }
diff --git a/WORKSPACE.bzlmod b/WORKSPACE.bzlmod deleted file mode 100644 index 004dafb..0000000 --- a/WORKSPACE.bzlmod +++ /dev/null
@@ -1,151 +0,0 @@ -# npm packages are split into different node_modules directories based on their -# usage. -# 1. @npm (node_modules) - contains packages to run tests, check code, etc... -# It is expected that @npm is used ONLY to run tools. No packages from @npm -# are used by other code in gerrit. -# 2. @tools_npm (tools/node_tools/node_modules) - the tools/node_tools folder -# contains self-written tools which are run for building and/or testing. The -# @tools_npm directory contains all the packages needed to run this tools. -# 3. @ui_npm (polygerrit-ui/app/node_modules) - packages with source code which -# are necessary to run polygerrit and to bundle it. Only code from these -# packages can be included in the final bundle for polygerrit. @ui_npm folder -# must not have devDependencies. All devDependencies must be placed in -# @ui_dev_npm. -# 4. @ui_dev_npm (polygerrit-ui/node_modules) - devDependencies for polygerrit. -# The packages from these folder can be used for testing, but must not be -# included in the final bundle. -# 5. @plugins_npm (plugins/node_modules) - plugin dependencies for polygerrit -# plugins. The packages here are expected to be used in plugins. -# Note: separation between @ui_npm and @ui_dev_npm is necessary because with -# rules_nodejs we can't generate two external repositories from the same -# package.json. At the same time we want to avoid accidental usages of code -# from devDependencies in polygerrit bundle. -workspace( - name = "gerrit", -) - -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -load("//plugins:external_plugin_deps.bzl", "external_plugin_deps") - -# Gerrit-specific patched rules_nodejs 5.8.5 release artifact. -# Windows-specific paths are disabled since Gerrit only supports Linux -# and macOS builders. -http_archive( - name = "build_bazel_rules_nodejs", - sha256 = "cd4f06efb688067a2a891c9b5647184b12708dcff36bebabeddce666f5422e7f", - urls = [ - "https://github.com/davido/rules_nodejs/releases/download/v5.8.5-unixonly/rules_nodejs-5.8.5-gerrit.tar.gz", - ], -) - -load("@build_bazel_rules_nodejs//:repositories.bzl", "build_bazel_rules_nodejs_dependencies") - -build_bazel_rules_nodejs_dependencies() - -# This is required just because we have a dependency on @bazel/concatjs. -# We don't actually use any of this web_testing stuff. -# TODO: Remove this dependency. -http_archive( - name = "io_bazel_rules_webtesting", - sha256 = "e9abb7658b6a129740c0b3ef6f5a2370864e102a5ba5ffca2cea565829ed825a", - urls = [ - "https://github.com/bazelbuild/rules_webtesting/releases/download/0.3.5/rules_webtesting.tar.gz", - ], -) - -# TODO: Remove this, see comments on `io_bazel_rules_webtesting`. -load("@io_bazel_rules_webtesting//web:repositories.bzl", "web_test_repositories") - -# TODO: Remove this, see comments on `io_bazel_rules_webtesting`. -web_test_repositories() - -# TODO: Remove this, see comments on `io_bazel_rules_webtesting`. -load("@io_bazel_rules_webtesting//web/versioned:browsers-0.3.3.bzl", "browser_repositories") - -# TODO: Remove this, see comments on `io_bazel_rules_webtesting`. -browser_repositories( - chromium = True, - firefox = True, -) - -load("@build_bazel_rules_nodejs//:index.bzl", "node_repositories", "yarn_install") - -NODE_20_REPO = { - "20.19.5-darwin_arm64": ("node-v20.19.5-darwin-arm64.tar.gz", "node-v20.19.5-darwin-arm64", "cfed7503d8d99fbcf2f52e408ec52f616058eb0867b34dbc3437259993ef5cba"), - "20.19.5-darwin_amd64": ("node-v20.19.5-darwin-x64.tar.gz", "node-v20.19.5-darwin-x64", "f9cff058f2766d4d0631dc69b5f7f27664b3a42ff186e25ac7e1ac269af7e696"), - "20.19.5-linux_arm64": ("node-v20.19.5-linux-arm64.tar.xz", "node-v20.19.5-linux-arm64", "d462267863ae8ee556039ebdf559055a8ec562c633889ef1403f3adb449ba1dd"), - "20.19.5-linux_ppc64le": ("node-v20.19.5-linux-ppc64le.tar.xz", "node-v20.19.5-linux-ppc64le", "ef98025e71d6d498476a95f144e353be074b24431b22eaa81bc64f921ea7d57f"), - "20.19.5-linux_s390x": ("node-v20.19.5-linux-s390x.tar.xz", "node-v20.19.5-linux-s390x", "a2e56c4b7fbffd0e6eef3a89e1c5945962fe85b4e2acfa59edc77a9238cc7901"), - "20.19.5-linux_amd64": ("node-v20.19.5-linux-x64.tar.xz", "node-v20.19.5-linux-x64", "315046739a513a70e03a4a55a8afda8cf979f30852e576075c340084e3f8ac0f"), - "20.19.5-windows_amd64": ("node-v20.19.5-win-x64.zip", "node-v20.19.5-win-x64", "c48159529572a5a947eef2d55d6485dfdc4ce8e67216402e2f6de52ad5d95695"), -} - -node_repositories( - node_repositories = NODE_20_REPO, - node_version = "20.19.5", - yarn_version = "1.22.19", -) - -yarn_install( - name = "npm", - exports_directories_only = False, - frozen_lockfile = False, - package_json = "//:package.json", - package_path = "", - symlink_node_modules = True, - yarn_lock = "//:yarn.lock", -) - -yarn_install( - name = "ui_npm", - args = [ - "--prod", - # By default, yarn install all optional dependencies. - # In some cases, it installs a lot of additional dependencies which - # are not required (for example, "resemblejs" has one optional - # dependencies "canvas" that leads to tens of additional dependencies). - # Each additional dependency requires a license even if it is not used - # in our code. We want to ensure that all optional dependencies are - # explicitly added to package.json. - "--ignore-optional", - ], - exports_directories_only = False, - frozen_lockfile = False, - package_json = "//:polygerrit-ui/app/package.json", - package_path = "polygerrit-ui/app", - symlink_node_modules = True, - yarn_lock = "//:polygerrit-ui/app/yarn.lock", -) - -yarn_install( - name = "ui_dev_npm", - exports_directories_only = False, - frozen_lockfile = False, - package_json = "//:polygerrit-ui/package.json", - package_path = "polygerrit-ui", - symlink_node_modules = True, - yarn_lock = "//:polygerrit-ui/yarn.lock", -) - -yarn_install( - name = "tools_npm", - exports_directories_only = False, - frozen_lockfile = False, - package_json = "//:tools/node_tools/package.json", - package_path = "tools/node_tools", - symlink_node_modules = True, - yarn_lock = "//:tools/node_tools/yarn.lock", -) - -yarn_install( - name = "plugins_npm", - args = ["--prod"], - exports_directories_only = False, - frozen_lockfile = False, - package_json = "//:plugins/package.json", - package_path = "plugins", - symlink_node_modules = True, - yarn_lock = "//:plugins/yarn.lock", -) - -external_plugin_deps()
diff --git a/antlr3/BUILD b/antlr3/BUILD index 23641e3..3236d31 100644 --- a/antlr3/BUILD +++ b/antlr3/BUILD
@@ -1,5 +1,5 @@ load("@rules_java//java:defs.bzl", "java_library") -load("//tools/bzl:genrule2.bzl", "genrule2") +load("@com_googlesource_gerrit_bazlets//tools:genrule2.bzl", "genrule2") genrule2( name = "query",
diff --git a/configs/agent_configs.textproto b/configs/agent_configs.textproto new file mode 100644 index 0000000..08f2602 --- /dev/null +++ b/configs/agent_configs.textproto
@@ -0,0 +1,64 @@ +# Frontend Engineering Agent +configs: { + id: "gerrit-frontend-engineering" + display_name: "Gerrit Frontend Engineering" + description: "Analyzes Polygerrit UI, frontend TypeScript/JavaScript files, and UI elements for architecture and style correctness." + skills: "gerrit_frontend_engineering" + include_filters: { + project: "gerrit" + path_regex: "polygerrit-ui/.*" + } + automatic: true +} + +# System Logic Agent +configs: { + id: "gerrit-system-logic" + display_name: "Gerrit System Logic & Correctness" + description: "Analyzes Gerrit backend logic, Java sources, database transactions, and correctness rules." + skills: "gerrit_system_logic" + include_filters: { + project: "gerrit" + path_regex: ".*\\.java" + } + automatic: true +} + +# Hygiene & Operations Agent +configs: { + id: "gerrit-hygiene-operations" + display_name: "Gerrit Hygiene & Operations" + description: "Analyzes code hygiene, formatting, release preparation, and downstream dependencies." + skills: "gerrit_hygiene_operations" + include_filters: { + project: "gerrit" + } + automatic: true +} + +# Commit Message Reviewer Agent +configs: { + id: "gerrit-commit-message-review" + display_name: "Gerrit Commit Message Reviewer" + description: "Reviews the CL commit message for correctness, grammar, formatting compliance, and clarity." + skills: "gerrit_commit_message_review" + include_filters: { + project: "gerrit" + path_regex: "^/?COMMIT_MSG$" + } + automatic: true +} + +# TypeScript Style Review Agent +configs: { + id: "gerrit-typescript-style-review" + display_name: "TypeScript Style Review" + description: "Checks TypeScript files against style guide, focusing on property visibility and bypasses." + skills: "typescript_style_review" + include_filters: { + project: "gerrit" + project: "TestRepo" + path_regex: ".*\\.ts$" + } +} +
diff --git a/configs/skills/gerrit_commit_message_review/SKILL.md b/configs/skills/gerrit_commit_message_review/SKILL.md new file mode 100644 index 0000000..46448f8 --- /dev/null +++ b/configs/skills/gerrit_commit_message_review/SKILL.md
@@ -0,0 +1,502 @@ +--- +name: gerrit-commit-message-review +description: Proofreads and suggests structural improvements for Git commit messages to ensure style guide compliance, completeness, and accuracy. +--- + +# Git Commit Message & Metadata Standards + +## Executive Summary + +Welcome to the definitive engineering reference for formatting, structuring, and +preserving metadata inside Git commit messages. This document encapsulates the +core conventions for commit hygiene, which are essential to ensuring commit log +histories remain clean, readable, and highly traceable across development +lifecycles. + +Adhering to these standards ensures metadata traceability, and provides clear, +long-term context to future developers. + +## Summary + +| Chapter Theme / Title | Scope & Objective | +| :--- | :--- | +| **Commit Title Conventions** | Defines stylistic and length requirements for the first line of the commit message to optimize history navigation. | +| **Commit Body Structure & Formatting** | Outlines instructions to clearly explain the "what" and "why" of the patchset with pragmatic conciseness and precise wrapping. | +| **Metadata Footers & Preservations** | Enforces the strict preservation of system-critical integration footers (such as Change-Id and issue tracking IDs). | +| **Review Feedback & Suggested Commit Message** | Instructs the reviewer to provide a complete, fully-compliant, copy-pasteable revised commit message. | + + +-------------------------------------------------------------------------------- + +## Chapter: Commit Title Conventions + +**Context:** The title line of a Git commit message is the first line of visual feedback for engineers navigating repository logs. To ensure standard sizing, clarity, and readability, title structures are subject to rigid constraints. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :--- | :--- | :--- | :--- | +| **T1-01** | Concise & Imperative Commit Titles | High | Writing titles exceeding 60 characters, or utilizing past-tense/progressive verbs (e.g. "Fixed...", "Fixing..."). | + +### Rules + +#### T1-01: Concise & Imperative Commit Titles + +> **Rule:** Commit titles must be 60 characters or less, start with an imperative verb (e.g., "Add", "Fix", "Update", "Remove"), and use sentence case without trailing punctuation. +> +> **What:** The commit title line must be a concise, imperative sentence summary strictly 60 characters or less. +> +> **Applies To:** Git commit message first line. +> +> **Why:** The codebase's core validation rules programmatically block and flag commits with subjects exceeding 60 characters. Keeping the title under this strict limit avoids repository presubmit upload blockages and ensures neat display in CLI tools. + +**Trap 1: Writing passive, overly long, or descriptive titles using progressive or past tense.** + +**Don't:** +```text +Fixing the loading spinner bug in gr-reply-dialog.ts and adding tests +``` + +**Do:** +```text +Fix loading spinner and add test coverage +``` + +-------------------------------------------------------------------------------- + +## Chapter: Commit Body Structure & Formatting + +**Context:** The body of a commit is a vital repository asset storing the architectural intent behind a change. It must explain engineering decisions with pragmatic conciseness, provide targeted context, and be wrapped strictly for terminal compatibility. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :--- | :--- | :--- | :--- | +| **T2-01** | Explaining the Context: What and Why | High | Omitting commit bodies entirely, repeating the title, describing "how" instead of "why", or leaving critical design/bug links without context. | +| **T2-02** | Strict Line Wrapping at 72 Characters | High | Writing continuous, single-line paragraphs that span past 72 characters, causing awkward wrapping in console windows. | +| **T2-03** | Pragmatic Tone, Conciseness, and Anti-Filler | High | Writing verbose, flowery prose, introducing generic engineering philosophy/boilerplate, or using redundant Q&A layouts on simple changes. | + +-------------------------------------------------------------------------------- + +### Rules + +#### T2-01: Explaining the Context: What and Why + +> **Rule:** The commit message body must clearly explain *what* changes were made and *why* they were necessary, focusing on context and architectural intent, while maintaining a concise, non-redundant, and pragmatic tone. The body must jump directly into the technical context or problem and must never repeat, rephrase, or start with a high-level introductory summary of the title. For complex, security-sensitive, or high-risk changes, the explanation should explicitly ground the "why" by referencing the relevant issue, bug tracking ID, or design/RFC document. Do not include empty, low-value, or vacuous bug references (e.g. 'To address b/XXXXX, ...') in the body text unless accompanied by descriptive context explaining what the bug or issue represents. If the specific bug context is not known or cannot be verified, the bug reference must be omitted from the body paragraphs entirely, relying solely on the metadata footer for tracking. +> +> **What:** Explanations in the body must detail the problem and the rationale for the solution, leaving the mechanical "how" to be read from the code diff, and omitting generic value propositions of development practices, non-technical fluff, or introductory meta-sentences. The opening paragraph of the body must begin directly with the context or problem being solved and not restate or paraphrase the commit title. If the commit relates to a complex problem or implements an approved design specification, the body should draw from and cite these linked resources to clarify the reasoning in a direct, straightforward manner. If citing a bug tracker ID in the body paragraphs, ensure it adds concrete value by describing what problem or feedback is tracked there; otherwise, leave the bug reference out of the narrative body and let the footer handle the link. +> +> **Applies To:** Commit message lines following the spacer blank line (line 3 and onward). +> +> **Why:** Obvious code listings are redundant. Context is key: for complex or sensitive engineering changes, subsequent maintainers must understand the origin of a requirement or design constraint (e.g., a specific bug, CVE, or design specification) without having to guess, establishing clear auditability. + +**Trap 1: Repeating or rephrasing the title, starting the body with a high-level introductory summary sentence, or omitting concrete context.** + +The commit title is already the high-level summary of the change. Starting the body of the commit message with a rephrasing, restatement, or introductory "thesis statement" (e.g. "Add the agent and define its corresponding guidelines") is highly redundant and wastes reader time. Do not write introductory meta-sentences; begin the body paragraphs by jumping directly into the technical context or the problem being solved. + +**Don't:** +```text +Fix loading spinner and add test coverage + +This change fixes the loading spinner and adds test coverage to gr-reply-dialog.ts. +``` +*(Problem: Repeats the title almost verbatim in the body opening.)* + +**Do:** +```text +Fix loading spinner and add test coverage + +The loading spinner in gr-reply-dialog was experiencing visual jitter +on rapid page transitions due to a race condition in the reactive +lifecycle hook. + +This change moves property assignments out of firstUpdated to avoid +unnecessary second-pass rendering, stabilizing the visual state. +``` +*(Rationale: Jumps immediately into the concrete problem.)* + +**Trap 2: Omitting context from linked bugs or design documents in complex, critical, or security-sensitive changes.** + +While simple or minor bug fixes do not need to explicitly reference their issue IDs inside the body, major, high-risk, or architectural modifications that reference external specs, RFCs, or bug tracker tickets should integrate that context into the "why" explanation. Failing to do so makes the commit message look disconnected from its metadata, making review and auditability difficult. + +**Don't:** +```text +Enhance project deletion permission validation + +Only users with administrative privileges are allowed to delete a +project, but the server was previously checking for owner status only. +This change corrects the check to require system administrator scope. + +Bug: gerrit:40012901 +``` +*(Problem: A security-sensitive permission model change is being made under a bug, but the body only explains the mechanical change. It completely misses the security context—like the permission bypass mentioned in the bug report—making the reasoning for this risk-heavy change unclear without looking up the bug.)* + +**Do:** +```text +Enhance project deletion permission validation + +To resolve the permission bypass reported in gerrit:40012901, where +project owners could bypass global security policies to delete resource +containers, we must restrict deletion calls to administrators. + +As defined in the project deletion security spec (https://example.com/gerrit-delete-spec), +only system-level administrators should have the capability to +destroy project repositories in production environments. + +Bug: gerrit:40012901 +``` + +**Trap 3: Adding vacuous or low-value bug/issue references to the commit body.** + +Do not introduce generic, filler sentences or phrases (like "To address b/12345...", "In reference to b/12345...", or "As requested in b/12345...") into the narrative body of a commit message unless you are actually adding descriptive, valuable context from that bug or design document. If the bug context is not known, or if you cannot visit/verify the bug contents, **omit** the reference from the body text entirely. The metadata footer at the bottom (e.g., `Bug: b/12345` or `Google-Bug-ID: b/12345`) is the correct place to handle automated tracking; adding a vacuous mention in the body adds zero explanatory value and only increases noise. + +**Don't (Vacuous reference in body):** +```text +Suggest full commit message and avoid pedantic reviews + +To address b/505405738, this change updates the review guidelines to +require that commit message reviews provide a complete, copy-pasteable +revised message when formatting issues are found. It also instructs the +reviewer to avoid pedantic feedback on minor casing or phrasing if the +original message is already clear and compliant. + +Google-Bug-ID: b/505405738 +Release-Notes: skip +Change-Id: I75ef56099ea36b8838a65746abb3a4771fcefd23 +``` +*(Problem: The body paragraph starts with "To address b/505405738, ...", but this phrase does not explain what the bug represents or what context it provides. It is a low-value, mechanical filler phrase.)* + +**Do (When bug context is unknown - omit the reference from the body):** +```text +Suggest full commit message and avoid pedantic reviews + +Automated commit message reviews can sometimes generate fragmented, +pedantic feedback on minor phrasing nits, creating friction rather than +saving developer time. + +To improve usability, SKILL.md now requires reviewers to generate a +complete, copy-pasteable revised commit message when formatting issues +are found, and to suppress feedback on minor nits if the original +commit is fundamentally sound. + +Google-Bug-ID: b/505405738 +Release-Notes: skip +Change-Id: I75ef56099ea36b8838a65746abb3a4771fcefd23 +``` +*(Rationale: Since the bug context is not explicitly visited or verified, the vacuous reference is completely removed from the body text, and the body jumps directly into the problem context first rather than starting with a generic "This change..." statement.)* + +**Do (When bug context is visited/known - integrate descriptive context):** +```text +Suggest full commit message and avoid pedantic reviews + +In b/505405738, developers reported being slowed down by fragmented, +pedantic automated review feedback and requested ready-to-apply +suggestions to reduce manual editing friction. + +To address this, this change updates the guidelines to require a +complete, copy-pasteable revised message when formatting issues are +found, and instructs reviewers to tolerate minor casing or phrasing +variations when the original message is already compliant. + +Google-Bug-ID: b/505405738 +Release-Notes: skip +Change-Id: I75ef56099ea36b8838a65746abb3a4771fcefd23 +``` +*(Rationale: The bug reference in the body now adds actual value by explaining exactly what issue/feedback was reported in b/505405738, providing important context to future maintainers, while explaining the problem/need before the solution.)* +``` +*(Rationale: Since this is a high-risk change (a security bypass), the body explicitly connects to the bug report gerrit:40012901 and links the authoritative security spec (e.g., https://example.com/gerrit-delete-spec). This establishes bulletproof reasoning and traceability for a critical modification.)* + +-------------------------------------------------------------------------------- + +#### T2-02: Strict Line Wrapping at 72 Characters + +> **Rule:** Wrap the body of all commit messages strictly at 72 characters per line, except for unwrappable URLs, file paths, or commands. +> +> **What:** Lines in the commit body must have explicit carriage returns at or before 72 columns. +> +> **Applies To:** Git commit message bodies. +> +> **Why:** Terminal output screens wrap at standard columns. Explicitly wrapping to 72 characters ensures clean reading in simple text editors, CLI viewers, and patch viewers. + +**Trap 1: Appending full paragraphs without manual word wrapping.** + +**Don't:** +```text +This change refactors the core caching helper and resolves a race condition that occurs when the same component gets disconnected rapidly from the DOM during teardown, which historically resulted in an uncaught exception. +``` + +**Do:** +```text +This change refactors the core caching helper and resolves a race +condition that occurs when the same component gets disconnected +rapidly from the DOM during teardown, which historically resulted +in an uncaught exception. +``` + +-------------------------------------------------------------------------------- + +#### T2-03: Pragmatic Tone, Conciseness, and Anti-Filler + +> **Rule:** Commit messages must be concise, direct, and free of conversational filler, obvious generalities, marketing/PR speak, or excessive boilerplate structure. Every sentence must serve to communicate technical context. +> +> **What:** Avoid long-winded introductions (e.g. "To ensure standard high-quality..."), platitudes (e.g. "Commit messages are vital repositories of engineering intent..."), and rigid Q&A-style templates (like "What is changing / Why this is necessary" headers) unless the change is highly complex and structurally demands them. Keep explanations simple, direct, and focused on the technical problem and its solution. +> +> **Applies To:** Git commit message bodies. +> +> **Why:** Verbose, flowery, or highly templated commit messages clutter repository logs and increase cognitive load for engineers searching history. A staff engineer's time is valuable; the commit message must deliver maximum signal-to-noise ratio. + +**Trap 1: Including generic software engineering justifications or explaining why code review, testing, or good practices are important in general.** +Do not write essays on general design philosophy in the commit message. Stick strictly to the specific change's technical facts. + +**Don't:** +```text +Register gerrit-commit-message-review agent and skill + +To enforce rigorous commit log hygiene across this repository, this +change registers the new 'gerrit-commit-message-review' AI reviewer +agent and establishes its associated quality guidelines. + +Commit messages are permanent repositories of engineering intent, but +manual review is prone to human oversight. Under the feature request +in Issue 505405738, this system automates audit checks to provide +instant, high-fidelity feedback. By deploying a specialized agent that +evaluates formatting style, structural completeness, and footer +integrity, developers receive automated proofreading findings directly +in the Gerrit Checks UI (with ready-to-apply autofixes where +applicable). +``` +*(Problem: Highly verbose, generic text explaining general AI reviewer value propositions and repository hygiene, filled with fluff sentences like "Commit messages are permanent repositories...")* + +**Do:** +```text +Register gerrit-commit-message-review agent and skill + +To automate commit log hygiene audits across this repository, this +change registers the new agent to trigger automatically on COMMIT_MSG +changes. The accompanying skill definition outlines rules for +subject-line format, strict 72-character line wrapping, context and +intent explanation, and strict preservation of integration footers. + +Bug: Issue 505405738 +``` +*(Rationale: High signal-to-noise ratio. Completely avoids vacuous issue references in the body and jumps directly into the technical mechanism, leaving the issue linkage to the metadata footer.)* + +**Trap 2: Forcing complex multi-section layouts (like bullet points or Q&A headers) for straightforward, medium-sized, or simple changes.** +Use simple, direct paragraphs instead of lists and structural headings whenever possible. Only use numbered lists when listing a sequence of highly distinct architectural changes. + +**Don't:** +```text +Fix loading spinner and add test coverage + +What is changing: +1. The loading spinner in gr-reply-dialog.ts is fixed. +2. Property assignments are moved out of firstUpdated to avoid dual rendering. +3. Tests are added. + +Why this is necessary: +The loading spinner was experiencing visual jitter on rapid page transitions +due to a race condition in the reactive lifecycle hook, which is bad for UX. +``` +*(Problem: Trivial bug fix is forced into a multi-headed structure with redundant wording.)* + +**Do:** +```text +Fix loading spinner and add test coverage + +The loading spinner in gr-reply-dialog was experiencing visual jitter +on rapid page transitions due to a race condition in the reactive +lifecycle hook. + +This change moves property assignments out of firstUpdated to avoid +unnecessary second-pass rendering, stabilizing the visual state. +``` +*(Rationale: Short, elegant, two paragraphs of narrative flow. Fully explains the problem and high-level solution without rigid, repetitive structural overhead.)* + +**Trap 3: Over-explaining straightforward changes, using sequential narrations ("First", "Second"), or explaining obvious developer-experience benefits.** +Avoid writing multiple paragraphs or sequential lists to explain simple, singular enhancements. Never dedicate sentences to explaining *why* a change is beneficial in general (e.g. explaining that "this saves developer effort" or "improves developer experience"). Ground the explanation purely in the technical delta and keep simple changes within a single, dense paragraph of 2 to 3 sentences max. + +**Don't (Too Verbose):** +```text +Suggest full commit message and avoid pedantic reviews + +To address b/505405738 and improve the developer experience when +addressing commit message feedback, this change updates the review +guidelines in SKILL.md with a new chapter outlining response standards. + +First, if any formatting or hygiene issues are found, the reviewer must +provide a complete, fully-compliant, and copy-pasteable revised commit +message. This saves developer effort and streamlines the edit workflow. + +Second, to prevent automated review noise, the reviewer must adopt a +pragmatic approach and tolerate minor casing or subjective phrasing +differences if the message is already clear and compliant. +``` +*(Problem: Highly verbose, uses sequential enumeration ("First", "Second"), and explains obvious general benefits like "This saves developer effort and streamlines the edit workflow".)* + +**Do (Ultra-Concise & High Density):** +```text +Suggest full commit message and avoid pedantic reviews + +Automated commit message reviews can generate fragmented, pedantic +feedback on minor phrasing nits, creating friction rather than saving +developer time. This change updates SKILL.md to require reviewers to +provide a complete, copy-pasteable revised commit message when +violations are found, and to tolerate minor variations if the original +message is fundamentally compliant. + +Google-Bug-ID: b/505405738 +Release-Notes: skip +``` +*(Rationale: Highly concise and high signal-to-noise ratio. Explains the problem/why context first, and avoids any vacuous issue reference prefixes or "This change..." introductory meta-sentences, while utilizing the metadata footer for bug linkage.)* + + +-------------------------------------------------------------------------------- + +## Chapter: Metadata Footers & Preservations + +**Context:** Dynamic metadata footers serve as vital integration links connecting code changes to issue tracking systems, code review platforms, and automated release auditing pipelines. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :--- | :--- | :--- | :--- | +| **T3-01** | Mandatory Integration Footer Preservation | Critical | Modifying, corrupting, or dropping structured footers (Change-Id, Bug, or issue tracking keys) during edits. | + +-------------------------------------------------------------------------------- + +### Rules + +#### T3-01: Mandatory Integration Footer Preservation + +> **Rule:** Always preserve all structured git metadata footers at the bottom of the commit message, matching the appropriate tracker style based on environment context. +> +> **What:** Do not modify, corrupt, or drop system-critical footers such as Change-Id, Bug, or tracker reference keys during edits. +> +> **Applies To:** Commit message footer block at the bottom. +> +> **Why:** Code review platforms (such as Gerrit) track revisions strictly using the `Change-Id` footer. Deleting it detaches revision history or breaks integration webhooks. Similarly, issue tracking systems rely on matching keys (e.g., `Bug: <ID>`, `Closes #<ID>`) to link code commits with project tickets. + +**Trap 1: Amending or rewriting the commit message and dropping the original metadata footers.** + +**Don't:** +```text +Update system cache configs + +Refactored memory size and cache duration parameters. +``` + +**Do (Standard issue tracker format):** +```text +Update system cache configs + +Refactored memory size and cache duration parameters. + +Bug: Issue 12345 +Release-Notes: skip +Change-Id: Iab12cd34ef560078009000120034005600780090 +``` + +**Do (GitHub/GitLab tracker format):** +```text +Update system cache configs + +Refactored memory size and cache duration parameters. + +Closes #1234567 +Release-Notes: skip +Change-Id: Iab12cd34ef560078009000120034005600780090 +``` + +-------------------------------------------------------------------------------- + +## Chapter: Review Feedback & Suggested Commit Message + +**Context:** To provide maximum value and minimize friction, the reviewer must not only point out formatting/hygiene violations but also provide a complete, corrected, and fully-compliant commit message that the developer can copy and paste directly into Gerrit's commit message editor. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :--- | :--- | :--- | :--- | +| **T4-01** | Provide a Complete Suggested Commit Message | High | Providing only high-level feedback or listing line-by-line suggestions without providing a single, copy-pasteable revised commit message. | +| **T4-02** | Pragmatic Tolerance & Anti-Noise | High | Suggesting rewrites for minor stylistic differences or trivial casing when the original commit message is already highly informative and readable. | + +-------------------------------------------------------------------------------- + +### Rules + +#### T4-01: Provide a Complete Suggested Commit Message + +> **Rule:** Whenever any formatting, structure, or hygiene issues are identified in the commit message, the review feedback must include a dedicated section containing the complete, fully-compliant, and improved version of the commit message wrapped inside a markdown code block (e.g., using `text` or `git` syntax highlighting). If the original commit message is already satisfactory, fully compliant, and informative, no revised version or suggestions should be provided. +> +> **What:** The review feedback must provide the complete revised commit message in a single code block as a drop-in replacement ONLY when violations or potential improvements are found. This suggested message must meticulously apply all the guidelines defined in this skill (e.g., title length under 60 chars, imperative verbs, strict 72-character line wrapping in the body, pragmatic and concise explanation). It **must** preserve all existing metadata footers (like `Change-Id`, `Bug`, `Closes`, and `Release-Notes`) exactly as they appeared in the original message. If the commit message is already fully compliant, the reviewer should state that no improvements are necessary and omit the suggestion block. +> +> **Applies To:** Review feedback reports and summary comments on COMMIT_MSG. +> +> **Why:** Developers want to resolve formatting issues as quickly as possible. Providing a complete, copy-pasteable improved message eliminates the need for the developer to manually re-wrap lines or rewrite sentences, significantly improving the developer experience. However, forcing a rewrite when the message is already of high quality causes unnecessary noise and friction. + +**Trap 1: Providing feedback comments on individual lines but omitting a single, unified revised commit message.** + +**Don't:** +```text +Line 1: The title has 65 characters, which is over the 60-character limit. Please shorten it. +Line 3: This line is 85 characters long. Please wrap it at 72 characters. +``` + +**Do:** +````text +### Commit Message Review + +I found a few formatting issues with your commit message: +1. The title is too long (65 characters). +2. The body paragraphs are not wrapped at 72 characters. + +Here is an improved, fully-compliant version of your commit message that you can copy and paste directly into the Gerrit edit dialog: + +```text +Fix loading spinner and add test coverage + +The loading spinner in gr-reply-dialog was experiencing visual jitter +on rapid page transitions due to a race condition in the reactive +lifecycle hook. + +This change moves property assignments out of firstUpdated to avoid +unnecessary second-pass rendering, stabilizing the visual state. + +Bug: Issue 12345 +Release-Notes: skip +Change-Id: Iab12cd34ef560078009000120034005600780090 +``` +```` +*(Rationale: Provides a ready-to-use solution that saves developer effort.)* + +-------------------------------------------------------------------------------- + +#### T4-02: Pragmatic Tolerance & Anti-Noise + +> **Rule:** The reviewer must adopt a pragmatic, non-pedantic approach to evaluating commit messages. Do NOT suggest rewrites for minor stylistic differences, subjective phrasing preferences, or trivial casing choices if the original message is already clear, informative, correctly wrapped, and compliant with core limits. +> +> **What:** Apply a high threshold of value before flagging a commit message or suggesting an alternative. Trivial stylistic points (such as starting a component prefix colon with a lowercase verb, e.g., `GrepServlet: add ...` vs `GrepServlet: Add ...`, or slightly differing sentence structures that express the same context) are considered acceptable. The reviewer must **never** post comments or generate a suggestion block for these minor variations. Suggest revisions **only** when there are clear, objective violations (e.g., title > 60 chars, body lines > 72 chars, missing essential context, or corrupted/missing metadata). +> +> **Applies To:** All review feedback reports and comments. +> +> **Why:** Superfluous or nitpicky reviews (often referred to as "pedantic noise") irritate authors, waste review cycles, and erode trust in automated tooling. AI reviews must focus strictly on high-value correctness, safety, and critical readability standards. + +**Trap 1: Flagging a well-written, informative, and correctly-wrapped commit message over minor sentence formatting or prefix casing.** + +**Don't (Pedantic Noise):** +```text +### Commit Message Review + +The title uses a lowercase verb after the prefix ("add" instead of "Add"). Also, we can improve the body phrasing to be slightly more descriptive. + +Suggested Commit Message: +GrepServlet: Add JSON content search endpoint + +To support content search in Gitiles (Issue 376381593)... +``` +*(Problem: The original commit message was already outstanding. Suggesting a rewrite for trivial casing and wording differences adds no structural value.)* + +**Do:** +State that the commit message is fully compliant and satisfactory, and do not post any individual comments or suggested rewrite. + +
diff --git a/configs/skills/gerrit_frontend_engineering/SKILL.md b/configs/skills/gerrit_frontend_engineering/SKILL.md new file mode 100644 index 0000000..d89e403 --- /dev/null +++ b/configs/skills/gerrit_frontend_engineering/SKILL.md
@@ -0,0 +1,2938 @@ +--- +name: gerrit-frontend-engineering +description: Provides guidance and best practices on Polygerrit UI development, frontend architecture, and TypeScript/JS coding standards in Gerrit. +--- + +# Frontend Engineering & UI Development Engineering Guide + +## Executive Summary + +Welcome to the Frontend Engineering & UI Development guide. This repository +serves as the authoritative source of tribal knowledge for our UI architecture, +born from historical refactoring efforts, critical performance optimizations, +and the ongoing necessity to prevent recurrent regression of known failure +modes. By codifying these engineering standards, we ensure that incoming +engineers can confidently navigate the complexities of our frontend ecosystem +without falling into legacy traps, introducing unverified UI states, or +triggering silent runtime failures. + +This guide enforces strict architectural boundaries across the entire UI +development lifecycle. It mandates rigorous state encapsulation within the Lit +framework, uncompromising TypeScript type safety, and highly resilient +client-server integrations. It further standardizes hermetic UI testing +methodologies, unifies our CSS design systems, and outlines strict strategies +for client-side performance profiling. + +Adherence to these principles guarantees structural consistency and system +reliability. Whether you are building dynamic web components, integrating +complex REST API endpoints, or optimizing data payloads for AI contexts and +telemetry, this documentation establishes the foundational constraints required +to ship a performant, predictable, and scalable user interface. + +## Summary + +| Chapter Theme / Title | Scope & Objective | +| :----------------------------------- | :------------------------------------ | +| **Lit Framework Idioms & State | Enforce strict Lit framework idioms | +: Encapsulation** : by leveraging declarative rendering, : +: : reactive property encapsulation, and : +: : native lifecycle hooks. Avoid : +: : imperative DOM manipulation and : +: : properly isolate transient UI status : +: : from the core data models. : +| **TypeScript Strictness & Type | This domain governs the strict | +: Safety** : enforcement of TypeScript type safety : +: : by explicitly forbidding unsafe : +: : casting and blanket compiler : +: : suppressions. It mandates precise : +: : component modeling, strict interface : +: : adherence, and proper access : +: : modifiers to eliminate silent runtime : +: : failures and unverified states. : +| **Hermetic Testing & Visual | This chapter mandates the strict | +: Regression** : isolation of unit tests, : +: : comprehensive visual validation using : +: : full shadow DOM snapshots, and the : +: : centralization of test data : +: : generation to guarantee hermetic : +: : execution and prevent false-positive : +: : assertions. : +| **Client-Side Performance & | This theme governs the optimization | +: Telemetry** : of client-side performance by : +: : enforcing strict telemetry on : +: : synchronous CPU-bound operations and : +: : minimizing network latency. It : +: : mandates the caching of asynchronous : +: : API promises, the derivation of state : +: : from existing payloads, and the rigid : +: : bounding of background data requests. : +| **CSS Architecture & Design System | This domain governs the consistent | +: Consistency** : application of styles across the UI, : +: : mandating the use of content-driven : +: : layout techniques, externalized : +: : custom property configurations for : +: : visual assets, and declarative Lit : +: : directives over imperative inline : +: : styling. : +| **API Integration & Error Handling** | This chapter governs the resilient | +: : integration of frontend logic with : +: : REST APIs. It establishes strict : +: : constraints for maintaining backend : +: : payload parity, explicitly modeling : +: : structural variances, and enforcing : +: : centralized error handling over : +: : localized `try...catch` blocks. : +| **AI Context & Telemetry Payload | This domain governs the client-side | +: Optimization** : lifecycle and optimization of data : +: : structures sent to AI agents and : +: : telemetry pipelines. It strictly : +: : dictates payload deduplication : +: : strategies, transparent AI token : +: : constraint surfacing, and rigorous : +: : parsing validation to prevent silent : +: : context loss. : + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- + +## Chapter: Lit Framework Idioms & State Encapsulation + +**Context:** Enforce strict Lit framework idioms by leveraging declarative +rendering, reactive property encapsulation, and native lifecycle hooks. Avoid +imperative DOM manipulation and properly isolate transient UI status from the +core data models. + +### Summary + +| Rule ID | Principle / | Priority | Primary Symptom / Trap | +: : Constraint : : : +| :-------- | :----------------- | :------- | :------------------------------- | +| **T1-01** | Separation of Data | High | Reusing a data property to hold | +: : and UI State : : UI loading or error text, which : +: : Variables : : corrupts the data being passed : +: : : : to child components or the : +: : : : clipboard. : +| **T1-02** | Explicit Boolean | High | Inspecting the DOM via | +: : Properties over : : `this.children` in Lit element : +: : Dynamic Slot : : methods to determine if a named : +: : Detection : : slot element was provided by the : +: : : : parent. : +| **T1-03** | Declarative Event | Medium | Attaching DOM event listeners | +: : Listeners in Lit : : imperatively within the : +: : Templates : : component constructor. : +| **T1-04** | Declarative | Medium | Chaining ternary operators | +: : Conditional CSS : : inside a template string to : +: : via classMap : : build a class list. : +| **T1-05** | Centralized | Medium | Defining a custom manager object | +: : Periodic : : or singleton strictly tied to : +: : LitElement Updates : : one specific UI component type : +: : via Generic : : for periodic updates. : +: : Utility : : : +| **T1-06** | Declarative | Medium | Manually invoking setAttribute | +: : Attribute : : or removeAttribute within a Lit : +: : Reflection in Lit : : lifecycle method to sync DOM : +: : Components : : properties. : +| **T1-07** | Idempotent | Medium | Firing a tracking event inside | +: : Telemetry : : `updated` purely based on : +: : Reporting in Lit : : conditional presence, without a : +: : Component : : state flag acknowledging it : +: : Lifecycle Hooks : : fired. : +| **T1-08** | Strict Truthiness | High | Relying on optional chaining | +: : Checks for : : length checks to represent empty : +: : Asynchronous Array : : or missing data. : +: : Data : : : +| **T1-09** | Pre-computing | Medium | A helper method called in | +: : Derived State in : : `render()` that parses raw : +: : Lit Lifecycle : : strings into arrays on every : +: : Methods : : invocation. : +| **T1-10** | Returning | Medium | Using an empty template literal | +: : `nothing` for : : or omitting a return when a : +: : Empty Templates in : : condition fails in the template. : +: : Lit : : : +| **T1-11** | Single-Pass | Medium | Assigning values to `@state()` | +: : Initialization of : : or `@property()` fields inside : +: : Reactive : : the `firstUpdated` hook. : +: : Properties : : : +| **T1-12** | Guarding | High | Executing timeout handlers or | +: : Asynchronous State : : asynchronous DOM updates without : +: : Updates : : confirming the component remains : +: : Post-Disconnection : : in the DOM structure. : +| **T1-13** | Use Lit classMap | Medium | Concatenating ternary operators | +: : Directive for : : inside the `class` attribute : +: : Conditional CSS : : string of a Lit HTML template. : +: : Classes : : : +| **T1-14** | Strict Lit State | High | Using the `@property` decorator | +: : Encapsulation : : for variables that represent : +: : : : internal component state (like : +: : : : data lists or loading spinners). : +| **T1-15** | Declarative | Medium | Controlling element visibility | +: : Component : : using CSS class conditionals. : +: : Visibility : : : +: : Toggling : : : +| **T1-16** | Immutable Lit Form | High | Mutating form state fields | +: : State Resets : : directly without triggering a : +: : : : reference change for Lit to : +: : : : detect. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T1-01: Separation of Data and UI State Variables + +> **Rule:** Never overload core data variables with transient UI status strings. +> You must use dedicated status properties to manage UI state separately from +> the underlying data model. +> +> **What:** Do not overload core data variables with transient UI status strings +> (e.g., 'Loading...', 'Error'). Use dedicated status properties to manage UI +> state separately from the underlying data model. +> +> **Applies To:** Lit components managing asynchronous operations and displaying +> resulting data alongside loading or error states. +> +> **Why:** A `generatedPassword` property was overloaded to temporarily hold +> 'Generating...' or 'Failed to generate'. This caused the associated 'Copy to +> clipboard' component to copy the error or status text instead of a valid +> password. Failing to adhere to this typically results in **Invalid Data +> Copied**. + +**Trap 1: Reusing a data property to hold UI loading or error text, which +corrupts the data being passed to child components or the clipboard.** + +**Don't:** + +```typescript +this._generatedPassword = 'Generating...'; +this.restApiService.generatePassword().then(newPassword => { + this._generatedPassword = newPassword ?? 'Failed to generate'; +}); +``` + +**Do:** + +```typescript +this.status = 'Generating...'; +this.restApiService.generatePassword().then(newPassword => { + if (newPassword) { + this.generatedPassword = newPassword; + this.status = undefined; + } else { + this.status = 'Failed to generate'; + } +}); +``` + +#### T1-02: Explicit Boolean Properties over Dynamic Slot Detection + +> **Rule:** Always control conditional layout wrappers using explicit boolean +> properties mapped to the component API. Never dynamically query the DOM for +> the presence of assigned slots. +> +> **What:** Control the rendering of conditional layout wrappers using explicit +> boolean properties mapped to the component's API, rather than dynamically +> querying the DOM for the presence of assigned slots. +> +> **Applies To:** Lit components featuring conditional layout containers that +> wrap `<slot>` elements (e.g., search bars with optional leading icons). +> +> **Why:** Dynamically checking `hasNamedSlot` by iterating over `this.children` +> was computationally brittle and caused rendering bugs where structural +> elements (like search icons) were accidentally removed or created unintended +> 'ghost spacing'. Failing to adhere to this typically results in **Layout +> Regression / Missing Elements**. + +**Trap 1: Inspecting the DOM via `this.children` in Lit element methods to +determine if a named slot element was provided by the parent.** + +**Don't:** + +```typescript +private hasNamedSlot(name: string): boolean { + return Array.from(this.children).some( + el => el.getAttribute('slot') === name + ); +} + +render() { + return this.hasNamedSlot('leading-icon') ? html`<div><slot name="leading-icon"></slot></div>` : nothing; +} +``` + +**Do:** + +```typescript +@property({type: Boolean}) +showLeadingIcon = false; + +render() { + return this.showLeadingIcon ? html`<div><slot name="leading-icon"></slot></div>` : nothing; +} +``` + +#### T1-03: Declarative Event Listeners in Lit Templates + +> **Rule:** Always bind event listeners declaratively directly within the +> component's `render()` template. Never imperatively attach listeners using +> `this.addEventListener` in the constructor. +> +> **What:** Bind event listeners declaratively directly within the component's +> `render()` template using `@event` syntax, rather than imperatively attaching +> them using `this.addEventListener` in the constructor. +> +> **Applies To:** LitElement initialization and user interaction event handling. +> +> **Why:** Imperatively adding listeners via the constructor disconnects the +> logic from the declarative template, risks memory leaks if not cleaned up, +> leaves inline documentation orphaned, and triggers automated code health +> warnings. Failing to adhere to this typically results in **Structural +> Anti-Pattern / Orphaned Context**. + +**Trap 1: Attaching DOM event listeners imperatively within the component +constructor.** + +**Don't:** + +```typescript +constructor() { + super(); + // BAD: Imperative binding + this.addEventListener('mousedown', e => this.handleMouseDown(e)); +} +``` + +**Do:** + +```html +override render() { + // GOOD: Declarative binding directly in the Lit template + return html` + <div class="menu" @mousedown=${this.handleMenuMouseDown}> + <div class="menu-item">${this.hoverCardText}</div> + </div> + `; +} +``` + +#### T1-04: Declarative Conditional CSS via classMap + +> **Rule:** Must use the Lit `classMap` directive for applying conditional CSS +> classes in templates. Avoid manual string interpolation with ternary +> operators. +> +> **What:** Use the Lit `classMap` directive for applying conditional CSS +> classes in templates instead of manual string interpolation with ternary +> operators. +> +> **Applies To:** Lit Framework templates (`render()` methods). +> +> **Why:** Manual string interpolation for classes is difficult to read and +> prone to whitespace concatenation errors, which leads to incorrectly applied +> or missed CSS selectors during dynamic state changes. Failing to adhere to +> this typically results in **Malformed Class Strings / UI Bugs**. + +**Trap 1: Chaining ternary operators inside a template string to build a class +list.** + +**Don't:** + +```typescript +<div class="diffContainer ${this.shownSidebar ? 'sidebarOpen' : ''} ${this.file?.diffs_too_expensive_to_compute ? 'hidden' : ''}"> +``` + +**Do:** + +```typescript +<div class=${classMap({ + diffContainer: true, + sidebarOpen: this.shownSidebar, + hidden: !!this.file?.diffs_too_expensive_to_compute +})}> +``` + +#### T1-05: Centralized Periodic LitElement Updates via Generic Utility + +> **Rule:** Always register components requiring timer-based periodic +> re-rendering with a centralized update manager. Never implement individual +> `setInterval` loops inside isolated components. +> +> **What:** Components requiring timer-based periodic re-rendering must register +> with a centralized `PeriodicUpdateManager` rather than implementing individual +> `setInterval` and lifecycle cleanup logic inside the component. +> +> **Applies To:** LitElements displaying time-sensitive data (e.g., relative +> dates, "time ago" formatters). +> +> **Why:** Individual date formatter components initially implemented their own +> object literal manager or interval timers. This violated the separation of +> concerns and led to memory leaks if components failed to clean up their +> specific timers upon disconnection. Failing to adhere to this typically +> results in **Memory Leaks / Duplicated Timer Logic**. + +**Trap 1: Defining a custom manager object or singleton strictly tied to one +specific UI component type for periodic updates.** + +**Don't:** + +```typescript +export const dateFormatterManager = { + formatters: new Set<GrDateFormatter>(), + register(formatter) { /* set interval to call requestUpdate */ } +} +``` + +**Do:** + +```typescript +// In periodic-update-util.ts +export class PeriodicUpdateManager<T extends LitElement> { + constructor(private readonly refreshIntervalMs: number) {} + register(component: T) { /* generic interval logic */ } +} + +// In component +override connectedCallback() { + super.connectedCallback(); + dateFormatterManager.register(this); +} +``` + +#### T1-06: Declarative Attribute Reflection in Lit Components + +> **Rule:** Must use Lit's `@property({reflect: true})` decorator to synchronize +> a component's property state with DOM attributes. Never manually manipulate +> attributes via `setAttribute` within lifecycle methods. +> +> **What:** Use Lit's @property({reflect: true}) decorator to automatically +> synchronize a component's property state with its corresponding DOM +> attributes, replacing manual DOM manipulation calls. +> +> **Applies To:** Lit web components, styling hooks, and state management. +> +> **Why:** A custom icon wrapper was manually setting and removing an attribute +> inside the `willUpdate` lifecycle method to apply CSS selectors, violating +> declarative state management principles. Failing to adhere to this typically +> results in **Imperative DOM Manipulation**. + +**Trap 1: Manually invoking setAttribute or removeAttribute within a Lit +lifecycle method to sync DOM properties.** + +**Don't:** + +```typescript +override willUpdate() { + if (this.icon) { + this.setAttribute('custom', ''); + } else { + this.removeAttribute('custom'); + } +} +``` + +**Do:** + +```typescript +@property({type: Boolean, reflect: true}) +custom = false; + +override willUpdate() { + this.custom = this.icon ? true : false; +} +``` + +#### T1-07: Idempotent Telemetry Reporting in Lit Component Lifecycle Hooks + +> **Rule:** Must guard telemetry interactions fired during the `updated()` +> lifecycle hook with a dedicated state flag. Never allow subsequent, unrelated +> property changes to trigger duplicate reporting. +> +> **What:** Telemetry interactions fired during the `updated()` lifecycle hook +> must be guarded by a dedicated state flag to prevent duplicate reporting +> triggered by subsequent, unrelated property changes. +> +> **Applies To:** Lit components executing side effects (like analytics or +> impression tracking) within the `updated()` loop. +> +> **Why:** When streaming AI responses, the component's state rapidly updated. +> Without an idempotent guard flag, the system generated multiple telemetry +> events for the exact same interaction every time the state re-rendered. +> Failing to adhere to this typically results in **Duplicate Impression +> Reporting**. + +**Trap 1: Firing a tracking event inside `updated` purely based on conditional +presence, without a state flag acknowledging it fired.** + +**Don't:** + +```typescript +override updated(changedProperties: PropertyValues) { + if (this.message()?.responseComplete) { + this.reportSuggestionsShown(); + } +} +``` + +**Do:** + +```typescript +private reportedSuggestionsShown = false; + +override updated(changedProperties: PropertyValues) { + if (!this.reportedSuggestionsShown && this.message()?.responseComplete) { + this.reportSuggestionsShown(); + this.reportedSuggestionsShown = true; + } +} +``` + +#### T1-08: Strict Truthiness Checks for Asynchronous Array Data + +> **Rule:** Always explicitly check for null or undefined before evaluating the +> `.length` property of asynchronous array data. Never rely solely on optional +> chaining length checks for truthiness. +> +> **What:** When determining the fallback UI state based on asynchronous array +> data (like API responses), explicitly check for null/undefined before checking +> the `.length` property. +> +> **Applies To:** Lit components conditionally rendering UI elements based on +> the loaded state of arrays. +> +> **Why:** Checking `array?.length === 0` fails when the array is still +> `undefined`, because `undefined === 0` resolves to `false`, causing the +> application to skip rendering the fallback UI during the loading or +> uninitialized state. Failing to adhere to this typically results in **Missing +> Fallback UI**. + +**Trap 1: Relying on optional chaining length checks to represent empty or +missing data.** + +**Don't:** + +```typescript +if (this.repoLabels?.length === 0) { + return this.renderDefaultParameterInputField(); +} +``` + +**Do:** + +```typescript +if (!this.repoLabels || this.repoLabels.length === 0) { + return this.renderDefaultParameterInputField(); +} +``` + +#### T1-09: Pre-computing Derived State in Lit Lifecycle Methods + +> **Rule:** Always pre-compute derived state within the `willUpdate` lifecycle +> method or a dedicated observer. Never process strings or execute heavy array +> computations directly inside the `render()` loop. +> +> **What:** Avoid processing strings or doing heavy array computations directly +> inside the `render()` method or helper template methods. Instead, reactively +> compute derived state (e.g., parsing a string into an array) within the +> `willUpdate` lifecycle method or a dedicated observer. +> +> **Applies To:** Lit components dealing with data transformation before +> rendering. +> +> **Why:** Dynamic computation inside render cycles degrades performance and +> muddles template readability. Helper methods that executed `.split()` and +> regex operations on strings were running repeatedly on every re-render. +> Failing to adhere to this typically results in **Redundant Computations**. + +**Trap 1: A helper method called in `render()` that parses raw strings into +arrays on every invocation.** + +**Don't:** + +```typescript +private getParameters(): string[] { + if (this.parameters) return this.parameters; + if (this.parameterStr?.trim()) { + return this.parameterStr.trim().split(/\s+/); + } + return []; +} + +render() { + const params = this.getParameters(); + // ... +} +``` + +**Do:** + +```typescript +// Handle the calculation inside `willUpdate` reacting to changes in `this.parameterStr` +willUpdate(changedProperties: PropertyValues) { + if (changedProperties.has('parameterStr')) { + this.parameters = this.parameterStr?.trim() ? this.parameterStr.trim().split(/\s+/) : []; + } +} +``` + +#### T1-10: Returning `nothing` for Empty Templates in Lit + +> **Rule:** Must explicitly return the `nothing` sentinel value instead of an +> empty template literal when rendering conditionally empty elements. +> +> **What:** In Lit templates, explicitly return the `nothing` sentinel value +> instead of an empty template literal when rendering conditionally empty +> elements. +> +> **Applies To:** Lit components, specifically conditional rendering blocks. +> +> **Why:** Historically, empty template instances (html``) were returned for +> false conditions, which created unnecessary markers in the DOM, slightly +> degrading rendering efficiency and DOM cleanliness. Failing to adhere to this +> typically results in **DOM Clutter**. + +**Trap 1: Using an empty template literal or omitting a return when a condition +fails in the template.** + +**Don't:** + +```typescript +// BAD: Returning empty template +render() { + if (!this.show) return html``; + return html`<div>Content</div>`; +} +``` + +**Do:** + +```typescript +// GOOD: Returning nothing +import {nothing} from 'lit'; + +render() { + if (!this.show) return nothing; + return html`<div>Content</div>`; +} +``` + +#### T1-11: Single-Pass Initialization of Reactive Properties + +> **Rule:** Always initialize base reactive properties during construction or +> via bound property updates. Never use the `firstUpdated` lifecycle hook for +> initial assignment. +> +> **What:** Base reactive properties must be initialized during construction or +> via bound property updates rather than using the `firstUpdated` lifecycle +> hook, which triggers a redundant second render cycle. +> +> **Applies To:** Lit components, specifically lifecycle hooks (`constructor`, +> `willUpdate`, `firstUpdated`). +> +> **Why:** Assigning derived URL states inside `firstUpdated` triggered +> immediate, unnecessary re-renders, hurting initial paint performance. Failing +> to adhere to this typically results in **Redundant Re-rendering**. + +**Trap 1: Assigning values to `@state()` or `@property()` fields inside the +`firstUpdated` hook.** + +**Don't:** + +```typescript +// BAD: Triggers a second render cycle immediately after the first +override firstUpdated() { + this.hostUrl = window.location.origin; +} +``` + +**Do:** + +```typescript +// GOOD: Reactively bound to updates before render +override willUpdate(changedProperties: PropertyValues) { + if (!this.hostUrl) { + this.hostUrl = window.location.origin; + } +} +``` + +**Exceptions:** Properties that strictly depend on measuring DOM dimensions or +child element readiness after layout. + +#### T1-12: Guarding Asynchronous State Updates Post-Disconnection + +> **Rule:** Always explicitly verify `this.isConnected` before executing +> asynchronous tasks or debounced updates. Never execute callbacks that mutate +> state during DOM teardown. +> +> **What:** Components that schedule asynchronous tasks or debounced updates +> must explicitly verify `this.isConnected` before executing work to prevent +> mutations during DOM teardown. +> +> **Applies To:** Event handlers, async callbacks, and debounced routines in Lit +> web components. +> +> **Why:** Property updates triggered `willUpdate` hooks even after +> `disconnectedCallback` had run, scheduling new background tasks for components +> that were no longer attached to the document. Failing to adhere to this +> typically results in **Memory Leaks**. + +**Trap 1: Executing timeout handlers or asynchronous DOM updates without +confirming the component remains in the DOM structure.** + +**Don't:** + +```typescript +// BAD: Running debounced task without verifying connection +updateSuggestions() { + this.scheduleDebounceTask(); +} +``` + +**Do:** + +```typescript +// GOOD: Short-circuiting if disconnected +updateSuggestions() { + if (!this.isConnected) return; + this.scheduleDebounceTask(); +} +``` + +#### T1-13: Use Lit classMap Directive for Conditional CSS Classes + +> **Rule:** Must use Lit's `classMap` directive for managing multiple +> conditional CSS classes. Never use manual string concatenation or ternary +> chaining within class attributes. +> +> **What:** Use Lit's `classMap` directive for managing multiple conditional CSS +> classes rather than manual string interpolation. +> +> **Applies To:** Lit component templates rendering conditional classes based on +> component state. +> +> **Why:** Historically, manual string concatenation for multiple conditional +> CSS classes led to messy, error-prone template structures that were difficult +> to read and maintain. Failing to adhere to this typically results in +> **Unreadable/Error-Prone Templates**. + +**Trap 1: Concatenating ternary operators inside the `class` attribute string of +a Lit HTML template.** + +**Don't:** + +```html +class="context-chip ${this.isSuggestion ? 'suggested-chip' : ''} ${this.isCustomAction ? 'custom-action-chip' : ''}" +``` + +**Do:** + +```html +class=${classMap({'context-chip': true, 'suggested-chip': this.isSuggestion, 'custom-action-chip': this.isCustomAction})} +``` + +#### T1-14: Strict Lit State Encapsulation + +> **Rule:** Always isolate internal component variables that drive UI re-renders +> using the `@state` decorator. Never use `@property` for internal state that is +> not intended to be configured via HTML attributes. +> +> **What:** Internal component variables that drive UI re-renders but are not +> intended to be configured via HTML attributes must use the `@state` decorator +> instead of `@property`. +> +> **Applies To:** All Lit-based Web Components. +> +> **Why:** Component logic was exposing internal data retrieval statuses (like +> loading state or fetched lists) as public `@property` attributes. This +> polluted the component's public API surface and allowed external DOM +> manipulation to improperly overwrite internal component state. Failing to +> adhere to this typically results in **State Leakage**. + +**Trap 1: Using the `@property` decorator for variables that represent internal +component state (like data lists or loading spinners).** + +**Don't:** + +```typescript +// BAD: Internal state exposed as an attribute +@property({type: Boolean}) +_loading = true; + +@property({type: Array}) +submitRequirements?: SubmitRequirementInfo[]; +``` + +**Do:** + +```typescript +// GOOD: Internal state isolated using @state +@state() +loading = true; + +@state() +submitRequirements?: SubmitRequirementInfo[]; +``` + +#### T1-15: Declarative Component Visibility Toggling + +> **Rule:** Always handle conditional rendering of DOM elements using Lit's +> declarative `when` or `nothing` directives. Never dynamically apply CSS +> classes that set `display: none` to toggle visibility. +> +> **What:** Conditional rendering of DOM elements must be handled using Lit's +> declarative `when` or `nothing` directives within the HTML template, rather +> than dynamically applying CSS classes that set `display: none`. +> +> **Applies To:** All Lit templates, particularly for loading states and +> conditional sections. +> +> **Why:** Loading states were previously managed by dynamically assigning a +> `.loading` CSS class to an element, which relied on external stylesheet rules +> to hide/show the node. This made the UI state harder to reason about and +> bypassed Lit's native DOM reconciliation. Failing to adhere to this typically +> results in **Layout Shifts / DOM Bloat**. + +**Trap 1: Controlling element visibility using CSS class conditionals.** + +**Don't:** + +```typescript +// BAD: CSS-driven visibility +render() { + return html` + <table class="${this.loading ? 'loading' : ''}"> + <!-- rows --> + </table> + `; +} +// Relying on: .loading #target { display: none; } +``` + +**Do:** + +```typescript +// GOOD: Declarative Lit rendering directives +render() { + return html` + <tbody> + ${when( + this.loading, + () => html`<tr><td>Loading...</td></tr>`, + () => html`<!-- Render data rows -->` + )} + </tbody> + `; +} +``` + +#### T1-16: Immutable Lit Form State Resets + +> **Rule:** Always assign a newly constructed object reference to the state +> property when resetting form state or clearing dialog inputs. Never mutate the +> existing object's properties in-place. +> +> **What:** When resetting form state or clearing dialog inputs in Lit, assign a +> newly constructed object reference to the state property rather than mutating +> the existing object's properties in-place. +> +> **Applies To:** Lit form components and dialogs with complex object state +> (`@state`). +> +> **Why:** Form dialogs failed to clear properly after creating a new item +> because the state object was mutated in place or partially reset, resulting in +> stale UI data where fields appeared populated but submitted empty values. +> Failing to adhere to this typically results in **Stale UI Data**. + +**Trap 1: Mutating form state fields directly without triggering a reference +change for Lit to detect.** + +**Don't:** + +```typescript +// BAD: In-place mutation does not consistently trigger a full re-render +handleCreateCancel() { + this.newRequirement.name = ''; + this.newRequirement.description = ''; + this.dialog.close(); +} +``` + +**Do:** + +```typescript +// GOOD: Provide a new object reference to trigger full reactive updates +private getEmptyRequirement() { + return { name: '', description: '' }; +} + +handleCreateCancel() { + this.newRequirement = this.getEmptyRequirement(); + this.dialog.close(); +} +``` + +-------------------------------------------------------------------------------- + +### Cross-Domain Dependencies + +* **Upstream:** T6 | API Integration & Error Handling - *Fetching asynchronous + data structures via REST clients dictates Lit component reactive loading + states and rendering fallbacks.* +* **Downstream:** T5 | CSS Architecture & Design System Consistency - *Lit + template directives like `classMap` dynamically consume centralized + structural CSS classes and shared UI styles.* +* **Downstream:** T7 | AI Context & Telemetry Payload Optimization - *Lit + lifecycle methods like `updated()` natively trigger telemetry interaction + payloads that require idempotent guards to prevent data bloat.* + +## Chapter: TypeScript Strictness & Type Safety + +**Context:** This domain governs the strict enforcement of TypeScript type +safety by explicitly forbidding unsafe casting and blanket compiler +suppressions. It mandates precise component modeling, strict interface +adherence, and proper access modifiers to eliminate silent runtime failures and +unverified states. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / | +: : : : Trap : +| :-------- | :------------------------------- | :------- | :----------------- | +| **T2-01** | Targeted Type Casting over Broad | High | Suppressing all | +: : Error Suppression : : TypeScript : +: : : : compiler errors on : +: : : : a line just to : +: : : : bypass a private : +: : : : visibility check : +: : : : in a unit test. : +| **T2-02** | Removal of Underscore Prefixes | Medium | Prefixing reactive | +: : for Reactive Properties : : Lit properties : +: : : : with an underscore : +: : : : to denote private : +: : : : state. : +| **T2-03** | Elimination of TypeScript | High | Suppressing the | +: : Compiler Suppressions in Unit : : compiler error to : +: : Tests : : mutate a private : +: : : : property in the : +: : : : test setup. : +| **T2-04** | Utilizing TypeScript Utility | High | Constructing an | +: : Types over Unsafe Casting : : object with : +: : : : missing properties : +: : : : and masking the : +: : : : error by casting : +: : : : it as the full : +: : : : interface type. : +| **T2-05** | Prototype Methods over Arrow | Medium | Using an arrow | +: : Function Properties : : function assigned : +: : : : to a variable : +: : : : inside the class : +: : : : body. : +| **T2-06** | Elimination of Unsafe `any` Type | High | Suppressing | +: : Casts : : TypeScript errors : +: : : : or forcibly : +: : : : casting objects to : +: : : : `any` when dynamic : +: : : : types don't : +: : : : strictly align. : +| **T2-07** | Testing Private State | Medium | Forcibly accessing | +: : Encapsulation Rules : : class internals : +: : : : using string-keyed : +: : : : bracket notation : +: : : : in test files. : +| **T2-08** | Eliminate Unsafe 'any' Type | Medium | Casting function | +: : Casting in Test Stubs : : arguments to `any` : +: : : : within a mock's : +: : : : custom callback : +: : : : function. : +| **T2-09** | Suppression of Private Member | Medium | Casting the class | +: : Access in Tests via : : reference or : +: : @ts-expect-error : : global object to : +: : : : `any` to bypass : +: : : : TypeScript's : +: : : : visibility and : +: : : : presence checks. : +| **T2-10** | Enforce Type Contracts Over | Medium | Adding | +: : Redundant Runtime Checks : : `.filter(Boolean)` : +: : : : or explicit : +: : : : `undefined` checks : +: : : : on an array that : +: : : : is strictly typed : +: : : : as containing only : +: : : : defined objects. : +| **T2-11** | Explicit TypeScript Access | Medium | Prefixing internal | +: : Modifiers : : class methods or : +: : : : properties with an : +: : : : underscore while : +: : : : leaving them : +: : : : functionally : +: : : : public. : +| **T2-12** | Idiomatic Boolean Casting | Medium | Casting a | +: : : : potentially : +: : : : undefined boolean : +: : : : to a strict : +: : : : boolean using the : +: : : : nullish coalescing : +: : : : operator. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T2-01: Targeted Type Casting over Broad Error Suppression + +> **Rule:** Always use targeted type casts (e.g., `as any`) to bypass specific +> visibility constraints in unit tests rather than silencing the entire line +> with `@ts-expect-error`. +> +> **What:** Replace blanket `@ts-expect-error` directives with targeted type +> casts (e.g., `(element as any)`) when attempting to access private component +> methods or properties in unit tests. +> +> **Applies To:** TypeScript unit tests interacting with encapsulated component +> logic. +> +> **Why:** Using `// @ts-expect-error` suppresses all TypeScript errors on the +> subsequent line. Historically, this masked actual bugs like typos in test +> assertions (e.g., a typo in `assert.isFalse`), rendering the tests unreliable. +> Failing to adhere to this typically results in **Masked Bugs / Silent +> Failures**. + +**Trap 1: Suppressing all TypeScript compiler errors on a line just to bypass a +private visibility check in a unit test.** + +**Don't:** + +```typescript +// BAD: Masks all TS errors, including typos in the assert statement. +// @ts-expect-error +assert.isFalse(element.hasAiComments()); +``` + +**Do:** + +```typescript +// GOOD: Bypasses visibility selectively while maintaining strict type checking on the assertion. +assert.isFalse((element as any).hasAiComments()); +``` + +-------------------------------------------------------------------------------- + +#### T2-02: Removal of Underscore Prefixes for Reactive Properties + +> **Rule:** Never use leading underscores for Lit element properties; strictly +> enforce private state using TypeScript access modifiers. +> +> **What:** Do not use leading underscores for Lit element properties. Rely on +> TypeScript `private` access modifiers to encapsulate internal state instead of +> naming conventions. +> +> **Applies To:** All LitElement `@property` and `@state` declarations. +> +> **Why:** Leading underscores were heavily used in the legacy Polymer +> implementation to denote privacy, but they violate current TypeScript +> strictness standards and style guidelines for the modernized PolyGerrit UI. +> Failing to adhere to this typically results in **Style Guide Violation**. + +**Trap 1: Prefixing reactive Lit properties with an underscore to denote private +state.** + +**Don't:** + +```typescript +@property({type: String}) +_passwordUrl: string | null = null; +``` + +**Do:** + +```typescript +@property({type: String}) +passwordUrl: string | null = null; +``` + +-------------------------------------------------------------------------------- + +#### T2-03: Elimination of TypeScript Compiler Suppressions in Unit Tests + +> **Rule:** Must never bypass compiler checks to modify test internals; +> explicitly elevate visibility of the required property and document the +> exception. +> +> **What:** Unit tests must not bypass compiler checks using @ts-expect-error to +> test internal logic. Instead, widen the visibility of the target property or +> method from private to public/internal, and document it with a comment. +> +> **Applies To:** TypeScript unit test suites and component class files (e.g., +> Lit components). +> +> **Why:** Developers were using @ts-expect-error directives to suppress +> compiler errors when assigning or calling private component members in tests. +> This masked actual type regressions from the TS compiler. Failing to adhere to +> this typically results in **Type Safety Bypass**. + +**Trap 1: Suppressing the compiler error to mutate a private property in the +test setup.** + +**Don't:** + +```typescript +// In test file: +// @ts-expect-error +element.docsBaseUrl = 'https://docs.com/'; +// @ts-expect-error +assert.equal(element.computeHelpUrl(), '...'); +``` + +**Do:** + +```typescript +// In component file: +// private but used in test +public docsBaseUrl = ''; + +// In test file (no suppressions): +element.docsBaseUrl = 'https://docs.com/'; +assert.equal(element.computeHelpUrl(), '...'); +``` + +-------------------------------------------------------------------------------- + +#### T2-04: Utilizing TypeScript Utility Types over Unsafe Casting + +> **Rule:** Always construct precise subsets of interfaces using utility types +> like `Pick<T, K>` rather than forcibly blinding the compiler via `as Type`. +> +> **What:** When creating objects that fulfill only a specific subset of an +> interface's requirements, construct the correct type signature using +> TypeScript utility types (e.g., Pick<T, K>) rather than using 'as Type' type +> assertions to blind the compiler. +> +> **Applies To:** TypeScript data transformations, API response mapping, and +> component state variables. +> +> **Why:** A subset of a LabelDefinitionInfo object was generated and +> aggressively typed using `as LabelDefinitionInfo`. This misled consumers of +> the data about which properties were actually populated, creating potential +> runtime hazards. Failing to adhere to this typically results in **Unsafe Type +> Assertion**. + +**Trap 1: Constructing an object with missing properties and masking the error +by casting it as the full interface type.** + +**Don't:** + +```typescript +const partial = { + name: 'LabelName', + values: { '+1': '' } +} as LabelDefinitionInfo; +``` + +**Do:** + +```typescript +const partial: Pick<LabelDefinitionInfo, 'name'> & Pick<LabelDefinitionInfo, 'values'> = { + name: 'LabelName', + values: { '+1': '' } +}; +``` + +-------------------------------------------------------------------------------- + +#### T2-05: Prototype Methods over Arrow Function Properties + +> **Rule:** Always define class behaviors as standard prototype methods to avoid +> the excess instantiation overhead caused by arrow function properties. +> +> **What:** Define class behaviors using standard class methods rather than +> assigning arrow functions as class properties to optimize memory usage. +> +> **Applies To:** TypeScript class definitions across the application +> (Providers, Services, Models). +> +> **Why:** Assigning arrow functions directly as class properties caused a new +> instance of the function to be created in memory for every instantiation of +> the class, unnecessarily increasing memory overhead. Failing to adhere to this +> typically results in **Excessive Memory Overhead**. + +**Trap 1: Using an arrow function assigned to a variable inside the class +body.** + +**Don't:** + +```typescript +export class LabelSuggestionsProvider { + getSuggestions = ( + predicate: string, + expression: string + ): Promise<AutocompleteSuggestion[]> => { + // logic + }; +} +``` + +**Do:** + +```typescript +export class LabelSuggestionsProvider { + getSuggestions( + predicate: string, + expression: string + ): Promise<AutocompleteSuggestion[]> { + // logic + } +} +``` + +**Exceptions:** Arrow functions are acceptable if the method is passed around as +a callback and strictly requires preserving the `this` lexical binding without +manual `.bind(this)`. + +-------------------------------------------------------------------------------- + +#### T2-06: Elimination of Unsafe `any` Type Casts + +> **Rule:** Never use the `any` type; enforce strict bounds using concrete +> interfaces or rely on `unknown` for safe downcasting. +> +> **What:** The `any` type must be strictly avoided. Use explicit interfaces, +> strict types, or `unknown` for downcasting, eliminating `@ts-expect-error` and +> `@typescript-eslint/no-explicit-any`. +> +> **Applies To:** Global TypeScript codebase, particularly component state +> assignments, plugin configurations, and test setups. +> +> **Why:** Developer convenience led to pervasive use of `any` type casting, +> bypassing the compiler and masking structural mismatches that caused uncaught +> runtime exceptions when interfaces evolved. Failing to adhere to this +> typically results in **Type Erasure**. + +**Trap 1: Suppressing TypeScript errors or forcibly casting objects to `any` +when dynamic types don't strictly align.** + +**Don't:** + +```typescript +// BAD: Bypassing type safety +// eslint-disable-next-line @typescript-eslint/no-explicit-any +.then((element: any) => { + assert.strictEqual(element, module); +}) +``` + +**Do:** + +```typescript +// GOOD: Using unknown or explicit interfaces +.then((element: unknown) => { + assert.strictEqual(element, module); +}) +``` + +-------------------------------------------------------------------------------- + +#### T2-07: Testing Private State Encapsulation Rules + +> **Rule:** Must never pierce class boundaries using bracket notation +> (`['property']`) in tests; natively expose and document properties required +> for testing. +> +> **What:** Do not bypass private property access via bracket notation +> (`element['privateProp']`) in tests. If a property must be exposed for +> testing, remove the `private` modifier and document it with a standard +> comment. +> +> **Applies To:** Component state definitions and their respective test suites. +> +> **Why:** Engineers were circumventing class lexical scope constraints by using +> bracket notation to set private component states in tests. This violated the +> TypeScript style guide and evaded static analysis tools. Failing to adhere to +> this typically results in **Encapsulation Violation**. + +**Trap 1: Forcibly accessing class internals using string-keyed bracket notation +in test files.** + +**Don't:** + +```typescript +// BAD: Bypassing private scope +element['stages'] = [{ condition: 'status:open' }]; +``` + +**Do:** + +```typescript +// GOOD: Remove private, add explicit documentation, and use dot notation +// In component: +@state() // private but used in tests +stages: Stage[] = []; + +// In test: +element.stages = [{ condition: 'status:open' }]; +``` + +-------------------------------------------------------------------------------- + +#### T2-08: Eliminate Unsafe 'any' Type Casting in Test Stubs + +> **Rule:** Never substitute actual types with `any` in mock definitions; +> enforce accurate mock function signatures or use `unknown`. +> +> **What:** Avoid using the `any` type when defining function arguments in stub +> callbacks (e.g., Sinon `callsFake`). Use the actual mocked type or `unknown`. +> +> **Applies To:** Sinon stubs and mock definitions within unit tests. +> +> **Why:** Using the 'any' type bypassed the TypeScript compiler's type +> checking, allowing signature mismatches between the mock and the actual +> implementation to silently pass. Failing to adhere to this typically results +> in **Silent Type Mismatches**. + +**Trap 1: Casting function arguments to `any` within a mock's custom callback +function.** + +**Don't:** + +```typescript +sinon.stub(Obj, 'method').callsFake(function (this: Obj, account: any) { ... }) +``` + +**Do:** + +```typescript +sinon.stub(Obj, 'method').callsFake(function (this: Obj, account: unknown) { ... }) +``` + +-------------------------------------------------------------------------------- + +#### T2-09: Suppression of Private Member Access in Tests via @ts-expect-error + +> **Rule:** Always apply `@ts-expect-error` to suppress targeted visibility +> issues in mocks rather than annihilating all type validation by casting to +> `any`. +> +> **What:** When accessing private or static members in unit tests for mocking +> purposes, use the `@ts-expect-error` directive instead of casting the entire +> class or object to `any`. +> +> **Applies To:** Unit test setup scripts needing to stub private, protected, or +> unexported properties on classes or global objects. +> +> **Why:** Casting objects to 'any' completely stripped all type checking for +> subsequent operations. Utilizing `@ts-expect-error` maintains the type +> context, ensuring the test fails at compile-time if the underlying property's +> visibility or type changes in the future. Failing to adhere to this typically +> results in **Complete Type Loss**. + +**Trap 1: Casting the class reference or global object to `any` to bypass +TypeScript's visibility and presence checks.** + +**Don't:** + +```typescript +// BAD: Overrides all type checking +const libLoader = (GrImageViewer as any).libLoader; +(window as any).resemble = sinon.stub(); +``` + +**Do:** + +```typescript +// GOOD: Suppresses visibility error but retains underlying type +// @ts-expect-error +const libLoader = GrImageViewer.libLoader; +// @ts-expect-error +window.resemble = sinon.stub(); +``` + +-------------------------------------------------------------------------------- + +#### T2-10: Enforce Type Contracts Over Redundant Runtime Checks + +> **Rule:** Must never litter code with redundant falsy checks for +> arrays/objects explicitly typed as defined; fix non-compliant test data +> upstream. +> +> **What:** Do not add redundant runtime filtering or null-checks for conditions +> that the TypeScript definitions explicitly forbid. Fix the upstream source or +> test data instead. +> +> **Applies To:** Business logic and data processing on strictly typed models. +> +> **Why:** Adding defensive runtime checks for strictly-typed data degraded +> readability and masked underlying flaws in test setups that were passing +> invalid, poorly-mocked data into functions. Failing to adhere to this +> typically results in **Masked Upstream Bugs / Cluttered Logic**. + +**Trap 1: Adding `.filter(Boolean)` or explicit `undefined` checks on an array +that is strictly typed as containing only defined objects.** + +**Don't:** + +```typescript +// Method strictly accepts: changes: ChangeInfo[] +async sync(changes: ChangeInfo[]) { + // BAD: Redundant runtime check + const validChanges = changes.filter(c => c && isChangeInfo(c)); + const basicChanges = new Map(validChanges.map(c => [c.id, c])); +} +``` + +**Do:** + +```typescript +// GOOD: Trust the contract and fix failing tests upstream +async sync(changes: ChangeInfo[]) { + const basicChanges = new Map(changes.map(c => [c.id, c])); +} +``` + +**Exceptions:** External payloads lacking robust typing across integration +boundaries. + +-------------------------------------------------------------------------------- + +#### T2-11: Explicit TypeScript Access Modifiers + +> **Rule:** Always codify class visibility through strict `private`, +> `protected`, or `public` modifiers instead of falling back on leading +> underscore conventions. +> +> **What:** Class members and methods intended for internal use must be enforced +> using the TypeScript `private` access modifier rather than relying on the +> legacy `_` (underscore) naming convention. +> +> **Applies To:** TypeScript classes and Web Component definitions. +> +> **Why:** The codebase contained legacy properties like `_loading` and methods +> without explicit access modifiers, which failed to utilize the TypeScript +> compiler to prevent external dependencies from coupling to internal +> implementation details. Failing to adhere to this typically results in +> **Broken Encapsulation**. + +**Trap 1: Prefixing internal class methods or properties with an underscore +while leaving them functionally public.** + +**Don't:** + +```typescript +// BAD: Relying on naming conventions for privacy +@state() +_loading = true; + +renderBoolean() { ... } +``` + +**Do:** + +```typescript +// GOOD: Enforcing privacy with TypeScript modifiers +@state() +private loading = true; + +private renderCheckmark() { ... } +``` + +-------------------------------------------------------------------------------- + +#### T2-12: Idiomatic Boolean Casting + +> **Rule:** Always cast optional or truthy/falsy values using the idiomatic +> double-negation operator (`!!`) rather than the nullish coalescing operator +> (`?? false`). +> +> **What:** To cast an optionally undefined or truthy/falsy value to a strict +> boolean, use the double-negation operator (`!!`) instead of the nullish +> coalescing operator (`?? false`). +> +> **Applies To:** TypeScript logic handling optional object properties or API +> responses. +> +> **Why:** During permission evaluation, `access?.is_owner ?? false` was flagged +> during review as unidiomatic, and the codebase was aligned to use standard +> double-negation for boolean casts. Failing to adhere to this typically results +> in **Unidiomatic Code**. + +**Trap 1: Casting a potentially undefined boolean to a strict boolean using the +nullish coalescing operator.** + +**Don't:** + +```typescript +// BAD: Verbose and unidiomatic casting +this.isProjectOwner = access?.is_owner ?? false; +``` + +**Do:** + +```typescript +// GOOD: Concise, standard boolean cast +this.isProjectOwner = !!access?.is_owner; +``` + +-------------------------------------------------------------------------------- + +### Cross-Domain Dependencies + +* **Upstream:** T1 | Lit Framework Idioms & State Encapsulation - *TypeScript + strictness principles govern the visibility and structural integrity of Lit + component state variables and properties.* +* **Downstream:** T3 | Hermetic Testing & Visual Regression - *Strict typing + directly dictates the syntax and permitted mocking strategies for test stubs + and fixtures.* + +## Chapter: Hermetic Testing & Visual Regression + +**Context:** This chapter mandates the strict isolation of unit tests, +comprehensive visual validation using full shadow DOM snapshots, and the +centralization of test data generation to guarantee hermetic execution and +prevent false-positive assertions. + +### Summary + +| Rule ID | Principle / | Priority | Primary Symptom / Trap | +: : Constraint : : : +| :-------- | :----------------- | :------- | :------------------------------- | +| **T3-01** | Strict DOM Query | High | Querying for an element and | +: : Assertions in : : manually asserting its existence : +: : Testing : : using an assertion library. : +| **T3-02** | Shadow DOM Event | Medium | Relying exclusively on the | +: : Property Fallbacks : : element property which may : +: : for Tests : : remain empty in test fixtures. : +| **T3-03** | Comprehensive | High | Checking the `length` of | +: : Shadow DOM : : elements matching a query : +: : Snapshot : : selector to verify UI rendering. : +: : Assertions : : : +| **T3-04** | Centralized Test | Medium | Manually creating partial data | +: : Data Generation : : structures in tests and using : +: : via Factory : : type casting to satisfy the : +: : Helpers : : compiler. : +| **T3-05** | Isolation of | Medium | Adding a visualDiff snapshot | +: : Visual Regression : : assertion inside standard : +: : Tests : : logical unit tests. : +| **T3-06** | Awaiting | High | Triggering a UI update and | +: : Asynchronous : : immediately asserting on the : +: : Render Cycles : : resulting DOM without waiting : +: : Before Assertions : : for the Lit rendering engine : +: : : : lifecycle. : +| **T3-07** | DRY Centralization | Medium | Duplicating 5+ lines of UI | +: : of Shared Test : : interaction setup across : +: : Setup Logic : : multiple test cases. : +| **T3-08** | Avoid | Medium | Assigning a random, dynamic | +: : Monkey-Patching : : property to the component being : +: : Test-Only : : tested to track mock responses : +: : Properties on : : or internal states. : +: : Component : : : +: : Instances : : : +| **T3-09** | Explicit Mocking | High | Allowing the component to | +: : of External : : natively load its third-party : +: : Network Assets in : : script dependencies during a : +: : Tests : : basic unit test fixture setup. : +| **T3-10** | Strict Limits on | High | Increasing the Mocha test suite | +: : Test Timeouts for : : timeout inside a failing : +: : Visual Regressions : : screenshot test to force it to : +: : : : pass. : +| **T3-11** | Realistic | High | Rendering a generic template | +: : Component : : without required inputs and : +: : Initialization in : : asserting the shadow DOM is : +: : Tests : : empty. : +| **T3-12** | Descriptive and | Medium | Naming a test based on an | +: : Contextual Test : : abstract concept rather than the : +: : Case Naming : : behavioral condition. : +| **T3-13** | Production-Aligned | Medium | Defining static mock data for | +: : Visual Regression : : visual tests that includes : +: : Mock Data : : properties normally filtered out : +: : : : by the production environment. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T3-01: Strict DOM Query Assertions in Testing + +> **Rule:** Always use the `queryAndAssert` utility to locate and verify DOM +> elements simultaneously in unit tests. +> +> **What:** Use the `queryAndAssert` utility to locate DOM elements in unit +> tests instead of using `query` coupled with manual truthiness assertions or +> optional chaining. +> +> **Applies To:** Frontend Web Component testing; specifically LitElement test +> fixtures interacting with the DOM. +> +> **Why:** Using a standard `query` that returns `null` when an element is +> missing causes cryptic `TypeError`s later in the test execution. Asserting +> immediately provides clear, fail-fast test reporting. Failing to adhere to +> this typically results in **Test TypeErrors / Cryptic Failures**. + +**Trap 1: Querying for an element and manually asserting its existence using an +assertion library.** + +**Don't:** + +```typescript +const warning = query(element, '.expensiveDiff'); +assert.isOk(warning); +assert.include(warning.textContent, 'Diff too expensive'); +``` + +**Do:** + +```typescript +const warning = queryAndAssert(element, '.expensiveDiff'); +assert.include(warning.textContent, 'Diff too expensive'); +``` + +**Trap 2: Using optional chaining to silently swallow null elements when +checking class lists.** + +**Don't:** + +```typescript +const diffContainer = query(element, '.diffContainer'); +assert.isTrue(diffContainer?.classList.contains('hidden')); +``` + +**Do:** + +```typescript +const diffContainer = queryAndAssert(element, '.diffContainer'); +assert.isTrue(diffContainer.classList.contains('hidden')); +``` + +#### T3-02: Shadow DOM Event Property Fallbacks for Tests + +> **Rule:** Must implement explicit attribute fallbacks when reading custom +> event properties that fail to synchronize in hermetic test environments. +> +> **What:** When handling custom event properties (like `value` from custom +> `md-outlined-select` components) that fail to reliably synchronize to the +> target's property in the testing environment, safely fallback to reading the +> `value` attribute. +> +> **Applies To:** Lit element event handlers dealing with custom web components +> and executed in hermetic testing environments. +> +> **Why:** Automated tests simulating user selections in `md-outlined-select` +> were failing because the testing framework failed to read the value from the +> event target property, requiring an explicit fallback to the attribute. +> Failing to adhere to this typically results in **False Negative Test +> Failures**. + +**Trap 1: Relying exclusively on the element property which may remain empty in +test fixtures.** + +**Don't:** + +```typescript +@change=${(e: Event) => { + this.selectedLabelForVote = (e.target as HTMLSelectElement).value; +}} +``` + +**Do:** + +```typescript +@change=${(e: Event) => { + // TODO: Remove reading from attribute once test env issue is fixed + this.selectedLabelForVote = + ((e.target as HTMLSelectElement).value || + (e.target as HTMLSelectElement).getAttribute('value')) ?? ''; +}} +``` + +#### T3-03: Comprehensive Shadow DOM Snapshot Assertions + +> **Rule:** Never use shallow DOM assertions; always validate UI structures +> using `assert.shadowDom.equal`. +> +> **What:** UI component tests must use `assert.shadowDom.equal` to validate the +> entire rendered structure against an expected HTML snapshot, rather than +> asserting shallow DOM properties. +> +> **Applies To:** Frontend UI unit testing of Lit web components. +> +> **Why:** During refactoring, comprehensive DOM checks were accidentally +> replaced with shallow element counts, creating a blind spot where regressions +> in structural layout, attributes, and text content could slip past CI. Failing +> to adhere to this typically results in **Missed UI Regressions**. + +**Trap 1: Checking the `length` of elements matching a query selector to verify +UI rendering.** + +**Don't:** + +```typescript +// BAD: Shallow DOM assertion +const flowElements = element.shadowRoot!.querySelectorAll('.flow'); +assert.equal(flowElements.length, 2); +``` + +**Do:** + +```typescript +// GOOD: Full Shadow DOM snapshot assertion +assert.shadowDom.equal( + element, + /* HTML */ ` + <div class="container"> + <div class="flow">...</div> + <div class="flow">...</div> + </div> + ` +); +``` + +#### T3-04: Centralized Test Data Generation via Factory Helpers + +> **Rule:** Always construct mock data structures using centralized factory +> helpers configured with `Partial<T>` overrides. +> +> **What:** Avoid manually instantiating large mock data payloads. Utilize +> centralized factory functions that accept `Partial<T>` and provide sensible +> defaults, eliminating manual type assertions. +> +> **Applies To:** Frontend unit tests and mock data setup blocks. +> +> **Why:** Tests repeatedly hardcoded large data objects, making the test suite +> brittle to schema changes and requiring explicit type casting (`as FlowInfo`) +> to bypass compiler complaints about missing fields. Failing to adhere to this +> typically results in **Brittle Tests**. + +**Trap 1: Manually creating partial data structures in tests and using type +casting to satisfy the compiler.** + +**Don't:** + +```typescript +// BAD: Manual object creation with casting +const flow = { + uuid: 'flow1', + owner: {name: 'owner1'}, + created: '2025-01-01T10:00:00.000Z' as Timestamp, +} as FlowInfo; +``` + +**Do:** + +```typescript +// GOOD: Using a test data generator with Partial overrides +const flow = createFlow({ + uuid: 'flow1', + owner: {name: 'owner1', _account_id: 1 as AccountId}, +}); +``` + +#### T3-05: Isolation of Visual Regression Tests + +> **Rule:** Must isolate DOM snapshot and screenshot operations into parallel +> `_screenshot_test.ts` files. +> +> **What:** Visual regression (screenshot) tests utilizing DOM snapshots must +> reside in a separate `_screenshot_test.ts` file rather than being appended to +> standard unit test files. +> +> **Applies To:** Test directory structure and files utilizing `visualDiff`. +> +> **Why:** Combining fast unit tests with slow visual diffing tests bloated unit +> execution times and complicated testing step separation in the CI pipeline. +> Failing to adhere to this typically results in **CI Bottleneck**. + +**Trap 1: Adding a visualDiff snapshot assertion inside standard logical unit +tests.** + +**Don't:** + +* Putting visual `visualDiff` test blocks into the standard + `gr-[component]_test.ts` file alongside business logic tests. + +**Do:** + +* Creating a parallel `gr-[component]_screenshot_test.ts` dedicated + exclusively to visual assertions. + +#### T3-06: Awaiting Asynchronous Render Cycles Before Assertions + +> **Rule:** Always `await element.updateComplete` prior to evaluating DOM nodes +> following a UI state change. +> +> **What:** Tests asserting UI state changes must strictly `await +> element.updateComplete` before querying DOM elements or their properties, +> avoiding false positive evaluations. +> +> **Applies To:** Lit web component unit tests. +> +> **Why:** Tests were erroneously passing because asynchronous state updates +> lacked `await`, causing assertions to execute and pass before the updated +> component render cycle actually fired. Failing to adhere to this typically +> results in **False Positive Tests**. + +**Trap 1: Triggering a UI update and immediately asserting on the resulting DOM +without waiting for the Lit rendering engine lifecycle.** + +**Don't:** + +```typescript +// BAD: Synchronous assertion after state change +element.renderInOrder([{path: 'p2'}]); +assert.equal(reviewStub.callCount, 1); +``` + +**Do:** + +```typescript +// GOOD: Awaiting update completion +await element.renderInOrder([{path: 'p2'}]); +await element.updateComplete; +assert.equal(reviewStub.callCount, 1); +``` + +#### T3-07: DRY Centralization of Shared Test Setup Logic + +> **Rule:** Must extract repeated setup mechanisms and multi-step UI sequences +> into centralized, shared asynchronous helpers. +> +> **What:** Repeated interaction sequences required to establish a test's +> initial state (e.g., clicking menus, awaiting cycles, querying elements) must +> be extracted into asynchronous helper functions. +> +> **Applies To:** Component test suites with repetitive, multi-step UI +> interaction setups. +> +> **Why:** Tests were repeatedly copying and pasting the multi-step DOM +> interaction needed to open a modal and fetch an input reference, inflating the +> codebase and making structural changes painful. Failing to adhere to this +> typically results in **High Maintenance Burden**. + +**Trap 1: Duplicating 5+ lines of UI interaction setup across multiple test +cases.** + +**Don't:** + +* Copying and pasting the same set of element lookups, simulated clicks, and + `await element.updateComplete;` lines into every test block. + +**Do:** + +* Extracting the setup steps into a shared, typed async helper (e.g., `async + function openLinkDialogAndGetInput(): Promise<HTMLInputElement>`) and + calling it in each test. + +#### T3-08: Avoid Monkey-Patching Test-Only Properties on Component Instances + +> **Rule:** Never mutate component instances with custom, untyped tracking +> properties; track test states using locally scoped variables. +> +> **What:** Avoid mutating component instances with test-only custom properties +> to track state during tests. Utilize locally scoped variables inside the test +> setup or perform proper stub verification instead. +> +> **Applies To:** Unit testing, specifically when stubbing instance methods or +> external calls. +> +> **Why:** Attaching custom test-only properties (like tracking URLs directly on +> the element) polluted the component object model, bypassed encapsulation, and +> risked state leakage between unit tests. Failing to adhere to this typically +> results in **Test State Leakage**. + +**Trap 1: Assigning a random, dynamic property to the component being tested to +track mock responses or internal states.** + +**Don't:** + +```typescript +.callsFake(function (this: MyComponent, arg: any) { + // BAD: Modifying the instance for testing purposes + (this as any).__test_url = arg; + return 'data:image...'; +}); +// ... later in the test ... +assert.equal((element as any).__test_url, expectedUrl); +``` + +**Do:** + +```typescript +let generatedUrl: string; +.callsFake(function (this: MyComponent, arg: unknown) { + // GOOD: Using a scoped variable for tracking + generatedUrl = arg; + return 'data:image...'; +}); +// ... later in the test ... +assert.equal(generatedUrl, expectedUrl); +``` + +#### T3-09: Explicit Mocking of External Network Assets in Tests + +> **Rule:** Must completely stub remote library calls or image-fetching +> mechanisms to execute entirely in isolation. +> +> **What:** External libraries that trigger network requests (e.g., fetching +> scripts or image diffing workers) must be fully mocked in local test setups to +> prevent 404 network errors and ensure hermetic execution. +> +> **Applies To:** Unit testing for components integrating with third-party, +> dynamically loaded libraries. +> +> **Why:** Failure to mock external dependencies caused the test runner to +> attempt fetching remote or non-existent local assets, resulting in 404 console +> errors, flaky tests, and significantly slower execution times. Failing to +> adhere to this typically results in **404 Network Errors**. + +**Trap 1: Allowing the component to natively load its third-party script +dependencies during a basic unit test fixture setup.** + +**Don't:** + +```typescript +setup(async () => { + // BAD: Component attempts to download external libraries over the network + element = await fixture(`<my-viewer></my-viewer>`); +}); +``` + +**Do:** + +```typescript +setup(async () => { + // GOOD: Mock external library loaders to resolve instantly + // @ts-expect-error + const libLoader = MyViewer.libLoader; + sinon.stub(libLoader, 'getLibrary').resolves(); + + // @ts-expect-error + window.externalLib = sinon.stub().returns({ ... }); + + element = await fixture(`<my-viewer></my-viewer>`); +}); +``` + +#### T3-10: Strict Limits on Test Timeouts for Visual Regressions + +> **Rule:** Never augment native test timeouts (`this.timeout()`) as a +> workaround for slow rendering components or unreliable baselines. +> +> **What:** Do not artificially inflate test timeouts (e.g., Mocha +> `this.timeout()`) to mask slow component rendering or flakiness in visual +> regression tests. Maintain default timeouts. +> +> **Applies To:** Screenshot testing and visual regression test suites. +> +> **Why:** Artificially extending timeouts allowed significant performance +> regressions in UI rendering to go unnoticed, as tests would wait excessively +> long for slow components to stabilize rather than failing fast. Failing to +> adhere to this typically results in **Masked Performance Regressions**. + +**Trap 1: Increasing the Mocha test suite timeout inside a failing screenshot +test to force it to pass.** + +**Don't:** + +```typescript +suite('component screenshot tests', function () { + this.timeout(4000); + // ... test body ... +}); +``` + +**Do:** + +```typescript +suite('component screenshot tests', () => { + // Default timeout ensures fast failure if rendering degrades + // Update screenshots with CLI commands if baseline legitimately changed + // ... test body ... +}); +``` + +#### T3-11: Realistic Component Initialization in Tests + +> **Rule:** Must initialize required component properties to functional values +> prior to asserting against the shadow DOM. +> +> **What:** Unit tests that assert against a component's shadow DOM must +> initialize the component with realistic property states, ensuring the DOM is +> not trivially empty or unrendered. +> +> **Applies To:** Lit component tests doing Shadow DOM assertions. +> +> **Why:** Asserting against an empty or completely uninitialized DOM resulted +> in false-positive test passes that failed to verify any actual template or +> structural logic. Failing to adhere to this typically results in **False +> Positive Tests**. + +**Trap 1: Rendering a generic template without required inputs and asserting the +shadow DOM is empty.** + +**Don't:** + +```typescript +test('render', async () => { + await element.updateComplete; + assert.shadowDom.equal(element, ''); +}); +``` + +**Do:** + +```typescript +test('render', async () => { + element.name = 'Test'; + await element.updateComplete; + assert.shadowDom.equal( + element, + `<div><h3 class="title">Test</h3></div>` + ); +}); +``` + +#### T3-12: Descriptive and Contextual Test Case Naming + +> **Rule:** Always name test cases with strict, functional "renders [state] if +> [condition]" descriptions. +> +> **What:** Test cases must use descriptive 'renders X if Y' naming structures +> rather than relying on abstract, ambiguous, or overly technical jargon that +> lacks functional context. +> +> **Applies To:** Unit test descriptions (the string argument in `test()` or +> `it()`). +> +> **Why:** Ambiguous test names using mathematical or overly specific internal +> jargon made it difficult for developers to deduce the functional intent of the +> test upon failure without reading the implementation. Failing to adhere to +> this typically results in **Unclear Test Intent**. + +**Trap 1: Naming a test based on an abstract concept rather than the behavioral +condition.** + +**Don't:** + +* test('render attempt ordinal', async () => { ... }); + +**Do:** + +* test('renders attempt number if not single attempt', async () => { ... }); + +#### T3-13: Production-Aligned Visual Regression Mock Data + +> **Rule:** Must guarantee that mock datasets used in visual regressions bypass +> data points filtered out by production pipeline logic. +> +> **What:** Mock data structures used to drive screenshot/visual regression +> tests must precisely mirror the data shapes and filtering logic deployed in +> the production frontend. +> +> **Applies To:** Frontend visual regression tests (`*_screenshot_test.ts`) and +> mock data fixtures. +> +> **Why:** Visual tests previously hardcoded reference types that were +> intentionally stripped out by frontend plugins in production. This resulted in +> visual tests asserting on UI components that users would never actually see. +> Failing to adhere to this typically results in **False Positive Tests**. + +**Trap 1: Defining static mock data for visual tests that includes properties +normally filtered out by the production environment.** + +**Don't:** + +* Defining test data that bypasses standard application filtering logic to + artificially inflate component coverage. + +**Do:** + +* Removing filtered mock references from visual test configurations so the + test exactly matches the filtered production state. + +-------------------------------------------------------------------------------- + +### Cross-Domain Dependencies + +* **Upstream:** T1 | Lit Framework Idioms & State Encapsulation - *Hermetic UI + tests strictly rely on accurate Lit lifecycle declarations and asynchronous + `updateComplete` chains mapped here.* +* **Upstream:** T2 | TypeScript Strictness & Type Safety - *Type-safe factory + generators utilizing `Partial<T>` and eliminating `any` casting rely + completely on the strictness mandates of this domain.* + +## Chapter: Client-Side Performance & Telemetry + +**Context:** This theme governs the optimization of client-side performance by +enforcing strict telemetry on synchronous CPU-bound operations and minimizing +network latency. It mandates the caching of asynchronous API promises, the +derivation of state from existing payloads, and the rigid bounding of background +data requests. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :-------- | :------------------------ | :------- | :------------------------ | +| **T4-01** | Synchronous Execution | High | Assuming a save operation | +: : Telemetry Profiling : : is only slow due to : +: : : : network requests and : +: : : : leaving synchronous data : +: : : : preparation unmeasured. : +| **T4-02** | Elimination of Redundant | High | Executing an asynchronous | +: : API Data Fetches : : API call to pull data : +: : : : that already exists as a : +: : : : property of a currently : +: : : : loaded state object. : +| **T4-03** | Promise-Based API Request | High | Calling the API directly | +: : Caching : : on every query invocation : +: : : : without storing the : +: : : : ongoing or resolved : +: : : : request. : +| **T4-04** | Capped Payloads for | High | Passing `undefined` or | +: : Autocomplete Suggestion : : leaving limit parameters : +: : Requests : : empty for data : +: : : : aggregation endpoints : +: : : : used merely for : +: : : : suggestion dropdowns. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T4-01: Synchronous Execution Telemetry Profiling + +> **Rule:** Always instrument synchronous, CPU-bound logic with telemetry timers +> to surface main-thread blocking operations. Never assume UI latency is solely +> caused by asynchronous network requests. +> +> **What:** Instrument synchronous, CPU-bound logic (e.g., object comparison, +> distance calculations) with telemetry timers to identify main-thread blocking +> operations, rather than solely profiling asynchronous network calls. +> +> **Applies To:** Frontend Performance Profiling; particularly in high-frequency +> or data-heavy UI actions like draft comment saving and text matching. +> +> **Why:** Historically, performance metrics focused heavily on network request +> times. This hid UI freezes caused by expensive O(n^2) synchronous calculations +> on the main thread, such as running deep equality checks against large +> AI-generated patch suggestions. Failing to adhere to this typically results in +> **Main Thread UI Freezes**. + +**Trap 1: Assuming a save operation is only slow due to network requests and +leaving synchronous data preparation unmeasured.** + +**Don't:** + +```typescript +// BAD: Only timing the network request +const result = await this.restApiService.saveDraft(draft); +``` + +**Do:** + +```typescript +// GOOD: Timing CPU-bound data preparation independently +const fixTimer = this.reporting.getTimer('UpdateDraftComment - isFixSuggestionChanged'); +if (this.isFixSuggestionChanged()) { + draft.fix_suggestions = this.getFixSuggestions(); +} +fixTimer.end(); + +const networkTimer = this.reporting.getTimer('UpdateDraftComment - network'); +const result = await this.restApiService.saveDraft(draft); +networkTimer.end(); +``` + +-------------------------------------------------------------------------------- + +#### T4-02: Elimination of Redundant API Data Fetches + +> **Rule:** Always derive state directly from globally hydrated context objects +> instead of executing redundant REST API requests. +> +> **What:** Derive required state directly from existing context payloads (such +> as the globally hydrated Change object) rather than making redundant network +> requests to fetch subsets of identical data. +> +> **Applies To:** Frontend components querying repository metadata, permissions, +> or labels. +> +> **Why:** The UI was performing a dedicated REST API call to fetch repository +> labels, adding UI latency, even though the allowed labels were already +> hydrated within the `change.permitted_labels` property on the global change +> object. Failing to adhere to this typically results in **Redundant Network +> Latency**. + +**Trap 1: Executing an asynchronous API call to pull data that already exists as +a property of a currently loaded state object.** + +**Don't:** + +```typescript +// Anti-pattern: Fetching when the data is already there +this.repoLabels = await this.restApiService.getRepoLabels(change.project); +``` + +**Do:** + +```typescript +// Preferred: Mapping from existing context +const permittedLabels = change.permitted_labels ?? {}; +this.repoLabels = Object.entries(permittedLabels).map(...); +``` + +**Exceptions:** If the existing payload is known to be stale or intentionally +truncated for performance reasons in that specific context. + +-------------------------------------------------------------------------------- + +#### T4-03: Promise-Based API Request Caching + +> **Rule:** Must cache the Promise of an API request to prevent redundant +> network calls during rapid UI events, ensuring errors resolve safely to avoid +> cache poisoning. +> +> **What:** Cache the Promise of an API request to prevent redundant network +> calls during rapid UI events (e.g., autocomplete typing), and ensure failed +> requests resolve to a safe fallback (like undefined) so the cache is not +> poisoned permanently. +> +> **Applies To:** Frontend services making API calls based on user input, +> specifically Autocomplete and Suggestion Providers. +> +> **Why:** Fetching data on every keystroke without caching led to spamming the +> backend API, especially when the data subset being searched was static (e.g., +> repository labels) and could be filtered purely on the client side. Failing to +> adhere to this typically results in **Backend API Spam / High Latency**. + +**Trap 1: Calling the API directly on every query invocation without storing the +ongoing or resolved request.** + +**Don't:** + +```typescript +getSuggestions(expression: string) { + if (!this.repoName) return Promise.resolve([]); + return this.restApiService.getRepoLabels(this.repoName).then(labels => { + // filter logic + }); +} +``` + +**Do:** + +```typescript +getSuggestions(expression: string) { + if (!this.repoName) return Promise.resolve([]); + if (!this.cachedLabelsPromise) { + this.cachedLabelsPromise = this.restApiService + .getRepoLabels(this.repoName) + .catch(err => { + reportingService.error('Provider', err); + return undefined; // Ensure caught errors resolve safely + }); + } + return this.cachedLabelsPromise.then(labels => { + // filter logic + }); +} +``` + +**Exceptions:** Queries that rely on server-side filtering (e.g., passing the +user's keystroke to the backend endpoint directly) cannot be cached in this +manner. + +-------------------------------------------------------------------------------- + +#### T4-04: Capped Payloads for Autocomplete Suggestion Requests + +> **Rule:** Never execute unbounded background API queries; always enforce hard +> payload limits for components like autocomplete and dialogs to prevent UI +> freezes. +> +> **What:** Backend API queries triggered by background UI components (like +> autocomplete dropdowns or dialog filling) must be strictly bounded to a hard +> limit to prevent downloading excessive payloads. +> +> **Applies To:** API query parameters, specifically REST API requests fetching +> background data for UI presentation. +> +> **Why:** An unbounded query for open changes downloaded over 600kB of data +> from the network, causing a severe UX lag and freezing the user interface on +> slower internet connections. Failing to adhere to this typically results in +> **Network Bottleneck**. + +**Trap 1: Passing `undefined` or leaving limit parameters empty for data +aggregation endpoints used merely for suggestion dropdowns.** + +**Don't:** + +```typescript +// BAD: Unbounded fetching for suggestions +await this.restApiService.getChanges( + undefined, // no limit + 'is:open -age:90d' +); +``` + +**Do:** + +```typescript +// GOOD: Hardcoded maximum to prevent bandwidth exhaustion +await this.restApiService.getChanges( + /* changeNumber=*/ 450, + 'is:open -age:90d' +); +``` + +-------------------------------------------------------------------------------- + +### Cross-Domain Dependencies + +* **Upstream:** T6 | API Integration & Error Handling - *Defines the core + structures of REST API operations and error fallback mechanisms necessary + for effective client-side caching.* +* **Downstream:** T7 | AI Context & Telemetry Payload Optimization - *Consumes + the telemetry pipelines defined here to bound and optimize specialized AI + interaction reporting.* + +## Chapter: CSS Architecture & Design System Consistency + +**Context:** This domain governs the consistent application of styles across the +UI, mandating the use of content-driven layout techniques, externalized custom +property configurations for visual assets, and declarative Lit directives over +imperative inline styling. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :-------- | :------------------------ | :------- | :------------------------ | +| **T5-01** | Dynamic Sizing for Custom | Medium | Embedding static pixel | +: : SVG Icons via CSS : : values for width and : +: : : : height inside the raw SVG : +: : : : literal. : +| **T5-02** | Dynamic Width via | Medium | Hardcoding width in | +: : Content-Driven CSS Sizing : : pixels or percentages to : +: : : : achieve visual uniformity : +: : : : despite unpredictable : +: : : : content strings. : +| **T5-03** | Declarative Conditional | Medium | Controlling visibility by | +: : Visibility using Class : : binding ternary operators : +: : Maps : : to the inline `style` : +: : : : attribute. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T5-01: Dynamic Sizing for Custom SVG Icons via CSS + +> **Rule:** Always omit hardcoded dimensional attributes from custom SVG markup. +> Must control icon sizing dynamically using CSS custom properties to ensure +> cross-context scalability. +> +> **What:** Custom SVG icon definitions must not contain hardcoded `width` or +> `height` attributes within the markup. Dimensions should be omitted from the +> SVG template and controlled dynamically via CSS custom properties. +> +> **Applies To:** Lit icon wrapper components (`gr-icon`) and centralized custom +> SVG asset files. +> +> **Why:** Hardcoded dimensions within SVG source strings forced icons into +> rigid sizes, preventing them from scaling properly when nested inside generic +> buttons, dense lists, or varying display contexts. Failing to adhere to this +> typically results in **Layout Clipping / Unresponsive UI**. + +**Trap 1: Embedding static pixel values for width and height inside the raw SVG +literal.** + +**Don't:** + +```typescript +const spark = svg`<svg width="24px" height="24px" viewBox="0 0 960 960">...</svg>`; +``` + +**Do:** + +```typescript +const spark = svg`<svg viewBox="0 0 960 960">...</svg>`; + +// Within gr-icon CSS: +// svg { +// width: var(--gr-icon-size, 20px); +// height: var(--gr-icon-size, 20px); +// } +``` + +-------------------------------------------------------------------------------- + +#### T5-02: Dynamic Width via Content-Driven CSS Sizing + +> **Rule:** Always prefer `width: fit-content` over arbitrary fixed pixel limits +> for dynamically generated UI cards. Never enforce visual uniformity at the +> expense of content readability. +> +> **What:** Use `width: fit-content` for dynamically generated UI cards rather +> than uniform fixed widths when content length is variable and unpredictable. +> +> **Applies To:** UI component styling, specifically CSS rules for lists of +> cards or informational blocks. +> +> **Why:** Fixed-width cards were initially suggested for uniform alignment, but +> variable-length rules caused clipping or excessive whitespace. `fit-content` +> provided a more flexible baseline layout. Failing to adhere to this typically +> results in **Visual Clipping**. + +**Trap 1: Hardcoding width in pixels or percentages to achieve visual uniformity +despite unpredictable content strings.** + +**Don't:** + +```css +/* BAD: Fixed width that might clip */ +.flow-card { + width: 300px; +} +``` + +**Do:** + +```css +/* GOOD: Width determined by content */ +.flow-card { + width: fit-content; +} +``` + +**Exceptions:** Cases where uniform width is strictly mandated by UX mocks and +content is truncated gracefully with ellipses. + +-------------------------------------------------------------------------------- + +#### T5-03: Declarative Conditional Visibility using Class Maps + +> **Rule:** Must use standard CSS utility classes via Lit's `classMap` directive +> to toggle element visibility. Never bind ternary operators to inject inline +> style strings. +> +> **What:** Conditional visibility logic must rely on a standard CSS `.hidden` +> utility class applied via Lit's `classMap` directive, rather than injecting +> inline style strings. +> +> **Applies To:** Component rendering templates (`html` strings) handling +> dynamically hidden UI states. +> +> **Why:** Visibility was frequently controlled via verbose ternary operators +> injecting hardcoded CSS text into `style` attributes, making code harder to +> read and reuse. Failing to adhere to this typically results in **Brittle +> Inline Styling**. + +**Trap 1: Controlling visibility by binding ternary operators to the inline +`style` attribute.** + +**Don't:** + +```typescript +// BAD: Inline conditional styles +<md-icon-button + style=${this.supportsHistory ? '' : 'visibility:hidden; pointer-events:none;'} +> +``` + +**Do:** + +```typescript +// GOOD: Using Lit classMap with a dedicated CSS rule +<md-icon-button + class=${classMap({ + 'history-button': true, + 'hidden': !this.supportsHistory + })} +> +``` + +**Exceptions:** Truly dynamic, mathematically computed layout styles (e.g., +precise pixel offsets or user-defined color themes). + +-------------------------------------------------------------------------------- + +### Cross-Domain Dependencies + +* **Upstream:** T1 | Lit Framework Idioms & State Encapsulation - *Lit + directives like `classMap` provide the declarative mechanism required to + apply standard CSS utility classes effectively.* + +## Chapter: API Integration & Error Handling + +**Context:** This chapter governs the resilient integration of frontend logic +with REST APIs. It establishes strict constraints for maintaining backend +payload parity, explicitly modeling structural variances, and enforcing +centralized error handling over localized `try...catch` blocks. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :-------- | :------------------------ | :------- | :------------------------ | +| **T6-01** | Explicit Server Error | High | Firing mutative API | +: : Reporting in REST API : : requests without : +: : Calls : : explicitly configuring : +: : : : the fetch client to : +: : : : report server-side HTTP : +: : : : errors. : +| **T6-02** | Strict Frontend-Backend | Critical | Using a descriptive but | +: : API Payload Parity : : incorrect property name : +: : : : in the UI that does not : +: : : : match the exact backend : +: : : : JSON key. : +| **T6-03** | Delegation to Centralized | Medium | Wrapping API calls in | +: : REST API Error Handlers : : verbose catch blocks : +: : : : solely to surface generic : +: : : : network failure messages. : +| **T6-04** | Centralized Error | Medium | Wrapping an API call in a | +: : Callbacks for API Calls : : try/catch block and : +: : : : firing a custom error : +: : : : event on catch alongside : +: : : : null checks. : +| **T6-05** | Modeling Inconsistent | Medium | Assuming a single payload | +: : Backend API Payloads : : structure across : +: : : : disparate endpoints and : +: : : : experiencing undefined : +: : : : runtime property access. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T6-01: Explicit Server Error Reporting in REST API Calls + +> **Rule:** Always enable explicit server error reporting for mutating API +> requests (PUT, POST, DELETE) to guarantee backend failures reach the UI. +> +> **What:** State-mutating REST API calls (PUT, POST, DELETE) must explicitly +> enable server error reporting in their configuration to ensure backend +> failures are caught and surfaced to the UI. +> +> **Applies To:** REST API service layer (`gr-rest-api-impl.ts`), specifically +> when migrating to the new `fetch` helper structure. +> +> **Why:** During the migration to a modern REST API helper module, API failures +> for mutating operations could be silently swallowed unless `reportServerError: +> true` was explicitly passed in the request configuration. Failing to adhere to +> this typically results in **Silent API Failures**. + +**Trap 1: Firing mutative API requests without explicitly configuring the fetch +client to report server-side HTTP errors.** + +**Don't:** + +```typescript +this._restApiHelperNew.fetch({ + fetchOptions: getFetchOptions({ + method: HttpMethod.PUT, + body: config, + }), + url, +}); +``` + +**Do:** + +```typescript +this._restApiHelperNew.fetch({ + fetchOptions: getFetchOptions({ + method: HttpMethod.PUT, + body: config, + }), + url, + reportServerError: true, +}); +``` + +-------------------------------------------------------------------------------- + +#### T6-02: Strict Frontend-Backend API Payload Parity + +> **Rule:** Must perfectly mirror the exact naming conventions of backend JSON +> response fields within frontend TypeScript interfaces; never substitute +> assumed names. +> +> **What:** Frontend TypeScript interfaces must perfectly mirror the naming +> conventions of the backend JSON response fields; UI logic cannot substitute +> similar or assumed names. +> +> **Applies To:** REST API models, data layer mappings, and UI conditionals +> consuming backend payloads. +> +> **Why:** A UI feature designed to hide a component when a diff was too large +> failed silently in production because the frontend referenced +> `diffs_too_expensive_to_compute` while a previous implementation or +> documentation referred to `too_expensive_to_compute`, resulting in +> `undefined`. Failing to adhere to this typically results in **Silent UI State +> Failures**. + +**Trap 1: Using a descriptive but incorrect property name in the UI that does +not match the exact backend JSON key.** + +**Don't:** + +```typescript +// BAD: Backend sends 'diffs_too_expensive_to_compute' +if (this.file?.too_expensive_to_compute) { + renderWarning(); +} +``` + +**Do:** + +```typescript +// GOOD: Exact matching property name +if (this.file?.diffs_too_expensive_to_compute) { + renderWarning(); +} +``` + +-------------------------------------------------------------------------------- + +#### T6-03: Delegation to Centralized REST API Error Handlers + +> **Rule:** Never wrap standard REST API calls in explicit `try...catch` blocks +> to handle generic network failures. +> +> **What:** Do not wrap standard REST API calls in explicit try...catch blocks +> to handle generalized network failures, as the global framework centrally +> intercepts these and dispatches user-visible network errors. +> +> **Applies To:** API service integrations and asynchronous handlers making +> network requests. +> +> **Why:** A reviewer expressed concern about unhandled promise rejections if a +> network call failed inside a finally block. The framework is designed +> specifically not to throw standard API errors back to the caller unless +> explicitly opted into via a custom errFn, handling generic network error +> alerting natively. Failing to adhere to this typically results in **Redundant +> Error Boilerplate**. + +**Trap 1: Wrapping API calls in verbose catch blocks solely to surface generic +network failure messages.** + +**Don't:** + +```typescript +try { + await this.restApiService.createFlow(changeNum); +} catch (e) { + fireNetworkError(this, e); +} +``` + +**Do:** + +```typescript +// Fire and let the framework's centralized errFn handle generic failures. +await this.restApiService.createFlow(changeNum); +``` + +**Exceptions:** When a specific component logic must intercept an error +silently, or when a custom `errFn` is explicitly provided to bypass default +handling. + +-------------------------------------------------------------------------------- + +#### T6-04: Centralized Error Callbacks for API Calls + +> **Rule:** Always use built-in centralized error callback mechanisms (`errFn`) +> provided by the REST API service instead of manual `try...catch` blocks. +> +> **What:** Use built-in centralized error callback mechanisms (`errFn`) +> provided by the REST API service instead of wrapping calls in manual +> `try...catch` blocks that result in redundant local error dispatching. +> +> **Applies To:** Asynchronous REST API calls within frontend components. +> +> **Why:** Manual `try...catch` blocks often resulted in duplicate error +> notifications being fired to the user—once for the native fetch rejection by +> the global handler, and once for the custom component fallback. Failing to +> adhere to this typically results in **Duplicate Error Notifications**. + +**Trap 1: Wrapping an API call in a try/catch block and firing a custom error +event on catch alongside null checks.** + +**Don't:** + +```typescript +try { + response = await this.restApiService.getPatchContent(); +} catch (e) { + fireError(this, 'Failed'); + return; +} +if (!response) { fireError(this, 'Failed'); return; } +``` + +**Do:** + +```typescript +const response = await this.restApiService.getPatchContent(errFn); +if (!response) return; +``` + +-------------------------------------------------------------------------------- + +#### T6-05: Modeling Inconsistent Backend API Payloads + +> **Rule:** Must explicitly document and type structural variances with optional +> fields when backend endpoints return disparate keys for the same entity. +> +> **What:** When backend endpoints return structurally differing payload keys +> for the same conceptual entity, the frontend interface must explicitly type +> the variance and track the technical debt, rather than forcing a singular, +> inaccurate type. +> +> **Applies To:** Frontend API interface definitions and REST service layers. +> +> **Why:** Two related backend chat endpoints returned data under different keys +> (`response` vs `chat_response`). Forcing the frontend to expect only +> `response` broke conversation loading, necessitating explicit optional types. +> Failing to adhere to this typically results in **Type Safety Violation**. + +**Trap 1: Assuming a single payload structure across disparate endpoints and +experiencing undefined runtime property access.** + +**Don't:** + +```typescript +// BAD: Forces all endpoints to return 'response' +interface ConversationTurn { + response: ChatResponse; +} +``` + +**Do:** + +```typescript +// GOOD: Accurately model the backend variance and track the tech debt +interface ConversationTurn { + response: ChatResponse; + // TODO: Clean this up - when loadConversation is used we get chat_response instead of response + chat_response?: ChatResponse; +} +``` + +## Chapter: AI Context & Telemetry Payload Optimization + +**Context:** This domain governs the client-side lifecycle and optimization of +data structures sent to AI agents and telemetry pipelines. It strictly dictates +payload deduplication strategies, transparent AI token constraint surfacing, and +rigorous parsing validation to prevent silent context loss. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :-------- | :------------------------ | :------- | :------------------------ | +| **T7-01** | Elimination of Implicit | Medium | Manually injecting the | +: : Telemetry Payload Fields : : current domain/host into : +: : : : the analytics event : +: : : : detail object. : +| **T7-02** | Deduplication of | Medium | Defining telemetry | +: : Telemetry Payload : : interfaces that manually : +: : Attributes : : require context variables : +: : : : easily derivable from the : +: : : : current session or : +: : : : backend data warehouse. : +| **T7-03** | Surface AI Context Prompt | Medium | Rendering an empty | +: : Sizes in the UI : : container or entirely : +: : : : omitting the size : +: : : : calculation for AI prompt : +: : : : payloads. : +| **T7-04** | Validating AI Context | High | Filtering out undefined | +: : Data Parsing : : parsed items before : +: : : : validating if the parsing : +: : : : step encountered : +: : : : failures. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T7-01: Elimination of Implicit Telemetry Payload Fields + +> **Rule:** Never include implicit environmental data like domain or host origin +> in frontend telemetry payloads. Always rely entirely on backend log enrichment +> to capture network request origins. +> +> **What:** Exclude implicit environmental data (e.g., the browser's host or +> domain origin) from explicit frontend telemetry interaction payloads, relying +> instead on backend log enrichment. +> +> **Applies To:** Frontend telemetry and metrics reporting services. +> +> **Why:** Sending fields like `window.location.host` in interaction reporting +> payloads creates redundant data bloat, as the logging infrastructure +> automatically captures the origin of the network request. Failing to adhere to +> this typically results in **Telemetry Payload Bloat**. + +**Trap 1: Manually injecting the current domain/host into the analytics event +detail object.** + +**Don't:** + +```typescript +const details: AiAgentEventDetails = { + host: window.location.host, + agentId, + conversationId: this.conversationId, +}; +this.reportingService.reportInteraction(Interaction.AI_AGENT, details); +``` + +**Do:** + +```typescript +const details: Pick<AiAgentEventDetails, 'agentId' | 'conversationId'> = { + agentId, + conversationId: this.conversationId, +}; +this.reportingService.reportInteraction(Interaction.AI_AGENT, details); +``` + +-------------------------------------------------------------------------------- + +#### T7-02: Deduplication of Telemetry Payload Attributes + +> **Rule:** Must omit redundant context variables from telemetry payloads if +> they can be derived via backend SQL joins. Delegate session identity and role +> resolution to the downstream data pipeline. +> +> **What:** Telemetry payloads sent from frontend components must not include +> redundant context variables (like changeId or userRole) if they can be +> attached via global session context or derived via SQL joins on the backend. +> +> **Applies To:** Frontend reporting services, constants defining EventDetails, +> and UI components firing interaction events. +> +> **Why:** The telemetry payloads for AI interactions manually calculated and +> passed user roles and change IDs, unnecessarily bloating the payload and +> duplicating data already captured globally by the reporting service. Failing +> to adhere to this typically results in **Redundant Payload Bloat**. + +**Trap 1: Defining telemetry interfaces that manually require context variables +easily derivable from the current session or backend data warehouse.** + +**Don't:** + +```typescript +export type AiAgentEventDetails = { + changeId: string; + userRole: 'author' | 'reviewer'; + // ... other fields +}; +``` + +**Do:** + +```typescript +export type AiAgentEventDetails = { + // Rely on global session_id attached by reporting service + agentId: string; + turnIndex: number; +}; +``` + +**Trap 2: Performing complex logic on the client to derive a metric that can be +natively handled using a SQL join during data analysis.** + +**Don't:** + +```typescript +const userRole = this.account?._account_id === this.change.owner._account_id ? 'author' : 'reviewer'; +this.reportingService.reportInteraction('action', { userRole }); +``` + +**Do:** + +* Fire the core event identifier with the minimal context required. Delegate + the user role resolution to the data pipeline via SQL joins using the global + session identity. + +-------------------------------------------------------------------------------- + +#### T7-03: Surface AI Context Prompt Sizes in the UI + +> **Rule:** Always calculate and display prompt capacities (such as word or +> token counts) directly in the UI to make context limits explicit to the user. +> +> **What:** Dialogs or interfaces handling AI prompt generation must calculate +> and explicitly display the prompt size (word or token count) to provide the +> user with clear context limits. +> +> **Applies To:** AI prompt dialogue components and context builders. +> +> **Why:** Failing to display prompt sizes left users blind to context +> limitations, leading to rejected backend payloads or silently truncated +> context when the payload exceeded token bounds. Failing to adhere to this +> typically results in **Unpredictable AI Truncation**. + +**Trap 1: Rendering an empty container or entirely omitting the size calculation +for AI prompt payloads.** + +**Don't:** + +```html +<div class="actions"> + <div class="size"></div> + <gr-button>Copy Prompt</gr-button> +</div> +``` + +**Do:** + +```html +<div class="actions"> + <div class="size"> + ${this.calculatePromptWordCount(this.promptText)} words + </div> + <gr-button>Copy Prompt</gr-button> +</div> +``` + +-------------------------------------------------------------------------------- + +#### T7-04: Validating AI Context Data Parsing + +> **Rule:** Must assert the full structural integrity of parsed AI context +> datasets before executing any filtering logic. Never silently drop undefined +> items without evaluating parsing failures. +> +> **What:** When parsing unstructured or semi-structured data for AI context +> payloads, validation logic must assert the integrity of the entire dataset +> before filtering out invalid entries. +> +> **Applies To:** Frontend chat panels and AI context item parsers. +> +> **Why:** Historically, the application filtered out undefined context items +> immediately after parsing. This swallowed partial parsing failures, meaning +> users were not alerted when some of their context links failed to load, +> leading to degraded AI prompts. Failing to adhere to this typically results in +> **Silent Data Loss**. + +**Trap 1: Filtering out undefined parsed items before validating if the parsing +step encountered failures.** + +**Don't:** + +```typescript +// BAD: The check evaluates the array after undefined items are already removed. +const contextItems = (links ?? []) + .map(link => parseLink(link)) + .filter(isDefined); + +if (contextItems.some(item => !item)) { + fireAlert('Failed to parse links.'); +} +``` + +**Do:** + +```typescript +// GOOD: Count items before and after filtering to detect failures, or check the mapped array prior to filtering. +const parsedItems = (links ?? []).map(link => parseLink(link)); +const validItems = parsedItems.filter(isDefined); + +if (links.length > 0 && validItems.length === 0) { + fireAlert('Failed to parse one or more context item links.'); +} +```
diff --git a/configs/skills/gerrit_hygiene_operations/SKILL.md b/configs/skills/gerrit_hygiene_operations/SKILL.md new file mode 100644 index 0000000..095cfe4 --- /dev/null +++ b/configs/skills/gerrit_hygiene_operations/SKILL.md
@@ -0,0 +1,538 @@ +--- +name: gerrit-hygiene-operations +description: Provides rules, patterns, and best practices for code hygiene, formatting, downstream plugin dependencies, and release operations in Gerrit. +--- + +# Gerrit Hygiene & Operations Engineering Guide + +## Executive Summary + +Welcome to the authoritative engineering guide for maintaining code hygiene, +managing downstream deployments, and optimizing operations within the codebase. +This living repository serves as the definitive source of tribal knowledge aimed +at preventing technical debt and keeping our development ecosystem scalable, +predictable, and resilient against integration friction. + +The domains covered in this payload target the most common sources of technical +debt, pipeline flakiness, and deployment desynchronization. You will find +mandates covering the strict encapsulation of proprietary infrastructure to +protect external contributors from opaque tooling, strategies for eliminating +visual regression test flakiness, and protocols for dead-code elimination during +experiment decommissioning. Furthermore, it outlines performance optimization +standards—such as client-side request parallelization—and mandates strict CI/CD +hygiene via TypeScript formatting rules and automated release note generation. + +Engineers are expected to internalize these guidelines when architecting UI +components, authoring API documentation, or coordinating downstream releases. +Adhering to these principles directly reduces deployment noise, ensures a +seamless developer experience for both internal and open-source contributors, +and fortifies the integrity of our continuous integration pipelines. + +## Summary + +| Chapter Theme / Title | Scope & Objective | +| :---------------------------------- | :------------------------------------- | +| **Downstream Ecosystem Deployment | Governs the coordination of release | +: Synchronization** : timelines and cross-project dependency : +: : management for external plugins across : +: : isolated, downstream Gerrit : +: : deployments. Requires strict auditing : +: : to prevent integration failures during : +: : core platform rollouts. : +| **TypeScript Code Formatting & | Defines structural code style and | +: Syntax Normalization** : linting mandates for frontend : +: : TypeScript components. Strict : +: : enforcement of formatting rules, such : +: : as line-length constraints, ensures : +: : optimal diff readability and prevents : +: : automated CI pipeline failures. : +| **Proprietary Infrastructure | Establishes strict boundaries for | +: Encapsulation** : documenting public APIs by forbidding : +: : the leakage of proprietary backend : +: : paths or internal corporate URLs. : +: : Ensures environment encapsulation and : +: : prevents exposing dead links or opaque : +: : references to open-source : +: : contributors. : +| **Client-Side Request | Governs the optimization of frontend | +: Parallelization** : loading metrics by transitioning : +: : monolithic or batched server-side : +: : queries into parallelized, : +: : asynchronous client-side fan-out : +: : requests. Mandates concurrent : +: : execution to improve performance and : +: : simplify caching logic. : +| **Experiment Decommissioning & Dead | Dictates the mandatory cleanup | +: Code Elimination** : required when promoting successful : +: : experimental features to default : +: : behavior. Enforces the strict : +: : elimination of legacy fallback : +: : methods, feature flag evaluations, and : +: : outdated service dependencies to : +: : prevent dead code accumulation. : +| **Commit Metadata & Release Note | Mandates the injection of structured | +: Automation** : git footers into commit messages. : +: : Strict adherence ensures that : +: : automated CI/CD pipelines successfully : +: : parse and generate accurate changelogs : +: : and release notes without manual : +: : intervention. : +| **Visual Regression Test | Governs strategies for eliminating | +: Determinism** : visual regression test flakiness by : +: : deliberately orchestrating : +: : deterministic, transitional UI states. : +: : Establishes the standard of : +: : intentionally omitting API mocks to : +: : reliably capture loading states during : +: : screenshot baseline generation. : + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- + +## Chapter: Downstream Ecosystem Deployment Synchronization + +**Context:** This chapter governs the coordination of release timelines and +cross-project dependency management for external plugins across isolated, +downstream Gerrit deployments. Strict auditing of these external ecosystems is +required to prevent integration failures when rolling out core platform updates. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :-------- | :---------------------- | :------- | :-------------------------- | +| **T1-01** | Downstream Plugin | High | Proceeding with a core | +: : Dependency Verification : : release timeline without : +: : During Core Rollout : : verifying downstream custom : +: : : : plugin compatibility. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T1-01: Downstream Plugin Dependency Verification During Core Rollout + +> **Rule:** Always audit and verify the compatibility of cross-project +> dependencies, such as custom plugins, before executing core release updates to +> isolated downstream instances. +> +> **What:** Before rolling out core release updates to isolated downstream +> ecosystem instances, cross-project dependencies (e.g., custom plugins) must be +> explicitly audited and verified for compatibility. +> +> **Applies To:** Release management and deployment synchronization to +> downstream environments (e.g., Chromium, pdfium, v8). +> +> **Why:** Deploying core updates without simultaneously auditing and upgrading +> heavily used plugins (like avatars-external) in downstream installations +> historically risked deployment delays and integration failures. Failing to +> adhere to this typically results in **Deployment Desynchronization**. + +**Trap 1: Proceeding with a core release timeline without verifying downstream +custom plugin compatibility.** + +**Don't:** + +* Roll out the core release independently of external plugin compatibility + states. + +**Do:** + +* Audit and flag downstream plugin requirements (e.g., avatars-external) for + updates prior to or alongside the core release. + +## Chapter: TypeScript Code Formatting & Syntax Normalization + +**Context:** This section defines the structural code style and linting mandates +for frontend TypeScript components. Strict enforcement of formatting rules, such +as line-length constraints, ensures optimal diff readability and prevents +automated CI pipeline failures. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :-------- | :--------------------- | :------- | :------------------------ | +| **T2-01** | Strict Line Length | Medium | Chaining long promise | +: : Formatting : : callbacks or variable : +: : : : assignments on a single : +: : : : line. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T2-01: Strict Line Length Formatting + +> **Rule:** Always format TypeScript files to strictly adhere to linting limits +> by wrapping long chained expressions. Never commit code that triggers structural +> style violations. +> +> **What:** TypeScript frontend files must strictly adhere to linting standards +> by properly wrapping long chained expressions. +> +> **Applies To:** TypeScript UI components (e.g., Lit elements like +> `gr-reply-dialog.ts`). +> +> **Why:** Inconsistent formatting and over-extended lines caused unnecessary diff +> noise and failed automated linting checks in the frontend CI pipeline. Failing +> to adhere to this typically results in **Linting Pipeline Failure**. + +**Trap 1: Chaining long promise callbacks or variable assignments on a single +line.** + +**Don't:** + +```typescript +return this.saveReview(reviewInput, errFn).then(result => { +``` + +**Do:** + +```typescript +return this.saveReview(reviewInput, errFn) + .then(result => { +``` + +## Chapter: Proprietary Infrastructure Encapsulation + +**Context:** This domain establishes strict boundaries for documenting +public-facing APIs, Enums, and interface definitions by explicitly forbidding +the leakage of proprietary backend paths (e.g., `google3/`) or internal +corporate URLs. Adherence ensures environment encapsulation and prevents +exposing dead links or opaque references to open-source contributors. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :-------- | :------------------------ | :------- | :------------------------ | +| **T3-01** | Omission of Proprietary | Medium | Pasting internal | +: : Infrastructure Paths from : : repository file paths : +: : Enum Documentation : : into the docblock of an : +: : : : interface or Enum. : +| **T3-02** | Exclusion of Internal | Medium | Using proprietary | +: : Code Search URIs from : : internal code search URLs : +: : Interface Types : : to provide examples of : +: : : : acceptable payload : +: : : : strings. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T3-01: Omission of Proprietary Infrastructure Paths from Enum Documentation + +> **Rule:** Always strip internal directory paths from JSDoc or TSDoc comments +> when defining public-facing data models. +> +> **What:** JSDoc or TSDoc comments defining data models (e.g., Enums) must not +> leak proprietary, internal backend repository or directory paths. +> +> **Applies To:** Frontend API definitions, specifically Protobuf-mapped Enums. +> +> **Why:** Internal directory paths (e.g., google3/.../proto) were mistakenly +> included in open-source frontend code documentation, breaking environment +> encapsulation. Failing to adhere to this typically results in **Information +> Leakage**. + +**Trap 1: Pasting internal repository file paths into the docblock of an +interface or Enum.** + +**Don't:** + +```typescript +/** + * Enum to match the Action proto from CRUAS. + * google3/path/to/internal/service/proto/file.proto. + */ +export enum ActionEnum { ... } +``` + +**Do:** + +```typescript +/** + * Enum to match the Action proto from CRUAS. + */ +export enum ActionEnum { ... } +``` + +#### T3-02: Exclusion of Internal Code Search URIs from Interface Types + +> **Rule:** Never include proprietary code search URLs when documenting type +> definitions or payload schemas. +> +> **What:** Code comments must not contain URL links to internal code search +> tools or proprietary source depots when documenting interface field types. +> +> **Applies To:** API interface declarations and payload schemas (e.g., +> `ContextItem`). +> +> **Why:** A developer linked directly to a proprietary source code URI +> (source.corp.google.com) to explain a type ID field, rendering the +> documentation inaccessible and useless for external open-source contributors. +> Failing to adhere to this typically results in **Dead Link / Opacity**. + +**Trap 1: Using proprietary internal code search URLs to provide examples of +acceptable payload strings.** + +**Don't:** + +```typescript +// type_id should match the types here: https://source.corp.google.com/piper///depot/google3/... +type_id: string; +``` + +**Do:** + +```typescript +// type_id should map to standard application contexts (e.g., 'gerrit_change', 'bug_tracker'). +type_id: string; +``` + +## Chapter: Client-Side Request Parallelization + +**Context:** This chapter governs the optimization of frontend loading metrics +by transitioning monolithic or batched server-side queries into parallelized, +asynchronous client-side fan-out requests. This approach mandates client-side +concurrent execution to improve dashboard performance and simplify downstream +caching logic. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :-------- | :---------------------- | :------- | :--------------------- | +| **T4-01** | Client-Side Fan-out for | High | Forwarding an array of | +: : Dashboard Query : : queries to a dedicated : +: : Processing : : multi-query endpoint : +: : : : method. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T4-01: Client-Side Fan-out for Dashboard Query Processing + +> **Rule:** Always map and execute multiple backend query requests concurrently +> using client-side parallelization constructs. Never rely on batched, +> single-request multi-query endpoints to aggregate data. +> +> **What:** Multiple backend query requests must be mapped and executed +> concurrently using Promise.all on the client side, rather than relying on a +> single, batched multi-query backend endpoint. +> +> **Applies To:** API Service layer (`GrRestApiServiceImpl`), specifically +> dashboard data fetching routines. +> +> **Why:** A legacy approach prefetched batched queries in the backend, which +> complicated caching and backend logic. Shifting to client-side parallel +> requests explicitly improved the DashboardDisplayed performance metric. +> Failing to adhere to this typically results in **Performance Degradation**. + +**Trap 1: Forwarding an array of queries to a dedicated multi-query endpoint +method.** + +**Don't:** + +```typescript +return this.getChangesForMultipleQueries(changesPerPage, queries, offset, options); +``` + +**Do:** + +```typescript +const requestPromises = queries.map(query => + this.getChanges(changesPerPage, query, offset, options) +); +return Promise.all(requestPromises).then(results => { + if (results.includes(undefined)) return undefined; + return results as ChangeInfo[][]; +}); +``` + +-------------------------------------------------------------------------------- + +### Cross-Domain Dependencies + +* **Downstream:** T5 | Experiment Decommissioning & Dead Code Elimination - + *Transitioning to client-side fan-outs routinely exposes obsolete backend + batching mechanisms and experimental feature flags that must be subsequently + purged.* + +## Chapter: Experiment Decommissioning & Dead Code Elimination + +**Context:** This domain governs the mandatory cleanup required when promoting +successful experimental features to default behavior. It enforces the strict +elimination of legacy fallback methods, feature flag evaluations, and outdated +service dependencies to prevent dead code accumulation. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :-------- | :------------------------ | :------- | :------------------------ | +| **T5-01** | Aggressive Pruning of | Medium | Leaving the legacy | +: : Decommissioned Experiment : : fallback method in the : +: : Fallbacks : : class after removing the : +: : : : experiment flag toggle : +: : : : and its invocation block. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T5-01: Aggressive Pruning of Decommissioned Experiment Fallbacks + +> **Rule:** Must completely delete all fallback methods, feature flags, and +> obsolete service dependencies immediately upon graduating an experimental +> feature to default behavior. +> +> **What:** When an experimental feature is promoted to default behavior, all +> corresponding flag evaluations (`KnownExperimentId`), service dependencies +> (`FlagsService`), and obsolete fallback routing methods must be entirely +> deleted. +> +> **Applies To:** Service layer components and API implementations during +> feature flag graduation. +> +> **Why:** Failing to aggressively remove obsolete fallback methods (like +> `getChangesForMultipleQueries`) after adopting client-side parallel requests +> natively led to dead code and confusion over correct API utilization. Failing +> to adhere to this typically results in **Technical Debt Accumulation**. + +**Trap 1: Leaving the legacy fallback method in the class after removing the +experiment flag toggle and its invocation block.** + +**Don't:** + +* Remove the `if (experimentEnabled)` evaluation, but leave + `getChangesForMultipleQueries(queries)` declared in the class definition. + +**Do:** + +* Delete the deprecated fallback method `getChangesForMultipleQueries` + entirely to ensure no other components attempt to invoke it. + +-------------------------------------------------------------------------------- + +### Cross-Domain Dependencies + +* **Upstream:** T4 | Client-Side Request Parallelization - *Transitioning to + parallelized client-side requests triggers the deprecation and subsequent + cleanup of legacy monolithic server-side queries.* + +## Chapter: Commit Metadata & Release Note Automation + +**Context:** This section mandates the injection of structured git footers into +commit messages. Strict adherence ensures that automated CI/CD pipelines +successfully parse and generate accurate changelogs and release notes without +manual intervention. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :-------- | :---------------------- | :------- | :-------------------------- | +| **T6-01** | Mandatory Release-Notes | High | Omitting the footer | +: : Footer Injection : : entirely or formatting the : +: : : : release note description as : +: : : : a multi-line paragraph. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T6-01: Mandatory Release-Notes Footer Injection + +> **Rule:** Always append a properly formatted, single-line `Release-Notes:` +> footer to all commits. While a descriptive note is valuable for significant +> updates, using `Release-Notes: skip` is completely acceptable and common for +> minor UI changes, styling tweaks, small fixes, or any change where a detailed +> public changelog entry is not necessary. +> +> **What:** Commit messages must include a properly formatted, single-line +> 'Release-Notes:' footer to satisfy CI/CD submit requirements and trigger +> automated changelog generation. If a change is minor or a public release note +> is not needed (even for user-facing adjustments like adding spacing to prevent +> accidental clicks), using the value 'skip' is fully valid and acceptable. +> +> **Applies To:** Git commit messages for all changes, including frontend UI, +> backend, and configuration updates. +> +> **Why:** Without a dedicated footer, automated release note parsers or submission +> checks can fail. However, not every commit needs a public release note; hence, +> `Release-Notes: skip` is supported to keep git history clean and satisfy automation +> without generating unnecessary public changelog entries. + +**Trap 1: Omitting the footer entirely or formatting the release note +description as a multi-line paragraph.** + +**Don't:** + +* Submit a commit without an explicit `Release-Notes:` footer, or spread the + release note description across multiple lines in the commit body. + +**Do:** + +* For major features or configuration changes: Append a single-line footer with a + descriptive summary, e.g.: + `Release-Notes: Add config to control if review footers should be included into the commit message on submit` +* For minor adjustments (such as fixing accidental clicks by adding spacing), + trivial fixes, or refactorings: A detailed release note description can still be + provided if desired, but appending `Release-Notes: skip` is completely acceptable. + +**Exceptions:** None. The `Release-Notes:` footer must be present on all commits, +but the value `skip` is always acceptable. + +## Chapter: Visual Regression Test Determinism + +**Context:** This chapter governs the strategies for eliminating visual +regression test flakiness by deliberately orchestrating deterministic, +transitional UI states. Specifically, it establishes the standard of +intentionally omitting API mocks to reliably capture loading states during +screenshot baseline generation. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :-------- | :---------------------- | :------- | :-------------------------- | +| **T7-01** | Intentional Omission of | High | Mocking all APIs by default | +: : Mocks for Deterministic : : in a screenshot test, : +: : UI Baselines : : causing a race condition : +: : : : where the mock resolves : +: : : : faster than the screenshot : +: : : : is taken. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T7-01: Intentional Omission of Mocks for Deterministic UI Baselines + +> **Rule:** Always omit API mocks when attempting to reliably capture +> transitional UI states (such as loading spinners) in visual regression tests. +> Never provide mock responses if the objective is to baseline a pre-resolved, +> asynchronous view. +> +> **What:** To capture stable, deterministic transitional UI states (like +> loading spinners) in visual regression tests, the underlying API endpoints +> must be intentionally left unmocked. +> +> **Applies To:** Screenshot baseline testing (e.g., `_screenshot_test.ts`) for +> asynchronous UI components. +> +> **Why:** Capturing fully resolved UI states was difficult to synchronize, +> causing flaky screenshot tests. Relying on an unmocked API ensures the +> component hangs reliably in the 'loading' state, producing a stable visual +> baseline for the loader. Failing to adhere to this typically results in **Test +> Flakiness**. + +**Trap 1: Mocking all APIs by default in a screenshot test, causing a race +condition where the mock resolves faster than the screenshot is taken.** + +**Don't:** + +* Providing mock responses for the API in order to capture the transitional + loading state, introducing timing-based flakiness. + +**Do:** + +* Leave the data-fetching API unmocked so the component indefinitely hangs in + its transitional `loading` state, yielding a 100% deterministic screenshot + of the loader.
diff --git a/configs/skills/gerrit_system_logic/SKILL.md b/configs/skills/gerrit_system_logic/SKILL.md new file mode 100644 index 0000000..823ff4c --- /dev/null +++ b/configs/skills/gerrit_system_logic/SKILL.md
@@ -0,0 +1,2559 @@ +--- +name: gerrit-system-logic +description: Provides rules, patterns, and best practices for Gerrit backend system logic, Java APIs, performance, and correctness. +--- + +# System Logic & Correctness Engineering Guide + +## Executive Summary + +This engineering guide serves as the definitive reference for maintaining system +logic and operational correctness within Gerrit's repository management and +code review infrastructure. It exists to capture critical +historical tribal knowledge, prevent the regression of known failure modes—such +as JGit thread-safety violations or NoteDb schema lock-ins—and establish strict, +non-negotiable architectural boundaries for newly contributed code. By +standardizing these patterns, the guide provides incoming engineers with the +necessary context to safely navigate and modify highly concurrent, +state-dependent subsystems. + +Historically, undocumented assumptions surrounding asynchronous task resolution, +object cache sizes, and concurrent worker pools led to silent data omissions, +degraded latency, and complex race conditions. To mitigate this, the guide +enforces strict constraints around execution environments, including explicit +per-thread JGit object isolation, decoupled persistence models, and two-step +schema rollouts. It additionally hardens the system's security posture by +formalizing impersonation tracking, standardizing Contributor License Agreement +(CLA) checks, and preventing identity-cycling vulnerabilities through rigorous +account validation protocols. + +The overarching technical domains covered within this repository span concurrent +thread safety, resilient NoteDb serialization and Protobuf schema evolution, +optimized REST API payload handling, deterministic Guice-based test sandboxing, +and Bazel build infrastructure alignment. Adhering to these documented policies +guarantees that the platform remains scalable, secure, and resilient against +regressions across both internal infrastructure workflows and distributed +cluster upgrades. + +## Summary + +| Chapter Theme / Title | Scope & Objective | +| :----------------------------------- | :------------------------------------ | +| **JGit Concurrency & Thread Safety** | To prevent severe race conditions and | +: : data corruption during parallel : +: : processing, Gerrit requires strict : +: : per-thread isolation of JGit : +: : resources like `RevWalk`, : +: : `Repository`, and `ObjectReader`. : +: : Never share these non-thread-safe : +: : instances across `ExecutorService` : +: : boundaries or parallel streams. : +| **NoteDb Serialization & Schema | Governs the serialization and schema | +: Evolution** : evolution strategies for persisting : +: : Gerrit change metadata to Git notes : +: : (NoteDb). Strictly enforces two-step : +: : schema rollouts, decoupled transfer : +: : objects, and permissive JSON parsing : +: : to guarantee data integrity across : +: : distributed cluster upgrades. : +| **Query Engine Performance & | Governs the configuration, | +: Operator Enforcement** : evaluation, and strict syntactic : +: : enforcement of query predicates : +: : within the repository engine. Focuses : +: : on decoupling strict user-facing : +: : evaluation constraints from : +: : background observability and : +: : implementing phased, non-blocking : +: : rollouts to prevent configuration : +: : deadlocks. : +| **Access Control & Impersonation | Governs the evaluation and auditing | +: Security** : of impersonated access operations : +: : (e.g., `SUBMIT_AS` and `RUN_AS`). : +: : Defines strict mechanisms for : +: : isolating the initiating "real user" : +: : from the "impersonated user" during : +: : dynamic permission checks, and : +: : mandates automated, centralized : +: : logging in NoteDb and Protobuf to : +: : guarantee uncorrupted audit trails. : +| **Account Lifecycle & Vulnerability | Defines constraints for handling | +: Mitigation** : deleted or recreated user accounts to : +: : prevent identity-cycling exploits, : +: : specifically isolating validation : +: : checks and filtering orphaned : +: : approvals during merge operations : +: : without degrading system performance. : +| **REST API Resource Routing & | REST API endpoints must strictly | +: Payload Optimization** : reflect hierarchical entity : +: : relationships while optimizing JSON : +: : payloads to minimize bandwidth and : +: : processing latency. Modifications to : +: : serialization pipelines must : +: : explicitly gate expensive permission : +: : checks and gracefully handle : +: : redundant or experimental fields : +: : without causing UI ambiguity. : +| **Build Infrastructure & Dependency | Governs the configuration, | +: Alignment** : versioning, and migration of the : +: : build system infrastructure, focusing : +: : heavily on Bazel modules (`bzlmod`) : +: : and dependency graphs. Establishes : +: : constraints to ensure strict version : +: : alignment, prevent compliance : +: : pipeline stalls, and maintain : +: : reliable developer bootstrapping : +: : environments. : +| **Asynchronous Notification | Asynchronous notification pipelines | +: Consistency** : must capture exact entity state at : +: : the moment of initialization rather : +: : than fetching it dynamically during : +: : execution. This guarantees data : +: : consistency and prevents delayed : +: : background tasks from inadvertently : +: : processing future, out-of-band : +: : updates from persistent storage. : +| **Test Suite Configuration & | Dictates the implementation of | +: Isolation** : parameterized integration tests, : +: : strict Guice dependency injection, : +: : and granular flaky test isolation. : +: : Ensures deterministic test coverage : +: : across multiple backend : +: : configurations without leaking : +: : generic APIs or brittle global : +: : states. : +| **Diff Engine Computation Limits** | Explicitly tracking timeouts and | +: : fallback states for expensive file : +: : diff computations guarantees that : +: : frontend clients are alerted to : +: : incomplete operations. This prevents : +: : silent data omission and ensures : +: : accurate representation of modified : +: : files in the user interface. : +| **Concurrent Formatting & Caching** | Parallelizing expensive API | +: : formatting requires strict adherence : +: : to explicit thread-local context : +: : propagation, cache isolation for : +: : mutated elements, and collection : +: : pre-allocation. Failing to manage : +: : concurrency strictly leads to context : +: : leakage across parallel streams, : +: : memory overhead, and poisoned UI : +: : states. : +| **Protobuf Schema Management & | Governs the mapping between Java | +: Conversion** : entities and Protobuf messages, : +: : mandating automated reflection-based : +: : validation and standardized converter : +: : abstractions to prevent cache : +: : inconsistencies and silent field : +: : loss. : +| **Compliance & CLA Enforcement** | Strictly enforces Contributor License | +: : Agreement (CLA) checks across all : +: : administrative REST API endpoints : +: : that modify project configurations : +: : and access rules. This guarantees : +: : compliance consistency across both : +: : direct code pushes and API-driven : +: : administrative actions. : + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- + +## Chapter: JGit Concurrency & Thread Safety + +**Context:** To prevent severe race conditions and data corruption during +parallel processing, Gerrit requires strict per-thread isolation of JGit +resources like `RevWalk`, `Repository`, and `ObjectReader`. Never share these +non-thread-safe instances across `ExecutorService` boundaries or parallel +streams. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :-------- | :------------------------ | :------- | :------------------------ | +| **T1-01** | Strict Thread Isolation | Critical | Passing a shared | +: : of JGit RevWalk Instances : : `RevWalk` instance from : +: : : : the main thread into : +: : : : worker tasks submitted to : +: : : : an executor. : +| **T1-02** | Thread-Safe JGit | Critical | Passing a single shared | +: : Repository Instantiation : : JGit Repository instance : +: : for Concurrent : : to multiple asynchronous : +: : Computation : : worker threads. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T1-01: Strict Thread Isolation of JGit RevWalk Instances + +> **Rule:** Always instantiate JGit objects like `RevWalk`, `Repository`, and +> `ObjectReader` per-thread when parallelizing tasks. Never share a single +> instance across worker threads in an `ExecutorService`. +> +> **What:** JGit objects like `RevWalk`, `Repository`, and `ObjectReader` are +> not thread-safe and must be strictly instantiated per-thread when +> parallelizing tasks via an `ExecutorService`. +> +> **Applies To:** JGit operations, specifically diff computation and object +> parsing within multi-threaded ExecutorServices. +> +> **Why:** Sharing a single RevWalk instance across parallel diff evaluation +> tasks led to severe race conditions, resulting in corrupted diff results, +> application crashes, or thread deadlocks. Failing to adhere to this typically +> results in **Race Conditions / Data Corruption**. + +**Trap 1: Passing a shared `RevWalk` instance from the main thread into worker +tasks submitted to an executor.** + +**Don't:** + +```java +// BAD: Sharing a single RevWalk across executor threads +RevWalk sharedRw = new RevWalk(repo); +for (Key key : keys) { + executor.submit(() -> evaluator.execute(key, sharedRw)); +} +``` + +**Do:** + +```java +// GOOD: Opening a new Repository and RevWalk inside the submitted task +for (Key key : keys) { + executor.submit(() -> { + try (Repository threadRepo = repoManager.openRepository(project); + ObjectReader threadReader = threadRepo.newObjectReader(); + RevWalk threadRw = new RevWalk(threadReader)) { + return evaluator.execute(key, threadRw); + } + }); +} +``` + +-------------------------------------------------------------------------------- + +#### T1-02: Thread-Safe JGit Repository Instantiation for Concurrent Computation + +> **Rule:** Must open individual `Repository` instances for each worker thread +> when parallelizing background computations. +> +> **What:** JGit's `openRepository` returns a `Repository` object that is not +> thread-safe. When parallelizing computations (such as file diffing), each +> thread or worker must instantiate its own isolated repository handle rather +> than sharing a single instance. +> +> **Applies To:** Background workers, parallel stream processors, and +> `DiffExecutor` implementations interacting with JGit `Repository` instances. +> +> **Why:** Historically, reusing a single `Repository` handle across multiple +> threads during parallel cache generation caused unpredictable speed-ups, race +> conditions, and corrupted object reads. Failing to adhere to this typically +> results in **Race Condition / State Corruption**. + +**Trap 1: Passing a single shared JGit Repository instance to multiple +asynchronous worker threads.** + +**Don't:** + +```java +Repository sharedRepo = repoManager.openRepository(project); +for (FileDiffCacheKey key : keys) { + executor.submit(() -> computeDiff(sharedRepo, key)); +} +``` + +**Do:** + +```java +for (FileDiffCacheKey key : keys) { + executor.submit(() -> { + try (Repository workerRepo = repoManager.openRepository(project)) { + return computeDiff(workerRepo, key); + } + }); +} +``` + +-------------------------------------------------------------------------------- + +### Cross-Domain Dependencies + +* **Downstream:** T10 | Diff Engine Computation Limits - *Parallelizing file + diff computations requires strict per-thread JGit instantiation to safely + populate the FileDiffCache without race conditions.* +* **Downstream:** T11 | Concurrent Formatting & Caching - *Parallel streams + executing asynchronous tasks must internally allocate thread-local + Repository handles to prevent state leakage.* + +## Chapter: NoteDb Serialization & Schema Evolution + +**Context:** This chapter governs the serialization and schema evolution +strategies for persisting Gerrit change metadata to Git notes (NoteDb). It +strictly enforces two-step schema rollouts, decoupled transfer objects, and +permissive JSON parsing to guarantee data integrity across distributed cluster +upgrades. + +### Summary + +| Rule ID | Principle / | Priority | Primary Symptom / Trap | +: : Constraint : : : +| :-------- | :----------------- | :------- | :------------------------------- | +| **T2-01** | Two-Step Rollouts | Critical | Updating reading and writing | +: : for Cached : : logic simultaneously without : +: : Serialization : : accounting for mixed-version : +: : Schemas : : cluster environments. : +| **T2-02** | Forward-Compatible | High | Deploying a strict JSON parser | +: : NoteDb JSON : : that throws exceptions upon : +: : Parsing : : encountering undocumented : +: : : : fields. : +| **T2-03** | Decoupling | High | Serializing an internal database | +: : Persistent Storage : : representation straight to a Git : +: : Format from Legacy : : storage blob. : +: : DB Entities : : : +| **T2-04** | Push Certificate | High | Using negative boolean logic to | +: : Isolation by : : conditionally process metadata : +: : Comment Status : : that only applies to specific : +: : : : data domains. : +| **T2-05** | Industry-Standard | Medium | Using non-standard slang to | +: : Terminology for : : differentiate older formatting : +: : Deprecated Logic : : methods from modern JSON ones. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T2-01: Two-Step Rollouts for Cached Serialization Schemas + +> **Rule:** Always execute a two-step rollout and explicitly increment the cache +> version when mutating persistent cache schemas. +> +> **What:** Schema mutations in persistent caches must follow a two-step +> rollout: Step 1 introduces reader support for the new schema while continuing +> to write the old schema. Step 2 enables the new writer logic. A cache version +> increment is required. +> +> **Applies To:** NoteDb serialization, `ChangeNotesState`, `ChangeNotesCache`. +> +> **Why:** If a schema change is deployed without a two-step rollout, older +> instances of the application during a rolling restart will encounter cache +> records written by updated instances. Unable to parse the new schema, the +> older binaries will crash or drop data. Failing to adhere to this typically +> results in **Deserialization Crash**. + +**Trap 1: Updating reading and writing logic simultaneously without accounting +for mixed-version cluster environments.** + +**Don't:** + +* Deploying a single patchset that modifies both the serialization and + deserialization formats of a persistent cache object without bumping the + cache version. + +**Do:** + +* Deploying the schema change in two phases: first patchset updates + deserializers to handle both V1 and V2, and explicitly bumps the cache + version. A subsequent patchset updates the serializers to emit V2. + +-------------------------------------------------------------------------------- + +#### T2-02: Forward-Compatible NoteDb JSON Parsing + +> **Rule:** Must configure JSON parsers to be inherently permissive and safely +> ignore unknown fields when deserializing NoteDb revision notes. +> +> **What:** JSON parsers reading NoteDb revision notes must be inherently +> permissive, safely ignoring unknown fields to ensure system resilience against +> future metadata schema expansions. +> +> **Applies To:** NoteDb deserialization algorithms parsing Git blob byte arrays +> into JSON objects. +> +> **Why:** When introducing a new JSON-based payload format for persistent +> Gerrit metadata, strict parsing risked immediately breaking older service +> instances or external consumers whenever a new, valid metadata field was +> appended to the payload. Failing to adhere to this typically results in +> **Parsing Exception**. + +**Trap 1: Deploying a strict JSON parser that throws exceptions upon +encountering undocumented fields.** + +**Don't:** + +```java +// BAD: Strict parsing crashes on new schema properties +Gson strictGson = new GsonBuilder().create(); +RevisionNoteData data = strictGson.fromJson(reader, RevisionNoteData.class); +``` + +**Do:** + +```java +// GOOD: Permissive parsing explicitly ignoring unknown fields + regression tests verifying this behavior +Gson permissiveGson = new GsonBuilder().create(); +RevisionNoteData data = permissiveGson.fromJson(reader, RevisionNoteData.class); +``` + +-------------------------------------------------------------------------------- + +#### T2-03: Decoupling Persistent Storage Format from Legacy DB Entities + +> **Rule:** Never directly serialize legacy relational database objects into Git +> persistent storage. +> +> **What:** Data models persisted to Git (NoteDb) must utilize dedicated, +> format-specific DTOs rather than directly serializing legacy relational +> database objects. +> +> **Applies To:** NoteDb JSON Serialization pipelines and storage layer +> architecture. +> +> **Why:** Initially, legacy ReviewDb models were mapped directly into JSON +> payloads. This inherited awkward relational quirks (e.g., missing critical +> keys that were traditionally injected at runtime) and locked the new storage +> schema to the constraints of an outgoing SQL-based design. Failing to adhere +> to this typically results in **Schema Lock-In**. + +**Trap 1: Serializing an internal database representation straight to a Git +storage blob.** + +**Don't:** + +```java +// BAD: Serializing legacy database class +PatchLineComment comment = db.getComment(); +gson.toJson(comment, outputStream); +``` + +**Do:** + +```java +// GOOD: Mapping to a decoupled storage DTO +RevisionNoteData.Comment noteComment = new RevisionNoteData.Comment(comment, serverId); +gson.toJson(noteComment, outputStream); +``` + +-------------------------------------------------------------------------------- + +#### T2-04: Push Certificate Isolation by Comment Status + +> **Rule:** Must strictly map and extract push certificates exclusively when +> evaluating PUBLISHED comment records. +> +> **What:** Push certificates must be strictly mapped and extracted only when +> evaluating PUBLISHED comment records, as they inherently do not exist in the +> isolated user refs storing DRAFT data. +> +> **Applies To:** NoteDb revision note parsing logic handling cryptographic push +> metadata extraction. +> +> **Why:** Push certificates are persisted exclusively in the main change meta +> ref. Draft comments are inherently personal and physically stored in isolated +> refs within `All-Users`. Parsing logic initially failed to explicitly connect +> the certificate to the published context, risking logically malformed data +> associations. Failing to adhere to this typically results in **Metadata +> Corruption**. + +**Trap 1: Using negative boolean logic to conditionally process metadata that +only applies to specific data domains.** + +**Don't:** + +```java +if (!draftsOnly) { + pushCert = parsePushCert(changeId, raw, p); +} +``` + +**Do:** + +```java +if (status == PatchLineComment.Status.PUBLISHED) { + // Certificates exist strictly in the published change context + pushCert = parsePushCert(changeId, raw, p); +} else { + pushCert = null; +} +``` + +-------------------------------------------------------------------------------- + +#### T2-05: Industry-Standard Terminology for Deprecated Logic + +> **Rule:** Always use standard terminology like 'legacy' to designate older, +> decoupled schema formats. +> +> **What:** Code paths, variables, and methods handling older, decoupled schema +> formats must use standard terminology like 'legacy' rather than informal +> slang. +> +> **Applies To:** NoteDb schema evolution, serialization backwards-compatibility +> layers. +> +> **Why:** Engineers occasionally used informal suffixes (like 'Homebrew') to +> designate older serialization logic, which obscured the architectural intent +> for developers unfamiliar with the slang. Failing to adhere to this typically +> results in **Maintainability Degradation**. + +**Trap 1: Using non-standard slang to differentiate older formatting methods +from modern JSON ones.** + +**Don't:** + +```java +private void buildNoteHomebrew(ChangeNoteUtil noteUtil, OutputStream out) { + noteUtil.buildNote(buildCommentMap(), out); +} +``` + +**Do:** + +```java +private void buildNoteLegacy(ChangeNoteUtil noteUtil, OutputStream out) { + noteUtil.buildNote(buildCommentMap(), out); +} +``` + +## Chapter: Query Engine Performance & Operator Enforcement + +**Context:** This chapter governs the configuration, evaluation, and strict +syntactic enforcement of query predicates within the repository engine. It +focuses on decoupling strict user-facing evaluation constraints from background +observability and implementing phased, non-blocking rollouts to prevent +configuration deadlocks. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / | +: : : : Trap : +| :-------- | :-------------------------------- | :------- | :---------------- | +| **T3-01** | Non-Contributor Label Query | Medium | Blanket-excluding | +: : Isolation : : the 'owner' of : +: : : : the change from : +: : : : acting as a : +: : : : reviewer, even if : +: : : : someone else : +: : : : authored and : +: : : : uploaded the : +: : : : specific patchset : +: : : : being reviewed. : +| **T3-02** | Phased Rollout of Strict Query | Critical | Using a single | +: : Operator Enforcement : : feature toggle to : +: : : : immediately : +: : : : reject bad syntax : +: : : : across the entire : +: : : : system. : +| **T3-03** | Memoization of Static | Medium | Querying the | +: : Configuration in Query Evaluators : : global : +: : : : configuration : +: : : : object repeatedly : +: : : : within the core : +: : : : evaluation loop. : +| **T3-04** | Metrics Collection Decoupling | High | Passing global | +: : from Strict Query Constraints : : strict-mode : +: : : : configuration : +: : : : booleans into : +: : : : metrics-gathering : +: : : : query builders, : +: : : : thereby enforcing : +: : : : user-facing rules : +: : : : on background : +: : : : telemetry. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T3-01: Non-Contributor Label Query Isolation + +> **Rule:** Always explicitly exclude the uploader, committer, and author of the +> current patchset when evaluating `non_contributor` queries, allowing the +> change owner to act as an independent reviewer if they did not author the +> specific patchset. +> +> **What:** When evaluating label queries for `non_contributor`, the query +> engine must explicitly exclude votes from the uploader, committer, and author +> of the current patchset, treating the change owner as a valid independent +> reviewer only if they didn't author/upload the specific patchset. +> +> **Applies To:** Query Engine (`EqualsLabelPredicate.java`), Access Control +> Policies. +> +> **Why:** To enforce the 'four-eyes' principle, the system needed a way to +> distinguish between a change's code contributors and independent reviewers. +> The logic explicitly handles cases where the owner and uploader are different +> people, allowing the owner to act as an independent reviewer. Failing to +> adhere to this typically results in **Policy Bypass / Self-Approval**. + +**Trap 1: Blanket-excluding the 'owner' of the change from acting as a reviewer, +even if someone else authored and uploaded the specific patchset being +reviewed.** + +**Don't:** + +```java +// BAD: Excluding owner regardless of patchset authorship +if (account.equals(NON_CONTRIBUTOR_ACCOUNT_ID)) { + if (approver.equals(cd.getOwner())) return false; +} +``` + +**Do:** + +```java +// GOOD: Excluding only the uploader, committer, and author of the current patchset +if (account.equals(NON_CONTRIBUTOR_ACCOUNT_ID)) { + if (uploader.equals(approver) || committer.equals(approver) || author.equals(approver)) { + return false; + } +} +``` + +#### T3-02: Phased Rollout of Strict Query Operator Enforcement + +> **Rule:** Never use a single feature toggle to instantly enforce new query +> syntax constraints; you must decouple read and write configurations to allow +> manual migrations. +> +> **What:** Breaking syntactic requirements for repository queries must be +> rolled out via decoupled read/write configuration flags to avoid circular +> dependency deadlocks. +> +> **Applies To:** Submit Requirements evaluation and configuration validation +> layers. +> +> **Why:** Instantly enforcing strict query syntax (removing default searches) +> would break legacy submit configurations. Because a broken configuration +> blocks all repository submissions, teams would be permanently locked out from +> submitting the code necessary to fix the broken configuration. Failing to +> adhere to this typically results in **Repository Deadlock**. + +**Trap 1: Using a single feature toggle to immediately reject bad syntax across +the entire system.** + +**Don't:** + +* Instantly rejecting all evaluation queries that lack explicit operators. If + a project has a broken legacy config, it instantly fails, blocking all + submissions including configuration fixes. + +**Do:** + +* Split enforcement into two configs: `requireOperatorForUpdate` (blocks + saving new bad configs) and `requireOperatorForEvaluation` (throws errors + dynamically). Enable the update block first, migrate legacy rules manually, + then enable evaluation blocking. + +#### T3-03: Memoization of Static Configuration in Query Evaluators + +> **Rule:** Must cache query parsing and evaluation configurations during +> component initialization instead of polling the configuration object per +> execution. +> +> **What:** Configuration values that govern query parsing and evaluation must +> be read once during component initialization rather than fetched dynamically +> per execution. +> +> **Applies To:** Query Builders, Evaluators (e.g., +> SubmitRequirementsEvaluatorImpl), and high-frequency validation execution +> paths. +> +> **Why:** Historically, reading configuration values directly from the Config +> object upon every expression validation or evaluation incurred unnecessary CPU +> overhead, degrading the performance of high-volume operations like submit +> requirement checks. Failing to adhere to this typically results in +> **Performance Degradation**. + +**Trap 1: Querying the global configuration object repeatedly within the core +evaluation loop.** + +**Don't:** + +```java +public SubmitRequirementExpressionResult evaluateExpression( + SubmitRequirementExpression expression, ChangeData changeData) { + // BAD: Reading config on every evaluation + boolean reqOp = config.getBoolean("submit-requirement", null, "requireOperatorForEvaluation", false); + Predicate<ChangeData> predicate = queryBuilderFactory.create(reqOp).parse(expression.expressionString()); + // ... +} +``` + +**Do:** + +```java +// GOOD: Cache the configuration value at initialization time +public SubmitRequirementsEvaluatorImpl(...) { + this.requireOperatorForEvaluation = config.getBoolean("submit-requirement", null, "requireOperatorForEvaluation", false); +} + +public SubmitRequirementExpressionResult evaluateExpression( + SubmitRequirementExpression expression, ChangeData changeData) { + Predicate<ChangeData> predicate = queryBuilderFactory.create(this.requireOperatorForEvaluation).parse(expression.expressionString()); + // ... +} +``` + +**Exceptions:** Dynamic configurations explicitly designed and documented to be +hot-reloaded without server restarts. + +#### T3-04: Metrics Collection Decoupling from Strict Query Constraints + +> **Rule:** Always hard-code permissive configuration for metrics-gathering +> systems to prevent observability failures caused by malformed user metadata. +> +> **What:** Metrics gathering systems must bypass strict, user-facing query +> validation configurations to guarantee that observability pipelines do not +> fail due to malformed metadata or legacy data. +> +> **Applies To:** Metrics exporters (e.g., MergeMetrics), Background Jobs, and +> Observability Hooks. +> +> **Why:** Metrics collection for change merging relied on global server +> configuration for query parsing. If strict operator evaluation was enabled +> globally, legacy or malformed submit requirements would cause the metrics +> collection to throw an exception, halting the entire telemetry pipeline. +> Failing to adhere to this typically results in **Metrics Data Loss**. + +**Trap 1: Passing global strict-mode configuration booleans into +metrics-gathering query builders, thereby enforcing user-facing rules on +background telemetry.** + +**Don't:** + +```java +// BAD: Tying metrics collection to strict user configurations +boolean strictEvaluation = config.getBoolean("submit-requirement", null, "requireOperatorForEvaluation", false); +Predicate<ChangeData> predicate = submitRequirementChangequeryBuilderFactory + .create(strictEvaluation) + .parse(submitRequirement.submittabilityExpression()); +``` + +**Do:** + +```java +// GOOD: Hard-code permissive configuration to guarantee metrics stability +Predicate<ChangeData> predicate = submitRequirementChangequeryBuilderFactory + .create(false) // Hardcoded leniency + .parse(submitRequirement.submittabilityExpression()); +``` + +-------------------------------------------------------------------------------- + +### Cross-Domain Dependencies + +* **Upstream:** T4 | Access Control & Impersonation Security - *Access control + models dictate the contributor versus independent reviewer roles enforced + during query evaluation.* +* **Downstream:** T5 | Account Lifecycle & Vulnerability Mitigation - *The + query engine's submit requirement evaluations act as the enforcement barrier + against orphaned approvals from deleted accounts.* + +## Chapter: Access Control & Impersonation Security + +**Context:** This chapter governs the evaluation and auditing of impersonated +access operations (e.g., `SUBMIT_AS` and `RUN_AS`). It defines strict mechanisms +for isolating the initiating "real user" from the "impersonated user" during +dynamic permission checks, and mandates automated, centralized logging in NoteDb +and Protobuf to guarantee uncorrupted audit trails. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :-------- | :------------------------ | :------- | :------------------------ | +| **T4-01** | Impersonation Log | Medium | Allowing an impersonated | +: : Suppression for Reviewer : : session to generate a : +: : Updates : : change log message for a : +: : : : reviewer addition. : +| **T4-02** | Strict Context Evaluation | Critical | Checking for `SUBMIT_AS` | +: : for Impersonated Access : : within a pre-calculated : +: : Control : : collection of permissions : +: : : : belonging to the user : +: : : : context being : +: : : : impersonated. : +| **T4-03** | Centralized Impersonation | High | Appending the | +: : Auditing in NoteDb : : impersonation string : +: : Commits : : manually in specific API : +: : : : endpoints. : +| **T4-04** | System Metadata | High | Manually mutating the | +: : Preservation During : : change message to inject : +: : Impersonation Updates : : audit metadata during a : +: : : : batch update operation. : +| **T4-05** | Explicit Distinction of | Medium | Adding tracking fields to | +: : Effective vs Real : : Protobuf messages without : +: : Identity in Protobuf : : clarifying the : +: : Schemas : : impersonation : +: : : : relationship. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T4-01: Impersonation Log Suppression for Reviewer Updates + +> **Rule:** Always suppress system-generated impersonation change messages +> during operations that strictly update reviewers to avoid redundant logs. +> +> **What:** System-generated impersonation change messages must be suppressed +> for operations that explicitly update reviewers, as reviewer updates +> inherently persist their own metadata. +> +> **Applies To:** Gerrit server API, specifically `AddReviewersOp.java` and +> operations modifying `ReviewerUpdateInfo`. +> +> **Why:** Operations like adding a reviewer do not generate standard change +> messages by default, relying instead on structured reviewer-update lists. +> Allowing impersonation logic to inject a change message for these operations +> created redundant, noisy log entries. Failing to adhere to this typically +> results in **Audit Log Noise**. + +**Trap 1: Allowing an impersonated session to generate a change log message for +a reviewer addition.** + +**Don't:** + +```java +change = ctx.getChange(); +if (!accountIds.isEmpty()) { + // Proceeds with update, implicitly allowing impersonation messages +} +``` + +**Do:** + +```java +change = ctx.getChange(); +// Reviewer updates do not create change messages. In case of impersonation, we do not want to add an extra message to the log. +ctx.getUpdate(change.currentPatchSetId()).setSuppressImpersonationMessage(true); +if (!accountIds.isEmpty()) { + // Proceeds with update +} +``` + +#### T4-02: Strict Context Evaluation for Impersonated Access Control + +> **Rule:** Never authorize impersonated actions against the bulk permission set +> of the impersonated user; always evaluate against the real user initiating the +> impersonation. +> +> **What:** When authorizing impersonated actions (e.g., `SUBMIT_AS`), +> permissions must be verified against the `REAL_USER` (the identity initiating +> the impersonation) rather than checking the bulk permission set of the +> impersonated user. +> +> **Applies To:** Access control layers, `PermissionBackend`, and operational +> checkpoints like `MergeOp.java`. +> +> **Why:** Evaluating `SUBMIT_AS` against the general permissions pool of the +> impersonated user context was conceptually flawed and risked allowing +> unauthorized users to execute privileged actions if the real user lacked +> explicit impersonation rights. Failing to adhere to this typically results in +> **Security Bypass**. + +**Trap 1: Checking for `SUBMIT_AS` within a pre-calculated collection of +permissions belonging to the user context being impersonated.** + +**Don't:** + +```java +// BAD: Checking against the overall 'can' permission set which may not represent the real user's explicit rights. +if (!can.contains(ChangePermission.SUBMIT_AS)) { + // reject +} +``` + +**Do:** + +```java +// GOOD: Permissions are checked against the user who initiated the impersonation (REAL_USER). +if (!permissionBackend + .user(caller, ImpersonationPermissionMode.REAL_USER) + .change(cd) + .test(ChangePermission.SUBMIT_AS)) { + // reject +} +``` + +#### T4-03: Centralized Impersonation Auditing in NoteDb Commits + +> **Rule:** Must centrally inject impersonation clauses directly into NoteDb +> commit builders to guarantee consistent audit trails across all paths. +> +> **What:** Impersonation clauses (e.g., 'Performed by X on behalf of Y') must +> be automatically centralized within the NoteDb `AbstractChangeUpdate` commit +> builder, guaranteeing their presence in the system's versioned storage +> regardless of the operation or whether a user-provided message exists. +> +> **Applies To:** NoteDb mutation layers, `AbstractChangeUpdate.java`, and all +> REST endpoints modifying change states. +> +> **Why:** Historically, impersonation messages were manually appended by +> individual REST API handlers (like `PostReview`). This decentralized approach +> led to inconsistent audit trails where certain backend or automated operations +> failed to properly record the real user identity in the underlying Git commit. +> Failing to adhere to this typically results in **Incomplete Audit Trail**. + +**Trap 1: Appending the impersonation string manually in specific API +endpoints.** + +**Don't:** + +```java +// BAD: Decentralized in PostReview.java +String impersonationClause = String.format("(Posted by %s on behalf of %s)", caller, reviewer); +if (Strings.isNullOrEmpty(in.message)) { + in.message = impersonationClause; +} +``` + +**Do:** + +```java +// GOOD: Centralized in AbstractChangeUpdate.java for all NoteDb writes +private void addOptionalImpersonationMessage(CommitBuilder cb) { + if (realAccountId == null || realAccountId.equals(accountId)) return; + String impersonationClause = String.format("(Performed by %s on behalf of %s)", realLoggableName, loggableName); + // Safely inject before the footer +} +``` + +**Trap 2: Skipping the impersonation clause if the change update has no +user-visible commit message body.** + +**Don't:** + +```java +int firstFooterLine = indexOfFirstFooterLine(commitMsgLines); +// BAD: Bailing out if the message has no body +if (firstFooterLine == 2) return; +``` + +**Do:** + +```java +// GOOD: Append the clause regardless of the message body length using standard string manipulation. +Stream.concat(Arrays.stream(commitMsgLines).limit(firstFooterLine), Stream.of(impersonationClause, ""))... +``` + +**Exceptions:** Operations where the `realAccountId` strictly equals the +`accountId` (no impersonation taking place). + +#### T4-04: System Metadata Preservation During Impersonation Updates + +> **Rule:** Never manually mutate standard system change messages to document +> batch update reviewer modifications on behalf of other users. +> +> **What:** Manual override of change messages must be avoided when making +> system-level reviewer updates on behalf of other users, as it corrupts or +> suppresses structured audit logs. +> +> **Applies To:** REST API mutation endpoints, specifically batch updates +> involving user impersonation (`RUN_AS` / `onBehalfOf`). +> +> **Why:** Adding a manual "on behalf of" message to a reviewer modification +> suppressed the default system message (e.g., "Bob added to reviewers"), wiping +> out the primary context. Furthermore, tests verifying impersonation broke +> because reviewer updates bypass standard visible change messages entirely. +> Failing to adhere to this typically results in **Audit Trail Corruption**. + +**Trap 1: Manually mutating the change message to inject audit metadata during a +batch update operation.** + +**Don't:** + +```java +update.setChangeMessage(String.format("Reviewer added by %s on behalf of %s", realUser, impersonatedUser)); +``` + +**Do:** + +* Allow the backend to populate the structured `ReviewerUpdateInfo` + automatically. Do not append manual strings to the batch update. + +**Trap 2: Writing tests that assert against visible change messages to verify +background auditing.** + +**Don't:** + +```java +ChangeMessage m = Iterables.getLast(cmUtil.byChange(r.getChange().notes())); +assertThat(m.getMessage()).contains(expectedReviewerName); +``` + +**Do:** + +```java +ChangeMessageInfo lastMessage = Iterables.getLast(gApi.changes().id(r.getChangeId()).get().messages); +assertThat(lastMessage.realAuthor._accountId).isEqualTo(realUser.id().get()); +assertThat(lastMessage.author._accountId).isEqualTo(impersonatedUser.id().get()); +``` + +#### T4-05: Explicit Distinction of Effective vs Real Identity in Protobuf Schemas + +> **Rule:** Must explicitly document Protobuf schema fields storing +> impersonation audit trails with their exact `RUN_AS` semantics. +> +> **What:** Fields storing impersonation audit trails in Protobuf schemas must +> contain explicit inline documentation demonstrating exact `RUN_AS` semantics. +> +> **Applies To:** Protobuf schemas, particularly caching and status update +> models like `ReviewerStatusUpdateProto`. +> +> **Why:** Ambiguity in the protobuf schema left developers unsure which field +> represented the impersonated account vs the actual administrative account +> performing the action. Failing to adhere to this typically results in +> **Misinterpreted Audit Logs**. + +**Trap 1: Adding tracking fields to Protobuf messages without clarifying the +impersonation relationship.** + +**Don't:** + +```proto +int32 updated_by = 2; +int32 real_updated_by = 8; +``` + +**Do:** + +```proto +// Account ID of the effective user. +int32 updated_by = 2; +// Account ID of the real user. Set when impersonating using the RUN_AS permission. +// Example: if User X is impersonating user Y, real_updated_by is X. +int32 real_updated_by = 8; +``` + +-------------------------------------------------------------------------------- + +### Cross-Domain Dependencies + +* **Downstream:** T2 | NoteDb Serialization & Schema Evolution - *Centralized + impersonation auditing forces structured formatting and strict insertion + behaviors on underlying Git commits generated via NoteDb.* +* **Downstream:** T12 | Protobuf Schema Management & Conversion - *Protobuf + messages tracking access control state require rigorous entity documentation + to preserve the distinction between real and effective users during cache + serialization.* + +## Chapter: Account Lifecycle & Vulnerability Mitigation + +**Context:** This theme defines constraints for handling deleted or recreated +user accounts to prevent identity-cycling exploits, specifically isolating +validation checks and filtering orphaned approvals during merge operations +without degrading system performance. + +### Summary + +| Rule ID | Principle / | Priority | Primary Symptom / Trap | +: : Constraint : : : +| :-------- | :---------------- | :------- | :-------------------------------- | +| **T5-01** | Invalidation of | Critical | Evaluating submittability by | +: : Approvals from : : aggregating all present votes : +: : Deleted Accounts : : without verifying if the account : +: : : : IDs associated with those votes : +: : : : still exist. : +| **T5-02** | Deferral of | High | Triggering cache lookups for | +: : Expensive Account : : every account associated with a : +: : Validation to : : change during standard page loads : +: : Submission Time : : or SR recalculations. : +| **T5-03** | Graceful | High | Throwing a terminal exception as | +: : Degradation for : : soon as an orphaned or invalid : +: : Invalid Approval : : property is discovered during an : +: : Entities : : aggregation check. : +| **T5-04** | Positive-Intent | Medium | Creating a flag that requires an | +: : Feature Toggles : : administrator to explicitly turn : +: : for : : *on* a necessary security patch. : +: : Secure-by-Default : : : +: : States : : : + +-------------------------------------------------------------------------------- + +### Rules + +#### T5-01: Invalidation of Approvals from Deleted Accounts + +> **Rule:** Always dynamically filter and ignore `PatchSetApproval` votes +> originating from deleted accounts during submission checks. +> +> **What:** Code review approvals (`PatchSetApproval` votes) from deleted +> accounts must be dynamically filtered and ignored during submission checks to +> prevent identity-cycling exploits. +> +> **Applies To:** Submission validation (`MergeOp.checkSubmitRequirements`), +> Submit Requirement evaluation, and IAM lifecycle processing. +> +> **Why:** An exploit vector was identified where a user could upload a patch, +> delete their Gerrit account, and recreate an account linked to the same +> external ID. The new account inherited external group permissions (e.g., +> Code-Review+2) and could self-approve the change made by the now-deleted +> identity, bypassing the 'no self-approval' rule. Failing to adhere to this +> typically results in **Security Bypass / Self-Approval Exploit**. + +**Trap 1: Evaluating submittability by aggregating all present votes without +verifying if the account IDs associated with those votes still exist.** + +**Don't:** + +```java +for (PatchSetApproval psa : cd.currentApprovals()) { + if (psa.isApproved()) return true; +} +``` + +**Do:** + +```java +for (PatchSetApproval psa : filterOutApprovalsOfDeletedAccounts(cd.currentApprovals())) { + if (psa.isApproved()) return true; +} +``` + +#### T5-02: Deferral of Expensive Account Validation to Submission Time + +> **Rule:** Never execute costly cache lookups for account existence during +> continuous operations; must defer these validations to the final submission +> phase. +> +> **What:** Costly verification checks, such as querying an `AccountCache` for +> account existence, must not be integrated into continuous operations like +> general Submit Requirement (SR) evaluation. They must be deferred to the final +> `MergeOp` submission phase. +> +> **Applies To:** Submit Requirements engine and `MergeOp`. +> +> **Why:** An initial attempt to fix a vulnerability by filtering deleted +> account votes inside the continuous Submit Requirements engine caused severe +> global latency regressions because `AccountCache` was queried excessively. The +> fix had to be moved strictly to the submit button execution path. Failing to +> adhere to this typically results in **Severe Latency / System Degradation**. + +**Trap 1: Triggering cache lookups for every account associated with a change +during standard page loads or SR recalculations.** + +**Don't:** + +* Embedding `accountCache.get(accountId).isPresent()` inside the highly + trafficked Submit Requirement evaluation engine. + +**Do:** + +* Executing the account validation loop only within + `MergeOp.checkSubmitRequirements()` directly prior to the final merge + action. + +#### T5-03: Graceful Degradation for Invalid Approval Entities + +> **Rule:** Always silently filter out invalid or orphaned entities on a +> resource rather than hard-failing the operation. +> +> **What:** The presence of an invalid or orphaned entity (e.g., an approval +> from a deleted user) on a resource must not hard-fail operations on that +> resource. The invalid entity should be filtered out, and the operation allowed +> to proceed if the remaining valid entities satisfy the requirements. +> +> **Applies To:** Access validation, Submit requirement evaluation, and Merge +> logic. +> +> **Why:** A proposed solution suggested throwing a `ResourceConflictException` +> if *any* deleted account vote was detected on a change. This was rejected +> because it would block a change from being merged even if it possessed enough +> valid votes from active users to pass independently. Failing to adhere to this +> typically results in **Unnecessary Operation Blocking**. + +**Trap 1: Throwing a terminal exception as soon as an orphaned or invalid +property is discovered during an aggregation check.** + +**Don't:** + +```java +if (isDeleted(account)) { + throw new ResourceConflictException("Approval made by deleted account"); +} +``` + +**Do:** + +```java +// Silently filter invalid data and evaluate based on remaining valid data +Iterable<PatchSetApproval> validVotes = Iterables.filter(votes, v -> !isDeleted(v.accountId())); +``` + +#### T5-04: Positive-Intent Feature Toggles for Secure-by-Default States + +> **Rule:** Must name and implement feature toggles for security fixes such that +> the insecure legacy behavior requires an explicit opt-in. +> +> **What:** When introducing a critical security or architectural fix guarded by +> a feature toggle, the toggle's name and logic must represent the opt-in of the +> *legacy/insecure* behavior, ensuring the new secure behavior is the +> unconfigured system default. +> +> **Applies To:** Experiment Flag definitions (`ExperimentFeaturesConstants`) +> and Feature Toggle conditionals. +> +> **Why:** A security patch introduced a flag named +> `IGNORE_VOTES_OF_DELETED_ACCOUNTS`. Code reviewers forced an inversion of the +> flag to `CONSIDER_VOTES_OF_DELETED_ACCOUNTS` so that administrators did not +> have to explicitly enable the fix, preventing unpatched defaults. Failing to +> adhere to this typically results in **Accidental Misconfiguration / Security +> Hole**. + +**Trap 1: Creating a flag that requires an administrator to explicitly turn *on* +a necessary security patch.** + +**Don't:** + +```java +if (experimentFeatures.isFeatureEnabled("IGNORE_VOTES_OF_DELETED_ACCOUNTS")) { + filterDeletedAccounts(); +} +``` + +**Do:** + +```java +if (!experimentFeatures.isFeatureEnabled("CONSIDER_VOTES_OF_DELETED_ACCOUNTS")) { + filterDeletedAccounts(); +} +``` + +## Chapter: REST API Resource Routing & Payload Optimization + +**Context:** REST API endpoints must strictly reflect hierarchical entity +relationships while optimizing JSON payloads to minimize bandwidth and +processing latency. Modifications to serialization pipelines must explicitly +gate expensive permission checks and gracefully handle redundant or experimental +fields without causing UI ambiguity. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :-------- | :------------------------ | :------- | :------------------------ | +| **T6-01** | Explicit Boolean | Medium | Using a `toBoolean` | +: : Rendering for : : helper that converts : +: : Experimental UI Features : : `false` to `null` to : +: : : : minimize payload size, : +: : : : unintentionally leaving : +: : : : the UI client ambiguous : +: : : : about whether the feature : +: : : : is disabled or simply : +: : : : unconfigured. : +| **T6-02** | Hierarchical REST API | Medium | Exposing a sub-resource | +: : Resource Routing : : directly under a : +: : : : high-level container : +: : : : because it seems more : +: : : : direct. : +| **T6-03** | Explicit Query Parameters | Medium | Defaulting a new diff | +: : for Extensible Listing : : endpoint to return only : +: : APIs : : file names without : +: : : : requiring the client to : +: : : : ask for that specific : +: : : : format. : +| **T6-04** | Performance Justification | High | Appending a permission | +: : for Payload-Embedded : : verification step to : +: : Permission Checks : : every response object : +: : : : regardless of the : +: : : : client's actual need for : +: : : : that data. : +| **T6-05** | Omission of Redundant | Medium | Falling back to the | +: : Real-User Data in API : : primary user object if a : +: : Payloads : : distinct real user isn't : +: : : : found. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T6-01: Explicit Boolean Rendering for Experimental UI Features + +> **Rule:** Always explicitly serialize `false` for experimental UI feature +> flags to ensure strict UI gating, temporarily bypassing standard payload +> nullification. +> +> **What:** REST API responses should temporarily bypass the standard payload +> optimization (which drops `false` and `null` values) for experimental UI +> feature flags, explicitly returning `false` to ensure strict UI gating. +> +> **Applies To:** REST API JSON serializers (`ChangeJson`), UI Feature Flags. +> +> **Why:** To prevent an experimental AI review feature from leaking in the UI +> when permission was denied, the API was modified to explicitly serialize the +> `canAiReview = false` state rather than omitting it as the framework usually +> does for falsy values. Failing to adhere to this typically results in **UI +> Feature Leakage**. + +**Trap 1: Using a `toBoolean` helper that converts `false` to `null` to minimize +payload size, unintentionally leaving the UI client ambiguous about whether the +feature is disabled or simply unconfigured.** + +**Don't:** + +```java +// BAD: Omitting false values from payload +info.canAiReview = toBoolean(permissionBackend.test(AI_REVIEW)); +``` + +**Do:** + +```java +// GOOD: Explicitly forcing false to be serialized +info.canAiReview = permissionBackend.test(AI_REVIEW) ? true : false; +``` + +**Exceptions:** This is a temporary technical debt exception strictly permitted +until the `experiments.UiFeature__enable_ai_chat` feature flag is sunsetted. + +-------------------------------------------------------------------------------- + +#### T6-02: Hierarchical REST API Resource Routing + +> **Rule:** Never expose sub-resources at the root level; always nest them under +> their specific context-defining parent resources. +> +> **What:** REST API endpoints must reflect strict hierarchical entity +> relationships. Sub-resources must be nested under their specific +> context-defining parent resources rather than being exposed at the root level. +> +> **Applies To:** REST API Controller definitions, API endpoint URL schemas, and +> resource mapping. +> +> **Why:** Designing flat API endpoints (e.g., requesting a file diff directly +> from the project root) created ambiguities, as the backend could not +> definitively validate the file's existence without knowing the specific commit +> context. Failing to adhere to this typically results in **Ambiguous Resource +> Resolution**. + +**Trap 1: Exposing a sub-resource directly under a high-level container because +it seems more direct.** + +**Don't:** + +```text +// BAD: Missing the commit context required to resolve the file +GET /projects/{project}/files/{file}/diff?old={sha1}&new={sha1} +``` + +**Do:** + +```text +// GOOD: Nesting the file diff under the explicit commit it belongs to +GET /projects/{project}/commits/{commit-id}/files/{file}/diff?base={sha1} +``` + +-------------------------------------------------------------------------------- + +#### T6-03: Explicit Query Parameters for Extensible Listing APIs + +> **Rule:** Must require an explicit query parameter for REST endpoints designed +> to return sparse payloads to preserve future backwards-compatibility. +> +> **What:** When creating a REST API endpoint that deliberately returns a sparse +> payload (e.g., listing only file names), it must require an explicit query +> parameter indicating that reduced scope, allowing future backwards-compatible +> payload expansions. +> +> **Applies To:** REST API Endpoint design, list views, and differential payload +> returns. +> +> **Why:** Endpoints originally designed to return sparse data became locked +> into that format. When full data payloads were later needed, developers had to +> create entirely new endpoints because the default behavior could not be safely +> changed. Failing to adhere to this typically results in **API Backward +> Incompatibility**. + +**Trap 1: Defaulting a new diff endpoint to return only file names without +requiring the client to ask for that specific format.** + +**Don't:** + +```text +// BAD: Locks the API into only ever returning filenames +GET /projects/{project}/commits/{commit-id}/diff +``` + +**Do:** + +```text +// GOOD: Requires the client to acknowledge the limited scope +GET /projects/{project}/commits/{commit-id}/diff?nameOnly +``` + +**Exceptions:** Endpoints whose domain definition inherently restricts them to +simple lists (e.g., `GET /ids`). + +-------------------------------------------------------------------------------- + +#### T6-04: Performance Justification for Payload-Embedded Permission Checks + +> **Rule:** Always gate newly injected backend permission evaluations in heavily +> trafficked JSON payloads behind explicit client options to prevent global +> latency degradation. +> +> **What:** Injecting new backend permission evaluations into heavily trafficked +> REST API JSON payloads must be critically analyzed against the latency penalty +> and explicitly gated by granular request options. +> +> **Applies To:** REST API response serializers, specifically `ChangeJson` +> formatting pipelines. +> +> **Why:** Adding mandatory permission checks (like AI_REVIEW) to core change +> listing APIs increased the time complexity of the request for all users, +> introducing latency that could have been avoided by using a separate API or a +> request parameter gate. Failing to adhere to this typically results in **API +> Latency Degradation**. + +**Trap 1: Appending a permission verification step to every response object +regardless of the client's actual need for that data.** + +**Don't:** + +```java +// BAD: Unconditional permission check slows down all queries +out.canAiReview = permissionBackend.user(user).test(AI_REVIEW); +``` + +**Do:** + +```java +// GOOD: Gating the expensive check behind explicit client options and experiment flags +if (has(CURRENT_ACTIONS) && experiments.isEnabled(ENABLE_AI_CHAT)) { + out.canAiReview = permissionBackend.user(user).test(AI_REVIEW); +} +``` + +-------------------------------------------------------------------------------- + +#### T6-05: Omission of Redundant Real-User Data in API Payloads + +> **Rule:** Must omit the `real_updated_by` field (set to null) when serializing +> objects if it is identical to the primary `updated_by` field to conserve +> bandwidth. +> +> **What:** When serializing objects that support impersonation (like +> `ReviewerUpdateInfo`), the `real_updated_by` field must be omitted (set to +> null) if it is identical to the `updated_by` field, rather than duplicating +> the payload data. +> +> **Applies To:** REST API serialization layers, specifically `ChangeJson.java` +> and TypeScript interface definitions. +> +> **Why:** Returning the same account identity for both the primary actor and +> the real actor bloated the JSON payload. By omitting it, the API clearly +> signals when impersonation has not occurred while conserving bandwidth. +> Failing to adhere to this typically results in **Payload Bloat**. + +**Trap 1: Falling back to the primary user object if a distinct real user isn't +found.** + +**Don't:** + +```java +// BAD: Duplicating the object if no distinct real user exists +new ReviewerUpdateInfo( + c.date(), + accountLoader.get(c.updatedBy()), + c.realUpdatedBy().map(accountLoader::get).orElseGet(() -> accountLoader.get(c.updatedBy())), + ... +``` + +**Do:** + +```java +// GOOD: Return null to omit the field entirely +new ReviewerUpdateInfo( + c.date(), + accountLoader.get(c.updatedBy()), + c.realUpdatedBy().map(accountLoader::get).orElse(null), + ... +``` + +-------------------------------------------------------------------------------- + +### Cross-Domain Dependencies + +* **Upstream:** T4 | Access Control & Impersonation Security - *API + serialization layers invoke permission checks and impersonation contexts + that must be explicitly scoped to prevent payload bloat and latency + degradation.* + +## Chapter: Build Infrastructure & Dependency Alignment + +**Context:** This chapter governs the configuration, versioning, and migration +of the build system infrastructure, focusing heavily on Bazel modules (`bzlmod`) +and dependency graphs. It establishes constraints to ensure strict version +alignment, prevent compliance pipeline stalls, and maintain reliable developer +bootstrapping environments. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :-------- | :----------------------- | :------- | :------------------------- | +| **T7-01** | Bazelisk Version | High | Relying on deprecated | +: : Constraints for Bzlmod : : Bazelisk versions while : +: : Migrations : : altering legacy Bazel : +: : : : boundary markers : +: : : : (`WORKSPACE`). : +| **T7-02** | Strict Version Alignment | High | Pinning a specific older | +: : in Bazel Dependency : : minor version of a library : +: : Graphs : : for a single module : +: : : : without checking global or : +: : : : internal mirror alignment. : +| **T7-03** | Synchronized Dependency | Medium | Deprecating a build | +: : Manifest Documentation : : configuration file while : +: : : : leaving stale references : +: : : : to its usage in textual : +: : : : documentation. : +| **T7-04** | String Typing for Bazel | High | Passing a native boolean | +: : amend_artifact : : `True` to a strictly : +: : force_version : : string-typed attribute in : +: : : : a Bazel macro. : +| **T7-05** | Segregation of External | Medium | Placing non-internal | +: : Dependencies for : : dependencies alongside : +: : Compliance Bypassing : : standard organizational : +: : : : dependencies in global : +: : : : dependency files. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T7-01: Bazelisk Version Constraints for Bzlmod Migrations + +> **Rule:** Always upgrade `bazelisk` to at least v1.27.0 when migrating to +> `bzlmod` and removing or renaming the root `WORKSPACE` file. +> +> **What:** When migrating to Bazel Modules (`bzlmod`) and removing or renaming +> the root `WORKSPACE` file, `bazelisk` must be upgraded to at least v1.27.0 to +> correctly detect the `.bazelversion` file. +> +> **Applies To:** Build System / CI pipelines / Developer environment +> bootstrapping. +> +> **Why:** During the migration to Bzlmod, the `WORKSPACE` file was renamed to +> `WORKSPACE.bzlmod`. Older versions of `bazelisk` failed to locate the project +> root without a file strictly named `WORKSPACE`, ignoring the pinned +> `.bazelversion` and downloading the newest incompatible Bazel binary (e.g., +> v9.0.1). Failing to adhere to this typically results in **Build Failure / +> Incorrect Toolchain**. + +**Trap 1: Relying on deprecated Bazelisk versions while altering legacy Bazel +boundary markers (`WORKSPACE`).** + +**Don't:** + +* Attempting to build a Bzlmod-enabled project with an outdated `bazelisk` + that defaults to Bazel 9.x when `WORKSPACE` is missing. + +**Do:** + +* Require `bazelisk >= 1.27.0` which resolves `.bazelversion` even in purely + `bzlmod`-driven repositories lacking a `WORKSPACE` file. + +#### T7-02: Strict Version Alignment in Bazel Dependency Graphs + +> **Rule:** Must explicitly align external JVM dependencies to a single +> validated minor version across the entire build graph. +> +> **What:** External JVM dependencies must strictly align with a single +> validated minor version to prevent build system conflicts in +> `rules_jvm_external` and ensure compatibility with internal monolithic +> dependency mirrors. +> +> **Applies To:** Bazel build configurations, `WORKSPACE`, and `deps.toml` +> defining external library versions. +> +> **Why:** Introducing conflicting minor versions of transitive libraries (e.g., +> pulling Bytebuddy 1.18.4 while another module expects 1.18.5) caused the build +> system to trigger duplicate version checks and fail the build graph +> resolution. Failing to adhere to this typically results in **Build Failure / +> Duplicate Version**. + +**Trap 1: Pinning a specific older minor version of a library for a single +module without checking global or internal mirror alignment.** + +**Don't:** + +* Declaring `bytebuddy:1.18.4` in the JGit servlet dependency chain while the + rest of the build relies on `1.18.5`. + +**Do:** + +* Upgrading the local module dependency to match the globally available + version: `bytebuddy:1.18.5`, ensuring a single version traverses the entire + graph. + +#### T7-03: Synchronized Dependency Manifest Documentation + +> **Rule:** Always update developer documentation atomically in the exact same +> commit when modifying or deprecating dependency manifests. +> +> **What:** Build system developer documentation must be atomically updated in +> the exact same commit whenever dependency declaration manifests or build +> targets are migrated or deprecated. +> +> **Applies To:** Build system configuration (e.g., Bazel WORKSPACE/MODULE +> files) and corresponding developer-facing documentation (e.g., dev-bazel.txt). +> +> **Why:** During a migration of dependency management workflows, the legacy +> dependency configuration file was emptied, but the developer documentation +> still explicitly instructed engineers to reference the deprecated file path +> and run outdated Bazel pin targets. Failing to adhere to this typically +> results in **Developer Process Failure**. + +**Trap 1: Deprecating a build configuration file while leaving stale references +to its usage in textual documentation.** + +**Don't:** + +* Emptying legacy configuration files (e.g., `tools/deps.bzl`) but failing to + update documentation containing old execution targets like `bazel run + @gerrit_deps//:pin`. + +**Do:** + +* Atomically updating documentation to reference the new configuration files + (e.g., `tools/deps.toml`) and correctly mapped targets like `bazel run + @external_deps//:pin` within the deprecation commit. + +#### T7-04: String Typing for Bazel amend_artifact force_version + +> **Rule:** Never pass a Starlark boolean to the `force_version` attribute in +> `rules_jvm_external`; it must be strictly typed as a string. +> +> **What:** The `force_version` attribute within the `rules_jvm_external` Bazel +> module extension must be strictly passed as a string, not a native Starlark +> boolean. +> +> **Applies To:** Bazel build scripts (`MODULE.bazel`) leveraging the +> `rules_jvm_external` extension for Maven dependency resolution. +> +> **Why:** An attempt was made to enforce root-level dependency precedence over +> layered modules using a boolean data type. The underlying extension API +> structurally requires a string representation of the boolean value. Failing to +> adhere to this typically results in **Bazel Evaluation Failure**. + +**Trap 1: Passing a native boolean `True` to a strictly string-typed attribute +in a Bazel macro.** + +**Don't:** + +```python +maven.amend_artifact( + name = "external_deps", + coordinates = coord, + force_version = True, +) +``` + +**Do:** + +```python +maven.amend_artifact( + name = "external_deps", + coordinates = coord, + force_version = "true", +) +``` + +#### T7-05: Segregation of External Dependencies for Compliance Bypassing + +> **Rule:** Must isolate external dependencies into dedicated configuration +> files to avoid triggering unnecessary internal compliance reviews. +> +> **What:** Dependencies not utilized internally by the host organization must +> be isolated into dedicated build configurations to avoid triggering +> unnecessary compliance reviews. +> +> **Applies To:** Bazel workspace configurations, dependency management (e.g., +> deps.bzl, nongoogle.bzl). +> +> **Why:** Routine version bumps to open-source libraries (e.g., H2 database) +> that were only used in external distributions triggered mandatory internal +> 'Library-Compliance' votes, blocking development velocity and complicating +> functional PRs. Failing to adhere to this typically results in **Build +> Pipeline Stalls**. + +**Trap 1: Placing non-internal dependencies alongside standard organizational +dependencies in global dependency files.** + +**Don't:** + +* Define the H2 database dependency within the global `tools/deps.bzl` file + alongside core infrastructure libraries. + +**Do:** + +* Move the H2 database dependency into a segregated `tools/nongoogle.bzl` file + to explicitly demarcate its exclusion from internal compliance checks. + +**Trap 2: Bundling dependency location moves with functional, logical code +changes.** + +**Don't:** + +* Submitting a single patchset that upgrades the H2 database version, changes + database logic, and moves the dependency to nongoogle.bzl. + +**Do:** + +* Submit the structural move to `nongoogle.bzl` as an independent parent + change to keep the functional logic updates completely decoupled. + +**Exceptions:** Dependencies that share utilization across both internal +infrastructure and external deployments. + +-------------------------------------------------------------------------------- + +### Cross-Domain Dependencies + +* **Upstream:** T13 | Compliance & CLA Enforcement - *Organizational + compliance rules and automated pipelines directly dictate the necessity of + structurally segregating external dependencies in the build graph.* + +## Chapter: Asynchronous Notification Consistency + +**Context:** Asynchronous notification pipelines must capture exact entity state +at the moment of initialization rather than fetching it dynamically during +execution. This guarantees data consistency and prevents delayed background +tasks from inadvertently processing future, out-of-band updates from persistent +storage. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :-------- | :--------------------- | :------- | :--------------------------- | +| **T8-01** | Snapshot State Capture | High | Passing database identifiers | +: : for Asynchronous : : to a background task and : +: : Notifications : : reloading the entity during : +: : : : execution. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T8-01: Snapshot State Capture for Asynchronous Notifications + +> **Rule:** Must materialize and pass the exact entity state synchronously to +> asynchronous background tasks before handoff. +> +> **What:** Asynchronous tasks (like email dispatchers) must capture the exact +> entity state at initialization instead of fetching it from the persistent +> store dynamically during execution. +> +> **Applies To:** Asynchronous notification pipelines and background task +> executors. +> +> **Why:** A delayed asynchronous email task would query NoteDb directly during +> execution. If another thread performed an update during the delay, the email +> would accidentally pull 'too-new' state (e.g., a mismatched subject line) that +> belonged to the subsequent transaction. Failing to adhere to this typically +> results in **Race Condition / Stale Data**. + +**Trap 1: Passing database identifiers to a background task and reloading the +entity during execution.** + +**Don't:** + +```java +public ChangeEmailImpl(@Provided EmailArguments args, Project.NameKey project, Change.Id changeId) { + // Anti-pattern: Storing IDs and reading the database asynchronously later + this.changeId = changeId; +} +``` + +**Do:** + +```java +public ChangeEmailImpl(@Provided EmailArguments args, Change change) { + // Materialize state synchronously before handing off to the async thread + this.changeData = args.changeDataFactory.create(change); + this.change = changeData.change(); +} +``` + +-------------------------------------------------------------------------------- + +### Cross-Domain Dependencies + +* **Upstream:** T2 | NoteDb Serialization & Schema Evolution - *NoteDb acts as + the persistent storage layer that asynchronous notifications must avoid + querying during delayed executions to prevent reading advanced, out-of-band + states.* + +## Chapter: Test Suite Configuration & Isolation + +**Context:** This chapter dictates the implementation of parameterized +integration tests, strict Guice dependency injection, and granular flaky test +isolation. It ensures deterministic test coverage across multiple backend +configurations without leaking generic APIs or brittle global states. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :-------- | :----------------------- | :------- | :------------------------- | +| **T9-01** | Method-Level Granularity | Medium | Using class-level | +: : for Integration Test : : isolation to fix a single : +: : Sandboxing : : flaky test, penalizing the : +: : : : execution time of the : +: : : : entire suite. : +| **T9-02** | Test Class Interface | Medium | Making a test class | +: : Segregation : : implement `Provider<T>` : +: : : : merely to bind a variable : +: : : : in Guice. : +| **T9-03** | Strict Propagation of | High | Ignoring the parameterized | +: : Parameterized Test : : configuration in Guice : +: : Configurations : : module installation. : +| **T9-04** | Direct Instance Binding | Low | Binding a locally | +: : in Guice Modules : : available instance by : +: : : : wrapping it in a Guice : +: : : : Provider. : +| **T9-05** | Framework-Driven Test | High | Flipping global static | +: : Parametrization : : variables inside an : +: : : : inherited test class's : +: : : : @Before method. : +| **T9-06** | Declarative Dependency | Medium | Manually invoking the | +: : Injection via @Inject : : injector inside a method : +: : over Manual Resolution : : body. : +| **T9-07** | Deterministic Build | Medium | Checking for transient | +: : Environment Detection in : : directories like 'build' : +: : Tests : : or 'buck-out' to dictate : +: : : : conditional test logic. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T9-01: Method-Level Granularity for Integration Test Sandboxing + +> **Rule:** Always apply the `@Sandboxed` annotation strictly to specific flaky +> or state-dependent test methods rather than applying it globally to the test +> class. +> +> **What:** The `@Sandboxed` annotation must be applied directly to specific +> flaky or state-dependent test methods rather than at the class level. +> +> **Applies To:** Integration test framework (`AbstractDaemonTest`). +> +> **Why:** Applying the sandbox annotation to an entire class spins up a +> completely new database and daemon instance for every single test in the +> class, drastically increasing test execution time for tests that do not +> actually suffer from cross-test state interference. Failing to adhere to this +> typically results in **Test Suite Performance Degradation**. + +**Trap 1: Using class-level isolation to fix a single flaky test, penalizing the +execution time of the entire suite.** + +**Don't:** + +```java +// BAD: Class-level sandboxing +@Sandboxed +public class SubmitRequirementPredicateIT extends AbstractDaemonTest { + @Test public void testA() { ... } + @Test public void testB() { ... } +} +``` + +**Do:** + +```java +// GOOD: Method-level sandboxing +public class SubmitRequirementPredicateIT extends AbstractDaemonTest { + @Test public void testA() { ... } + + @Test + @Sandboxed + public void flakyTestB() { ... } +} +``` + +-------------------------------------------------------------------------------- + +#### T9-02: Test Class Interface Segregation + +> **Rule:** Never implement generic framework interfaces directly on test base +> classes to satisfy dependency injection bindings. +> +> **What:** Test base classes must not directly implement generic framework +> interfaces (like Guice's Provider<T>) to satisfy dependency injection +> bindings, as this leaks generic methods into the test class's public API. +> +> **Applies To:** Guice Dependency Injection, Unit/Integration Test +> Infrastructure. +> +> **Why:** Tests were modified to implement Guice Provider interfaces directly. +> This introduced overly generic methods (like `get()`) onto the base class, +> muddying the class's API footprint and risking unintended shadowing. Failing +> to adhere to this typically results in **Interface Pollution**. + +**Trap 1: Making a test class implement `Provider<T>` merely to bind a variable +in Guice.** + +**Don't:** + +```java +public abstract class AbstractChangeNotesTest extends GerritBaseTests implements Provider<Config> { + @ConfigSuite.Parameter + public Config testConfig; + + @Override + public Config get() { + return testConfig != null ? testConfig : new Config(); + } +} +``` + +**Do:** + +```java +// Use framework utilities like Providers.of() or nested classes instead of direct interface implementation. +bind(Config.class).annotatedWith(GerritServerConfig.class).toProvider(Providers.of(testConfig)); +``` + +-------------------------------------------------------------------------------- + +#### T9-03: Strict Propagation of Parameterized Test Configurations + +> **Rule:** Must explicitly inject the dynamically provided configuration object +> into the system under test to ensure valid parametrization. +> +> **What:** When utilizing a parameterized test suite, explicitly inject the +> provided configuration object into the system under test rather than +> instantiating a default state, which silently bypasses test parameters. +> +> **Applies To:** ConfigSuite runner; specifically Guice module installation and +> parameter injection. +> +> **Why:** A test setup block instantiated a new, empty Config rather than using +> the configuration variant provided by the test suite framework, completely +> nullifying the multi-backend test coverage. Failing to adhere to this +> typically results in **False Positive Test Coverage**. + +**Trap 1: Ignoring the parameterized configuration in Guice module +installation.** + +**Don't:** + +```java +@ConfigSuite.Parameter +public Config testConfig; + +// BAD: Installing with a default empty config, ignoring the test runner variations. +install(NoteDbModule.forTest(new Config())); +``` + +**Do:** + +```java +@ConfigSuite.Parameter +public Config testConfig; + +// GOOD: Passing the dynamically injected testConfig down into the module. +install(NoteDbModule.forTest(testConfig)); +``` + +-------------------------------------------------------------------------------- + +#### T9-04: Direct Instance Binding in Guice Modules + +> **Rule:** Always use the `.toInstance()` DSL when binding pre-instantiated +> objects in Guice modules. +> +> **What:** When binding a pre-existing object instance in a Guice module, use +> the direct `.toInstance()` DSL instead of wrapping it in redundant Provider +> abstractions. +> +> **Applies To:** Guice module configuration (`configure()` blocks). +> +> **Why:** Engineers wrapped pre-instantiated variables inside `Providers.of()` +> and bound them via `.toProvider()`, creating unnecessary object allocation and +> visually cluttered dependency setups. Failing to adhere to this typically +> results in **Boilerplate / Decreased Readability**. + +**Trap 1: Binding a locally available instance by wrapping it in a Guice +Provider.** + +**Don't:** + +```java +bind(Config.class).annotatedWith(GerritServerConfig.class) + .toProvider(Providers.of(testConfig)); +``` + +**Do:** + +```java +bind(Config.class).annotatedWith(GerritServerConfig.class) + .toInstance(testConfig); +``` + +-------------------------------------------------------------------------------- + +#### T9-05: Framework-Driven Test Parametrization + +> **Rule:** Must utilize declarative suite parameterization (e.g., +> `@ConfigSuite`) to drive data variations, completely avoiding mutable static +> state and test hierarchy hacks. +> +> **What:** Test configurations intended to exercise multiple backend +> representations must use explicit framework parameterization (e.g., +> ConfigSuite) rather than manual subclassing combined with mutable global +> state. +> +> **Applies To:** Test suites covering variable storage backends (e.g., NoteDb +> legacy format vs. JSON). +> +> **Why:** Developers previously verified new data formats by manually creating +> child test classes that flipped a static global boolean. This broke test +> isolation and created brittle, hard-to-follow test hierarchies. Failing to +> adhere to this typically results in **Test State Leakage / Brittle +> Inheritance**. + +**Trap 1: Flipping global static variables inside an inherited test class's +@Before method.** + +**Don't:** + +```java +public class ChangeNotesJsonTest extends ChangeNotesTest { + @Before + public void setJson() { + ChangeNoteUtil.writeJsonDefault = true; + } +} +``` + +**Do:** + +```java +@RunWith(ConfigSuite.class) +public abstract class AbstractChangeNotesTest { + @ConfigSuite.Default + public static Config legacyConfig() { + // return legacy config + } + + @ConfigSuite.Config + public static Config jsonConfig() { + // return json config + } +} +``` + +-------------------------------------------------------------------------------- + +#### T9-06: Declarative Dependency Injection via @Inject over Manual Resolution + +> **Rule:** Always enforce automated component lifecycles using `@Inject` +> annotations rather than querying the Guice Injector directly inside method +> bodies. +> +> **What:** Rely on the framework's automatic member injection lifecycle (e.g., +> `@Inject`) rather than manually fetching dependencies via the Injector locator +> pattern. +> +> **Applies To:** Guice Dependency Injection within Test instances and Service +> classes. +> +> **Why:** Tests routinely invoked `injector.getInstance()` inside helper +> methods. This obfuscated dependency requirements and broke cleanly when the +> test runner reset the injector under varying configurations. Failing to adhere +> to this typically results in **Hidden Dependencies / Lifecycle Desync**. + +**Trap 1: Manually invoking the injector inside a method body.** + +**Don't:** + +```java +void someTestMethod() { + ChangeNoteUtil noteUtil = injector.getInstance(ChangeNoteUtil.class); + // use noteUtil +} +``` + +**Do:** + +```java +@Inject +private ChangeNoteUtil noteUtil; + +void someTestMethod() { + // use noteUtil directly +} +``` + +-------------------------------------------------------------------------------- + +#### T9-07: Deterministic Build Environment Detection in Tests + +> **Rule:** Must establish build environment parameters using static source +> control metadata files, strictly ignoring volatile build output directories. +> +> **What:** Test setup frameworks must not infer project environments or build +> systems based on transient output directories that may not yet exist. +> +> **Applies To:** Integration and Plugin test acceptance frameworks. +> +> **Why:** The test framework erroneously classified Buck projects as Maven +> projects if the repository was freshly cloned and the `buck-out` directory +> hadn't been generated by an initial build yet, causing tests to misconfigure +> the environment. Failing to adhere to this typically results in **Flaky +> Environment Initialization**. + +**Trap 1: Checking for transient directories like 'build' or 'buck-out' to +dictate conditional test logic.** + +**Don't:** + +```java +boolean isMaven = !Files.exists(pluginRoot.resolve("buck-out")); +``` + +**Do:** + +* Rely strictly on deterministic source control metadata (e.g., checking for a + `pom.xml` or `BUCK` file) or explicitly scope out untested environments. + +-------------------------------------------------------------------------------- + +### Cross-Domain Dependencies + +* **Upstream:** T7 | Build Infrastructure & Dependency Alignment - + *Deterministic test environment detection relies on the core build system + declarations defined here.* +* **Downstream:** T2 | NoteDb Serialization & Schema Evolution - *NoteDb + legacy versus JSON backend variations natively consume the parameterized + suite structures enforced by this domain.* + +## Chapter: Diff Engine Computation Limits + +**Context:** Explicitly tracking timeouts and fallback states for expensive file +diff computations guarantees that frontend clients are alerted to incomplete +operations. This prevents silent data omission and ensures accurate +representation of modified files in the user interface. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :--------- | :--------------------- | :------- | :-------------------------- | +| **T10-01** | Explicit Signaling of | High | Discarding files from the | +: : Computation Limits in : : result list or relying on : +: : File Diffs : : opaque negative cache hits : +: : : : when computation limits are : +: : : : exceeded. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T10-01: Explicit Signaling of Computation Limits in File Diffs + +> **Rule:** Always explicitly flag timed-out file diffs in the API payload +> instead of silently omitting them or grouping them into generic negative +> caches. +> +> **What:** Expensive processes like file diff algorithms must respect strict +> timeouts and explicitly mark aborted files in the API response using a +> dedicated flag, rather than dropping the file or classifying it generically. +> +> **Applies To:** Diff algorithms, `FileInfo` REST API responses, and +> `FileDiffCache` outputs. +> +> **Why:** When a file's diff took too long to compute, the system previously +> marked it with a generic 'negative' cache flag or dropped it, which misled +> users into believing the file was unmodified instead of alerting them to the +> computation failure. Failing to adhere to this typically results in **Silent +> Data Omission / Misleading UI**. + +**Trap 1: Discarding files from the result list or relying on opaque negative +cache hits when computation limits are exceeded.** + +**Don't:** + +```java +// BAD: Silently omitting the timed-out file +if (fileDiffOutput.isEmpty() || fileDiffOutput.isNegative()) { + continue; +} +``` + +**Do:** + +```java +// GOOD: Including the file but explicitly flagging it as too expensive +if (fileDiffOutput.isEmpty() && !fileDiffOutput.isNegative()) { + continue; +} +// API serializes the result with tooExpensiveToCompute = true +``` + +-------------------------------------------------------------------------------- + +### Cross-Domain Dependencies + +* **Downstream:** T6 | REST API Resource Routing & Payload Optimization - + *Serializing explicit computation limit flags directly dictates the schema + and payload structure of REST API `FileInfo` responses.* + +## Chapter: Concurrent Formatting & Caching + +**Context:** *Parallelizing expensive API formatting requires strict adherence +to explicit thread-local context propagation, cache isolation for mutated +elements, and collection pre-allocation. Failing to manage concurrency strictly +leads to context leakage across parallel streams, memory overhead, and poisoned +UI states.* + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / | +: : : : Trap : +| :--------- | :------------------------------ | :------- | :---------------- | +| **T11-01** | Explicit User Context | High | Calling a | +: : Propagation in Parallel Streams : : Provider to fetch : +: : : : the current user : +: : : : context directly : +: : : : inside the : +: : : : mapping function : +: : : : of a parallel : +: : : : stream. : +| **T11-02** | Isolation of Mutated Query | High | Caching all | +: : Elements in Shared Caches : : elements of a : +: : : : query result : +: : : : indiscriminately : +: : : : before applying : +: : : : list-level : +: : : : pagination logic. : +| **T11-03** | Pre-allocation of Thread-Safe | Medium | Initializing a | +: : Caches for Large Queries : : ConcurrentHashMap : +: : : : using the default : +: : : : empty constructor : +: : : : despite knowing : +: : : : the size of the : +: : : : input list. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T11-01: Explicit User Context Propagation in Parallel Streams + +> **Rule:** Always resolve thread-local user contexts on the main execution +> thread and explicitly pass them into parallel worker threads. +> +> **What:** When processing data using Java parallel streams, thread-local user +> contexts (like `CurrentUser`) must be resolved on the main thread prior to +> stream execution and explicitly passed to the worker threads. +> +> **Applies To:** API response formatting, `parallelStream()` operations, and +> any Guice Provider resolution requiring request scope. +> +> **Why:** Relying on request-scoped Guice providers within parallel stream +> lambdas caused context leaks, where worker threads inherited the wrong +> thread-local state or threw out-of-scope exceptions. Failing to adhere to this +> typically results in **Context Loss / State Leakage**. + +**Trap 1: Calling a Provider to fetch the current user context directly inside +the mapping function of a parallel stream.** + +**Don't:** + +```java +// BAD: Evaluating the provider dynamically inside a parallel worker thread +list.parallelStream().map(item -> { + return format(item, userProvider.get()); +}).collect(toList()); +``` + +**Do:** + +```java +// GOOD: Resolving context on the main thread and passing it explicitly +CurrentUser user = userProvider.get(); +list.parallelStream().map(item -> { + return format(item, user); +}).collect(toList()); +``` + +-------------------------------------------------------------------------------- + +#### T11-02: Isolation of Mutated Query Elements in Shared Caches + +> **Rule:** Never cache elements of a query result that are subject to +> post-processing mutation, such as list-terminating pagination flags. +> +> **What:** When caching sequential or parallel query results, elements that are +> susceptible to post-processing mutation (such as appending a pagination flag +> like `_moreChanges`) must be explicitly excluded from the shared cache. +> +> **Applies To:** API response formatters, caching layers, and thread-safe +> collections (`ConcurrentHashMap`) used during pagination. +> +> **Why:** Caching the final element of a paginated list caused the +> `_moreChanges` boolean to leak into subsequent, unrelated queries that +> retrieved the same entity from the cache, yielding incorrect pagination states +> in the UI. Failing to adhere to this typically results in **Cache Poisoning / +> UI Pagination Errors**. + +**Trap 1: Caching all elements of a query result indiscriminately before +applying list-level pagination logic.** + +**Don't:** + +```java +// BAD: Caching all elements, then mutating the last one +for (ChangeData cd : changes) { + ChangeInfo info = format(cd); + cache.put(cd.getId(), info); + changeInfos.add(info); +} +changeInfos.get(changeInfos.size() - 1)._moreChanges = true; +``` + +**Do:** + +```java +// GOOD: Excluding the potentially mutated last element from the cache +for (int i = 0; i < changes.size(); i++) { + boolean isCacheable = cacheQueryResults && (i != changes.size() - 1); + ChangeInfo info = format(changes.get(i)); + if (isCacheable) { + cache.put(changes.get(i).getId(), info); + } + changeInfos.add(info); +} +``` + +-------------------------------------------------------------------------------- + +#### T11-03: Pre-allocation of Thread-Safe Caches for Large Queries + +> **Rule:** Must initialize expected capacities when instantiating thread-safe +> collections to process large parallel processing operations. +> +> **What:** When initializing a thread-safe map (`ConcurrentHashMap`) that will +> accommodate thousands of entries during parallel processing, explicitly +> provide the expected size/capacity to the constructor. +> +> **Applies To:** Concurrent processing layers and result caches for bulk data +> retrieval. +> +> **Why:** Failing to initialize capacities on heavily used collections led to +> unnecessary memory reallocation and rehashing overhead during parallel stream +> processing of large change lists. Failing to adhere to this typically results +> in **Memory Overhead / CPU Churn**. + +**Trap 1: Initializing a ConcurrentHashMap using the default empty constructor +despite knowing the size of the input list.** + +**Don't:** + +```java +// BAD: Default capacity leads to frequent rehashing +Map<Change.Id, ChangeInfo> cache = new ConcurrentHashMap<>(); +``` + +**Do:** + +```java +// GOOD: Pre-allocating capacity based on known input size +Map<Change.Id, ChangeInfo> cache = new ConcurrentHashMap<>(in.size()); +``` + +**Exceptions:** Queries where the result set is guaranteed to be trivial (e.g., +< 10 items). + +-------------------------------------------------------------------------------- + +### Cross-Domain Dependencies + +* **Upstream:** T9 | Test Suite Configuration & Isolation - *Guice Provider + configurations dictate the request-scoped boundaries that necessitate + explicit context extraction on the main thread.* +* **Downstream:** T6 | REST API Resource Routing & Payload Optimization - + *Thread-safe concurrent formatting directly accelerates the generation and + optimization of the final JSON payloads delivered by the API.* + +## Chapter: Protobuf Schema Management & Conversion + +**Context:** This section governs the mapping between Java entities and Protobuf +messages, mandating automated reflection-based validation and standardized +converter abstractions to prevent cache inconsistencies and silent field loss. + +### Summary + +| Rule ID | Principle / | Priority | Primary Symptom / Trap | +: : Constraint : : : +| :--------- | :----------------- | :------- | :------------------------------ | +| **T12-01** | Java Entity to | Critical | Modifying a Java data class | +: : Protobuf : : without programmatically : +: : Round-Trip : : enforcing that the new field is : +: : Validation : : mapped to the Protobuf schema. : +| **T12-02** | Standardized | Medium | Manually querying Protobuf | +: : Protobuf : : FieldDescriptors by integer : +: : Serialization via : : index and explicitly copying : +: : SafeProtoConverter : : values to a builder. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T12-01: Java Entity to Protobuf Round-Trip Validation + +> **Rule:** Must verify any field additions to cached Java entities via +> reflection-based round-trip tests to guarantee absolute parity with their +> Protobuf serializers. +> +> **What:** Any field additions to Java entities persisted in caches must be +> verified via reflection-based round-trip unit tests to ensure absolute parity +> with their Protobuf serializers. +> +> **Applies To:** Data storage models, cache serializers (e.g., +> `AccountProtoConverter`), and persistent entities. +> +> **Why:** Adding new fields (like avatar properties) to an entity without +> strictly validating the corresponding Protobuf converter caused those fields +> to be silently dropped when objects were retrieved from the cache. Failing to +> adhere to this typically results in **Silent Data Loss / Cache +> Inconsistency**. + +**Trap 1: Modifying a Java data class without programmatically enforcing that +the new field is mapped to the Protobuf schema.** + +**Don't:** + +```java +// BAD: Updating entity without ensuring serialization mapping +public class Account { + public String avatarEmail; // New field added, silently ignored by cache +} +``` + +**Do:** + +```java +// GOOD: Using reflection in a test suite to detect missed fields +@Test +public void accountFieldsMatchExpected() { + // Automatically fails if Account.class gains a field not handled by the converter + assertAllFieldsMapped(Account.class, AccountProtoConverter.class); +} +``` + +**Exceptions:** Transient fields explicitly marked to be ignored during +serialization. + +-------------------------------------------------------------------------------- + +#### T12-02: Standardized Protobuf Serialization via SafeProtoConverter + +> **Rule:** Never use manual Protobuf-to-Java serialization with direct +> `FieldDescriptor` lookups; always utilize the standardized +> `SafeProtoConverter` abstraction. +> +> **What:** Manual Protobuf-to-Java serialization and deserialization using +> direct `FieldDescriptor` lookups and explicit builder mapping should be +> avoided in favor of the standardized `SafeProtoConverter` abstraction. +> +> **Applies To:** Cache serialization logic (e.g., `FileDiffOutput.Serializer`) +> and any mapping between Java data objects and Protobuf entities. +> +> **Why:** Manual field mapping for Protobuf entities requires verbose +> boilerplate that is prone to field omission, index misalignment, and caching +> corruption during schema evolution. Failing to adhere to this typically +> results in **Data Loss / Schema Misalignment**. + +**Trap 1: Manually querying Protobuf FieldDescriptors by integer index and +explicitly copying values to a builder.** + +**Don't:** + +```java +private static final FieldDescriptor TOO_EXPENSIVE_DESCRIPTOR = FileDiffOutputProto.getDescriptor().findFieldByNumber(17); +if (proto.hasField(TOO_EXPENSIVE_DESCRIPTOR)) { + builder.tooExpensiveToCompute(Optional.of(proto.getTooExpensiveToCompute())); +} +``` + +**Do:** + +* Implement and utilize a standard SafeProtoConverter utility to abstract away + raw descriptor mappings. + +```java +return SafeProtoConverterUtil.fromProto(proto); +``` + +-------------------------------------------------------------------------------- + +### Cross-Domain Dependencies + +* **Downstream:** T5 | Account Lifecycle & Vulnerability Mitigation - *The + account cache relies on strictly validated Protobuf schemas to prevent + silent data loss of user attributes.* +* **Downstream:** T10 | Diff Engine Computation Limits - *File diff cache + entities rely on standardized Protobuf converters to serialize expensive + computation fallbacks.* + +## Chapter: Compliance & CLA Enforcement + +**Context:** Must strictly enforce Contributor License Agreement (CLA) checks +across all administrative REST API endpoints that modify project configurations +and access rules. This guarantees compliance consistency across both direct code +pushes and API-driven administrative actions. + +### Summary + +| Rule ID | Principle / Constraint | Priority | Primary Symptom / Trap | +| :--------- | :--------------------- | :------- | :------------------------- | +| **T13-01** | CLA Enforcement on | Critical | Relying on standard source | +: : Project Configuration : : code pushing paths for CLA : +: : Endpoints : : enforcement while ignoring : +: : : : REST-based project : +: : : : administration paths. : + +-------------------------------------------------------------------------------- + +### Rules + +#### T13-01: CLA Enforcement on Project Configuration Endpoints + +> **Rule:** Always verify Contributor License Agreements explicitly via +> `ContributorAgreementsChecker` before processing access review payloads. +> +> **What:** Contributor License Agreement (CLA) checks must be strictly enforced +> on administrative endpoints that create or modify project access rules. +> +> **Applies To:** REST API (`/projects/{project}/access:review`) and +> `RepoMetaDataUpdater`. +> +> **Why:** Bypassing the standard CLA requirements when proposing changes +> directly via the project access review API allowed unverified users to submit +> project-level configuration modifications. Failing to adhere to this typically +> results in **Compliance / Security Bypass**. + +**Trap 1: Relying on standard source code pushing paths for CLA enforcement +while ignoring REST-based project administration paths.** + +**Don't:** + +* Permitting REST API calls to `/access:review` to succeed if the user is + authenticated, without checking their legal agreement status. + +**Do:** + +```java +// Verify CLA explicitly before applying access review payloads +contributorAgreementsChecker.check(user.getAccountId()); +``` + +-------------------------------------------------------------------------------- + +### Cross-Domain Dependencies + +* **Upstream:** T4 | Access Control & Impersonation Security - *Base user + permissions and identity must be established and validated before executing + legal agreement compliance checks.* +* **Downstream:** T6 | REST API Resource Routing & Payload Optimization - + *REST endpoint handlers must integrate explicit compliance checkers prior to + validating and persisting configuration payloads.*
diff --git a/configs/skills/typescript_style_review/SKILL.md b/configs/skills/typescript_style_review/SKILL.md new file mode 100644 index 0000000..d1e79f2 --- /dev/null +++ b/configs/skills/typescript_style_review/SKILL.md
@@ -0,0 +1,42 @@ +--- +name: typescript-style-review +description: Reviews TypeScript code against the Google TS Style Guide. +--- + +# TypeScript Style Review Guide + +## Executive Summary + +This guide serves as the instruction set for reviewing TypeScript code. The +goal is to ensure that all TypeScript code adheres to the official Google +TypeScript Style Guide. + +## Guidelines + +You must review the TypeScript files in the changelist and ensure they comply +with the rules and best practices outlined in the official Google TypeScript +Style Guide. + +### Reference +* **Google TypeScript Style Guide**: + https://google.github.io/styleguide/tsguide.html + +## Review Instructions + +When performing the review: +1. Identify any violations of the Google TypeScript Style Guide. +2. For each violation, provide a clear explanation of the problem, a + suggestion for how to fix it (with a code snippet if appropriate), and a + reference to the relevant section of the style guide. +3. Format your comments using the following template: + +### Problem +Clearly and succinctly describe the style guide violation. + +### Suggestion +Provide a suggestion for improvement, including a code snippet. + +### Reference +Provide the link to the relevant section in the Google TypeScript Style Guide +(e.g., +`https://google.github.io/styleguide/tsguide.html#visibility-of-properties-accessed-from-templates`).
diff --git a/contrib/git-exproll.sh b/contrib/git-exproll.sh deleted file mode 100644 index 9ad7a85..0000000 --- a/contrib/git-exproll.sh +++ /dev/null
@@ -1,566 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) 2012, Code Aurora Forum. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# # Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# # Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# # Neither the name of Code Aurora Forum, Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED -# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS -# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR -# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN -# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -usage() { # error_message - - cat <<-EOF - usage: $(basename $0) [-unvt] [--noref] [--nolosse] [-r|--ratio number] - [git gc option...] git.repo - - -u|-h usage/help - -v verbose - -n dry-run don't actually repack anything - -t touch treat repo as if it had been touched - --noref avoid extra ref packing timestamp checking - --noloose do not run just because there are loose object dirs - (repacking may still run if they are referenced) - -r ratio <number> packfile ratio to aim for (default 10) - - git gc option will be passed as args to git gc - - git.repo to run gc against - - Garbage collect using a pseudo logarithmic packfile maintenance - approach. This approach attempts to minimize packfile churn - by keeping several generations of varying sized packfiles around - and only consolidating packfiles (or loose objects) which are - either new packfiles, or packfiles close to the same size as - another packfile. - - An estimate is used to predict when rollups (one consolidation - would cause another consolidation) would occur so that this - rollup can be done all at once via a single repack. This reduces - both the runtime and the pack file churn in rollup cases. - - Approach: plan each consolidation by creating a table like this: - - Id Keep Size Sha1(or consolidation list) Actions(repack down up note) - 1 - 11356 9052edfb7392646cd4e5f362b953675985f01f96 y - - New - 2 - 429088 010904d5c11cd26a79fda91b01ab454d1001b402 y - - New - c1 - 440444 [1,2] - - - - - Id: numbers preceded by a c are estimated "c pack" files - Keep: - none, k private keep, o our keep - Size: in disk blocks (default du output) - Sha1: of packfile, or consolidation list of packfile ids - Actions - repack: - n no, y yes - down: - noop, ^ consolidate with a file above - up: - noop, v consolidate with a file below - note: Human description of script decisions: - New (file is a new packfile) - Consolidate with:<list of packfile ids> - (too far from:<list of packfile ids>) - - On the first pass, always consolidate any new packfiles along - with loose objects and along with any packfiles which are within - the ratio size of their predecessors (note, the list is ordered - by increasing size). After each consolidation, insert a fake - consolidation, or "c pack", to naively represent the size and - ordered positioning of the anticipated new consolidated pack. - Every time a new pack is planned, rescan the list in case the - new "c pack" would cause more consolidation... - - Once the packfiles which need consolidation are determined, the - packfiles which will not be consolidated are marked with a .keep - file, and those which will be consolidated will have their .keep - removed if they have one. Thus, the packfiles with a .keep will - not get repacked. - - Packfile consolidation is determined by the --ratio parameter - (default is 10). This ratio is somewhat of a tradeoff. The - smaller the number, the more packfiles will be kept on average; - this increases disk utilization somewhat. However, a larger - ratio causes greater churn and may increase disk utilization due - to deleted packfiles not being reclaimed since they may still be - kept open by long running applications such as Gerrit. Sane - ratio values are probably between 2 and 10. Since most - consolidations actually end up smaller than the estimated - consolidated packfile size (due to compression), the true ratio - achieved will likely be 1 to 2 greater than the target ratio. - The smaller the target ratio, the greater this discrepancy. - - Finally, attempt to skip garbage collection entirely on untouched - repos. In order to determine if a repo has been touched, use the - timestamp on the script's keep files, if any relevant file/dir - is newer than a keep marker file, assume that the repo has been - touched and gc needs to run. Also assume gc needs to run whenever - there are loose object dirs since they may contain untouched - unreferenced loose objects which need to be pruned (once they - expire). - - In order to allow the keep files to be an effective timestamp - marker to detect relevant changes in a repo since the last run, - all relevant files and directories which may be modified during a - gc run (even during a noop gc run), must have their timestamps - reset to the same time as the keep files or gc will always run - even on untouched repos. The relevant files/dirs are all those - files and directories which garbage collection, object packing, - ref packing and pruning might change during noop actions. -EOF - - [ -n "$1" ] && info "ERROR $1" - - exit 128 -} - -debug() { [ -n "$SW_V" ] && info "$1" ; } -info() { echo "$1" >&2 ; } - -array_copy() { #v2 # array_src array_dst - local src=$1 dst=$2 - local s i=0 - eval s=\${#$src[@]} - while [ $i -lt $s ] ; do - eval $dst[$i]=\"\${$src[$i]}\" - i=$(($i + 1)) - done -} - -array_equals() { #v2 # array_name [vals...] - local a=$1 ; shift - local s=0 t=() val - array_copy "$a" t - for s in "${!t[@]}" ; do s=$((s+1)) ; done - [ "$s" -ne "$#" ] && return 1 - for val in "${t[@]}" ; do - [ "$val" = "$1" ] || return 2 - shift - done - return 0 -} - -packs_sizes() { # git.repo > "size pack"... - du -s "$1"/objects/pack/pack-$SHA1.pack | sort -n 2> /dev/null -} - -is_ourkeep() { grep -q "$KEEP" "$1" 2> /dev/null ; } # keep -has_ourkeep() { is_ourkeep "$(keep_for "$1")" ; } # pack -has_keep() { [ -f "$(keep_for "$1")" ] ; } # pack -is_repo() { [ -d "$1/objects" ] && [ -d "$1/refs/heads" ] ; } # git.repo - -keep() { # pack # returns true if we added our keep - keep=$(keep_for "$1") - [ -f "$keep" ] && return 1 - echo "$KEEP" > "$keep" - return 0 -} - -keep_for() { # packfile > keepfile - local keep=$(echo "$1" | sed -es'/\.pack$/.keep/') - [ "${keep/.keep}" = "$keep" ] && return 1 - echo "$keep" -} - -idx_for() { # packfile > idxfile - local idx=$(echo "$1" | sed -es'/\.pack$/.idx/') - [ "${idx/.idx}" = "$idx" ] && return 1 - echo "$idx" -} - -# pack_or_keep_file > sha -sha_for() { echo "$1" | sed -es'|\(.*/\)*pack-\([^.]*\)\..*$|\2|' ; } - -private_keeps() { # git.repo -> sets pkeeps - local repo=$1 ary=$2 - local keep keeps=("$repo"/objects/pack/pack-$SHA1.keep) - pkeeps=() - for keep in "${keeps[@]}" ; do - is_ourkeep "$keep" || pkeeps=("${pkeeps[@]}" "$keep") - done -} - -is_tooclose() { [ "$(($1 * $RATIO))" -gt "$2" ] ; } # smaller larger - -unique() { # [args...] > unique_words - local lines=$(while [ $# -gt 0 ] ; do echo "$1" ; shift ; done) - lines=$(echo "$lines" | sort -u) - echo $lines # as words -} - -outfs() { # fs [args...] > argfs... - local fs=$1 ; shift - [ $# -gt 0 ] && echo -n "$1" ; shift - while [ $# -gt 0 ] ; do echo -n "$fs$1" ; shift ; done -} - -sort_list() { # < list > formatted_list - # n has_keep size sha repack down up note - awk '{ note=$8; for(i=8;i<NF;i++) note=note " "$(i+1) - printf("%-5s %s %-14s %-40s %s %s %s %s\n", \ - $1,$2, $3, $4, $5,$6,$7,note)}' |\ - sort -k 3,3n -k 1,1n -} - -is_touched() { # git.repo - local repo=$1 - local loose keep ours newer - [ -n "$SW_T" ] && { debug "$SW_T -> treat as touched" ; return 0 ; } - - if [ -z "$SW_LOOSE" ] ; then - # If there are loose objects, they may need to be pruned, - # run even if nothing has really been touched. - loose=$(find "$repo/objects" -type d \ - -wholename "$repo/objects/[0-9][0-9]" - -print -quit 2>/dev/null) - [ -n "$loose" ] && { info "There are loose object directories" ; return 0 ; } - fi - - # If we don't have a keep, the current packfiles may not have been - # compressed with the current gc policy (gc may never have been run), - # so run at least once to repack everything. Also, we need a marker - # file for timestamp tracking (a dir needs to detect changes within - # it, so it cannot be a marker) and our keeps are something we control, - # use them. - for keep in "$repo"/objects/pack/pack-$SHA1.keep ; do - is_ourkeep "$keep" && { ours=$keep ; break ; } - done - [ -z "$ours" ] && { info 'We have no keep (we have never run?): run' ; return 0 ; } - - debug "Our timestamp keep: $ours" - # The wholename stuff seems to get touched by a noop git gc - newer=$(find "$repo/objects" "$repo/refs" "$repo/packed-refs" \ - '!' -wholename "$repo/objects/info" \ - '!' -wholename "$repo/objects/info/*" \ - -newer "$ours" \ - -print -quit 2>/dev/null) - [ -z "$newer" ] && return 1 - - info "Touched since last run: $newer" - return 0 -} - -touch_refs() { # git.repo start_date refs - local repo=$1 start_date=$2 refs=$3 - ( - debug "Setting start date($start_date) on unpacked refs:" - debug "$refs" - cd "$repo/refs" || return - # safe to assume no newlines in a ref name - echo "$refs" | xargs -d '\n' -n 1 touch -c -d "$start_date" - ) -} - -set_start_date() { # git.repo start_date refs refdirs packedrefs [packs] - local repo=$1 start_date=$2 refs=$3 refdirs=$4 packedrefs=$5 ; shift 5 - local pack keep idx repacked - - # This stuff is touched during object packs - while [ $# -gt 0 ] ; do - pack=$1 ; shift - keep="$(keep_for "$pack")" - idx="$(idx_for "$pack")" - touch -c -d "$start_date" "$pack" "$keep" "$idx" - debug "Setting start date on: $pack $keep $idx" - done - # This will prevent us from detecting any deletes in the pack dir - # since gc ran, except for private keeps which we are checking - # manually. But there really shouldn't be any other relevant deletes - # in this dir which should cause us to rerun next time, deleting a - # pack or index file by anything but gc would be bad! - debug "Setting start date on pack dir: $start_date" - touch -c -d "$start_date" "$repo/objects/pack" - - - if [ -z "$SW_REFS" ] ; then - repacked=$(find "$repo/packed-refs" -newer "$repo/objects/pack" - -print -quit 2>/dev/null) - if [ -n "$repacked" ] ; then - # The ref dirs and packed-ref files seem to get touched even on - # a noop refpacking - debug "Setting start date on packed-refs" - touch -c -d "$start_date" "$repo/packed-refs" - touch_refs "$repo" "$start_date" "$refdirs" - - # A ref repack does not imply a ref change, but since it is - # hard to tell, simply assume so - if [ "$refs" != "$(cd "$repo/refs" ; find -depth)" ] || \ - [ "$packedrefs" != "$(<"$repo/packed-refs")" ] ; then - # We retouch if needed (instead of simply checking then - # touching) to avoid a race between the check and the set. - debug " but refs actually got packed, so retouch packed-refs" - touch -c "$repo/packed-refs" - fi - fi - fi -} - -note_consolidate() { # note entry > note (no duplicated consolidated entries) - local note=$1 entry=$2 - local entries=() ifs=$IFS - if echo "$note" | grep -q 'Consolidate with:[0-9,c]' ; then - IFS=, - entries=( $(echo "$note" | sed -es'/^.*Consolidate with:\([0-9,c]*\).*$/\1/') ) - note=( $(echo "$note" | sed -es'/Consolidate with:[0-9,c]*//') ) - IFS=$ifs - fi - entries=( $(unique "${entries[@]}" "$entry") ) - echo "$note Consolidate with:$(outfs , "${entries[@]}")" -} - -note_toofar() { # note entry > note (no duplicated "too far" entries) - local note=$1 entry=$2 - local entries=() ifs=$IFS - if echo "$note" | grep -q '(too far from:[0-9,c]*)' ; then - IFS=, - entries=( $(echo "$note" | sed -es'/^.*(too far from:\([0-9,c]*\)).*$/\1/') ) - note=( $(echo "$note" | sed -es'/(too far from:[0-9,c]*)//') ) - IFS=$ifs - fi - entries=( $(unique "${entries[@]}" "$entry") ) - echo "$note (too far from:$(outfs , "${entries[@]}"))" -} - -last_entry() { # isRepack pline repackline > last_rows_entry - local size_hit=$1 pline=$2 repackline=$3 - if [ -n "$pline" ] ; then - if [ -n "$size_hit" ] ; then - echo "$repack_line" - else - echo "$pline" - fi - fi -} - -init_list() { # git.repo > shortlist - local repo=$1 - local file - local n has_keep size sha repack - - packs_sizes "$1" | { - while read size file ; do - n=$((n+1)) - repack=n - has_keep=- - if has_keep "$file" ; then - has_keep=k - has_ourkeep "$file" && has_keep=o - fi - sha=$(sha_for "$file") - echo "$n $has_keep $size $sha $repack" - done - } | sort_list -} - -consolidate_list() { # run < list > list - local run=$1 - local sum=0 psize=0 sum_size=0 size_hit pn clist pline repackline - local n has_keep size sha repack down up note - - { - while read n has_keep size sha repack down up note; do - [ -z "$up" ] && up='-' - [ -z "$down" ] && down="-" - - if [ "$has_keep" = "k" ] ; then - echo "$n $has_keep $size $sha $repack - - Private" - continue - fi - - if [ "$repack" = "n" ] ; then - if is_tooclose $psize $size ; then - size_hit=y - repack=y - sum=$(($sum + $sum_size + $size)) - sum_size=0 # Prevents double summing this entry - clist=($(unique "${clist[@]}" $pn $n)) - down="^" - [ "$has_keep" = "-" ] && note="$note New +" - note=$(note_consolidate "$note" "$pn") - elif [ "$has_keep" = "-" ] ; then - repack=y - sum=$(($sum + $size)) - sum_size=0 # Prevents double summing this entry - clist=($(unique "${clist[@]}" $n)) - note="$note New" - elif [ $psize -ne 0 ] ; then - sum_size=$size - down="!" - note=$(note_toofar "$note" "$pn") - else - sum_size=$size - fi - else - sum_size=$size - fi - - # By preventing "c files" (consolidated) from being marked - # "repack" they won't get keeps - repack2=y - [ "${n/c}" != "$n" ] && { repack=- ; repack2=- ; } - - last_entry "$size_hit" "$pline" "$repack_line" - # Delay the printout until we know whether we are - # being consolidated with the entry following us - # (we won't know until the next iteration). - # size_hit is used to determine which of the lines - # below will actually get printed above on the next - # iteration. - pline="$n $has_keep $size $sha $repack $down $up $note" - repack_line="$n $has_keep $size $sha $repack2 $down v $note" - - pn=$n ; psize=$size # previous entry data - size_hit='' # will not be consolidated up - - done - last_entry "$size_hit" "$pline" "$repack_line" - - [ $sum -gt 0 ] && echo "c$run - $sum [$(outfs , "${clist[@]}")] - - -" - - } | sort_list -} - -process_list() { # git.repo > list - local list=$(init_list "$1") plist run=0 - - while true ; do - plist=$list - run=$((run +1)) - list=$(echo "$list" | consolidate_list "$run") - if [ "$plist" != "$list" ] ; then - debug "------------------------------------------------------------------------------------" - debug "$HEADER" - debug "$list" - else - break - fi - done - debug "------------------------------------------------------------------------------------" - echo "$list" -} - -repack_list() { # git.repo < list - local repo=$1 - local start_date newpacks=0 pkeeps keeps=1 refs refdirs rtn - local packedrefs=$(<"$repo/packed-refs") - - # so they don't appear touched after a noop refpacking - if [ -z "$SW_REFS" ] ; then - refs=$(cd "$repo/refs" ; find -depth) - refdirs=$(cd "$repo/refs" ; find -type d -depth) - debug "Before refs:" - debug "$refs" - fi - - # Find a private keep snapshot which has not changed from - # before our start_date so private keep deletions during gc - # can be detected - while ! array_equals pkeeps "${keeps[@]}" ; do - debug "Getting a private keep snapshot" - private_keeps "$repo" - keeps=("${pkeeps[@]}") - debug "before keeps: ${keeps[*]}" - start_date=$(date) - private_keeps "$repo" - debug "after keeps: ${pkeeps[*]}" - done - - while read n has_keep size sha repack down up note; do - if [ "$repack" = "y" ] ; then - keep="$repo/objects/pack/pack-$sha.keep" - info "Repacking $repo/objects/pack/pack-$sha.pack" - [ -f "$keep" ] && rm -f "$keep" - fi - done - - ( cd "$repo" && git gc "${GC_OPTS[@]}" ) ; rtn=$? - - # Mark any files withoug a .keep with our .keep - packs=("$repo"/objects/pack/pack-$SHA1.pack) - for pack in "${packs[@]}" ; do - if keep "$pack" ; then - info "New pack: $pack" - newpacks=$((newpacks+1)) - fi - done - - # Record start_time. If there is more than 1 new packfile, we - # don't want to risk touching it with an older date since that - # would prevent consolidation on the next run. If the private - # keeps have changed, then we should run next time no matter what. - if [ $newpacks -le 1 ] || ! array_equals pkeeps "${keeps[@]}" ; then - set_start_date "$repo" "$start_date" "$refs" "$refdirs" "$packedrefs" "${packs[@]}" - fi - - return $rtn # we really only care about the gc error code -} - -git_gc() { # git.repo - local list=$(process_list "$1") - if [ -z "$SW_V" ] ; then - info "Running $PROG on $1. git gc options: ${GC_OPTS[@]}" - echo "$HEADER" >&2 - echo "$list" >&2 ; - fi - echo "$list" | repack_list "$1" -} - - -PROG=$(basename "$0") -HEADER="Id Keep Size Sha1(or consolidation list) Actions(repack down up note)" -KEEP=git-exproll -HEX='[0-9a-f]' -HEX10=$HEX$HEX$HEX$HEX$HEX$HEX$HEX$HEX$HEX$HEX -SHA1=$HEX10$HEX10$HEX10$HEX10 - -RATIO=10 -SW_N='' ; SW_V='' ; SW_T='' ; SW_REFS='' ; SW_LOOSE='' ; GC_OPTS=() -while [ $# -gt 0 ] ; do - case "$1" in - -u|-h) usage ;; - -n) SW_N="$1" ;; - -v) SW_V="$1" ;; - - -t) SW_T="$1" ;; - --norefs) SW_REFS="$1" ;; - --noloose) SW_LOOSE="$1" ;; - - -r|--ratio) shift ; RATIO="$1" ;; - - *) [ $# -le 1 ] && break - GC_OPTS=( "${GC_OPTS[@]}" "$1" ) - ;; - esac - shift -done - - -REPO="$1" -if ! is_repo "$REPO" ; then - REPO=$REPO/.git - is_repo "$REPO" || usage "($1) is not likely a git repo" -fi - - -if [ -z "$SW_N" ] ; then - is_touched "$REPO" || { info "Repo untouched since last run" ; exit ; } - git_gc "$REPO" -else - is_touched "$REPO" || info "Repo untouched since last run, analyze anyway." - process_list "$REPO" >&2 -fi
diff --git a/external_deps.lock.json b/external_deps.lock.json index 7d8bf51..5ffa8970b 100644 --- a/external_deps.lock.json +++ b/external_deps.lock.json
@@ -8,10 +8,10 @@ "com.github.ben-manes.caffeine:caffeine": 1735183231, "com.github.ben-manes.caffeine:guava": -2131703186, "com.github.rholder:guava-retrying": 1309513165, - "com.google.auto.factory:auto-factory": -1233360965, - "com.google.auto.service:auto-service-annotations": 953830824, - "com.google.auto.value:auto-value": 1146221104, - "com.google.auto.value:auto-value-annotations": -1101276935, + "com.google.auto.factory:auto-factory": -1222857861, + "com.google.auto.service:auto-service-annotations": 964333928, + "com.google.auto.value:auto-value": -661233359, + "com.google.auto.value:auto-value-annotations": 1386235898, "com.google.auto:auto-common": -832702775, "com.google.code.findbugs:jsr305": -1992157670, "com.google.code.gson:gson": 2063056812, @@ -33,7 +33,7 @@ "com.google.inject:guice": -1660789120, "com.google.j2objc:j2objc-annotations": -727464895, "com.google.jimfs:jimfs": -1004381565, - "com.google.protobuf:protobuf-java": 1906581597, + "com.google.protobuf:protobuf-java": 1247529435, "com.google.template:soy": -1478719887, "com.google.truth.extensions:truth-java8-extension": -129319374, "com.google.truth.extensions:truth-liteproto-extension": 1463279446, @@ -109,7 +109,7 @@ "commons-dbcp:commons-dbcp": -873877417, "commons-digester:commons-digester": 1688456634, "commons-io:commons-io": 1305681826, - "commons-logging:commons-logging": 243315756, + "commons-logging:commons-logging": -264381874, "commons-net:commons-net": 1227155931, "commons-pool:commons-pool": -2015226625, "commons-validator:commons-validator": -89255997, @@ -126,10 +126,10 @@ "javax.servlet:javax.servlet-api": 669233360, "junit:junit": -744267592, "log4j:log4j": 182326902, - "net.bytebuddy:byte-buddy": 731630558, - "net.bytebuddy:byte-buddy-agent": 294689510, - "net.java.dev.jna:jna": 929040997, - "net.java.dev.jna:jna-platform": 1235639073, + "net.bytebuddy:byte-buddy": -67913315, + "net.bytebuddy:byte-buddy-agent": -728183915, + "net.java.dev.jna:jna": -867910362, + "net.java.dev.jna:jna-platform": -561312286, "net.minidev:json-smart": -1043043954, "net.sf.jopt-simple:jopt-simple": 906822697, "net.sourceforge.nekohtml:nekohtml": 1723624706, @@ -153,12 +153,12 @@ "org.apache.lucene:lucene-queries": 1341801569, "org.apache.lucene:lucene-queryparser": 2090205297, "org.apache.lucene:lucene-sandbox": 1803509280, - "org.apache.mina:mina-core": -955969490, + "org.apache.mina:mina-core": -1403307213, "org.apache.sshd:sshd-common": 1844247612, "org.apache.sshd:sshd-core": 602454664, - "org.apache.sshd:sshd-mina": -1925771193, - "org.apache.sshd:sshd-osgi": 1671034928, - "org.apache.sshd:sshd-sftp": 2079258759, + "org.apache.sshd:sshd-mina": -1915268089, + "org.apache.sshd:sshd-osgi": 1681538032, + "org.apache.sshd:sshd-sftp": 2089761863, "org.asciidoctor:asciidoctorj": -457860213, "org.assertj:assertj-core": -1145412507, "org.bouncycastle:bcpg-jdk18on": -1572213535, @@ -169,7 +169,7 @@ "org.commonmark:commonmark-ext-autolink": -1853742120, "org.commonmark:commonmark-ext-gfm-strikethrough": 350394231, "org.commonmark:commonmark-ext-gfm-tables": 1881582931, - "org.eclipse.jetty.ee10:jetty-ee10-servlet": 754433625, + "org.eclipse.jetty.ee10:jetty-ee10-servlet": 835495267, "org.eclipse.jetty.ee8:jetty-ee8-nested": -1627496831, "org.eclipse.jetty.ee8:jetty-ee8-security": 1632270986, "org.eclipse.jetty.ee8:jetty-ee8-servlet": -1338697711, @@ -210,304 +210,304 @@ "xml-apis:xml-apis": -113825062 }, "__RESOLVED_ARTIFACTS_HASH": { - "antlr:antlr": 2120497295, - "aopalliance:aopalliance": -1268465981, - "aopalliance:aopalliance:jar:sources": 442956464, - "args4j:args4j": 1165073517, - "args4j:args4j:jar:sources": -1061197748, - "ch.qos.reload4j:reload4j": 1742597253, - "ch.qos.reload4j:reload4j:jar:sources": -505557081, - "com.beust:jcommander": -86812675, - "com.beust:jcommander:jar:sources": -377940076, - "com.github.ben-manes.caffeine:caffeine": 386266583, - "com.github.ben-manes.caffeine:caffeine:jar:sources": 824791053, - "com.github.ben-manes.caffeine:guava": -586447867, - "com.github.ben-manes.caffeine:guava:jar:sources": -2102940169, - "com.github.rholder:guava-retrying": -193656863, - "com.github.rholder:guava-retrying:jar:sources": 402259526, - "com.google.auto.factory:auto-factory": 117391549, - "com.google.auto.factory:auto-factory:jar:sources": 1626334411, - "com.google.auto.service:auto-service-annotations": -2030804522, - "com.google.auto.service:auto-service-annotations:jar:sources": 1880995980, - "com.google.auto.value:auto-value": 1610847974, - "com.google.auto.value:auto-value-annotations": 818774630, - "com.google.auto.value:auto-value-annotations:jar:sources": -1313058273, - "com.google.auto.value:auto-value:jar:sources": -1087092342, - "com.google.auto:auto-common": 1232278285, - "com.google.auto:auto-common:jar:sources": 1238804620, - "com.google.code.findbugs:jsr305": 1028218835, - "com.google.code.findbugs:jsr305:jar:sources": 1130389911, - "com.google.code.gson:gson": 1676184452, - "com.google.code.gson:gson:jar:sources": -1194250318, - "com.google.common.html.types:types": -560413073, - "com.google.common.html.types:types:jar:sources": 1541167117, - "com.google.errorprone:error_prone_annotations": -2118374750, - "com.google.errorprone:error_prone_annotations:jar:sources": -88858373, - "com.google.flogger:flogger": 2071094150, - "com.google.flogger:flogger-log4j-backend": -1752942291, - "com.google.flogger:flogger-log4j-backend:jar:sources": 167088335, - "com.google.flogger:flogger-system-backend": 170636970, - "com.google.flogger:flogger-system-backend:jar:sources": 267353177, - "com.google.flogger:flogger:jar:sources": -399970922, - "com.google.flogger:google-extensions": 683610995, - "com.google.flogger:google-extensions:jar:sources": -362108063, - "com.google.gitiles:blame-cache": -1735353948, - "com.google.gitiles:blame-cache:jar:sources": -370259038, - "com.google.gitiles:gitiles-servlet": -1411302355, - "com.google.gitiles:gitiles-servlet:jar:sources": 192620509, - "com.google.guava:failureaccess": -121989663, - "com.google.guava:failureaccess:jar:sources": 2092951686, - "com.google.guava:guava": -1983533712, - "com.google.guava:guava-testlib": 869030181, - "com.google.guava:guava-testlib:jar:sources": -1116521441, - "com.google.guava:guava:jar:sources": 1163674882, - "com.google.guava:listenablefuture": -181371066, - "com.google.inject.extensions:guice-assistedinject": -191835468, - "com.google.inject.extensions:guice-assistedinject:jar:sources": 1682505664, - "com.google.inject.extensions:guice-servlet": -1455734849, - "com.google.inject.extensions:guice-servlet:jar:sources": 1055060429, - "com.google.inject:guice": -1714385564, - "com.google.inject:guice:jar:sources": 1581524423, - "com.google.j2objc:j2objc-annotations": -1833492981, - "com.google.j2objc:j2objc-annotations:jar:sources": 1596689722, - "com.google.jimfs:jimfs": -542987600, - "com.google.jimfs:jimfs:jar:sources": 1692501412, - "com.google.jsinterop:jsinterop-annotations": 1916195800, - "com.google.jsinterop:jsinterop-annotations:jar:sources": -1738684177, - "com.google.protobuf:protobuf-java": -1428941287, - "com.google.protobuf:protobuf-java:jar:sources": -121171885, - "com.google.template:soy": 296053403, - "com.google.template:soy:jar:sources": 934091432, - "com.google.truth.extensions:truth-java8-extension": 2072975728, - "com.google.truth.extensions:truth-java8-extension:jar:sources": -404559504, - "com.google.truth.extensions:truth-liteproto-extension": -47385409, - "com.google.truth.extensions:truth-liteproto-extension:jar:sources": -96134878, - "com.google.truth.extensions:truth-proto-extension": 92287925, - "com.google.truth.extensions:truth-proto-extension:jar:sources": -1961892523, - "com.google.truth:truth": 1710077790, - "com.google.truth:truth:jar:sources": -1194452779, - "com.googlecode.javaewah:JavaEWAH": 157530325, - "com.googlecode.javaewah:JavaEWAH:jar:sources": 728434282, - "com.googlecode.prolog-cafe:prolog-cafeteria": 1166081954, - "com.googlecode.prolog-cafe:prolog-cafeteria:jar:sources": 186391213, - "com.googlecode.prolog-cafe:prolog-compiler": -741003406, - "com.googlecode.prolog-cafe:prolog-compiler:jar:sources": 126666107, - "com.googlecode.prolog-cafe:prolog-io": -900599131, - "com.googlecode.prolog-cafe:prolog-io:jar:sources": -1440088888, - "com.googlecode.prolog-cafe:prolog-runtime": 828309397, - "com.googlecode.prolog-cafe:prolog-runtime:jar:sources": -1157091735, - "com.h2database:h2": 682770038, - "com.h2database:h2:jar:sources": -675004040, - "com.ibm.icu:icu4j": -1964869803, - "com.ibm.icu:icu4j:jar:sources": 864352766, - "com.icegreen:greenmail": 537606032, - "com.icegreen:greenmail:jar:sources": -1978817971, - "com.jcraft:jsch": 1986987929, - "com.jcraft:jsch:jar:sources": 707308247, - "com.jcraft:jzlib": 1581025040, - "com.jcraft:jzlib:jar:sources": 807243431, - "com.ryanharter.auto.value:auto-value-gson-extension": 1639397257, - "com.ryanharter.auto.value:auto-value-gson-extension:jar:sources": 1925708692, - "com.ryanharter.auto.value:auto-value-gson-factory": 1817252669, - "com.ryanharter.auto.value:auto-value-gson-factory:jar:sources": -768025607, - "com.ryanharter.auto.value:auto-value-gson-runtime": 1015116695, - "com.ryanharter.auto.value:auto-value-gson-runtime:jar:sources": -831897407, - "com.squareup:javapoet": 1313128977, - "com.squareup:javapoet:jar:sources": -1200205912, - "com.sun.mail:javax.mail": -382507283, - "com.sun.mail:javax.mail:jar:sources": 1073816789, - "com.vladsch.flexmark:flexmark-all:jar:lib": 347351331, - "commons-codec:commons-codec": 1048744614, - "commons-codec:commons-codec:jar:sources": -1637317619, - "commons-dbcp:commons-dbcp": -1946826524, - "commons-dbcp:commons-dbcp:jar:sources": -1238094905, - "commons-io:commons-io": 1463238719, - "commons-io:commons-io:jar:sources": -120466886, - "commons-net:commons-net": 29132340, - "commons-net:commons-net:jar:sources": 1898783167, - "commons-pool:commons-pool": -3173032, - "commons-pool:commons-pool:jar:sources": 1763080609, - "commons-validator:commons-validator": -1615658806, - "commons-validator:commons-validator:jar:sources": 1267489728, - "dk.brics:automaton": 1622735787, - "dk.brics:automaton:jar:sources": 1946522418, - "eu.medsea.mimeutil:mime-util": 1812001452, - "io.dropwizard.metrics:metrics-core": -1856132969, - "io.dropwizard.metrics:metrics-core:jar:sources": 159424675, - "io.github.java-diff-utils:java-diff-utils": 457660153, - "io.github.java-diff-utils:java-diff-utils:jar:sources": -619235043, - "io.sweers.autotransient:autotransient": 1340991802, - "io.sweers.autotransient:autotransient:jar:sources": 80080981, - "jakarta.inject:jakarta.inject-api": 851002214, - "jakarta.inject:jakarta.inject-api:jar:sources": -1570274750, - "jakarta.servlet:jakarta.servlet-api": -92202749, - "jakarta.servlet:jakarta.servlet-api:jar:sources": -1017144686, - "javax.activation:activation": -1092171588, - "javax.activation:activation:jar:sources": 1310123688, - "javax.inject:javax.inject": -1960241368, - "javax.inject:javax.inject:jar:sources": -34689928, - "javax.servlet:javax.servlet-api": 1796323811, - "javax.servlet:javax.servlet-api:jar:sources": 137081253, - "junit:junit": 238187285, - "junit:junit:jar:sources": 1084731434, - "net.bytebuddy:byte-buddy": 1931414768, - "net.bytebuddy:byte-buddy-agent": 1328010878, - "net.bytebuddy:byte-buddy-agent:jar:sources": -899388612, - "net.bytebuddy:byte-buddy:jar:sources": 1062390101, - "net.java.dev.jna:jna": -1916526385, - "net.java.dev.jna:jna-platform": 459618853, - "net.java.dev.jna:jna-platform:jar:sources": -2077304647, - "net.java.dev.jna:jna:jar:sources": 1518406952, - "net.minidev:json-smart": -1913865264, - "net.minidev:json-smart:jar:sources": 475976688, - "net.sf.jopt-simple:jopt-simple": 1531230776, - "net.sf.jopt-simple:jopt-simple:jar:sources": -1087190884, - "net.sourceforge.nekohtml:nekohtml": 589300296, - "net.sourceforge.nekohtml:nekohtml:jar:sources": 1455941061, - "org.antlr:ST4": 943007212, - "org.antlr:ST4:jar:sources": 1076411807, - "org.antlr:antlr": -10615566, - "org.antlr:antlr-runtime": -20800628, - "org.antlr:antlr-runtime:jar:sources": 1652502717, - "org.antlr:antlr:jar:sources": -1207538557, - "org.antlr:stringtemplate": -1826141279, - "org.antlr:stringtemplate:jar:sources": 831821310, - "org.apache.commons:commons-compress": -2083070990, - "org.apache.commons:commons-compress:jar:sources": 128824058, - "org.apache.commons:commons-lang3": 1593572986, - "org.apache.commons:commons-lang3:jar:sources": 1589181154, - "org.apache.commons:commons-math3": 1532637713, - "org.apache.commons:commons-math3:jar:sources": 1655744467, - "org.apache.commons:commons-text": -1205775100, - "org.apache.commons:commons-text:jar:sources": 1247506591, - "org.apache.httpcomponents:fluent-hc": 1857459163, - "org.apache.httpcomponents:fluent-hc:jar:sources": 1555727994, - "org.apache.httpcomponents:httpclient": -940371367, - "org.apache.httpcomponents:httpclient:jar:sources": 1069741198, - "org.apache.httpcomponents:httpcore": -279989236, - "org.apache.httpcomponents:httpcore:jar:sources": 1796128621, - "org.apache.james:apache-mime4j-core": 596731567, - "org.apache.james:apache-mime4j-core:jar:sources": 1221134342, - "org.apache.james:apache-mime4j-dom": 2052814931, - "org.apache.james:apache-mime4j-dom:jar:sources": 2056952874, - "org.apache.lucene:lucene-analysis-common": 1674710283, - "org.apache.lucene:lucene-analysis-common:jar:sources": 334029180, - "org.apache.lucene:lucene-backward-codecs": -508942990, - "org.apache.lucene:lucene-backward-codecs:jar:sources": 1999324390, - "org.apache.lucene:lucene-core": -1994692613, - "org.apache.lucene:lucene-core:jar:sources": -164995544, - "org.apache.lucene:lucene-misc": -739909715, - "org.apache.lucene:lucene-misc:jar:sources": -128984682, - "org.apache.lucene:lucene-queryparser": -1416736164, - "org.apache.lucene:lucene-queryparser:jar:sources": 1960943881, - "org.apache.mina:mina-core": 603384434, - "org.apache.mina:mina-core:jar:sources": 1763715353, - "org.apache.sshd:sshd-mina": 707703392, - "org.apache.sshd:sshd-mina:jar:sources": 458343698, - "org.apache.sshd:sshd-osgi": 1463287175, - "org.apache.sshd:sshd-osgi:jar:sources": -1934894876, - "org.apache.sshd:sshd-sftp": -1455178358, - "org.apache.sshd:sshd-sftp:jar:sources": 1450030982, - "org.asciidoctor:asciidoctorj": -943976727, - "org.asciidoctor:asciidoctorj:jar:sources": -1022891917, - "org.assertj:assertj-core": 1465577983, - "org.assertj:assertj-core:jar:sources": 1906472612, - "org.bouncycastle:bcpg-jdk18on": 726067526, - "org.bouncycastle:bcpg-jdk18on:jar:sources": 812126316, - "org.bouncycastle:bcpkix-jdk18on": 2070253037, - "org.bouncycastle:bcpkix-jdk18on:jar:sources": -1497475064, - "org.bouncycastle:bcprov-jdk18on": -1778261676, - "org.bouncycastle:bcprov-jdk18on:jar:sources": -1245673492, - "org.bouncycastle:bcutil-jdk18on": 86705488, - "org.bouncycastle:bcutil-jdk18on:jar:sources": -784396673, - "org.checkerframework:checker-compat-qual": -1678975214, - "org.checkerframework:checker-compat-qual:jar:sources": -673395382, - "org.checkerframework:checker-qual": -1657280421, - "org.checkerframework:checker-qual:jar:sources": 324669507, - "org.commonmark:commonmark": -1467575831, - "org.commonmark:commonmark-ext-autolink": -1808977749, - "org.commonmark:commonmark-ext-autolink:jar:sources": -1324197024, - "org.commonmark:commonmark-ext-gfm-strikethrough": 1872267513, - "org.commonmark:commonmark-ext-gfm-strikethrough:jar:sources": 1099767676, - "org.commonmark:commonmark-ext-gfm-tables": -435057552, - "org.commonmark:commonmark-ext-gfm-tables:jar:sources": -1247551792, - "org.commonmark:commonmark:jar:sources": -1275158082, - "org.eclipse.jetty.ee10:jetty-ee10-servlet": 72362488, - "org.eclipse.jetty.ee10:jetty-ee10-servlet:jar:sources": -916702046, - "org.eclipse.jetty.ee8:jetty-ee8-nested": -358625262, - "org.eclipse.jetty.ee8:jetty-ee8-nested:jar:sources": -259350089, - "org.eclipse.jetty.ee8:jetty-ee8-security": -2741086, - "org.eclipse.jetty.ee8:jetty-ee8-security:jar:sources": -741677001, - "org.eclipse.jetty.ee8:jetty-ee8-servlet": 1299305256, - "org.eclipse.jetty.ee8:jetty-ee8-servlet:jar:sources": 1072157307, - "org.eclipse.jetty.toolchain:jetty-servlet-api": 380579650, - "org.eclipse.jetty.toolchain:jetty-servlet-api:jar:sources": 212656908, - "org.eclipse.jetty:jetty-http": -469722928, - "org.eclipse.jetty:jetty-http:jar:sources": 1226271223, - "org.eclipse.jetty:jetty-io": 433684656, - "org.eclipse.jetty:jetty-io:jar:sources": -326976094, - "org.eclipse.jetty:jetty-jmx": -364185700, - "org.eclipse.jetty:jetty-jmx:jar:sources": -1132220518, - "org.eclipse.jetty:jetty-security": -1889906675, - "org.eclipse.jetty:jetty-security:jar:sources": -1709000645, - "org.eclipse.jetty:jetty-server": -65189690, - "org.eclipse.jetty:jetty-server:jar:sources": -2040105502, - "org.eclipse.jetty:jetty-session": -2074191661, - "org.eclipse.jetty:jetty-session:jar:sources": -1197680927, - "org.eclipse.jetty:jetty-util": 750724466, - "org.eclipse.jetty:jetty-util-ajax": -2113339989, - "org.eclipse.jetty:jetty-util-ajax:jar:sources": 793410527, - "org.eclipse.jetty:jetty-util:jar:sources": -1240837016, - "org.hamcrest:hamcrest": -1550813651, - "org.hamcrest:hamcrest-core": -1198150244, - "org.hamcrest:hamcrest-core:jar:sources": -1576492927, - "org.hamcrest:hamcrest:jar:sources": -1807813747, - "org.jruby:jruby-complete": 407729900, - "org.jruby:jruby-complete:jar:sources": 554963637, - "org.jsoup:jsoup": 789840847, - "org.jsoup:jsoup:jar:sources": 1377510617, - "org.jspecify:jspecify": -797399878, - "org.jspecify:jspecify:jar:sources": 1011232509, - "org.mockito:mockito-core": -1882151935, - "org.mockito:mockito-core:jar:sources": 41691546, - "org.nibor.autolink:autolink": 1237374319, - "org.nibor.autolink:autolink:jar:sources": 1695391615, - "org.objenesis:objenesis": -1055367721, - "org.objenesis:objenesis:jar:sources": 707329220, - "org.openid4java:openid4java": 1656473252, - "org.openid4java:openid4java:jar:sources": 789962531, - "org.openjdk.jmh:jmh-core": -381500549, - "org.openjdk.jmh:jmh-core:jar:sources": -1014071512, - "org.openjdk.jmh:jmh-generator-annprocess": -1315975534, - "org.openjdk.jmh:jmh-generator-annprocess:jar:sources": 81479330, - "org.ow2.asm:asm": 1540593910, - "org.ow2.asm:asm-analysis": 1735619699, - "org.ow2.asm:asm-analysis:jar:sources": 609078251, - "org.ow2.asm:asm-commons": -231881389, - "org.ow2.asm:asm-commons:jar:sources": 1140590651, - "org.ow2.asm:asm-tree": 1288506580, - "org.ow2.asm:asm-tree:jar:sources": -1050091087, - "org.ow2.asm:asm-util": 247171895, - "org.ow2.asm:asm-util:jar:sources": 354115585, - "org.ow2.asm:asm:jar:sources": 511988412, - "org.roaringbitmap:RoaringBitmap": 506688526, - "org.roaringbitmap:RoaringBitmap:jar:sources": 1956160126, - "org.roaringbitmap:shims": -1352997269, - "org.roaringbitmap:shims:jar:sources": -981899312, - "org.slf4j:jcl-over-slf4j": 429539128, - "org.slf4j:jcl-over-slf4j:jar:sources": 619461888, - "org.slf4j:slf4j-api": -630423788, - "org.slf4j:slf4j-api:jar:sources": -1112457040, - "org.slf4j:slf4j-ext": -1747709512, - "org.slf4j:slf4j-ext:jar:sources": 2070817143, - "org.slf4j:slf4j-reload4j": -1999639452, - "org.slf4j:slf4j-reload4j:jar:sources": -1774817644, - "org.slf4j:slf4j-simple": 426427623, - "org.slf4j:slf4j-simple:jar:sources": 1024382854, - "org.tukaani:xz": 1514570375, - "org.tukaani:xz:jar:sources": -610773207, - "xerces:xercesImpl": -723395208, - "xerces:xercesImpl:jar:sources": -127516017 + "antlr:antlr": -947608540, + "aopalliance:aopalliance": 1403132272, + "aopalliance:aopalliance:jar:sources": -1259601821, + "args4j:args4j": 208424838, + "args4j:args4j:jar:sources": -370825913, + "ch.qos.reload4j:reload4j": -523782034, + "ch.qos.reload4j:reload4j:jar:sources": -338991604, + "com.beust:jcommander": -1223620106, + "com.beust:jcommander:jar:sources": 1270399231, + "com.github.ben-manes.caffeine:caffeine": -1342502459, + "com.github.ben-manes.caffeine:caffeine:jar:sources": 1780037606, + "com.github.ben-manes.caffeine:guava": 1717885520, + "com.github.ben-manes.caffeine:guava:jar:sources": 1590068668, + "com.github.rholder:guava-retrying": -789983219, + "com.github.rholder:guava-retrying:jar:sources": 2030887181, + "com.google.auto.factory:auto-factory": 2025609377, + "com.google.auto.factory:auto-factory:jar:sources": -861333607, + "com.google.auto.service:auto-service-annotations": 195984873, + "com.google.auto.service:auto-service-annotations:jar:sources": -902556490, + "com.google.auto.value:auto-value": -1504415167, + "com.google.auto.value:auto-value-annotations": 641752776, + "com.google.auto.value:auto-value-annotations:jar:sources": -2083311588, + "com.google.auto.value:auto-value:jar:sources": -30292358, + "com.google.auto:auto-common": -195836273, + "com.google.auto:auto-common:jar:sources": -2100626169, + "com.google.code.findbugs:jsr305": -998441376, + "com.google.code.findbugs:jsr305:jar:sources": -640520676, + "com.google.code.gson:gson": -2014404431, + "com.google.code.gson:gson:jar:sources": 935710753, + "com.google.common.html.types:types": 1171780099, + "com.google.common.html.types:types:jar:sources": -1323749402, + "com.google.errorprone:error_prone_annotations": 804114225, + "com.google.errorprone:error_prone_annotations:jar:sources": -2115535816, + "com.google.flogger:flogger": -1346444653, + "com.google.flogger:flogger-log4j-backend": 661355127, + "com.google.flogger:flogger-log4j-backend:jar:sources": 1768481764, + "com.google.flogger:flogger-system-backend": 1919472289, + "com.google.flogger:flogger-system-backend:jar:sources": 2143617050, + "com.google.flogger:flogger:jar:sources": -1464119363, + "com.google.flogger:google-extensions": 1202868209, + "com.google.flogger:google-extensions:jar:sources": -1667103726, + "com.google.gitiles:blame-cache": 2017928431, + "com.google.gitiles:blame-cache:jar:sources": 467275313, + "com.google.gitiles:gitiles-servlet": 1918113734, + "com.google.gitiles:gitiles-servlet:jar:sources": -158011882, + "com.google.guava:failureaccess": 1715931538, + "com.google.guava:failureaccess:jar:sources": 1303858893, + "com.google.guava:guava": 555169272, + "com.google.guava:guava-testlib": 1085873063, + "com.google.guava:guava-testlib:jar:sources": 1351030420, + "com.google.guava:guava:jar:sources": 1246910673, + "com.google.guava:listenablefuture": 1079558157, + "com.google.inject.extensions:guice-assistedinject": 773600044, + "com.google.inject.extensions:guice-assistedinject:jar:sources": 550961491, + "com.google.inject.extensions:guice-servlet": 350832471, + "com.google.inject.extensions:guice-servlet:jar:sources": -828654042, + "com.google.inject:guice": 1105471728, + "com.google.inject:guice:jar:sources": -2060215316, + "com.google.j2objc:j2objc-annotations": 1702790440, + "com.google.j2objc:j2objc-annotations:jar:sources": -1254484583, + "com.google.jimfs:jimfs": 375012684, + "com.google.jimfs:jimfs:jar:sources": -555304721, + "com.google.jsinterop:jsinterop-annotations": 1636460091, + "com.google.jsinterop:jsinterop-annotations:jar:sources": 694679492, + "com.google.protobuf:protobuf-java": 1331070778, + "com.google.protobuf:protobuf-java:jar:sources": 133270104, + "com.google.template:soy": -677841031, + "com.google.template:soy:jar:sources": 895044971, + "com.google.truth.extensions:truth-java8-extension": 766384514, + "com.google.truth.extensions:truth-java8-extension:jar:sources": 1257445795, + "com.google.truth.extensions:truth-liteproto-extension": -1774954418, + "com.google.truth.extensions:truth-liteproto-extension:jar:sources": -754757455, + "com.google.truth.extensions:truth-proto-extension": -180673672, + "com.google.truth.extensions:truth-proto-extension:jar:sources": -171856482, + "com.google.truth:truth": 2133252626, + "com.google.truth:truth:jar:sources": 494258718, + "com.googlecode.javaewah:JavaEWAH": 1782327838, + "com.googlecode.javaewah:JavaEWAH:jar:sources": -77762967, + "com.googlecode.prolog-cafe:prolog-cafeteria": 1676859953, + "com.googlecode.prolog-cafe:prolog-cafeteria:jar:sources": -2075023034, + "com.googlecode.prolog-cafe:prolog-compiler": 796735585, + "com.googlecode.prolog-cafe:prolog-compiler:jar:sources": 2069727160, + "com.googlecode.prolog-cafe:prolog-io": 1604599374, + "com.googlecode.prolog-cafe:prolog-io:jar:sources": 2063038283, + "com.googlecode.prolog-cafe:prolog-runtime": -776672418, + "com.googlecode.prolog-cafe:prolog-runtime:jar:sources": 1445213194, + "com.h2database:h2": 1660949213, + "com.h2database:h2:jar:sources": 446387355, + "com.ibm.icu:icu4j": -1725310050, + "com.ibm.icu:icu4j:jar:sources": 163265109, + "com.icegreen:greenmail": 945965666, + "com.icegreen:greenmail:jar:sources": -245613146, + "com.jcraft:jsch": 1084501210, + "com.jcraft:jsch:jar:sources": -586408740, + "com.jcraft:jzlib": -1423321085, + "com.jcraft:jzlib:jar:sources": 1440827148, + "com.ryanharter.auto.value:auto-value-gson-extension": -1071085858, + "com.ryanharter.auto.value:auto-value-gson-extension:jar:sources": -670606081, + "com.ryanharter.auto.value:auto-value-gson-factory": -586162268, + "com.ryanharter.auto.value:auto-value-gson-factory:jar:sources": -543638406, + "com.ryanharter.auto.value:auto-value-gson-runtime": 1330169783, + "com.ryanharter.auto.value:auto-value-gson-runtime:jar:sources": 706425522, + "com.squareup:javapoet": -2135371934, + "com.squareup:javapoet:jar:sources": -54658965, + "com.sun.mail:javax.mail": -1925012432, + "com.sun.mail:javax.mail:jar:sources": 608124446, + "com.vladsch.flexmark:flexmark-all:jar:lib": 1454723856, + "commons-codec:commons-codec": -28659539, + "commons-codec:commons-codec:jar:sources": -499809306, + "commons-dbcp:commons-dbcp": 1878088686, + "commons-dbcp:commons-dbcp:jar:sources": 699520492, + "commons-io:commons-io": 1072619124, + "commons-io:commons-io:jar:sources": -238571367, + "commons-net:commons-net": 2103840351, + "commons-net:commons-net:jar:sources": -729248524, + "commons-pool:commons-pool": -212519749, + "commons-pool:commons-pool:jar:sources": 1134824914, + "commons-validator:commons-validator": 298719241, + "commons-validator:commons-validator:jar:sources": 1666065747, + "dk.brics:automaton": -509279352, + "dk.brics:automaton:jar:sources": 1035320481, + "eu.medsea.mimeutil:mime-util": -188093271, + "io.dropwizard.metrics:metrics-core": 740461012, + "io.dropwizard.metrics:metrics-core:jar:sources": -1988318320, + "io.github.java-diff-utils:java-diff-utils": -1180802694, + "io.github.java-diff-utils:java-diff-utils:jar:sources": 1551226070, + "io.sweers.autotransient:autotransient": 1808939417, + "io.sweers.autotransient:autotransient:jar:sources": -703216994, + "jakarta.inject:jakarta.inject-api": 188991469, + "jakarta.inject:jakarta.inject-api:jar:sources": -1584622703, + "jakarta.servlet:jakarta.servlet-api": -1421863120, + "jakarta.servlet:jakarta.servlet-api:jar:sources": -2084357823, + "javax.activation:activation": 1998212823, + "javax.activation:activation:jar:sources": 2062811499, + "javax.inject:javax.inject": 698155243, + "javax.inject:javax.inject:jar:sources": 1222576539, + "javax.servlet:javax.servlet-api": 735808080, + "javax.servlet:javax.servlet-api:jar:sources": -2015355058, + "junit:junit": -1256429642, + "junit:junit:jar:sources": 940567721, + "net.bytebuddy:byte-buddy": -645553046, + "net.bytebuddy:byte-buddy-agent": 567350281, + "net.bytebuddy:byte-buddy-agent:jar:sources": 467108684, + "net.bytebuddy:byte-buddy:jar:sources": -315779909, + "net.java.dev.jna:jna": 1622514527, + "net.java.dev.jna:jna-platform": 1756885266, + "net.java.dev.jna:jna-platform:jar:sources": -507721531, + "net.java.dev.jna:jna:jar:sources": -902150228, + "net.minidev:json-smart": 1099098947, + "net.minidev:json-smart:jar:sources": 1458315043, + "net.sf.jopt-simple:jopt-simple": -1677351973, + "net.sf.jopt-simple:jopt-simple:jar:sources": 1072431863, + "net.sourceforge.nekohtml:nekohtml": 1220479218, + "net.sourceforge.nekohtml:nekohtml:jar:sources": -689850578, + "org.antlr:ST4": 1197671269, + "org.antlr:ST4:jar:sources": 494740756, + "org.antlr:antlr": -1604354866, + "org.antlr:antlr-runtime": -1636816377, + "org.antlr:antlr-runtime:jar:sources": 290715958, + "org.antlr:antlr:jar:sources": -1910959184, + "org.antlr:stringtemplate": -1632674608, + "org.antlr:stringtemplate:jar:sources": -562160043, + "org.apache.commons:commons-compress": 2043911487, + "org.apache.commons:commons-compress:jar:sources": -1888643111, + "org.apache.commons:commons-lang3": -850748327, + "org.apache.commons:commons-lang3:jar:sources": 1059297009, + "org.apache.commons:commons-math3": -1383243934, + "org.apache.commons:commons-math3:jar:sources": -2132756896, + "org.apache.commons:commons-text": -549439287, + "org.apache.commons:commons-text:jar:sources": 400790036, + "org.apache.httpcomponents:fluent-hc": -1791063366, + "org.apache.httpcomponents:fluent-hc:jar:sources": -1265691559, + "org.apache.httpcomponents:httpclient": -930110091, + "org.apache.httpcomponents:httpclient:jar:sources": -779528763, + "org.apache.httpcomponents:httpcore": 67453319, + "org.apache.httpcomponents:httpcore:jar:sources": 2134002822, + "org.apache.james:apache-mime4j-core": 1172767236, + "org.apache.james:apache-mime4j-core:jar:sources": 1540423501, + "org.apache.james:apache-mime4j-dom": -704995319, + "org.apache.james:apache-mime4j-dom:jar:sources": -1311932247, + "org.apache.lucene:lucene-analysis-common": 966070693, + "org.apache.lucene:lucene-analysis-common:jar:sources": -1260454377, + "org.apache.lucene:lucene-backward-codecs": -1819747764, + "org.apache.lucene:lucene-backward-codecs:jar:sources": 2116900461, + "org.apache.lucene:lucene-core": -633809608, + "org.apache.lucene:lucene-core:jar:sources": 856971755, + "org.apache.lucene:lucene-misc": 882591687, + "org.apache.lucene:lucene-misc:jar:sources": 1204085693, + "org.apache.lucene:lucene-queryparser": 2111539382, + "org.apache.lucene:lucene-queryparser:jar:sources": 1992621418, + "org.apache.mina:mina-core": -584591076, + "org.apache.mina:mina-core:jar:sources": 71382131, + "org.apache.sshd:sshd-mina": 1147629185, + "org.apache.sshd:sshd-mina:jar:sources": -506380854, + "org.apache.sshd:sshd-osgi": 1092773821, + "org.apache.sshd:sshd-osgi:jar:sources": -2061003935, + "org.apache.sshd:sshd-sftp": -70173235, + "org.apache.sshd:sshd-sftp:jar:sources": 1034555480, + "org.asciidoctor:asciidoctorj": 1685789893, + "org.asciidoctor:asciidoctorj:jar:sources": 2091708864, + "org.assertj:assertj-core": -923964809, + "org.assertj:assertj-core:jar:sources": -697161745, + "org.bouncycastle:bcpg-jdk18on": -1258294405, + "org.bouncycastle:bcpg-jdk18on:jar:sources": -1733408473, + "org.bouncycastle:bcpkix-jdk18on": -294727450, + "org.bouncycastle:bcpkix-jdk18on:jar:sources": 1412420619, + "org.bouncycastle:bcprov-jdk18on": 1743462207, + "org.bouncycastle:bcprov-jdk18on:jar:sources": 1198104999, + "org.bouncycastle:bcutil-jdk18on": 968717615, + "org.bouncycastle:bcutil-jdk18on:jar:sources": -1508579276, + "org.checkerframework:checker-compat-qual": -1467964223, + "org.checkerframework:checker-compat-qual:jar:sources": 187825033, + "org.checkerframework:checker-qual": -739034920, + "org.checkerframework:checker-qual:jar:sources": 1223055344, + "org.commonmark:commonmark": 965353610, + "org.commonmark:commonmark-ext-autolink": -1259954266, + "org.commonmark:commonmark-ext-autolink:jar:sources": 2044805555, + "org.commonmark:commonmark-ext-gfm-strikethrough": -523313366, + "org.commonmark:commonmark-ext-gfm-strikethrough:jar:sources": 992870423, + "org.commonmark:commonmark-ext-gfm-tables": -1205584749, + "org.commonmark:commonmark-ext-gfm-tables:jar:sources": 1341057091, + "org.commonmark:commonmark:jar:sources": -1511261547, + "org.eclipse.jetty.ee10:jetty-ee10-servlet": -117453373, + "org.eclipse.jetty.ee10:jetty-ee10-servlet:jar:sources": 501419503, + "org.eclipse.jetty.ee8:jetty-ee8-nested": -1480285467, + "org.eclipse.jetty.ee8:jetty-ee8-nested:jar:sources": -2141029892, + "org.eclipse.jetty.ee8:jetty-ee8-security": -1089777429, + "org.eclipse.jetty.ee8:jetty-ee8-security:jar:sources": -374234244, + "org.eclipse.jetty.ee8:jetty-ee8-servlet": 709534403, + "org.eclipse.jetty.ee8:jetty-ee8-servlet:jar:sources": 1796299448, + "org.eclipse.jetty.toolchain:jetty-servlet-api": 1364182673, + "org.eclipse.jetty.toolchain:jetty-servlet-api:jar:sources": 736604807, + "org.eclipse.jetty:jetty-http": -1578822522, + "org.eclipse.jetty:jetty-http:jar:sources": -1317713476, + "org.eclipse.jetty:jetty-io": -225847225, + "org.eclipse.jetty:jetty-io:jar:sources": 116677681, + "org.eclipse.jetty:jetty-jmx": 1202016563, + "org.eclipse.jetty:jetty-jmx:jar:sources": -1552312519, + "org.eclipse.jetty:jetty-security": 501899561, + "org.eclipse.jetty:jetty-security:jar:sources": 1985223928, + "org.eclipse.jetty:jetty-server": -963731471, + "org.eclipse.jetty:jetty-server:jar:sources": -466994191, + "org.eclipse.jetty:jetty-session": 1869188771, + "org.eclipse.jetty:jetty-session:jar:sources": -1630590830, + "org.eclipse.jetty:jetty-util": -1027747345, + "org.eclipse.jetty:jetty-util-ajax": 573093314, + "org.eclipse.jetty:jetty-util-ajax:jar:sources": 455294676, + "org.eclipse.jetty:jetty-util:jar:sources": -280937557, + "org.hamcrest:hamcrest": 1282317766, + "org.hamcrest:hamcrest-core": 649657847, + "org.hamcrest:hamcrest-core:jar:sources": -1646511374, + "org.hamcrest:hamcrest:jar:sources": 1394599014, + "org.jruby:jruby-complete": -1216064857, + "org.jruby:jruby-complete:jar:sources": -308329410, + "org.jsoup:jsoup": 885756132, + "org.jsoup:jsoup:jar:sources": -2058254438, + "org.jspecify:jspecify": 117231129, + "org.jspecify:jspecify:jar:sources": -2134060298, + "org.mockito:mockito-core": -287830973, + "org.mockito:mockito-core:jar:sources": 1900207417, + "org.nibor.autolink:autolink": -443901116, + "org.nibor.autolink:autolink:jar:sources": -1863403724, + "org.objenesis:objenesis": 1536526812, + "org.objenesis:objenesis:jar:sources": 321662415, + "org.openid4java:openid4java": 1109904517, + "org.openid4java:openid4java:jar:sources": -1312563952, + "org.openjdk.jmh:jmh-core": 262505124, + "org.openjdk.jmh:jmh-core:jar:sources": 814651627, + "org.openjdk.jmh:jmh-generator-annprocess": 554503664, + "org.openjdk.jmh:jmh-generator-annprocess:jar:sources": -59690191, + "org.ow2.asm:asm": 1614653533, + "org.ow2.asm:asm-analysis": 865618371, + "org.ow2.asm:asm-analysis:jar:sources": 860015432, + "org.ow2.asm:asm-commons": 33716409, + "org.ow2.asm:asm-commons:jar:sources": 899206392, + "org.ow2.asm:asm-tree": -1173653421, + "org.ow2.asm:asm-tree:jar:sources": 590990274, + "org.ow2.asm:asm-util": 1667785410, + "org.ow2.asm:asm-util:jar:sources": 902318962, + "org.ow2.asm:asm:jar:sources": 340034775, + "org.roaringbitmap:RoaringBitmap": -1389417978, + "org.roaringbitmap:RoaringBitmap:jar:sources": -144719403, + "org.roaringbitmap:shims": -1535336248, + "org.roaringbitmap:shims:jar:sources": -422658237, + "org.slf4j:jcl-over-slf4j": 1113513525, + "org.slf4j:jcl-over-slf4j:jar:sources": 494484499, + "org.slf4j:slf4j-api": 500377983, + "org.slf4j:slf4j-api:jar:sources": 1751644771, + "org.slf4j:slf4j-ext": -1625091915, + "org.slf4j:slf4j-ext:jar:sources": 194861628, + "org.slf4j:slf4j-reload4j": -1779685465, + "org.slf4j:slf4j-reload4j:jar:sources": -1558574593, + "org.slf4j:slf4j-simple": -1055624668, + "org.slf4j:slf4j-simple:jar:sources": 751828941, + "org.tukaani:xz": -321288404, + "org.tukaani:xz:jar:sources": -893332662, + "xerces:xercesImpl": -1852819301, + "xerces:xercesImpl:jar:sources": -1057334748 }, "artifacts": { "antlr:antlr": { @@ -567,31 +567,31 @@ }, "com.google.auto.factory:auto-factory": { "shasums": { - "jar": "d59fb7ada5962a480abf0b81d4d2a14a2952f17c026732359af8b585e531c16c", - "sources": "c6098f8976b8833cf40edc36d1e3f7cd5cbb474c018185650b5ca4e24e713e6a" + "jar": "9b4505cd7a60574d59386672c1d51d6154b803c892677eb909cf4155ebee771f", + "sources": "4f46cc87784c2339d103c3b3d4e19bc03dd3ecca018ea931c61c4b63a4829616" }, - "version": "1.0.1" + "version": "1.1.0" }, "com.google.auto.service:auto-service-annotations": { "shasums": { - "jar": "c7bec54b7b5588b5967e870341091c5691181d954cf2039f1bf0a6eeb837473b", - "sources": "b013ca159b0fea3a0041d3d5fbb3b7e49a819da80a172a01fb17dd28fd98e72b" + "jar": "cb5667036f75e9682b493b8b47adbe391bcff72b9d3e16c52ae725f514665af2", + "sources": "10ca2b6c041ec8a7dc07d391ab792a55a4d6518d8067c8e63b7deda92e1de9e9" }, - "version": "1.0.1" + "version": "1.1.0" }, "com.google.auto.value:auto-value": { "shasums": { - "jar": "aaf8d637bfed3c420436b9facf1b7a88d12c8785374e4202382783005319c2c3", - "sources": "4bff06fe077d68f964bd5e05f020ed78fd7870730441e403a2eb306360c4890a" + "jar": "0c556370ca3d072ba7dec6b37751ccad0e4804b6f3390cc9acb800d821dd9c0a", + "sources": "a9eb12d149bfbcfc7fb670689d67dea3440b0ecaea15829e2953a03a1503b29c" }, - "version": "1.11.0" + "version": "1.11.1" }, "com.google.auto.value:auto-value-annotations": { "shasums": { - "jar": "5a055ce4255333b3346e1a8703da5bf8ff049532286fdcd31712d624abe111dd", - "sources": "d7941e5f19bb38afcfa85350d57e5245856c23c98c2bbe32f6d31b5577f2bc33" + "jar": "6c61a11420a5cddf3313888e9d335d5eedfbeb9a8da26591470fbfe61f5bf859", + "sources": "2bc2b5c2f8b1dbe3c05673750f16ec51db2ef9bf54be942c278e16035e5dca21" }, - "version": "1.11.0" + "version": "1.11.1" }, "com.google.auto:auto-common": { "shasums": { @@ -741,10 +741,10 @@ }, "com.google.protobuf:protobuf-java": { "shasums": { - "jar": "3ca892fd6ea8b37d01bb6917dbc0bf2637548b756753f65a28d4f1d4d982347f", - "sources": "ed30fe6a51c7c15a6f123448304c97185f2039f2aeca9d5e3b4f53de3a4c813c" + "jar": "400621a8f0aac52fab7ec88f492edd356f5a4355caa8f92028f217a8b5e4ac3b", + "sources": "63798a62a5f62575c814b92b1b951252bfd379ebe8207610db41622e03b007dc" }, - "version": "4.33.4" + "version": "4.35.0" }, "com.google.template:soy": { "shasums": { @@ -1012,31 +1012,31 @@ }, "net.bytebuddy:byte-buddy": { "shasums": { - "jar": "227d3e0ad51915809143f6a744ac0f4cb21f03214dab28e52ee7007c5ad7e9d9", - "sources": "8529b9ec93e698165dde6fddfd57e0820bb8f6d17f91ada1d8c525563636dc4d" + "jar": "8b31f4ea806afaa900b67bffd8498760d1f65464f4c2ea78cdbda2f3e633898b", + "sources": "d40bbc426b8e79a026b4e570e6f57b0a8659b122864c874cfa309121066f1fb1" }, - "version": "1.18.8" + "version": "1.18.10" }, "net.bytebuddy:byte-buddy-agent": { "shasums": { - "jar": "e303594d597de090abcb54580aa788478fc5e58f259e6ae914a1115f207c43af", - "sources": "881b5f123cefa2afc4ed89f8662da3ef9975e3a6e00f8f563b0470caf34b9511" + "jar": "9cfa3c71c8f07bfb5f4c3c68db7715940d792801eba82d346fc343f7bd7c2401", + "sources": "af62bcc1d124110ee1917f61d4cf3c19eb832f9213a555a1d375b743327345a8" }, - "version": "1.18.8" + "version": "1.18.10" }, "net.java.dev.jna:jna": { "shasums": { - "jar": "260c4b1e22b1db9e110ee441c4f13ce115f841fa48c41d78750986214b395557", - "sources": "0b9224e215b3c6a464959e3f994ddd64c14d46fb4014facd6afa1cc18e469466" + "jar": "4fb141dd8ef6b0585ffceea4bc49602fbc6312fa977e2c488794ea3e6aafecae", + "sources": "0136f719ddd91d92d2368aa2c59a0c436810ac0b9c29024044383758b0e99786" }, - "version": "5.18.1" + "version": "5.19.1" }, "net.java.dev.jna:jna-platform": { "shasums": { - "jar": "ad14c1b1ec4f43d396231219dfa635ebf828f738eac9f890ea1bc07795892d9a", - "sources": "5ffcac4b35114c6539ab9485592a90153ddeefb60e675dd9e8a2ee24e54ec1bc" + "jar": "3b3864f5b449e9c3c24b16861524b622b086563f44e0cd8384c8efc5a6052f82", + "sources": "8288e781424a34b0478928be5664b01ace168e80608178d37d02464041528940" }, - "version": "5.18.1" + "version": "5.19.1" }, "net.minidev:json-smart": { "shasums": { @@ -1187,31 +1187,31 @@ }, "org.apache.mina:mina-core": { "shasums": { - "jar": "39b2dfc8e84380bf7adab657d3d5e1625cb6592a885ebdb854ec5c6f7a3ec88d", - "sources": "6c7823b8ed5a8d3511b8fb7ba6166ab825a45784741cd15c9991a75e54ad0dba" + "jar": "09b4b5e416834e5281dd0dfccac1a10413d6f42c89f133b1c43641e34f33e840", + "sources": "ea8192924cc0fd86742be74097c53edbce68232e9b27099ced307912e53ef1ea" }, - "version": "2.2.4" + "version": "2.2.9" }, "org.apache.sshd:sshd-mina": { "shasums": { - "jar": "9ac956852adadcc5857e702bbc3accd0b4f22dc95b4d32a3c3e7d842478cabe6", - "sources": "763d0820dfc362c8ae6cfa5b10a49b349d855f60e5f792dad0a0497c13dc2f75" + "jar": "7399961346882ef50952ae9774e8dd1eac7ce232d2f82c058ade2c20c02591f1", + "sources": "cc526f8a54d375e87b21419e1c3ce7462637718c6e692fce3940cff98a5151a5" }, - "version": "2.17.1" + "version": "2.18.0" }, "org.apache.sshd:sshd-osgi": { "shasums": { - "jar": "77c7c19d86dd59c63cedc9fea79708df264ea779c30076ce5dcfc51dc941a78b", - "sources": "b51cb4bc79d4b63b5ba1f72bb8e71b24f85597f1d1a8de4f49b5adb452d3a6cb" + "jar": "e771c4239b1c4818c102368d57d0cc082c224eefb76224524473bf7021d1a32f", + "sources": "dd5838716293c8aeaf9487c50c6b313dff304e7ddca84ce16923f73865ba4d1d" }, - "version": "2.17.1" + "version": "2.18.0" }, "org.apache.sshd:sshd-sftp": { "shasums": { - "jar": "84727e3ac45458e1efde1058269964e2526aa3d520584870ba007adb20534b3c", - "sources": "6282c3628c62cfcec6578611b0c106f206fc418385b06b899cb8fb79aece5200" + "jar": "bf52fd81d63337cb40050c70ee460c80e78a0a4b77f15da7125eb1841f527e93", + "sources": "5171d9bac4ee76a013beccc1fdeab93fb43aeb84172d19b929ee158b9281c648" }, - "version": "2.17.1" + "version": "2.18.0" }, "org.asciidoctor:asciidoctorj": { "shasums": { @@ -1299,10 +1299,10 @@ }, "org.eclipse.jetty.ee10:jetty-ee10-servlet": { "shasums": { - "jar": "7275d4bde7b0e790929bfba30efb2c13cf4eb3624bf03014f4d123366a1168e7", - "sources": "f0d39ce34f5f32d9fb50b47a2f5022e2ac2ba0055626252df355a6fc1ab51104" + "jar": "05cccc74bbe1478ceb765e967b1cb1a3c8d9b51e99acf586190887e467ffd87d", + "sources": "51a734b4bc04235b24ccd759515dd2947e5b675c227c556f02262b765bce47eb" }, - "version": "12.1.9" + "version": "12.1.10" }, "org.eclipse.jetty.ee8:jetty-ee8-nested": { "shasums": { @@ -1582,8 +1582,7 @@ "com.google.auto.value:auto-value-annotations", "com.google.auto:auto-common", "com.google.guava:guava", - "com.squareup:javapoet", - "javax.inject:javax.inject" + "com.squareup:javapoet" ], "com.google.auto:auto-common": [ "com.google.guava:guava" @@ -2012,8 +2011,7 @@ "autovalue.shaded.com.google.j2objc.annotations", "autovalue.shaded.com.squareup.javapoet", "autovalue.shaded.net.ltgt.gradle.incap", - "autovalue.shaded.org.checkerframework.checker.nullness.qual", - "autovalue.shaded.org.checkerframework.framework.qual", + "autovalue.shaded.org.jspecify.annotations", "autovalue.shaded.org.objectweb.asm", "com.google.auto.value.extension", "com.google.auto.value.extension.memoized.processor", @@ -5226,7 +5224,7 @@ }, "org.apache.sshd:sshd-sftp": { "java.nio.file.spi.FileSystemProvider": [ - "org.apache.sshd.sftp.client.fs.SftpFileSystemProvider" + "org.apache.sshd.sftp.client.fs.SftpFileSystemProviderFacade" ], "org.apache.sshd.server.subsystem.SubsystemFactory": [ "org.apache.sshd.sftp.server.SftpSubsystemFactory" @@ -5234,7 +5232,7 @@ }, "org.apache.sshd:sshd-sftp:jar:sources": { "java.nio.file.spi.FileSystemProvider": [ - "org.apache.sshd.sftp.client.fs.SftpFileSystemProvider" + "org.apache.sshd.sftp.client.fs.SftpFileSystemProviderFacade" ], "org.apache.sshd.server.subsystem.SubsystemFactory": [ "org.apache.sshd.sftp.server.SftpSubsystemFactory"
diff --git a/java/com/google/gerrit/acceptance/ChangeIndexedCounter.java b/java/com/google/gerrit/acceptance/ChangeIndexedCounter.java index e4caa1a..f9698dc 100644 --- a/java/com/google/gerrit/acceptance/ChangeIndexedCounter.java +++ b/java/com/google/gerrit/acceptance/ChangeIndexedCounter.java
@@ -30,7 +30,7 @@ } @Override - public void onChangeDeleted(int id) { + public void onChangeDeleted(String projectName, int id) { countsByChange.incrementAndGet(id); deletionsByChange.incrementAndGet(id); }
diff --git a/java/com/google/gerrit/entities/Comment.java b/java/com/google/gerrit/entities/Comment.java index 288eb2c..3a331c3 100644 --- a/java/com/google/gerrit/entities/Comment.java +++ b/java/com/google/gerrit/entities/Comment.java
@@ -230,6 +230,7 @@ public String parentUuid; public Range range; public String tag; + public Boolean isAi; @Nullable public List<FixSuggestion> fixSuggestions; @@ -258,6 +259,7 @@ c.realAuthor == null ? null : c.realAuthor.getId()); this.lineNbr = c.lineNbr; this.range = c.range != null ? new Range(c.range) : null; + this.isAi = c.isAi; } public Comment( @@ -379,7 +381,8 @@ && Objects.equals(tag, c.tag) && Objects.equals(revId, c.revId) && Objects.equals(serverId, c.serverId) - && Objects.equals(fixSuggestions, c.fixSuggestions); + && Objects.equals(fixSuggestions, c.fixSuggestions) + && Objects.equals(isAi, c.isAi); } @Override @@ -397,7 +400,8 @@ tag, revId, serverId, - fixSuggestions); + fixSuggestions, + isAi); } @Override @@ -418,6 +422,7 @@ .add("range", Objects.toString(range, "")) .add("revId", Objects.toString(revId, "")) .add("tag", Objects.toString(tag, "")) + .add("isAi", Objects.toString(isAi, "")) .add("fixSuggestions", Objects.toString(fixSuggestions, "")); } }
diff --git a/java/com/google/gerrit/entities/Permission.java b/java/com/google/gerrit/entities/Permission.java index 885edc6..c212f0c 100644 --- a/java/com/google/gerrit/entities/Permission.java +++ b/java/com/google/gerrit/entities/Permission.java
@@ -60,6 +60,7 @@ public static final String SUBMIT = "submit"; public static final String SUBMIT_AS = "submitAs"; public static final String TOGGLE_WORK_IN_PROGRESS_STATE = "toggleWipState"; + public static final String POST_REVIEW_COMMENT = "postReviewComment"; public static final String VIEW_PRIVATE_CHANGES = "viewPrivateChanges"; public static final String AI_REVIEW = "aiReview"; @@ -99,6 +100,7 @@ NAMES_LC.add(SUBMIT.toLowerCase(Locale.US)); NAMES_LC.add(SUBMIT_AS.toLowerCase(Locale.US)); NAMES_LC.add(TOGGLE_WORK_IN_PROGRESS_STATE.toLowerCase(Locale.US)); + NAMES_LC.add(POST_REVIEW_COMMENT.toLowerCase(Locale.US)); NAMES_LC.add(VIEW_PRIVATE_CHANGES.toLowerCase(Locale.US)); NAMES_LC.add(AI_REVIEW.toLowerCase(Locale.US));
diff --git a/java/com/google/gerrit/entities/SubmitRequirementExpressionResult.java b/java/com/google/gerrit/entities/SubmitRequirementExpressionResult.java index 8bf50f8..111b4fb 100644 --- a/java/com/google/gerrit/entities/SubmitRequirementExpressionResult.java +++ b/java/com/google/gerrit/entities/SubmitRequirementExpressionResult.java
@@ -159,6 +159,9 @@ ERROR, /** Submit requirement expression was not evaluated. */ - NOT_EVALUATED + NOT_EVALUATED, + + /** Submit requirement expression was timeout as maintained in gerrit.config. */ + TIMEOUT } }
diff --git a/java/com/google/gerrit/entities/SubmitRequirementResult.java b/java/com/google/gerrit/entities/SubmitRequirementResult.java index d9bb162..a0928cd 100644 --- a/java/com/google/gerrit/entities/SubmitRequirementResult.java +++ b/java/com/google/gerrit/entities/SubmitRequirementResult.java
@@ -108,6 +108,11 @@ @Memoized public Status status() { + if (isTimeout(submittabilityExpressionResult()) + || isTimeout(applicabilityExpressionResult()) + || isTimeout(overrideExpressionResult())) { + return Status.TIMEOUT; + } if (forced().orElse(false)) { return Status.FORCED; } else if (assertError(submittabilityExpressionResult()) @@ -125,6 +130,11 @@ } } + private static boolean isTimeout(Optional<SubmitRequirementExpressionResult> r) { + return r.filter(v -> v.status() == SubmitRequirementExpressionResult.Status.TIMEOUT) + .isPresent(); + } + /** Returns true if the submit requirement is fulfilled and can allow change submission. */ @Memoized public boolean fulfilled() { @@ -177,7 +187,10 @@ * The "submit requirement" was bypassed during submission, e.g. by pushing for review with the * %submit option. */ - FORCED + FORCED, + + /** The submit requirement is TIMEOUT during evaluation. */ + TIMEOUT } @AutoValue.Builder
diff --git a/java/com/google/gerrit/entities/converter/HumanCommentProtoConverter.java b/java/com/google/gerrit/entities/converter/HumanCommentProtoConverter.java index 7cb78fa..c2d0d77 100644 --- a/java/com/google/gerrit/entities/converter/HumanCommentProtoConverter.java +++ b/java/com/google/gerrit/entities/converter/HumanCommentProtoConverter.java
@@ -61,6 +61,9 @@ .setUnresolved(val.unresolved) .setWrittenOnMillis(val.writtenOn.toInstant().toEpochMilli()) .setServerId(val.serverId); + if (val.isAi != null) { + res.setIsAi(val.isAi); + } if (!val.key.filename.equals(PATCHSET_LEVEL)) { InFilePosition.Builder inFilePos = InFilePosition.newBuilder() @@ -151,6 +154,9 @@ // set it even if it's the same as the `author`. res.realAuthor = new Comment.Identity(accountIdConverter.fromProto(proto.getRealAuthor())); } + if (proto.hasIsAi()) { + res.isAi = proto.getIsAi(); + } optInFilePosition.ifPresent( inFilePosition -> {
diff --git a/java/com/google/gerrit/extensions/api/projects/CommitApi.java b/java/com/google/gerrit/extensions/api/projects/CommitApi.java index 18ba0c0..16e8228 100644 --- a/java/com/google/gerrit/extensions/api/projects/CommitApi.java +++ b/java/com/google/gerrit/extensions/api/projects/CommitApi.java
@@ -31,4 +31,14 @@ /** List files in a specific commit against the parent commit. */ Map<String, FileInfo> files(int parentNum) throws RestApiException; + + /** + * Lists files that differ between this commit and a base commit. + * + * @param base the base commit SHA1 (40 characters) + * @param nameOnly whether to return only the list of files without diff info + * @return map of file paths to FileInfo + * @throws RestApiException if commits are not in ancestor/descendant relationship or not visible + */ + Map<String, FileInfo> diffFiles(String base, boolean nameOnly) throws RestApiException; }
diff --git a/java/com/google/gerrit/extensions/api/projects/ProjectApi.java b/java/com/google/gerrit/extensions/api/projects/ProjectApi.java index f96755a..a8407d1 100644 --- a/java/com/google/gerrit/extensions/api/projects/ProjectApi.java +++ b/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -22,6 +22,8 @@ import com.google.gerrit.extensions.common.BatchLabelInput; import com.google.gerrit.extensions.common.BatchSubmitRequirementInput; import com.google.gerrit.extensions.common.ChangeInfo; +import com.google.gerrit.extensions.common.DiffInfo; +import com.google.gerrit.extensions.common.FileInfo; import com.google.gerrit.extensions.common.LabelDefinitionInfo; import com.google.gerrit.extensions.common.ListTagSortOption; import com.google.gerrit.extensions.common.ProjectInfo; @@ -66,6 +68,29 @@ Map<String, Set<String>> commitsIn(Collection<String> commits, Collection<String> refs) throws RestApiException; + /** + * Lists files that differ between two commits. + * + * @param oldCommit the old commit SHA1 (40 characters) + * @param newCommit the new commit SHA1 (40 characters) + * @param nameOnly whether to return only the list of files without diff info + * @return map of file paths to FileInfo + * @throws RestApiException if commits are not in ancestor/descendant relationship or not visible + */ + Map<String, FileInfo> diffFiles(String oldCommit, String newCommit, boolean nameOnly) + throws RestApiException; + + /** + * Gets the diff for a specific file between two commits. + * + * @param oldCommit the old commit SHA1 (40 characters) + * @param newCommit the new commit SHA1 (40 characters) + * @param path the file path + * @return DiffInfo for the file + * @throws RestApiException if commits are not in ancestor/descendant relationship or not visible + */ + DiffInfo diffFile(String oldCommit, String newCommit, String path) throws RestApiException; + ListRefsRequest<BranchInfo> branches(); ListRefsRequest<TagInfo> tags();
diff --git a/java/com/google/gerrit/extensions/auth/oauth/OAuthAuthorizationInfo.java b/java/com/google/gerrit/extensions/auth/oauth/OAuthAuthorizationInfo.java new file mode 100644 index 0000000..6c7d14b --- /dev/null +++ b/java/com/google/gerrit/extensions/auth/oauth/OAuthAuthorizationInfo.java
@@ -0,0 +1,36 @@ +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.extensions.auth.oauth; + +import com.google.gerrit.common.Nullable; + +public class OAuthAuthorizationInfo { + private final String authorizationUrl; + private final String pkceVerifier; + + public OAuthAuthorizationInfo(String url, @Nullable String verifier) { + this.authorizationUrl = url; + this.pkceVerifier = verifier; + } + + public String getAuthorizationUrl() { + return authorizationUrl; + } + + @Nullable + public String getPkceVerifier() { + return pkceVerifier; + } +}
diff --git a/java/com/google/gerrit/extensions/auth/oauth/OAuthServiceProvider.java b/java/com/google/gerrit/extensions/auth/oauth/OAuthServiceProvider.java index c3d760b..0333793 100644 --- a/java/com/google/gerrit/extensions/auth/oauth/OAuthServiceProvider.java +++ b/java/com/google/gerrit/extensions/auth/oauth/OAuthServiceProvider.java
@@ -14,6 +14,7 @@ package com.google.gerrit.extensions.auth.oauth; +import com.google.gerrit.common.Nullable; import com.google.gerrit.extensions.annotations.ExtensionPoint; import java.io.IOException; @@ -26,7 +27,19 @@ * * @return the OAuth service URL to redirect your users for authentication */ - String getAuthorizationUrl(); + default String getAuthorizationUrl() { + return getAuthorizationInfo().getAuthorizationUrl(); + } + + /** + * Returns the URL where you should redirect your users to authenticate your application. + * + * @return the OAuth service URL to redirect your users for authentication and verifier string + * generated during initial authorization phase + */ + default OAuthAuthorizationInfo getAuthorizationInfo() { + return new OAuthAuthorizationInfo(getAuthorizationUrl(), null); + } /** * Retrieve the access token @@ -34,11 +47,24 @@ * @param verifier verifier code * @return access token */ - OAuthToken getAccessToken(OAuthVerifier verifier); + default OAuthToken getAccessToken(OAuthVerifier verifier) { + return getAccessToken(verifier, null); + } /** - * After establishing of secure communication channel, this method supossed to access the - * protected resoure and retrieve the username. + * Retrieve the access token + * + * @param verifier verifier code + * @param codeVerifier verifier string generated during initial authorization phase + * @return access token + */ + default OAuthToken getAccessToken(OAuthVerifier verifier, @Nullable String codeVerifier) { + return getAccessToken(verifier); + } + + /** + * After establishing of secure communication channel, this method supposed to access the + * protected resource and retrieve the user name. * * @return OAuth user information */
diff --git a/java/com/google/gerrit/extensions/client/Comment.java b/java/com/google/gerrit/extensions/client/Comment.java index 4bfa566..3f33713 100644 --- a/java/com/google/gerrit/extensions/client/Comment.java +++ b/java/com/google/gerrit/extensions/client/Comment.java
@@ -54,6 +54,8 @@ public List<FixSuggestionInfo> fixSuggestions; + public Boolean isAi; + // TODO(issue-40014498): Migrate timestamp fields in *Info/*Input classes from type Timestamp to // Instant @SuppressWarnings("JdkObsolete") @@ -157,7 +159,8 @@ && Objects.equals(updated, c.updated) && Objects.equals(message, c.message) && Objects.equals(commitId, c.commitId) - && Objects.equals(fixSuggestions, c.fixSuggestions); + && Objects.equals(fixSuggestions, c.fixSuggestions) + && Objects.equals(isAi, c.isAi); } return false; } @@ -165,6 +168,17 @@ @Override public int hashCode() { return Objects.hash( - patchSet, id, path, side, parent, line, range, inReplyTo, updated, message, fixSuggestions); + patchSet, + id, + path, + side, + parent, + line, + range, + inReplyTo, + updated, + message, + fixSuggestions, + isAi); } }
diff --git a/java/com/google/gerrit/extensions/client/DiffPreferencesInfo.java b/java/com/google/gerrit/extensions/client/DiffPreferencesInfo.java index 8de9826..c6f0dc3 100644 --- a/java/com/google/gerrit/extensions/client/DiffPreferencesInfo.java +++ b/java/com/google/gerrit/extensions/client/DiffPreferencesInfo.java
@@ -42,6 +42,12 @@ IGNORE_ALL } + public enum ResponsiveMode { + NONE, + SHRINK_ONLY, + FULL_RESPONSIVE + } + public Integer context; public Integer tabSize; public Integer fontSize; @@ -60,7 +66,8 @@ public Boolean renderEntireFile; public Boolean hideEmptyPane; public Boolean matchBrackets; - public Boolean lineWrapping; + @Deprecated public Boolean lineWrapping; + public ResponsiveMode responsiveMode; public Whitespace ignoreWhitespace; public Boolean retainHeader; public Boolean skipDeleted; @@ -93,6 +100,7 @@ && equalBooleanPreferencesFields(this.hideEmptyPane, other.hideEmptyPane) && equalBooleanPreferencesFields(this.matchBrackets, other.matchBrackets) && equalBooleanPreferencesFields(this.lineWrapping, other.lineWrapping) + && Objects.equals(this.responsiveMode, other.responsiveMode) && Objects.equals(this.ignoreWhitespace, other.ignoreWhitespace) && equalBooleanPreferencesFields(this.retainHeader, other.retainHeader) && equalBooleanPreferencesFields(this.skipDeleted, other.skipDeleted) @@ -122,6 +130,7 @@ hideEmptyPane, matchBrackets, lineWrapping, + responsiveMode, ignoreWhitespace, retainHeader, skipDeleted, @@ -151,6 +160,7 @@ .add("hideEmptyPane", hideEmptyPane) .add("matchBrackets", matchBrackets) .add("lineWrapping", lineWrapping) + .add("responsiveMode", responsiveMode) .add("ignoreWhitespace", ignoreWhitespace) .add("retainHeader", retainHeader) .add("skipDeleted", skipDeleted) @@ -180,6 +190,7 @@ i.hideEmptyPane = false; i.matchBrackets = false; i.lineWrapping = false; + i.responsiveMode = ResponsiveMode.NONE; i.ignoreWhitespace = Whitespace.IGNORE_NONE; i.retainHeader = false; i.skipDeleted = false;
diff --git a/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java b/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java index b93f752..9f5202d 100644 --- a/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java +++ b/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
@@ -150,6 +150,7 @@ public Boolean allowSuggestCodeWhileCommenting; public Boolean allowAutocompletingComments; public String aiChatSelectedModel; + public String labelFilter; /** * The sidebar section that the user prefers to have open on the diff page, or "NONE" if all @@ -232,7 +233,8 @@ && equalBooleanPreferencesFields( this.allowAutocompletingComments, other.allowAutocompletingComments) && Objects.equals(this.diffPageSidebar, other.diffPageSidebar) - && Objects.equals(this.aiChatSelectedModel, other.aiChatSelectedModel); + && Objects.equals(this.aiChatSelectedModel, other.aiChatSelectedModel) + && Objects.equals(this.labelFilter, other.labelFilter); } @Override @@ -263,7 +265,8 @@ allowSuggestCodeWhileCommenting, allowAutocompletingComments, diffPageSidebar, - aiChatSelectedModel); + aiChatSelectedModel, + labelFilter); } @Override @@ -295,6 +298,7 @@ .add("allowAutocompletingComments", allowAutocompletingComments) .add("diffPageSidebar", diffPageSidebar) .add("aiChatSelectedModel", aiChatSelectedModel) + .add("labelFilter", labelFilter) .toString(); } @@ -324,6 +328,7 @@ p.allowAutocompletingComments = true; p.diffPageSidebar = "NONE"; p.aiChatSelectedModel = null; + p.labelFilter = null; return p; } }
diff --git a/java/com/google/gerrit/extensions/common/FileInfo.java b/java/com/google/gerrit/extensions/common/FileInfo.java index 29a6d4f..db85ac8 100644 --- a/java/com/google/gerrit/extensions/common/FileInfo.java +++ b/java/com/google/gerrit/extensions/common/FileInfo.java
@@ -29,6 +29,12 @@ public long sizeDelta; public long size; + /** + * Returns {@code true} if the diff computation was not able to compute a diff, i.e. for diffs + * taking a very long time to compute. + */ + public Boolean diffsTooExpensiveToCompute; + @Override public boolean equals(Object o) { if (o instanceof FileInfo) { @@ -43,14 +49,23 @@ && Objects.equals(linesInserted, fileInfo.linesInserted) && Objects.equals(linesDeleted, fileInfo.linesDeleted) && sizeDelta == fileInfo.sizeDelta - && size == fileInfo.size; + && size == fileInfo.size + && Objects.equals(diffsTooExpensiveToCompute, fileInfo.diffsTooExpensiveToCompute); } return false; } @Override public int hashCode() { - return Objects.hash(status, binary, oldPath, linesInserted, linesDeleted, sizeDelta, size); + return Objects.hash( + status, + binary, + oldPath, + linesInserted, + linesDeleted, + sizeDelta, + size, + diffsTooExpensiveToCompute); } @Override @@ -78,6 +93,8 @@ + sizeDelta + ", size=" + size + + ", diffsTooExpensiveToCompute=" + + diffsTooExpensiveToCompute + "}"; } }
diff --git a/java/com/google/gerrit/extensions/common/SubmitRequirementExpressionInfo.java b/java/com/google/gerrit/extensions/common/SubmitRequirementExpressionInfo.java index 5ae8500..20ea4be 100644 --- a/java/com/google/gerrit/extensions/common/SubmitRequirementExpressionInfo.java +++ b/java/com/google/gerrit/extensions/common/SubmitRequirementExpressionInfo.java
@@ -76,7 +76,10 @@ ERROR, /** Expression was not evaluated. */ - NOT_EVALUATED + NOT_EVALUATED, + + /** Submit requirement expression was timeout as maintained in gerrit.config. */ + TIMEOUT } @Override
diff --git a/java/com/google/gerrit/extensions/common/SubmitRequirementResultInfo.java b/java/com/google/gerrit/extensions/common/SubmitRequirementResultInfo.java index cf0d53c..07ab976 100644 --- a/java/com/google/gerrit/extensions/common/SubmitRequirementResultInfo.java +++ b/java/com/google/gerrit/extensions/common/SubmitRequirementResultInfo.java
@@ -50,7 +50,10 @@ * The "submit requirement" was bypassed during submission, e.g. by pushing for review with the * %submit option. */ - FORCED + FORCED, + + /** The submit requirement is TIMEOUT during evaluation. */ + TIMEOUT } /** Submit requirement name. */
diff --git a/java/com/google/gerrit/extensions/events/ChangeIndexedListener.java b/java/com/google/gerrit/extensions/events/ChangeIndexedListener.java index 82d0a53..64b62a6 100644 --- a/java/com/google/gerrit/extensions/events/ChangeIndexedListener.java +++ b/java/com/google/gerrit/extensions/events/ChangeIndexedListener.java
@@ -43,7 +43,7 @@ void onChangeIndexed(String projectName, int id); /** Invoked when a change is deleted from the index. */ - void onChangeDeleted(int id); + void onChangeDeleted(String projectName, int id); /** * Invoked when all change indexes are deleted for a given project
diff --git a/java/com/google/gerrit/extensions/restapi/Response.java b/java/com/google/gerrit/extensions/restapi/Response.java index cc0c134..851ec96 100644 --- a/java/com/google/gerrit/extensions/restapi/Response.java +++ b/java/com/google/gerrit/extensions/restapi/Response.java
@@ -15,7 +15,6 @@ package com.google.gerrit.extensions.restapi; import static com.google.common.base.Preconditions.checkState; -import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; import com.google.common.collect.ImmutableMultimap; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -23,6 +22,10 @@ /** Special return value to mean specific HTTP status codes in a REST API. */ public abstract class Response<T> { + // Intentionally keep this constant local to avoid introducing an HTTP library + // dependency into the public gerrit-extension-api. + private static final int HTTP_INTERNAL_SERVER_ERROR = 500; + @SuppressWarnings({"rawtypes"}) private static final Response NONE = new None(); @@ -74,7 +77,7 @@ /** Arbitrary status code with wrapped result. */ public static <T> Response<T> withStatusCode(int statusCode, T value) { checkState( - statusCode < SC_INTERNAL_SERVER_ERROR, + statusCode < HTTP_INTERNAL_SERVER_ERROR, "Status code must be < 500. To return an internal server error REST endpoint" + " implementations should throw an exception"); return new Impl<>(statusCode, value);
diff --git a/java/com/google/gerrit/httpd/RemoteUserUtil.java b/java/com/google/gerrit/httpd/RemoteUserUtil.java index 8856f91..3d3e54b 100644 --- a/java/com/google/gerrit/httpd/RemoteUserUtil.java +++ b/java/com/google/gerrit/httpd/RemoteUserUtil.java
@@ -23,13 +23,13 @@ import com.google.common.io.BaseEncoding; import com.google.gerrit.common.Nullable; import com.google.gerrit.server.config.AuthConfig; +import com.google.inject.Inject; import com.google.inject.ProvisionException; +import com.google.inject.Singleton; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import java.util.stream.Collectors; -import javax.inject.Inject; -import javax.inject.Singleton; import javax.servlet.http.HttpServletRequest; import org.apache.commons.net.util.SubnetUtils;
diff --git a/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java b/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java index 297505a..58c8bad1 100644 --- a/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java +++ b/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
@@ -22,6 +22,7 @@ import com.google.common.io.BaseEncoding; import com.google.gerrit.auth.oauth.OAuthTokenCache; import com.google.gerrit.entities.Account; +import com.google.gerrit.extensions.auth.oauth.OAuthAuthorizationInfo; import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider; import com.google.gerrit.extensions.auth.oauth.OAuthToken; import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo; @@ -60,13 +61,14 @@ private final AccountManager accountManager; private final CanonicalWebUrl urlProvider; private final OAuthTokenCache tokenCache; + private final ExternalIdKeyFactory externalIdKeyFactory; + private final AuthRequest.Factory authRequestFactory; private OAuthServiceProvider serviceProvider; private OAuthUserInfo user; private Account.Id accountId; private String redirectToken; private boolean linkMode; - private final ExternalIdKeyFactory externalIdKeyFactory; - private final AuthRequest.Factory authRequestFactory; + private String pkceVerifier; @Inject OAuthSession( @@ -107,7 +109,8 @@ } logger.atFine().log("Login-Retrieve-User %s", this); - OAuthToken token = oauth.getAccessToken(new OAuthVerifier(request.getParameter("code"))); + OAuthToken token = + oauth.getAccessToken(new OAuthVerifier(request.getParameter("code")), this.pkceVerifier); user = oauth.getUserInfo(token); if (isLoggedIn()) { @@ -126,7 +129,12 @@ // we cannot use LoginUrlToken.getToken() method, // because it relies on getPathInfo() and it is always null here. redirectToken = redirectToken.substring(request.getContextPath().length()); - response.sendRedirect(oauth.getAuthorizationUrl() + "&state=" + state); + + OAuthAuthorizationInfo authInfo = oauth.getAuthorizationInfo(); + // capture the verifier in the session + this.pkceVerifier = authInfo.getPkceVerifier(); + + response.sendRedirect(authInfo.getAuthorizationUrl() + "&state=" + state); return false; } @@ -228,6 +236,7 @@ user = null; redirectToken = null; serviceProvider = null; + pkceVerifier = null; } private boolean checkState(ServletRequest request) {
diff --git a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java index 0671172..f7eb673 100644 --- a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java +++ b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
@@ -237,9 +237,6 @@ "manifestPath", urlInScriptTagOrdainer.apply(Strings.nullToEmpty(canonicalPath) + "/manifest.webmanifest")); - if (urlParameterMap.containsKey("ce")) { - data.put("polyfillCE", "true"); - } if (urlParameterMap.containsKey("gf")) { data.put("useGoogleFonts", "true"); }
diff --git a/java/com/google/gerrit/launcher/GerritLauncher.java b/java/com/google/gerrit/launcher/GerritLauncher.java index 53f4af9..79205ed 100644 --- a/java/com/google/gerrit/launcher/GerritLauncher.java +++ b/java/com/google/gerrit/launcher/GerritLauncher.java
@@ -325,6 +325,7 @@ List<URL> extapi = new ArrayList<>(); move(jars, "gerrit-extension-api-", extapi); move(jars, "guice-", extapi); + move(jars, "jakarta.inject-api-2.0.1.jar", extapi); move(jars, "javax.inject-1.jar", extapi); move(jars, "aopalliance-1.0.jar", extapi); move(jars, "guice-servlet-", extapi); @@ -728,9 +729,9 @@ throw new FileNotFoundException("Cannot extract path from " + u); } - // Pop up to the top-level source folder by looking for WORKSPACE. + // Pop up to the top-level source folder by looking for MODULE.bazel. dir = Path.of(u.getPath()); - while (!Files.isRegularFile(dir.resolve("WORKSPACE"))) { + while (!Files.isRegularFile(dir.resolve("MODULE.bazel"))) { Path parent = dir.getParent(); if (parent == null) { throw new FileNotFoundException("Cannot find source root from " + u);
diff --git a/java/com/google/gerrit/server/BUILD b/java/com/google/gerrit/server/BUILD index edede84..756000b 100644 --- a/java/com/google/gerrit/server/BUILD +++ b/java/com/google/gerrit/server/BUILD
@@ -35,6 +35,9 @@ exclude = CONSTANTS_SRC + GERRIT_GLOBAL_MODULE_SRC + TESTING_SRC + PROLOG_SRC, ), + # Pin AutoFactory to the javax inject API. See the "Bump auto-factory + # version to 1.1.0" change for why; the follow-up removes this pin. + javacopts = ["-Acom.google.auto.factory.InjectApi=javax"], resource_strip_prefix = "resources", resources = ["//resources/com/google/gerrit/server"], visibility = ["//visibility:public"],
diff --git a/java/com/google/gerrit/server/account/Accounts.java b/java/com/google/gerrit/server/account/Accounts.java index 55192e9..ec781e0 100644 --- a/java/com/google/gerrit/server/account/Accounts.java +++ b/java/com/google/gerrit/server/account/Accounts.java
@@ -14,11 +14,17 @@ package com.google.gerrit.server.account; +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.google.common.collect.ImmutableList; import com.google.gerrit.entities.Account; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.Random; import java.util.Set; import org.eclipse.jgit.errors.ConfigInvalidException; @@ -62,6 +68,19 @@ List<Account.Id> firstNIds(int n) throws IOException; /** + * Returns n random account IDs. + * + * @param n the number of account IDs that should be returned + * @param seed seed that should be used to randomize the order + * @return n random account IDs + */ + default ImmutableList<Account.Id> randomNIds(int n, long seed) throws IOException { + List<Account.Id> allIds = new ArrayList<>(allIds()); + Collections.shuffle(allIds, new Random(seed)); + return allIds.stream().limit(n).collect(toImmutableList()); + } + + /** * Checks if any account exists. * * @return {@code true} if at least one account exists, otherwise {@code false}.
diff --git a/java/com/google/gerrit/server/account/AuthTokenExpiryNotifier.java b/java/com/google/gerrit/server/account/AuthTokenExpiryNotifier.java index 99b2da4..986b7d0 100644 --- a/java/com/google/gerrit/server/account/AuthTokenExpiryNotifier.java +++ b/java/com/google/gerrit/server/account/AuthTokenExpiryNotifier.java
@@ -20,7 +20,6 @@ import com.google.gerrit.exceptions.EmailException; import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.lifecycle.LifecycleModule; -import com.google.gerrit.server.account.storage.notedb.AccountsNoteDbImpl; import com.google.gerrit.server.config.ScheduleConfig; import com.google.gerrit.server.config.ScheduleConfig.Schedule; import com.google.gerrit.server.git.WorkQueue; @@ -40,7 +39,7 @@ private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private static final long FIRST_NOTIFICATION_BEFORE_EXPIRY = 7L; // 7 days - private final AccountsNoteDbImpl accounts; + private final Accounts accounts; private final AuthTokenAccessor tokenAccessor; private final EmailFactories emailFactories; @@ -81,7 +80,7 @@ @Inject public AuthTokenExpiryNotifier( - AccountsNoteDbImpl accounts, AuthTokenAccessor tokenAccessor, EmailFactories emailFactories) { + Accounts accounts, AuthTokenAccessor tokenAccessor, EmailFactories emailFactories) { this.accounts = accounts; this.tokenAccessor = tokenAccessor; this.emailFactories = emailFactories; @@ -102,18 +101,22 @@ now.plus(FIRST_NOTIFICATION_BEFORE_EXPIRY - 1, ChronoUnit.DAYS))) { logger.atInfo().log( "Token %s for account %s is expiring soon.", token.id(), account.account().id()); - emailFactories - .createOutgoingEmail( - AUTH_TOKEN_WILL_EXPIRE, - emailFactories.createAuthTokenWillExpireEmail(account.account(), token)) - .send(); + try { + emailFactories + .createOutgoingEmail( + AUTH_TOKEN_WILL_EXPIRE, + emailFactories.createAuthTokenWillExpireEmail(account.account(), token)) + .send(); + } catch (EmailException e) { + logger.atSevere().withCause(e).log( + "Failed to send token expiry notification email for token %s of account %s", + token.id(), account.account().id()); + } } } } } catch (IOException | ConfigInvalidException e) { throw new RuntimeException("Failed to read accounts from NoteDB", e); - } catch (EmailException e) { - logger.atSevere().withCause(e).log("Failed to send token expiry notification email"); } } }
diff --git a/java/com/google/gerrit/server/account/MaxAuthTokenLifetimeApplier.java b/java/com/google/gerrit/server/account/MaxAuthTokenLifetimeApplier.java index 4c813d1..eef35cf 100644 --- a/java/com/google/gerrit/server/account/MaxAuthTokenLifetimeApplier.java +++ b/java/com/google/gerrit/server/account/MaxAuthTokenLifetimeApplier.java
@@ -18,7 +18,6 @@ import com.google.common.base.Stopwatch; import com.google.common.flogger.FluentLogger; import com.google.gerrit.entities.Account; -import com.google.gerrit.server.account.storage.notedb.AccountsNoteDbImpl; import com.google.gerrit.server.git.MultiProgressMonitor; import com.google.gerrit.server.git.MultiProgressMonitor.Task; import com.google.gerrit.server.git.MultiProgressMonitor.TaskKind; @@ -44,7 +43,7 @@ private final MultiProgressMonitor.Factory multiProgressMonitorFactory; private final AuthTokenAccessor tokenAccessor; private final LockManager lockManager; - private final AccountsNoteDbImpl accounts; + private final Accounts accounts; private final Instant expiryInstant; private MultiProgressMonitor mpm; @@ -60,7 +59,7 @@ MultiProgressMonitor.Factory multiProgressMonitorFactory, AuthTokenAccessor tokenAccessor, LockManager lockManager, - AccountsNoteDbImpl accounts, + Accounts accounts, @Assisted Instant expiryInstant) { this.multiProgressMonitorFactory = multiProgressMonitorFactory; this.tokenAccessor = tokenAccessor;
diff --git a/java/com/google/gerrit/server/account/ServiceUserClassifierImpl.java b/java/com/google/gerrit/server/account/ServiceUserClassifierImpl.java index a05baf5..cfe7857 100644 --- a/java/com/google/gerrit/server/account/ServiceUserClassifierImpl.java +++ b/java/com/google/gerrit/server/account/ServiceUserClassifierImpl.java
@@ -23,6 +23,7 @@ import com.google.gerrit.server.logging.TraceContext; import com.google.gerrit.server.logging.TraceContext.TraceTimer; import com.google.inject.AbstractModule; +import com.google.inject.Inject; import com.google.inject.Module; import com.google.inject.Scopes; import com.google.inject.Singleton; @@ -31,7 +32,6 @@ import java.util.List; import java.util.Optional; import java.util.Set; -import javax.inject.Inject; /** * An implementation of {@link ServiceUserClassifier} that will consider a user to be a robot if
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdKeyFactory.java b/java/com/google/gerrit/server/account/externalids/ExternalIdKeyFactory.java index 560a1c7..1d11bb9 100644 --- a/java/com/google/gerrit/server/account/externalids/ExternalIdKeyFactory.java +++ b/java/com/google/gerrit/server/account/externalids/ExternalIdKeyFactory.java
@@ -20,8 +20,8 @@ import com.google.gerrit.common.UsedAt.Project; import com.google.gerrit.server.config.AuthConfig; import com.google.inject.ImplementedBy; -import javax.inject.Inject; -import javax.inject.Singleton; +import com.google.inject.Inject; +import com.google.inject.Singleton; @Singleton public class ExternalIdKeyFactory {
diff --git a/java/com/google/gerrit/server/account/externalids/storage/notedb/ExternalIdFactoryNoteDbImpl.java b/java/com/google/gerrit/server/account/externalids/storage/notedb/ExternalIdFactoryNoteDbImpl.java index aa321e7..cb3e894 100644 --- a/java/com/google/gerrit/server/account/externalids/storage/notedb/ExternalIdFactoryNoteDbImpl.java +++ b/java/com/google/gerrit/server/account/externalids/storage/notedb/ExternalIdFactoryNoteDbImpl.java
@@ -28,9 +28,9 @@ import com.google.gerrit.server.account.externalids.ExternalIdFactory; import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory; import com.google.gerrit.server.config.AuthConfig; +import com.google.inject.Inject; +import com.google.inject.Singleton; import java.util.Set; -import javax.inject.Inject; -import javax.inject.Singleton; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ObjectId;
diff --git a/java/com/google/gerrit/server/account/storage/notedb/AccountNoteDbWriteStorageModule.java b/java/com/google/gerrit/server/account/storage/notedb/AccountNoteDbWriteStorageModule.java index 083f993..0384324 100644 --- a/java/com/google/gerrit/server/account/storage/notedb/AccountNoteDbWriteStorageModule.java +++ b/java/com/google/gerrit/server/account/storage/notedb/AccountNoteDbWriteStorageModule.java
@@ -23,8 +23,15 @@ import com.google.gerrit.server.account.storage.notedb.validators.ExternalIdUpdateValidator; import com.google.gerrit.server.git.validators.CommitValidationListener; import com.google.gerrit.server.git.validators.MergeValidationListener; +import com.google.gerrit.server.index.account.AccountIndexCollection; +import com.google.gerrit.server.index.account.AccountIndexRewriter; +import com.google.gerrit.server.index.account.AccountIndexer; +import com.google.gerrit.server.index.account.AccountIndexerImpl; import com.google.gerrit.server.index.account.ReindexAccountsAfterRefUpdate; import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.assistedinject.FactoryModuleBuilder; /** Module that binds {@link AccountsUpdate} */ public class AccountNoteDbWriteStorageModule extends AbstractModule { @@ -44,5 +51,16 @@ DynamicSet.bind(binder(), CommitValidationListener.class).to(AccountCommitValidator.class); DynamicSet.bind(binder(), CommitValidationListener.class).to(ExternalIdUpdateValidator.class); DynamicSet.bind(binder(), MergeValidationListener.class).to(AccountMergeValidator.class); + + // Indexing + install(new FactoryModuleBuilder().build(AccountIndexerImpl.Factory.class)); + bind(AccountIndexRewriter.class); + } + + @Provides + @Singleton + AccountIndexer getAccountIndexer( + AccountIndexerImpl.Factory factory, AccountIndexCollection indexes) { + return factory.create(indexes); } }
diff --git a/java/com/google/gerrit/server/account/storage/notedb/AccountsNoteDbImpl.java b/java/com/google/gerrit/server/account/storage/notedb/AccountsNoteDbImpl.java index 28d3a43..b0c35be 100644 --- a/java/com/google/gerrit/server/account/storage/notedb/AccountsNoteDbImpl.java +++ b/java/com/google/gerrit/server/account/storage/notedb/AccountsNoteDbImpl.java
@@ -39,6 +39,7 @@ import com.google.gerrit.server.config.VersionedDefaultPreferences; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.inject.Inject; +import com.google.inject.Singleton; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -46,11 +47,11 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Stream; -import javax.inject.Singleton; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; +/** NoteDb-based implementation of {@link Accounts}. */ @Singleton public class AccountsNoteDbImpl implements Accounts { private static final FluentLogger logger = FluentLogger.forEnclosingClass();
diff --git a/java/com/google/gerrit/server/account/storage/notedb/AccountsUpdateNoteDbImpl.java b/java/com/google/gerrit/server/account/storage/notedb/AccountsUpdateNoteDbImpl.java index 663eddd..1820e70 100644 --- a/java/com/google/gerrit/server/account/storage/notedb/AccountsUpdateNoteDbImpl.java +++ b/java/com/google/gerrit/server/account/storage/notedb/AccountsUpdateNoteDbImpl.java
@@ -57,7 +57,9 @@ import com.google.gerrit.server.update.RetryHelper; import com.google.gerrit.server.update.RetryableAction.Action; import com.google.gerrit.server.update.context.RefUpdateContext; +import com.google.inject.Inject; import com.google.inject.Provider; +import com.google.inject.Singleton; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; @@ -67,8 +69,6 @@ import java.util.Optional; import java.util.Set; import java.util.function.Function; -import javax.inject.Inject; -import javax.inject.Singleton; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.BatchRefUpdate; import org.eclipse.jgit.lib.ObjectId;
diff --git a/java/com/google/gerrit/server/api/projects/CommitApiImpl.java b/java/com/google/gerrit/server/api/projects/CommitApiImpl.java index e055a00..ee14164 100644 --- a/java/com/google/gerrit/server/api/projects/CommitApiImpl.java +++ b/java/com/google/gerrit/server/api/projects/CommitApiImpl.java
@@ -29,7 +29,9 @@ import com.google.gerrit.server.restapi.project.CommitIncludedIn; import com.google.gerrit.server.restapi.project.FilesInCommitCollection; import com.google.gerrit.server.restapi.project.GetCommit; +import com.google.gerrit.server.restapi.project.ListDiffFiles; import com.google.inject.Inject; +import com.google.inject.Provider; import com.google.inject.assistedinject.Assisted; import java.util.Map; @@ -44,6 +46,7 @@ private final CommitIncludedIn includedIn; private final CommitResource commitResource; private final FilesInCommitCollection.ListFiles listFiles; + private final Provider<ListDiffFiles> listDiffFiles; @Inject CommitApiImpl( @@ -52,12 +55,14 @@ CherryPickCommit cherryPickCommit, CommitIncludedIn includedIn, FilesInCommitCollection.ListFiles listFiles, + Provider<ListDiffFiles> listDiffFiles, @Assisted CommitResource commitResource) { this.changes = changes; this.getCommit = getCommit; this.cherryPickCommit = cherryPickCommit; this.includedIn = includedIn; this.listFiles = listFiles; + this.listDiffFiles = listDiffFiles; this.commitResource = commitResource; } @@ -96,4 +101,13 @@ throw asRestApiException("Cannot retrieve files", e); } } + + @Override + public Map<String, FileInfo> diffFiles(String base, boolean nameOnly) throws RestApiException { + try { + return listDiffFiles.get().setBase(base).setNameOnly(nameOnly).apply(commitResource).value(); + } catch (Exception e) { + throw asRestApiException("Cannot retrieve diff files", e); + } + } }
diff --git a/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java index ea9d09e..5319195 100644 --- a/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java +++ b/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -52,6 +52,8 @@ import com.google.gerrit.extensions.common.BatchLabelInput; import com.google.gerrit.extensions.common.BatchSubmitRequirementInput; import com.google.gerrit.extensions.common.ChangeInfo; +import com.google.gerrit.extensions.common.DiffInfo; +import com.google.gerrit.extensions.common.FileInfo; import com.google.gerrit.extensions.common.Input; import com.google.gerrit.extensions.common.LabelDefinitionInfo; import com.google.gerrit.extensions.common.ProjectInfo; @@ -63,8 +65,10 @@ import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.extensions.restapi.TopLevelResource; +import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.permissions.GlobalPermission; import com.google.gerrit.server.permissions.PermissionBackend; +import com.google.gerrit.server.project.FileResource; import com.google.gerrit.server.project.ProjectJson; import com.google.gerrit.server.project.ProjectResource; import com.google.gerrit.server.restapi.project.Check; @@ -80,6 +84,7 @@ import com.google.gerrit.server.restapi.project.GetAccess; import com.google.gerrit.server.restapi.project.GetConfig; import com.google.gerrit.server.restapi.project.GetDescription; +import com.google.gerrit.server.restapi.project.GetDiffFile; import com.google.gerrit.server.restapi.project.GetHead; import com.google.gerrit.server.restapi.project.GetParent; import com.google.gerrit.server.restapi.project.Index; @@ -109,6 +114,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.eclipse.jgit.lib.ObjectId; public class ProjectApiImpl implements ProjectApi { interface Factory { @@ -163,6 +169,8 @@ private final PostSubmitRequirementsReview postSubmitRequirementsReview; private final LabelApiImpl.Factory labelApi; private final SubmitRequirementApiImpl.Factory submitRequirementApi; + private final GitRepositoryManager repoManager; + private final Provider<GetDiffFile> getDiffFile; @AssistedInject ProjectApiImpl( @@ -170,6 +178,7 @@ CreateProject createProject, ProjectApiImpl.Factory projectApi, ProjectsCollection projects, + GitRepositoryManager repoManager, GetDescription getDescription, PutDescription putDescription, ChildProjectApiImpl.Factory childApi, @@ -209,12 +218,14 @@ SubmitRequirementApiImpl.Factory submitRequirementApi, PostSubmitRequirements postSubmitRequirements, PostSubmitRequirementsReview postSubmitRequirementsReview, + Provider<GetDiffFile> getDiffFile, @Assisted ProjectResource project) { this( permissionBackend, createProject, projectApi, projects, + repoManager, getDescription, putDescription, childApi, @@ -255,6 +266,7 @@ submitRequirementApi, postSubmitRequirements, postSubmitRequirementsReview, + getDiffFile, null); } @@ -264,6 +276,7 @@ CreateProject createProject, ProjectApiImpl.Factory projectApi, ProjectsCollection projects, + GitRepositoryManager repoManager, GetDescription getDescription, PutDescription putDescription, ChildProjectApiImpl.Factory childApi, @@ -303,12 +316,14 @@ SubmitRequirementApiImpl.Factory submitRequirementApi, PostSubmitRequirements postSubmitRequirements, PostSubmitRequirementsReview postSubmitRequirementsReview, + Provider<GetDiffFile> getDiffFile, @Assisted String name) { this( permissionBackend, createProject, projectApi, projects, + repoManager, getDescription, putDescription, childApi, @@ -349,6 +364,7 @@ submitRequirementApi, postSubmitRequirements, postSubmitRequirementsReview, + getDiffFile, name); } @@ -357,6 +373,7 @@ CreateProject createProject, ProjectApiImpl.Factory projectApi, ProjectsCollection projects, + GitRepositoryManager repoManager, GetDescription getDescription, PutDescription putDescription, ChildProjectApiImpl.Factory childApi, @@ -397,11 +414,13 @@ SubmitRequirementApiImpl.Factory submitRequirementApi, PostSubmitRequirements postSubmitRequirements, PostSubmitRequirementsReview postSubmitRequirementsReview, + Provider<GetDiffFile> getDiffFile, String name) { this.permissionBackend = permissionBackend; this.createProject = createProject; this.projectApi = projectApi; this.projects = projects; + this.repoManager = repoManager; this.getDescription = getDescription; this.putDescription = putDescription; this.childApi = childApi; @@ -443,6 +462,7 @@ this.submitRequirementApi = submitRequirementApi; this.postSubmitRequirements = postSubmitRequirements; this.postSubmitRequirementsReview = postSubmitRequirementsReview; + this.getDiffFile = getDiffFile; } @Override @@ -584,6 +604,28 @@ } @Override + public Map<String, FileInfo> diffFiles(String oldCommit, String newCommit, boolean nameOnly) + throws RestApiException { + if (newCommit == null) { + throw new BadRequestException("new commit SHA1 is required"); + } + return commit(newCommit).diffFiles(oldCommit, nameOnly); + } + + @Override + public DiffInfo diffFile(String oldCommit, String newCommit, String path) + throws RestApiException { + try { + FileResource resource = + FileResource.create( + repoManager, checkExists().getProjectState(), ObjectId.fromString(newCommit), path); + return getDiffFile.get().setBase(oldCommit).apply(resource).value(); + } catch (Exception e) { + throw asRestApiException("Cannot get diff file", e); + } + } + + @Override public ListRefsRequest<BranchInfo> branches() { return new ListRefsRequest<>() { @Override
diff --git a/java/com/google/gerrit/server/approval/testing/TestPatchSetApprovalUuidGenerator.java b/java/com/google/gerrit/server/approval/testing/TestPatchSetApprovalUuidGenerator.java index 676640d..e1caf2f 100644 --- a/java/com/google/gerrit/server/approval/testing/TestPatchSetApprovalUuidGenerator.java +++ b/java/com/google/gerrit/server/approval/testing/TestPatchSetApprovalUuidGenerator.java
@@ -19,9 +19,9 @@ import com.google.gerrit.entities.PatchSetApproval; import com.google.gerrit.entities.PatchSetApproval.UUID; import com.google.gerrit.server.approval.PatchSetApprovalUuidGenerator; +import com.google.inject.Singleton; import java.time.Instant; import java.util.Locale; -import javax.inject.Singleton; /** * Implementation of {@link PatchSetApprovalUuidGenerator} that returns predictable {@link UUID}.
diff --git a/java/com/google/gerrit/server/cache/h2/CacheStoreStartupExecutor.java b/java/com/google/gerrit/server/cache/h2/CacheStoreStartupExecutor.java new file mode 100644 index 0000000..903173c --- /dev/null +++ b/java/com/google/gerrit/server/cache/h2/CacheStoreStartupExecutor.java
@@ -0,0 +1,24 @@ +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.cache.h2; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.inject.BindingAnnotation; +import java.lang.annotation.Retention; + +@Retention(RUNTIME) +@BindingAnnotation +public @interface CacheStoreStartupExecutor {}
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java b/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java index ab4c852..a0534ca 100644 --- a/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java +++ b/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
@@ -87,6 +87,7 @@ private final boolean pruneOnStartup; private final Schedule schedule; private final AtomicBoolean isDiskCacheReadOnly; + @Nullable private final ExecutorService startupExecutor; @Inject H2CacheFactory( @@ -95,6 +96,7 @@ DynamicMap<Cache<?, ?>> cacheMap, @Nullable @CacheCleanupExecutor ScheduledExecutorService cleanupExecutor, @Nullable @CacheStoreExecutor ExecutorService storeExecutor, + @Nullable @CacheStoreStartupExecutor ExecutorService startupExecutor, @Nullable @CacheDir Path cacheDir, Set<CacheOptions> options, @Named("DiskCacheReadOnly") AtomicBoolean isDiskCacheReadOnly) { @@ -112,13 +114,15 @@ this.cleanup = cleanupExecutor; this.options = options; this.isDiskCacheReadOnly = isDiskCacheReadOnly; + this.startupExecutor = startupExecutor; } @Override public void start() { if (executor != null) { + ExecutorService se = startupExecutor != null ? startupExecutor : executor; for (H2CacheImpl<?, ?> cache : caches) { - executor.execute(cache::start); + se.execute(cache::start); if (cleanup != null) { if (pruneOnStartup) { @SuppressWarnings("unused") @@ -135,6 +139,9 @@ TimeUnit.MILLISECONDS); } } + if (se != executor) { + se.shutdown(); + } } } @@ -142,6 +149,10 @@ public void stop() { if (executor != null) { try { + if (startupExecutor != null) { + startupExecutor.shutdownNow(); + } + if (cleanup != null) { cleanup.shutdownNow(); } @@ -214,8 +225,13 @@ private <V, K> SqlStore<K, V> newSqlStore(PersistentCacheDef<K, V> def, long maxSize) { StringBuilder url = new StringBuilder(); - url.append("jdbc:h2:") - .append(cacheDir.resolve(def.name() + "-v" + COMPATIBILITY_VERSION).toUri()); + url.append("jdbc:h2:file:") + .append( + cacheDir + .resolve(def.name() + "-v" + COMPATIBILITY_VERSION) + .toAbsolutePath() + .toString() + .replace(";", "\\;")); if (h2CacheSize >= 0) { url.append(";CACHE_SIZE="); // H2 CACHE_SIZE is always given in KB
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java index 39afb38..7dada27 100644 --- a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java +++ b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
@@ -208,7 +208,9 @@ } void start() { + logger.atFine().log("Opening disk cache %s...", cacheName); store.open(); + logger.atFine().log("Finished opening disk cache %s...", cacheName); } void stop() {
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheModule.java b/java/com/google/gerrit/server/cache/h2/H2CacheModule.java index baf08e6..0ac8123 100644 --- a/java/com/google/gerrit/server/cache/h2/H2CacheModule.java +++ b/java/com/google/gerrit/server/cache/h2/H2CacheModule.java
@@ -115,6 +115,24 @@ } @Provides + @Singleton + @Nullable + @CacheStoreStartupExecutor + ExecutorService createDiskCacheStoreStartupExecutor( + @Nullable @CacheDir Path cacheDir, @GerritServerConfig Config cfg) { + if (cacheDir == null) { + return null; + } + int startupThreads = cfg.getInt("cache", null, "startupThreads", 1); + if (startupThreads > 1) { + return new LoggingContextAwareExecutorService( + Executors.newFixedThreadPool( + 1, new ThreadFactoryBuilder().setNameFormat("DiskCache-Store-Startup-%d").build())); + } + return null; + } + + @Provides Set<CacheOptions> getOptions() { return options; }
diff --git a/java/com/google/gerrit/server/change/ChangeInserter.java b/java/com/google/gerrit/server/change/ChangeInserter.java index 25c2279..468fbdd 100644 --- a/java/com/google/gerrit/server/change/ChangeInserter.java +++ b/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -707,6 +707,7 @@ ctx.getRevWalk().getObjectReader(), commitId, ctx.getIdentifiedUser(), + change.getCherryPickOf(), diffOperationsForCommitValidationFactory.create( ctx.getRepoView(), ctx.getInserter()))) { if (!validate) {
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java index 86065ed..a2472e6 100644 --- a/java/com/google/gerrit/server/change/ChangeJson.java +++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -51,7 +51,6 @@ import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.flogger.FluentLogger; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -123,6 +122,8 @@ import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeData.ChangedLines; import com.google.gerrit.server.util.AttentionSetUtil; +import com.google.gerrit.server.util.ManualRequestContext; +import com.google.gerrit.server.util.ThreadLocalRequestContext; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; @@ -137,7 +138,9 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; @@ -248,6 +251,7 @@ private final Metrics metrics; private final RevisionJson revisionJson; private final Optional<PluginDefinedInfosFactory> pluginDefinedInfosFactory; + private final ThreadLocalRequestContext requestContext; private final boolean includeMergeable; private final boolean lazyLoad; private final boolean cacheQueryResultsByChangeNum; @@ -273,6 +277,7 @@ RemoveReviewerControl removeReviewerControl, TrackingFooters trackingFooters, Metrics metrics, + ThreadLocalRequestContext requestContext, RevisionJson.Factory revisionJsonFactory, @GerritServerConfig Config cfg, ExperimentFeatures experimentFeatures, @@ -293,6 +298,7 @@ this.removeReviewerControl = removeReviewerControl; this.trackingFooters = trackingFooters; this.metrics = metrics; + this.requestContext = requestContext; this.revisionJson = revisionJsonFactory.create(options); this.options = Sets.immutableEnumSet(options); this.includeMergeable = MergeabilityComputationBehavior.fromConfig(cfg).includeInApi(); @@ -324,24 +330,29 @@ } public ChangeInfo format(ChangeData cd) { - return format(cd, Optional.empty(), true, getPluginInfos(cd)); + return format(cd, Optional.empty(), true, getPluginInfos(cd), userProvider.get()); } public ChangeInfo format(RevisionResource rsrc) { ChangeData cd = changeDataFactory.create(rsrc.getNotes()); - return format(cd, Optional.of(rsrc.getPatchSet().id()), true, getPluginInfos(cd)); + return format( + cd, Optional.of(rsrc.getPatchSet().id()), true, getPluginInfos(cd), userProvider.get()); } public List<List<ChangeInfo>> format(List<QueryResult<ChangeData>> in) throws PermissionBackendException { try (Timer0.Context ignored = metrics.formatQueryResultsLatency.start()) { accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS)); + CurrentUser user = userProvider.get(); List<List<ChangeInfo>> res = new ArrayList<>(in.size()); - Map<Change.Id, ChangeInfo> cache = Maps.newHashMapWithExpectedSize(in.size()); + Map<Change.Id, ChangeInfo> cache = new ConcurrentHashMap<>(in.size()); + List<ChangeData> allChanges = + in.stream().flatMap(e -> e.entities().stream()).collect(toList()); + ensureLoaded(allChanges); ImmutableListMultimap<Change.Id, PluginDefinedInfo> pluginInfosByChange = - getPluginInfos(in.stream().flatMap(e -> e.entities().stream()).collect(toList())); + getPluginInfos(allChanges); for (QueryResult<ChangeData> r : in) { - List<ChangeInfo> infos = toChangeInfos(r.entities(), cache, pluginInfosByChange); + List<ChangeInfo> infos = toChangeInfos(r.entities(), cache, pluginInfosByChange, user); if (!infos.isEmpty() && r.more()) { infos.get(infos.size() - 1)._moreChanges = true; } @@ -355,11 +366,19 @@ public List<ChangeInfo> format(Collection<ChangeData> in) throws PermissionBackendException { accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS)); ensureLoaded(in); - List<ChangeInfo> out = new ArrayList<>(in.size()); + // CurrentUser is thread-safe for reading. It is not mutated during the parallel formatting. + CurrentUser user = userProvider.get(); ImmutableListMultimap<Change.Id, PluginDefinedInfo> pluginInfosByChange = getPluginInfos(in); - for (ChangeData cd : in) { - out.add(format(cd, Optional.empty(), false, pluginInfosByChange.get(cd.getId()))); - } + List<ChangeInfo> out = + in.parallelStream() + .map( + cd -> { + try (ManualRequestContext ctx = new ManualRequestContext(user, requestContext)) { + return format( + cd, Optional.empty(), false, pluginInfosByChange.get(cd.getId()), user); + } + }) + .collect(toList()); accountLoader.fill(); return out; } @@ -379,7 +398,7 @@ return checkOnly(changeDataFactory.create(project, id)); } ChangeData cd = changeDataFactory.create(notes); - return format(cd, Optional.empty(), true, getPluginInfos(cd)); + return format(cd, Optional.empty(), true, getPluginInfos(cd), userProvider.get()); } private static List<LegacySubmitRequirementInfo> requirementsFor(ChangeData cd) { @@ -482,15 +501,16 @@ ChangeData cd, Optional<PatchSet.Id> limitToPsId, boolean fillAccountLoader, - List<PluginDefinedInfo> pluginInfosForChange) { + List<PluginDefinedInfo> pluginInfosForChange, + CurrentUser user) { try { if (fillAccountLoader) { accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS)); - ChangeInfo res = toChangeInfo(cd, limitToPsId, pluginInfosForChange); + ChangeInfo res = toChangeInfo(cd, limitToPsId, pluginInfosForChange, user); accountLoader.fill(); return res; } - return toChangeInfo(cd, limitToPsId, pluginInfosForChange); + return toChangeInfo(cd, limitToPsId, pluginInfosForChange, user); } catch (PatchListNotAvailableException | GpgException | IOException @@ -510,29 +530,38 @@ TraceContext.newTimer( "Load change data for lazyLoad options", Metadata.builder().resourceCount(all.size()).build())) { - for (ChangeData cd : all) { - // Mark all ChangeDatas as coming from the index, but allow backfilling data from NoteDb - cd.setStorageConstraint(ChangeData.StorageConstraint.INDEX_PRIMARY_NOTEDB_SECONDARY); - } - ChangeData.ensureChangeLoaded(all); - if (has(ALL_REVISIONS)) { - ChangeData.ensureAllPatchSetsLoaded(all); - } else if (has(CURRENT_REVISION) || has(MESSAGES)) { - ChangeData.ensureCurrentPatchSetLoaded(all); - } - if (has(REVIEWED) && userProvider.get().isIdentifiedUser()) { - ChangeData.ensureReviewedByLoadedForOpenChanges(all); - } - if (has(STAR) && userProvider.get().isIdentifiedUser()) { - ChangeData.ensureChangeServerId(all); - } - ChangeData.ensureCurrentApprovalsLoaded(all); + boolean isIdentifiedUser = userProvider.get().isIdentifiedUser(); + all.parallelStream() + .forEach( + cd -> { + // Mark all ChangeDatas as coming from the index, but allow backfilling data from + // NoteDb + cd.setStorageConstraint( + ChangeData.StorageConstraint.INDEX_PRIMARY_NOTEDB_SECONDARY); + + Set<ChangeData> singleCdSet = Collections.singleton(cd); + ChangeData.ensureChangeLoaded(singleCdSet); + if (has(ALL_REVISIONS)) { + ChangeData.ensureAllPatchSetsLoaded(singleCdSet); + } else if (has(CURRENT_REVISION) || has(MESSAGES)) { + ChangeData.ensureCurrentPatchSetLoaded(singleCdSet); + } + if (has(REVIEWED) && isIdentifiedUser) { + ChangeData.ensureReviewedByLoadedForOpenChanges(singleCdSet); + } + if (has(STAR) && isIdentifiedUser) { + ChangeData.ensureChangeServerId(singleCdSet); + } + ChangeData.ensureCurrentApprovalsLoaded(singleCdSet); + }); } } else { - for (ChangeData cd : all) { - // Mark all ChangeDatas as coming from the index. Disallow using NoteDb - cd.setStorageConstraint(ChangeData.StorageConstraint.INDEX_ONLY); - } + all.parallelStream() + .forEach( + cd -> { + // Mark all ChangeDatas as coming from the index. Disallow using NoteDb + cd.setStorageConstraint(ChangeData.StorageConstraint.INDEX_ONLY); + }); } } @@ -543,50 +572,61 @@ private List<ChangeInfo> toChangeInfos( List<ChangeData> changes, Map<Change.Id, ChangeInfo> cache, - ImmutableListMultimap<Change.Id, PluginDefinedInfo> pluginInfosByChange) { + ImmutableListMultimap<Change.Id, PluginDefinedInfo> pluginInfosByChange, + CurrentUser user) { try (Timer0.Context ignored = metrics.toChangeInfosLatency.start()) { - List<ChangeInfo> changeInfos = new ArrayList<>(changes.size()); - for (int i = 0; i < changes.size(); i++) { - // We can only cache and re-use an entity if it's not the last in the list. The last entity - // may later get _moreChanges set. If it was cached or re-used, that setting would propagate - // to the original entity yielding wrong results. - // This problem has two sides where 'last in the list' has to be respected: - // (1) Caching - // (2) Reusing - boolean isCacheable = cacheQueryResultsByChangeNum && (i != changes.size() - 1); - ChangeData cd = changes.get(i); - if (cd.hasFailedParsingFromIndex()) { - Optional<ChangeInfo> faultyChangeInfo = createFaultyChangeInfo(cd); - if (faultyChangeInfo.isPresent()) { - changeInfos.add(faultyChangeInfo.get()); - } - continue; - } - try { - Change.Id cdUniqueId = cd.virtualId(); - ChangeInfo info = cache.get(cdUniqueId); - if (info != null && isCacheable) { - changeInfos.add(info); - continue; - } + List<ChangeInfo> changeInfos = + IntStream.range(0, changes.size()) + .parallel() + .mapToObj( + i -> { + try (ManualRequestContext ctx = + new ManualRequestContext(user, requestContext)) { + ChangeData cd = changes.get(i); + // Cache/re-use only if it is not the last entity in the list. + // The last entity may have _moreChanges set later, which would + // propagate to the original cached/re-used entity, yielding + // incorrect results. This applies to both caching and reusing. + boolean isCacheable = + cacheQueryResultsByChangeNum && (i != changes.size() - 1); + if (cd.hasFailedParsingFromIndex()) { + return createFaultyChangeInfo(cd).orElse(null); + } + try { + Change.Id cdUniqueId = cd.virtualId(); + if (isCacheable) { + ChangeInfo info = cache.get(cdUniqueId); + if (info != null) { + return info; + } + } - // Compute and cache if possible - ensureLoaded(Collections.singleton(cd)); - info = format(cd, Optional.empty(), false, pluginInfosByChange.get(cd.getId())); - changeInfos.add(info); - if (isCacheable) { - cache.put(cdUniqueId, info); - } - } catch (RuntimeException e) { - Optional<RequestCancelledException> requestCancelledException = - RequestCancelledException.getFromCausalChain(e); - if (requestCancelledException.isPresent()) { - throw e; - } - logger.atWarning().withCause(e).log( - "Omitting corrupt change %s from results", cd.getId()); - } - } + ChangeInfo info = + format( + cd, + Optional.empty(), + false, + pluginInfosByChange.get(cd.getId()), + user); + if (isCacheable) { + cache.put(cdUniqueId, info); + } + return info; + } catch (RuntimeException e) { + Optional<RequestCancelledException> requestCancelledException = + RequestCancelledException.getFromCausalChain(e); + if (requestCancelledException.isPresent()) { + throw e; + } + logger.atWarning().withCause(e).log( + "Omitting corrupt change %s from results", cd.getId()); + return null; + } + } + }) + .filter(java.util.Objects::nonNull) + .collect(toList()); + if (has(STAR) && userProvider.get().isIdentifiedUser()) { populateStarField(changeInfos); } @@ -639,18 +679,21 @@ private ChangeInfo toChangeInfo( ChangeData cd, Optional<PatchSet.Id> limitToPsId, - List<PluginDefinedInfo> pluginInfosForChange) + List<PluginDefinedInfo> pluginInfosForChange, + CurrentUser user) throws PatchListNotAvailableException, GpgException, PermissionBackendException, IOException { try (Timer0.Context ignored = metrics.toChangeInfoLatency.start()) { - return toChangeInfoImpl(cd, limitToPsId, pluginInfosForChange); + return toChangeInfoImpl(cd, limitToPsId, pluginInfosForChange, user); } } private ChangeInfo toChangeInfoImpl( - ChangeData cd, Optional<PatchSet.Id> limitToPsId, List<PluginDefinedInfo> pluginInfos) + ChangeData cd, + Optional<PatchSet.Id> limitToPsId, + List<PluginDefinedInfo> pluginInfos, + CurrentUser user) throws PatchListNotAvailableException, GpgException, PermissionBackendException, IOException { ChangeInfo out = new ChangeInfo(); - CurrentUser user = userProvider.get(); if (has(CHECK)) { out.problems = checkerProvider.get().check(cd.notes(), fix).problems(); @@ -767,7 +810,7 @@ if (has(LABELS) || has(DETAILED_LABELS)) { out.reviewers = reviewerMap(cd.reviewers(), cd.reviewersByEmail(), false); out.pendingReviewers = reviewerMap(cd.pendingReviewers(), cd.pendingReviewersByEmail(), true); - out.removableReviewers = removableReviewers(cd, out); + out.removableReviewers = removableReviewers(cd, out, user); } setSubmitter(cd, out); @@ -954,7 +997,7 @@ } } - private List<AccountInfo> removableReviewers(ChangeData cd, ChangeInfo out) + private List<AccountInfo> removableReviewers(ChangeData cd, ChangeInfo out, CurrentUser user) throws PermissionBackendException { try (TraceTimer timer = TraceContext.newTimer( @@ -989,7 +1032,7 @@ // Check if the user has the permission to remove a reviewer. This means we can bypass the // permission checks for a specific reviewer in the loop saving potentially many permission // checks. - PermissionBackend.WithUser withUser = permissionBackend.user(userProvider.get()); + PermissionBackend.WithUser withUser = permissionBackend.user(user); boolean canRemoveAnyReviewer = withUser.change(cd).test(ChangePermission.REMOVE_REVIEWER) || withUser @@ -1013,7 +1056,7 @@ if ((cd.change().isMerged() && value != 0) || (!canRemoveAnyReviewer && !RemoveReviewerControl.canRemoveReviewerWithoutPermissionCheck( - cd.change(), userProvider.get(), id, value))) { + cd.change(), user, id, value))) { fixed.add(id); } } @@ -1028,8 +1071,7 @@ for (AccountInfo ai : ccs) { if (ai._accountId != null) { Account.Id id = Account.id(ai._accountId); - if (canRemoveAnyReviewer - || removeReviewerControl.testRemoveReviewer(cd, userProvider.get(), id, 0)) { + if (canRemoveAnyReviewer || removeReviewerControl.testRemoveReviewer(cd, user, id, 0)) { removable.add(id); } } @@ -1050,7 +1092,7 @@ for (AccountInfo info : infos) { if (info._accountId == null) { if (canRemoveAnyReviewer - || removeReviewerControl.testRemoveReviewer(cd, userProvider.get(), null, 0)) { + || removeReviewerControl.testRemoveReviewer(cd, user, null, 0)) { result.add(info); } }
diff --git a/java/com/google/gerrit/server/change/CommentsValidator.java b/java/com/google/gerrit/server/change/CommentsValidator.java index c02fb83..9b75b5e 100644 --- a/java/com/google/gerrit/server/change/CommentsValidator.java +++ b/java/com/google/gerrit/server/change/CommentsValidator.java
@@ -33,12 +33,12 @@ import com.google.gerrit.server.patch.PatchListCache; import com.google.gerrit.server.patch.PatchListKey; import com.google.gerrit.server.patch.PatchListNotAvailableException; +import com.google.inject.Inject; +import com.google.inject.Singleton; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import javax.inject.Inject; -import javax.inject.Singleton; import org.eclipse.jgit.lib.ObjectId; @Singleton
diff --git a/java/com/google/gerrit/server/change/FileInfoJsonImpl.java b/java/com/google/gerrit/server/change/FileInfoJsonImpl.java index cda6191..11f5c94 100644 --- a/java/com/google/gerrit/server/change/FileInfoJsonImpl.java +++ b/java/com/google/gerrit/server/change/FileInfoJsonImpl.java
@@ -97,6 +97,15 @@ for (String path : fileDiffs.keySet()) { FileDiffOutput fileDiff = fileDiffs.get(path); FileInfo fileInfo = new FileInfo(); + if (fileDiff.isNegative() || fileDiff.isTooExpensive()) { + fileInfo.diffsTooExpensiveToCompute = true; + fileInfo.status = + fileDiff.changeType() != Patch.ChangeType.MODIFIED + ? fileDiff.changeType().getCode() + : null; + result.put(path, fileInfo); + continue; + } fileInfo.status = fileDiff.changeType() != Patch.ChangeType.MODIFIED ? fileDiff.changeType().getCode()
diff --git a/java/com/google/gerrit/server/change/PatchSetInserter.java b/java/com/google/gerrit/server/change/PatchSetInserter.java index b18ae7f..cc5aea7 100644 --- a/java/com/google/gerrit/server/change/PatchSetInserter.java +++ b/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -475,6 +475,7 @@ ctx.getRevWalk().getObjectReader(), commitId, ctx.getIdentifiedUser(), + origNotes.getChange().getCherryPickOf(), diffOperationsForCommitValidationFactory.create( ctx.getRepoView(), ctx.getInserter()))) { if (!validate) {
diff --git a/java/com/google/gerrit/server/change/ReviewerModifier.java b/java/com/google/gerrit/server/change/ReviewerModifier.java index f34a673..92dd9db 100644 --- a/java/com/google/gerrit/server/change/ReviewerModifier.java +++ b/java/com/google/gerrit/server/change/ReviewerModifier.java
@@ -717,6 +717,10 @@ a -> { if (a.isFailure()) { if (a.isIgnorableFailure()) { + logger.atWarning().log( + "Ignored failure while adding reviewer '%s': %s", + a.input.reviewer, a.result.error); + return false; } // Shouldn't happen, caller should have checked that there were no errors.
diff --git a/java/com/google/gerrit/server/config/FileBasedAllProjectsConfigProvider.java b/java/com/google/gerrit/server/config/FileBasedAllProjectsConfigProvider.java index db21e1f..77c5617 100644 --- a/java/com/google/gerrit/server/config/FileBasedAllProjectsConfigProvider.java +++ b/java/com/google/gerrit/server/config/FileBasedAllProjectsConfigProvider.java
@@ -17,8 +17,8 @@ import com.google.common.annotations.VisibleForTesting; import com.google.gerrit.server.project.ProjectConfig; import com.google.inject.Inject; +import com.google.inject.Singleton; import java.util.Optional; -import javax.inject.Singleton; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS;
diff --git a/java/com/google/gerrit/server/config/UserPreferencesConverter.java b/java/com/google/gerrit/server/config/UserPreferencesConverter.java index c6662f5..7aa33bf 100644 --- a/java/com/google/gerrit/server/config/UserPreferencesConverter.java +++ b/java/com/google/gerrit/server/config/UserPreferencesConverter.java
@@ -131,6 +131,7 @@ builder, builder::setAllowAutocompletingComments, info.allowAutocompletingComments); builder = setIfNotNull(builder, builder::setDiffPageSidebar, info.diffPageSidebar); builder = setIfNotNull(builder, builder::setAiChatSelectedModel, info.aiChatSelectedModel); + builder = setIfNotNull(builder, builder::setLabelFilter, info.labelFilter); return builder.build(); } @@ -201,6 +202,7 @@ res.diffPageSidebar = proto.hasDiffPageSidebar() ? proto.getDiffPageSidebar() : null; res.aiChatSelectedModel = proto.hasAiChatSelectedModel() ? proto.getAiChatSelectedModel() : null; + res.labelFilter = proto.hasLabelFilter() ? proto.getLabelFilter() : null; return res; } @@ -295,6 +297,12 @@ builder = setEnumIfNotNull( builder, + builder::setResponsiveMode, + UserPreferences.DiffPreferencesInfo.ResponsiveMode::valueOf, + info.responsiveMode); + builder = + setEnumIfNotNull( + builder, builder::setIgnoreWhitespace, UserPreferences.DiffPreferencesInfo.Whitespace::valueOf, info.ignoreWhitespace); @@ -330,6 +338,17 @@ res.hideEmptyPane = proto.hasHideEmptyPane() ? proto.getHideEmptyPane() : null; res.matchBrackets = proto.hasMatchBrackets() ? proto.getMatchBrackets() : null; res.lineWrapping = proto.hasLineWrapping() ? proto.getLineWrapping() : null; + if (proto.hasResponsiveMode()) { + res.responsiveMode = + DiffPreferencesInfo.ResponsiveMode.valueOf(proto.getResponsiveMode().name()); + } else if (proto.hasLineWrapping()) { + res.responsiveMode = + proto.getLineWrapping() + ? DiffPreferencesInfo.ResponsiveMode.FULL_RESPONSIVE + : DiffPreferencesInfo.ResponsiveMode.NONE; + } else { + res.responsiveMode = null; + } res.ignoreWhitespace = proto.hasIgnoreWhitespace() ? DiffPreferencesInfo.Whitespace.valueOf(proto.getIgnoreWhitespace().name())
diff --git a/java/com/google/gerrit/server/events/CommitReceivedEvent.java b/java/com/google/gerrit/server/events/CommitReceivedEvent.java index e0bc112..70cc1c4 100644 --- a/java/com/google/gerrit/server/events/CommitReceivedEvent.java +++ b/java/com/google/gerrit/server/events/CommitReceivedEvent.java
@@ -15,6 +15,8 @@ package com.google.gerrit.server.events; import com.google.common.collect.ImmutableListMultimap; +import com.google.gerrit.common.Nullable; +import com.google.gerrit.entities.PatchSet; import com.google.gerrit.entities.Project; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.patch.DiffOperationsForCommitValidation; @@ -37,6 +39,9 @@ public RevCommit commit; public IdentifiedUser user; + /** The source change and patch set that this commit was cherry-picked from (if any) */ + @Nullable public PatchSet.Id cherryPickOf; + /** * Use this for computing the modified files of the received commits. Using {@link * com.google.gerrit.server.patch.DiffOperations} from commit validators is not safe, see javadoc @@ -57,6 +62,7 @@ ObjectReader reader, ObjectId commitId, IdentifiedUser user, + @Nullable PatchSet.Id cherryPickOf, DiffOperationsForCommitValidation diffOperations) throws IOException { this(); @@ -68,6 +74,7 @@ this.revWalk = new RevWalk(reader); this.commit = revWalk.parseCommit(commitId); this.user = user; + this.cherryPickOf = cherryPickOf; this.diffOperations = diffOperations; revWalk.parseBody(commit); }
diff --git a/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java b/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java index 9156f61..c15e347 100644 --- a/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java +++ b/java/com/google/gerrit/server/experiments/ExperimentFeaturesConstants.java
@@ -61,6 +61,10 @@ public static final String ALLOW_FIX_SUGGESTIONS_IN_COMMENTS = "GerritBackendFeature__allow_fix_suggestions_in_comments"; + /** Whether to enforce a timeout during file diff computation. */ + public static final String TIMEOUT_FILE_DIFF_COMPUTATION = + "GerritBackendFeature__timeout_file_diff_computation"; + /** Whether submit_records should only be returned along with submit_requirements. */ public static final String SKIP_SUBMIT_RECORDS_WITHOUT_SUBMIT_REQUIREMENTS = "GerritBackendFeature__skip_submit_records_without_submit_requirements"; @@ -68,4 +72,8 @@ /** Whether to consider votes of deleted accounts. */ public static final String CONSIDER_VOTES_OF_DELETED_ACCOUNTS = "GerritBackendFeature__consider_votes_of_deleted_accounts"; + + /** Whether we restrict the creation of branch permissions. */ + public static final String GERRIT_BACKEND_FEATURE_RESTRICT_BRANCH_PERMISSIONS = + "GerritBackendFeature__restrict_branch_permissions"; }
diff --git a/java/com/google/gerrit/server/git/CommitUtil.java b/java/com/google/gerrit/server/git/CommitUtil.java index 58d862e..0eb142d 100644 --- a/java/com/google/gerrit/server/git/CommitUtil.java +++ b/java/com/google/gerrit/server/git/CommitUtil.java
@@ -41,6 +41,7 @@ import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.ReviewerSet; import com.google.gerrit.server.Sequences; +import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.approval.ApprovalsUtil; import com.google.gerrit.server.change.ChangeInserter; import com.google.gerrit.server.change.ChangeMessages; @@ -115,6 +116,7 @@ private final ChangeReverted changeReverted; private final BatchUpdate.Factory updateFactory; private final MessageIdGenerator messageIdGenerator; + private final AccountCache accountCache; @Inject CommitUtil( @@ -129,7 +131,8 @@ ChangeNotes.Factory changeNotesFactory, ChangeReverted changeReverted, BatchUpdate.Factory updateFactory, - MessageIdGenerator messageIdGenerator) { + MessageIdGenerator messageIdGenerator, + AccountCache accountCache) { this.repoManager = repoManager; this.serverIdent = serverIdent; this.seq = seq; @@ -142,6 +145,7 @@ this.changeReverted = changeReverted; this.updateFactory = updateFactory; this.messageIdGenerator = messageIdGenerator; + this.accountCache = accountCache; } public static CommitInfo toCommitInfo(RevCommit commit) throws IOException { @@ -473,12 +477,25 @@ ValidationOptionsUtil.getValidateOptionsAsMultimap(input.validationOptions)); ReviewerSet reviewerSet = approvalsUtil.getReviewers(notes); - Set<Account.Id> reviewers = new HashSet<>(); - reviewers.add(changeToRevert.getOwner()); - reviewers.addAll(reviewerSet.byState(ReviewerStateInternal.REVIEWER)); + if (accountExists(changeToRevert.getOwner())) { + reviewers.add(changeToRevert.getOwner()); + } + for (Account.Id reviewer : reviewerSet.byState(ReviewerStateInternal.REVIEWER)) { + // Add the original reviewers only if they exist, to avoid adding deleted accounts. + if (accountExists(reviewer)) { + reviewers.add(reviewer); + } + } reviewers.remove(user.getAccountId()); - Set<Account.Id> ccs = new HashSet<>(reviewerSet.byState(ReviewerStateInternal.CC)); + + Set<Account.Id> ccs = new HashSet<>(); + for (Account.Id cc : reviewerSet.byState(ReviewerStateInternal.CC)) { + // Add the original CCs only if they exist, to avoid adding deleted accounts. + if (accountExists(cc)) { + ccs.add(cc); + } + } ccs.remove(user.getAccountId()); ins.setReviewersAndCcsIgnoreVisibility(reviewers, ccs); ins.setRevertOf(notes.getChangeId()); @@ -515,6 +532,10 @@ bu.addOp(revertedChangeId, new PostRevertedMessageOp(revertingChangeKey)); } + private boolean accountExists(Account.Id accountId) { + return accountCache.get(accountId).isPresent(); + } + private class ChangeRevertedNotifyOp implements BatchUpdateOp { private final Change.Id revertedChangeId; private final Change.Id revertingChangeId;
diff --git a/java/com/google/gerrit/server/git/receive/BranchCommitValidator.java b/java/com/google/gerrit/server/git/receive/BranchCommitValidator.java index d0c32f4..0d02bb9 100644 --- a/java/com/google/gerrit/server/git/receive/BranchCommitValidator.java +++ b/java/com/google/gerrit/server/git/receive/BranchCommitValidator.java
@@ -182,6 +182,7 @@ objectReader, commit, user, + change != null ? change.getCherryPickOf() : null, diffOperationsForCommitValidation)) { CommitValidators validators; if (isMerged) {
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java index 848e259..6c11800 100644 --- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java +++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -2745,6 +2745,12 @@ "Creating new change for %s even though it is already tracked", name); } + Change change = null; + ChangeLookup lookup = pending.get(c); + if (lookup.changeKey != null && lookup.destChanges.size() == 1) { + change = lookup.destChanges.getFirst().change(); + } + // Validate the received commits. Do not invoke the CommitValidationInfoListener's yet // because we create changes/patch-sets for the commits only later and we need to provide // the patch set ID, that we don't know yet, to CommitValidationInfoListener's. @@ -2759,7 +2765,7 @@ magicBranch.merged, rejectCommits, /* invokeCommitValidationInfoListeners= */ false, - /* change= */ null); + change); validationInfosByCommit.put(c.name(), validationResult.validationInfos()); messages.addAll(validationResult.messages()); if (!validationResult.isValid()) { @@ -3155,6 +3161,9 @@ } bu.setNotify(magicBranch.getNotifyForNewChange()); + logger.atFine().log( + "Inserting change with reviewers %s and ccs %s", + magicBranch.getReviewers(), magicBranch.getCcs()); bu.insertChange( ins.setReviewersAndCcsAsStrings(magicBranch.getReviewers(), magicBranch.getCcs()) .setApprovals(approvals)
diff --git a/java/com/google/gerrit/server/git/receive/ReplaceOp.java b/java/com/google/gerrit/server/git/receive/ReplaceOp.java index a15419a..c7bda52 100644 --- a/java/com/google/gerrit/server/git/receive/ReplaceOp.java +++ b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
@@ -284,6 +284,7 @@ ctx.getRevWalk().getObjectReader(), commitId, ctx.getIdentifiedUser(), + change.getCherryPickOf(), diffOperationsForCommitValidationFactory.create( ctx.getRepoView(), ctx.getInserter()))) { commitValidationInfoListeners.runEach(
diff --git a/java/com/google/gerrit/server/index/IndexModule.java b/java/com/google/gerrit/server/index/IndexModule.java index b5a7bc9..6b7e87b 100644 --- a/java/com/google/gerrit/server/index/IndexModule.java +++ b/java/com/google/gerrit/server/index/IndexModule.java
@@ -40,9 +40,6 @@ import com.google.gerrit.server.git.WorkQueue; import com.google.gerrit.server.index.account.AccountIndexCollection; import com.google.gerrit.server.index.account.AccountIndexDefinition; -import com.google.gerrit.server.index.account.AccountIndexRewriter; -import com.google.gerrit.server.index.account.AccountIndexer; -import com.google.gerrit.server.index.account.AccountIndexerImpl; import com.google.gerrit.server.index.account.AccountSchemaDefinitions; import com.google.gerrit.server.index.change.AllChangesIndexer; import com.google.gerrit.server.index.change.ChangeIndexCollection; @@ -123,10 +120,9 @@ .setDefault() .toInstance(Ticker.systemTicker()); - bind(AccountIndexRewriter.class); + // AccountIndexer is bound in AccountNoteDbWriteStorageModule. bind(AccountIndexCollection.class); listener().to(AccountIndexCollection.class); - factory(AccountIndexerImpl.Factory.class); bind(ChangeIndexRewriter.class); bind(ChangeIndexCollection.class); @@ -188,13 +184,6 @@ @Provides @Singleton - AccountIndexer getAccountIndexer( - AccountIndexerImpl.Factory factory, AccountIndexCollection indexes) { - return factory.create(indexes); - } - - @Provides - @Singleton ChangeIndexer getChangeIndexer( @IndexExecutor(INTERACTIVE) ListeningExecutorService executor, ChangeIndexer.Factory factory,
diff --git a/java/com/google/gerrit/server/index/change/AllChangesIndexer.java b/java/com/google/gerrit/server/index/change/AllChangesIndexer.java index 52f3844..6c79ef9 100644 --- a/java/com/google/gerrit/server/index/change/AllChangesIndexer.java +++ b/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
@@ -513,7 +513,7 @@ logger.atInfo().log("Removing %d changes from index", changesInIndex.size()); for (Change.Id id : changesInIndex) { logger.atFine().log("Deleting change %s from index", id); - indexer.delete(id); + indexer.delete(name, id); } }
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java index 75142ac..e26165a 100644 --- a/java/com/google/gerrit/server/index/change/ChangeField.java +++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -1632,6 +1632,12 @@ + "," + srResult.submitRequirement().name().toLowerCase(Locale.US)); } + case TIMEOUT -> { + result.add( + SubmitRecord.Label.Status.NEED.name() + + "," + + srResult.submitRequirement().name().toLowerCase(Locale.US)); + } case NOT_APPLICABLE, ERROR -> result.add( SubmitRecord.Label.Status.IMPOSSIBLE.name()
diff --git a/java/com/google/gerrit/server/index/change/ChangeIndexer.java b/java/com/google/gerrit/server/index/change/ChangeIndexer.java index 1b72b7a..196d56a 100644 --- a/java/com/google/gerrit/server/index/change/ChangeIndexer.java +++ b/java/com/google/gerrit/server/index/change/ChangeIndexer.java
@@ -50,7 +50,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; @@ -376,9 +375,9 @@ } } - private void fireChangeDeletedFromIndexEvent(int id) { + private void fireChangeDeletedFromIndexEvent(String projectName, int id) { if (notifyListeners) { - indexedListeners.runEach(l -> l.onChangeDeleted(id)); + indexedListeners.runEach(l -> l.onChangeDeleted(projectName, id)); } } @@ -430,7 +429,7 @@ */ public ListenableFuture<ChangeData> deleteAsync(Project.NameKey project, Change.Id id) { fireChangeScheduledForDeletionFromIndexEvent(id.get()); - return submit(new DeleteTask(id, Optional.of(project))); + return submit(new DeleteTask(id, project)); } /** @@ -438,9 +437,9 @@ * * @param id change ID to delete. */ - public void delete(Change.Id id) { + public void delete(Project.NameKey project, Change.Id id) { fireChangeScheduledForDeletionFromIndexEvent(id.get()); - doDelete(id); + doDelete(project, id); } /** @@ -457,11 +456,7 @@ } private void doDelete(Project.NameKey project, Change.Id id) { - new DeleteTask(id, Optional.of(project)).call(); - } - - private void doDelete(Change.Id id) { - new DeleteTask(id, Optional.empty()).call(); + new DeleteTask(id, project).call(); } /** @@ -655,9 +650,9 @@ // Not AbstractIndexTask as it doesn't need a request context. private class DeleteTask implements Callable<ChangeData> { private final Change.Id id; - private final Optional<Project.NameKey> project; + private final Project.NameKey project; - private DeleteTask(Change.Id id, Optional<Project.NameKey> project) { + private DeleteTask(Change.Id id, Project.NameKey project) { this.id = id; this.project = project; } @@ -678,12 +673,7 @@ .changeId(id.get()) .indexVersion(i.getSchema().getVersion()) .build())) { - // Some index implementation require ProjectKey to build a database key - // If delete(K) method is used, this will require changeId -> projectKey lookup (index - // query), which is expensive. - // Use changeData with ProjectKey and deleteByValue(V) method, if possible - project.ifPresentOrElse( - p -> i.deleteByValue(changeDataFactory.create(p, id)), () -> i.delete(id)); + i.deleteByValue(changeDataFactory.create(project, id)); } catch (RuntimeException e) { throw new StorageException( String.format( @@ -692,7 +682,7 @@ e); } } - fireChangeDeletedFromIndexEvent(id.get()); + fireChangeDeletedFromIndexEvent(project.name(), id.get()); return null; } }
diff --git a/java/com/google/gerrit/server/mail/send/DefaultEmailFactories.java b/java/com/google/gerrit/server/mail/send/DefaultEmailFactories.java index 079f660..dee7f8a 100644 --- a/java/com/google/gerrit/server/mail/send/DefaultEmailFactories.java +++ b/java/com/google/gerrit/server/mail/send/DefaultEmailFactories.java
@@ -30,12 +30,12 @@ import com.google.gerrit.server.mail.send.InboundEmailRejectionEmailDecorator.InboundEmailError; import com.google.gerrit.server.mail.send.OutgoingEmail.EmailDecorator; import com.google.gerrit.server.patch.filediff.FileDiffOutput; +import com.google.inject.Inject; +import com.google.inject.Singleton; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import javax.inject.Inject; -import javax.inject.Singleton; import org.eclipse.jgit.lib.ObjectId; /** Default versions of Gerrit email notifications. */
diff --git a/java/com/google/gerrit/server/mail/send/MailSoySauceModule.java b/java/com/google/gerrit/server/mail/send/MailSoySauceModule.java index 25b2ebd..46bf8cc 100644 --- a/java/com/google/gerrit/server/mail/send/MailSoySauceModule.java +++ b/java/com/google/gerrit/server/mail/send/MailSoySauceModule.java
@@ -23,13 +23,13 @@ import com.google.gerrit.server.CacheRefreshExecutor; import com.google.gerrit.server.cache.CacheModule; import com.google.inject.Inject; +import com.google.inject.Provider; import com.google.inject.ProvisionException; import com.google.inject.Singleton; import com.google.inject.name.Named; import com.google.template.soy.jbcsrc.api.SoySauce; import java.time.Duration; import java.util.concurrent.ExecutionException; -import javax.inject.Provider; /** * Provides support for soy templates
diff --git a/java/com/google/gerrit/server/notedb/DraftCommentsNotesReader.java b/java/com/google/gerrit/server/notedb/DraftCommentsNotesReader.java index 6f48aa0..b7cee28 100644 --- a/java/com/google/gerrit/server/notedb/DraftCommentsNotesReader.java +++ b/java/com/google/gerrit/server/notedb/DraftCommentsNotesReader.java
@@ -27,6 +27,7 @@ import com.google.gerrit.server.config.AllUsersName; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.query.change.ChangeNumberVirtualIdAlgorithm; +import com.google.inject.Inject; import com.google.inject.Singleton; import java.io.IOException; import java.util.ArrayList; @@ -35,7 +36,6 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import javax.inject.Inject; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository;
diff --git a/java/com/google/gerrit/server/patch/DiffOperations.java b/java/com/google/gerrit/server/patch/DiffOperations.java index 9ee4bbe..7712cc4 100644 --- a/java/com/google/gerrit/server/patch/DiffOperations.java +++ b/java/com/google/gerrit/server/patch/DiffOperations.java
@@ -202,4 +202,29 @@ String fileName, @Nullable DiffPreferencesInfo.Whitespace whitespace) throws DiffNotAvailableException; + + /** + * Returns the diff for a single file between two patchset commits with custom diff options. For + * deleted files, the {@code fileName} parameter should contain the old name of the file. This + * method will return {@link FileDiffOutput#empty(String, ObjectId, ObjectId)} if the requested + * file identified by {@code fileName} has unchanged content or does not exist at both commits. + * + * @param project a project name representing a git repository. + * @param oldCommit 20 bytes SHA-1 of the old commit used in the diff. + * @param newCommit 20 bytes SHA-1 of the new commit used in the diff. + * @param fileName the file name for which the diff should be evaluated. + * @param whitespace preference controlling whitespace effect in diff computation. + * @param diffOptions options controlling diff behavior such as rebase filtering. + * @return the diff for the single file between the two commits. + * @throws DiffNotAvailableException if an internal error occurred in Git while evaluating the + * diff. + */ + FileDiffOutput getModifiedFile( + Project.NameKey project, + ObjectId oldCommit, + ObjectId newCommit, + String fileName, + @Nullable DiffPreferencesInfo.Whitespace whitespace, + DiffOptions diffOptions) + throws DiffNotAvailableException; }
diff --git a/java/com/google/gerrit/server/patch/DiffOperationsImpl.java b/java/com/google/gerrit/server/patch/DiffOperationsImpl.java index 85cd030..0e3342f 100644 --- a/java/com/google/gerrit/server/patch/DiffOperationsImpl.java +++ b/java/com/google/gerrit/server/patch/DiffOperationsImpl.java
@@ -33,6 +33,8 @@ import com.google.gerrit.extensions.client.DiffPreferencesInfo; import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace; import com.google.gerrit.server.cache.CacheModule; +import com.google.gerrit.server.experiments.ExperimentFeatures; +import com.google.gerrit.server.experiments.ExperimentFeaturesConstants; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.patch.diff.ModifiedFilesCache; import com.google.gerrit.server.patch.diff.ModifiedFilesCacheImpl; @@ -84,6 +86,7 @@ private final ModifiedFilesLoader.Factory modifiedFilesLoaderFactory; private final FileDiffCache fileDiffCache; private final BaseCommitUtil baseCommitUtil; + private final ExperimentFeatures experimentFeatures; public static Module module() { return new CacheModule() { @@ -99,19 +102,21 @@ } @Inject - public DiffOperationsImpl( + DiffOperationsImpl( GitRepositoryManager repoManager, ModifiedFilesCache modifiedFilesCache, ModifiedFilesCacheImpl modifiedFilesCacheImpl, ModifiedFilesLoader.Factory modifiedFilesLoaderFactory, FileDiffCache fileDiffCache, - BaseCommitUtil baseCommit) { + BaseCommitUtil baseCommit, + ExperimentFeatures experimentFeatures) { this.repoManager = repoManager; this.modifiedFilesCache = modifiedFilesCache; this.modifiedFilesCacheImpl = modifiedFilesCacheImpl; this.modifiedFilesLoaderFactory = modifiedFilesLoaderFactory; this.fileDiffCache = fileDiffCache; this.baseCommitUtil = baseCommit; + this.experimentFeatures = experimentFeatures; } @Override @@ -145,6 +150,80 @@ } } + private ImmutableMap<String, FileDiffOutput> getModifiedFiles( + DiffParameters diffParams, DiffOptions diffOptions) throws DiffNotAvailableException { + logger.atFine().log( + "getModifiedFiles (diffParams: %s, diffOptions: %s)", diffParams, diffOptions); + try { + Project.NameKey project = diffParams.project(); + ObjectId newCommit = diffParams.newCommit(); + ObjectId oldCommit = diffParams.baseCommit(); + ComparisonType cmp = diffParams.comparisonType(); + + ImmutableList<ModifiedFile> modifiedFiles; + if (diffOptions.skipRebaseFiltering()) { + try (Repository repo = repoManager.openRepository(project); + RevWalk revWalk = new RevWalk(repo)) { + ModifiedFilesLoader loader = + modifiedFilesLoaderFactory + .createWithRetrievingModifiedFilesForTreesFromGitModifiedFilesCache() + .withSkipRebaseFiltering(true); + loader.withRenameDetection(RENAME_SCORE); + modifiedFiles = loader.load(project, repo.getConfig(), revWalk, oldCommit, newCommit); + } + } else { + modifiedFiles = + modifiedFilesCache.get(createModifiedFilesKey(project, oldCommit, newCommit)); + } + + boolean useTimeout = + experimentFeatures.isFeatureEnabled( + ExperimentFeaturesConstants.TIMEOUT_FILE_DIFF_COMPUTATION, project); + List<FileDiffCacheKey> fileCacheKeys = new ArrayList<>(); + fileCacheKeys.add( + createFileDiffCacheKey( + project, + oldCommit, + newCommit, + COMMIT_MSG, + DEFAULT_DIFF_ALGORITHM, + useTimeout, + /* whitespace= */ null)); + + if (cmp.isAgainstAutoMerge() || isMergeAgainstParent(cmp, project, newCommit)) { + fileCacheKeys.add( + createFileDiffCacheKey( + project, + oldCommit, + newCommit, + MERGE_LIST, + DEFAULT_DIFF_ALGORITHM, + useTimeout, + /* whitespace= */ null)); + } + + if (diffParams.skipFiles() == null) { + modifiedFiles.stream() + .map( + entity -> + createFileDiffCacheKey( + project, + oldCommit, + newCommit, + entity.newPath().isPresent() + ? entity.newPath().get() + : entity.oldPath().get(), + DEFAULT_DIFF_ALGORITHM, + useTimeout, + /* whitespace= */ null)) + .forEach(fileCacheKeys::add); + } + return getModifiedFilesForKeys(fileCacheKeys, diffOptions); + } catch (IOException e) { + throw new DiffNotAvailableException(e); + } + } + @Override public Map<String, FileDiffOutput> listModifiedFilesAgainstParent( Project.NameKey project, ObjectId newCommit, int parent, DiffOptions diffOptions) @@ -247,9 +326,10 @@ newCommit, fileName, DEFAULT_DIFF_ALGORITHM, - /* useTimeout= */ true, + experimentFeatures.isFeatureEnabled( + ExperimentFeaturesConstants.TIMEOUT_FILE_DIFF_COMPUTATION, project), whitespace); - return getModifiedFileForKey(key); + return getModifiedFileForKey(key, DiffOptions.DEFAULTS); } catch (IOException e) { throw new DiffNotAvailableException( "Failed to evaluate the parent/base commit for commit " + newCommit, e); @@ -264,6 +344,19 @@ String fileName, @Nullable DiffPreferencesInfo.Whitespace whitespace) throws DiffNotAvailableException { + return getModifiedFile( + project, oldCommit, newCommit, fileName, whitespace, DiffOptions.DEFAULTS); + } + + @Override + public FileDiffOutput getModifiedFile( + Project.NameKey project, + ObjectId oldCommit, + ObjectId newCommit, + String fileName, + @Nullable DiffPreferencesInfo.Whitespace whitespace, + DiffOptions diffOptions) + throws DiffNotAvailableException { FileDiffCacheKey key = createFileDiffCacheKey( project, @@ -271,73 +364,16 @@ newCommit, fileName, DEFAULT_DIFF_ALGORITHM, - /* useTimeout= */ true, + experimentFeatures.isFeatureEnabled( + ExperimentFeaturesConstants.TIMEOUT_FILE_DIFF_COMPUTATION, project), whitespace); - return getModifiedFileForKey(key); + return getModifiedFileForKey(key, diffOptions); } - private ImmutableMap<String, FileDiffOutput> getModifiedFiles( - DiffParameters diffParams, DiffOptions diffOptions) throws DiffNotAvailableException { - logger.atFine().log( - "getModifiedFiles (diffParams: %s, diffOptions: %s)", diffParams, diffOptions); - try { - Project.NameKey project = diffParams.project(); - ObjectId newCommit = diffParams.newCommit(); - ObjectId oldCommit = diffParams.baseCommit(); - ComparisonType cmp = diffParams.comparisonType(); - - ImmutableList<ModifiedFile> modifiedFiles = - modifiedFilesCache.get(createModifiedFilesKey(project, oldCommit, newCommit)); - - List<FileDiffCacheKey> fileCacheKeys = new ArrayList<>(); - fileCacheKeys.add( - createFileDiffCacheKey( - project, - oldCommit, - newCommit, - COMMIT_MSG, - DEFAULT_DIFF_ALGORITHM, - /* useTimeout= */ true, - /* whitespace= */ null)); - - if (cmp.isAgainstAutoMerge() || isMergeAgainstParent(cmp, project, newCommit)) { - fileCacheKeys.add( - createFileDiffCacheKey( - project, - oldCommit, - newCommit, - MERGE_LIST, - DEFAULT_DIFF_ALGORITHM, - /* useTimeout= */ true, - /* whitespace= */ null)); - } - - if (diffParams.skipFiles() == null) { - modifiedFiles.stream() - .map( - entity -> - createFileDiffCacheKey( - project, - oldCommit, - newCommit, - entity.newPath().isPresent() - ? entity.newPath().get() - : entity.oldPath().get(), - DEFAULT_DIFF_ALGORITHM, - /* useTimeout= */ true, - /* whitespace= */ null)) - .forEach(fileCacheKeys::add); - } - return getModifiedFilesForKeys(fileCacheKeys, diffOptions); - } catch (IOException e) { - throw new DiffNotAvailableException(e); - } - } - - private FileDiffOutput getModifiedFileForKey(FileDiffCacheKey key) + private FileDiffOutput getModifiedFileForKey(FileDiffCacheKey key, DiffOptions diffOptions) throws DiffNotAvailableException { ImmutableMap<String, FileDiffOutput> diffList = - getModifiedFilesForKeys(ImmutableList.of(key), DiffOptions.DEFAULTS); + getModifiedFilesForKeys(ImmutableList.of(key), diffOptions); return diffList.containsKey(key.newFilePath()) ? diffList.get(key.newFilePath()) : FileDiffOutput.empty(key.newFilePath(), key.oldCommit(), key.newCommit()); @@ -367,10 +403,9 @@ key.newFilePath(), // Use the fallback diff algorithm DiffAlgorithm.HISTOGRAM_NO_FALLBACK, - // We don't enforce timeouts with the fallback algorithm. Timeouts were introduced - // because of a bug in JGit that happens only when the histogram algorithm uses - // Myers as fallback. See https://issues.gerritcodereview.com/issues/40000618 - /* useTimeout= */ false, + // Enforce a timeout even when falling back + experimentFeatures.isFeatureEnabled( + ExperimentFeaturesConstants.TIMEOUT_FILE_DIFF_COMPUTATION, key.project()), key.whitespace()); logger.atFine().log( "fallback to computing git file diff for %s with %s as diff algorithm and no timeout", @@ -393,7 +428,7 @@ ImmutableMap.Builder<String, FileDiffOutput> diffs = ImmutableMap.builder(); for (FileDiffOutput fileDiffOutput : fileDiffOutputs) { - if (fileDiffOutput.isEmpty() + if ((fileDiffOutput.isEmpty() && !fileDiffOutput.isNegative()) || (diffOptions.skipFilesWithAllEditsDueToRebase() && allDueToRebase(fileDiffOutput))) { continue; } @@ -403,7 +438,7 @@ diffs.put(fileDiffOutput.newPath().get(), fileDiffOutput); } } - return diffs.build(); + return diffs.buildOrThrow(); } private static boolean allDueToRebase(FileDiffOutput fileDiffOutput) {
diff --git a/java/com/google/gerrit/server/patch/DiffOptions.java b/java/com/google/gerrit/server/patch/DiffOptions.java index 4d54be1..7927b10 100644 --- a/java/com/google/gerrit/server/patch/DiffOptions.java +++ b/java/com/google/gerrit/server/patch/DiffOptions.java
@@ -19,18 +19,32 @@ @AutoValue public abstract class DiffOptions { public static final DiffOptions DEFAULTS = - DiffOptions.builder().skipFilesWithAllEditsDueToRebase(true).build(); + DiffOptions.builder() + .skipFilesWithAllEditsDueToRebase(true) + .skipRebaseFiltering(false) + .build(); public abstract boolean skipFilesWithAllEditsDueToRebase(); + /** + * Whether to skip the rebase-filtering algorithm in ModifiedFilesLoader. + * + * <p>If true, the full list of files changed between the two commits will be returned, even if + * they are not parent-child or do not share a common parent (e.g. general repository-level + * diffs). + */ + public abstract boolean skipRebaseFiltering(); + public static DiffOptions.Builder builder() { - return new AutoValue_DiffOptions.Builder(); + return new AutoValue_DiffOptions.Builder().skipRebaseFiltering(false); } @AutoValue.Builder public abstract static class Builder { public abstract Builder skipFilesWithAllEditsDueToRebase(boolean value); + public abstract Builder skipRebaseFiltering(boolean value); + public abstract DiffOptions build(); } }
diff --git a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java index 1e7e0e8..1de12d7 100644 --- a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java +++ b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -53,18 +53,18 @@ import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; -class PatchScriptBuilder { +public class PatchScriptBuilder { private DiffPreferencesInfo diffPrefs; private final FileTypeRegistry registry; private IntraLineDiffCalculator intralineDiffCalculator; @Inject - PatchScriptBuilder(FileTypeRegistry ftr) { + public PatchScriptBuilder(FileTypeRegistry ftr) { registry = ftr; } - void setDiffPrefs(DiffPreferencesInfo dp) { + public void setDiffPrefs(DiffPreferencesInfo dp) { diffPrefs = dp; } @@ -73,7 +73,7 @@ } /** Convert into {@link PatchScript} using the new diff cache output. */ - PatchScript toPatchScript(Repository git, FileDiffOutput content) throws IOException { + public PatchScript toPatchScript(Repository git, FileDiffOutput content) throws IOException { PatchFileChange change = new PatchFileChange( content.edits().stream().map(TaggedEdit::jgitEdit).collect(toImmutableList()),
diff --git a/java/com/google/gerrit/server/patch/diff/ModifiedFilesLoader.java b/java/com/google/gerrit/server/patch/diff/ModifiedFilesLoader.java index 30be303..1091d54 100644 --- a/java/com/google/gerrit/server/patch/diff/ModifiedFilesLoader.java +++ b/java/com/google/gerrit/server/patch/diff/ModifiedFilesLoader.java
@@ -98,7 +98,8 @@ * trees that are newly created or that were created in memory only. Also see the javadoc on * this class. */ - ModifiedFilesLoader createWithRetrievingModifiedFilesForTreesFromGitModifiedFilesCache() { + public ModifiedFilesLoader + createWithRetrievingModifiedFilesForTreesFromGitModifiedFilesCache() { return new ModifiedFilesLoader(gitModifiedFilesCache); } } @@ -107,6 +108,8 @@ @Nullable private Integer renameScore = null; + private boolean skipRebaseFiltering = false; + ModifiedFilesLoader(@Nullable GitModifiedFilesCache gitModifiedFilesCache) { this.gitModifiedFilesCache = gitModifiedFilesCache; } @@ -123,6 +126,12 @@ return this; } + @CanIgnoreReturnValue + public ModifiedFilesLoader withSkipRebaseFiltering(boolean skipRebaseFiltering) { + this.skipRebaseFiltering = skipRebaseFiltering; + return this; + } + /** * Loads the files that have been modified between {@code baseCommit} and {@code newCommit}. * @@ -155,6 +164,9 @@ if (baseCommit.equals(ObjectId.zeroId())) { return modifiedFiles; } + if (skipRebaseFiltering) { + return modifiedFiles; + } RevCommit revCommitBase = DiffUtil.getRevCommit(revWalk, baseCommit); RevCommit revCommitNew = DiffUtil.getRevCommit(revWalk, newCommit); if (DiffUtil.areRelated(revCommitBase, revCommitNew)) {
diff --git a/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java b/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java index d417fe3..2c50d10 100644 --- a/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java +++ b/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
@@ -30,12 +30,15 @@ import com.google.gerrit.entities.Project; import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace; import com.google.gerrit.server.cache.CacheModule; +import com.google.gerrit.server.experiments.ExperimentFeatures; +import com.google.gerrit.server.experiments.ExperimentFeaturesConstants; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.logging.Metadata; import com.google.gerrit.server.logging.TraceContext; import com.google.gerrit.server.logging.TraceContext.TraceTimer; import com.google.gerrit.server.patch.AutoMerger; import com.google.gerrit.server.patch.ComparisonType; +import com.google.gerrit.server.patch.DiffExecutor; import com.google.gerrit.server.patch.DiffNotAvailableException; import com.google.gerrit.server.patch.DiffUtil; import com.google.gerrit.server.patch.Text; @@ -58,6 +61,10 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; import org.eclipse.jgit.diff.EditList; import org.eclipse.jgit.diff.RawText; @@ -146,12 +153,19 @@ static class FileDiffLoader extends CacheLoader<FileDiffCacheKey, FileDiffOutput> { private final GitRepositoryManager repoManager; private final AllDiffsEvaluator.Factory allDiffsEvaluatorFactory; + private final ExecutorService diffExecutor; + private final ExperimentFeatures experimentFeatures; @Inject FileDiffLoader( - AllDiffsEvaluator.Factory allDiffsEvaluatorFactory, GitRepositoryManager manager) { + AllDiffsEvaluator.Factory allDiffsEvaluatorFactory, + GitRepositoryManager manager, + @DiffExecutor ExecutorService diffExecutor, + ExperimentFeatures experimentFeatures) { this.allDiffsEvaluatorFactory = allDiffsEvaluatorFactory; this.repoManager = manager; + this.diffExecutor = diffExecutor; + this.experimentFeatures = experimentFeatures; } @Override @@ -191,7 +205,7 @@ fileKeys.add(key); } } - result.putAll(createFileEntries(reader, fileKeys, rw)); + result.putAll(createFileEntries(project, reader, fileKeys, rw)); } catch (IOException e) { logger.atWarning().log("Failed to open the repository %s: %s", project, e.getMessage()); } @@ -390,13 +404,53 @@ } private Map<FileDiffCacheKey, FileDiffOutput> createFileEntries( - ObjectReader reader, List<FileDiffCacheKey> keys, RevWalk rw) + Project.NameKey project, ObjectReader reader, List<FileDiffCacheKey> keys, RevWalk rw) throws DiffNotAvailableException, IOException { - Map<AugmentedFileDiffCacheKey, AllFileGitDiffs> allFileDiffs = - allDiffsEvaluatorFactory.create(rw).execute(wrapKeys(keys, rw)); - + Map<AugmentedFileDiffCacheKey, AllFileGitDiffs> allFileDiffs = new HashMap<>(); Map<FileDiffCacheKey, FileDiffOutput> result = new HashMap<>(); + if (experimentFeatures.isFeatureEnabled( + ExperimentFeaturesConstants.TIMEOUT_FILE_DIFF_COMPUTATION, project)) { + Map<FileDiffCacheKey, Future<Map<AugmentedFileDiffCacheKey, AllFileGitDiffs>>> futures = + new HashMap<>(); + for (FileDiffCacheKey key : keys) { + List<AugmentedFileDiffCacheKey> augmentedKeys = wrapKeys(ImmutableList.of(key), rw); + futures.put( + key, + diffExecutor.submit( + () -> { + try (Repository repo = repoManager.openRepository(project); + ObjectReader innerReader = repo.newObjectReader(); + RevWalk innerRw = new RevWalk(innerReader)) { + return allDiffsEvaluatorFactory.create(innerRw).execute(augmentedKeys); + } + })); + } + + for (FileDiffCacheKey key : keys) { + try { + allFileDiffs.putAll(futures.get(key).get(1, TimeUnit.MINUTES)); + } catch (InterruptedException | TimeoutException e) { + logger.atWarning().withCause(e).log( + "Timeout reached while computing diff for key: %s", key); + result.put( + key, + FileDiffOutput.createExpensive( + key.newFilePath(), key.oldCommit(), key.newCommit())); + } catch (ExecutionException e) { + if (e.getCause() instanceof DiffNotAvailableException) { + throw (DiffNotAvailableException) e.getCause(); + } + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } + throw new DiffNotAvailableException(e); + } + } + } else { + allFileDiffs.putAll(allDiffsEvaluatorFactory.create(rw).execute(wrapKeys(keys, rw))); + } + for (AugmentedFileDiffCacheKey augmentedKey : allFileDiffs.keySet()) { AllFileGitDiffs allDiffs = allFileDiffs.get(augmentedKey); GitFileDiff mainGitDiff = allDiffs.mainDiff().gitDiff(); @@ -406,7 +460,7 @@ // negative result. result.put( augmentedKey.key(), - FileDiffOutput.createNegative( + FileDiffOutput.createExpensive( mainGitDiff.newPath().orElse(""), augmentedKey.key().oldCommit(), augmentedKey.key().newCommit()));
diff --git a/java/com/google/gerrit/server/patch/filediff/FileDiffOutput.java b/java/com/google/gerrit/server/patch/filediff/FileDiffOutput.java index dd6b8d9..3c24894 100644 --- a/java/com/google/gerrit/server/patch/filediff/FileDiffOutput.java +++ b/java/com/google/gerrit/server/patch/filediff/FileDiffOutput.java
@@ -121,6 +121,12 @@ */ public abstract Optional<Boolean> negative(); + /** + * Returns {@code true} if the diff computation was not able to compute a diff, i.e. for diffs + * taking a very long time to compute. + */ + public abstract Optional<Boolean> diffsTooExpensiveToCompute(); + public abstract Builder toBuilder(); /** A boolean indicating if all underlying edits of the file diff are due to rebase. */ @@ -177,6 +183,17 @@ .build(); } + /** + * Create an expensive file diff. We use this to cache diffs for entries that result in timeouts. + */ + public static FileDiffOutput createExpensive( + String filePath, ObjectId oldCommitId, ObjectId newCommitId) { + return empty(filePath, oldCommitId, newCommitId).toBuilder() + .negative(Optional.of(true)) + .diffsTooExpensiveToCompute(Optional.of(true)) + .build(); + } + /** Returns true if this entity represents an unchanged file between two commits. */ public boolean isEmpty() { return headerLines().isEmpty() && edits().isEmpty(); @@ -190,6 +207,14 @@ return negative().isPresent() && negative().get(); } + /** + * Returns {@code true} if the diff computation was not able to compute a diff. We cache + * diffsTooExpensiveToCompute result in this case. + */ + public boolean isTooExpensive() { + return diffsTooExpensiveToCompute().isPresent() && diffsTooExpensiveToCompute().get(); + } + public static Builder builder() { return new AutoValue_FileDiffOutput.Builder(); } @@ -217,6 +242,9 @@ if (negative().isPresent()) { result += 1; } + if (diffsTooExpensiveToCompute().isPresent()) { + result += 1; + } return result; } @@ -255,6 +283,12 @@ public abstract Builder negative(Optional<Boolean> value); + /** + * Returns {@code true} if the diff computation was not able to compute a diff, i.e. for diffs + * taking a very long time to compute. + */ + public abstract Builder diffsTooExpensiveToCompute(Optional<Boolean> value); + public abstract FileDiffOutput build(); } @@ -288,6 +322,9 @@ private static final FieldDescriptor NEW_SHA_DESCRIPTOR = FileDiffOutputProto.getDescriptor().findFieldByNumber(16); + private static final FieldDescriptor TOO_EXPENSIVE_DESCRIPTOR = + FileDiffOutputProto.getDescriptor().findFieldByNumber(17); + @Override public byte[] serialize(FileDiffOutput fileDiff) { ObjectIdConverter idConverter = ObjectIdConverter.create(); @@ -332,6 +369,10 @@ builder.setNegative(fileDiff.negative().get()); } + if (fileDiff.diffsTooExpensiveToCompute().isPresent()) { + builder.setDiffsTooExpensiveToCompute(fileDiff.diffsTooExpensiveToCompute().get()); + } + if (fileDiff.oldMode().isPresent()) { builder.setOldMode(FILE_MODE_CONVERTER.reverse().convert(fileDiff.oldMode().get())); } @@ -388,6 +429,9 @@ if (proto.hasField(NEGATIVE_DESCRIPTOR)) { builder.negative(Optional.of(proto.getNegative())); } + if (proto.hasField(TOO_EXPENSIVE_DESCRIPTOR)) { + builder.diffsTooExpensiveToCompute(Optional.of(proto.getDiffsTooExpensiveToCompute())); + } if (proto.hasField(OLD_MODE_DESCRIPTOR)) { builder.oldMode(Optional.of(FILE_MODE_CONVERTER.convert(proto.getOldMode()))); }
diff --git a/java/com/google/gerrit/server/permissions/ChangeControl.java b/java/com/google/gerrit/server/permissions/ChangeControl.java index 2d2ec18..3941975 100644 --- a/java/com/google/gerrit/server/permissions/ChangeControl.java +++ b/java/com/google/gerrit/server/permissions/ChangeControl.java
@@ -21,8 +21,8 @@ import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.permissions.PermissionBackend.ForChange; import com.google.gerrit.server.query.change.ChangeData; +import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; -import javax.inject.Inject; /** Access control management for a user accessing a single change. */ public class ChangeControl extends AbstractChangeControl {
diff --git a/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java b/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java index 0113355..2217d81 100644 --- a/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java +++ b/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java
@@ -195,17 +195,17 @@ case FLUSH_CACHES, KILL_TASK, RUN_GC, VIEW_CACHES, VIEW_QUEUE -> has(globalPermissionName(perm)) || can(GlobalPermission.MAINTAIN_SERVER); case CREATE_ACCOUNT, - CREATE_GROUP, - DELETE_GROUP, - CREATE_PROJECT, - MAINTAIN_SERVER, - MODIFY_ACCOUNT, - READ_AS, - STREAM_EVENTS, - VIEW_ACCESS, - VIEW_ALL_ACCOUNTS, - VIEW_CONNECTIONS, - VIEW_PLUGINS -> + CREATE_GROUP, + DELETE_GROUP, + CREATE_PROJECT, + MAINTAIN_SERVER, + MODIFY_ACCOUNT, + READ_AS, + STREAM_EVENTS, + VIEW_ACCESS, + VIEW_ALL_ACCOUNTS, + VIEW_CONNECTIONS, + VIEW_PLUGINS -> has(globalPermissionName(perm)) || isAdmin(); case VIEW_SECONDARY_EMAILS -> has(globalPermissionName(perm))
diff --git a/java/com/google/gerrit/server/permissions/RefVisibilityControl.java b/java/com/google/gerrit/server/permissions/RefVisibilityControl.java index 756e922..665f995 100644 --- a/java/com/google/gerrit/server/permissions/RefVisibilityControl.java +++ b/java/com/google/gerrit/server/permissions/RefVisibilityControl.java
@@ -29,8 +29,8 @@ import com.google.gerrit.server.account.GroupControl; import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.query.change.ChangeData; -import javax.inject.Inject; -import javax.inject.Singleton; +import com.google.inject.Inject; +import com.google.inject.Singleton; import org.eclipse.jgit.lib.Constants; /**
diff --git a/java/com/google/gerrit/server/plugins/AutoRegisterUtil.java b/java/com/google/gerrit/server/plugins/AutoRegisterUtil.java index d592d17..20a032d 100644 --- a/java/com/google/gerrit/server/plugins/AutoRegisterUtil.java +++ b/java/com/google/gerrit/server/plugins/AutoRegisterUtil.java
@@ -23,7 +23,7 @@ public static Annotation calculateBindAnnotation(Class<Object> impl) { Annotation n = impl.getAnnotation(Export.class); if (n == null) { - n = impl.getAnnotation(javax.inject.Named.class); + n = impl.getAnnotation(jakarta.inject.Named.class); } if (n == null) { n = impl.getAnnotation(com.google.inject.name.Named.class);
diff --git a/java/com/google/gerrit/server/project/SubmitRequirementExecutor.java b/java/com/google/gerrit/server/project/SubmitRequirementExecutor.java new file mode 100644 index 0000000..eee357f --- /dev/null +++ b/java/com/google/gerrit/server/project/SubmitRequirementExecutor.java
@@ -0,0 +1,24 @@ +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.project; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.inject.BindingAnnotation; +import java.lang.annotation.Retention; + +@BindingAnnotation +@Retention(RUNTIME) +public @interface SubmitRequirementExecutor {}
diff --git a/java/com/google/gerrit/server/project/SubmitRequirementsEvaluatorImpl.java b/java/com/google/gerrit/server/project/SubmitRequirementsEvaluatorImpl.java index 2b7a3c1..d0943c7 100644 --- a/java/com/google/gerrit/server/project/SubmitRequirementsEvaluatorImpl.java +++ b/java/com/google/gerrit/server/project/SubmitRequirementsEvaluatorImpl.java
@@ -18,8 +18,10 @@ import static com.google.gerrit.server.project.ProjectCache.illegalState; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.flogger.FluentLogger; +import com.google.common.util.concurrent.MoreExecutors; import com.google.gerrit.entities.PredicateResult; import com.google.gerrit.entities.SubmitRequirement; import com.google.gerrit.entities.SubmitRequirementExpression; @@ -29,6 +31,7 @@ import com.google.gerrit.index.query.Predicate; import com.google.gerrit.index.query.QueryParseException; import com.google.gerrit.server.config.GerritServerConfig; +import com.google.gerrit.server.git.WorkQueue; import com.google.gerrit.server.logging.Metadata; import com.google.gerrit.server.logging.TraceContext; import com.google.gerrit.server.logging.TraceContext.TraceTimer; @@ -39,10 +42,18 @@ import com.google.gerrit.server.util.OneOffRequestContext; import com.google.inject.Inject; import com.google.inject.Module; +import com.google.inject.Provides; import com.google.inject.Scopes; +import com.google.inject.Singleton; import java.util.Locale; import java.util.Map; import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Function; import java.util.stream.Stream; import org.eclipse.jgit.lib.Config; @@ -57,10 +68,12 @@ private final Config config; private final boolean requireOperatorForUpdate; private final boolean requireOperatorForEvaluation; + private final ExecutorService executor; // Use a request context to execute predicates as an internal user with expanded visibility. // This is so that the evaluation does not depend on who is running the current request (e.g. // a "ownerin" predicate with group that is not visible to the person making this request). private final OneOffRequestContext requestContext; + private final long executionTimeout; public static Module module() { return new FactoryModule() { @@ -72,16 +85,36 @@ factory(SubmitRequirementChangeQueryBuilder.Factory.class); } + + @Provides + @Singleton + @SubmitRequirementExecutor + ExecutorService provideSubmitRequirementExecutor( + WorkQueue workQueue, @GerritServerConfig Config config) { + int evaluationThreads = config.getInt("submitRequirement", null, "evaluationThreads", -1); + + if (evaluationThreads < 0) { + return MoreExecutors.newDirectExecutorService(); + } + + if (evaluationThreads == 0) { + evaluationThreads = Runtime.getRuntime().availableProcessors(); + } + + return MoreExecutors.listeningDecorator( + workQueue.createQueue(evaluationThreads, "submit-requirement-evaluator")); + } }; } @Inject - private SubmitRequirementsEvaluatorImpl( + public SubmitRequirementsEvaluatorImpl( SubmitRequirementChangeQueryBuilder.Factory queryBuilderFactory, ProjectCache projectCache, PluginSetContext<SubmitRequirement> globalSubmitRequirements, @GerritServerConfig Config config, - OneOffRequestContext requestContext) { + OneOffRequestContext requestContext, + @SubmitRequirementExecutor ExecutorService executor) { this.queryBuilderFactory = queryBuilderFactory; this.projectCache = projectCache; this.globalSubmitRequirements = globalSubmitRequirements; @@ -89,6 +122,9 @@ this.requestContext = requestContext; this.requireOperatorForUpdate = requireOperatorForUpdate(); this.requireOperatorForEvaluation = requireOperatorForEvaluation(); + this.executor = executor; + this.executionTimeout = + config.getTimeUnit("submitRequirement", null, "executionTimeout", 0, TimeUnit.MILLISECONDS); } @Override @@ -139,7 +175,9 @@ .parse(expression.expressionString()); PredicateResult predicateResult = changeData.evaluatePredicateTree(predicate); return SubmitRequirementExpressionResult.create(expression, predicateResult); - } catch (QueryParseException | SubmitRequirementEvaluationException e) { + } catch (QueryParseException + | SubmitRequirementEvaluationException + | IllegalArgumentException e) { logger.atWarning().withCause(e).log( "Failed to evaluate submit requirement expression: %s", expression.expressionString()); return SubmitRequirementExpressionResult.error(expression, e.getMessage()); @@ -151,61 +189,130 @@ TraceContext.newTimer( "Evaluate submit requirement " + sr.name(), Metadata.builder().changeId(cd.change().getId().get()).build())) { - Optional<SubmitRequirementExpressionResult> applicabilityResult = - sr.applicabilityExpression().isPresent() - ? Optional.of(evaluateExpression(sr.applicabilityExpression().get(), cd)) - : Optional.empty(); - Optional<SubmitRequirementExpressionResult> submittabilityResult = - Optional.of( - SubmitRequirementExpressionResult.notEvaluated(sr.submittabilityExpression())); - Optional<SubmitRequirementExpressionResult> overrideResult = - sr.overrideExpression().isPresent() - ? Optional.of( - SubmitRequirementExpressionResult.notEvaluated(sr.overrideExpression().get())) - : Optional.empty(); - if (!sr.applicabilityExpression().isPresent() - || SubmitRequirementResult.assertPass(applicabilityResult)) { - submittabilityResult = Optional.of(evaluateExpression(sr.submittabilityExpression(), cd)); - overrideResult = - sr.overrideExpression().isPresent() - ? Optional.of(evaluateExpression(sr.overrideExpression().get(), cd)) - : Optional.empty(); - } + Callable<SubmitRequirementResult> task = + () -> { + try (ManualRequestContext ctx = requestContext.open()) { + Optional<SubmitRequirementExpressionResult> applicabilityResult = + sr.applicabilityExpression().isPresent() + ? Optional.of(evaluateExpression(sr.applicabilityExpression().get(), cd)) + : Optional.empty(); - if (applicabilityResult.isPresent()) { - logger.atFine().log( - "Applicability expression result for SR name '%s':" - + " passing atoms: %s, failing atoms: %s", - sr.name(), - applicabilityResult.get().passingAtoms(), - applicabilityResult.get().failingAtoms()); - } - if (submittabilityResult.isPresent()) { - logger.atFine().log( - "Submittability expression result for SR name '%s':" - + " passing atoms: %s, failing atoms: %s", - sr.name(), - submittabilityResult.get().passingAtoms(), - submittabilityResult.get().failingAtoms()); - } - if (overrideResult.isPresent()) { - logger.atFine().log( - "Override expression result for SR name '%s':" - + " passing atoms: %s, failing atoms: %s", - sr.name(), overrideResult.get().passingAtoms(), overrideResult.get().failingAtoms()); - } + Optional<SubmitRequirementExpressionResult> submittabilityResult = + Optional.of( + SubmitRequirementExpressionResult.notEvaluated( + sr.submittabilityExpression())); - return SubmitRequirementResult.builder() - .legacy(Optional.of(false)) - .submitRequirement(sr) - .patchSetCommitId(cd.currentPatchSet().commitId()) - .submittabilityExpressionResult(submittabilityResult) - .applicabilityExpressionResult(applicabilityResult) - .overrideExpressionResult(overrideResult) - .build(); + Optional<SubmitRequirementExpressionResult> overrideResult = + sr.overrideExpression().isPresent() + ? Optional.of( + SubmitRequirementExpressionResult.notEvaluated( + sr.overrideExpression().get())) + : Optional.empty(); + + if (!sr.applicabilityExpression().isPresent() + || SubmitRequirementResult.assertPass(applicabilityResult)) { + submittabilityResult = + Optional.of(evaluateExpression(sr.submittabilityExpression(), cd)); + overrideResult = + sr.overrideExpression().isPresent() + ? Optional.of(evaluateExpression(sr.overrideExpression().get(), cd)) + : Optional.empty(); + } + + if (applicabilityResult.isPresent()) { + logger.atFine().log( + "Applicability expression result for SR name '%s':" + + " passing atoms: %s, failing atoms: %s", + sr.name(), + applicabilityResult.get().passingAtoms(), + applicabilityResult.get().failingAtoms()); + } + if (submittabilityResult.isPresent()) { + logger.atFine().log( + "Submittability expression result for SR name '%s':" + + " passing atoms: %s, failing atoms: %s", + sr.name(), + submittabilityResult.get().passingAtoms(), + submittabilityResult.get().failingAtoms()); + } + if (overrideResult.isPresent()) { + logger.atFine().log( + "Override expression result for SR name '%s':" + + " passing atoms: %s, failing atoms: %s", + sr.name(), + overrideResult.get().passingAtoms(), + overrideResult.get().failingAtoms()); + } + + return SubmitRequirementResult.builder() + .legacy(Optional.of(false)) + .submitRequirement(sr) + .patchSetCommitId(cd.currentPatchSet().commitId()) + .submittabilityExpressionResult(submittabilityResult) + .applicabilityExpressionResult(applicabilityResult) + .overrideExpressionResult(overrideResult) + .build(); + } + }; + Future<SubmitRequirementResult> future = executor.submit(task); + + try { + return future.get(executionTimeout, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + future.cancel(true); + logger.atWarning().log("Submit requirement '%s' evaluation timed out", sr.name()); + + return timeoutResult(sr, cd); + } catch (ExecutionException | InterruptedException e) { + logger.atSevere().withCause(e).log("Error evaluating Submit requirement: %s", sr.name()); + return errorResult(sr, cd, e); + } } } + private SubmitRequirementResult timeoutResult(SubmitRequirement sr, ChangeData cd) { + SubmitRequirementExpressionResult timeout = + SubmitRequirementExpressionResult.create( + sr.submittabilityExpression(), + SubmitRequirementExpressionResult.Status.TIMEOUT, + ImmutableList.of(), + ImmutableList.of("Execution timeout exceeded")); + + return SubmitRequirementResult.builder() + .legacy(Optional.of(false)) + .submitRequirement(sr) + .patchSetCommitId(cd.currentPatchSet().commitId()) + .submittabilityExpressionResult(Optional.of(timeout)) + .applicabilityExpressionResult( + sr.applicabilityExpression().map(SubmitRequirementExpressionResult::notEvaluated)) + .overrideExpressionResult( + sr.overrideExpression().map(SubmitRequirementExpressionResult::notEvaluated)) + .build(); + } + + private SubmitRequirementResult errorResult(SubmitRequirement sr, ChangeData cd, Throwable e) { + String msg = + (e instanceof ExecutionException && e.getCause() != null) + ? e.getCause().getMessage() + : e.getMessage(); + if (msg == null) { + msg = e.toString(); + } + SubmitRequirementExpressionResult error = + SubmitRequirementExpressionResult.error(sr.submittabilityExpression(), msg); + + return SubmitRequirementResult.builder() + .legacy(Optional.of(false)) + .submitRequirement(sr) + .patchSetCommitId(cd.currentPatchSet().commitId()) + .submittabilityExpressionResult(Optional.of(error)) + .applicabilityExpressionResult( + sr.applicabilityExpression().map(SubmitRequirementExpressionResult::notEvaluated)) + .overrideExpressionResult( + sr.overrideExpression().map(SubmitRequirementExpressionResult::notEvaluated)) + .build(); + } + /** * Evaluate and return all {@link SubmitRequirement}s. *
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java index 9dc5d4c..a3b1e3e 100644 --- a/java/com/google/gerrit/server/query/change/ChangeData.java +++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -37,7 +37,6 @@ import com.google.common.collect.SetMultimap; import com.google.common.collect.Table; import com.google.common.flogger.FluentLogger; -import com.google.common.primitives.Ints; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.gerrit.common.Nullable; import com.google.gerrit.common.UsedAt; @@ -812,12 +811,10 @@ } if (refStates != null) { ImmutableSet<RefState> refs = refStates.get(project); - if (refs != null) { - String metaRef = RefNames.changeMetaRef(getId()); - for (RefState r : refs) { - if (r.ref().equals(metaRef)) { - return Optional.of(r.id()); - } + String metaRef = RefNames.changeMetaRef(getId()); + for (RefState r : refs) { + if (r.ref().equals(metaRef)) { + return Optional.of(r.id()); } } } @@ -1279,9 +1276,7 @@ if (!lazyload()) { return null; } - - // Fail on overflow. - totalCommentCount = Ints.checkedCast((long) publishedComments().size()); + totalCommentCount = publishedComments().size(); } return totalCommentCount; }
diff --git a/java/com/google/gerrit/server/restapi/account/QueryAccounts.java b/java/com/google/gerrit/server/restapi/account/QueryAccounts.java index 8966ec4..f0476d1 100644 --- a/java/com/google/gerrit/server/restapi/account/QueryAccounts.java +++ b/java/com/google/gerrit/server/restapi/account/QueryAccounts.java
@@ -30,6 +30,11 @@ import com.google.gerrit.index.query.Predicate; import com.google.gerrit.index.query.QueryParseException; import com.google.gerrit.index.query.QueryResult; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Description.Units; +import com.google.gerrit.metrics.MetricMaker; +import com.google.gerrit.metrics.Timer0; +import com.google.gerrit.server.account.AccountControl; import com.google.gerrit.server.account.AccountDirectory.FillOptions; import com.google.gerrit.server.account.AccountInfoComparator; import com.google.gerrit.server.account.AccountLoader; @@ -43,6 +48,7 @@ import com.google.gerrit.server.query.account.AccountQueryProcessor; import com.google.inject.Inject; import com.google.inject.Provider; +import com.google.inject.Singleton; import java.util.Collections; import java.util.EnumSet; import java.util.LinkedHashMap; @@ -69,6 +75,8 @@ private final Provider<AccountQueryProcessor> queryProcessorProvider; private final boolean suggestConfig; private final int suggestFrom; + private final AccountControl.Factory accountControlFactory; + private final Metrics metrics; private AccountLoader accountLoader; private boolean suggest; @@ -134,11 +142,15 @@ AccountLoader.Factory accountLoaderFactory, AccountQueryBuilder queryBuilder, Provider<AccountQueryProcessor> queryProcessorProvider, - @GerritServerConfig Config cfg) { + @GerritServerConfig Config cfg, + AccountControl.Factory accountControlFactory, + Metrics metrics) { this.permissionBackend = permissionBackend; this.accountLoaderFactory = accountLoaderFactory; this.queryBuilder = queryBuilder; this.queryProcessorProvider = queryProcessorProvider; + this.accountControlFactory = accountControlFactory; + this.metrics = metrics; this.suggestFrom = cfg.getInt("suggest", null, "from", 0); this.options = EnumSet.noneOf(ListAccountsOption.class); @@ -220,10 +232,15 @@ // active accounts should be queried queryPred = AccountPredicates.andActive(queryPred); } + AccountControl accountControl = accountControlFactory.get(); QueryResult<AccountState> result = queryProcessor.query(queryPred); - for (AccountState accountState : result.entities()) { - Account.Id id = accountState.account().id(); - matches.put(id, accountLoader.get(id)); + try (Timer0.Context ignored = metrics.filterVisibilityLatency.start()) { + for (AccountState accountState : result.entities()) { + Account.Id id = accountState.account().id(); + if (accountControl.canSee(accountState)) { + matches.put(id, accountLoader.get(id)); + } + } } accountLoader.fill(); @@ -241,4 +258,19 @@ throw new BadRequestException(e.getMessage(), e); } } + + @Singleton + private static class Metrics { + final Timer0 filterVisibilityLatency; + + @Inject + Metrics(MetricMaker metricMaker) { + filterVisibilityLatency = + metricMaker.newTimer( + "accounts/query/filter_visibility", + new Description("Latency for removing accounts that are not visible to the caller") + .setCumulative() + .setUnit(Units.MILLISECONDS)); + } + } }
diff --git a/java/com/google/gerrit/server/restapi/change/CommentJson.java b/java/com/google/gerrit/server/restapi/change/CommentJson.java index aa9b363..d8be4c0 100644 --- a/java/com/google/gerrit/server/restapi/change/CommentJson.java +++ b/java/com/google/gerrit/server/restapi/change/CommentJson.java
@@ -217,6 +217,7 @@ r.updated = c.writtenOn; r.range = toRange(c.range); r.tag = c.tag; + r.isAi = c.isAi; if (loader != null) { r.author = loader.get(c.author.getId()); }
diff --git a/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java b/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java index 998a09e..1eeb8d3 100644 --- a/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java +++ b/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java
@@ -66,7 +66,6 @@ private final Provider<CommentJson> commentJson; private final CommentsUtil commentsUtil; private final PatchSetUtil psUtil; - private final ChangeNotes.Factory changeNotesFactory; private final PluginSetContext<CommentValidator> commentValidators; @Inject @@ -75,13 +74,11 @@ Provider<CommentJson> commentJson, CommentsUtil commentsUtil, PatchSetUtil psUtil, - ChangeNotes.Factory changeNotesFactory, PluginSetContext<CommentValidator> commentValidators) { this.updateFactory = updateFactory; this.commentJson = commentJson; this.commentsUtil = commentsUtil; this.psUtil = psUtil; - this.changeNotesFactory = changeNotesFactory; this.commentValidators = commentValidators; } @@ -104,7 +101,7 @@ throw new BadRequestException( String.format("Invalid inReplyTo, comment %s not found", in.inReplyTo)); } - validateDraftComment(rsrc, in, changeNotesFactory, commentValidators, commentsUtil); + validateDraftComment(rsrc, in, commentValidators, commentsUtil); try (RefUpdateContext ctx = RefUpdateContext.open(CHANGE_MODIFICATION)) { try (BatchUpdate bu = updateFactory.create(rsrc.getProject(), rsrc.getUser(), TimeUtil.now())) { @@ -120,13 +117,12 @@ static void validateDraftComment( RevisionResource rsrc, DraftInput in, - ChangeNotes.Factory changeNotesFactory, PluginSetContext<CommentValidator> commentValidators, CommentsUtil commentsUtil) throws BadRequestException { HumanComment comment = createDraftComment( - changeNotesFactory.create(rsrc.getProject(), rsrc.getChange().getId()), + rsrc.getNotes(), rsrc.getUser(), TimeUtil.now(), in, @@ -188,6 +184,7 @@ CommentsUtil.createFixSuggestionsFromInput(draftInput.fixSuggestions)); comment.setLineNbrAndRange(draftInput.line, draftInput.range); comment.tag = draftInput.tag; + comment.isAi = draftInput.isAi; commentsUtil.setCommentCommitId(comment, change, ps); return comment;
diff --git a/java/com/google/gerrit/server/restapi/change/PatchSetCreator.java b/java/com/google/gerrit/server/restapi/change/PatchSetCreator.java index 7b7ee13..88ec9c9 100644 --- a/java/com/google/gerrit/server/restapi/change/PatchSetCreator.java +++ b/java/com/google/gerrit/server/restapi/change/PatchSetCreator.java
@@ -44,14 +44,14 @@ import com.google.gerrit.server.update.UpdateException; import com.google.gerrit.server.update.context.RefUpdateContext; import com.google.gerrit.server.util.time.TimeUtil; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; import java.io.IOException; import java.time.Instant; import java.time.ZoneId; import java.util.List; import java.util.Optional; -import javax.inject.Inject; -import javax.inject.Provider; -import javax.inject.Singleton; import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter;
diff --git a/java/com/google/gerrit/server/restapi/change/PostReviewOp.java b/java/com/google/gerrit/server/restapi/change/PostReviewOp.java index 8af3739..0bcd356 100644 --- a/java/com/google/gerrit/server/restapi/change/PostReviewOp.java +++ b/java/com/google/gerrit/server/restapi/change/PostReviewOp.java
@@ -467,6 +467,7 @@ commentsUtil.setCommentCommitId(comment, ctx.getChange(), ps); comment.setLineNbrAndRange(inputComment.line, inputComment.range); comment.tag = in.tag; + comment.isAi = inputComment.isAi; if (existingComments.contains(CommentSetEntry.create(comment))) { continue;
diff --git a/java/com/google/gerrit/server/restapi/change/PutDraftComment.java b/java/com/google/gerrit/server/restapi/change/PutDraftComment.java index 07db841..fd275ab 100644 --- a/java/com/google/gerrit/server/restapi/change/PutDraftComment.java +++ b/java/com/google/gerrit/server/restapi/change/PutDraftComment.java
@@ -33,7 +33,6 @@ import com.google.gerrit.server.DraftCommentsReader; import com.google.gerrit.server.PatchSetUtil; import com.google.gerrit.server.change.DraftCommentResource; -import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.notedb.ChangeUpdate; import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gerrit.server.plugincontext.PluginSetContext; @@ -58,7 +57,6 @@ private final DraftCommentsReader draftCommentsReader; private final PatchSetUtil psUtil; private final Provider<CommentJson> commentJson; - private final ChangeNotes.Factory changeNotesFactory; private final PluginSetContext<CommentValidator> commentValidators; @Inject @@ -69,7 +67,6 @@ DraftCommentsReader draftCommentsReader, PatchSetUtil psUtil, Provider<CommentJson> commentJson, - ChangeNotes.Factory changeNotesFactory, PluginSetContext<CommentValidator> commentValidators) { this.updateFactory = updateFactory; this.delete = delete; @@ -77,7 +74,6 @@ this.draftCommentsReader = draftCommentsReader; this.psUtil = psUtil; this.commentJson = commentJson; - this.changeNotesFactory = changeNotesFactory; this.commentValidators = commentValidators; } @@ -101,7 +97,7 @@ String.format("Invalid inReplyTo, comment %s not found", in.inReplyTo)); } CreateDraftComment.validateDraftComment( - rsrc.getRevisionResource(), in, changeNotesFactory, commentValidators, commentsUtil); + rsrc.getRevisionResource(), in, commentValidators, commentsUtil); try (RefUpdateContext ctx = RefUpdateContext.open(CHANGE_MODIFICATION)) { try (BatchUpdate bu = updateFactory.create(rsrc.getChange().getProject(), rsrc.getUser(), TimeUtil.now())) { @@ -180,6 +176,9 @@ if (in.unresolved != null) { e.unresolved = in.unresolved; } + if (in.isAi != null) { + e.isAi = in.isAi; + } if (in.fixSuggestions != null) { e.fixSuggestions = CommentsUtil.createFixSuggestionsFromInput(in.fixSuggestions); } else {
diff --git a/java/com/google/gerrit/server/restapi/config/IndexChanges.java b/java/com/google/gerrit/server/restapi/config/IndexChanges.java index caca5bc..c011b56 100644 --- a/java/com/google/gerrit/server/restapi/config/IndexChanges.java +++ b/java/com/google/gerrit/server/restapi/config/IndexChanges.java
@@ -14,73 +14,130 @@ package com.google.gerrit.server.restapi.config; +import com.google.common.base.Preconditions; import com.google.common.flogger.FluentLogger; import com.google.gerrit.common.data.GlobalCapability; import com.google.gerrit.entities.Change; +import com.google.gerrit.entities.Project; import com.google.gerrit.extensions.annotations.RequiresCapability; +import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.RestModifyView; import com.google.gerrit.server.change.ChangeFinder; import com.google.gerrit.server.config.ConfigResource; import com.google.gerrit.server.index.change.ChangeIndexer; import com.google.gerrit.server.notedb.ChangeNotes; +import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.query.change.ChangeData; +import com.google.gerrit.server.query.change.InternalChangeQuery; import com.google.gerrit.server.restapi.config.IndexChanges.Input; import com.google.inject.Inject; +import com.google.inject.Provider; import com.google.inject.Singleton; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER) @Singleton public class IndexChanges implements RestModifyView<ConfigResource, Input> { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + private static final Pattern PROJECT_WITH_CHANGE_NUM_REGEX = Pattern.compile("^([^~]+)~(\\d+)$"); - public static class Input { - public Set<String> changes; - boolean deleteMissing; + public record Input(Set<String> changes, boolean deleteMissing) { + + public Input() { + this(Collections.emptySet(), false); + } } private final ChangeFinder changeFinder; private final ChangeData.Factory changeDataFactory; + private final Provider<InternalChangeQuery> queryProvider; + private final ChangeNotes.Factory notesFactory; private final ChangeIndexer indexer; @Inject IndexChanges( - ChangeFinder changeFinder, ChangeData.Factory changeDataFactory, ChangeIndexer indexer) { + ChangeFinder changeFinder, + ChangeData.Factory changeDataFactory, + Provider<InternalChangeQuery> queryProvider, + ChangeNotes.Factory notesFactory, + ChangeIndexer indexer) { this.changeFinder = changeFinder; this.changeDataFactory = changeDataFactory; + this.queryProvider = queryProvider; + this.notesFactory = notesFactory; this.indexer = indexer; } @Override - public Response<String> apply(ConfigResource resource, Input input) { + public Response<String> apply(ConfigResource resource, Input input) throws Exception { if (input == null || input.changes == null) { return Response.ok("Nothing to index"); } - for (String id : input.changes) { - List<ChangeNotes> notes = changeFinder.find(id); + if (input.deleteMissing) { + List<ProjectWithChangeNumTuple> changeIds = new ArrayList<>(); + for (String id : input.changes) { + changeIds.add(parseIntoProjectWithChangeNumTuple(id)); + } - if (notes.isEmpty()) { - logger.atWarning().log("Change %s missing in NoteDb", id); - if (input.deleteMissing) { - Optional<Change.Id> changeId = Change.Id.tryParse(id); - if (changeId.isPresent()) { - logger.atWarning().log("Deleting change %s from index", changeId.get()); - indexer.delete(changeId.get()); + for (ProjectWithChangeNumTuple changeInfo : changeIds) { + List<ChangeData> changes = + queryProvider.get().byProjectChangeNumber(changeInfo.project(), changeInfo.changeId()); + Preconditions.checkState( + changes.size() <= 1, + "Ambiguous change ID %s in project %s", + changeInfo.changeId(), + changeInfo.project()); + + if (!changes.isEmpty()) { + try { + // Probe NoteDb: NoSuchChangeException means the change is in the index + // but absent from disk, so it should be deleted. + ChangeNotes unused = notesFactory.create(changeInfo.project(), changeInfo.changeId()); + } catch (NoSuchChangeException e) { + logger.atWarning().log( + "Change %s~%s missing in NoteDb", changeInfo.project(), changeInfo.changeId()); + ChangeData cd = changes.getFirst(); + logger.atWarning().log( + "Deleting change %s~%s from index", cd.project(), cd.change().getChangeId()); + indexer.delete(cd.project(), cd.virtualId()); + continue; } } - continue; - } - for (ChangeNotes n : notes) { - indexer.index(changeDataFactory.create(n)); - logger.atFine().log("Indexed change %s", id); + indexer.index(changeInfo.project, changeInfo.changeId); + logger.atFine().log("Indexed change %s:%s", changeInfo.project, changeInfo.changeId); } + } else { + input.changes.stream() + .flatMap(cid -> changeFinder.find(cid).stream()) + .map(changeDataFactory::create) + .forEach( + cd -> { + indexer.index(cd); + logger.atFine().log("Indexed change %s:%s", cd.project(), cd.getId()); + }); } return Response.ok("Indexed changes " + input.changes); } + + record ProjectWithChangeNumTuple(Project.NameKey project, Change.Id changeId) {} + + ProjectWithChangeNumTuple parseIntoProjectWithChangeNumTuple(String id) + throws BadRequestException { + Matcher projectWithChangeNumMatcher = PROJECT_WITH_CHANGE_NUM_REGEX.matcher(id); + if (projectWithChangeNumMatcher.matches()) { + return new ProjectWithChangeNumTuple( + Project.nameKey(projectWithChangeNumMatcher.group(1)), + Change.id(Integer.parseInt(projectWithChangeNumMatcher.group(2)))); + } + throw new BadRequestException("Change ID must be in project~changeNumber format: " + id); + } }
diff --git a/java/com/google/gerrit/server/restapi/config/ListCaches.java b/java/com/google/gerrit/server/restapi/config/ListCaches.java index 6dc17ce..471e83a 100644 --- a/java/com/google/gerrit/server/restapi/config/ListCaches.java +++ b/java/com/google/gerrit/server/restapi/config/ListCaches.java
@@ -35,6 +35,7 @@ import com.google.inject.Inject; import java.util.Map; import java.util.TreeMap; +import java.util.function.Predicate; import java.util.stream.Stream; import org.kohsuke.args4j.Option; @@ -61,11 +62,17 @@ } public Map<String, CacheInfo> getCacheInfos() { + return getCacheInfos(name -> true); + } + + public Map<String, CacheInfo> getCacheInfos(Predicate<String> nameFilter) { Map<String, CacheInfo> cacheInfos = new TreeMap<>(); for (Extension<Cache<?, ?>> e : cacheMap) { - cacheInfos.put( - cacheNameOf(e.getPluginName(), e.getExportName()), - CacheInfoFactory.create(e.getProvider().get())); + String name = cacheNameOf(e.getPluginName(), e.getExportName()); + if (!nameFilter.test(name)) { + continue; + } + cacheInfos.put(name, CacheInfoFactory.create(e.getProvider().get())); } return cacheInfos; }
diff --git a/java/com/google/gerrit/server/restapi/project/GetDiffFile.java b/java/com/google/gerrit/server/restapi/project/GetDiffFile.java new file mode 100644 index 0000000..70f22be --- /dev/null +++ b/java/com/google/gerrit/server/restapi/project/GetDiffFile.java
@@ -0,0 +1,178 @@ +// Copyright (C) 2025 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.restapi.project; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.google.gerrit.common.data.PatchScript; +import com.google.gerrit.entities.Project; +import com.google.gerrit.extensions.client.DiffPreferencesInfo; +import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace; +import com.google.gerrit.extensions.common.DiffInfo; +import com.google.gerrit.extensions.common.DiffWebLinkInfo; +import com.google.gerrit.extensions.common.WebLinkInfo; +import com.google.gerrit.extensions.restapi.BadRequestException; +import com.google.gerrit.extensions.restapi.ResourceConflictException; +import com.google.gerrit.extensions.restapi.ResourceNotFoundException; +import com.google.gerrit.extensions.restapi.Response; +import com.google.gerrit.extensions.restapi.RestApiException; +import com.google.gerrit.extensions.restapi.RestReadView; +import com.google.gerrit.server.diff.DiffInfoCreator; +import com.google.gerrit.server.diff.DiffSide; +import com.google.gerrit.server.diff.DiffWebLinksProvider; +import com.google.gerrit.server.git.GitRepositoryManager; +import com.google.gerrit.server.patch.DiffNotAvailableException; +import com.google.gerrit.server.patch.DiffOperations; +import com.google.gerrit.server.patch.DiffOptions; +import com.google.gerrit.server.patch.PatchScriptBuilder; +import com.google.gerrit.server.patch.filediff.FileDiffOutput; +import com.google.gerrit.server.project.FileResource; +import com.google.gerrit.server.project.ProjectState; +import com.google.inject.Inject; +import com.google.inject.Provider; +import java.io.IOException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.kohsuke.args4j.Option; + +/** + * Gets the diff for a specific file between two commits. + * + * <p>GET /projects/{project}/commits/{commit}/files/{file}/diff?base={sha1} + * + * <p>Returns the same format as /changes/{id}/revisions/{rev}/files/{file}/diff to ensure identical + * output. + */ +public class GetDiffFile implements RestReadView<FileResource> { + + private final GitRepositoryManager repoManager; + private final DiffOperations diffOperations; + private final Provider<PatchScriptBuilder> patchScriptBuilderProvider; + private final ProjectDiffUtils diffUtils; + + @Option(name = "--base", metaVar = "SHA1", usage = "base commit SHA1 (40 characters)") + private String baseSha; + + @Option(name = "--whitespace") + private Whitespace whitespace; + + @Option(name = "--intraline") + private boolean intraline; + + public GetDiffFile setBase(String baseSha) { + this.baseSha = baseSha; + return this; + } + + @Inject + GetDiffFile( + GitRepositoryManager repoManager, + DiffOperations diffOperations, + Provider<PatchScriptBuilder> patchScriptBuilderProvider, + ProjectDiffUtils diffUtils) { + this.repoManager = repoManager; + this.diffOperations = diffOperations; + this.patchScriptBuilderProvider = patchScriptBuilderProvider; + this.diffUtils = diffUtils; + } + + @Override + public Response<DiffInfo> apply(FileResource rsrc) + throws RestApiException, + BadRequestException, + ResourceNotFoundException, + ResourceConflictException, + IOException { + diffUtils.validateSha1(baseSha, "base"); + + ProjectState projectState = rsrc.getProjectState(); + projectState.checkStatePermitsRead(); + Project.NameKey project = projectState.getNameKey(); + String filePath = rsrc.getPath(); + + ObjectId baseCommitId = diffUtils.parseObjectId(baseSha, "base"); + ObjectId newCommitId = rsrc.getRev(); + + Whitespace ws = whitespace != null ? whitespace : Whitespace.IGNORE_LEADING_AND_TRAILING; + + try (Repository repo = repoManager.openRepository(project); + RevWalk rw = new RevWalk(repo)) { + RevCommit baseCommit = diffUtils.parseCommit(rw, baseCommitId, "base"); + RevCommit newCommit = rw.parseCommit(newCommitId); + + // Validate that commits are in ancestor/descendant relationship + diffUtils.validateAncestorRelationship(rw, baseCommit, newCommit); + + // Verify visibility of all commits in the path + diffUtils.verifyPathVisibility(projectState, repo, rw, baseCommit, newCommit); + + // Get the file diff + // Don't skip files due to rebase - this is a direct commit comparison, not patchset + // comparison + DiffOptions diffOptions = + DiffOptions.builder().skipFilesWithAllEditsDueToRebase(false).build(); + FileDiffOutput fileDiffOutput = + diffOperations.getModifiedFile( + project, baseCommitId, newCommitId, filePath, ws, diffOptions); + + // Convert to PatchScript + DiffPreferencesInfo prefs = new DiffPreferencesInfo(); + prefs.ignoreWhitespace = ws; + prefs.intralineDifference = intraline; + + PatchScriptBuilder builder = patchScriptBuilderProvider.get(); + builder.setDiffPrefs(prefs); + PatchScript ps = builder.toPatchScript(repo, fileDiffOutput); + + // Create DiffInfo + // For project-level diff, we don't have change context for web links + DiffWebLinksProvider emptyLinksProvider = new EmptyDiffWebLinksProvider(); + DiffInfoCreator diffInfoCreator = + new DiffInfoCreator(projectState, emptyLinksProvider, intraline); + + DiffSide sideA = + DiffSide.create( + ps.getFileInfoA(), + MoreObjects.firstNonNull(ps.getOldName(), ps.getNewName()), + DiffSide.Type.SIDE_A); + DiffSide sideB = DiffSide.create(ps.getFileInfoB(), ps.getNewName(), DiffSide.Type.SIDE_B); + + DiffInfo result = diffInfoCreator.create(ps, sideA, sideB); + return Response.ok(result); + } catch (DiffNotAvailableException e) { + throw diffUtils.mapDiffException(e); + } + } + + /** Empty implementation since project-level diff has no change context for web links. */ + private static class EmptyDiffWebLinksProvider implements DiffWebLinksProvider { + @Override + public ImmutableList<DiffWebLinkInfo> getDiffLinks() { + return ImmutableList.of(); + } + + @Override + public ImmutableList<WebLinkInfo> getEditWebLinks() { + return ImmutableList.of(); + } + + @Override + public ImmutableList<WebLinkInfo> getFileWebLinks(DiffSide.Type type) { + return ImmutableList.of(); + } + } +}
diff --git a/java/com/google/gerrit/server/restapi/project/LabelDefinitionInputParser.java b/java/com/google/gerrit/server/restapi/project/LabelDefinitionInputParser.java index 11d8b19..abbe50d 100644 --- a/java/com/google/gerrit/server/restapi/project/LabelDefinitionInputParser.java +++ b/java/com/google/gerrit/server/restapi/project/LabelDefinitionInputParser.java
@@ -77,8 +77,8 @@ if (newBranch.isEmpty()) { continue; } - if (!RefPattern.isRE(newBranch) && !newBranch.startsWith(RefNames.REFS)) { - newBranch = RefNames.REFS_HEADS + newBranch; + if (!RefPattern.isRE(newBranch)) { + newBranch = RefNames.fullName(newBranch); } try { RefPattern.validate(newBranch);
diff --git a/java/com/google/gerrit/server/restapi/project/ListDiffFiles.java b/java/com/google/gerrit/server/restapi/project/ListDiffFiles.java new file mode 100644 index 0000000..0a2925a --- /dev/null +++ b/java/com/google/gerrit/server/restapi/project/ListDiffFiles.java
@@ -0,0 +1,170 @@ +// Copyright (C) 2025 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.restapi.project; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.gerrit.entities.Patch; +import com.google.gerrit.entities.Project; +import com.google.gerrit.extensions.common.FileInfo; +import com.google.gerrit.extensions.restapi.BadRequestException; +import com.google.gerrit.extensions.restapi.ResourceConflictException; +import com.google.gerrit.extensions.restapi.ResourceNotFoundException; +import com.google.gerrit.extensions.restapi.Response; +import com.google.gerrit.extensions.restapi.RestApiException; +import com.google.gerrit.extensions.restapi.RestReadView; +import com.google.gerrit.server.git.GitRepositoryManager; +import com.google.gerrit.server.patch.DiffNotAvailableException; +import com.google.gerrit.server.patch.DiffOperations; +import com.google.gerrit.server.patch.DiffOptions; +import com.google.gerrit.server.patch.FilePathAdapter; +import com.google.gerrit.server.patch.filediff.FileDiffOutput; +import com.google.gerrit.server.project.CommitResource; +import com.google.gerrit.server.project.ProjectState; +import com.google.inject.Inject; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.kohsuke.args4j.Option; + +/** + * Lists files that differ between two commits in a project. + * + * <p>GET /projects/{project}/commits/{commit}/diff?base={sha1} + * + * <p>Returns the same format as /changes/{id}/revisions/{rev}/files to ensure identical output. + */ +public class ListDiffFiles implements RestReadView<CommitResource> { + private final GitRepositoryManager repoManager; + private final DiffOperations diffOperations; + private final ProjectDiffUtils diffUtils; + + @Option(name = "--base", metaVar = "SHA1", usage = "base commit SHA1 (40 characters)") + private String baseSha; + + @Option(name = "--name-only", usage = "return only the list of files") + private boolean nameOnly; + + @CanIgnoreReturnValue + public ListDiffFiles setBase(String baseSha) { + this.baseSha = baseSha; + return this; + } + + @CanIgnoreReturnValue + public ListDiffFiles setNameOnly(boolean nameOnly) { + this.nameOnly = nameOnly; + return this; + } + + @Inject + ListDiffFiles( + GitRepositoryManager repoManager, DiffOperations diffOperations, ProjectDiffUtils diffUtils) { + this.repoManager = repoManager; + this.diffOperations = diffOperations; + this.diffUtils = diffUtils; + } + + @Override + public Response<Map<String, FileInfo>> apply(CommitResource rsrc) + throws RestApiException, + BadRequestException, + ResourceNotFoundException, + ResourceConflictException, + IOException { + if (!nameOnly) { + throw new BadRequestException("name-only parameter is required for listing diff files"); + } + + diffUtils.validateSha1(baseSha, "base"); + + rsrc.getProjectState().checkStatePermitsRead(); + Project.NameKey project = rsrc.getProjectState().getNameKey(); + ProjectState projectState = rsrc.getProjectState(); + + ObjectId baseCommitId = diffUtils.parseObjectId(baseSha, "base"); + ObjectId newCommitId = rsrc.getCommit(); + + try (Repository repo = repoManager.openRepository(project); + RevWalk rw = new RevWalk(repo)) { + RevCommit baseCommit = diffUtils.parseCommit(rw, baseCommitId, "base"); + RevCommit newCommit = rw.parseCommit(newCommitId); + + // Validate that commits are in ancestor/descendant relationship + diffUtils.validateAncestorRelationship(rw, baseCommit, newCommit); + + // Walk all commits in path and verify visibility for each (critical for private changes) + diffUtils.verifyPathVisibility(projectState, repo, rw, baseCommit, newCommit); + + // Compute the diff + // Don't skip files due to rebase - this is a direct commit comparison, not patchset + // comparison + DiffOptions diffOptions = + DiffOptions.builder() + .skipFilesWithAllEditsDueToRebase(false) + .skipRebaseFiltering(true) + .build(); + Map<String, FileDiffOutput> fileDiffs = + diffOperations.listModifiedFiles(project, baseCommitId, newCommitId, diffOptions); + + return Response.ok(asFileInfo(fileDiffs)); + } catch (DiffNotAvailableException e) { + throw diffUtils.mapDiffException(e); + } + } + + /** + * Converts FileDiffOutput map to FileInfo map. + * + * <p>This is the same conversion logic as FileInfoJsonImpl.asFileInfo() to ensure identical + * output format. + */ + private Map<String, FileInfo> asFileInfo(Map<String, FileDiffOutput> fileDiffs) { + Map<String, FileInfo> result = new HashMap<>(); + for (String path : fileDiffs.keySet()) { + FileDiffOutput fileDiff = fileDiffs.get(path); + FileInfo fileInfo = new FileInfo(); + fileInfo.status = + fileDiff.changeType() != Patch.ChangeType.MODIFIED + ? fileDiff.changeType().getCode() + : null; + fileInfo.oldPath = FilePathAdapter.getOldPath(fileDiff.oldPath(), fileDiff.changeType()); + fileInfo.sizeDelta = fileDiff.sizeDelta(); + fileInfo.size = fileDiff.size(); + fileInfo.oldMode = + fileDiff.oldMode().isPresent() && !fileDiff.oldMode().get().equals(Patch.FileMode.MISSING) + ? fileDiff.oldMode().get().getMode() + : null; + fileInfo.newMode = + fileDiff.newMode().isPresent() && !fileDiff.newMode().get().equals(Patch.FileMode.MISSING) + ? fileDiff.newMode().get().getMode() + : null; + fileDiff.oldSha().ifPresent(sha -> fileInfo.oldSha = sha.name()); + fileDiff.newSha().ifPresent(sha -> fileInfo.newSha = sha.name()); + + if (fileDiff.patchType().get() == Patch.PatchType.BINARY) { + fileInfo.binary = true; + } else { + fileInfo.linesInserted = fileDiff.insertions() > 0 ? fileDiff.insertions() : null; + fileInfo.linesDeleted = fileDiff.deletions() > 0 ? fileDiff.deletions() : null; + } + result.put(path, fileInfo); + } + return result; + } +}
diff --git a/java/com/google/gerrit/server/restapi/project/PostLabelsReview.java b/java/com/google/gerrit/server/restapi/project/PostLabelsReview.java index 4e9d432..177f26d 100644 --- a/java/com/google/gerrit/server/restapi/project/PostLabelsReview.java +++ b/java/com/google/gerrit/server/restapi/project/PostLabelsReview.java
@@ -27,9 +27,9 @@ import com.google.gerrit.server.restapi.project.RepoMetaDataUpdater.ConfigChangeCreator; import com.google.gerrit.server.update.UpdateException; import com.google.inject.Inject; +import com.google.inject.Singleton; import java.io.IOException; import java.util.Map; -import javax.inject.Singleton; import org.eclipse.jgit.errors.ConfigInvalidException; @Singleton
diff --git a/java/com/google/gerrit/server/restapi/project/PostSubmitRequirements.java b/java/com/google/gerrit/server/restapi/project/PostSubmitRequirements.java index 71080a5..2c7bfce 100644 --- a/java/com/google/gerrit/server/restapi/project/PostSubmitRequirements.java +++ b/java/com/google/gerrit/server/restapi/project/PostSubmitRequirements.java
@@ -23,9 +23,9 @@ import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.project.ProjectConfig; import com.google.gerrit.server.project.SubmitRequirementResource; +import com.google.inject.Inject; import com.google.inject.Provider; -import javax.inject.Inject; -import javax.inject.Singleton; +import com.google.inject.Singleton; @Singleton public class PostSubmitRequirements
diff --git a/java/com/google/gerrit/server/restapi/project/PostSubmitRequirementsReview.java b/java/com/google/gerrit/server/restapi/project/PostSubmitRequirementsReview.java index f0a371a..c9485fc 100644 --- a/java/com/google/gerrit/server/restapi/project/PostSubmitRequirementsReview.java +++ b/java/com/google/gerrit/server/restapi/project/PostSubmitRequirementsReview.java
@@ -25,8 +25,8 @@ import com.google.gerrit.server.restapi.project.RepoMetaDataUpdater.ConfigChangeCreator; import com.google.gerrit.server.update.UpdateException; import com.google.inject.Inject; +import com.google.inject.Singleton; import java.io.IOException; -import javax.inject.Singleton; import org.eclipse.jgit.errors.ConfigInvalidException; @Singleton
diff --git a/java/com/google/gerrit/server/restapi/project/ProjectDiffUtils.java b/java/com/google/gerrit/server/restapi/project/ProjectDiffUtils.java new file mode 100644 index 0000000..36c0999 --- /dev/null +++ b/java/com/google/gerrit/server/restapi/project/ProjectDiffUtils.java
@@ -0,0 +1,152 @@ +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.restapi.project; + +import com.google.gerrit.extensions.restapi.BadRequestException; +import com.google.gerrit.extensions.restapi.ResourceConflictException; +import com.google.gerrit.extensions.restapi.ResourceNotFoundException; +import com.google.gerrit.extensions.restapi.RestApiException; +import com.google.gerrit.server.patch.DiffNotAvailableException; +import com.google.gerrit.server.project.ProjectState; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.io.IOException; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.NoMergeBaseException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; + +/** Utility class for common diff operations in the project REST API. */ +@Singleton +public class ProjectDiffUtils { + private static final int SHA1_LENGTH = 40; + + private final CommitsCollection commitsCollection; + + @Inject + ProjectDiffUtils(CommitsCollection commitsCollection) { + this.commitsCollection = commitsCollection; + } + + public void validateSha1(String sha, String paramName) throws BadRequestException { + if (sha == null || sha.isEmpty()) { + throw new BadRequestException("Missing required parameter: " + paramName); + } + if (sha.length() != SHA1_LENGTH) { + throw new BadRequestException( + String.format( + "Parameter '%s' must be a 40-character SHA1, got %d characters", + paramName, sha.length())); + } + if (!sha.matches("[0-9a-fA-F]+")) { + throw new BadRequestException( + String.format("Parameter '%s' must be a valid hexadecimal SHA1", paramName)); + } + } + + public ObjectId parseObjectId(String sha, String paramName) throws BadRequestException { + try { + return ObjectId.fromString(sha); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Invalid SHA1 for " + paramName + ": " + sha, e); + } + } + + public RevCommit parseCommit(RevWalk rw, ObjectId commitId, String paramName) + throws ResourceNotFoundException, IOException { + try { + return rw.parseCommit(commitId); + } catch (MissingObjectException e) { + throw new ResourceNotFoundException( + String.format("Commit '%s' (%s) not found", paramName, commitId.name()), e); + } catch (IncorrectObjectTypeException e) { + throw new ResourceNotFoundException( + String.format("Object '%s' (%s) is not a commit", paramName, commitId.name()), e); + } + } + + public void validateAncestorRelationship(RevWalk rw, RevCommit oldCommit, RevCommit newCommit) + throws BadRequestException, IOException { + boolean oldIsAncestorOfNew = rw.isMergedInto(oldCommit, newCommit); + boolean newIsAncestorOfOld = rw.isMergedInto(newCommit, oldCommit); + + if (!oldIsAncestorOfNew && !newIsAncestorOfOld) { + throw new BadRequestException( + String.format( + "Commits %s and %s are not in ancestor/descendant relationship", + oldCommit.name(), newCommit.name())); + } + } + + public void verifyPathVisibility( + ProjectState projectState, + Repository repo, + RevWalk rw, + RevCommit oldCommit, + RevCommit newCommit) + throws ResourceNotFoundException, IOException { + // Check visibility of both endpoints + if (!commitsCollection.canRead(projectState, repo, oldCommit) + || !commitsCollection.canRead(projectState, repo, newCommit)) { + throw new ResourceNotFoundException("Commit not visible"); + } + + // Determine topological order to walk from descendant to ancestor + rw.reset(); + boolean oldIsAncestorOfNew = rw.isMergedInto(oldCommit, newCommit); + boolean newIsAncestorOfOld = rw.isMergedInto(newCommit, oldCommit); + + RevCommit descendant; + RevCommit ancestor; + if (oldIsAncestorOfNew) { + descendant = newCommit; + ancestor = oldCommit; + } else if (newIsAncestorOfOld) { + descendant = oldCommit; + ancestor = newCommit; + } else { + // Commits are not related, visibility check covers endpoints but no path exists. + // This is usually caught by validateAncestorRelationship. + return; + } + + // Walk all commits between descendant and ancestor and check visibility + rw.reset(); + rw.markStart(descendant); + rw.markUninteresting(ancestor); + + for (RevCommit commit : rw) { + if (!commitsCollection.canRead(projectState, repo, commit)) { + throw new ResourceNotFoundException("Commit not visible"); + } + } + } + + /** Maps {@link DiffNotAvailableException} to appropriate {@link RestApiException}. */ + public RestApiException mapDiffException(DiffNotAvailableException e) { + Throwable cause = e.getCause(); + if (cause != null && !(cause instanceof NoMergeBaseException)) { + cause = cause.getCause(); + } + if (cause instanceof NoMergeBaseException) { + return new ResourceConflictException( + String.format("Cannot create auto merge commit: %s", e.getMessage()), e); + } + return new ResourceNotFoundException("Cannot compute diff: " + e.getMessage(), e); + } +}
diff --git a/java/com/google/gerrit/server/restapi/project/ProjectRestApiModule.java b/java/com/google/gerrit/server/restapi/project/ProjectRestApiModule.java index adba60e..a3739c1 100644 --- a/java/com/google/gerrit/server/restapi/project/ProjectRestApiModule.java +++ b/java/com/google/gerrit/server/restapi/project/ProjectRestApiModule.java
@@ -35,6 +35,7 @@ bind(ProjectsCollection.class); bind(ListProjects.class).to(ListProjectsImpl.class); bind(DashboardsCollection.class); + bind(ProjectDiffUtils.class); DynamicMap.mapOf(binder(), BRANCH_KIND); DynamicMap.mapOf(binder(), CHILD_PROJECT_KIND); @@ -80,9 +81,12 @@ post(COMMIT_KIND, "cherrypick").to(CherryPickCommit.class); child(COMMIT_KIND, "files").to(FilesInCommitCollection.class); get(COMMIT_KIND, "in").to(CommitIncludedIn.class); + get(COMMIT_KIND, "diff").to(ListDiffFiles.class); get(PROJECT_KIND, "commits:in").to(CommitsIncludedInRefs.class); + get(FILE_KIND, "diff").to(GetDiffFile.class); + get(PROJECT_KIND, "config").to(GetConfig.class); put(PROJECT_KIND, "config").to(PutConfig.class); put(PROJECT_KIND, "config:review").to(PutConfigReview.class);
diff --git a/java/com/google/gerrit/server/restapi/project/PutConfigReview.java b/java/com/google/gerrit/server/restapi/project/PutConfigReview.java index 7e6cc19..eda92bb 100644 --- a/java/com/google/gerrit/server/restapi/project/PutConfigReview.java +++ b/java/com/google/gerrit/server/restapi/project/PutConfigReview.java
@@ -23,9 +23,9 @@ import com.google.gerrit.server.project.ProjectResource; import com.google.gerrit.server.restapi.project.RepoMetaDataUpdater.ConfigChangeCreator; import com.google.gerrit.server.update.UpdateException; +import com.google.inject.Inject; +import com.google.inject.Singleton; import java.io.IOException; -import javax.inject.Inject; -import javax.inject.Singleton; import org.eclipse.jgit.errors.ConfigInvalidException; @Singleton
diff --git a/java/com/google/gerrit/server/restapi/project/RepoMetaDataUpdater.java b/java/com/google/gerrit/server/restapi/project/RepoMetaDataUpdater.java index 62c88276..d386f85 100644 --- a/java/com/google/gerrit/server/restapi/project/RepoMetaDataUpdater.java +++ b/java/com/google/gerrit/server/restapi/project/RepoMetaDataUpdater.java
@@ -49,10 +49,10 @@ import com.google.gerrit.server.update.UpdateException; import com.google.gerrit.server.update.context.RefUpdateContext; import com.google.gerrit.server.util.time.TimeUtil; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; import java.io.IOException; -import javax.inject.Inject; -import javax.inject.Provider; -import javax.inject.Singleton; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.ObjectId;
diff --git a/java/com/google/gerrit/server/rules/prolog/PrologSubmitRuleUtilImpl.java b/java/com/google/gerrit/server/rules/prolog/PrologSubmitRuleUtilImpl.java index 6be71f8..4cc30ec 100644 --- a/java/com/google/gerrit/server/rules/prolog/PrologSubmitRuleUtilImpl.java +++ b/java/com/google/gerrit/server/rules/prolog/PrologSubmitRuleUtilImpl.java
@@ -20,8 +20,8 @@ import com.google.gerrit.entities.SubmitTypeRecord; import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.rules.PrologSubmitRuleUtil; +import com.google.inject.Inject; import com.google.inject.Singleton; -import javax.inject.Inject; /** Implementation of {@link PrologSubmitRuleUtil}. */ @Singleton
diff --git a/java/com/google/gerrit/server/schema/AllProjectsCreator.java b/java/com/google/gerrit/server/schema/AllProjectsCreator.java index 514b993..6eb9a02 100644 --- a/java/com/google/gerrit/server/schema/AllProjectsCreator.java +++ b/java/com/google/gerrit/server/schema/AllProjectsCreator.java
@@ -219,6 +219,10 @@ grant(config, refsFor, Permission.PUSH, registered); grant(config, refsFor, Permission.PUSH_MERGE, registered); }); + + config.upsertAccessSection( + AccessSection.ALL, + refsFor -> grant(config, refsFor, Permission.POST_REVIEW_COMMENT, registered)); } private void initDefaultAclsForServiceUsers(
diff --git a/java/com/google/gerrit/server/schema/GrantPostReviewCommentPermission.java b/java/com/google/gerrit/server/schema/GrantPostReviewCommentPermission.java new file mode 100644 index 0000000..e9f34fd --- /dev/null +++ b/java/com/google/gerrit/server/schema/GrantPostReviewCommentPermission.java
@@ -0,0 +1,97 @@ +// Copyright (C) 2025 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.schema; + +import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; +import static com.google.gerrit.server.schema.AclUtil.grant; + +import com.google.gerrit.entities.AccessSection; +import com.google.gerrit.entities.GroupReference; +import com.google.gerrit.entities.Permission; +import com.google.gerrit.entities.Project; +import com.google.gerrit.server.GerritPersonIdent; +import com.google.gerrit.server.extensions.events.GitReferenceUpdated; +import com.google.gerrit.server.git.GitRepositoryManager; +import com.google.gerrit.server.git.meta.MetaDataUpdate; +import com.google.gerrit.server.group.SystemGroupBackend; +import com.google.gerrit.server.project.ProjectConfig; +import com.google.inject.Inject; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Repository; + +/** + * This class adds the "postReviewComment" permission to all hosts that call this method with the + * relevant projectName. This class should be called with AllProjects as the project, by all hosts + * before enabling the "postReviewComment" permission. + */ +public class GrantPostReviewCommentPermission { + + private final GitRepositoryManager repoManager; + private final ProjectConfig.Factory projectConfigFactory; + private final SystemGroupBackend systemGroupBackend; + private final PersonIdent serverUser; + + @Inject + public GrantPostReviewCommentPermission( + GitRepositoryManager repoManager, + ProjectConfig.Factory projectConfigFactory, + SystemGroupBackend systemGroupBackend, + @GerritPersonIdent PersonIdent serverUser) { + this.repoManager = repoManager; + this.projectConfigFactory = projectConfigFactory; + this.systemGroupBackend = systemGroupBackend; + this.serverUser = serverUser; + } + + public void execute(Project.NameKey projectName) throws IOException, ConfigInvalidException { + GroupReference registeredUsers = systemGroupBackend.getGroup(REGISTERED_USERS); + try (Repository repo = repoManager.openRepository(projectName)) { + MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, projectName, repo); + ProjectConfig projectConfig = projectConfigFactory.read(md); + + AtomicBoolean shouldExit = new AtomicBoolean(false); + projectConfig.upsertAccessSection( + AccessSection.ALL, + all -> { + Permission permissionOnRefsStar = + all.build().getPermission(Permission.POST_REVIEW_COMMENT); + if (permissionOnRefsStar != null) { + if (permissionOnRefsStar.getRule(registeredUsers) == null) { + // If admins already changed the permission, don't do anything. + shouldExit.set(true); + return; + } + // permission already exists on refs/* for Registered Users, don't do anything. + return; + } + // If the permission doesn't exist on refs/* for Registered Users, grant it. + grant(projectConfig, all, Permission.POST_REVIEW_COMMENT, registeredUsers); + }); + + if (shouldExit.get()) { + return; + } + + md.getCommitBuilder().setAuthor(serverUser); + md.getCommitBuilder().setCommitter(serverUser); + md.setMessage("Add Post Review Comment permission for all registered users\n"); + + projectConfig.commit(md); + } + } +}
diff --git a/java/com/google/gerrit/server/schema/JdbcAccountPatchReviewStore.java b/java/com/google/gerrit/server/schema/JdbcAccountPatchReviewStore.java index 9079836..3ca034d 100644 --- a/java/com/google/gerrit/server/schema/JdbcAccountPatchReviewStore.java +++ b/java/com/google/gerrit/server/schema/JdbcAccountPatchReviewStore.java
@@ -431,7 +431,12 @@ return 0; } - private static String createH2Url(Path path) { - return new StringBuilder().append("jdbc:h2:").append(path.toUri().toString()).toString(); + @VisibleForTesting + static String createH2Url(Path path) { + return new StringBuilder() + .append("jdbc:h2:file:") + .append(path.toAbsolutePath().toString()) + .toString() + .replace(";", "\\;"); } }
diff --git a/java/com/google/gerrit/server/schema/NoteDbSchemaVersions.java b/java/com/google/gerrit/server/schema/NoteDbSchemaVersions.java index d84ae60..69e6fb3 100644 --- a/java/com/google/gerrit/server/schema/NoteDbSchemaVersions.java +++ b/java/com/google/gerrit/server/schema/NoteDbSchemaVersions.java
@@ -34,7 +34,8 @@ Schema_182.class, Schema_183.class, Schema_184.class, - Schema_185.class) + Schema_185.class, + Schema_186.class) .collect(toImmutableSortedMap(naturalOrder(), v -> guessVersion(v).get(), v -> v)); public static final int FIRST = ALL.firstKey();
diff --git a/java/com/google/gerrit/server/schema/Schema_186.java b/java/com/google/gerrit/server/schema/Schema_186.java new file mode 100644 index 0000000..de1e315 --- /dev/null +++ b/java/com/google/gerrit/server/schema/Schema_186.java
@@ -0,0 +1,24 @@ +// Copyright (C) 2025 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.schema; + +public class Schema_186 implements NoteDbSchemaVersion { + @Override + public void upgrade(NoteDbSchemaVersion.Arguments args, UpdateUI ui) throws Exception { + new GrantPostReviewCommentPermission( + args.repoManager, args.projectConfigFactory, args.systemGroupBackend, args.serverUser) + .execute(args.allProjects); + } +}
diff --git a/java/com/google/gerrit/server/schema/testing/AllProjectsCreatorTestUtil.java b/java/com/google/gerrit/server/schema/testing/AllProjectsCreatorTestUtil.java index 5c3bcc1..7168cb6 100644 --- a/java/com/google/gerrit/server/schema/testing/AllProjectsCreatorTestUtil.java +++ b/java/com/google/gerrit/server/schema/testing/AllProjectsCreatorTestUtil.java
@@ -54,6 +54,7 @@ ImmutableList.of( "[access \"refs/*\"]", " read = group Administrators", + " postReviewComment = group Registered Users", " read = block group Blocked Users", "[access \"refs/for/*\"]", " addPatchSet = group Registered Users",
diff --git a/java/com/google/gerrit/server/submit/MergeOp.java b/java/com/google/gerrit/server/submit/MergeOp.java index 0a45852..eddee82 100644 --- a/java/com/google/gerrit/server/submit/MergeOp.java +++ b/java/com/google/gerrit/server/submit/MergeOp.java
@@ -441,7 +441,7 @@ String.format( "submit requirement '%s' has an error: %s", srResult.submitRequirement().name(), srResult.errorMessage().orElse(""))); - case UNSATISFIED -> + case UNSATISFIED, TIMEOUT -> throw new ResourceConflictException( String.format( "submit requirement '%s' is unsatisfied.",
diff --git a/java/com/google/gerrit/server/update/SuperprojectUpdateSubmissionListener.java b/java/com/google/gerrit/server/update/SuperprojectUpdateSubmissionListener.java index 813bee9..fad29a7 100644 --- a/java/com/google/gerrit/server/update/SuperprojectUpdateSubmissionListener.java +++ b/java/com/google/gerrit/server/update/SuperprojectUpdateSubmissionListener.java
@@ -21,12 +21,12 @@ import com.google.gerrit.server.submit.MergeOpRepoManager; import com.google.gerrit.server.submit.SubmoduleOp; import com.google.inject.AbstractModule; +import com.google.inject.Inject; import com.google.inject.Provides; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Optional; -import javax.inject.Inject; import org.eclipse.jgit.transport.ReceiveCommand; /** Update superprojects after submission is done */
diff --git a/java/com/google/gerrit/sshd/commands/ShowCaches.java b/java/com/google/gerrit/sshd/commands/ShowCaches.java index 7a958b7..71e5bd1 100644 --- a/java/com/google/gerrit/sshd/commands/ShowCaches.java +++ b/java/com/google/gerrit/sshd/commands/ShowCaches.java
@@ -45,8 +45,13 @@ import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import org.apache.sshd.common.io.IoAcceptor; import org.apache.sshd.common.io.IoSession; import org.apache.sshd.mina.MinaSession; @@ -79,6 +84,12 @@ @Option(name = "--show-threads", usage = "show detailed thread counts") private boolean showThreads; + @Option( + name = "--cache", + usage = "show the named cache; may be supplied more than once", + metaVar = "NAME") + private List<String> caches = new ArrayList<>(); + @Inject private SshDaemon daemon; @Inject private ListCaches listCaches; @Inject private GetSummary getSummary; @@ -153,14 +164,19 @@ } private Collection<CacheInfo> getCaches() { - @SuppressWarnings("unchecked") - Map<String, CacheInfo> caches = - (Map<String, CacheInfo>) listCaches.apply(new ConfigResource()).value(); - for (Map.Entry<String, CacheInfo> entry : caches.entrySet()) { + Map<String, CacheInfo> selected; + if (caches.isEmpty()) { + selected = listCaches.getCacheInfos(); + } else { + Set<String> filter = + caches.stream().map(n -> n.toLowerCase(Locale.US)).collect(Collectors.toSet()); + selected = listCaches.getCacheInfos(n -> filter.contains(n.toLowerCase(Locale.US))); + } + for (Map.Entry<String, CacheInfo> entry : selected.entrySet()) { CacheInfo cache = entry.getValue(); cache.name = entry.getKey(); } - return caches.values(); + return selected.values(); } private void memSummary(MemSummaryInfo memSummary) {
diff --git a/java/com/google/gerrit/testing/InMemoryModule.java b/java/com/google/gerrit/testing/InMemoryModule.java index 745f89a..c37dce9 100644 --- a/java/com/google/gerrit/testing/InMemoryModule.java +++ b/java/com/google/gerrit/testing/InMemoryModule.java
@@ -190,6 +190,11 @@ @Override protected void configure() { + configure(true); + } + + protected void configure(boolean bindGerritApi) { + // Do NOT bind @RemotePeer, as it is bound in a child injector of // ChangeMergeQueue (bound via GerritGlobalModule below), so there cannot be // a binding in the parent injector. If you need @RemotePeer, you must bind @@ -226,7 +231,11 @@ AuthConfig authConfig = cfgInjector.getInstance(AuthConfig.class); install(new AuthModule(authConfig)); - install(new GerritApiModule()); + + if (bindGerritApi) { + install(new GerritApiModule()); + } + install(new ProjectQueryBuilderModule()); install(new DefaultRefLogIdentityProvider.Module()); factory(PluginUser.Factory.class);
diff --git a/java/com/google/gerrit/util/cli/BUILD b/java/com/google/gerrit/util/cli/BUILD index f62aea90..70954a9 100644 --- a/java/com/google/gerrit/util/cli/BUILD +++ b/java/com/google/gerrit/util/cli/BUILD
@@ -1,4 +1,4 @@ -load("@rules_java//java:defs.bzl", "java_library") +load("@rules_java//java:defs.bzl", "java_binary", "java_library") java_library( name = "cli",
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AbstractAccountIT.java similarity index 80% rename from javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java rename to javatests/com/google/gerrit/acceptance/api/accounts/AbstractAccountIT.java index 4272a88..6c5c70e 100644 --- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java +++ b/javatests/com/google/gerrit/acceptance/api/accounts/AbstractAccountIT.java
@@ -19,7 +19,6 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; -import static com.google.gerrit.acceptance.GitUtil.deleteRef; import static com.google.gerrit.acceptance.GitUtil.fetch; import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow; import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability; @@ -32,8 +31,6 @@ import static com.google.gerrit.gpg.testing.TestKeys.validKeyWithExpiration; import static com.google.gerrit.gpg.testing.TestKeys.validKeyWithSecondUserId; import static com.google.gerrit.gpg.testing.TestKeys.validKeyWithoutExpiration; -import static com.google.gerrit.server.account.AccountProperties.ACCOUNT; -import static com.google.gerrit.server.account.AccountProperties.ACCOUNT_CONFIG; import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GPGKEY; import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_MAILTO; import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS; @@ -41,13 +38,8 @@ import static com.google.gerrit.server.project.ProjectCache.illegalState; import static com.google.gerrit.testing.GerritJUnit.assertThrows; import static com.google.gerrit.testing.TestActionRefUpdateContext.testRefAction; -import static com.google.gerrit.truth.ConfigSubject.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; -import static java.util.concurrent.TimeUnit.SECONDS; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; -import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; @@ -112,13 +104,12 @@ import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo; import com.google.gerrit.extensions.api.config.ConsistencyCheckInput; import com.google.gerrit.extensions.api.config.ConsistencyCheckInput.CheckAccountsInput; -import com.google.gerrit.extensions.client.ProjectWatchInfo; +import com.google.gerrit.extensions.client.ListAccountsOption; import com.google.gerrit.extensions.common.AccountDetailInfo; import com.google.gerrit.extensions.common.AccountInfo; import com.google.gerrit.extensions.common.AccountStateInfo; import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.common.CommentInfo; -import com.google.gerrit.extensions.common.EmailInfo; import com.google.gerrit.extensions.common.GpgKeyInfo; import com.google.gerrit.extensions.common.GroupInfo; import com.google.gerrit.extensions.common.MetadataInfo; @@ -138,12 +129,10 @@ import com.google.gerrit.httpd.CacheBasedWebSession; import com.google.gerrit.server.ExceptionHook; import com.google.gerrit.server.IdentifiedUser; -import com.google.gerrit.server.Sequence; import com.google.gerrit.server.Sequences; import com.google.gerrit.server.ServerInitiated; import com.google.gerrit.server.account.AccountControl; import com.google.gerrit.server.account.AccountLimits; -import com.google.gerrit.server.account.AccountProperties; import com.google.gerrit.server.account.AccountState; import com.google.gerrit.server.account.AccountStateProvider; import com.google.gerrit.server.account.AccountsUpdate; @@ -155,17 +144,11 @@ import com.google.gerrit.server.account.externalids.ExternalIdFactory; import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory; import com.google.gerrit.server.account.externalids.ExternalIds; -import com.google.gerrit.server.account.externalids.storage.notedb.ExternalIdFactoryNoteDbImpl; -import com.google.gerrit.server.account.externalids.storage.notedb.ExternalIdNotes; -import com.google.gerrit.server.account.externalids.storage.notedb.ExternalIdsNoteDbImpl; -import com.google.gerrit.server.account.storage.notedb.AccountsUpdateNoteDbImpl; import com.google.gerrit.server.change.AccountPatchReviewStore; import com.google.gerrit.server.config.AuthConfig; -import com.google.gerrit.server.extensions.events.GitReferenceUpdated; import com.google.gerrit.server.git.meta.MetaDataUpdate; import com.google.gerrit.server.group.testing.TestGroupBackend; import com.google.gerrit.server.index.account.AccountIndexer; -import com.google.gerrit.server.index.account.StalenessChecker; import com.google.gerrit.server.permissions.PermissionBackend; import com.google.gerrit.server.plugincontext.PluginSetContext; import com.google.gerrit.server.project.RefPattern; @@ -173,7 +156,6 @@ import com.google.gerrit.server.restapi.account.GetCapabilities; import com.google.gerrit.server.update.RetryHelper; import com.google.gerrit.server.update.RetryListener; -import com.google.gerrit.server.util.time.TimeUtil; import com.google.gerrit.server.validators.AccountActivationValidationListener; import com.google.gerrit.server.validators.ValidationException; import com.google.gerrit.testing.ConfigSuite; @@ -198,11 +180,9 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; import java.util.stream.StreamSupport; import javax.servlet.http.HttpServletResponse; import org.apache.http.HttpResponse; -import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.CloseableHttpClient; @@ -215,26 +195,16 @@ import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; import org.eclipse.jgit.junit.TestRepository; -import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.ObjectReader; -import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.PushCertificateIdent; -import org.eclipse.jgit.transport.PushResult; -import org.eclipse.jgit.transport.RemoteRefUpdate; -import org.eclipse.jgit.treewalk.TreeWalk; import org.junit.After; import org.junit.Before; import org.junit.Test; -public class AccountIT extends AbstractDaemonTest { +public abstract class AbstractAccountIT extends AbstractDaemonTest { @ConfigSuite.Default public static Config enableSignedPushConfig() { Config cfg = new Config(); @@ -254,23 +224,20 @@ @Inject protected GroupOperations groupOperations; - @Inject private @ServerInitiated Provider<AccountsUpdate> accountsUpdateProvider; + @Inject @ServerInitiated protected Provider<AccountsUpdate> accountsUpdateProvider; @Inject private AccountIndexer accountIndexer; - @Inject private ExternalIdNotes.Factory extIdNotesFactory; - @Inject private ExternalIdsNoteDbImpl externalIdsNoteDbImpl; - @Inject private GitReferenceUpdated gitReferenceUpdated; - @Inject private Provider<InternalAccountQuery> accountQueryProvider; - @Inject private Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory; + @Inject protected ExternalIds externalIds; + @Inject protected Provider<InternalAccountQuery> accountQueryProvider; + @Inject protected Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory; @Inject private Provider<PublicKeyStore> publicKeyStoreProvider; @Inject private RetryHelper.Metrics retryMetrics; - @Inject private Sequences seq; - @Inject private StalenessChecker stalenessChecker; + @Inject protected Sequences seq; @Inject private VersionedAuthorizedKeys.Accessor authorizedKeys; @Inject private PluginSetContext<ExceptionHook> exceptionHooks; @Inject private PluginSetContext<RetryListener> retryListeners; - @Inject private ExternalIdKeyFactory externalIdKeyFactory; - @Inject private ExternalIdFactoryNoteDbImpl externalIdFactoryNoteDbImpl; - @Inject private AuthConfig authConfig; + @Inject protected ExternalIdKeyFactory externalIdKeyFactory; + @Inject protected ExternalIdFactory externalIdFactory; + @Inject protected AuthConfig authConfig; @Inject private AccountControl.Factory accountControlFactory; @Inject private AccountOperations accountOperations; @Inject private AccountLimits.Factory limitsFactory; @@ -300,7 +267,7 @@ if (repo.getRefDatabase().exactRef(ref) != null) { RefUpdate ru = repo.updateRef(ref); ru.setForceUpdate(true); - assertWithMessage("Failed to delete " + ref) + assertWithMessage("Failed to delete %s", ref) .that(ru.delete()) .isEqualTo(RefUpdate.Result.FORCED); } @@ -341,18 +308,6 @@ } @Test - public void createByAccountCreator() throws Exception { - RefUpdateCounter refUpdateCounter = createRefUpdateCounter(); - try (Registration registration = extensionRegistry.newRegistration().add(refUpdateCounter)) { - Account.Id accountId = createByAccountCreator(1); - refUpdateCounter.assertRefUpdateFor( - RefUpdateCounter.projectRef(allUsers, RefNames.refsUsers(accountId)), - RefUpdateCounter.projectRef(allUsers, RefNames.REFS_EXTERNAL_IDS), - RefUpdateCounter.projectRef(allUsers, RefNames.REFS_SEQUENCES + Sequence.NAME_ACCOUNTS)); - } - } - - @Test public void createWithInvalidEmailAddress() throws Exception { AccountInput input = new AccountInput(); input.username = name("test"); @@ -372,37 +327,6 @@ return new AccountIndexedCounter(); } - @UsedAt(UsedAt.Project.GOOGLE) - protected Account.Id createByAccountCreator(int expectedAccountReindexCalls) throws Exception { - AccountIndexedCounter accountIndexedCounter = getAccountIndexedCounter(); - try (Registration registration = - extensionRegistry.newRegistration().add(accountIndexedCounter)) { - String name = "foo"; - TestAccount foo = accountCreator.create(name); - AccountInfo info = gApi.accounts().id(foo.id().get()).get(); - if (server.isUsernameSupported()) { - assertThat(info.username).isEqualTo(name); - } else { - assertThat(info.email).isEqualTo(foo.email()); - } - assertThat(info.name).isEqualTo(name); - accountIndexedCounter.assertReindexOf(foo, expectedAccountReindexCalls); - assertUserBranch(foo.id(), name, null); - return foo.id(); - } - } - - @Test - public void createAnonymousCowardByAccountCreator() throws Exception { - AccountIndexedCounter accountIndexedCounter = getAccountIndexedCounter(); - try (Registration registration = - extensionRegistry.newRegistration().add(accountIndexedCounter)) { - TestAccount anonymousCoward = accountCreator.create(); - accountIndexedCounter.assertReindexOf(anonymousCoward); - assertUserBranchWithoutAccountConfig(anonymousCoward.id()); - } - } - @Test public void create() throws Exception { AccountIndexedCounter accountIndexedCounter = getAccountIndexedCounter(); @@ -453,56 +377,6 @@ } @Test - public void commitMessageOnAccountUpdates() throws Exception { - AccountsUpdate au = accountsUpdateProvider.get(); - Account.Id accountId = Account.id(seq.nextAccountId()); - au.insert("Create Test Account", accountId, u -> {}); - assertLastCommitMessageOfUserBranch(accountId, "Create Test Account"); - - au.update("Set Status", accountId, u -> u.setStatus("Foo")); - assertLastCommitMessageOfUserBranch(accountId, "Set Status"); - } - - private void assertLastCommitMessageOfUserBranch(Account.Id accountId, String expectedMessage) - throws Exception { - try (Repository repo = repoManager.openRepository(allUsers); - RevWalk rw = new RevWalk(repo)) { - Ref exactRef = repo.exactRef(RefNames.refsUsers(accountId)); - assertThat(rw.parseCommit(exactRef.getObjectId()).getShortMessage()) - .isEqualTo(expectedMessage); - } - } - - @Test - @UseClockStep - public void createAtomically() throws Exception { - Account.Id accountId = Account.id(seq.nextAccountId()); - String fullName = "Foo"; - ExternalId extId = getExternalIdFactory().createEmail(accountId, "foo@example.com"); - AccountState accountState = - accountsUpdateProvider - .get() - .insert( - "Create Account Atomically", - accountId, - u -> u.setFullName(fullName).addExternalId(extId)); - assertThat(accountState.account().fullName()).isEqualTo(fullName); - - AccountInfo info = gApi.accounts().id(accountId.get()).get(); - assertThat(info.name).isEqualTo(fullName); - - List<EmailInfo> emails = gApi.accounts().id(accountId.get()).getEmails(); - assertThat(emails.stream().map(e -> e.email).collect(toSet())).containsExactly(extId.email()); - - RevCommit commitUserBranch = - projectOperations.project(allUsers).getHead(RefNames.refsUsers(accountId)); - RevCommit commitRefsMetaExternalIds = - projectOperations.project(allUsers).getHead(RefNames.REFS_EXTERNAL_IDS); - assertThat(commitUserBranch.getCommitTime()) - .isEqualTo(commitRefsMetaExternalIds.getCommitTime()); - } - - @Test public void updateNonExistingAccount() throws Exception { Account.Id nonExistingAccountId = Account.id(999999); AtomicBoolean consumerCalled = new AtomicBoolean(); @@ -516,55 +390,16 @@ } @Test - public void updateAccountWithoutAccountConfigNoteDb() throws Exception { - TestAccount anonymousCoward = accountCreator.create(); - assertUserBranchWithoutAccountConfig(anonymousCoward.id()); + public void randomNIds() throws Exception { + accountOperations.newAccount().create(); + accountOperations.newAccount().create(); + accountOperations.newAccount().create(); - String status = "OOO"; - Optional<AccountState> accountState = - accountsUpdateProvider - .get() - .update("Set status", anonymousCoward.id(), u -> u.setStatus(status)); - assertThat(accountState).isPresent(); - Account account = accountState.get().account(); - assertThat(account.fullName()).isNull(); - assertThat(account.status()).isEqualTo(status); - assertUserBranch(anonymousCoward.id(), null, status); - } + ImmutableList<Account.Id> result = accounts.randomNIds(2, 12345L); - private void assertUserBranchWithoutAccountConfig(Account.Id accountId) throws Exception { - assertUserBranch(accountId, null, null); - } - - private void assertUserBranch( - Account.Id accountId, @Nullable String name, @Nullable String status) throws Exception { - try (Repository repo = repoManager.openRepository(allUsers); - RevWalk rw = new RevWalk(repo); - ObjectReader or = repo.newObjectReader()) { - Ref ref = repo.exactRef(RefNames.refsUsers(accountId)); - assertThat(ref).isNotNull(); - RevCommit c = rw.parseCommit(ref.getObjectId()); - long timestampDiffMs = - Math.abs(c.getCommitTime() * 1000L - getAccount(accountId).registeredOn().toEpochMilli()); - assertThat(timestampDiffMs).isAtMost(SECONDS.toMillis(1)); - - // Check the 'account.config' file. - try (TreeWalk tw = TreeWalk.forPath(or, ACCOUNT_CONFIG, c.getTree())) { - if (name != null || status != null) { - assertThat(tw).isNotNull(); - Config cfg = new Config(); - cfg.fromText(new String(or.open(tw.getObjectId(0), OBJ_BLOB).getBytes(), UTF_8)); - assertThat(cfg) - .stringValue(ACCOUNT, null, AccountProperties.KEY_FULL_NAME) - .isEqualTo(name); - assertThat(cfg) - .stringValue(ACCOUNT, null, AccountProperties.KEY_STATUS) - .isEqualTo(status); - } else { - // No account properties were set, hence an 'account.config' file was not created. - assertThat(tw).isNull(); - } - } + assertThat(result).hasSize(2); + for (Account.Id id : result) { + assertThat(gApi.accounts().id(id.get()).get()).isNotNull(); } } @@ -1151,7 +986,7 @@ assertThat( gApi.accounts().id(foo.id().get()).getEmails().stream() .map(e -> e.email) - .collect(toSet())) + .collect(toImmutableSet())) .containsExactly(email, secondaryEmail); } @@ -1173,7 +1008,7 @@ assertThat( gApi.accounts().id(foo.id().get()).getEmails().stream() .map(e -> e.email) - .collect(toSet())) + .collect(toImmutableSet())) .containsExactly(email, secondaryEmail); } @@ -1419,7 +1254,7 @@ assertThat( gApi.accounts().self().getExternalIds().stream() .map(e -> e.identity) - .collect(toSet())) + .collect(toImmutableSet())) .containsAtLeast(extId1, extId2); requestScopeOperations.resetCurrentApiUser(); @@ -1433,7 +1268,7 @@ assertThat( gApi.accounts().self().getExternalIds().stream() .map(e -> e.identity) - .collect(toSet())) + .collect(toImmutableSet())) .containsNoneOf(extId1, extId2); } } @@ -1472,7 +1307,9 @@ requestScopeOperations.resetCurrentApiUser(); assertThat(getEmails()).contains(ldapEmail); assertThat( - gApi.accounts().self().getExternalIds().stream().map(e -> e.identity).collect(toSet())) + gApi.accounts().self().getExternalIds().stream() + .map(e -> e.identity) + .collect(toImmutableSet())) .contains(ldapExternalId); } @@ -1502,7 +1339,9 @@ admin.id(), ldapEmail))); assertThat( - gApi.accounts().self().getExternalIds().stream().map(e -> e.identity).collect(toSet())) + gApi.accounts().self().getExternalIds().stream() + .map(e -> e.identity) + .collect(toImmutableSet())) .containsAtLeast(ldapExternalId, nonLdapExternalId); requestScopeOperations.resetCurrentApiUser(); @@ -1513,7 +1352,9 @@ requestScopeOperations.resetCurrentApiUser(); assertThat(getExtIdsEmail()).doesNotContain(nonLdapEMail); assertThat( - gApi.accounts().self().getExternalIds().stream().map(e -> e.identity).collect(toSet())) + gApi.accounts().self().getExternalIds().stream() + .map(e -> e.identity) + .collect(toImmutableSet())) .contains(ldapExternalId); } @@ -1731,98 +1572,20 @@ public void refsUsersSelfIsAdvertised() throws Exception { TestRepository<?> testRepository = cloneProject(allUsers, user); try (Git git = testRepository.git()) { - List<String> advertisedRefs = - git.lsRemote().call().stream().map(Ref::getName).collect(toList()); + ImmutableList<String> advertisedRefs = + git.lsRemote().call().stream().map(Ref::getName).collect(toImmutableList()); assertThat(advertisedRefs).contains(RefNames.REFS_USERS_SELF); } } @Test - public void createDefaultUserBranch() throws Exception { - try (Repository repo = repoManager.openRepository(allUsers)) { - assertThat(repo.exactRef(RefNames.REFS_USERS_DEFAULT)).isNull(); - } - - projectOperations - .project(allUsers) - .forUpdate() - .add(allow(Permission.CREATE).ref(RefNames.REFS_USERS_DEFAULT).group(adminGroupUuid())) - .add(allow(Permission.PUSH).ref(RefNames.REFS_USERS_DEFAULT).group(adminGroupUuid())) - .update(); - - TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers); - pushFactory - .create(admin.newIdent(), allUsersRepo) - .to(RefNames.REFS_USERS_DEFAULT) - .assertOkStatus(); - - try (Repository repo = repoManager.openRepository(allUsers)) { - assertThat(repo.exactRef(RefNames.REFS_USERS_DEFAULT)).isNotNull(); - } - } - - @Test - public void cannotDeleteUserBranch() throws Exception { - projectOperations - .project(allUsers) - .forUpdate() - .add( - allow(Permission.DELETE) - .ref(RefNames.REFS_USERS + "${" + RefPattern.USERID_SHARDED + "}") - .group(REGISTERED_USERS) - .force(true)) - .update(); - - TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers); - String userRef = RefNames.refsUsers(admin.id()); - PushResult r = deleteRef(allUsersRepo, userRef); - RemoteRefUpdate refUpdate = r.getRemoteUpdate(userRef); - assertThat(refUpdate.getStatus()).isEqualTo(RemoteRefUpdate.Status.REJECTED_OTHER_REASON); - assertThat(refUpdate.getMessage()).contains("Not allowed to delete user branch."); - - try (Repository repo = repoManager.openRepository(allUsers)) { - assertThat(repo.exactRef(userRef)).isNotNull(); - } - } - - @Test - public void deleteUserBranchWithAccessDatabaseCapability() throws Exception { - projectOperations - .allProjectsForUpdate() - .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS)) - .update(); - projectOperations - .project(allUsers) - .forUpdate() - .add( - allow(Permission.DELETE) - .ref(RefNames.REFS_USERS + "${" + RefPattern.USERID_SHARDED + "}") - .group(REGISTERED_USERS) - .force(true)) - .update(); - - TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers); - String userRef = RefNames.refsUsers(admin.id()); - PushResult r = deleteRef(allUsersRepo, userRef); - RemoteRefUpdate refUpdate = r.getRemoteUpdate(userRef); - assertThat(refUpdate.getStatus()).isEqualTo(RemoteRefUpdate.Status.OK); - - try (Repository repo = repoManager.openRepository(allUsers)) { - assertThat(repo.exactRef(userRef)).isNull(); - } - - assertThat(accountCache.get(admin.id())).isEmpty(); - assertThat(accountQueryProvider.get().byDefault(admin.id().toString(), true)).isEmpty(); - } - - @Test public void addGpgKey() throws Exception { TestKey key = validKeyWithoutExpiration(); String id = key.getKeyIdString(); addExternalIdEmail(admin, "test1@example.com"); sender.clear(); - assertKeyMapContains(key, addGpgKey(key.getPublicKeyArmored())); + assertKeyMapContains(key, addGpgKeyForSelf(key.getPublicKeyArmored())); assertKeys(key); assertThat(sender.getMessages()).hasSize(1); assertThat(sender.getMessages().get(0).body()).contains("new GPG keys have been added"); @@ -1841,7 +1604,9 @@ sender.clear(); requestScopeOperations.setApiUser(admin.id()); - assertThrows(ResourceNotFoundException.class, () -> addGpgKey(user, key.getPublicKeyArmored())); + assertThrows( + ResourceNotFoundException.class, + () -> addGpgKeyForAccount(user, key.getPublicKeyArmored())); } @Test @@ -1852,7 +1617,7 @@ PGPPublicKey pk = key.getPublicKey(); sender.clear(); - GpgKeyInfo info = addGpgKey(armor(pk)).get(id); + GpgKeyInfo info = addGpgKeyForSelf(armor(pk)).get(id); assertThat(info.userIds).hasSize(2); assertIteratorSize(2, getOnlyKeyFromStore(key).getUserIDs()); assertThat(sender.getMessages()).hasSize(1); @@ -1885,12 +1650,13 @@ accountIndexedCounter.assertReindexOf(user); TestKey key = validKeyWithSecondUserId(); - addGpgKey(key.getPublicKeyArmored()); + addGpgKeyForSelf(key.getPublicKeyArmored()); requestScopeOperations.setApiUser(user.id()); ResourceConflictException thrown = assertThrows( - ResourceConflictException.class, () -> addGpgKey(user, key.getPublicKeyArmored())); + ResourceConflictException.class, + () -> addGpgKeyForAccount(user, key.getPublicKeyArmored())); assertThat(thrown) .hasMessageThat() .contains("GPG key already associated with another account"); @@ -1924,7 +1690,7 @@ TestKey key = validKeyWithoutExpiration(); String id = key.getKeyIdString(); addExternalIdEmail(admin, "test1@example.com"); - addGpgKey(key.getPublicKeyArmored()); + addGpgKeyForSelf(key.getPublicKeyArmored()); assertKeys(key); accountIndexedCounter.clear(); @@ -1996,7 +1762,8 @@ @Test public void addMalformedGpgKey() throws Exception { String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\ntest\n-----END PGP PUBLIC KEY BLOCK-----"; - BadRequestException unused = assertThrows(BadRequestException.class, () -> addGpgKey(key)); + BadRequestException unused = + assertThrows(BadRequestException.class, () -> addGpgKeyForSelf(key)); } @Test @@ -2213,33 +1980,6 @@ assertThat(accountQueryProvider.get().byDefault(name, true)).hasSize(2); } - @Test - public void checkMetaIdAndUniqueTag() throws Exception { - // In open-source Gerrit, the uniqueTag and metaId are always the same. Check them together - // in this test. - // metaId and uniqueTag are set when account is loaded - assertThat(accounts.get(admin.id()).get().account().metaId()).isEqualTo(getMetaId(admin.id())); - assertThat(accounts.get(admin.id()).get().account().uniqueTag()) - .isEqualTo(getMetaId(admin.id())); - - // metaId and uniqueTag are set when account is created - AccountsUpdate au = accountsUpdateProvider.get(); - Account.Id accountId = Account.id(seq.nextAccountId()); - AccountState accountState = au.insert("Create Test Account", accountId, u -> {}); - assertThat(accountState.account().metaId()).isEqualTo(getMetaId(accountId)); - assertThat(accountState.account().uniqueTag()).isEqualTo(getMetaId(accountId)); - - // metaId and uniqueTag are set when account is updated - Optional<AccountState> updatedAccountState = - au.update("Set Full Name", accountId, u -> u.setFullName("foo")); - assertThat(updatedAccountState).isPresent(); - Account updatedAccount = updatedAccountState.get().account(); - assertThat(accountState.account().metaId()).isNotEqualTo(updatedAccount.metaId()); - assertThat(accountState.account().uniqueTag()).isNotEqualTo(updatedAccount.uniqueTag()); - assertThat(updatedAccount.metaId()).isEqualTo(getMetaId(accountId)); - assertThat(updatedAccount.uniqueTag()).isEqualTo(getMetaId(accountId)); - } - private EmailInput newEmailInput(String email, boolean noConfirmation) { EmailInput input = new EmailInput(); input.email = email; @@ -2251,16 +1991,6 @@ return newEmailInput(email, true); } - @Nullable - private String getMetaId(Account.Id accountId) throws IOException { - try (Repository repo = repoManager.openRepository(allUsers); - RevWalk rw = new RevWalk(repo); - ObjectReader or = repo.newObjectReader()) { - Ref ref = repo.exactRef(RefNames.refsUsers(accountId)); - return ref != null ? ref.getObjectId().name() : null; - } - } - @Test public void allGroupsForAnAdminAccountCanBeRetrieved() throws Exception { List<GroupInfo> groups = gApi.accounts().id(admin.id().get()).getGroups(); @@ -2466,12 +2196,12 @@ "Set Status", admin.id(), (a, u) -> { - if ("A-1".equals(a.account().status())) { + if (Objects.equals(a.account().status(), "A-1")) { bgIndicatorA1ToB1.set(true); u.setStatus("B-1"); } - if ("A-2".equals(a.account().status())) { + if (Objects.equals(a.account().status(), "A-2")) { bgIndicatorA2ToB2.set(true); u.setStatus("B-2"); } @@ -2526,7 +2256,7 @@ assertThat( gApi.accounts().id(accountId.get()).getExternalIds().stream() .map(i -> i.identity) - .collect(toSet())) + .collect(toImmutableSet())) .containsExactly(extIdA1.key().get()); ExternalId extIdB1 = getExternalIdFactory().create("foo", "B-1", accountId); @@ -2558,99 +2288,11 @@ assertThat( gApi.accounts().id(accountId.get()).getExternalIds().stream() .map(i -> i.identity) - .collect(toSet())) + .collect(toImmutableSet())) .containsExactly(extIdB2.key().get()); } @Test - public void stalenessChecker() throws Exception { - // Newly created account is not stale. - AccountInfo accountInfo = gApi.accounts().create(name("foo")).get(); - Account.Id accountId = Account.id(accountInfo._accountId); - assertThat(stalenessChecker.check(accountId).isStale()).isFalse(); - - // Manually updating the user ref makes the index document stale. - String userRef = RefNames.refsUsers(accountId); - testRefAction( - () -> { - try (Repository repo = repoManager.openRepository(allUsers); - ObjectInserter oi = repo.newObjectInserter(); - RevWalk rw = new RevWalk(repo)) { - RevCommit commit = rw.parseCommit(repo.exactRef(userRef).getObjectId()); - - PersonIdent ident = new PersonIdent(serverIdent.get(), TimeUtil.now()); - CommitBuilder cb = new CommitBuilder(); - cb.setTreeId(commit.getTree()); - cb.setCommitter(ident); - cb.setAuthor(ident); - cb.setMessage(commit.getFullMessage()); - ObjectId emptyCommit = oi.insert(cb); - oi.flush(); - - RefUpdate updateRef = repo.updateRef(userRef); - updateRef.setExpectedOldObjectId(commit.toObjectId()); - updateRef.setNewObjectId(emptyCommit); - assertThat(updateRef.forceUpdate()).isEqualTo(RefUpdate.Result.FORCED); - } - }); - assertStaleAccountAndReindex(accountId); - - // Manually inserting/updating/deleting an external ID of the user makes the index document - // stale. - try (Repository repo = repoManager.openRepository(allUsers)) { - testRefAction( - () -> { - ExternalIdNotes extIdNotes = getExternalIdNotes(repo); - - ExternalId.Key key = externalIdKeyFactory.create("foo", "foo"); - extIdNotes.insert(getExternalIdFactory().create(key, accountId)); - try (MetaDataUpdate update = metaDataUpdateFactory.create(allUsers)) { - extIdNotes.commit(update); - } - assertStaleAccountAndReindex(accountId); - - extIdNotes = getExternalIdNotes(repo); - extIdNotes.upsert( - getExternalIdFactory().createWithEmail(key, accountId, "foo@example.com")); - try (MetaDataUpdate update = metaDataUpdateFactory.create(allUsers)) { - extIdNotes.commit(update); - } - assertStaleAccountAndReindex(accountId); - - extIdNotes = getExternalIdNotes(repo); - extIdNotes.delete(accountId, key); - try (MetaDataUpdate update = metaDataUpdateFactory.create(allUsers)) { - extIdNotes.commit(update); - } - }); - assertStaleAccountAndReindex(accountId); - } - - // Manually delete account - testRefAction( - () -> { - try (Repository repo = repoManager.openRepository(allUsers); - RevWalk rw = new RevWalk(repo)) { - RevCommit commit = rw.parseCommit(repo.exactRef(userRef).getObjectId()); - RefUpdate updateRef = repo.updateRef(userRef); - updateRef.setExpectedOldObjectId(commit.toObjectId()); - updateRef.setNewObjectId(ObjectId.zeroId()); - updateRef.setForceUpdate(true); - assertThat(updateRef.delete()).isEqualTo(RefUpdate.Result.FORCED); - } - }); - assertStaleAccountAndReindex(accountId); - } - - private void assertStaleAccountAndReindex(Account.Id accountId) throws IOException { - assertThat(stalenessChecker.check(accountId).isStale()).isTrue(); - - // Reindex fixes staleness - accountIndexer.index(accountId); - assertThat(stalenessChecker.check(accountId).isStale()).isFalse(); - } - - @Test @UseClockStep public void deleteAllDraftComments() throws Exception { try { @@ -2898,55 +2540,6 @@ } @Test - public void externalIdBatchUpdates() throws Exception { - String extId1String = "foo:bar"; - String extId2String = "foo:baz"; - ExternalId extId1 = - getExternalIdFactory() - .createWithEmail(externalIdKeyFactory.parse(extId1String), admin.id(), "1@foo.com"); - ExternalId extId2 = - getExternalIdFactory() - .createWithEmail(externalIdKeyFactory.parse(extId2String), user.id(), "2@foo.com"); - - int initialCommits = countExternalIdsCommits(); - AccountsUpdate.UpdateArguments ua1 = - new AccountsUpdate.UpdateArguments( - "Add External ID", admin.id(), u -> u.addExternalId(extId1)); - AccountsUpdate.UpdateArguments ua2 = - new AccountsUpdate.UpdateArguments( - "Add External ID", user.id(), u -> u.addExternalId(extId2)); - ImmutableList<Optional<AccountState>> accountStates = - accountsUpdateProvider.get().updateBatch(ImmutableList.of(ua1, ua2)); - assertThat(accountStates).hasSize(2); - assertThat(accountStates.get(0).get().externalIds()).contains(extId1); - assertThat(accountStates.get(1).get().externalIds()).contains(extId2); - assertThat( - gApi.accounts().id(admin.id().get()).getExternalIds().stream() - .map(e -> e.identity) - .collect(toSet())) - .contains(extId1String); - assertThat( - gApi.accounts().id(user.id().get()).getExternalIds().stream() - .map(e -> e.identity) - .collect(toSet())) - .contains(extId2String); - - // Ensure that we only applied one single commit. - int afterUpdateCommits = countExternalIdsCommits(); - assertThat(afterUpdateCommits).isEqualTo(initialCommits + 1); - } - - @UsedAt(UsedAt.Project.GOOGLE) - protected int countExternalIdsCommits() throws Exception { - try (Repository allUsersRepo = repoManager.openRepository(allUsers); - Git git = new Git(allUsersRepo)) { - ObjectId refsMetaExternalIdsHead = - allUsersRepo.exactRef(RefNames.REFS_EXTERNAL_IDS).getObjectId(); - return Iterables.size(git.log().add(refsMetaExternalIdsHead).call()); - } - } - - @Test public void externalIdBatchUpdates_fail_sameAccount() { ExternalId extId1 = getExternalIdFactory() @@ -2992,49 +2585,6 @@ } @Test - public void externalIdBatchUpdates_commitMsg_multipleAccounts() throws Exception { - ExternalId extId1 = - getExternalIdFactory() - .createWithEmail(externalIdKeyFactory.parse("foo:bar"), admin.id(), "1@foo.com"); - ExternalId extId2 = - getExternalIdFactory() - .createWithEmail(externalIdKeyFactory.parse("foo:baz"), user.id(), "2@foo.com"); - - AccountsUpdate.UpdateArguments ua1 = - new AccountsUpdate.UpdateArguments( - "first message", admin.id(), u -> u.addExternalId(extId1)); - AccountsUpdate.UpdateArguments ua2 = - new AccountsUpdate.UpdateArguments( - "second message", user.id(), u -> u.addExternalId(extId2)); - accountsUpdateProvider.get().updateBatch(ImmutableList.of(ua1, ua2)); - - try (Repository allUsersRepo = repoManager.openRepository(allUsers); - RevWalk rw = new RevWalk(allUsersRepo)) { - RevCommit commit = - rw.parseCommit(allUsersRepo.exactRef(RefNames.REFS_EXTERNAL_IDS).getObjectId()); - - assertThat(commit.getFullMessage()).isEqualTo("Batch update for 2 accounts\n"); - } - } - - @Test - public void externalIdBatchUpdates_commitMsg_singleAccount() throws Exception { - ExternalId extId = - getExternalIdFactory() - .createWithEmail(externalIdKeyFactory.parse("foo:bar"), admin.id(), "1@foo.com"); - - accountsUpdateProvider.get().update("foobar", admin.id(), u -> u.addExternalId(extId)); - - try (Repository allUsersRepo = repoManager.openRepository(allUsers); - RevWalk rw = new RevWalk(allUsersRepo)) { - RevCommit commit = - rw.parseCommit(allUsersRepo.exactRef(RefNames.REFS_EXTERNAL_IDS).getObjectId()); - - assertThat(commit.getFullMessage()).isEqualTo("foobar\n"); - } - } - - @Test public void searchForSecondaryEmailRequiresModifyAccountPermission() throws Exception { String email = "preferred@example.com"; TestAccount foo = accountCreator.create(name("foo"), email, "Foo", null); @@ -3056,24 +2606,6 @@ .isEqualTo(foo.id().get()); } - @Test - public void getAccountFromMetaId() throws Exception { - AccountState preUpdateState = accountCache.get(admin.id()).get(); - requestScopeOperations.setApiUser(admin.id()); - gApi.accounts().self().setStatus("New status"); - - AccountState postUpdateStatus = accountCache.get(admin.id()).get(); - assertThat(postUpdateStatus).isNotEqualTo(preUpdateState); - assertThat( - accountCache.getFromMetaId( - admin.id(), ObjectId.fromString(preUpdateState.account().metaId()))) - .isEqualTo(preUpdateState); - assertThat( - accountCache.getFromMetaId( - admin.id(), ObjectId.fromString(postUpdateStatus.account().metaId()))) - .isEqualTo(postUpdateStatus); - } - @CanIgnoreReturnValue private CommentInfo createDraft(Result r, String path, String message) throws Exception { DraftInput in = new DraftInput(); @@ -3107,204 +2639,6 @@ } @Test - public void projectWatchesUpdate_refsUsersUpdated() throws Exception { - AccountState preUpdateState = accountCache.get(admin.id()).get(); - requestScopeOperations.setApiUser(admin.id()); - - ProjectWatchInfo projectWatchInfo = new ProjectWatchInfo(); - projectWatchInfo.project = project.get(); - projectWatchInfo.notifyAllComments = true; - gApi.accounts().self().setWatchedProjects(ImmutableList.of(projectWatchInfo)); - - AccountState updatedState1 = accountCache.get(admin.id()).get(); - assertThat(preUpdateState.account().metaId()).isNotEqualTo(updatedState1.account().metaId()); - - gApi.accounts().self().deleteWatchedProjects(ImmutableList.of(projectWatchInfo)); - - AccountState updatedState2 = accountCache.get(admin.id()).get(); - assertThat(updatedState1.account().metaId()).isNotEqualTo(updatedState2.account().metaId()); - } - - @Test - public void updateExternalId_externalIdApiUpdate_refsUsersUpdated() throws Exception { - AccountState preUpdateState = accountCache.get(admin.id()).get(); - requestScopeOperations.setApiUser(admin.id()); - - gApi.accounts().self().addEmail(newEmailInput("secondary@non.google")); - assertExternalIds( - admin.id(), - ImmutableSet.of( - "mailto:admin@example.com", "username:admin", "mailto:secondary@non.google")); - - AccountState updatedState1 = accountCache.get(admin.id()).get(); - assertThat(preUpdateState.account().metaId()).isNotEqualTo(updatedState1.account().metaId()); - - gApi.accounts().self().deleteExternalIds(ImmutableList.of("mailto:secondary@non.google")); - - AccountState updatedState2 = accountCache.get(admin.id()).get(); - assertThat(updatedState1.account().metaId()).isNotEqualTo(updatedState2.account().metaId()); - } - - @Test - public void addExternalId_accountUpdate_refsUsersUpdated() throws Exception { - AccountState preUpdateState = accountCache.get(admin.id()).get(); - requestScopeOperations.setApiUser(admin.id()); - - ExternalId externalId = getExternalIdFactory().create("custom", "value", admin.id()); - accountsUpdateProvider - .get() - .update("Add External ID", admin.id(), u -> u.addExternalId(externalId)); - assertExternalIds( - admin.id(), ImmutableSet.of("mailto:admin@example.com", "username:admin", "custom:value")); - - AccountState updatedState = accountCache.get(admin.id()).get(); - assertThat(preUpdateState.account().metaId()).isNotEqualTo(updatedState.account().metaId()); - } - - @Test - public void deleteExternalId_accountUpdate_refsUsersUpdated() throws Exception { - AccountState preUpdateState = accountCache.get(admin.id()).get(); - requestScopeOperations.setApiUser(admin.id()); - - ExternalId externalId = createEmailExternalId(admin.id(), "admin@example.com"); - accountsUpdateProvider - .get() - .update("Remove External ID", admin.id(), u -> u.deleteExternalId(externalId)); - assertExternalIds(admin.id(), ImmutableSet.of("username:admin")); - - AccountState updatedState = accountCache.get(admin.id()).get(); - assertThat(preUpdateState.account().metaId()).isNotEqualTo(updatedState.account().metaId()); - } - - @Test - public void updateExternalId_accountUpdate_refsUsersUpdated() throws Exception { - AccountState preUpdateState = accountCache.get(admin.id()).get(); - requestScopeOperations.setApiUser(admin.id()); - - ExternalId externalId = - getExternalIdFactory() - .createWithEmail( - SCHEME_MAILTO, "secondary@non.google", admin.id(), "secondary@non.google"); - accountsUpdateProvider - .get() - .update("Update External ID", admin.id(), u -> u.updateExternalId(externalId)); - assertExternalIds( - admin.id(), - ImmutableSet.of( - "mailto:admin@example.com", "username:admin", "mailto:secondary@non.google")); - - AccountState updatedState = accountCache.get(admin.id()).get(); - assertThat(preUpdateState.account().metaId()).isNotEqualTo(updatedState.account().metaId()); - } - - @Test - public void replaceExternalId_accountUpdate_refsUsersUpdated() throws Exception { - AccountState preUpdateState = accountCache.get(admin.id()).get(); - requestScopeOperations.setApiUser(admin.id()); - - ExternalId externalId = - getExternalIdFactory() - .createWithEmail( - SCHEME_MAILTO, "secondary@non.google", admin.id(), "secondary@non.google"); - ExternalId oldExternalId = - getExternalIdsReader().get(createEmailExternalId(admin.id(), admin.email()).key()).get(); - accountsUpdateProvider - .get() - .update( - "Replace External ID", admin.id(), u -> u.replaceExternalId(oldExternalId, externalId)); - assertExternalIds(admin.id(), ImmutableSet.of("mailto:secondary@non.google", "username:admin")); - - AccountState updatedState = accountCache.get(admin.id()).get(); - assertThat(accountCache.get(admin.id()).get()).isNotSameInstanceAs(preUpdateState); - if (preUpdateState.account().metaId() == null) { - // When the test is executed on google infrastructure, metaId should be either always set - // or always be null. - assertThat(updatedState.account().metaId()).isNull(); - } else { - assertThat(preUpdateState.account().metaId()).isNotEqualTo(updatedState.account().metaId()); - } - } - - @Test - public void accountUpdate_updateBatch_allUsersExternalIdsUpdated_refsUsersUpdated() - throws Exception { - AccountState preUpdateAdminState = accountCache.get(admin.id()).get(); - AccountState preUpdateUserState = accountCache.get(user.id()).get(); - - requestScopeOperations.setApiUser(admin.id()); - ExternalId extId1 = - getExternalIdFactory() - .createWithEmail("custom", "admin-id", admin.id(), "admin-id@test.com"); - - ExternalId extId2 = - getExternalIdFactory().createWithEmail("custom", "user-id", user.id(), "user-id@test.com"); - - AccountsUpdate.UpdateArguments ua1 = - new AccountsUpdate.UpdateArguments( - "Add External ID", admin.id(), u -> u.addExternalId(extId1)); - AccountsUpdate.UpdateArguments ua2 = - new AccountsUpdate.UpdateArguments( - "Add External ID", user.id(), u -> u.addExternalId(extId2)); - AccountIndexedCounter accountIndexedCounter = getAccountIndexedCounter(); - try (Registration registration = - extensionRegistry.newRegistration().add(accountIndexedCounter)) { - accountsUpdateProvider.get().updateBatch(ImmutableList.of(ua1, ua2)); - } - accountIndexedCounter.assertReindexOf(admin.id(), 1); - accountIndexedCounter.assertReindexOf(user.id(), 1); - - assertExternalIds( - admin.id(), - ImmutableSet.of("mailto:admin@example.com", "username:admin", "custom:admin-id")); - assertExternalIds( - user.id(), ImmutableSet.of("username:user1", "mailto:user1@example.com", "custom:user-id")); - // Assert reindexing has worked on the updated accounts. - assertThat( - Iterables.getOnlyElement(gApi.accounts().query("admin-id@test.com").get())._accountId) - .isEqualTo(admin.id().get()); - assertThat(Iterables.getOnlyElement(gApi.accounts().query("user-id@test.com").get())._accountId) - .isEqualTo(user.id().get()); - AccountState updatedAdminState = accountCache.get(admin.id()).get(); - AccountState updatedUserState = accountCache.get(user.id()).get(); - assertThat(preUpdateAdminState.account().metaId()) - .isNotEqualTo(updatedAdminState.account().metaId()); - assertThat(preUpdateUserState.account().metaId()) - .isNotEqualTo(updatedUserState.account().metaId()); - } - - @Test - public void accountUpdate_updateBatch_someUsersExternalIdsUpdated_refsUsersUpdated() - throws Exception { - AccountState preUpdateAdminState = accountCache.get(admin.id()).get(); - AccountState preUpdateUserState = accountCache.get(user.id()).get(); - - requestScopeOperations.setApiUser(admin.id()); - AccountsUpdate.UpdateArguments ua1 = - new AccountsUpdate.UpdateArguments( - "Update Display Name", admin.id(), u -> u.setDisplayName("DN")); - AccountsUpdate.UpdateArguments ua2 = - new AccountsUpdate.UpdateArguments( - "Remove external Id", - user.id(), - u -> u.deleteExternalId(createEmailExternalId(user.id(), user.email()))); - AccountIndexedCounter accountIndexedCounter = getAccountIndexedCounter(); - try (Registration registration = - extensionRegistry.newRegistration().add(accountIndexedCounter)) { - accountsUpdateProvider.get().updateBatch(ImmutableList.of(ua1, ua2)); - } - accountIndexedCounter.assertReindexOf(admin.id(), 1); - accountIndexedCounter.assertReindexOf(user.id(), 1); - - // Only the version in config of the user with external id update was updated. - AccountState updatedAdminState = accountCache.get(admin.id()).get(); - AccountState updatedUserState = accountCache.get(user.id()).get(); - assertThat(preUpdateAdminState.account().metaId()) - .isNotEqualTo(updatedAdminState.account().metaId()); - assertThat(preUpdateUserState.account().metaId()) - .isNotEqualTo(updatedUserState.account().metaId()); - } - - @Test public void accountUpdate_emptyStringsToUnset() throws Exception { AccountState preUpdateState = accountCache.get(admin.id()).get(); requestScopeOperations.setApiUser(admin.id()); @@ -3485,7 +2819,7 @@ deleted, PushCertificateIdent.parse(validKeyWithoutExpiration().getFirstUserId()).getEmailAddress()); TestKey key = validKeyWithoutExpiration(); - addGpgKey(deleted, key.getPublicKeyArmored()); + addGpgKeyForAccount(deleted, key.getPublicKeyArmored()); assertKeys(key); assertIteratorSize(1, getOnlyKeyFromStore(key).getUserIDs()); @@ -3781,10 +3115,11 @@ GroupMembership testGroupMembership = new GroupMembership() { @Override - public Set<AccountGroup.UUID> intersection(Iterable<AccountGroup.UUID> groupUuids) { + public ImmutableSet<AccountGroup.UUID> intersection( + Iterable<AccountGroup.UUID> groupUuids) { return StreamSupport.stream(groupUuids.spliterator(), /* parallel= */ false) .filter(this::contains) - .collect(toSet()); + .collect(toImmutableSet()); } @Override @@ -3819,7 +3154,7 @@ return testGroupBackend; } - private void assertExternalIds(Account.Id accountId, ImmutableSet<String> extIds) + protected void assertExternalIds(Account.Id accountId, ImmutableSet<String> extIds) throws Exception { assertExternalIds( gApi.accounts().id(accountId.get()).getExternalIds().stream() @@ -3911,10 +3246,10 @@ Account.Id currAccountId = localCtx.getContext().getUser().getAccountId(); Iterable<String> expectedFps = expected.transform(k -> BaseEncoding.base16().encode(k.getPublicKey().getFingerprint())); - Set<String> actualFps = + ImmutableSet<String> actualFps = getExternalIdsReader().byAccount(currAccountId, SCHEME_GPGKEY).stream() .map(e -> e.key().id()) - .collect(toSet()); + .collect(toImmutableSet()); assertWithMessage("external IDs in database") .that(actualFps) .containsExactlyElementsIn(expectedFps); @@ -3962,12 +3297,13 @@ } @CanIgnoreReturnValue - private Map<String, GpgKeyInfo> addGpgKey(String armored) throws Exception { - return addGpgKey(admin, armored); + private Map<String, GpgKeyInfo> addGpgKeyForSelf(String armored) throws Exception { + return addGpgKeyForAccount(admin, armored); } @CanIgnoreReturnValue - private Map<String, GpgKeyInfo> addGpgKey(TestAccount account, String armored) throws Exception { + private Map<String, GpgKeyInfo> addGpgKeyForAccount(TestAccount account, String armored) + throws Exception { return testRefAction( () -> { AccountIndexedCounter accountIndexedCounter = getAccountIndexedCounter(); @@ -4021,15 +3357,13 @@ return gApi.accounts().id(user.id().get()); } - private Set<String> getCookiesNames() { - Set<String> cookieNames = - httpCookieStore.getCookies().stream() - .map(cookie -> cookie.getName()) - .collect(Collectors.toSet()); - return cookieNames; + private ImmutableSet<String> getCookiesNames() { + return httpCookieStore.getCookies().stream() + .map(cookie -> cookie.getName()) + .collect(toImmutableSet()); } - private void webLogin(Integer accountId) throws IOException, ClientProtocolException { + private void webLogin(Integer accountId) throws IOException { httpGetAndAssertStatus( "login?account_id=" + accountId, HttpServletResponse.SC_MOVED_TEMPORARILY); } @@ -4051,50 +3385,21 @@ r -> r.withBlockStrategy(noSleepBlockStrategy))); } - private ExternalIdNotes getExternalIdNotes(Repository allUsersRepo) - throws ConfigInvalidException, IOException { - return ExternalIdNotes.load( - allUsers, - allUsersRepo, - externalIdFactoryNoteDbImpl, - authConfig.isUserNameCaseInsensitiveMigrationMode()); - } + @UsedAt(UsedAt.Project.GOOGLE) + protected abstract AccountsUpdate getAccountsUpdateWithRunnables( + Runnable afterReadRevision, Runnable beforeCommit, RetryHelper retryHelper); @UsedAt(UsedAt.Project.GOOGLE) protected ExternalIdFactory getExternalIdFactory() { - return externalIdFactoryNoteDbImpl; + return externalIdFactory; } @UsedAt(UsedAt.Project.GOOGLE) protected ExternalIds getExternalIdsReader() { - return externalIdsNoteDbImpl; + return externalIds; } - @UsedAt(UsedAt.Project.GOOGLE) - protected AccountsUpdate getAccountsUpdateWithRunnables( - Runnable afterReadRevision, Runnable beforeCommit, RetryHelper retryHelper) { - return getAccountsUpdateNoteDbImplWithRunnables(afterReadRevision, beforeCommit, retryHelper); - } - - @UsedAt(UsedAt.Project.GOOGLE) - protected final AccountsUpdateNoteDbImpl getAccountsUpdateNoteDbImplWithRunnables( - Runnable afterReadRevision, Runnable beforeCommit, RetryHelper retryHelper) { - return new AccountsUpdateNoteDbImpl( - repoManager, - gitReferenceUpdated, - Optional.empty(), - allUsers, - externalIdsNoteDbImpl, - extIdNotesFactory, - metaDataUpdateInternalFactory, - retryHelper, - serverIdent.get(), - afterReadRevision, - beforeCommit); - } - - private void httpGetAndAssertStatus(String urlPath, int expectedHttpStatus) - throws ClientProtocolException, IOException { + private void httpGetAndAssertStatus(String urlPath, int expectedHttpStatus) throws IOException { HttpGet httpGet = new HttpGet(canonicalWebUrl.get() + urlPath); HttpResponse loginResponse = httpclient.execute(httpGet); assertThat(loginResponse.getStatusLine().getStatusCode()).isEqualTo(expectedHttpStatus); @@ -4152,7 +3457,7 @@ } public static class TestAccountStateProvider implements AccountStateProvider { - private ArrayList<MetadataInfo> metadataList = new ArrayList<>(); + private final List<MetadataInfo> metadataList = new ArrayList<>(); public MetadataInfo addMetadata( String name, @Nullable String value, @Nullable String description) { @@ -4169,4 +3474,34 @@ return ImmutableList.copyOf(metadataList); } } + + @Test + @GerritConfig(name = "accounts.visibility", value = "SAME_GROUP") + public void queryAccountsVisibilitySameGroup() throws Exception { + TestAccount user2 = accountCreator.user2(); + + // Switch to user context (user and user2 are not in the same group by default) + requestScopeOperations.setApiUser(user.id()); + + // Querying for user2 should return nothing + List<AccountInfo> result = gApi.accounts().query("email:" + user2.email()).get(); + assertThat(result).isEmpty(); + + // Querying for user2 with details should also return nothing (reproduces Bypass 1) + result = + gApi.accounts() + .query("email:" + user2.email()) + .withOption(ListAccountsOption.DETAILS) + .get(); + assertThat(result).isEmpty(); + + // Suggesting for user2 should also return nothing (reproduces Bypass 2) + result = gApi.accounts().suggestAccounts(user2.email()).get(); + assertThat(result).isEmpty(); + + // Querying for self should still work + result = gApi.accounts().query("email:" + user.email()).get(); + assertThat(result).hasSize(1); + assertThat(result.get(0)._accountId).isEqualTo(user.id().get()); + } }
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountNoteDbIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountNoteDbIT.java new file mode 100644 index 0000000..7a32314 --- /dev/null +++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountNoteDbIT.java
@@ -0,0 +1,783 @@ +package com.google.gerrit.acceptance.api.accounts; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.common.truth.Truth.assertThat; +import static com.google.gerrit.acceptance.GitUtil.deleteRef; +import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow; +import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability; +import static com.google.gerrit.server.account.AccountProperties.ACCOUNT; +import static com.google.gerrit.server.account.AccountProperties.ACCOUNT_CONFIG; +import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_MAILTO; +import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; +import static com.google.gerrit.testing.TestActionRefUpdateContext.testRefAction; +import static com.google.gerrit.truth.ConfigSubject.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.gerrit.acceptance.AccountIndexedCounter; +import com.google.gerrit.acceptance.ExtensionRegistry.Registration; +import com.google.gerrit.acceptance.TestAccount; +import com.google.gerrit.acceptance.UseClockStep; +import com.google.gerrit.common.Nullable; +import com.google.gerrit.common.UsedAt; +import com.google.gerrit.common.data.GlobalCapability; +import com.google.gerrit.entities.Account; +import com.google.gerrit.entities.Permission; +import com.google.gerrit.entities.RefNames; +import com.google.gerrit.extensions.client.ProjectWatchInfo; +import com.google.gerrit.extensions.common.AccountInfo; +import com.google.gerrit.extensions.common.EmailInfo; +import com.google.gerrit.server.Sequence; +import com.google.gerrit.server.account.AccountProperties; +import com.google.gerrit.server.account.AccountState; +import com.google.gerrit.server.account.AccountsUpdate; +import com.google.gerrit.server.account.externalids.ExternalId; +import com.google.gerrit.server.account.externalids.ExternalIdFactory; +import com.google.gerrit.server.account.externalids.ExternalIds; +import com.google.gerrit.server.account.externalids.storage.notedb.ExternalIdFactoryNoteDbImpl; +import com.google.gerrit.server.account.externalids.storage.notedb.ExternalIdNotes; +import com.google.gerrit.server.account.externalids.storage.notedb.ExternalIdsNoteDbImpl; +import com.google.gerrit.server.account.storage.notedb.AccountsUpdateNoteDbImpl; +import com.google.gerrit.server.extensions.events.GitReferenceUpdated; +import com.google.gerrit.server.git.meta.MetaDataUpdate; +import com.google.gerrit.server.index.account.AccountIndexer; +import com.google.gerrit.server.index.account.StalenessChecker; +import com.google.gerrit.server.project.RefPattern; +import com.google.gerrit.server.update.RetryHelper; +import com.google.gerrit.server.util.time.TimeUtil; +import com.google.inject.Inject; +import java.io.IOException; +import java.time.Duration; +import java.util.List; +import java.util.Optional; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.PushResult; +import org.eclipse.jgit.transport.RemoteRefUpdate; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.junit.Test; + +public class AccountNoteDbIT extends AbstractAccountIT { + @Inject private AccountIndexer accountIndexer; + @Inject private StalenessChecker stalenessChecker; + @Inject private ExternalIdNotes.Factory extIdNotesFactory; + @Inject private ExternalIdsNoteDbImpl externalIdsNoteDbImpl; + @Inject private GitReferenceUpdated gitReferenceUpdated; + @Inject private ExternalIdFactoryNoteDbImpl externalIdFactoryNoteDbImpl; + + @Override + protected ExternalIdFactory getExternalIdFactory() { + return externalIdFactoryNoteDbImpl; + } + + @Override + protected ExternalIds getExternalIdsReader() { + return externalIdsNoteDbImpl; + } + + @Override + protected AccountsUpdate getAccountsUpdateWithRunnables( + Runnable afterReadRevision, Runnable beforeCommit, RetryHelper retryHelper) { + return getAccountsUpdateNoteDbImplWithRunnables(afterReadRevision, beforeCommit, retryHelper); + } + + @UsedAt(UsedAt.Project.GOOGLE) + protected Account.Id createByAccountCreator(int expectedAccountReindexCalls) throws Exception { + AccountIndexedCounter accountIndexedCounter = getAccountIndexedCounter(); + try (Registration registration = + extensionRegistry.newRegistration().add(accountIndexedCounter)) { + String name = "foo"; + TestAccount foo = accountCreator.create(name); + AccountInfo info = gApi.accounts().id(foo.id().get()).get(); + if (server.isUsernameSupported()) { + assertThat(info.username).isEqualTo(name); + } else { + assertThat(info.email).isEqualTo(foo.email()); + } + assertThat(info.name).isEqualTo(name); + accountIndexedCounter.assertReindexOf(foo, expectedAccountReindexCalls); + assertUserBranch(foo.id(), name, null); + return foo.id(); + } + } + + @Test + public void createByAccountCreator() throws Exception { + RefUpdateCounter refUpdateCounter = createRefUpdateCounter(); + try (Registration registration = extensionRegistry.newRegistration().add(refUpdateCounter)) { + Account.Id accountId = createByAccountCreator(1); + refUpdateCounter.assertRefUpdateFor( + RefUpdateCounter.projectRef(allUsers, RefNames.refsUsers(accountId)), + RefUpdateCounter.projectRef(allUsers, RefNames.REFS_EXTERNAL_IDS), + RefUpdateCounter.projectRef(allUsers, RefNames.REFS_SEQUENCES + Sequence.NAME_ACCOUNTS)); + } + } + + @Test + public void createAnonymousCowardByAccountCreator() throws Exception { + AccountIndexedCounter accountIndexedCounter = getAccountIndexedCounter(); + try (Registration registration = + extensionRegistry.newRegistration().add(accountIndexedCounter)) { + TestAccount anonymousCoward = accountCreator.create(); + accountIndexedCounter.assertReindexOf(anonymousCoward); + assertUserBranchWithoutAccountConfig(anonymousCoward.id()); + } + } + + @Test + public void commitMessageOnAccountUpdates() throws Exception { + AccountsUpdate au = accountsUpdateProvider.get(); + Account.Id accountId = Account.id(seq.nextAccountId()); + au.insert("Create Test Account", accountId, u -> {}); + assertLastCommitMessageOfUserBranch(accountId, "Create Test Account"); + + au.update("Set Status", accountId, u -> u.setStatus("Foo")); + assertLastCommitMessageOfUserBranch(accountId, "Set Status"); + } + + private void assertLastCommitMessageOfUserBranch(Account.Id accountId, String expectedMessage) + throws Exception { + try (Repository repo = repoManager.openRepository(allUsers); + RevWalk rw = new RevWalk(repo)) { + Ref exactRef = repo.exactRef(RefNames.refsUsers(accountId)); + assertThat(rw.parseCommit(exactRef.getObjectId()).getShortMessage()) + .isEqualTo(expectedMessage); + } + } + + @Test + @UseClockStep + public void createAtomically() throws Exception { + Account.Id accountId = Account.id(seq.nextAccountId()); + String fullName = "Foo"; + ExternalId extId = getExternalIdFactory().createEmail(accountId, "foo@example.com"); + AccountState accountState = + accountsUpdateProvider + .get() + .insert( + "Create Account Atomically", + accountId, + u -> u.setFullName(fullName).addExternalId(extId)); + assertThat(accountState.account().fullName()).isEqualTo(fullName); + + AccountInfo info = gApi.accounts().id(accountId.get()).get(); + assertThat(info.name).isEqualTo(fullName); + + List<EmailInfo> emails = gApi.accounts().id(accountId.get()).getEmails(); + assertThat(emails.stream().map(e -> e.email).collect(toImmutableSet())) + .containsExactly(extId.email()); + + RevCommit commitUserBranch = + projectOperations.project(allUsers).getHead(RefNames.refsUsers(accountId)); + RevCommit commitRefsMetaExternalIds = + projectOperations.project(allUsers).getHead(RefNames.REFS_EXTERNAL_IDS); + assertThat(commitUserBranch.getCommitTime()) + .isEqualTo(commitRefsMetaExternalIds.getCommitTime()); + } + + @Test + public void updateAccountWithoutAccountConfigNoteDb() throws Exception { + TestAccount anonymousCoward = accountCreator.create(); + assertUserBranchWithoutAccountConfig(anonymousCoward.id()); + + String status = "OOO"; + Optional<AccountState> accountState = + accountsUpdateProvider + .get() + .update("Set status", anonymousCoward.id(), u -> u.setStatus(status)); + assertThat(accountState).isPresent(); + Account account = accountState.get().account(); + assertThat(account.fullName()).isNull(); + assertThat(account.status()).isEqualTo(status); + assertUserBranch(anonymousCoward.id(), null, status); + } + + private void assertUserBranchWithoutAccountConfig(Account.Id accountId) throws Exception { + assertUserBranch(accountId, null, null); + } + + private void assertUserBranch( + Account.Id accountId, @Nullable String name, @Nullable String status) throws Exception { + try (Repository repo = repoManager.openRepository(allUsers); + RevWalk rw = new RevWalk(repo); + ObjectReader or = repo.newObjectReader()) { + Ref ref = repo.exactRef(RefNames.refsUsers(accountId)); + assertThat(ref).isNotNull(); + RevCommit c = rw.parseCommit(ref.getObjectId()); + long timestampDiffMs = + Math.abs(c.getCommitTime() * 1000L - getAccount(accountId).registeredOn().toEpochMilli()); + assertThat(timestampDiffMs).isAtMost(Duration.ofSeconds(1).toMillis()); + + // Check the 'account.config' file. + try (TreeWalk tw = TreeWalk.forPath(or, ACCOUNT_CONFIG, c.getTree())) { + if (name != null || status != null) { + assertThat(tw).isNotNull(); + Config cfg = new Config(); + cfg.fromText(new String(or.open(tw.getObjectId(0), OBJ_BLOB).getBytes(), UTF_8)); + assertThat(cfg) + .stringValue(ACCOUNT, null, AccountProperties.KEY_FULL_NAME) + .isEqualTo(name); + assertThat(cfg) + .stringValue(ACCOUNT, null, AccountProperties.KEY_STATUS) + .isEqualTo(status); + } else { + // No account properties were set, hence an 'account.config' file was not created. + assertThat(tw).isNull(); + } + } + } + } + + @Test + public void checkMetaIdAndUniqueTag() throws Exception { + // In open-source Gerrit, the uniqueTag and metaId are always the same. Check them together + // in this test. + // metaId and uniqueTag are set when account is loaded + assertThat(accounts.get(admin.id()).get().account().metaId()).isEqualTo(getMetaId(admin.id())); + assertThat(accounts.get(admin.id()).get().account().uniqueTag()) + .isEqualTo(getMetaId(admin.id())); + + // metaId and uniqueTag are set when account is created + AccountsUpdate au = accountsUpdateProvider.get(); + Account.Id accountId = Account.id(seq.nextAccountId()); + AccountState accountState = au.insert("Create Test Account", accountId, u -> {}); + assertThat(accountState.account().metaId()).isEqualTo(getMetaId(accountId)); + assertThat(accountState.account().uniqueTag()).isEqualTo(getMetaId(accountId)); + + // metaId and uniqueTag are set when account is updated + Optional<AccountState> updatedAccountState = + au.update("Set Full Name", accountId, u -> u.setFullName("foo")); + assertThat(updatedAccountState).isPresent(); + Account updatedAccount = updatedAccountState.get().account(); + assertThat(accountState.account().metaId()).isNotEqualTo(updatedAccount.metaId()); + assertThat(accountState.account().uniqueTag()).isNotEqualTo(updatedAccount.uniqueTag()); + assertThat(updatedAccount.metaId()).isEqualTo(getMetaId(accountId)); + assertThat(updatedAccount.uniqueTag()).isEqualTo(getMetaId(accountId)); + } + + @Nullable + private String getMetaId(Account.Id accountId) throws IOException { + try (Repository repo = repoManager.openRepository(allUsers); + RevWalk rw = new RevWalk(repo); + ObjectReader or = repo.newObjectReader()) { + Ref ref = repo.exactRef(RefNames.refsUsers(accountId)); + return ref != null ? ref.getObjectId().name() : null; + } + } + + @Test + public void externalIdBatchUpdates() throws Exception { + String extId1String = "foo:bar"; + String extId2String = "foo:baz"; + ExternalId extId1 = + getExternalIdFactory() + .createWithEmail(externalIdKeyFactory.parse(extId1String), admin.id(), "1@foo.com"); + ExternalId extId2 = + getExternalIdFactory() + .createWithEmail(externalIdKeyFactory.parse(extId2String), user.id(), "2@foo.com"); + + int initialCommits = countExternalIdsCommits(); + AccountsUpdate.UpdateArguments ua1 = + new AccountsUpdate.UpdateArguments( + "Add External ID", admin.id(), u -> u.addExternalId(extId1)); + AccountsUpdate.UpdateArguments ua2 = + new AccountsUpdate.UpdateArguments( + "Add External ID", user.id(), u -> u.addExternalId(extId2)); + ImmutableList<Optional<AccountState>> accountStates = + accountsUpdateProvider.get().updateBatch(ImmutableList.of(ua1, ua2)); + assertThat(accountStates).hasSize(2); + assertThat(accountStates.get(0).get().externalIds()).contains(extId1); + assertThat(accountStates.get(1).get().externalIds()).contains(extId2); + assertThat( + gApi.accounts().id(admin.id().get()).getExternalIds().stream() + .map(e -> e.identity) + .collect(toImmutableSet())) + .contains(extId1String); + assertThat( + gApi.accounts().id(user.id().get()).getExternalIds().stream() + .map(e -> e.identity) + .collect(toImmutableSet())) + .contains(extId2String); + + // Ensure that we only applied one single commit. + int afterUpdateCommits = countExternalIdsCommits(); + assertThat(afterUpdateCommits).isEqualTo(initialCommits + 1); + } + + @UsedAt(UsedAt.Project.GOOGLE) + protected int countExternalIdsCommits() throws Exception { + try (Repository allUsersRepo = repoManager.openRepository(allUsers); + Git git = new Git(allUsersRepo)) { + ObjectId refsMetaExternalIdsHead = + allUsersRepo.exactRef(RefNames.REFS_EXTERNAL_IDS).getObjectId(); + return Iterables.size(git.log().add(refsMetaExternalIdsHead).call()); + } + } + + @Test + public void externalIdBatchUpdates_commitMsg_multipleAccounts() throws Exception { + ExternalId extId1 = + getExternalIdFactory() + .createWithEmail(externalIdKeyFactory.parse("foo:bar"), admin.id(), "1@foo.com"); + ExternalId extId2 = + getExternalIdFactory() + .createWithEmail(externalIdKeyFactory.parse("foo:baz"), user.id(), "2@foo.com"); + + AccountsUpdate.UpdateArguments ua1 = + new AccountsUpdate.UpdateArguments( + "first message", admin.id(), u -> u.addExternalId(extId1)); + AccountsUpdate.UpdateArguments ua2 = + new AccountsUpdate.UpdateArguments( + "second message", user.id(), u -> u.addExternalId(extId2)); + accountsUpdateProvider.get().updateBatch(ImmutableList.of(ua1, ua2)); + + try (Repository allUsersRepo = repoManager.openRepository(allUsers); + RevWalk rw = new RevWalk(allUsersRepo)) { + RevCommit commit = + rw.parseCommit(allUsersRepo.exactRef(RefNames.REFS_EXTERNAL_IDS).getObjectId()); + + assertThat(commit.getFullMessage()).isEqualTo("Batch update for 2 accounts\n"); + } + } + + @Test + public void externalIdBatchUpdates_commitMsg_singleAccount() throws Exception { + ExternalId extId = + getExternalIdFactory() + .createWithEmail(externalIdKeyFactory.parse("foo:bar"), admin.id(), "1@foo.com"); + + accountsUpdateProvider.get().update("foobar", admin.id(), u -> u.addExternalId(extId)); + + try (Repository allUsersRepo = repoManager.openRepository(allUsers); + RevWalk rw = new RevWalk(allUsersRepo)) { + RevCommit commit = + rw.parseCommit(allUsersRepo.exactRef(RefNames.REFS_EXTERNAL_IDS).getObjectId()); + + assertThat(commit.getFullMessage()).isEqualTo("foobar\n"); + } + } + + @Test + public void getAccountFromMetaId() throws Exception { + AccountState preUpdateState = accountCache.get(admin.id()).get(); + requestScopeOperations.setApiUser(admin.id()); + gApi.accounts().self().setStatus("New status"); + + AccountState postUpdateStatus = accountCache.get(admin.id()).get(); + assertThat(postUpdateStatus).isNotEqualTo(preUpdateState); + assertThat( + accountCache.getFromMetaId( + admin.id(), ObjectId.fromString(preUpdateState.account().metaId()))) + .isEqualTo(preUpdateState); + assertThat( + accountCache.getFromMetaId( + admin.id(), ObjectId.fromString(postUpdateStatus.account().metaId()))) + .isEqualTo(postUpdateStatus); + } + + @Test + public void projectWatchesUpdate_refsUsersUpdated() throws Exception { + AccountState preUpdateState = accountCache.get(admin.id()).get(); + requestScopeOperations.setApiUser(admin.id()); + + ProjectWatchInfo projectWatchInfo = new ProjectWatchInfo(); + projectWatchInfo.project = project.get(); + projectWatchInfo.notifyAllComments = true; + gApi.accounts().self().setWatchedProjects(ImmutableList.of(projectWatchInfo)); + + AccountState updatedState1 = accountCache.get(admin.id()).get(); + assertThat(preUpdateState.account().metaId()).isNotEqualTo(updatedState1.account().metaId()); + + gApi.accounts().self().deleteWatchedProjects(ImmutableList.of(projectWatchInfo)); + + AccountState updatedState2 = accountCache.get(admin.id()).get(); + assertThat(updatedState1.account().metaId()).isNotEqualTo(updatedState2.account().metaId()); + } + + @Test + public void updateExternalId_externalIdApiUpdate_refsUsersUpdated() throws Exception { + AccountState preUpdateState = accountCache.get(admin.id()).get(); + requestScopeOperations.setApiUser(admin.id()); + + gApi.accounts().self().addEmail(newEmailInput("secondary@non.google")); + assertExternalIds( + admin.id(), + ImmutableSet.of( + "mailto:admin@example.com", "username:admin", "mailto:secondary@non.google")); + + AccountState updatedState1 = accountCache.get(admin.id()).get(); + assertThat(preUpdateState.account().metaId()).isNotEqualTo(updatedState1.account().metaId()); + + gApi.accounts().self().deleteExternalIds(ImmutableList.of("mailto:secondary@non.google")); + + AccountState updatedState2 = accountCache.get(admin.id()).get(); + assertThat(updatedState1.account().metaId()).isNotEqualTo(updatedState2.account().metaId()); + } + + @Test + public void addExternalId_accountUpdate_refsUsersUpdated() throws Exception { + AccountState preUpdateState = accountCache.get(admin.id()).get(); + requestScopeOperations.setApiUser(admin.id()); + + ExternalId externalId = getExternalIdFactory().create("custom", "value", admin.id()); + accountsUpdateProvider + .get() + .update("Add External ID", admin.id(), u -> u.addExternalId(externalId)); + assertExternalIds( + admin.id(), ImmutableSet.of("mailto:admin@example.com", "username:admin", "custom:value")); + + AccountState updatedState = accountCache.get(admin.id()).get(); + assertThat(preUpdateState.account().metaId()).isNotEqualTo(updatedState.account().metaId()); + } + + @Test + public void deleteExternalId_accountUpdate_refsUsersUpdated() throws Exception { + AccountState preUpdateState = accountCache.get(admin.id()).get(); + requestScopeOperations.setApiUser(admin.id()); + + ExternalId externalId = createEmailExternalId(admin.id(), "admin@example.com"); + accountsUpdateProvider + .get() + .update("Remove External ID", admin.id(), u -> u.deleteExternalId(externalId)); + assertExternalIds(admin.id(), ImmutableSet.of("username:admin")); + + AccountState updatedState = accountCache.get(admin.id()).get(); + assertThat(preUpdateState.account().metaId()).isNotEqualTo(updatedState.account().metaId()); + } + + @Test + public void updateExternalId_accountUpdate_refsUsersUpdated() throws Exception { + AccountState preUpdateState = accountCache.get(admin.id()).get(); + requestScopeOperations.setApiUser(admin.id()); + + ExternalId externalId = + getExternalIdFactory() + .createWithEmail( + SCHEME_MAILTO, "secondary@non.google", admin.id(), "secondary@non.google"); + accountsUpdateProvider + .get() + .update("Update External ID", admin.id(), u -> u.updateExternalId(externalId)); + assertExternalIds( + admin.id(), + ImmutableSet.of( + "mailto:admin@example.com", "username:admin", "mailto:secondary@non.google")); + + AccountState updatedState = accountCache.get(admin.id()).get(); + assertThat(preUpdateState.account().metaId()).isNotEqualTo(updatedState.account().metaId()); + } + + @Test + public void replaceExternalId_accountUpdate_refsUsersUpdated() throws Exception { + AccountState preUpdateState = accountCache.get(admin.id()).get(); + requestScopeOperations.setApiUser(admin.id()); + + ExternalId externalId = + getExternalIdFactory() + .createWithEmail( + SCHEME_MAILTO, "secondary@non.google", admin.id(), "secondary@non.google"); + ExternalId oldExternalId = + getExternalIdsReader().get(createEmailExternalId(admin.id(), admin.email()).key()).get(); + accountsUpdateProvider + .get() + .update( + "Replace External ID", admin.id(), u -> u.replaceExternalId(oldExternalId, externalId)); + assertExternalIds(admin.id(), ImmutableSet.of("mailto:secondary@non.google", "username:admin")); + + AccountState updatedState = accountCache.get(admin.id()).get(); + assertThat(accountCache.get(admin.id()).get()).isNotSameInstanceAs(preUpdateState); + if (preUpdateState.account().metaId() == null) { + // When the test is executed on google infrastructure, metaId should be either always set + // or always be null. + assertThat(updatedState.account().metaId()).isNull(); + } else { + assertThat(preUpdateState.account().metaId()).isNotEqualTo(updatedState.account().metaId()); + } + } + + @Test + public void accountUpdate_updateBatch_allUsersExternalIdsUpdated_refsUsersUpdated() + throws Exception { + AccountState preUpdateAdminState = accountCache.get(admin.id()).get(); + AccountState preUpdateUserState = accountCache.get(user.id()).get(); + + requestScopeOperations.setApiUser(admin.id()); + ExternalId extId1 = + getExternalIdFactory() + .createWithEmail("custom", "admin-id", admin.id(), "admin-id@test.com"); + + ExternalId extId2 = + getExternalIdFactory().createWithEmail("custom", "user-id", user.id(), "user-id@test.com"); + + AccountsUpdate.UpdateArguments ua1 = + new AccountsUpdate.UpdateArguments( + "Add External ID", admin.id(), u -> u.addExternalId(extId1)); + AccountsUpdate.UpdateArguments ua2 = + new AccountsUpdate.UpdateArguments( + "Add External ID", user.id(), u -> u.addExternalId(extId2)); + AccountIndexedCounter accountIndexedCounter = getAccountIndexedCounter(); + try (Registration registration = + extensionRegistry.newRegistration().add(accountIndexedCounter)) { + accountsUpdateProvider.get().updateBatch(ImmutableList.of(ua1, ua2)); + } + accountIndexedCounter.assertReindexOf(admin.id(), 1); + accountIndexedCounter.assertReindexOf(user.id(), 1); + + assertExternalIds( + admin.id(), + ImmutableSet.of("mailto:admin@example.com", "username:admin", "custom:admin-id")); + assertExternalIds( + user.id(), ImmutableSet.of("username:user1", "mailto:user1@example.com", "custom:user-id")); + // Assert reindexing has worked on the updated accounts. + assertThat( + Iterables.getOnlyElement(gApi.accounts().query("admin-id@test.com").get())._accountId) + .isEqualTo(admin.id().get()); + assertThat(Iterables.getOnlyElement(gApi.accounts().query("user-id@test.com").get())._accountId) + .isEqualTo(user.id().get()); + AccountState updatedAdminState = accountCache.get(admin.id()).get(); + AccountState updatedUserState = accountCache.get(user.id()).get(); + assertThat(preUpdateAdminState.account().metaId()) + .isNotEqualTo(updatedAdminState.account().metaId()); + assertThat(preUpdateUserState.account().metaId()) + .isNotEqualTo(updatedUserState.account().metaId()); + } + + @Test + public void accountUpdate_updateBatch_someUsersExternalIdsUpdated_refsUsersUpdated() + throws Exception { + AccountState preUpdateAdminState = accountCache.get(admin.id()).get(); + AccountState preUpdateUserState = accountCache.get(user.id()).get(); + + requestScopeOperations.setApiUser(admin.id()); + AccountsUpdate.UpdateArguments ua1 = + new AccountsUpdate.UpdateArguments( + "Update Display Name", admin.id(), u -> u.setDisplayName("DN")); + AccountsUpdate.UpdateArguments ua2 = + new AccountsUpdate.UpdateArguments( + "Remove external Id", + user.id(), + u -> u.deleteExternalId(createEmailExternalId(user.id(), user.email()))); + AccountIndexedCounter accountIndexedCounter = getAccountIndexedCounter(); + try (Registration registration = + extensionRegistry.newRegistration().add(accountIndexedCounter)) { + accountsUpdateProvider.get().updateBatch(ImmutableList.of(ua1, ua2)); + } + accountIndexedCounter.assertReindexOf(admin.id(), 1); + accountIndexedCounter.assertReindexOf(user.id(), 1); + + // Only the version in config of the user with external id update was updated. + AccountState updatedAdminState = accountCache.get(admin.id()).get(); + AccountState updatedUserState = accountCache.get(user.id()).get(); + assertThat(preUpdateAdminState.account().metaId()) + .isNotEqualTo(updatedAdminState.account().metaId()); + assertThat(preUpdateUserState.account().metaId()) + .isNotEqualTo(updatedUserState.account().metaId()); + } + + @Test + public void deleteUserBranchWithAccessDatabaseCapability() throws Exception { + projectOperations + .allProjectsForUpdate() + .add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS)) + .update(); + projectOperations + .project(allUsers) + .forUpdate() + .add( + allow(Permission.DELETE) + .ref(RefNames.REFS_USERS + "${" + RefPattern.USERID_SHARDED + "}") + .group(REGISTERED_USERS) + .force(true)) + .update(); + + TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers); + String userRef = RefNames.refsUsers(admin.id()); + PushResult r = deleteRef(allUsersRepo, userRef); + RemoteRefUpdate refUpdate = r.getRemoteUpdate(userRef); + assertThat(refUpdate.getStatus()).isEqualTo(RemoteRefUpdate.Status.OK); + + try (Repository repo = repoManager.openRepository(allUsers)) { + assertThat(repo.exactRef(userRef)).isNull(); + } + + assertThat(accountCache.get(admin.id())).isEmpty(); + assertThat(accountQueryProvider.get().byDefault(admin.id().toString(), true)).isEmpty(); + } + + @Test + public void cannotDeleteUserBranch() throws Exception { + projectOperations + .project(allUsers) + .forUpdate() + .add( + allow(Permission.DELETE) + .ref(RefNames.REFS_USERS + "${" + RefPattern.USERID_SHARDED + "}") + .group(REGISTERED_USERS) + .force(true)) + .update(); + + TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers); + String userRef = RefNames.refsUsers(admin.id()); + PushResult r = deleteRef(allUsersRepo, userRef); + RemoteRefUpdate refUpdate = r.getRemoteUpdate(userRef); + assertThat(refUpdate.getStatus()).isEqualTo(RemoteRefUpdate.Status.REJECTED_OTHER_REASON); + assertThat(refUpdate.getMessage()).contains("Not allowed to delete user branch."); + + try (Repository repo = repoManager.openRepository(allUsers)) { + assertThat(repo.exactRef(userRef)).isNotNull(); + } + } + + @Test + public void createDefaultUserBranch() throws Exception { + try (Repository repo = repoManager.openRepository(allUsers)) { + assertThat(repo.exactRef(RefNames.REFS_USERS_DEFAULT)).isNull(); + } + + projectOperations + .project(allUsers) + .forUpdate() + .add(allow(Permission.CREATE).ref(RefNames.REFS_USERS_DEFAULT).group(adminGroupUuid())) + .add(allow(Permission.PUSH).ref(RefNames.REFS_USERS_DEFAULT).group(adminGroupUuid())) + .update(); + + TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers); + pushFactory + .create(admin.newIdent(), allUsersRepo) + .to(RefNames.REFS_USERS_DEFAULT) + .assertOkStatus(); + + try (Repository repo = repoManager.openRepository(allUsers)) { + assertThat(repo.exactRef(RefNames.REFS_USERS_DEFAULT)).isNotNull(); + } + } + + @Test + public void stalenessChecker() throws Exception { + // Newly created account is not stale. + AccountInfo accountInfo = gApi.accounts().create(name("foo")).get(); + Account.Id accountId = Account.id(accountInfo._accountId); + assertThat(stalenessChecker.check(accountId).isStale()).isFalse(); + + // Manually updating the user ref makes the index document stale. + String userRef = RefNames.refsUsers(accountId); + testRefAction( + () -> { + try (Repository repo = repoManager.openRepository(allUsers); + ObjectInserter oi = repo.newObjectInserter(); + RevWalk rw = new RevWalk(repo)) { + RevCommit commit = rw.parseCommit(repo.exactRef(userRef).getObjectId()); + + PersonIdent ident = new PersonIdent(serverIdent.get(), TimeUtil.now()); + CommitBuilder cb = new CommitBuilder(); + cb.setTreeId(commit.getTree()); + cb.setCommitter(ident); + cb.setAuthor(ident); + cb.setMessage(commit.getFullMessage()); + ObjectId emptyCommit = oi.insert(cb); + oi.flush(); + + RefUpdate updateRef = repo.updateRef(userRef); + updateRef.setExpectedOldObjectId(commit.toObjectId()); + updateRef.setNewObjectId(emptyCommit); + assertThat(updateRef.forceUpdate()).isEqualTo(RefUpdate.Result.FORCED); + } + }); + assertStaleAccountAndReindex(accountId); + + // Manually inserting/updating/deleting an external ID of the user makes the index document + // stale. + try (Repository repo = repoManager.openRepository(allUsers)) { + testRefAction( + () -> { + ExternalIdNotes extIdNotes = getExternalIdNotes(repo); + + ExternalId.Key key = externalIdKeyFactory.create("foo", "foo"); + extIdNotes.insert(getExternalIdFactory().create(key, accountId)); + try (MetaDataUpdate update = metaDataUpdateFactory.create(allUsers)) { + extIdNotes.commit(update); + } + assertStaleAccountAndReindex(accountId); + + extIdNotes = getExternalIdNotes(repo); + extIdNotes.upsert( + getExternalIdFactory().createWithEmail(key, accountId, "foo@example.com")); + try (MetaDataUpdate update = metaDataUpdateFactory.create(allUsers)) { + extIdNotes.commit(update); + } + assertStaleAccountAndReindex(accountId); + + extIdNotes = getExternalIdNotes(repo); + extIdNotes.delete(accountId, key); + try (MetaDataUpdate update = metaDataUpdateFactory.create(allUsers)) { + extIdNotes.commit(update); + } + }); + assertStaleAccountAndReindex(accountId); + } + + // Manually delete account + testRefAction( + () -> { + try (Repository repo = repoManager.openRepository(allUsers); + RevWalk rw = new RevWalk(repo)) { + RevCommit commit = rw.parseCommit(repo.exactRef(userRef).getObjectId()); + RefUpdate updateRef = repo.updateRef(userRef); + updateRef.setExpectedOldObjectId(commit.toObjectId()); + updateRef.setNewObjectId(ObjectId.zeroId()); + updateRef.setForceUpdate(true); + assertThat(updateRef.delete()).isEqualTo(RefUpdate.Result.FORCED); + } + }); + assertStaleAccountAndReindex(accountId); + } + + private ExternalIdNotes getExternalIdNotes(Repository allUsersRepo) + throws ConfigInvalidException, IOException { + return ExternalIdNotes.load( + allUsers, + allUsersRepo, + externalIdFactoryNoteDbImpl, + authConfig.isUserNameCaseInsensitiveMigrationMode()); + } + + @UsedAt(UsedAt.Project.GOOGLE) + protected final AccountsUpdateNoteDbImpl getAccountsUpdateNoteDbImplWithRunnables( + Runnable afterReadRevision, Runnable beforeCommit, RetryHelper retryHelper) { + return new AccountsUpdateNoteDbImpl( + repoManager, + gitReferenceUpdated, + Optional.empty(), + allUsers, + externalIdsNoteDbImpl, + extIdNotesFactory, + metaDataUpdateInternalFactory, + retryHelper, + serverIdent.get(), + afterReadRevision, + beforeCommit); + } + + private void assertStaleAccountAndReindex(Account.Id accountId) throws IOException { + assertThat(stalenessChecker.check(accountId).isStale()).isTrue(); + + // Reindex fixes staleness + accountIndexer.index(accountId); + assertThat(stalenessChecker.check(accountId).isStale()).isFalse(); + } +}
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/BUILD b/javatests/com/google/gerrit/acceptance/api/accounts/BUILD index c441402..a09ecf5 100644 --- a/javatests/com/google/gerrit/acceptance/api/accounts/BUILD +++ b/javatests/com/google/gerrit/acceptance/api/accounts/BUILD
@@ -2,7 +2,10 @@ load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests") acceptance_tests( - srcs = glob(["*IT.java"]), + srcs = glob( + ["*IT.java"], + exclude = ["AbstractAccountIT.java"], + ), group = "api_account", labels = [ "api", @@ -20,7 +23,10 @@ java_library( name = "util", testonly = True, - srcs = glob(["TestRealm.java"]), + srcs = [ + "AbstractAccountIT.java", + "TestRealm.java", + ], deps = [ "//java/com/google/gerrit/acceptance:lib", ],
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/DiffPreferencesIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/DiffPreferencesIT.java index 7c6e5ed..981b6e9 100644 --- a/javatests/com/google/gerrit/acceptance/api/accounts/DiffPreferencesIT.java +++ b/javatests/com/google/gerrit/acceptance/api/accounts/DiffPreferencesIT.java
@@ -33,6 +33,12 @@ } @Test + public void responsiveModeDefault() throws Exception { + DiffPreferencesInfo d = DiffPreferencesInfo.defaults(); + assertThat(d.responsiveMode).isEqualTo(DiffPreferencesInfo.ResponsiveMode.NONE); + } + + @Test public void setDiffPreferences() throws Exception { DiffPreferencesInfo i = DiffPreferencesInfo.defaults(); @@ -61,9 +67,11 @@ i.hideEmptyPane ^= true; i.matchBrackets ^= true; i.lineWrapping ^= true; + i.responsiveMode = DiffPreferencesInfo.ResponsiveMode.SHRINK_ONLY; DiffPreferencesInfo o = gApi.accounts().id(admin.id().get()).setDiffPreferences(i); assertPrefs(o, i); + // Re-getting the preferences should yield the same fields o = gApi.accounts().id(admin.id().get()).getDiffPreferences(); assertPrefs(o, i);
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java index dd2d0c3..0453d60 100644 --- a/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java +++ b/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
@@ -87,6 +87,7 @@ i.allowSuggestCodeWhileCommenting ^= false; i.allowAutocompletingComments ^= false; i.aiChatSelectedModel = "test-ai-model"; + i.labelFilter = "Code-Review,Verified"; i.diffPageSidebar = "plugin-insight"; i.diffView = DiffView.UNIFIED_DIFF; i.my = new ArrayList<>(); @@ -105,6 +106,7 @@ assertThat(o.aiChatSelectedModel).isEqualTo(i.aiChatSelectedModel); assertThat(o.diffPageSidebar).isEqualTo(i.diffPageSidebar); assertThat(o.disableKeyboardShortcuts).isEqualTo(i.disableKeyboardShortcuts); + assertThat(o.labelFilter).isEqualTo(i.labelFilter); } @Test
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/MessageIdGeneratorIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/MessageIdGeneratorIT.java index 984b32d..a1a6755 100644 --- a/javatests/com/google/gerrit/acceptance/api/accounts/MessageIdGeneratorIT.java +++ b/javatests/com/google/gerrit/acceptance/api/accounts/MessageIdGeneratorIT.java
@@ -26,8 +26,8 @@ import com.google.gerrit.mail.MailMessage; import com.google.gerrit.server.mail.send.MessageIdGenerator; import com.google.gerrit.server.util.time.TimeUtil; +import com.google.inject.Inject; import java.time.Instant; -import javax.inject.Inject; import org.eclipse.jgit.lib.Repository; import org.junit.Test;
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java index d716006..fee36d3 100644 --- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java +++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -42,6 +42,7 @@ import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_COMMIT; import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION; import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS; +import static com.google.gerrit.extensions.client.ListChangesOption.DOWNLOAD_COMMANDS; import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES; import static com.google.gerrit.extensions.client.ListChangesOption.PUSH_CERTIFICATES; import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWED; @@ -155,6 +156,7 @@ import com.google.gerrit.extensions.common.LabelInfo; import com.google.gerrit.extensions.common.RevisionInfo; import com.google.gerrit.extensions.common.TrackingIdInfo; +import com.google.gerrit.extensions.config.DownloadScheme; import com.google.gerrit.extensions.events.AttentionSetListener; import com.google.gerrit.extensions.events.ChangeIndexedListener; import com.google.gerrit.extensions.registration.DynamicSet; @@ -1220,8 +1222,7 @@ @Test public void deleteAllForProjectNotifiesListeners() { - TestDeleteAllForProjectListener deleteAllForProjectsListener = - new TestDeleteAllForProjectListener(); + TestDeleteForProjectListener deleteAllForProjectsListener = new TestDeleteForProjectListener(); try (Registration ignore = extensionRegistry.newRegistration().add(deleteAllForProjectsListener)) { @@ -1229,7 +1230,26 @@ indexer.deleteAllForProject(project); } - assertThat(deleteAllForProjectsListener.getFiredCount()).isEqualTo(1); + assertThat(deleteAllForProjectsListener.getAllChangesPerProjectDeletedFiredCount()) + .isEqualTo(1); + } + + @SuppressWarnings("FutureReturnValueIgnored") + @Test + public void deleteChangeFromIndexNotifiesListeners() { + TestChange change = changeOperations.newChange().createAndGet(); + TestDeleteForProjectListener deleteAllForProjectsListener = new TestDeleteForProjectListener(); + String projectName = "my-test-project"; + + try (Registration ignore = + extensionRegistry.newRegistration().add(deleteAllForProjectsListener)) { + + var unused = + indexer.deleteAsync(Project.NameKey.parse(projectName), change.numericChangeId()); + } + + assertThat(deleteAllForProjectsListener.getSingleChangeDeletedFiredCount()).isEqualTo(1); + assertThat(deleteAllForProjectsListener.getReceivedProjectName()).isEqualTo(projectName); } @Test @@ -2950,6 +2970,30 @@ } @Test + public void queryChangesOptionsBulk() throws Exception { + int numChanges = 20; + for (int i = 0; i < numChanges; i++) { + createChange(); + } + + try (Registration registration = + extensionRegistry.newRegistration().add(new TestDownloadScheme(), "test-scheme")) { + List<ChangeInfo> results = + gApi.changes() + .query("project:" + project.get() + " status:open") + .withOptions(CHANGE_ACTIONS, CURRENT_REVISION, CURRENT_ACTIONS, DOWNLOAD_COMMANDS) + .get(); + assertThat(results).hasSize(numChanges); + for (ChangeInfo result : results) { + assertThat(result.actions).containsKey("abandon"); + RevisionInfo rev = Iterables.getOnlyElement(result.revisions.values()); + assertThat(rev.actions).isNotEmpty(); + assertThat(rev.fetch).isNotEmpty(); + } + } + } + + @Test public void queryChangesOwnerWithDifferentUsers() throws Exception { PushOneCommit.Result r = createChange(); assertThat( @@ -5299,7 +5343,7 @@ Change.Id changeId = change.getChange().getId(); String metaRef = changeMetaRef(changeId); - indexer.delete(changeId); + indexer.delete(change.getChange().project(), changeId); try (Repository repo = repoManager.openRepository(project); ObjectInserter inserter = repo.newObjectInserter(); @@ -5417,26 +5461,66 @@ } } - public static class TestDeleteAllForProjectListener implements ChangeIndexedListener { - private final AtomicInteger firedCount = new AtomicInteger(0); + public static class TestDeleteForProjectListener implements ChangeIndexedListener { + private final AtomicInteger allChangesPerProjectDeletedFiredCount = new AtomicInteger(0); + private final AtomicInteger singleChangeDeletedFiredCount = new AtomicInteger(0); + private String receivedProjectName = null; @Override public void onChangeIndexed(String projectName, int id) {} @Override - public void onChangeDeleted(int id) {} + public void onChangeDeleted(String projectName, int id) { + singleChangeDeletedFiredCount.incrementAndGet(); + receivedProjectName = projectName; + } @Override public void onAllChangesDeletedForProject(String projectName) { - firedCount.incrementAndGet(); + allChangesPerProjectDeletedFiredCount.incrementAndGet(); } - public int getFiredCount() { - return firedCount.get(); + public int getAllChangesPerProjectDeletedFiredCount() { + return allChangesPerProjectDeletedFiredCount.get(); + } + + public int getSingleChangeDeletedFiredCount() { + return singleChangeDeletedFiredCount.get(); + } + + public String getReceivedProjectName() { + return receivedProjectName; } } private void voteLabel(String changeId, String labelName, int score) throws RestApiException { gApi.changes().id(changeId).current().review(new ReviewInput().label(labelName, score)); } + + private static class TestDownloadScheme extends DownloadScheme { + @Override + public String getUrl(String project) { + return "http://foo/" + project; + } + + @Override + public boolean isAuthRequired() { + return true; + } + + @Override + public boolean isAuthSupported() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public boolean isHidden() { + return false; + } + } }
diff --git a/javatests/com/google/gerrit/acceptance/api/change/PostReviewIT.java b/javatests/com/google/gerrit/acceptance/api/change/PostReviewIT.java index 8bf46fa..44190f8 100644 --- a/javatests/com/google/gerrit/acceptance/api/change/PostReviewIT.java +++ b/javatests/com/google/gerrit/acceptance/api/change/PostReviewIT.java
@@ -1322,6 +1322,63 @@ } } + @Test + public void postCommentWithIsAi() throws Exception { + PushOneCommit.Result r = createChange(); + String filePath = r.getChange().currentFilePaths().get(0); + + CommentInput comment = new CommentInput(); + comment.line = 1; + comment.message = "AI comment"; + comment.path = filePath; + comment.isAi = true; + + ReviewInput input = new ReviewInput(); + input.comments = ImmutableMap.of(filePath, ImmutableList.of(comment)); + gApi.changes().id(r.getChangeId()).current().review(input); + + List<com.google.gerrit.extensions.common.CommentInfo> comments = + gApi.changes().id(r.getChangeId()).commentsAsList(); + assertThat(comments).hasSize(1); + com.google.gerrit.extensions.common.CommentInfo commentInfo = comments.get(0); + assertThat(commentInfo.message).isEqualTo("AI comment"); + assertThat(commentInfo.isAi).isTrue(); + } + + @Test + public void createDraftWithIsAiAndPublish() throws Exception { + PushOneCommit.Result r = createChange(); + String filePath = r.getChange().currentFilePaths().get(0); + + DraftInput draft = new DraftInput(); + draft.line = 1; + draft.message = "AI draft"; + draft.path = filePath; + draft.isAi = true; + + gApi.changes().id(r.getChangeId()).revision(r.getCommit().getName()).createDraft(draft); + + List<com.google.gerrit.extensions.common.CommentInfo> drafts = + gApi.changes().id(r.getChangeId()).draftsAsList(); + assertThat(drafts).hasSize(1); + com.google.gerrit.extensions.common.CommentInfo draftInfo = drafts.get(0); + assertThat(draftInfo.message).isEqualTo("AI draft"); + assertThat(draftInfo.isAi).isTrue(); + + // Publish drafts + ReviewInput reviewInput = new ReviewInput(); + reviewInput.drafts = DraftHandling.PUBLISH; + gApi.changes().id(r.getChangeId()).current().review(reviewInput); + + // Verify published comment has isAi = true + List<com.google.gerrit.extensions.common.CommentInfo> comments = + gApi.changes().id(r.getChangeId()).commentsAsList(); + assertThat(comments).hasSize(1); + com.google.gerrit.extensions.common.CommentInfo commentInfo = comments.get(0); + assertThat(commentInfo.message).isEqualTo("AI draft"); + assertThat(commentInfo.isAi).isTrue(); + } + private static void assertAttentionSet( ImmutableSet<AttentionSetUpdate> attentionSet, Account.Id... accounts) { assertThat(attentionSet.stream().map(AttentionSetUpdate::account).collect(Collectors.toList()))
diff --git a/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java b/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java index 8e2a3ba..62d1941 100644 --- a/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java +++ b/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java
@@ -14,6 +14,7 @@ package com.google.gerrit.acceptance.api.change; +import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.truth.Truth.assertThat; import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow; import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block; @@ -34,6 +35,7 @@ import com.google.gerrit.acceptance.testsuite.account.AccountOperations; import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations; +import com.google.gerrit.entities.Account; import com.google.gerrit.entities.BranchNameKey; import com.google.gerrit.entities.Permission; import com.google.gerrit.entities.Project; @@ -516,6 +518,53 @@ } @Test + public void revertDoesNotAddDeletedReviewersOrCcs() throws Exception { + PushOneCommit.Result r = createChange(); + + TestAccount reviewer = + accountCreator.create("reviewer-temp", "reviewer-temp@example.com", "Reviewer Temp", null); + TestAccount cc = accountCreator.create("cc-temp", "cc-temp@example.com", "CC Temp", null); + TestAccount reverter = accountCreator.admin2(); + + ReviewInput in = ReviewInput.approve(); + in.reviewer(reviewer.email()); + in.reviewer(cc.email(), ReviewerState.CC, true); + in.reviewer(reverter.email()); + + gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(in); + gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit(); + + // Delete reviewer and CC accounts + deleteAccount(reviewer.id()); + deleteAccount(cc.id()); + + requestScopeOperations.setApiUser(reverter.id()); + Map<ReviewerState, Collection<AccountInfo>> result = + gApi.changes().id(r.getChangeId()).revert().get().reviewers; + + // The deleted reviewer and CC should not be added. + // Only the change owner (admin) should be added as reviewer (since they are not deleted). + // Reverter (admin2) is the new owner, so they are not in reviewers list. + if (result.containsKey(ReviewerState.REVIEWER)) { + ImmutableList<Integer> reviewers = + result.get(ReviewerState.REVIEWER).stream() + .map(a -> a._accountId) + .collect(toImmutableList()); + assertThat(reviewers).containsExactly(admin.id().get()); + } + if (result.containsKey(ReviewerState.CC)) { + ImmutableList<Integer> ccs = + result.get(ReviewerState.CC).stream().map(a -> a._accountId).collect(toImmutableList()); + assertThat(ccs).isEmpty(); + } + } + + private void deleteAccount(Account.Id id) throws Exception { + requestScopeOperations.setApiUser(id); + gApi.accounts().self().delete(); + } + + @Test @GerritConfig(name = "accounts.visibility", value = "SAME_GROUP") public void revertWithNonVisibleUsers() throws Exception { projectOperations
diff --git a/javatests/com/google/gerrit/acceptance/api/flow/BUILD b/javatests/com/google/gerrit/acceptance/api/flow/BUILD index 24aea20..384e7e7 100644 --- a/javatests/com/google/gerrit/acceptance/api/flow/BUILD +++ b/javatests/com/google/gerrit/acceptance/api/flow/BUILD
@@ -1,3 +1,4 @@ +load("@rules_java//java:defs.bzl", "java_library") load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests") acceptance_tests(
diff --git a/javatests/com/google/gerrit/acceptance/api/project/AccessIT.java b/javatests/com/google/gerrit/acceptance/api/project/AccessIT.java index 3558e61..80886d1 100644 --- a/javatests/com/google/gerrit/acceptance/api/project/AccessIT.java +++ b/javatests/com/google/gerrit/acceptance/api/project/AccessIT.java
@@ -68,6 +68,7 @@ import com.google.gerrit.server.group.SystemGroupBackend; import com.google.gerrit.server.group.testing.TestGroupBackend; import com.google.gerrit.server.project.ProjectConfig; +import com.google.gerrit.server.schema.GrantPostReviewCommentPermission; import com.google.gerrit.server.schema.GrantRevertPermission; import com.google.inject.Inject; import java.util.Arrays; @@ -98,6 +99,7 @@ @Inject private RequestScopeOperations requestScopeOperations; @Inject private ExtensionRegistry extensionRegistry; @Inject private GrantRevertPermission grantRevertPermission; + @Inject private GrantPostReviewCommentPermission grantPostReviewCommentPermission; private Project.NameKey newProjectName; @@ -209,6 +211,67 @@ } @Test + public void grantPostReviewCommentPermission() throws Exception { + String ref = "refs/*"; + String groupId = "global:Registered-Users"; + + grantPostReviewCommentPermission.execute(newProjectName); + + ProjectAccessInfo info = pApi().access(); + assertThat(info.local.containsKey(ref)).isTrue(); + AccessSectionInfo accessSectionInfo = info.local.get(ref); + assertThat(accessSectionInfo.permissions.containsKey(Permission.POST_REVIEW_COMMENT)).isTrue(); + PermissionInfo permissionInfo = + accessSectionInfo.permissions.get(Permission.POST_REVIEW_COMMENT); + assertThat(permissionInfo.rules.containsKey(groupId)).isTrue(); + PermissionRuleInfo permissionRuleInfo = permissionInfo.rules.get(groupId); + assertThat(permissionRuleInfo.action).isEqualTo(PermissionRuleInfo.Action.ALLOW); + } + + @Test + public void grantPostReviewCommentPermissionOnlyWorksOnce() throws Exception { + grantPostReviewCommentPermission.execute(newProjectName); + grantPostReviewCommentPermission.execute(newProjectName); + + try (Repository repo = repoManager.openRepository(newProjectName)) { + MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, newProjectName, repo); + ProjectConfig projectConfig = projectConfigFactory.read(md); + AccessSection all = projectConfig.getAccessSection(AccessSection.ALL); + + Permission permission = all.getPermission(Permission.POST_REVIEW_COMMENT); + assertThat(permission.getRules()).hasSize(1); + } + } + + @Test + public void grantPostReviewCommentPermissionDoesntOverrideAdminsPreferences() throws Exception { + GroupReference otherGroup = systemGroupBackend.getGroup(ANONYMOUS_USERS); + + try (Repository repo = repoManager.openRepository(newProjectName)) { + MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, newProjectName, repo); + ProjectConfig projectConfig = projectConfigFactory.read(md); + projectConfig.upsertAccessSection( + AccessSection.ALL, + all -> { + grant(projectConfig, all, Permission.POST_REVIEW_COMMENT, otherGroup); + }); + md.getCommitBuilder().setAuthor(admin.newIdent()); + md.getCommitBuilder().setCommitter(admin.newIdent()); + md.setMessage("Add Post Review Comment permission for anonymous users\n"); + + projectConfig.commit(md); + } + projectCache.evict(newProjectName); + ProjectAccessInfo expected = pApi().access(); + + grantPostReviewCommentPermission.execute(newProjectName); + projectCache.evict(newProjectName); + ProjectAccessInfo actual = pApi().access(); + // Permissions don't change. + assertThat(actual.local).isEqualTo(expected.local); + } + + @Test public void getDefaultInheritance() throws Exception { String inheritedName = pApi().access().inheritsFrom.name; assertThat(inheritedName).isEqualTo(AllProjectsNameProvider.DEFAULT);
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/BUILD b/javatests/com/google/gerrit/acceptance/api/revision/BUILD index 9c6584e..191832d 100644 --- a/javatests/com/google/gerrit/acceptance/api/revision/BUILD +++ b/javatests/com/google/gerrit/acceptance/api/revision/BUILD
@@ -1,3 +1,4 @@ +load("@rules_java//java:defs.bzl", "java_library") load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests") [acceptance_tests(
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java index 5abc9dc..95eee42 100644 --- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java +++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -954,6 +954,63 @@ } @Test + public void cherryPickSetsCherryPickOfInCommitReceivedEvent() throws Exception { + PushOneCommit.Result r = createChange(); + CherryPickInput in = new CherryPickInput(); + in.destination = "foo"; + in.message = "cherry-pick"; + gApi.projects().name(project.get()).branch(in.destination).create(new BranchInput()); + + TestCommitValidationListener testCommitValidationListener = new TestCommitValidationListener(); + try (Registration registration = + extensionRegistry.newRegistration().add(testCommitValidationListener)) { + gApi.changes().id(r.getChangeId()).current().cherryPick(in); + assertThat(testCommitValidationListener.receiveEvent.cherryPickOf) + .isEqualTo(PatchSet.id(r.getChange().getId(), 1)); + } + } + + @Test + public void regularPushDoesNotSetCherryPickOfInCommitReceivedEvent() throws Exception { + TestCommitValidationListener testCommitValidationListener = new TestCommitValidationListener(); + try (Registration registration = + extensionRegistry.newRegistration().add(testCommitValidationListener)) { + createChange(); + assertThat(testCommitValidationListener.receiveEvent.cherryPickOf).isNull(); + } + } + + @Test + public void pushNewPatchSetToCherryPickedChangeSetsCherryPickOfInCommitReceivedEvent() + throws Exception { + // Create a change on master. + PushOneCommit.Result r = createChange(); + Change.Id origChangeId = r.getChange().getId(); + + // Create branch foo and cherry-pick the change to it. + BranchInput branchInput = new BranchInput(); + branchInput.revision = r.getCommit().getParent(0).name(); + gApi.projects().name(project.get()).branch("foo").create(branchInput); + + CherryPickInput cherryPickInput = new CherryPickInput(); + cherryPickInput.destination = "foo"; + cherryPickInput.message = r.getCommit().getFullMessage(); + ChangeApi cherryPicked = + gApi.changes().id(r.getChangeId()).current().cherryPick(cherryPickInput); + String cherryPickedChangeId = cherryPicked.get().changeId; + + // Push a new patchset to the cherry-picked change and verify that cherryPickOf is set + // in the CommitReceivedEvent during push validation. + TestCommitValidationListener testCommitValidationListener = new TestCommitValidationListener(); + try (Registration registration = + extensionRegistry.newRegistration().add(testCommitValidationListener)) { + amendChange(cherryPickedChangeId, "refs/for/foo", admin, testRepo); + assertThat(testCommitValidationListener.receiveEvent.cherryPickOf) + .isEqualTo(PatchSet.id(origChangeId, 1)); + } + } + + @Test public void cherryPickToExistingChangeUpdatesCherryPickOf() throws Exception { PushOneCommit.Result r1 = pushFactory @@ -1793,7 +1850,7 @@ } @Override - public void onChangeDeleted(int id) {} + public void onChangeDeleted(String projectName, int id) {} }; try (Registration registration = extensionRegistry.newRegistration().add(listener)) {
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java index 4ccb80b..3e82712 100644 --- a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java +++ b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -1904,7 +1904,7 @@ r = push.to("refs/for/master"); r.assertOkStatus(); - indexer.delete(r.getChange().getId()); + indexer.delete(project, r.getChange().getId()); assertPushRejected( pushHead(testRepo, "refs/for/master", false),
diff --git a/javatests/com/google/gerrit/acceptance/rest/TraceIT.java b/javatests/com/google/gerrit/acceptance/rest/TraceIT.java index c3b1672..b8f805c 100644 --- a/javatests/com/google/gerrit/acceptance/rest/TraceIT.java +++ b/javatests/com/google/gerrit/acceptance/rest/TraceIT.java
@@ -1195,7 +1195,7 @@ } @Override - public void onChangeDeleted(int id) {} + public void onChangeDeleted(String projectName, int id) {} } private static class TraceSubmitRule implements SubmitRule {
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java index 9140a71..45e95db 100644 --- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java +++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -97,6 +97,7 @@ import com.google.gerrit.server.index.change.ChangeIndexer; import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.project.testing.TestLabels; +import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.restapi.change.Submit; import com.google.gerrit.server.update.BatchUpdate; import com.google.gerrit.server.update.BatchUpdateOp; @@ -1042,12 +1043,12 @@ // Cherry-pick strategy does not query from index assume().that(getSubmitType()).isNotEqualTo(CHERRY_PICK); // retry on index - PushOneCommit.Result change = createChange(); + ChangeData change = createChange().getChange(); // Submit using full change Id to avoid using index. - String id = change.getChange().project() + "~" + change.getChange().getId().get(); + String id = change.project() + "~" + change.getId().get(); approve(id); - changeIndex.delete(change.getChange().getId()); + changeIndex.delete(change.project(), change.getId()); TestSubmitInput input = new TestSubmitInput(); @@ -1076,14 +1077,14 @@ throws Throwable { // Cherry-pick strategy does not query from the index assume().that(getSubmitType()).isNotEqualTo(CHERRY_PICK); - PushOneCommit.Result change = createChange(); + ChangeData change = createChange().getChange(); // Submit using full change Id to avoid using index. - String id = change.getChange().project() + "~" + change.getChange().getId().get(); + String id = change.project() + "~" + change.getId().get(); approve(id); // Delete the change from the index, to ensure the use of the backfill mechanism - changeIndex.delete(change.getChange().getId()); + changeIndex.delete(change.project(), change.getId()); testMetricMaker.reset();
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/IndexChangesIT.java b/javatests/com/google/gerrit/acceptance/rest/config/IndexChangesIT.java index 614ce80..416f8d2 100644 --- a/javatests/com/google/gerrit/acceptance/rest/config/IndexChangesIT.java +++ b/javatests/com/google/gerrit/acceptance/rest/config/IndexChangesIT.java
@@ -16,36 +16,69 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block; +import static com.google.gerrit.entities.RefNames.changeMetaRef; import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; +import static com.google.gerrit.testing.TestActionRefUpdateContext.openTestRefUpdateContext; import com.google.common.collect.ImmutableSet; import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.acceptance.ChangeIndexedCounter; import com.google.gerrit.acceptance.ExtensionRegistry; import com.google.gerrit.acceptance.ExtensionRegistry.Registration; +import com.google.gerrit.acceptance.PushOneCommit; +import com.google.gerrit.acceptance.config.GerritConfig; import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; +import com.google.gerrit.entities.Change; import com.google.gerrit.entities.Permission; +import com.google.gerrit.entities.Project; +import com.google.gerrit.entities.RefNames; import com.google.gerrit.extensions.common.ChangeInfo; +import com.google.gerrit.index.IndexConfig; +import com.google.gerrit.index.QueryOptions; +import com.google.gerrit.server.index.change.ChangeIndex; +import com.google.gerrit.server.index.change.ChangeIndexCollection; +import com.google.gerrit.server.index.change.IndexedChangeQuery; +import com.google.gerrit.server.query.change.ChangeData; +import com.google.gerrit.server.query.change.ChangeNumberVirtualIdAlgorithm; import com.google.gerrit.server.restapi.config.IndexChanges; import com.google.inject.Inject; +import java.util.Optional; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; import org.junit.Test; public class IndexChangesIT extends AbstractDaemonTest { + private static final String TEST_CHANGE_NUM = "1"; + private static final String TEST_CHANGE_ID = "I8350971af868ee34b17fc8703aa9ef40c03f5ec5"; + private static final boolean PRESERVE_MISSING = false; + private static final boolean DELETE_MISSING = true; @Inject private ProjectOperations projectOperations; @Inject private ExtensionRegistry extensionRegistry; + @Inject private ChangeIndexCollection changeIndexCollection; + @Inject private IndexConfig indexConfig; + @Inject private ChangeNumberVirtualIdAlgorithm changeNumberVirtualIdAlgorithm; @Test public void indexRequestFromNonAdminRejected() throws Exception { ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter(); try (Registration registration = extensionRegistry.newRegistration().add(changeIndexedCounter)) { - String changeId = createChange().getChangeId(); - IndexChanges.Input in = new IndexChanges.Input(); - in.changes = ImmutableSet.of(changeId); + PushOneCommit.Result change = createChange(); changeIndexedCounter.clear(); - userRestSession.post("/config/server/index.changes", in).assertForbidden(); - assertThat(changeIndexedCounter.getCount(info(changeId))).isEqualTo(0); + userRestSession + .post("/config/server/index.changes", indexChangesInput(change.getChange().getId())) + .assertForbidden(); + assertThat(changeIndexedCounter.getCount(info(change.getChangeId()))).isEqualTo(0); } } @@ -54,51 +87,204 @@ ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter(); try (Registration registration = extensionRegistry.newRegistration().add(changeIndexedCounter)) { - String changeId = createChange().getChangeId(); - IndexChanges.Input in = new IndexChanges.Input(); - in.changes = ImmutableSet.of(changeId); + PushOneCommit.Result change = createChange(); changeIndexedCounter.clear(); - adminRestSession.post("/config/server/index.changes", in).assertOK(); - assertThat(changeIndexedCounter.getCount(info(changeId))).isEqualTo(1); + adminRestSession + .post("/config/server/index.changes", indexChangesInput(change.getChange().getId())) + .assertOK(); + assertThat(changeIndexedCounter.getCount(info(change.getChangeId()))).isEqualTo(1); } } @Test + public void indexChangeNotInIndex() throws Exception { + PushOneCommit.Result change = createChange(); + Change.Id changeId = change.getChange().getId(); + + assertThat(getChangeFromIndex(changeId)).isPresent(); + indexer.delete(project, changeId); + assertThat(getChangeFromIndex(changeId)).isEmpty(); + + adminRestSession.post("/config/server/index.changes", indexChangesInput(changeId)).assertOK(); + assertThat(getChangeFromIndex(changeId)).isPresent(); + } + + @Test public void indexNonVisibleChange() throws Exception { ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter(); try (Registration registration = extensionRegistry.newRegistration().add(changeIndexedCounter)) { - String changeId = createChange().getChangeId(); + String changeId = projectAndChangeNumId(project, createChange().getChange().getId()); ChangeInfo changeInfo = info(changeId); projectOperations .project(project) .forUpdate() .add(block(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS)) .update(); - IndexChanges.Input in = new IndexChanges.Input(); changeIndexedCounter.clear(); - in.changes = ImmutableSet.of(changeId); - adminRestSession.post("/config/server/index.changes", in).assertOK(); + adminRestSession.post("/config/server/index.changes", indexChangesInput(changeId)).assertOK(); assertThat(changeIndexedCounter.getCount(changeInfo)).isEqualTo(1); } } @Test + public void indexChangeWithPlainNumericIdAccepted() throws Exception { + Change.Id changeId = createChange().getChange().getId(); + adminRestSession + .post("/config/server/index.changes", indexChangesInput(String.valueOf(changeId.get()))) + .assertOK(); + } + + @Test + public void deleteMissingChangeFromIndexWithPlainNumericIdRejected() throws Exception { + adminRestSession + .post("/config/server/index.changes", indexChangesInput(TEST_CHANGE_NUM, DELETE_MISSING)) + .assertBadRequest(); + } + + @Test + public void indexChangeWithTripletIdAccepted() throws Exception { + String changeId = createChange().getChangeId(); + adminRestSession + .post("/config/server/index.changes", indexChangesInput(project + "~master~" + changeId)) + .assertOK(); + } + + @Test + public void deleteMissingChangeFromIndexWithTripletIdRejected() throws Exception { + adminRestSession + .post( + "/config/server/index.changes", + indexChangesInput(project + "~master~" + TEST_CHANGE_ID, DELETE_MISSING)) + .assertBadRequest(); + } + + @Test + public void deleteMissingChangeFromIndexByProjectAndNumericId() throws Exception { + PushOneCommit.Result result = createChange(); + Change.Id changeId = result.getChange().getId(); + + assertThat(getChangeFromIndex(changeId)).isPresent(); + deleteChangeFromNoteDbWithoutUpdatingIndex(changeId); + assertThat(getChangeFromIndex(changeId)).isPresent(); + + adminRestSession + .post( + "/config/server/index.changes", + indexChangesInput(projectAndChangeNumId(project, changeId), DELETE_MISSING)) + .assertOK(); + + assertThat(getChangeFromIndex(changeId)).isEmpty(); + } + + @Test public void indexMultipleChanges() throws Exception { ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter(); try (Registration registration = extensionRegistry.newRegistration().add(changeIndexedCounter)) { ImmutableSet.Builder<String> changeIds = ImmutableSet.builder(); for (int i = 0; i < 10; i++) { - changeIds.add(createChange().getChangeId()); + changeIds.add(projectAndChangeNumId(project, createChange().getChange().getId())); } - IndexChanges.Input in = new IndexChanges.Input(); - in.changes = changeIds.build(); + IndexChanges.Input in = new IndexChanges.Input(changeIds.build(), PRESERVE_MISSING); changeIndexedCounter.clear(); adminRestSession.post("/config/server/index.changes", in).assertOK(); - for (String changeId : in.changes) { + for (String changeId : in.changes()) { assertThat(changeIndexedCounter.getCount(info(changeId))).isEqualTo(1); } } } + + @Test + @GerritConfig(name = "gerrit.importedServerId", value = "imported-server-id") + public void deleteMissingImportedChangeFromIndex() throws Exception { + PushOneCommit.Result result = createImportedChange(); + Change.Id changeId = result.getChange().getId(); + Change.Id virtualId = + changeNumberVirtualIdAlgorithm.apply(() -> "imported-server-id", changeId); + + assertThat(getChangeFromIndex(virtualId)).isPresent(); + deleteChangeFromNoteDbWithoutUpdatingIndex(changeId); + assertThat(getChangeFromIndex(virtualId)).isPresent(); + + adminRestSession + .post("/config/server/index.changes", indexChangesInput(changeId, PRESERVE_MISSING)) + .assertOK(); + assertThat(getChangeFromIndex(virtualId)).isPresent(); + + adminRestSession + .post("/config/server/index.changes", indexChangesInput(changeId, DELETE_MISSING)) + .assertOK(); + assertThat(getChangeFromIndex(virtualId)).isEmpty(); + } + + private IndexChanges.Input indexChangesInput(String changeId) { + return new IndexChanges.Input(ImmutableSet.of(changeId), PRESERVE_MISSING); + } + + private IndexChanges.Input indexChangesInput(Change.Id changeId) { + return indexChangesInput(projectAndChangeNumId(project, changeId)); + } + + private IndexChanges.Input indexChangesInput(String changeId, boolean deleteMissing) { + return new IndexChanges.Input(ImmutableSet.of(changeId), deleteMissing); + } + + private IndexChanges.Input indexChangesInput(Change.Id changeId, boolean deleteMissing) { + return indexChangesInput(projectAndChangeNumId(project, changeId), deleteMissing); + } + + private PushOneCommit.Result createImportedChange() throws Exception { + PushOneCommit.Result change = createChange(); + Change.Id changeId = change.getChange().getId(); + String metaRef = changeMetaRef(changeId); + + try (Repository repo = repoManager.openRepository(project); + ObjectInserter inserter = repo.newObjectInserter(); + ObjectReader reader = repo.newObjectReader(); + RevWalk revWalk = new RevWalk(reader); + var ignored = openTestRefUpdateContext()) { + + Ref ref = repo.getRefDatabase().exactRef(metaRef); + RevCommit tip = revWalk.parseCommit(ref.getObjectId()); + + CommitBuilder commit = new CommitBuilder(); + commit.setTreeId(tip.getTree()); + commit.setAuthor( + new PersonIdent("Gerrit User " + admin.id(), admin.id() + "@imported-server-id")); + commit.setCommitter(new PersonIdent("Gerrit Code Review", admin.email())); + commit.setMessage(tip.getFullMessage()); + + ObjectId commitId = inserter.insert(commit); + inserter.flush(); + + RefUpdate refUpdate = repo.updateRef(metaRef); + refUpdate.setNewObjectId(commitId); + refUpdate.forceUpdate(); + } + + // Re-index after rewriting the meta-ref so the index reflects the imported serverId, + // ensuring the virtualId in the index matches what the API will compute at delete time. + indexer.delete(project, changeId); + indexer.index(project, changeId); + + return change; + } + + private void deleteChangeFromNoteDbWithoutUpdatingIndex(Change.Id changeId) throws Exception { + try (Repository repo = repoManager.openRepository(project); + TestRepository<Repository> testRepo = new TestRepository<>(repo)) { + testRepo.delete(RefNames.changeMetaRef(changeId)); + } + } + + Optional<ChangeData> getChangeFromIndex(Change.Id changeId) { + ChangeIndex idx = changeIndexCollection.getSearchIndex(); + QueryOptions opts = IndexedChangeQuery.createOptions(indexConfig, 0, 1, ImmutableSet.of()); + return idx.get(changeId, opts); + } + + String projectAndChangeNumId(Project.NameKey project, Change.Id changeNum) { + return project + "~" + changeNum; + } }
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/DiffCommitsIT.java b/javatests/com/google/gerrit/acceptance/rest/project/DiffCommitsIT.java new file mode 100644 index 0000000..3461682 --- /dev/null +++ b/javatests/com/google/gerrit/acceptance/rest/project/DiffCommitsIT.java
@@ -0,0 +1,596 @@ +// Copyright (C) 2025 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.acceptance.rest.project; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block; +import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; +import static com.google.gerrit.testing.GerritJUnit.assertThrows; + +import com.google.common.collect.ImmutableMap; +import com.google.gerrit.acceptance.AbstractDaemonTest; +import com.google.gerrit.acceptance.PushOneCommit; +import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; +import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations; +import com.google.gerrit.entities.BranchNameKey; +import com.google.gerrit.entities.Permission; +import com.google.gerrit.extensions.common.ChangeType; +import com.google.gerrit.extensions.common.DiffInfo; +import com.google.gerrit.extensions.common.FileInfo; +import com.google.gerrit.extensions.restapi.BadRequestException; +import com.google.gerrit.extensions.restapi.ResourceNotFoundException; +import com.google.gerrit.server.restapi.project.GetDiffFile; +import com.google.gerrit.server.restapi.project.ListDiffFiles; +import com.google.inject.Inject; +import java.util.Map; +import org.eclipse.jgit.lib.ObjectId; +import org.junit.Before; +import org.junit.Test; + +/** Test class for project-level diff endpoints: {@link ListDiffFiles} and {@link GetDiffFile}. */ +public class DiffCommitsIT extends AbstractDaemonTest { + private static final String R_HEADS_MASTER = "refs/heads/master"; + + @Inject private ProjectOperations projectOperations; + @Inject private RequestScopeOperations requestScopeOperations; + + private ObjectId initialCommit; + + @Before + public void setUp() throws Exception { + initialCommit = testRepo.getRepository().resolve("HEAD"); + } + + @Test + public void listDiffFiles_success() throws Exception { + // Create a commit with file changes + PushOneCommit.Result result = + pushFactory + .create( + admin.newIdent(), + testRepo, + "Test commit", + ImmutableMap.of("file1.txt", "content1", "file2.txt", "content2")) + .to("refs/heads/master"); + result.assertOkStatus(); + + ObjectId newCommit = result.getCommit(); + + Map<String, FileInfo> files = + gApi.projects().name(project.get()).diffFiles(initialCommit.name(), newCommit.name(), true); + + assertThat(files).isNotEmpty(); + assertThat(files.keySet()).contains("file1.txt"); + assertThat(files.keySet()).contains("file2.txt"); + } + + @Test + public void listDiffFiles_missingOldParam() throws Exception { + PushOneCommit.Result result = createChange(); + ObjectId newCommit = result.getCommit(); + + BadRequestException thrown = + assertThrows( + BadRequestException.class, + () -> gApi.projects().name(project.get()).diffFiles(null, newCommit.name(), true)); + assertThat(thrown).hasMessageThat().contains("base"); + } + + @Test + public void listDiffFiles_missingNewParam() throws Exception { + BadRequestException thrown = + assertThrows( + BadRequestException.class, + () -> gApi.projects().name(project.get()).diffFiles(initialCommit.name(), null, true)); + assertThat(thrown).hasMessageThat().contains("new commit SHA1"); + } + + @Test + public void listDiffFiles_invalidSha1Format() throws Exception { + BadRequestException thrown = + assertThrows( + BadRequestException.class, + () -> + gApi.projects() + .name(project.get()) + .diffFiles("invalid", initialCommit.name(), true)); + assertThat(thrown).hasMessageThat().contains("40-character SHA1"); + } + + @Test + public void listDiffFiles_commitNotFound() throws Exception { + String nonExistentSha = "0000000000000000000000000000000000000000"; + + ResourceNotFoundException thrown = + assertThrows( + ResourceNotFoundException.class, + () -> + gApi.projects() + .name(project.get()) + .diffFiles(initialCommit.name(), nonExistentSha, true)); + assertThat(thrown).hasMessageThat().contains("Not found"); + } + + @Test + public void listDiffFiles_commitsNotInAncestorRelationship() throws Exception { + // Create branch1 at the initial commit first + createBranch(BranchNameKey.create(project, "branch1")); + + // Create a commit on master + PushOneCommit.Result result1 = + pushFactory + .create(admin.newIdent(), testRepo, "Commit 1", "file1.txt", "content1") + .to("refs/heads/master"); + result1.assertOkStatus(); + + // Reset to initial commit and push to branch1 + testRepo.reset(initialCommit); + + PushOneCommit.Result result2 = + pushFactory + .create(admin.newIdent(), testRepo, "Commit 2", "file2.txt", "content2") + .to("refs/heads/branch1"); + result2.assertOkStatus(); + + // These two commits are siblings (share common ancestor) but neither is ancestor of the other + BadRequestException thrown = + assertThrows( + BadRequestException.class, + () -> + gApi.projects() + .name(project.get()) + .diffFiles(result1.getCommit().name(), result2.getCommit().name(), true)); + assertThat(thrown).hasMessageThat().contains("ancestor/descendant"); + } + + @Test + public void listDiffFiles_verifyPathVisibility() throws Exception { + // Create branch 'visible' at initial commit + createBranch(BranchNameKey.create(project, "visible")); + + // Create a commit on master + PushOneCommit.Result result = + pushFactory + .create(admin.newIdent(), testRepo, "Test commit", "file.txt", "content") + .to("refs/heads/master"); + result.assertOkStatus(); + + ObjectId newCommit = result.getCommit(); + + // Block read permission on master, but keep it on 'visible' + projectOperations + .project(project) + .forUpdate() + .add(block(Permission.READ).ref(R_HEADS_MASTER).group(REGISTERED_USERS)) + .update(); + + // Use non-admin user + requestScopeOperations.setApiUser(user.id()); + + // Fails because target commit (newCommit) is on a hidden ref + ResourceNotFoundException thrown = + assertThrows( + ResourceNotFoundException.class, + () -> + gApi.projects() + .name(project.get()) + .diffFiles(initialCommit.name(), newCommit.name(), true)); + // This fails in CommitsCollection.parse which returns "Not found: <sha1>" + assertThat(thrown).hasMessageThat().contains("Not found"); + } + + @Test + public void listDiffFiles_reverseDiff_verifyPathVisibility() throws Exception { + // Create branch 'visible' at initial commit + createBranch(BranchNameKey.create(project, "visible")); + + // Create a commit on master + PushOneCommit.Result result = + pushFactory + .create(admin.newIdent(), testRepo, "Test commit", "file.txt", "content") + .to("refs/heads/master"); + result.assertOkStatus(); + + ObjectId newCommit = result.getCommit(); + + // Block read permission on master, but keep it on 'visible' + projectOperations + .project(project) + .forUpdate() + .add(block(Permission.READ).ref(R_HEADS_MASTER).group(REGISTERED_USERS)) + .update(); + + // Use non-admin user + requestScopeOperations.setApiUser(user.id()); + + // In a reverse diff (comparing descendant to ancestor): + // target = initialCommit (visible via 'visible' branch) + // base = newCommit (hidden because it's only on 'master') + ResourceNotFoundException thrown = + assertThrows( + ResourceNotFoundException.class, + () -> + gApi.projects() + .name(project.get()) + .diffFiles(newCommit.name(), initialCommit.name(), true)); + // This should be caught by verifyPathVisibility which throws "Commit not visible" + assertThat(thrown).hasMessageThat().contains("not visible"); + } + + @Test + public void getDiffFile_success() throws Exception { + String fileContent = "Line 1\nLine 2\nLine 3\n"; + PushOneCommit.Result result = + pushFactory + .create(admin.newIdent(), testRepo, "Test commit", "test-file.txt", fileContent) + .to("refs/heads/master"); + result.assertOkStatus(); + + ObjectId newCommit = result.getCommit(); + + DiffInfo diffInfo = + gApi.projects() + .name(project.get()) + .diffFile(initialCommit.name(), newCommit.name(), "test-file.txt"); + + assertThat(diffInfo).isNotNull(); + assertThat(diffInfo.metaB).isNotNull(); + assertThat(diffInfo.metaB.name).isEqualTo("test-file.txt"); + } + + @Test + public void getDiffFile_unchangedFile() throws Exception { + // When requesting a file that exists but hasn't changed between commits, we should get an + // empty diff (no changes) + PushOneCommit.Result result = + pushFactory + .create(admin.newIdent(), testRepo, "Test commit", "existing-file.txt", "content") + .to("refs/heads/master"); + result.assertOkStatus(); + + ObjectId commit1 = result.getCommit(); + + // Create another commit that doesn't change existing-file.txt + PushOneCommit.Result result2 = + pushFactory + .create(admin.newIdent(), testRepo, "Second commit", "other-file.txt", "other content") + .to("refs/heads/master"); + result2.assertOkStatus(); + ObjectId commit2 = result2.getCommit(); + + // Request diff for existing-file.txt which hasn't changed + DiffInfo diffInfo = + gApi.projects() + .name(project.get()) + .diffFile(commit1.name(), commit2.name(), "existing-file.txt"); + + // Should return a diff result (possibly empty) + assertThat(diffInfo).isNotNull(); + } + + @Test + public void getDiffFile_missingOldParam() throws Exception { + PushOneCommit.Result result = + pushFactory + .create(admin.newIdent(), testRepo, "Test commit", "file.txt", "content") + .to("refs/heads/master"); + result.assertOkStatus(); + + BadRequestException thrown = + assertThrows( + BadRequestException.class, + () -> + gApi.projects() + .name(project.get()) + .diffFile(null, result.getCommit().name(), "file.txt")); + assertThat(thrown).hasMessageThat().contains("base"); + } + + @Test + public void getDiffFile_verifyPathVisibility() throws Exception { + PushOneCommit.Result result = + pushFactory + .create(admin.newIdent(), testRepo, "Test commit", "file.txt", "content") + .to("refs/heads/master"); + result.assertOkStatus(); + + ObjectId newCommit = result.getCommit(); + + // Block read permission on master + projectOperations + .project(project) + .forUpdate() + .add(block(Permission.READ).ref(R_HEADS_MASTER).group(REGISTERED_USERS)) + .update(); + + // Use non-admin user + requestScopeOperations.setApiUser(user.id()); + + ResourceNotFoundException thrown = + assertThrows( + ResourceNotFoundException.class, + () -> + gApi.projects() + .name(project.get()) + .diffFile(initialCommit.name(), newCommit.name(), "file.txt")); + assertThat(thrown).hasMessageThat().contains("not visible"); + } + + @Test + public void listDiffFiles_modifiedFile() throws Exception { + // First create a file + PushOneCommit.Result result1 = + pushFactory + .create(admin.newIdent(), testRepo, "Initial commit", "file.txt", "initial content") + .to("refs/heads/master"); + result1.assertOkStatus(); + ObjectId commit1 = result1.getCommit(); + + // Then modify the file + PushOneCommit.Result result2 = + pushFactory + .create(admin.newIdent(), testRepo, "Modify file", "file.txt", "modified content") + .to("refs/heads/master"); + result2.assertOkStatus(); + ObjectId commit2 = result2.getCommit(); + + Map<String, FileInfo> files = + gApi.projects().name(project.get()).diffFiles(commit1.name(), commit2.name(), true); + + assertThat(files).containsKey("file.txt"); + FileInfo fileInfo = files.get("file.txt"); + // Modified files should not have a status (status is null for MODIFIED) + assertThat(fileInfo.status).isNull(); + } + + @Test + public void listDiffFiles_deletedFile() throws Exception { + // Create a file + PushOneCommit.Result result = + pushFactory + .create(admin.newIdent(), testRepo, "Add file", "file-to-check.txt", "content") + .to("refs/heads/master"); + result.assertOkStatus(); + ObjectId commitWithFile = result.getCommit(); + + // When comparing from the commit with the file to initialCommit (which doesn't have it), + // the file should appear as deleted + Map<String, FileInfo> files = + gApi.projects() + .name(project.get()) + .diffFiles(commitWithFile.name(), initialCommit.name(), true); + + assertThat(files).containsKey("file-to-check.txt"); + FileInfo fileInfo = files.get("file-to-check.txt"); + assertThat(fileInfo.status).isEqualTo('D'); + } + + @Test + public void listDiffFiles_addedFile() throws Exception { + PushOneCommit.Result result = + pushFactory + .create(admin.newIdent(), testRepo, "Add file", "new-file.txt", "new content") + .to("refs/heads/master"); + result.assertOkStatus(); + ObjectId newCommit = result.getCommit(); + + Map<String, FileInfo> files = + gApi.projects().name(project.get()).diffFiles(initialCommit.name(), newCommit.name(), true); + + assertThat(files).containsKey("new-file.txt"); + FileInfo fileInfo = files.get("new-file.txt"); + assertThat(fileInfo.status).isEqualTo('A'); + } + + @Test + public void listDiffFiles_includesFilesFromIntermediateCommits() throws Exception { + // Create a chain of commits: initial -> commit1 (adds file1) -> commit2 (adds file2) + // When comparing initial to commit2, we should see both file1 and file2 + + // Commit 1: Add file1.txt + PushOneCommit.Result result1 = + pushFactory + .create(admin.newIdent(), testRepo, "Add file1", "file1.txt", "content of file1") + .to("refs/heads/master"); + result1.assertOkStatus(); + + // Commit 2: Add file2.txt (doesn't touch file1.txt) + PushOneCommit.Result result2 = + pushFactory + .create(admin.newIdent(), testRepo, "Add file2", "file2.txt", "content of file2") + .to("refs/heads/master"); + result2.assertOkStatus(); + ObjectId commit2 = result2.getCommit(); + + // Compare initial (grandparent) to commit2 (grandchild) + // Should include file1.txt from commit1 and file2.txt from commit2 + Map<String, FileInfo> files = + gApi.projects().name(project.get()).diffFiles(initialCommit.name(), commit2.name(), true); + + // Both files should be present + assertThat(files.keySet()).contains("file1.txt"); + assertThat(files.keySet()).contains("file2.txt"); + + // Both should be marked as added + assertThat(files.get("file1.txt").status).isEqualTo('A'); + assertThat(files.get("file2.txt").status).isEqualTo('A'); + } + + @Test + public void listDiffFiles_includesFilesFromMultipleIntermediateCommits() throws Exception { + // Create a longer chain: initial -> c1 -> c2 -> c3 -> c4 + // When comparing initial to c4, all files from intermediate commits should be visible + + PushOneCommit.Result r1 = + pushFactory + .create(admin.newIdent(), testRepo, "Commit 1", "a.txt", "a") + .to("refs/heads/master"); + r1.assertOkStatus(); + + PushOneCommit.Result r2 = + pushFactory + .create(admin.newIdent(), testRepo, "Commit 2", "b.txt", "b") + .to("refs/heads/master"); + r2.assertOkStatus(); + + PushOneCommit.Result r3 = + pushFactory + .create(admin.newIdent(), testRepo, "Commit 3", "c.txt", "c") + .to("refs/heads/master"); + r3.assertOkStatus(); + + PushOneCommit.Result r4 = + pushFactory + .create(admin.newIdent(), testRepo, "Commit 4", "d.txt", "d") + .to("refs/heads/master"); + r4.assertOkStatus(); + ObjectId commit4 = r4.getCommit(); + + // Compare initial to commit4 - should see all 4 files + Map<String, FileInfo> files = + gApi.projects().name(project.get()).diffFiles(initialCommit.name(), commit4.name(), true); + + assertThat(files.keySet()).contains("a.txt"); + assertThat(files.keySet()).contains("b.txt"); + assertThat(files.keySet()).contains("c.txt"); + assertThat(files.keySet()).contains("d.txt"); + } + + @Test + public void listDiffFiles_includesFilesAfterRebase() throws Exception { + // Scenario: Create parent change, child change, amend parent, rebase child. + // The project-level diff should show ALL files, not filter out "rebase-only" changes. + // + // Setup: + // 1. Create change1 with fileA.txt + // 2. Create change2 (child of change1) with fileB.txt + // 3. Amend change1 to add fileC.txt + // 4. Rebase change2 on the amended change1 + // 5. Compare initialCommit to rebased change2 - should see fileA.txt, fileB.txt, fileC.txt + + // Create change1 with fileA.txt + PushOneCommit.Result change1 = + pushFactory + .create(admin.newIdent(), testRepo, "Change 1", "fileA.txt", "content A") + .to("refs/for/master"); + change1.assertOkStatus(); + String change1Id = change1.getChangeId(); + + // Create change2 (child of change1) with fileB.txt + PushOneCommit.Result change2 = + pushFactory + .create(admin.newIdent(), testRepo, "Change 2", "fileB.txt", "content B") + .to("refs/for/master"); + change2.assertOkStatus(); + String change2Id = change2.getChangeId(); + + // Amend change1 to add fileC.txt + testRepo.reset(change1.getCommit()); + PushOneCommit.Result amendResult = + pushFactory + .create( + admin.newIdent(), + testRepo, + "Change 1 amended", + ImmutableMap.of("fileA.txt", "content A", "fileC.txt", "content C"), + change1Id) + .to("refs/for/master"); + amendResult.assertOkStatus(); + + // Approve and submit change1 + gApi.changes() + .id(change1Id) + .current() + .review(com.google.gerrit.extensions.api.changes.ReviewInput.approve()); + gApi.changes().id(change1Id).current().submit(); + + // Rebase change2 on top of the submitted change1 + gApi.changes().id(change2Id).rebase(); + + // Get the rebased commit SHA + String rebasedCommitSha = gApi.changes().id(change2Id).get().getCurrentRevision().commit.commit; + + // Compare initialCommit to rebased change2 + // Should see all files: fileA.txt, fileB.txt, fileC.txt + Map<String, FileInfo> files = + gApi.projects().name(project.get()).diffFiles(initialCommit.name(), rebasedCommitSha, true); + + // All three files should be present, including fileC.txt which was added + // in the amended parent change and might be flagged as "due to rebase" + assertThat(files.keySet()).contains("fileA.txt"); + assertThat(files.keySet()).contains("fileB.txt"); + assertThat(files.keySet()).contains("fileC.txt"); + } + + @Test + public void getDiffFile_showsContentForRebasedFile() throws Exception { + // Similar to above, but verify that the file diff endpoint also shows content + // for files that might be flagged as "due to rebase" + + // Create change1 with fileA.txt + PushOneCommit.Result change1 = + pushFactory + .create(admin.newIdent(), testRepo, "Change 1", "fileA.txt", "content A") + .to("refs/for/master"); + change1.assertOkStatus(); + String change1Id = change1.getChangeId(); + + // Create change2 (child of change1) with fileB.txt + PushOneCommit.Result change2 = + pushFactory + .create(admin.newIdent(), testRepo, "Change 2", "fileB.txt", "content B") + .to("refs/for/master"); + change2.assertOkStatus(); + String change2Id = change2.getChangeId(); + + // Amend change1 to add fileC.txt + testRepo.reset(change1.getCommit()); + PushOneCommit.Result amendResult = + pushFactory + .create( + admin.newIdent(), + testRepo, + "Change 1 amended", + ImmutableMap.of("fileA.txt", "content A", "fileC.txt", "rebase content"), + change1Id) + .to("refs/for/master"); + amendResult.assertOkStatus(); + + // Approve and submit change1 + gApi.changes() + .id(change1Id) + .current() + .review(com.google.gerrit.extensions.api.changes.ReviewInput.approve()); + gApi.changes().id(change1Id).current().submit(); + + // Rebase change2 on top of the submitted change1 + gApi.changes().id(change2Id).rebase(); + + // Get the rebased commit SHA + String rebasedCommitSha = gApi.changes().id(change2Id).get().getCurrentRevision().commit.commit; + + // Get diff for fileC.txt - this file was added in the amended parent + // and might be flagged as "due to rebase", but should still have content + DiffInfo diffInfo = + gApi.projects() + .name(project.get()) + .diffFile(initialCommit.name(), rebasedCommitSha, "fileC.txt"); + + assertThat(diffInfo).isNotNull(); + assertThat(diffInfo.changeType).isEqualTo(ChangeType.ADDED); + // Verify the content is present (not filtered out) + assertThat(diffInfo.content).isNotEmpty(); + } +}
diff --git a/javatests/com/google/gerrit/acceptance/server/change/BUILD b/javatests/com/google/gerrit/acceptance/server/change/BUILD index 56c27a0..3f9767f 100644 --- a/javatests/com/google/gerrit/acceptance/server/change/BUILD +++ b/javatests/com/google/gerrit/acceptance/server/change/BUILD
@@ -1,3 +1,4 @@ +load("@rules_java//java:defs.bzl", "java_library") load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests") acceptance_tests(
diff --git a/javatests/com/google/gerrit/acceptance/server/permissions/ExternalUserPermissionIT.java b/javatests/com/google/gerrit/acceptance/server/permissions/ExternalUserPermissionIT.java index 0e13732..8cf8206 100644 --- a/javatests/com/google/gerrit/acceptance/server/permissions/ExternalUserPermissionIT.java +++ b/javatests/com/google/gerrit/acceptance/server/permissions/ExternalUserPermissionIT.java
@@ -51,10 +51,10 @@ import com.google.gerrit.server.permissions.PermissionBackend; import com.google.gerrit.server.project.ProjectState; import com.google.inject.AbstractModule; +import com.google.inject.Inject; import com.google.inject.Module; import java.util.Collection; import java.util.stream.StreamSupport; -import javax.inject.Inject; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.junit.Before;
diff --git a/javatests/com/google/gerrit/acceptance/server/permissions/GroupBackedUserPermissionIT.java b/javatests/com/google/gerrit/acceptance/server/permissions/GroupBackedUserPermissionIT.java index 4ad1caa..f0dd3a7 100644 --- a/javatests/com/google/gerrit/acceptance/server/permissions/GroupBackedUserPermissionIT.java +++ b/javatests/com/google/gerrit/acceptance/server/permissions/GroupBackedUserPermissionIT.java
@@ -42,8 +42,8 @@ import com.google.gerrit.server.permissions.PermissionBackend; import com.google.gerrit.server.query.change.GroupBackedUser; import com.google.inject.AbstractModule; +import com.google.inject.Inject; import com.google.inject.Module; -import javax.inject.Inject; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.junit.Before;
diff --git a/javatests/com/google/gerrit/acceptance/server/project/ProjectCacheIT.java b/javatests/com/google/gerrit/acceptance/server/project/ProjectCacheIT.java index d10d559..2418172 100644 --- a/javatests/com/google/gerrit/acceptance/server/project/ProjectCacheIT.java +++ b/javatests/com/google/gerrit/acceptance/server/project/ProjectCacheIT.java
@@ -26,9 +26,9 @@ import com.google.gerrit.server.config.PluginConfigFactory; import com.google.gerrit.server.project.ProjectCacheImpl; import com.google.gerrit.server.project.ProjectConfig; +import com.google.inject.Inject; import com.google.inject.name.Named; import java.util.Optional; -import javax.inject.Inject; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS; import org.junit.Test;
diff --git a/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java b/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java index 0bed233..52a380f 100644 --- a/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java +++ b/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java
@@ -20,6 +20,10 @@ import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; import static com.google.gerrit.server.project.testing.TestLabels.value; import static com.google.gerrit.testing.GerritJUnit.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableMap; import com.google.common.collect.MoreCollectors; @@ -47,18 +51,27 @@ import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.index.query.Predicate; import com.google.gerrit.index.query.QueryParseException; +import com.google.gerrit.server.plugincontext.PluginSetContext; import com.google.gerrit.server.project.SubmitRequirementEvaluationException; import com.google.gerrit.server.project.SubmitRequirementsEvaluatorImpl; import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeQueryBuilder; import com.google.gerrit.server.query.change.ChangeQueryBuilder.ChangeIsOperandFactory; import com.google.gerrit.server.query.change.InternalChangeQuery; +import com.google.gerrit.server.query.change.SubmitRequirementChangeQueryBuilder; import com.google.gerrit.server.query.change.SubmitRequirementPredicate; +import com.google.gerrit.server.util.OneOffRequestContext; import com.google.inject.Inject; import com.google.inject.Provider; import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; @NoHttpd public class SubmitRequirementsEvaluatorIT extends AbstractDaemonTest { @@ -68,6 +81,9 @@ @Inject private ExtensionRegistry extensionRegistry; @Inject private RequestScopeOperations requestScopeOperations; @Inject private ChangeOperations changeOperations; + @Inject SubmitRequirementChangeQueryBuilder.Factory queryBuilderFactory; + @Inject PluginSetContext<SubmitRequirement> globalSubmitRequirements; + @Inject OneOffRequestContext oneOffRequestContext; private ChangeData changeData; private String changeId; @@ -998,6 +1014,29 @@ assertThat(srResult.errorMessage().get()).isEqualTo("Invalid content pattern."); } + @Test + public void invalidPathRegex_returnsErrorExpressionResult() throws Exception { + SubmitRequirementExpression exp = SubmitRequirementExpression.create("path:\"^foo[\""); + + SubmitRequirementExpressionResult srResult = evaluator.evaluateExpression(exp, changeData); + assertThat(srResult.status()).isEqualTo(SubmitRequirementExpressionResult.Status.ERROR); + assertThat(srResult.errorMessage().get()).contains("unexpected end-of-string"); + } + + @Test + public void invalidPathRegex_returnsErrorRequirementResult() throws Exception { + SubmitRequirement sr = + createSubmitRequirement( + /* applicabilityExpr= */ null, + /* submittabilityExpr= */ "path:\"^foo[\"", + /* overrideExpr= */ null); + + SubmitRequirementResult srResult = evaluator.evaluateRequirement(sr, changeData); + assertThat(srResult.status()).isEqualTo(SubmitRequirementResult.Status.ERROR); + assertThat(srResult.submittabilityExpressionResult().get().errorMessage().get()) + .contains("unexpected end-of-string"); + } + private void voteLabel(String changeId, String labelName, int score) throws RestApiException { gApi.changes().id(changeId).current().review(new ReviewInput().label(labelName, score)); } @@ -1078,4 +1117,62 @@ return this; } } + + @Test + @GerritConfig(name = "submitRequirement.executionTimeout", value = "2") + @GerritConfig(name = "submitRequirement.evaluationThreads", value = "2") + public void evaluateRequirement_timesOut_returnsTimeoutResult() throws Exception { + ExecutorService mockExecutor = Mockito.mock(ExecutorService.class); + Future<SubmitRequirementResult> timedOutFuture = Mockito.mock(Future.class); + SubmitRequirementsEvaluatorImpl evaluatorWithMockedExecutor = + new SubmitRequirementsEvaluatorImpl( + queryBuilderFactory, + projectCache, + globalSubmitRequirements, + cfg, + oneOffRequestContext, + mockExecutor); + + SubmitRequirement sr = + SubmitRequirement.builder() + .setName("timeout-test") + .setSubmittabilityExpression(SubmitRequirementExpression.create("is:true")) + .setAllowOverrideInChildProjects(false) + .build(); + + when(mockExecutor.submit((Callable<SubmitRequirementResult>) any())).thenReturn(timedOutFuture); + when(timedOutFuture.get(anyLong(), any(TimeUnit.class))) + .thenThrow(new TimeoutException("Simulated timeout")); + + SubmitRequirementResult result = + evaluatorWithMockedExecutor.evaluateRequirement(sr, changeData); + + verify(timedOutFuture).cancel(true); + assertThat(result.submittabilityExpressionResult()).isPresent(); + assertThat(result.submittabilityExpressionResult().get().status()) + .isEqualTo(SubmitRequirementExpressionResult.Status.TIMEOUT); + assertThat(result.submitRequirement()).isEqualTo(sr); + } + + @Test + @GerritConfig(name = "submitRequirement.executionTimeout", value = "2") + @GerritConfig(name = "submitRequirement.evaluationThreads", value = "-1") + public void evaluateRequirement_negativeThreads_runsDirectExecutor_noTimeout() { + SubmitRequirement sr = + SubmitRequirement.builder() + .setName("direct-exec-test") + .setSubmittabilityExpression(SubmitRequirementExpression.create("is:true")) + .setAllowOverrideInChildProjects(false) + .build(); + + SubmitRequirementResult result = evaluator.evaluateRequirement(sr, changeData); + + assertThat(result.submittabilityExpressionResult()).isPresent(); + + SubmitRequirementExpressionResult exprResult = result.submittabilityExpressionResult().get(); + + assertThat(exprResult.status()).isEqualTo(SubmitRequirementExpressionResult.Status.PASS); + + assertThat(result.status()).isEqualTo(SubmitRequirementResult.Status.SATISFIED); + } }
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/index/DefaultIndexBindingIT.java b/javatests/com/google/gerrit/acceptance/testsuite/index/DefaultIndexBindingIT.java index f6e5fb3..107d241 100644 --- a/javatests/com/google/gerrit/acceptance/testsuite/index/DefaultIndexBindingIT.java +++ b/javatests/com/google/gerrit/acceptance/testsuite/index/DefaultIndexBindingIT.java
@@ -23,7 +23,7 @@ import com.google.gerrit.index.testing.AbstractFakeIndex; import com.google.gerrit.server.index.change.ChangeIndexCollection; import com.google.gerrit.testing.SystemPropertiesTestRule; -import javax.inject.Inject; +import com.google.inject.Inject; import org.junit.ClassRule; import org.junit.Test;
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/index/FakeIndexBindingIT.java b/javatests/com/google/gerrit/acceptance/testsuite/index/FakeIndexBindingIT.java index acb2e5a..d7afb69 100644 --- a/javatests/com/google/gerrit/acceptance/testsuite/index/FakeIndexBindingIT.java +++ b/javatests/com/google/gerrit/acceptance/testsuite/index/FakeIndexBindingIT.java
@@ -21,7 +21,7 @@ import com.google.gerrit.index.testing.AbstractFakeIndex; import com.google.gerrit.server.index.change.ChangeIndexCollection; import com.google.gerrit.testing.SystemPropertiesTestRule; -import javax.inject.Inject; +import com.google.inject.Inject; import org.junit.ClassRule; import org.junit.Test;
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/index/LuceneIndexBindingIT.java b/javatests/com/google/gerrit/acceptance/testsuite/index/LuceneIndexBindingIT.java index 5dd6f01..36ea5d1 100644 --- a/javatests/com/google/gerrit/acceptance/testsuite/index/LuceneIndexBindingIT.java +++ b/javatests/com/google/gerrit/acceptance/testsuite/index/LuceneIndexBindingIT.java
@@ -21,7 +21,7 @@ import com.google.gerrit.lucene.LuceneChangeIndex; import com.google.gerrit.server.index.change.ChangeIndexCollection; import com.google.gerrit.testing.SystemPropertiesTestRule; -import javax.inject.Inject; +import com.google.inject.Inject; import org.junit.ClassRule; import org.junit.Test;
diff --git a/javatests/com/google/gerrit/entities/converter/AccountProtoConverterTest.java b/javatests/com/google/gerrit/entities/converter/AccountProtoConverterTest.java index 756a266..e447365 100644 --- a/javatests/com/google/gerrit/entities/converter/AccountProtoConverterTest.java +++ b/javatests/com/google/gerrit/entities/converter/AccountProtoConverterTest.java
@@ -23,6 +23,8 @@ import java.lang.reflect.Type; import java.time.Instant; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; /** * Tests for {@link AccountProtoConverter}. @@ -32,8 +34,9 @@ * {@code SafeProtoConverter} because proto3 cannot distinguish between unset and empty string * fields, while Account treats null and empty string differently. */ +@RunWith(JUnit4.class) public class AccountProtoConverterTest { - private final AccountProtoConverter converter = AccountProtoConverter.INSTANCE; + private static final AccountProtoConverter CONVERTER = AccountProtoConverter.INSTANCE; @Test public void allFieldsConvertedToProtoAndBack() { @@ -49,7 +52,7 @@ .setUniqueTag("unique-123") .build(); - Account roundTripped = converter.fromProto(converter.toProto(account)); + Account roundTripped = CONVERTER.fromProto(CONVERTER.toProto(account)); assertThat(roundTripped).isEqualTo(account); } @@ -61,7 +64,7 @@ .setInactive(false) .build(); - Account roundTripped = converter.fromProto(converter.toProto(account)); + Account roundTripped = CONVERTER.fromProto(CONVERTER.toProto(account)); assertThat(roundTripped.fullName()).isNull(); assertThat(roundTripped.displayName()).isNull(); @@ -79,7 +82,7 @@ .setInactive(false) .build(); - Account roundTripped = converter.fromProto(converter.toProto(account)); + Account roundTripped = CONVERTER.fromProto(CONVERTER.toProto(account)); assertThat(roundTripped.avatarEmail()).isEqualTo("avatar@example.com"); assertThat(roundTripped.preferredEmail()).isEqualTo("preferred@example.com"); @@ -94,7 +97,7 @@ .setInactive(false) .build(); - Account roundTripped = converter.fromProto(converter.toProto(account)); + Account roundTripped = CONVERTER.fromProto(CONVERTER.toProto(account)); assertThat(roundTripped.avatarEmail()).isNull(); assertThat(roundTripped.effectiveAvatarEmail()).isEqualTo("preferred@example.com"); @@ -107,8 +110,8 @@ Account inactive = Account.builder(Account.id(2), Instant.ofEpochMilli(1L)).setInactive(true).build(); - assertThat(converter.fromProto(converter.toProto(active)).inactive()).isFalse(); - assertThat(converter.fromProto(converter.toProto(inactive)).inactive()).isTrue(); + assertThat(CONVERTER.fromProto(CONVERTER.toProto(active)).inactive()).isFalse(); + assertThat(CONVERTER.fromProto(CONVERTER.toProto(inactive)).inactive()).isTrue(); } /** @@ -133,6 +136,6 @@ .put("status", String.class) .put("metaId", String.class) .put("uniqueTag", String.class) - .build()); + .buildOrThrow()); } }
diff --git a/javatests/com/google/gerrit/extensions/registration/DynamicItemTest.java b/javatests/com/google/gerrit/extensions/registration/DynamicItemTest.java index 5ca382a..a2c9ef7 100644 --- a/javatests/com/google/gerrit/extensions/registration/DynamicItemTest.java +++ b/javatests/com/google/gerrit/extensions/registration/DynamicItemTest.java
@@ -28,7 +28,10 @@ import com.google.inject.TypeLiteral; import java.util.function.Consumer; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +@RunWith(JUnit4.class) public class DynamicItemTest { private static final String PLUGIN_NAME = "plugin-name"; private static final String ANOTHER_PLUGIN = "another-plugin"; @@ -57,12 +60,12 @@ } @Test - public void shouldAssignDynamicItemTwice_GerritCoreThenPlugin() { + public void shouldAssignDynamicItemTwice_gerritCoreThenPlugin() { shouldAssignDynamicItemTwice(PluginName.GERRIT, ANOTHER_PLUGIN); } @Test - public void shouldAssignDynamicItemTwice_PluginOverridesPlugin() { + public void shouldAssignDynamicItemTwice_pluginOverridesPlugin() { shouldAssignDynamicItemTwice(PLUGIN_NAME, ANOTHER_PLUGIN); }
diff --git a/javatests/com/google/gerrit/httpd/BUILD b/javatests/com/google/gerrit/httpd/BUILD index 2b93045..b8258bb 100644 --- a/javatests/com/google/gerrit/httpd/BUILD +++ b/javatests/com/google/gerrit/httpd/BUILD
@@ -8,7 +8,9 @@ "//java/com/google/gerrit/extensions:api", "//java/com/google/gerrit/httpd", "//java/com/google/gerrit/server", + "//java/com/google/gerrit/server/restapi", "//java/com/google/gerrit/testing:gerrit-junit", + "//java/com/google/gerrit/testing:gerrit-test-util", "//javatests/com/google/gerrit/util/http/testutil", "//lib:gson", "//lib:guava",
diff --git a/javatests/com/google/gerrit/httpd/raw/IndexServletTest.java b/javatests/com/google/gerrit/httpd/raw/IndexServletTest.java index d582b4b..3b7f439 100644 --- a/javatests/com/google/gerrit/httpd/raw/IndexServletTest.java +++ b/javatests/com/google/gerrit/httpd/raw/IndexServletTest.java
@@ -15,27 +15,181 @@ package com.google.gerrit.httpd.raw; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.gerrit.extensions.annotations.Exports; import com.google.gerrit.extensions.api.GerritApi; +import com.google.gerrit.extensions.api.accounts.AccountApi; import com.google.gerrit.extensions.api.accounts.Accounts; import com.google.gerrit.extensions.api.config.Config; import com.google.gerrit.extensions.api.config.Server; import com.google.gerrit.extensions.common.ServerInfo; +import com.google.gerrit.extensions.config.CloneCommand; +import com.google.gerrit.extensions.config.DownloadCommand; +import com.google.gerrit.extensions.config.DownloadScheme; import com.google.gerrit.extensions.restapi.AuthException; +import com.google.gerrit.extensions.restapi.RestApiException; +import com.google.gerrit.server.AnonymousUser; +import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.account.GroupMembership; +import com.google.gerrit.server.account.ListGroupMembership; +import com.google.gerrit.server.config.ConfigResource; import com.google.gerrit.server.experiments.ConfigExperimentFeatures; import com.google.gerrit.server.experiments.ExperimentFeatures; import com.google.gerrit.server.experiments.ExperimentFeaturesConstants; +import com.google.gerrit.server.restapi.config.GetServerInfo; +import com.google.gerrit.server.util.ManualRequestContext; +import com.google.gerrit.server.util.ThreadLocalRequestContext; +import com.google.gerrit.testing.InMemoryModule; import com.google.gerrit.util.http.testutil.FakeHttpServletRequest; import com.google.gerrit.util.http.testutil.FakeHttpServletResponse; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Provider; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +@RunWith(MockitoJUnitRunner.class) public class IndexServletTest { + private static final String FAKE_USER1 = "user1"; + private static final String FAKE_USER2 = "user2"; + private static final FakeCurrentUser FAKE_CURRENT_USER1 = new FakeCurrentUser(FAKE_USER1); + private static final FakeCurrentUser FAKE_CURRENT_USER2 = new FakeCurrentUser(FAKE_USER2); + + @Mock Server serverApi; + + @Mock GerritApi gerritApi; + + @Mock Config configApi; + + @Mock Accounts accountsApi; + + @Inject ThreadLocalRequestContext threadLocalRequestContext; + + @Inject GetServerInfo getServerInfo; + + private static class FakeCurrentUser extends CurrentUser { + private static final GroupMembership groups = new ListGroupMembership(List.of()); + + private final String username; + + FakeCurrentUser(String name) { + username = name; + } + + @Override + public GroupMembership getEffectiveGroups() { + return groups; + } + + @Override + public Object getCacheKey() { + return username; + } + + @Override + public Optional<String> getUserName() { + return Optional.ofNullable(username); + } + + @Override + public boolean isIdentifiedUser() { + return true; + } + } + + private static class FakeDownloadScheme extends DownloadScheme { + @Override + public String getUrl(String project) { + return "some-protocol://" + project; + } + + @Override + public boolean isAuthRequired() { + return true; + } + + @Override + public boolean isAuthSupported() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public boolean isHidden() { + return false; + } + } + + private static class FakeDownloadCommand extends DownloadCommand { + + @Inject Provider<CurrentUser> currentUserProvider; + + @Override + public String getCommand(DownloadScheme scheme, String project, String ref) { + CurrentUser currentUser = currentUserProvider.get(); + String url = scheme.getUrl(project); + if (currentUser.isIdentifiedUser()) { + url = url + "/" + currentUser.getUserName().orElseThrow(); + } + return String.format("fake git fetch %s %s", url, ref); + } + } + + private static class FakeCloneCommand extends CloneCommand { + + @Inject Provider<CurrentUser> currentUserProvider; + + @Override + public String getCommand(DownloadScheme scheme, String project) { + CurrentUser currentUser = currentUserProvider.get(); + String url = scheme.getUrl(project); + if (currentUser.isIdentifiedUser()) { + url = url + "/" + currentUser.getUserName().orElseThrow(); + } + return String.format("fake git clone %s", url); + } + } + + @Before + public void setup() throws RestApiException { + Injector injector = + Guice.createInjector( + new InMemoryModule() { + @Override + protected void configure() { + configure(false); + bind(GerritApi.class).toInstance(gerritApi); + bind(DownloadScheme.class) + .annotatedWith(Exports.named("testscheme")) + .to(FakeDownloadScheme.class); + bind(DownloadCommand.class) + .annotatedWith(Exports.named("checkout")) + .to(FakeDownloadCommand.class); + bind(CloneCommand.class) + .annotatedWith(Exports.named("clone")) + .to(FakeCloneCommand.class); + } + }); + injector.injectMembers(this); + } @Test public void renderTemplate() throws Exception { @@ -105,4 +259,77 @@ + String.join("\\x22,\\x22", expectedEnabled) + "\\x22\\x5d');</script>"); } + + @Test + public void downloadInfoIsTheSameForTwoAnonymousUsers() throws Exception { + try (ManualRequestContext ctx = mockGerritApi(new AnonymousUser())) { + + IndexServlet servlet = + new IndexServlet( + null, + null, + null, + gerritApi, + new ConfigExperimentFeatures(new org.eclipse.jgit.lib.Config())); + + FakeHttpServletResponse indexHtmlResponse1 = new FakeHttpServletResponse(); + servlet.doGet(new FakeHttpServletRequest(), indexHtmlResponse1); + FakeHttpServletResponse indexHtmlResponse2 = new FakeHttpServletResponse(); + servlet.doGet(new FakeHttpServletRequest(), indexHtmlResponse2); + + assertThat(indexHtmlResponse1.getActualBodyString()) + .isEqualTo(indexHtmlResponse2.getActualBodyString()); + } + } + + @Test + public void downloadInfoIsNotCachedForIdentifiedUsers() throws Exception { + FakeHttpServletResponse indexHtmlResponse1 = new FakeHttpServletResponse(); + FakeHttpServletResponse indexHtmlResponse2 = new FakeHttpServletResponse(); + + IndexServlet servlet = + new IndexServlet( + null, + null, + null, + gerritApi, + new ConfigExperimentFeatures(new org.eclipse.jgit.lib.Config())); + + try (ManualRequestContext ctx = mockGerritApi(FAKE_CURRENT_USER1)) { + servlet.doGet(new FakeHttpServletRequest(), indexHtmlResponse1); + } + + try (ManualRequestContext ctx = mockGerritApi(FAKE_CURRENT_USER2)) { + servlet.doGet(new FakeHttpServletRequest(), indexHtmlResponse2); + } + + String indexBodyUser1 = indexHtmlResponse1.getActualBodyString(); + String indexBodyUser2 = indexHtmlResponse2.getActualBodyString(); + assertThat(indexBodyUser1).contains(FAKE_USER1); + assertThat(indexBodyUser2).contains(FAKE_USER2); + assertThat(indexBodyUser1).isNotEqualTo(indexBodyUser2); + verify(serverApi, times(2)).getInfo(); + } + + private ManualRequestContext mockGerritApi(CurrentUser currentUser) throws RestApiException { + ManualRequestContext ctx = new ManualRequestContext(currentUser, threadLocalRequestContext); + if (currentUser.isIdentifiedUser()) { + AccountApi accountApi = mock(AccountApi.class); + lenient().when(accountsApi.self()).thenReturn(accountApi); + } else { + lenient() + .when(accountsApi.self()) + .thenThrow(new AuthException("user needs to be authenticated")); + } + + lenient().when(serverApi.getVersion()).thenReturn("123"); + lenient().when(serverApi.topMenus()).thenReturn(ImmutableList.of(), ImmutableList.of()); + lenient() + .when(serverApi.getInfo()) + .thenAnswer((m) -> getServerInfo.apply(new ConfigResource()).value()); + lenient().when(configApi.server()).thenReturn(serverApi); + lenient().when(gerritApi.accounts()).thenReturn(accountsApi); + lenient().when(gerritApi.config()).thenReturn(configApi); + return ctx; + } }
diff --git a/javatests/com/google/gerrit/server/cache/h2/BUILD b/javatests/com/google/gerrit/server/cache/h2/BUILD index 438990c..7b000653 100644 --- a/javatests/com/google/gerrit/server/cache/h2/BUILD +++ b/javatests/com/google/gerrit/server/cache/h2/BUILD
@@ -4,11 +4,14 @@ name = "tests", srcs = glob(["**/*.java"]), deps = [ + "//java/com/google/gerrit/extensions:api", + "//java/com/google/gerrit/server", "//java/com/google/gerrit/server/cache/h2", "//java/com/google/gerrit/server/cache/serialize", "//java/com/google/gerrit/server/util/time", "//lib:guava", "//lib:h2", + "//lib:jgit", "//lib:junit", "//lib/guice", "//lib/mockito",
diff --git a/javatests/com/google/gerrit/server/cache/h2/H2CacheFactoryTest.java b/javatests/com/google/gerrit/server/cache/h2/H2CacheFactoryTest.java new file mode 100644 index 0000000..9f55711 --- /dev/null +++ b/javatests/com/google/gerrit/server/cache/h2/H2CacheFactoryTest.java
@@ -0,0 +1,184 @@ +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.cache.h2; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.cache.Weigher; +import com.google.gerrit.extensions.registration.DynamicMap; +import com.google.gerrit.server.cache.CacheDef; +import com.google.gerrit.server.cache.MemoryCacheFactory; +import com.google.gerrit.server.cache.PersistentCacheDef; +import com.google.gerrit.server.cache.h2.H2CacheImpl.SqlStore; +import com.google.gerrit.server.cache.serialize.CacheSerializer; +import com.google.gerrit.server.cache.serialize.StringCacheSerializer; +import com.google.inject.TypeLiteral; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.time.Duration; +import java.util.EnumSet; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.annotation.Nullable; +import org.eclipse.jgit.lib.Config; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class H2CacheFactoryTest { + private static final TypeLiteral<String> STRING_TYPE = new TypeLiteral<>() {}; + + @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void newSqlStore_setsUrl() throws Exception { + Path cacheDir = temporaryFolder.newFolder("cache").toPath(); + H2CacheFactory factory = + new H2CacheFactory( + new NoOpMemoryCacheFactory(), + new Config(), + DynamicMap.emptyMap(), + null, + null, + null, + cacheDir, + EnumSet.noneOf(CacheOptions.class), + new AtomicBoolean(false)); + + PersistentCacheDef<String, String> def = new SimplePersistentCacheDef("demo-cache"); + SqlStore<String, String> store = invokeNewSqlStore(factory, def, 1L); + + assertThat(readField(store, "url")) + .isEqualTo( + "jdbc:h2:file:" + + cacheDir.resolve("demo-cache-v2").toAbsolutePath() + + ";DB_CLOSE_DELAY=-1"); + } + + @SuppressWarnings("unchecked") + private static SqlStore<String, String> invokeNewSqlStore( + H2CacheFactory factory, PersistentCacheDef<String, String> def, long maxSize) + throws Exception { + Method method = + H2CacheFactory.class.getDeclaredMethod("newSqlStore", PersistentCacheDef.class, long.class); + method.setAccessible(true); + return (SqlStore<String, String>) method.invoke(factory, def, maxSize); + } + + private static Object readField(Object target, String fieldName) throws Exception { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(target); + } + + private static class NoOpMemoryCacheFactory implements MemoryCacheFactory { + @Override + public <K, V> Cache<K, V> build(CacheDef<K, V> def) { + return CacheBuilder.newBuilder().build(); + } + + @Override + public <K, V> LoadingCache<K, V> build(CacheDef<K, V> def, CacheLoader<K, V> loader) { + return CacheBuilder.newBuilder().build(loader); + } + } + + private static class SimplePersistentCacheDef implements PersistentCacheDef<String, String> { + private final String name; + + private SimplePersistentCacheDef(String name) { + this.name = name; + } + + @Override + public String name() { + return name; + } + + @Override + public String configKey() { + return name; + } + + @Override + public TypeLiteral<String> keyType() { + return STRING_TYPE; + } + + @Override + public TypeLiteral<String> valueType() { + return STRING_TYPE; + } + + @Override + public long maximumWeight() { + return 0; + } + + @Override + @Nullable + public Duration expireAfterWrite() { + return null; + } + + @Override + @Nullable + public Duration expireFromMemoryAfterAccess() { + return null; + } + + @Override + @Nullable + public Duration refreshAfterWrite() { + return null; + } + + @Override + @Nullable + public Weigher<String, String> weigher() { + return null; + } + + @Override + @Nullable + public CacheLoader<String, String> loader() { + return null; + } + + @Override + public long diskLimit() { + return 1; + } + + @Override + public int version() { + return 1; + } + + @Override + public CacheSerializer<String> keySerializer() { + return StringCacheSerializer.INSTANCE; + } + + @Override + public CacheSerializer<String> valueSerializer() { + return StringCacheSerializer.INSTANCE; + } + } +}
diff --git a/javatests/com/google/gerrit/server/cache/serialize/entities/FileDiffOutputSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/entities/FileDiffOutputSerializerTest.java index 00272112..1eb8e21 100644 --- a/javatests/com/google/gerrit/server/cache/serialize/entities/FileDiffOutputSerializerTest.java +++ b/javatests/com/google/gerrit/server/cache/serialize/entities/FileDiffOutputSerializerTest.java
@@ -52,6 +52,7 @@ .headerLines(ImmutableList.of("header line 1", "header line 2")) .edits(edits) .negative(Optional.of(true)) + .diffsTooExpensiveToCompute(Optional.of(true)) .build(); byte[] serialized = FileDiffOutput.Serializer.INSTANCE.serialize(fileDiff);
diff --git a/javatests/com/google/gerrit/server/config/UserPreferencesConverterTest.java b/javatests/com/google/gerrit/server/config/UserPreferencesConverterTest.java index 16be1e4..1477df7 100644 --- a/javatests/com/google/gerrit/server/config/UserPreferencesConverterTest.java +++ b/javatests/com/google/gerrit/server/config/UserPreferencesConverterTest.java
@@ -290,6 +290,7 @@ .setHideEmptyPane(true) .setMatchBrackets(false) .setLineWrapping(true) + .setResponsiveMode(UserPreferences.DiffPreferencesInfo.ResponsiveMode.FULL_RESPONSIVE) .setIgnoreWhitespace(Whitespace.IGNORE_TRAILING) .setRetainHeader(true) .setSkipDeleted(false) @@ -327,6 +328,19 @@ } @Test + public void diffPreferencesInfo_migrationFromLineWrapping() { + UserPreferences.DiffPreferencesInfo protoWithLineWrapping = + UserPreferences.DiffPreferencesInfo.newBuilder().setLineWrapping(true).build(); + DiffPreferencesInfo res = DIFF_PREFERENCES_INFO_CONVERTER.fromProto(protoWithLineWrapping); + assertThat(res.responsiveMode).isEqualTo(DiffPreferencesInfo.ResponsiveMode.FULL_RESPONSIVE); + + UserPreferences.DiffPreferencesInfo protoWithNoLineWrapping = + UserPreferences.DiffPreferencesInfo.newBuilder().setLineWrapping(false).build(); + res = DIFF_PREFERENCES_INFO_CONVERTER.fromProto(protoWithNoLineWrapping); + assertThat(res.responsiveMode).isEqualTo(DiffPreferencesInfo.ResponsiveMode.NONE); + } + + @Test public void editPreferencesInfo_compareEnumNames() { // The converter assumes that the enum type equivalents have exactly the same values in both // classes. This test goes over all the enums to verify this assumption.
diff --git a/javatests/com/google/gerrit/server/mail/send/MailSoySauceModuleTest.java b/javatests/com/google/gerrit/server/mail/send/MailSoySauceModuleTest.java index ed179a7..bd32241 100644 --- a/javatests/com/google/gerrit/server/mail/send/MailSoySauceModuleTest.java +++ b/javatests/com/google/gerrit/server/mail/send/MailSoySauceModuleTest.java
@@ -30,11 +30,11 @@ import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; +import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.google.inject.name.Names; import com.google.template.soy.jbcsrc.api.SoySauce; import java.nio.file.Path; -import javax.inject.Provider; import org.eclipse.jgit.lib.Config; import org.junit.Test;
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java index f482287..6f8400c 100644 --- a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java +++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
@@ -1256,6 +1256,7 @@ .put("parentUuid", String.class) .put("range", Comment.Range.class) .put("tag", String.class) + .put("isAi", Boolean.class) .put("revId", String.class) .put("serverId", String.class) .put("unresolved", boolean.class)
diff --git a/javatests/com/google/gerrit/server/schema/JdbcAccountPatchReviewStoreTest.java b/javatests/com/google/gerrit/server/schema/JdbcAccountPatchReviewStoreTest.java new file mode 100644 index 0000000..d8c876b --- /dev/null +++ b/javatests/com/google/gerrit/server/schema/JdbcAccountPatchReviewStoreTest.java
@@ -0,0 +1,68 @@ +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.schema; + +import static com.google.common.truth.Truth.assertThat; + +import java.nio.file.Path; +import org.junit.Test; + +public class JdbcAccountPatchReviewStoreTest { + @Test + public void checkCreateH2Url() { + assertThat(JdbcAccountPatchReviewStore.createH2Url(Path.of("test"))) + .isEqualTo("jdbc:h2:file:" + Path.of("test").toAbsolutePath()); + assertThat(JdbcAccountPatchReviewStore.createH2Url(Path.of("test.db"))) + .isEqualTo("jdbc:h2:file:" + Path.of("test.db").toAbsolutePath()); + assertThat(JdbcAccountPatchReviewStore.createH2Url(Path.of("test.db.mv.db"))) + .isEqualTo("jdbc:h2:file:" + Path.of("test.db.mv.db").toAbsolutePath()); + assertThat(JdbcAccountPatchReviewStore.createH2Url(Path.of("test.db.mv.db.trace.db"))) + .isEqualTo("jdbc:h2:file:" + Path.of("test.db.mv.db.trace.db").toAbsolutePath()); + } + + @Test + public void checkCreateH2UrlWithPath() { + assertThat(JdbcAccountPatchReviewStore.createH2Url(Path.of("path/to/test"))) + .isEqualTo("jdbc:h2:file:" + Path.of("path/to/test").toAbsolutePath()); + assertThat(JdbcAccountPatchReviewStore.createH2Url(Path.of("path/to/test.db"))) + .isEqualTo("jdbc:h2:file:" + Path.of("path/to/test.db").toAbsolutePath()); + assertThat(JdbcAccountPatchReviewStore.createH2Url(Path.of("path/to/test.db.mv.db"))) + .isEqualTo("jdbc:h2:file:" + Path.of("path/to/test.db.mv.db").toAbsolutePath()); + assertThat(JdbcAccountPatchReviewStore.createH2Url(Path.of("path/to/test.db.mv.db.trace.db"))) + .isEqualTo("jdbc:h2:file:" + Path.of("path/to/test.db.mv.db.trace.db").toAbsolutePath()); + } + + @Test + public void checkCreateH2UrlWithSemicolon() { + assertThat(JdbcAccountPatchReviewStore.createH2Url(Path.of("test;test"))) + .isEqualTo( + "jdbc:h2:file:" + Path.of("test;test").toAbsolutePath().toString().replace(";", "\\;")); + assertThat(JdbcAccountPatchReviewStore.createH2Url(Path.of("test.db;test"))) + .isEqualTo( + "jdbc:h2:file:" + + Path.of("test.db;test").toAbsolutePath().toString().replace(";", "\\;")); + assertThat(JdbcAccountPatchReviewStore.createH2Url(Path.of("test.db.mv.db;test"))) + .isEqualTo( + "jdbc:h2:file:" + + Path.of("test.db.mv.db;test").toAbsolutePath().toString().replace(";", "\\;")); + assertThat(JdbcAccountPatchReviewStore.createH2Url(Path.of("test.db.mv.db.trace.db;test"))) + .isEqualTo( + "jdbc:h2:file:" + + Path.of("test.db.mv.db.trace.db;test") + .toAbsolutePath() + .toString() + .replace(";", "\\;")); + } +}
diff --git a/javatests/com/google/gerrit/util/concurrent/ConcurrentBloomFilterTest.java b/javatests/com/google/gerrit/util/concurrent/ConcurrentBloomFilterTest.java index 24b659a..eef5e11 100644 --- a/javatests/com/google/gerrit/util/concurrent/ConcurrentBloomFilterTest.java +++ b/javatests/com/google/gerrit/util/concurrent/ConcurrentBloomFilterTest.java
@@ -312,7 +312,7 @@ private static boolean await(CountDownLatch latch) { try { - return latch.await(100, TimeUnit.MILLISECONDS); + return latch.await(10, TimeUnit.SECONDS); } catch (InterruptedException e) { return false; } @@ -320,7 +320,7 @@ private static boolean get(Future<?> future) { try { - future.get(100, TimeUnit.MILLISECONDS); + future.get(10, TimeUnit.SECONDS); return true; } catch (Exception e) { return false;
diff --git a/lib/BUILD b/lib/BUILD index 0a9c9b0..5c7efa2 100644 --- a/lib/BUILD +++ b/lib/BUILD
@@ -1,4 +1,5 @@ load("@rules_java//java:defs.bzl", "java_library") +load("@rules_shell//shell:sh_test.bzl", "sh_test") exports_files(glob([ "LICENSE-*", @@ -210,6 +211,13 @@ ) java_library( + name = "jspecify", + data = ["//lib:LICENSE-Apache2.0"], + visibility = ["//visibility:public"], + exports = ["@external_deps//:org_jspecify_jspecify"], +) + +java_library( name = "blame-cache", data = ["//lib:LICENSE-Apache2.0"], visibility = ["//visibility:public"],
diff --git a/lib/emojis/BUILD b/lib/emojis/BUILD index 2264434..d1f488a 100644 --- a/lib/emojis/BUILD +++ b/lib/emojis/BUILD
@@ -1,6 +1,6 @@ # build emojis.min.js from node modules -load("@npm//@bazel/rollup:index.bzl", "rollup_bundle") +load("@aspect_rules_rollup//rollup:defs.bzl", "rollup") package( default_visibility = ["//visibility:public"], @@ -9,7 +9,7 @@ exports_files(["emojis.js"]) -rollup_bundle( +rollup( name = "emojis.min", srcs = [ ":emojis.js", @@ -20,13 +20,13 @@ config_file = "rollup.config.js", entry_point = "index.js", format = "iife", - rollup_bin = "//tools/node_tools:rollup-bin", + node_modules = "//tools/node_tools:node_modules", silent = True, sourcemap = "hidden", deps = [ - "@tools_npm//@rollup/plugin-commonjs", - "@tools_npm//@rollup/plugin-node-resolve", - "@tools_npm//@rollup/plugin-terser", - "@tools_npm//rollup", + "//tools/node_tools:node_modules/@rollup/plugin-commonjs", + "//tools/node_tools:node_modules/@rollup/plugin-node-resolve", + "//tools/node_tools:node_modules/@rollup/plugin-terser", + "//tools/node_tools:node_modules/rollup", ], )
diff --git a/lib/emojis/rollup.config.js b/lib/emojis/rollup.config.js index 4e98b87..5d81ed8 100644 --- a/lib/emojis/rollup.config.js +++ b/lib/emojis/rollup.config.js
@@ -28,19 +28,19 @@ // so require(plugin_name) can't find a plugin. // To fix it, requirePlugin tries: // 1. resolve module id using default behavior, i.e. it starts from __dirname -// 2. if module not found - it tries to resolve module starting from rollupBin -// location. +// 2. if module not found - it tries to resolve module starting from +// tools/node_tools/node_modules // This workaround also gives us additional power - we can place .config.js // file anywhere in a source tree and add all plugins in the same package.json // file as rollup node module. function requirePlugin(id) { - const rollupBinDir = path.dirname(process.argv[1]); - const pluginPath = require.resolve(id, {paths: [__dirname, rollupBinDir] }); + const nodeToolsModulesDir = path.join(__dirname, '../../tools/node_tools/node_modules'); + const pluginPath = require.resolve(id, {paths: [__dirname, nodeToolsModulesDir] }); return require(pluginPath); } const cjs = requirePlugin('@rollup/plugin-commonjs'); -const {nodeResolve} = requirePlugin('@rollup/plugin-node-resolve'); +const nodeResolve = requirePlugin('@rollup/plugin-node-resolve'); const terser = requirePlugin('@rollup/plugin-terser'); export default {
diff --git a/lib/highlightjs/BUILD b/lib/highlightjs/BUILD index aa15509..1e018d8 100644 --- a/lib/highlightjs/BUILD +++ b/lib/highlightjs/BUILD
@@ -1,6 +1,6 @@ # build highlight.min.js from node modules -load("@npm//@bazel/rollup:index.bzl", "rollup_bundle") +load("@aspect_rules_rollup//rollup:defs.bzl", "rollup") package( default_visibility = ["//visibility:public"], @@ -13,16 +13,16 @@ # highlightjs-gn. exports_files(["gn.js"]) -rollup_bundle( +rollup( name = "highlight.min", srcs = [ ":gn.js", - "@ui_npm//highlight.js", - "@ui_npm//highlightjs-closure-templates", - "@ui_npm//highlightjs-epp", - "@ui_npm//highlightjs-structured-text", - "@ui_npm//highlightjs-ttcn3", - "@ui_npm//highlightjs-vue", + "//polygerrit-ui/app:node_modules/highlight.js", + "//polygerrit-ui/app:node_modules/highlightjs-closure-templates", + "//polygerrit-ui/app:node_modules/highlightjs-epp", + "//polygerrit-ui/app:node_modules/highlightjs-structured-text", + "//polygerrit-ui/app:node_modules/highlightjs-ttcn3", + "//polygerrit-ui/app:node_modules/highlightjs-vue", ], args = [ "--bundleConfigAsCjs=true", @@ -30,13 +30,13 @@ config_file = "rollup.config.js", entry_point = "index.js", format = "iife", - rollup_bin = "//tools/node_tools:rollup-bin", + node_modules = "//tools/node_tools:node_modules", silent = True, sourcemap = "hidden", deps = [ - "@tools_npm//@rollup/plugin-commonjs", - "@tools_npm//@rollup/plugin-node-resolve", - "@tools_npm//@rollup/plugin-terser", - "@tools_npm//rollup", + "//tools/node_tools:node_modules/@rollup/plugin-commonjs", + "//tools/node_tools:node_modules/@rollup/plugin-node-resolve", + "//tools/node_tools:node_modules/@rollup/plugin-terser", + "//tools/node_tools:node_modules/rollup", ], )
diff --git a/lib/highlightjs/index.js b/lib/highlightjs/index.js index d0152e6..fdc40b4 100644 --- a/lib/highlightjs/index.js +++ b/lib/highlightjs/index.js
@@ -15,12 +15,12 @@ * limitations under the License. */ -import hljs from 'highlight.js'; -import soy from 'highlightjs-closure-templates'; -import epp from 'highlightjs-epp'; -import iecst from 'highlightjs-structured-text'; -import ttcn3 from 'highlightjs-ttcn3'; -import vue from 'highlightjs-vue'; +import hljs from '../../polygerrit-ui/app/node_modules/highlight.js'; +import soy from '../../polygerrit-ui/app/node_modules/highlightjs-closure-templates'; +import epp from '../../polygerrit-ui/app/node_modules/highlightjs-epp'; +import iecst from '../../polygerrit-ui/app/node_modules/highlightjs-structured-text'; +import ttcn3 from '../../polygerrit-ui/app/node_modules/highlightjs-ttcn3'; +import vue from '../../polygerrit-ui/app/node_modules/highlightjs-vue'; import gn from './gn'; hljs.registerLanguage('soy', soy); @@ -30,4 +30,18 @@ hljs.registerLanguage('vue', vue); hljs.registerLanguage('gn', gn); +// Patch the Objective-C language definition to support C++14 digit separators. +// TODO(upstream): Remove this workaround once highlight.js is upgraded to a +// version that includes https://github.com/highlightjs/highlight.js/pull/4322 +const objc = hljs.getLanguage('objectivec'); +const cpp = hljs.getLanguage('cpp'); +if (objc && cpp) { + const cppNumberMode = cpp.contains.find(m => m.className === 'number' || m.scope === 'number'); + const objcContains = objc.contains; + const objcNumIdx = objcContains.findIndex(m => m.scope === 'number' || m.className === 'number'); + if (cppNumberMode && objcNumIdx !== -1) { + objcContains[objcNumIdx] = cppNumberMode; + } +} + export default hljs;
diff --git a/lib/highlightjs/rollup.config.js b/lib/highlightjs/rollup.config.js index 85ab910..a865093 100644 --- a/lib/highlightjs/rollup.config.js +++ b/lib/highlightjs/rollup.config.js
@@ -28,14 +28,14 @@ // so require(plugin_name) can't find a plugin. // To fix it, requirePlugin tries: // 1. resolve module id using default behavior, i.e. it starts from __dirname -// 2. if module not found - it tries to resolve module starting from rollupBin -// location. +// 2. if module not found - it tries to resolve module starting from +// tools/node_tools/node_modules // This workaround also gives us additional power - we can place .config.js // file anywhere in a source tree and add all plugins in the same package.json // file as rollup node module. function requirePlugin(id) { - const rollupBinDir = path.dirname(process.argv[1]); - const pluginPath = require.resolve(id, {paths: [__dirname, rollupBinDir] }); + const nodeToolsModulesDir = path.join(__dirname, '../../tools/node_tools/node_modules'); + const pluginPath = require.resolve(id, {paths: [__dirname, nodeToolsModulesDir] }); return require(pluginPath); }
diff --git a/lib/js/BUILD b/lib/js/BUILD index d00a91e..4163227 100644 --- a/lib/js/BUILD +++ b/lib/js/BUILD
@@ -1,4 +1,4 @@ -load("//tools/bzl:js.bzl", "js_component") +load("@com_googlesource_gerrit_bazlets//js:defs.bzl", "js_component") package(default_visibility = ["//visibility:public"])
diff --git a/modules/java-prettify b/modules/java-prettify index 32fa081..1c0ef60 160000 --- a/modules/java-prettify +++ b/modules/java-prettify
@@ -1 +1 @@ -Subproject commit 32fa081a797a97beaf77a4f2efca26c39168e72f +Subproject commit 1c0ef60424995a24452ce5ccd54f60c7eb9a5051
diff --git a/modules/jgit b/modules/jgit index 38da6c1..d29c264 160000 --- a/modules/jgit +++ b/modules/jgit
@@ -1 +1 @@ -Subproject commit 38da6c1f8f8bc2ec7a19f4115fcea7c94a5875fa +Subproject commit d29c264b2983782418b4c8357a6422750edfa15c
diff --git a/package.json b/package.json index f1e24c5..97acc45 100644 --- a/package.json +++ b/package.json
@@ -3,13 +3,11 @@ "version": "3.11.0", "description": "Gerrit Code Review", "dependencies": { - "@bazel/concatjs": "^5.8.1", - "@bazel/rollup": "^5.8.1", - "@bazel/terser": "^5.8.1", - "@bazel/typescript": "^5.8.1", "@typescript-eslint/parser": "^8.32.0" }, "devDependencies": { + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "^9.26.0", "@koa/cors": "^5.0.0", "@types/page": "^1.11.9", "@typescript-eslint/eslint-plugin": "^8.32.0",
diff --git a/plugins/BUILD b/plugins/BUILD index c48ddf6..7af6fd2 100644 --- a/plugins/BUILD +++ b/plugins/BUILD
@@ -1,5 +1,8 @@ +load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") +load("@aspect_rules_js//js:defs.bzl", "js_library") +load("@com_googlesource_gerrit_bazlets//tools:genrule2.bzl", "genrule2") +load("@plugins_npm//:defs.bzl", "npm_link_all_packages") load("@rules_java//java:defs.bzl", "java_binary", "java_library") -load("//tools/bzl:genrule2.bzl", "genrule2") load("//tools/bzl:javadoc.bzl", "java_doc") load( "//tools/bzl:plugins.bzl", @@ -7,14 +10,19 @@ "CUSTOM_PLUGINS", ) +npm_link_all_packages(name = "node_modules") + package(default_visibility = ["//visibility:public"]) -exports_files([ - "eslint.config.js", - ".prettierrc.js", - "rollup.config.js", - "tsconfig-plugins-base.json", -]) +js_library( + name = "plugins-config-lib", + srcs = [ + ".prettierrc.js", + "eslint-plugin.config.js", + "rollup.config.js", + "tsconfig-plugins-base.json", + ], +) genrule2( name = "core", @@ -53,6 +61,8 @@ "//java/com/google/gerrit/metrics", "//java/com/google/gerrit/metrics/dropwizard", "//java/com/google/gerrit/entities", + "//java/com/google/gerrit/entities/converter:converters", + "//java/com/google/gerrit/entities/converter:proto_converter", "//java/com/google/gerrit/server/api", "//java/com/google/gerrit/server/audit", "//java/com/google/gerrit/server/cache/mem", @@ -81,12 +91,12 @@ "//lib/guice:guice", "//lib/guice:guice-assistedinject", "//lib/guice:guice-servlet", - "//lib/guice:javax_inject", "//lib/httpcomponents:httpclient", "//lib/httpcomponents:httpcore", "//lib:jgit-servlet", "//lib:jgit", "//lib:jsr305", + "//lib:jspecify", "//lib/log:api", "//lib/log:log4j", "//lib/mina:sshd", @@ -171,3 +181,15 @@ pkgs = ["com.google.gerrit"], title = "Gerrit Review Plugin API Documentation", ) + +copy_to_bin( + name = "rollup.config", + srcs = ["rollup.config.js"], + visibility = ["//plugins:__subpackages__"], +) + +copy_to_bin( + name = "tsconfig-plugins-base", + srcs = ["tsconfig-plugins-base.json"], + visibility = ["//plugins:__subpackages__"], +)
diff --git a/plugins/codemirror-editor b/plugins/codemirror-editor index e93d258..0b27d4f 160000 --- a/plugins/codemirror-editor +++ b/plugins/codemirror-editor
@@ -1 +1 @@ -Subproject commit e93d258f0b9178732f51025b6a0cdbee597a78a4 +Subproject commit 0b27d4f966d89622b44d2c16b96379f37f08ff64
diff --git a/plugins/commit-message-length-validator b/plugins/commit-message-length-validator index c38e0a9..275c528 160000 --- a/plugins/commit-message-length-validator +++ b/plugins/commit-message-length-validator
@@ -1 +1 @@ -Subproject commit c38e0a9d36767092b20558b28eff7f546c6d754c +Subproject commit 275c528d58a12d8c90cd9755240be06844fdea07
diff --git a/plugins/delete-project b/plugins/delete-project index 7090927..3603583 160000 --- a/plugins/delete-project +++ b/plugins/delete-project
@@ -1 +1 @@ -Subproject commit 7090927136857b5abaa22604e538813989f2559d +Subproject commit 3603583484c45e578aeff66885723dfd171932fa
diff --git a/plugins/download-commands b/plugins/download-commands index 15f2608..9e1337d 160000 --- a/plugins/download-commands +++ b/plugins/download-commands
@@ -1 +1 @@ -Subproject commit 15f26084cc2d1b2055e5fa5f9b0d5583883896a9 +Subproject commit 9e1337d12deb4ae23dc9c8e4a4ab0660822cf139
diff --git a/plugins/eslint.config.js b/plugins/eslint-plugin.config.js similarity index 100% rename from plugins/eslint.config.js rename to plugins/eslint-plugin.config.js
diff --git a/plugins/external_plugin_deps.bzl b/plugins/external_plugin_deps.bzl deleted file mode 100644 index c498979..0000000 --- a/plugins/external_plugin_deps.bzl +++ /dev/null
@@ -1,5 +0,0 @@ -# Deprecation notice: This file is deprecated. Please migrate dependencies to -# MODULE.bazel. - -def external_plugin_deps(): - pass
diff --git a/plugins/gitiles b/plugins/gitiles index c423243..0e3ccb9 160000 --- a/plugins/gitiles +++ b/plugins/gitiles
@@ -1 +1 @@ -Subproject commit c42324332aa1357bd9dfdf2de14593a36ffbe3e0 +Subproject commit 0e3ccb926a23b972d9f4472e3ba7d874fac481c8
diff --git a/plugins/hooks b/plugins/hooks index 7d4f143..41b7a62 160000 --- a/plugins/hooks +++ b/plugins/hooks
@@ -1 +1 @@ -Subproject commit 7d4f143a988c8f5595000684c1f0dd4501546e48 +Subproject commit 41b7a626220c730fd623f751139a77bce80f04a4
diff --git a/plugins/package.json b/plugins/package.json index e7fdc0f..efd7a90 100644 --- a/plugins/package.json +++ b/plugins/package.json
@@ -31,11 +31,9 @@ "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.40.0", "@lezer/highlight": "^1.2.3", - "@gerritcodereview/typescript-api": "3.13.0", + "@gerritcodereview/typescript-api": "3.14.0", "@material/web": "^2.4.1", "@open-wc/testing": "^4.0.0", - "@polymer/decorators": "^3.0.0", - "@polymer/polymer": "3.5.1", "@web/dev-server-esbuild": "^1.0.4", "@web/test-runner": "^0.20.2", "lit": "^3.3.1", @@ -43,13 +41,6 @@ "rxjs": "^6.6.7", "sinon": "^20.0.0" }, - "dependencies // comments": { - "@polymer/polymer": [ - "There is a an issue with release 3.5.2. Tests are failing with:", - "NotSupportedError: Failed to execute 'define' on 'CustomElementRegistry':", - "the name 'dom-module' has already been used with this registry at ..." - ] - }, "license": "Apache-2.0", "private": true }
diff --git a/plugins/plugin-manager b/plugins/plugin-manager index cc03ee8..ef8a081 160000 --- a/plugins/plugin-manager +++ b/plugins/plugin-manager
@@ -1 +1 @@ -Subproject commit cc03ee89d361ff92af739ffb83056dfc89e00726 +Subproject commit ef8a081488380a93994f2300c93d201986c21fe3
diff --git a/plugins/pnpm-lock.yaml b/plugins/pnpm-lock.yaml new file mode 100644 index 0000000..a2aca79 --- /dev/null +++ b/plugins/pnpm-lock.yaml
@@ -0,0 +1,4444 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@codemirror/autocomplete': + specifier: ^6.20.1 + version: 6.20.1 + '@codemirror/commands': + specifier: ^6.10.3 + version: 6.10.3 + '@codemirror/lang-cpp': + specifier: ^6.0.3 + version: 6.0.3 + '@codemirror/lang-css': + specifier: ^6.3.1 + version: 6.3.1 + '@codemirror/lang-go': + specifier: ^6.0.1 + version: 6.0.1 + '@codemirror/lang-html': + specifier: ^6.4.11 + version: 6.4.11 + '@codemirror/lang-java': + specifier: ^6.0.2 + version: 6.0.2 + '@codemirror/lang-javascript': + specifier: ^6.2.5 + version: 6.2.5 + '@codemirror/lang-jinja': + specifier: ^6.0.0 + version: 6.0.0 + '@codemirror/lang-json': + specifier: ^6.0.2 + version: 6.0.2 + '@codemirror/lang-less': + specifier: ^6.0.2 + version: 6.0.2 + '@codemirror/lang-markdown': + specifier: ^6.5.0 + version: 6.5.0 + '@codemirror/lang-php': + specifier: ^6.0.2 + version: 6.0.2 + '@codemirror/lang-python': + specifier: ^6.2.1 + version: 6.2.1 + '@codemirror/lang-rust': + specifier: ^6.0.2 + version: 6.0.2 + '@codemirror/lang-sass': + specifier: ^6.0.2 + version: 6.0.2 + '@codemirror/lang-sql': + specifier: ^6.10.0 + version: 6.10.0 + '@codemirror/lang-vue': + specifier: ^0.1.3 + version: 0.1.3 + '@codemirror/lang-xml': + specifier: ^6.1.0 + version: 6.1.0 + '@codemirror/lang-yaml': + specifier: ^6.1.2 + version: 6.1.2 + '@codemirror/language': + specifier: ^6.12.2 + version: 6.12.2 + '@codemirror/language-data': + specifier: ^6.5.2 + version: 6.5.2 + '@codemirror/legacy-modes': + specifier: ^6.5.2 + version: 6.5.2 + '@codemirror/lint': + specifier: ^6.9.5 + version: 6.9.5 + '@codemirror/search': + specifier: ^6.6.0 + version: 6.6.0 + '@codemirror/state': + specifier: ^6.6.0 + version: 6.6.0 + '@codemirror/view': + specifier: ^6.40.0 + version: 6.40.0 + '@gerritcodereview/typescript-api': + specifier: 3.14.0 + version: 3.14.0 + '@lezer/highlight': + specifier: ^1.2.3 + version: 1.2.3 + '@material/web': + specifier: ^2.4.1 + version: 2.4.1 + '@open-wc/testing': + specifier: ^4.0.0 + version: 4.0.0 + '@web/dev-server-esbuild': + specifier: ^1.0.4 + version: 1.0.4 + '@web/test-runner': + specifier: ^0.20.2 + version: 0.20.2 + lit: + specifier: ^3.3.1 + version: 3.3.1 + resemblejs: + specifier: ^5.0.0 + version: 5.0.0 + rxjs: + specifier: ^6.6.7 + version: 6.6.7 + sinon: + specifier: ^20.0.0 + version: 20.0.0 + +packages: + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@codemirror/autocomplete@6.20.1': + resolution: {integrity: sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==} + + '@codemirror/commands@6.10.3': + resolution: {integrity: sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==} + + '@codemirror/lang-angular@0.1.4': + resolution: {integrity: sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g==} + + '@codemirror/lang-cpp@6.0.3': + resolution: {integrity: sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==} + + '@codemirror/lang-css@6.3.1': + resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==} + + '@codemirror/lang-go@6.0.1': + resolution: {integrity: sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==} + + '@codemirror/lang-html@6.4.11': + resolution: {integrity: sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==} + + '@codemirror/lang-java@6.0.2': + resolution: {integrity: sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==} + + '@codemirror/lang-javascript@6.2.5': + resolution: {integrity: sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==} + + '@codemirror/lang-jinja@6.0.0': + resolution: {integrity: sha512-47MFmRcR8UAxd8DReVgj7WJN1WSAMT7OJnewwugZM4XiHWkOjgJQqvEM1NpMj9ALMPyxmlziEI1opH9IaEvmaw==} + + '@codemirror/lang-json@6.0.2': + resolution: {integrity: sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==} + + '@codemirror/lang-less@6.0.2': + resolution: {integrity: sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==} + + '@codemirror/lang-liquid@6.3.2': + resolution: {integrity: sha512-6PDVU3ZnfeYyz1at1E/ttorErZvZFXXt1OPhtfe1EZJ2V2iDFa0CwPqPgG5F7NXN0yONGoBogKmFAafKTqlwIw==} + + '@codemirror/lang-markdown@6.5.0': + resolution: {integrity: sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==} + + '@codemirror/lang-php@6.0.2': + resolution: {integrity: sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==} + + '@codemirror/lang-python@6.2.1': + resolution: {integrity: sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==} + + '@codemirror/lang-rust@6.0.2': + resolution: {integrity: sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==} + + '@codemirror/lang-sass@6.0.2': + resolution: {integrity: sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==} + + '@codemirror/lang-sql@6.10.0': + resolution: {integrity: sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==} + + '@codemirror/lang-vue@0.1.3': + resolution: {integrity: sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==} + + '@codemirror/lang-wast@6.0.2': + resolution: {integrity: sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==} + + '@codemirror/lang-xml@6.1.0': + resolution: {integrity: sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==} + + '@codemirror/lang-yaml@6.1.2': + resolution: {integrity: sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==} + + '@codemirror/language-data@6.5.2': + resolution: {integrity: sha512-CPkWBKrNS8stYbEU5kwBwTf3JB1kghlbh4FSAwzGW2TEscdeHHH4FGysREW86Mqnj3Qn09s0/6Ea/TutmoTobg==} + + '@codemirror/language@6.12.2': + resolution: {integrity: sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==} + + '@codemirror/legacy-modes@6.5.2': + resolution: {integrity: sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==} + + '@codemirror/lint@6.9.5': + resolution: {integrity: sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==} + + '@codemirror/search@6.6.0': + resolution: {integrity: sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==} + + '@codemirror/state@6.6.0': + resolution: {integrity: sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==} + + '@codemirror/view@6.40.0': + resolution: {integrity: sha512-WA0zdU7xfF10+5I3HhUUq3kqOx3KjqmtQ9lqZjfK7jtYk4G72YW9rezcSywpaUMCWOMlq+6E0pO1IWg1TNIhtg==} + + '@esbuild/aix-ppc64@0.25.9': + resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.9': + resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.9': + resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.9': + resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.9': + resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.9': + resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.9': + resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.9': + resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.9': + resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.9': + resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.9': + resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.9': + resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.9': + resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.9': + resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.9': + resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.9': + resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.9': + resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.9': + resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.9': + resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.9': + resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.9': + resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.9': + resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.9': + resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.9': + resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.9': + resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.9': + resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esm-bundle/chai@4.3.4-fix.0': + resolution: {integrity: sha512-26SKdM4uvDWlY8/OOOxSB1AqQWeBosCX3wRYUZO7enTAj03CtVxIiCimYVG2WpULcyV51qapK4qTovwkUr5Mlw==} + + '@gerritcodereview/typescript-api@3.14.0': + resolution: {integrity: sha512-GhYzh6h/bHUz2cU1pzHwlfWDeGSkGAlBulfvrUyG4N3tuA/jqUp5LSBDeeJHzXgmOSdNE7Qyravjh3wDd0REyg==} + + '@hapi/bourne@3.0.0': + resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.30': + resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + + '@lezer/common@1.5.1': + resolution: {integrity: sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==} + + '@lezer/cpp@1.1.5': + resolution: {integrity: sha512-DIhSXmYtJKLehrjzDFN+2cPt547ySQ41nA8yqcDf/GxMc+YM736xqltFkvADL2M0VebU5I+3+4ks2Vv+Kyq3Aw==} + + '@lezer/css@1.3.1': + resolution: {integrity: sha512-PYAKeUVBo3HFThruRyp/iK91SwiZJnzXh8QzkQlwijB5y+N5iB28+iLk78o2zmKqqV0uolNhCwFqB8LA7b0Svg==} + + '@lezer/go@1.0.1': + resolution: {integrity: sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==} + + '@lezer/highlight@1.2.3': + resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==} + + '@lezer/html@1.3.13': + resolution: {integrity: sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==} + + '@lezer/java@1.1.3': + resolution: {integrity: sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==} + + '@lezer/javascript@1.5.4': + resolution: {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==} + + '@lezer/json@1.0.3': + resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} + + '@lezer/lr@1.4.8': + resolution: {integrity: sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==} + + '@lezer/markdown@1.6.3': + resolution: {integrity: sha512-jpGm5Ps+XErS+xA4urw7ogEGkeZOahVQF21Z6oECF0sj+2liwZopd2+I8uH5I/vZsRuuze3OxBREIANLf6KKUw==} + + '@lezer/php@1.0.5': + resolution: {integrity: sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==} + + '@lezer/python@1.1.18': + resolution: {integrity: sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==} + + '@lezer/rust@1.0.2': + resolution: {integrity: sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==} + + '@lezer/sass@1.1.0': + resolution: {integrity: sha512-3mMGdCTUZ/84ArHOuXWQr37pnf7f+Nw9ycPUeKX+wu19b7pSMcZGLbaXwvD2APMBDOGxPmpK/O6S1v1EvLoqgQ==} + + '@lezer/xml@1.0.6': + resolution: {integrity: sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==} + + '@lezer/yaml@1.0.4': + resolution: {integrity: sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==} + + '@lit-labs/ssr-dom-shim@1.4.0': + resolution: {integrity: sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==} + + '@lit/reactive-element@2.1.1': + resolution: {integrity: sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==} + + '@mapbox/node-pre-gyp@1.0.11': + resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} + hasBin: true + + '@marijn/find-cluster-break@1.0.2': + resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + + '@material/web@2.4.1': + resolution: {integrity: sha512-0sk9t25acJ72Qv3r0n9r0lgDbPaAKnpm0p+QmEAAwYyZomHxuVbgrrAdtNXaRm7jFyGh+WsTr8bhtvCnpPRFjw==} + + '@mdn/browser-compat-data@4.2.1': + resolution: {integrity: sha512-EWUguj2kd7ldmrF9F+vI5hUOralPd+sdsUnYbRy33vZTuZkduC1shE9TtEMEjAQwyfyMb4ole5KtjF8MsnQOlA==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@open-wc/dedupe-mixin@2.0.1': + resolution: {integrity: sha512-+R4VxvceUxHAUJXJQipkkoV9fy10vNo+OnUnGKZnVmcwxMl460KLzytnUM4S35SI073R0yZQp9ra0MbPUwVcEA==} + + '@open-wc/scoped-elements@3.0.6': + resolution: {integrity: sha512-w1ayJaUUmBw8tALtqQ6cBueld+op+bufujzbrOdH0uCTXnSQkONYZzOH+9jyQ8auVgKLqcxZ8oU6SzfqQhQkPg==} + + '@open-wc/semantic-dom-diff@0.20.1': + resolution: {integrity: sha512-mPF/RPT2TU7Dw41LEDdaeP6eyTOWBD4z0+AHP4/d0SbgcfJZVRymlIB6DQmtz0fd2CImIS9kszaMmwMt92HBPA==} + + '@open-wc/testing-helpers@3.0.1': + resolution: {integrity: sha512-hyNysSatbgT2FNxHJsS3rGKcLEo6+HwDFu1UQL6jcSQUabp/tj3PyX7UnXL3H5YGv0lJArdYLSnvjLnjn3O2fw==} + + '@open-wc/testing@4.0.0': + resolution: {integrity: sha512-KI70O0CJEpBWs3jrTju4BFCy7V/d4tFfYWkg8pMzncsDhD7TYNHLw5cy+s1FHXIgVFetnMDhPpwlKIPvtTQW7w==} + + '@puppeteer/browsers@2.10.7': + resolution: {integrity: sha512-wHWLkQWBjHtajZeqCB74nsa/X70KheyOhySYBRmVQDJiNj0zjZR/naPCvdWjMhcG1LmjaMV/9WtTo5mpe8qWLw==} + engines: {node: '>=18'} + hasBin: true + + '@rollup/plugin-node-resolve@15.3.1': + resolution: {integrity: sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@5.2.0': + resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.49.0': + resolution: {integrity: sha512-rlKIeL854Ed0e09QGYFlmDNbka6I3EQFw7iZuugQjMb11KMpJCLPFL4ZPbMfaEhLADEL1yx0oujGkBQ7+qW3eA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.49.0': + resolution: {integrity: sha512-cqPpZdKUSQYRtLLr6R4X3sD4jCBO1zUmeo3qrWBCqYIeH8Q3KRL4F3V7XJ2Rm8/RJOQBZuqzQGWPjjvFUcYa/w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.49.0': + resolution: {integrity: sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.49.0': + resolution: {integrity: sha512-y8cXoD3wdWUDpjOLMKLx6l+NFz3NlkWKcBCBfttUn+VGSfgsQ5o/yDUGtzE9HvsodkP0+16N0P4Ty1VuhtRUGg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.49.0': + resolution: {integrity: sha512-3mY5Pr7qv4GS4ZvWoSP8zha8YoiqrU+e0ViPvB549jvliBbdNLrg2ywPGkgLC3cmvN8ya3za+Q2xVyT6z+vZqA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.49.0': + resolution: {integrity: sha512-C9KzzOAQU5gU4kG8DTk+tjdKjpWhVWd5uVkinCwwFub2m7cDYLOdtXoMrExfeBmeRy9kBQMkiyJ+HULyF1yj9w==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.49.0': + resolution: {integrity: sha512-OVSQgEZDVLnTbMq5NBs6xkmz3AADByCWI4RdKSFNlDsYXdFtlxS59J+w+LippJe8KcmeSSM3ba+GlsM9+WwC1w==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.49.0': + resolution: {integrity: sha512-ZnfSFA7fDUHNa4P3VwAcfaBLakCbYaxCk0jUnS3dTou9P95kwoOLAMlT3WmEJDBCSrOEFFV0Y1HXiwfLYJuLlA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.49.0': + resolution: {integrity: sha512-Z81u+gfrobVK2iV7GqZCBfEB1y6+I61AH466lNK+xy1jfqFLiQ9Qv716WUM5fxFrYxwC7ziVdZRU9qvGHkYIJg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.49.0': + resolution: {integrity: sha512-zoAwS0KCXSnTp9NH/h9aamBAIve0DXeYpll85shf9NJ0URjSTzzS+Z9evmolN+ICfD3v8skKUPyk2PO0uGdFqg==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loongarch64-gnu@4.49.0': + resolution: {integrity: sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-gnu@4.49.0': + resolution: {integrity: sha512-k9aEmOWt+mrMuD3skjVJSSxHckJp+SiFzFG+v8JLXbc/xi9hv2icSkR3U7uQzqy+/QbbYY7iNB9eDTwrELo14g==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.49.0': + resolution: {integrity: sha512-rDKRFFIWJ/zJn6uk2IdYLc09Z7zkE5IFIOWqpuU0o6ZpHcdniAyWkwSUWE/Z25N/wNDmFHHMzin84qW7Wzkjsw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.49.0': + resolution: {integrity: sha512-FkkhIY/hYFVnOzz1WeV3S9Bd1h0hda/gRqvZCMpHWDHdiIHn6pqsY3b5eSbvGccWHMQ1uUzgZTKS4oGpykf8Tw==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.49.0': + resolution: {integrity: sha512-gRf5c+A7QiOG3UwLyOOtyJMD31JJhMjBvpfhAitPAoqZFcOeK3Kc1Veg1z/trmt+2P6F/biT02fU19GGTS529A==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.49.0': + resolution: {integrity: sha512-BR7+blScdLW1h/2hB/2oXM+dhTmpW3rQt1DeSiCP9mc2NMMkqVgjIN3DDsNpKmezffGC9R8XKVOLmBkRUcK/sA==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.49.0': + resolution: {integrity: sha512-hDMOAe+6nX3V5ei1I7Au3wcr9h3ktKzDvF2ne5ovX8RZiAHEtX1A5SNNk4zt1Qt77CmnbqT+upb/umzoPMWiPg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-win32-arm64-msvc@4.49.0': + resolution: {integrity: sha512-wkNRzfiIGaElC9kXUT+HLx17z7D0jl+9tGYRKwd8r7cUqTL7GYAvgUY++U2hK6Ar7z5Z6IRRoWC8kQxpmM7TDA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.49.0': + resolution: {integrity: sha512-gq5aW/SyNpjp71AAzroH37DtINDcX1Qw2iv9Chyz49ZgdOP3NV8QCyKZUrGsYX9Yyggj5soFiRCgsL3HwD8TdA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.49.0': + resolution: {integrity: sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==} + cpu: [x64] + os: [win32] + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@13.0.5': + resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==} + + '@sinonjs/samsam@8.0.3': + resolution: {integrity: sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==} + + '@tootallnate/quickjs-emscripten@0.23.0': + resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + + '@types/accepts@1.3.7': + resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==} + + '@types/babel__code-frame@7.0.6': + resolution: {integrity: sha512-Anitqkl3+KrzcW2k77lRlg/GfLZLWXBuNgbEcIOU6M92yw42vsd3xV/Z/yAHEj8m+KUjL6bWOVOFqX8PFPJ4LA==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/chai-dom@1.11.3': + resolution: {integrity: sha512-EUEZI7uID4ewzxnU7DJXtyvykhQuwe+etJ1wwOiJyQRTH/ifMWKX+ghiXkxCUvNJ6IQDodf0JXhuP6zZcy2qXQ==} + + '@types/chai@4.3.20': + resolution: {integrity: sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==} + + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + + '@types/co-body@6.1.3': + resolution: {integrity: sha512-UhuhrQ5hclX6UJctv5m4Rfp52AfG9o9+d9/HwjxhVB5NjXxr5t9oKgJxN8xRHgr35oo8meUEHUPFWiKg6y71aA==} + + '@types/command-line-args@5.2.3': + resolution: {integrity: sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/content-disposition@0.5.9': + resolution: {integrity: sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==} + + '@types/convert-source-map@2.0.3': + resolution: {integrity: sha512-ag0BfJLZf6CQz8VIuRIEYQ5Ggwk/82uvTQf27RcpyDNbY0Vw49LIPqAxk5tqYfrCs9xDaIMvl4aj7ZopnYL8bA==} + + '@types/cookies@0.9.1': + resolution: {integrity: sha512-E/DPgzifH4sM1UMadJMWd6mO2jOd4g1Ejwzx8/uRCDpJis1IrlyQEcGAYEomtAqRYmD5ORbNXMeI9U0RiVGZbg==} + + '@types/debounce@1.2.4': + resolution: {integrity: sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/express-serve-static-core@5.0.7': + resolution: {integrity: sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==} + + '@types/express@5.0.3': + resolution: {integrity: sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==} + + '@types/http-assert@1.5.6': + resolution: {integrity: sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/keygrip@1.0.6': + resolution: {integrity: sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==} + + '@types/koa-compose@3.2.8': + resolution: {integrity: sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==} + + '@types/koa@2.15.0': + resolution: {integrity: sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==} + + '@types/koa@3.0.0': + resolution: {integrity: sha512-MOcVYdVYmkSutVHZZPh8j3+dAjLyR5Tl59CN0eKgpkE1h/LBSmPAsQQuWs+bKu7WtGNn+hKfJH9Gzml+PulmDg==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/node@24.3.0': + resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==} + + '@types/parse5@6.0.3': + resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} + + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/resolve@1.20.2': + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + + '@types/send@0.17.5': + resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==} + + '@types/serve-static@1.15.8': + resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} + + '@types/sinon-chai@3.2.12': + resolution: {integrity: sha512-9y0Gflk3b0+NhQZ/oxGtaAJDvRywCa5sIyaVnounqLvmf93yBF4EgIRspePtkMs3Tr844nCclYMlcCNmLCvjuQ==} + + '@types/sinon@17.0.4': + resolution: {integrity: sha512-RHnIrhfPO3+tJT0s7cFaXGZvsL4bbR3/k7z3P312qMS4JaS2Tk+KiwiLx1S0rQ56ERj00u1/BtdyVd0FY+Pdew==} + + '@types/sinonjs__fake-timers@8.1.5': + resolution: {integrity: sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/ws@7.4.7': + resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} + + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + + '@web/browser-logs@0.4.1': + resolution: {integrity: sha512-ypmMG+72ERm+LvP+loj9A64MTXvWMXHUOu773cPO4L1SV/VWg6xA9Pv7vkvkXQX+ItJtCJt+KQ+U6ui2HhSFUw==} + engines: {node: '>=18.0.0'} + + '@web/config-loader@0.3.3': + resolution: {integrity: sha512-ilzeQzrPpPLWZhzFCV+4doxKDGm7oKVfdKpW9wiUNVgive34NSzCw+WzXTvjE4Jgr5CkyTDIObEmMrqQEjhT0g==} + engines: {node: '>=18.0.0'} + + '@web/dev-server-core@0.7.5': + resolution: {integrity: sha512-Da65zsiN6iZPMRuj4Oa6YPwvsmZmo5gtPWhW2lx3GTUf5CAEapjVpZVlUXnKPL7M7zRuk72jSsIl8lo+XpTCtw==} + engines: {node: '>=18.0.0'} + + '@web/dev-server-esbuild@1.0.4': + resolution: {integrity: sha512-ia1LxBwwRiQBYhJ7/RtLenHyPjzle3SvTw3jOZaeGv8UGXVPOkQV8fR05caOtW/DPPZaZovNAybzRKVnNiYIZg==} + engines: {node: '>=18.0.0'} + + '@web/dev-server-rollup@0.6.4': + resolution: {integrity: sha512-sJZfTGCCrdku5xYnQQG51odGI092hKY9YFM0X3Z0tRY3iXKXcYRaLZrErw5KfCxr6g0JRuhe4BBhqXTA5Q2I3Q==} + engines: {node: '>=18.0.0'} + + '@web/dev-server@0.4.6': + resolution: {integrity: sha512-jj/1bcElAy5EZet8m2CcUdzxT+CRvUjIXGh8Lt7vxtthkN9PzY9wlhWx/9WOs5iwlnG1oj0VGo6f/zvbPO0s9w==} + engines: {node: '>=18.0.0'} + hasBin: true + + '@web/parse5-utils@2.1.0': + resolution: {integrity: sha512-GzfK5disEJ6wEjoPwx8AVNwUe9gYIiwc+x//QYxYDAFKUp4Xb1OJAGLc2l2gVrSQmtPGLKrTRcW90Hv4pEq1qA==} + engines: {node: '>=18.0.0'} + + '@web/test-runner-chrome@0.18.1': + resolution: {integrity: sha512-eO6ctCaqSguGM6G3cFobGHnrEs9wlv9Juj/Akyr4XLjeEMTheNULdvOXw9Bygi+QC/ir/0snMmt+/YKnfy8rYA==} + engines: {node: '>=18.0.0'} + + '@web/test-runner-commands@0.9.0': + resolution: {integrity: sha512-zeLI6QdH0jzzJMDV5O42Pd8WLJtYqovgdt0JdytgHc0d1EpzXDsc7NTCJSImboc2NcayIsWAvvGGeRF69SMMYg==} + engines: {node: '>=18.0.0'} + + '@web/test-runner-core@0.13.4': + resolution: {integrity: sha512-84E1025aUSjvZU1j17eCTwV7m5Zg3cZHErV3+CaJM9JPCesZwLraIa0ONIQ9w4KLgcDgJFw9UnJ0LbFf42h6tg==} + engines: {node: '>=18.0.0'} + + '@web/test-runner-coverage-v8@0.8.0': + resolution: {integrity: sha512-PskiucYpjUtgNfR2zF2AWqWwjXL7H3WW/SnCAYmzUrtob7X9o/+BjdyZ4wKbOxWWSbJO4lEdGIDLu+8X2Xw+lA==} + engines: {node: '>=18.0.0'} + + '@web/test-runner-mocha@0.9.0': + resolution: {integrity: sha512-ZL9F6FXd0DBQvo/h/+mSfzFTSRVxzV9st/AHhpgABtUtV/AIpVE9to6+xdkpu6827kwjezdpuadPfg+PlrBWqQ==} + engines: {node: '>=18.0.0'} + + '@web/test-runner@0.20.2': + resolution: {integrity: sha512-zfEGYEDnS0EI8qgoWFjmtkIXhqP15W40NW3dCaKtbxj5eU0a7E53f3GV/tZGD0GlZKF8d4Fyw+AFrwOJU9Z4GA==} + engines: {node: '>=18.0.0'} + hasBin: true + + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + aproba@2.1.0: + resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==} + + are-we-there-yet@2.0.0: + resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + + array-back@3.1.0: + resolution: {integrity: sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==} + engines: {node: '>=6'} + + array-back@6.2.2: + resolution: {integrity: sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==} + engines: {node: '>=12.17'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + ast-types@0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + axe-core@4.10.3: + resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==} + engines: {node: '>=4'} + + b4a@1.6.7: + resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + bare-events@2.6.1: + resolution: {integrity: sha512-AuTJkq9XmE6Vk0FJVNq5QxETrSA/vKHarWVBG5l/JbdCL1prJemiyJqUS0jrlXO0MftuPq4m3YVYhoNc5+aE/g==} + + bare-fs@4.2.1: + resolution: {integrity: sha512-mELROzV0IhqilFgsl1gyp48pnZsaV9xhQapHLDsvn4d4ZTfbFhcghQezl7FTEDNBcGqLUnNI3lUlm6ecrLWdFA==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-os@3.6.2: + resolution: {integrity: sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==} + engines: {bare: '>=1.14.0'} + + bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + + bare-stream@2.7.0: + resolution: {integrity: sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==} + peerDependencies: + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + bare-events: + optional: true + + basic-ftp@5.0.5: + resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} + engines: {node: '>=10.0.0'} + deprecated: Security vulnerability fixed in 5.2.1, please upgrade + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cache-content-type@1.0.1: + resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} + engines: {node: '>= 6.0.0'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + canvas@2.11.2: + resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==} + engines: {node: '>=6'} + + chai-a11y-axe@1.5.0: + resolution: {integrity: sha512-V/Vg/zJDr9aIkaHJ2KQu7lGTQQm5ZOH4u1k5iTMvIXuSVlSuUo0jcSpSqf9wUn9zl6oQXa4e4E0cqH18KOgKlQ==} + + chalk-template@0.4.0: + resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==} + engines: {node: '>=12'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + chrome-launcher@0.15.2: + resolution: {integrity: sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==} + engines: {node: '>=12.13.0'} + hasBin: true + + chromium-bidi@8.0.0: + resolution: {integrity: sha512-d1VmE0FD7lxZQHzcDUCKZSNRtRwISXDsdg4HjdTR5+Ll5nQ/vzU12JeNmupD6VWffrPSlrnGhEWlLESKH3VO+g==} + peerDependencies: + devtools-protocol: '*' + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + + co-body@6.2.0: + resolution: {integrity: sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA==} + engines: {node: '>=8.0.0'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + + command-line-args@5.2.1: + resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==} + engines: {node: '>=4.0.0'} + + command-line-usage@7.0.3: + resolution: {integrity: sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==} + engines: {node: '>=12.20.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookies@0.9.1: + resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} + engines: {node: '>= 0.8'} + + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + data-uri-to-buffer@6.0.2: + resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} + engines: {node: '>= 14'} + + debounce@1.2.1: + resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decompress-response@4.2.1: + resolution: {integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==} + engines: {node: '>=8'} + + deep-equal@1.0.1: + resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + default-gateway@6.0.3: + resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==} + engines: {node: '>= 10'} + + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + + degenerator@5.0.1: + resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} + engines: {node: '>= 14'} + + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + + depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dependency-graph@0.11.0: + resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==} + engines: {node: '>= 0.6.0'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + devtools-protocol@0.0.1475386: + resolution: {integrity: sha512-RQ809ykTfJ+dgj9bftdeL2vRVxASAuGU+I9LEx9Ij5TXU5HrgAQVmzi72VA+mkzscE12uzlRv5/tWWv9R9J1SA==} + + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + + diff@7.0.0: + resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==} + engines: {node: '>=0.3.1'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + errorstacks@2.4.1: + resolution: {integrity: sha512-jE4i0SMYevwu/xxAuzhly/KTwtj0xDhbzB6m1xPImxTkw8wcCbgarOQPfCVMi5JKVyW7in29pNJCCJrry3Ynnw==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + esbuild@0.25.9: + resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-replace@3.0.0: + resolution: {integrity: sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==} + engines: {node: '>=4.0.0'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gauge@3.0.2: + resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-uri@6.0.5: + resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} + engines: {node: '>= 14'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-assert@1.5.0: + resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==} + engines: {node: '>= 0.8'} + + http-errors@1.6.3: + resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} + engines: {node: '>= 0.6'} + + http-errors@1.8.1: + resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} + engines: {node: '>= 0.6'} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + inflation@2.1.0: + resolution: {integrity: sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==} + engines: {node: '>= 0.8.0'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.3: + resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + internal-ip@6.2.0: + resolution: {integrity: sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg==} + engines: {node: '>=10'} + + ip-address@10.0.1: + resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} + engines: {node: '>= 12'} + + ip-regex@4.3.0: + resolution: {integrity: sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==} + engines: {node: '>=8'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-ip@3.1.0: + resolution: {integrity: sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==} + engines: {node: '>=8'} + + is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isbinaryfile@5.0.5: + resolution: {integrity: sha512-vh9MWXjhhblwrHlt/yutrubDuBD01kKFscyVndE2/VEeEU5aAizrzuWAsEaCjo997k+IluhN6C4jgxfS69SCIw==} + engines: {node: '>= 18.0.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + keygrip@1.1.0: + resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} + engines: {node: '>= 0.6'} + + koa-compose@4.1.0: + resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} + + koa-convert@2.0.0: + resolution: {integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==} + engines: {node: '>= 10'} + + koa-etag@4.0.0: + resolution: {integrity: sha512-1cSdezCkBWlyuB9l6c/IFoe1ANCDdPBxkDkRiaIup40xpUub6U/wwRXoKBZw/O5BifX9OlqAjYnDyzM6+l+TAg==} + + koa-send@5.0.1: + resolution: {integrity: sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==} + engines: {node: '>= 8'} + + koa-static@5.0.0: + resolution: {integrity: sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==} + engines: {node: '>= 7.6.0'} + + koa@2.16.2: + resolution: {integrity: sha512-+CCssgnrWKx9aI3OeZwroa/ckG4JICxvIFnSiOUyl2Uv+UTI+xIw0FfFrWS7cQFpoePpr9o8csss7KzsTzNL8Q==} + engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4} + + lighthouse-logger@1.4.2: + resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} + + lit-element@4.2.1: + resolution: {integrity: sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==} + + lit-html@3.3.1: + resolution: {integrity: sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA==} + + lit@3.3.1: + resolution: {integrity: sha512-Ksr/8L3PTapbdXJCk+EJVB78jDodUMaP54gD24W186zGRARvwrsPfS60wae/SSCTCNZVPd1chXqio1qHQmu4NA==} + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + log-update@4.0.0: + resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} + engines: {node: '>=10'} + + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + + lru-cache@8.0.5: + resolution: {integrity: sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==} + engines: {node: '>=16.14'} + + make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + marky@1.3.0: + resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-response@2.1.0: + resolution: {integrity: sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==} + engines: {node: '>=8'} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nan@2.26.2: + resolution: {integrity: sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw==} + + nanocolors@0.2.13: + resolution: {integrity: sha512-0n3mSAQLPpGLV9ORXT5+C/D4mwew7Ebws69Hx4E2sgz2ZA5+32Q80B9tL8PbL7XHnRDiAxH/pnrUJ9a4fkTNTA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + netmask@2.0.2: + resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} + engines: {node: '>= 0.4.0'} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + npmlog@5.0.1: + resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} + deprecated: This package is no longer supported. + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + only@0.0.2: + resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==} + + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + + p-event@4.2.0: + resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==} + engines: {node: '>=8'} + + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + + pac-proxy-agent@7.2.0: + resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} + engines: {node: '>= 14'} + + pac-resolver@7.0.1: + resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} + engines: {node: '>= 14'} + + parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + portfinder@1.0.37: + resolution: {integrity: sha512-yuGIEjDAYnnOex9ddMnKZEMFE0CcGo6zbfzDklkmT1m5z734ss6JMzN9rNB3+RR7iS+F10D4/BVIaXOyh8PQKw==} + engines: {node: '>= 10.12'} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + proxy-agent@6.5.0: + resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} + engines: {node: '>= 14'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + puppeteer-core@24.17.0: + resolution: {integrity: sha512-RYOBKFiF+3RdwIZTEacqNpD567gaFcBAOKTT7742FdB1icXudrPI7BlZbYTYWK2wgGQUXt9Zi1Yn+D5PmCs4CA==} + engines: {node: '>=18'} + + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resemblejs@5.0.0: + resolution: {integrity: sha512-+B0eP9k9VDP/YhBbH+ZdYmHiotdtuc6blVI+h8wwkY2cOow+uiIpSmgkBBBtrEAL0D31/gR/AJPwDeX5TcwmIA==} + + resolve-path@1.4.0: + resolution: {integrity: sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==} + engines: {node: '>= 0.8'} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup@4.49.0: + resolution: {integrity: sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@6.6.7: + resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} + engines: {npm: '>=2.0.0'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + setprototypeof@1.1.0: + resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@3.1.1: + resolution: {integrity: sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==} + + sinon@20.0.0: + resolution: {integrity: sha512-+FXOAbdnj94AQIxH0w1v8gzNxkawVvNqE3jUzRLptR71Oykeu2RrQXXl/VQjKay+Qnh73fDt/oDfMo6xMeDQbQ==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + streamx@2.22.1: + resolution: {integrity: sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + style-mod@4.1.3: + resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + table-layout@4.1.1: + resolution: {integrity: sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==} + engines: {node: '>=12.17'} + + tar-fs@3.1.0: + resolution: {integrity: sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==} + + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + text-decoder@1.2.3: + resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsscmp@1.0.6: + resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} + engines: {node: '>=0.6.x'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-detect@4.1.0: + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} + engines: {node: '>=4'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typed-query-selector@2.12.0: + resolution: {integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==} + + typical@4.0.0: + resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==} + engines: {node: '>=8'} + + typical@7.3.0: + resolution: {integrity: sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==} + engines: {node: '>=12.17'} + + ua-parser-js@1.0.41: + resolution: {integrity: sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==} + hasBin: true + + undici-types@7.10.0: + resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + + wordwrapjs@5.1.0: + resolution: {integrity: sha512-JNjcULU2e4KJwUNv6CHgI46UvDGitb6dGryHajXTDiLgg1/RiGoPSDw4kZfYnwGtEXf2ZMeIewDQgFGzkCB2Sg==} + engines: {node: '>=12.17'} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + ylru@1.4.0: + resolution: {integrity: sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==} + engines: {node: '>= 4.0.0'} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +snapshots: + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.27.1': {} + + '@codemirror/autocomplete@6.20.1': + dependencies: + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.40.0 + '@lezer/common': 1.5.1 + + '@codemirror/commands@6.10.3': + dependencies: + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.40.0 + '@lezer/common': 1.5.1 + + '@codemirror/lang-angular@0.1.4': + dependencies: + '@codemirror/lang-html': 6.4.11 + '@codemirror/lang-javascript': 6.2.5 + '@codemirror/language': 6.12.2 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@codemirror/lang-cpp@6.0.3': + dependencies: + '@codemirror/language': 6.12.2 + '@lezer/cpp': 1.1.5 + + '@codemirror/lang-css@6.3.1': + dependencies: + '@codemirror/autocomplete': 6.20.1 + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.6.0 + '@lezer/common': 1.5.1 + '@lezer/css': 1.3.1 + + '@codemirror/lang-go@6.0.1': + dependencies: + '@codemirror/autocomplete': 6.20.1 + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.6.0 + '@lezer/common': 1.5.1 + '@lezer/go': 1.0.1 + + '@codemirror/lang-html@6.4.11': + dependencies: + '@codemirror/autocomplete': 6.20.1 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-javascript': 6.2.5 + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.40.0 + '@lezer/common': 1.5.1 + '@lezer/css': 1.3.1 + '@lezer/html': 1.3.13 + + '@codemirror/lang-java@6.0.2': + dependencies: + '@codemirror/language': 6.12.2 + '@lezer/java': 1.1.3 + + '@codemirror/lang-javascript@6.2.5': + dependencies: + '@codemirror/autocomplete': 6.20.1 + '@codemirror/language': 6.12.2 + '@codemirror/lint': 6.9.5 + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.40.0 + '@lezer/common': 1.5.1 + '@lezer/javascript': 1.5.4 + + '@codemirror/lang-jinja@6.0.0': + dependencies: + '@codemirror/lang-html': 6.4.11 + '@codemirror/language': 6.12.2 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@codemirror/lang-json@6.0.2': + dependencies: + '@codemirror/language': 6.12.2 + '@lezer/json': 1.0.3 + + '@codemirror/lang-less@6.0.2': + dependencies: + '@codemirror/lang-css': 6.3.1 + '@codemirror/language': 6.12.2 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@codemirror/lang-liquid@6.3.2': + dependencies: + '@codemirror/autocomplete': 6.20.1 + '@codemirror/lang-html': 6.4.11 + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.40.0 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@codemirror/lang-markdown@6.5.0': + dependencies: + '@codemirror/autocomplete': 6.20.1 + '@codemirror/lang-html': 6.4.11 + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.40.0 + '@lezer/common': 1.5.1 + '@lezer/markdown': 1.6.3 + + '@codemirror/lang-php@6.0.2': + dependencies: + '@codemirror/lang-html': 6.4.11 + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.6.0 + '@lezer/common': 1.5.1 + '@lezer/php': 1.0.5 + + '@codemirror/lang-python@6.2.1': + dependencies: + '@codemirror/autocomplete': 6.20.1 + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.6.0 + '@lezer/common': 1.5.1 + '@lezer/python': 1.1.18 + + '@codemirror/lang-rust@6.0.2': + dependencies: + '@codemirror/language': 6.12.2 + '@lezer/rust': 1.0.2 + + '@codemirror/lang-sass@6.0.2': + dependencies: + '@codemirror/lang-css': 6.3.1 + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.6.0 + '@lezer/common': 1.5.1 + '@lezer/sass': 1.1.0 + + '@codemirror/lang-sql@6.10.0': + dependencies: + '@codemirror/autocomplete': 6.20.1 + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.6.0 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@codemirror/lang-vue@0.1.3': + dependencies: + '@codemirror/lang-html': 6.4.11 + '@codemirror/lang-javascript': 6.2.5 + '@codemirror/language': 6.12.2 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@codemirror/lang-wast@6.0.2': + dependencies: + '@codemirror/language': 6.12.2 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@codemirror/lang-xml@6.1.0': + dependencies: + '@codemirror/autocomplete': 6.20.1 + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.40.0 + '@lezer/common': 1.5.1 + '@lezer/xml': 1.0.6 + + '@codemirror/lang-yaml@6.1.2': + dependencies: + '@codemirror/autocomplete': 6.20.1 + '@codemirror/language': 6.12.2 + '@codemirror/state': 6.6.0 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + '@lezer/yaml': 1.0.4 + + '@codemirror/language-data@6.5.2': + dependencies: + '@codemirror/lang-angular': 0.1.4 + '@codemirror/lang-cpp': 6.0.3 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-go': 6.0.1 + '@codemirror/lang-html': 6.4.11 + '@codemirror/lang-java': 6.0.2 + '@codemirror/lang-javascript': 6.2.5 + '@codemirror/lang-jinja': 6.0.0 + '@codemirror/lang-json': 6.0.2 + '@codemirror/lang-less': 6.0.2 + '@codemirror/lang-liquid': 6.3.2 + '@codemirror/lang-markdown': 6.5.0 + '@codemirror/lang-php': 6.0.2 + '@codemirror/lang-python': 6.2.1 + '@codemirror/lang-rust': 6.0.2 + '@codemirror/lang-sass': 6.0.2 + '@codemirror/lang-sql': 6.10.0 + '@codemirror/lang-vue': 0.1.3 + '@codemirror/lang-wast': 6.0.2 + '@codemirror/lang-xml': 6.1.0 + '@codemirror/lang-yaml': 6.1.2 + '@codemirror/language': 6.12.2 + '@codemirror/legacy-modes': 6.5.2 + + '@codemirror/language@6.12.2': + dependencies: + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.40.0 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + style-mod: 4.1.3 + + '@codemirror/legacy-modes@6.5.2': + dependencies: + '@codemirror/language': 6.12.2 + + '@codemirror/lint@6.9.5': + dependencies: + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.40.0 + crelt: 1.0.6 + + '@codemirror/search@6.6.0': + dependencies: + '@codemirror/state': 6.6.0 + '@codemirror/view': 6.40.0 + crelt: 1.0.6 + + '@codemirror/state@6.6.0': + dependencies: + '@marijn/find-cluster-break': 1.0.2 + + '@codemirror/view@6.40.0': + dependencies: + '@codemirror/state': 6.6.0 + crelt: 1.0.6 + style-mod: 4.1.3 + w3c-keyname: 2.2.8 + + '@esbuild/aix-ppc64@0.25.9': + optional: true + + '@esbuild/android-arm64@0.25.9': + optional: true + + '@esbuild/android-arm@0.25.9': + optional: true + + '@esbuild/android-x64@0.25.9': + optional: true + + '@esbuild/darwin-arm64@0.25.9': + optional: true + + '@esbuild/darwin-x64@0.25.9': + optional: true + + '@esbuild/freebsd-arm64@0.25.9': + optional: true + + '@esbuild/freebsd-x64@0.25.9': + optional: true + + '@esbuild/linux-arm64@0.25.9': + optional: true + + '@esbuild/linux-arm@0.25.9': + optional: true + + '@esbuild/linux-ia32@0.25.9': + optional: true + + '@esbuild/linux-loong64@0.25.9': + optional: true + + '@esbuild/linux-mips64el@0.25.9': + optional: true + + '@esbuild/linux-ppc64@0.25.9': + optional: true + + '@esbuild/linux-riscv64@0.25.9': + optional: true + + '@esbuild/linux-s390x@0.25.9': + optional: true + + '@esbuild/linux-x64@0.25.9': + optional: true + + '@esbuild/netbsd-arm64@0.25.9': + optional: true + + '@esbuild/netbsd-x64@0.25.9': + optional: true + + '@esbuild/openbsd-arm64@0.25.9': + optional: true + + '@esbuild/openbsd-x64@0.25.9': + optional: true + + '@esbuild/openharmony-arm64@0.25.9': + optional: true + + '@esbuild/sunos-x64@0.25.9': + optional: true + + '@esbuild/win32-arm64@0.25.9': + optional: true + + '@esbuild/win32-ia32@0.25.9': + optional: true + + '@esbuild/win32-x64@0.25.9': + optional: true + + '@esm-bundle/chai@4.3.4-fix.0': + dependencies: + '@types/chai': 4.3.20 + + '@gerritcodereview/typescript-api@3.14.0': {} + + '@hapi/bourne@3.0.0': {} + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.30': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@lezer/common@1.5.1': {} + + '@lezer/cpp@1.1.5': + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@lezer/css@1.3.1': + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@lezer/go@1.0.1': + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@lezer/highlight@1.2.3': + dependencies: + '@lezer/common': 1.5.1 + + '@lezer/html@1.3.13': + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@lezer/java@1.1.3': + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@lezer/javascript@1.5.4': + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@lezer/json@1.0.3': + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@lezer/lr@1.4.8': + dependencies: + '@lezer/common': 1.5.1 + + '@lezer/markdown@1.6.3': + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + + '@lezer/php@1.0.5': + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@lezer/python@1.1.18': + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@lezer/rust@1.0.2': + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@lezer/sass@1.1.0': + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@lezer/xml@1.0.6': + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@lezer/yaml@1.0.4': + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@lit-labs/ssr-dom-shim@1.4.0': {} + + '@lit/reactive-element@2.1.1': + dependencies: + '@lit-labs/ssr-dom-shim': 1.4.0 + + '@mapbox/node-pre-gyp@1.0.11': + dependencies: + detect-libc: 2.1.2 + https-proxy-agent: 5.0.1 + make-dir: 3.1.0 + node-fetch: 2.7.0 + nopt: 5.0.0 + npmlog: 5.0.1 + rimraf: 3.0.2 + semver: 7.7.4 + tar: 6.2.1 + transitivePeerDependencies: + - encoding + - supports-color + optional: true + + '@marijn/find-cluster-break@1.0.2': {} + + '@material/web@2.4.1': + dependencies: + lit: 3.3.1 + tslib: 2.8.1 + + '@mdn/browser-compat-data@4.2.1': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@open-wc/dedupe-mixin@2.0.1': {} + + '@open-wc/scoped-elements@3.0.6': + dependencies: + '@open-wc/dedupe-mixin': 2.0.1 + lit: 3.3.1 + + '@open-wc/semantic-dom-diff@0.20.1': + dependencies: + '@types/chai': 4.3.20 + '@web/test-runner-commands': 0.9.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@open-wc/testing-helpers@3.0.1': + dependencies: + '@open-wc/scoped-elements': 3.0.6 + lit: 3.3.1 + lit-html: 3.3.1 + + '@open-wc/testing@4.0.0': + dependencies: + '@esm-bundle/chai': 4.3.4-fix.0 + '@open-wc/semantic-dom-diff': 0.20.1 + '@open-wc/testing-helpers': 3.0.1 + '@types/chai-dom': 1.11.3 + '@types/sinon-chai': 3.2.12 + chai-a11y-axe: 1.5.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@puppeteer/browsers@2.10.7': + dependencies: + debug: 4.4.1 + extract-zip: 2.0.1 + progress: 2.0.3 + proxy-agent: 6.5.0 + semver: 7.7.4 + tar-fs: 3.1.0 + yargs: 17.7.2 + transitivePeerDependencies: + - bare-buffer + - supports-color + + '@rollup/plugin-node-resolve@15.3.1(rollup@4.49.0)': + dependencies: + '@rollup/pluginutils': 5.2.0(rollup@4.49.0) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-module: 1.0.0 + resolve: 1.22.10 + optionalDependencies: + rollup: 4.49.0 + + '@rollup/pluginutils@5.2.0(rollup@4.49.0)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.49.0 + + '@rollup/rollup-android-arm-eabi@4.49.0': + optional: true + + '@rollup/rollup-android-arm64@4.49.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.49.0': + optional: true + + '@rollup/rollup-darwin-x64@4.49.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.49.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.49.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.49.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.49.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.49.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.49.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.49.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.49.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.49.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.49.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.49.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.49.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.49.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.49.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.49.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.49.0': + optional: true + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@13.0.5': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@sinonjs/samsam@8.0.3': + dependencies: + '@sinonjs/commons': 3.0.1 + type-detect: 4.1.0 + + '@tootallnate/quickjs-emscripten@0.23.0': {} + + '@types/accepts@1.3.7': + dependencies: + '@types/node': 24.3.0 + + '@types/babel__code-frame@7.0.6': {} + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 24.3.0 + + '@types/chai-dom@1.11.3': + dependencies: + '@types/chai': 5.2.2 + + '@types/chai@4.3.20': {} + + '@types/chai@5.2.2': + dependencies: + '@types/deep-eql': 4.0.2 + + '@types/co-body@6.1.3': + dependencies: + '@types/node': 24.3.0 + '@types/qs': 6.14.0 + + '@types/command-line-args@5.2.3': {} + + '@types/connect@3.4.38': + dependencies: + '@types/node': 24.3.0 + + '@types/content-disposition@0.5.9': {} + + '@types/convert-source-map@2.0.3': {} + + '@types/cookies@0.9.1': + dependencies: + '@types/connect': 3.4.38 + '@types/express': 5.0.3 + '@types/keygrip': 1.0.6 + '@types/node': 24.3.0 + + '@types/debounce@1.2.4': {} + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/express-serve-static-core@5.0.7': + dependencies: + '@types/node': 24.3.0 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.5 + + '@types/express@5.0.3': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.0.7 + '@types/serve-static': 1.15.8 + + '@types/http-assert@1.5.6': {} + + '@types/http-errors@2.0.5': {} + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/keygrip@1.0.6': {} + + '@types/koa-compose@3.2.8': + dependencies: + '@types/koa': 3.0.0 + + '@types/koa@2.15.0': + dependencies: + '@types/accepts': 1.3.7 + '@types/content-disposition': 0.5.9 + '@types/cookies': 0.9.1 + '@types/http-assert': 1.5.6 + '@types/http-errors': 2.0.5 + '@types/keygrip': 1.0.6 + '@types/koa-compose': 3.2.8 + '@types/node': 24.3.0 + + '@types/koa@3.0.0': + dependencies: + '@types/accepts': 1.3.7 + '@types/content-disposition': 0.5.9 + '@types/cookies': 0.9.1 + '@types/http-assert': 1.5.6 + '@types/http-errors': 2.0.5 + '@types/keygrip': 1.0.6 + '@types/koa-compose': 3.2.8 + '@types/node': 24.3.0 + + '@types/mime@1.3.5': {} + + '@types/node@24.3.0': + dependencies: + undici-types: 7.10.0 + + '@types/parse5@6.0.3': {} + + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + + '@types/resolve@1.20.2': {} + + '@types/send@0.17.5': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 24.3.0 + + '@types/serve-static@1.15.8': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 24.3.0 + '@types/send': 0.17.5 + + '@types/sinon-chai@3.2.12': + dependencies: + '@types/chai': 5.2.2 + '@types/sinon': 17.0.4 + + '@types/sinon@17.0.4': + dependencies: + '@types/sinonjs__fake-timers': 8.1.5 + + '@types/sinonjs__fake-timers@8.1.5': {} + + '@types/trusted-types@2.0.7': {} + + '@types/ws@7.4.7': + dependencies: + '@types/node': 24.3.0 + + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 24.3.0 + optional: true + + '@web/browser-logs@0.4.1': + dependencies: + errorstacks: 2.4.1 + + '@web/config-loader@0.3.3': {} + + '@web/dev-server-core@0.7.5': + dependencies: + '@types/koa': 2.15.0 + '@types/ws': 7.4.7 + '@web/parse5-utils': 2.1.0 + chokidar: 4.0.3 + clone: 2.1.2 + es-module-lexer: 1.7.0 + get-stream: 6.0.1 + is-stream: 2.0.1 + isbinaryfile: 5.0.5 + koa: 2.16.2 + koa-etag: 4.0.0 + koa-send: 5.0.1 + koa-static: 5.0.0 + lru-cache: 8.0.5 + mime-types: 2.1.35 + parse5: 6.0.1 + picomatch: 2.3.1 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/dev-server-esbuild@1.0.4': + dependencies: + '@mdn/browser-compat-data': 4.2.1 + '@web/dev-server-core': 0.7.5 + esbuild: 0.25.9 + parse5: 6.0.1 + ua-parser-js: 1.0.41 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/dev-server-rollup@0.6.4': + dependencies: + '@rollup/plugin-node-resolve': 15.3.1(rollup@4.49.0) + '@web/dev-server-core': 0.7.5 + nanocolors: 0.2.13 + parse5: 6.0.1 + rollup: 4.49.0 + whatwg-url: 14.2.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/dev-server@0.4.6': + dependencies: + '@babel/code-frame': 7.27.1 + '@types/command-line-args': 5.2.3 + '@web/config-loader': 0.3.3 + '@web/dev-server-core': 0.7.5 + '@web/dev-server-rollup': 0.6.4 + camelcase: 6.3.0 + command-line-args: 5.2.1 + command-line-usage: 7.0.3 + debounce: 1.2.1 + deepmerge: 4.3.1 + internal-ip: 6.2.0 + nanocolors: 0.2.13 + open: 8.4.2 + portfinder: 1.0.37 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/parse5-utils@2.1.0': + dependencies: + '@types/parse5': 6.0.3 + parse5: 6.0.1 + + '@web/test-runner-chrome@0.18.1': + dependencies: + '@web/test-runner-core': 0.13.4 + '@web/test-runner-coverage-v8': 0.8.0 + chrome-launcher: 0.15.2 + puppeteer-core: 24.17.0 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - utf-8-validate + + '@web/test-runner-commands@0.9.0': + dependencies: + '@web/test-runner-core': 0.13.4 + mkdirp: 1.0.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/test-runner-core@0.13.4': + dependencies: + '@babel/code-frame': 7.27.1 + '@types/babel__code-frame': 7.0.6 + '@types/co-body': 6.1.3 + '@types/convert-source-map': 2.0.3 + '@types/debounce': 1.2.4 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@web/browser-logs': 0.4.1 + '@web/dev-server-core': 0.7.5 + chokidar: 4.0.3 + cli-cursor: 3.1.0 + co-body: 6.2.0 + convert-source-map: 2.0.0 + debounce: 1.2.1 + dependency-graph: 0.11.0 + globby: 11.1.0 + internal-ip: 6.2.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + log-update: 4.0.0 + nanocolors: 0.2.13 + nanoid: 3.3.11 + open: 8.4.2 + picomatch: 2.3.1 + source-map: 0.7.6 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/test-runner-coverage-v8@0.8.0': + dependencies: + '@web/test-runner-core': 0.13.4 + istanbul-lib-coverage: 3.2.2 + lru-cache: 8.0.5 + picomatch: 2.3.1 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/test-runner-mocha@0.9.0': + dependencies: + '@web/test-runner-core': 0.13.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/test-runner@0.20.2': + dependencies: + '@web/browser-logs': 0.4.1 + '@web/config-loader': 0.3.3 + '@web/dev-server': 0.4.6 + '@web/test-runner-chrome': 0.18.1 + '@web/test-runner-commands': 0.9.0 + '@web/test-runner-core': 0.13.4 + '@web/test-runner-mocha': 0.9.0 + camelcase: 6.3.0 + command-line-args: 5.2.1 + command-line-usage: 7.0.3 + convert-source-map: 2.0.0 + diff: 5.2.0 + globby: 11.1.0 + nanocolors: 0.2.13 + portfinder: 1.0.37 + source-map: 0.7.6 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - utf-8-validate + + abbrev@1.1.1: + optional: true + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + agent-base@6.0.2: + dependencies: + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + optional: true + + agent-base@7.1.4: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + aproba@2.1.0: + optional: true + + are-we-there-yet@2.0.0: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + optional: true + + array-back@3.1.0: {} + + array-back@6.2.2: {} + + array-union@2.1.0: {} + + ast-types@0.13.4: + dependencies: + tslib: 2.8.1 + + astral-regex@2.0.0: {} + + async@3.2.6: {} + + axe-core@4.10.3: {} + + b4a@1.6.7: {} + + balanced-match@1.0.2: + optional: true + + bare-events@2.6.1: + optional: true + + bare-fs@4.2.1: + dependencies: + bare-events: 2.6.1 + bare-path: 3.0.0 + bare-stream: 2.7.0(bare-events@2.6.1) + optional: true + + bare-os@3.6.2: + optional: true + + bare-path@3.0.0: + dependencies: + bare-os: 3.6.2 + optional: true + + bare-stream@2.7.0(bare-events@2.6.1): + dependencies: + streamx: 2.22.1 + optionalDependencies: + bare-events: 2.6.1 + optional: true + + basic-ftp@5.0.5: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + optional: true + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + buffer-crc32@0.2.13: {} + + bytes@3.1.2: {} + + cache-content-type@1.0.1: + dependencies: + mime-types: 2.1.35 + ylru: 1.4.0 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + camelcase@6.3.0: {} + + canvas@2.11.2: + dependencies: + '@mapbox/node-pre-gyp': 1.0.11 + nan: 2.26.2 + simple-get: 3.1.1 + transitivePeerDependencies: + - encoding + - supports-color + optional: true + + chai-a11y-axe@1.5.0: + dependencies: + axe-core: 4.10.3 + + chalk-template@0.4.0: + dependencies: + chalk: 4.1.2 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + chownr@2.0.0: + optional: true + + chrome-launcher@0.15.2: + dependencies: + '@types/node': 24.3.0 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.4.2 + transitivePeerDependencies: + - supports-color + + chromium-bidi@8.0.0(devtools-protocol@0.0.1475386): + dependencies: + devtools-protocol: 0.0.1475386 + mitt: 3.0.1 + zod: 3.25.76 + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@2.1.2: {} + + co-body@6.2.0: + dependencies: + '@hapi/bourne': 3.0.0 + inflation: 2.1.0 + qs: 6.14.0 + raw-body: 2.5.2 + type-is: 1.6.18 + + co@4.6.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-support@1.1.3: + optional: true + + command-line-args@5.2.1: + dependencies: + array-back: 3.1.0 + find-replace: 3.0.0 + lodash.camelcase: 4.3.0 + typical: 4.0.0 + + command-line-usage@7.0.3: + dependencies: + array-back: 6.2.2 + chalk-template: 0.4.0 + table-layout: 4.1.1 + typical: 7.3.0 + + concat-map@0.0.1: + optional: true + + console-control-strings@1.1.0: + optional: true + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + convert-source-map@2.0.0: {} + + cookies@0.9.1: + dependencies: + depd: 2.0.0 + keygrip: 1.1.0 + + crelt@1.0.6: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + data-uri-to-buffer@6.0.2: {} + + debounce@1.2.1: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + decompress-response@4.2.1: + dependencies: + mimic-response: 2.1.0 + optional: true + + deep-equal@1.0.1: {} + + deepmerge@4.3.1: {} + + default-gateway@6.0.3: + dependencies: + execa: 5.1.1 + + define-lazy-prop@2.0.0: {} + + degenerator@5.0.1: + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + + delegates@1.0.0: {} + + depd@1.1.2: {} + + depd@2.0.0: {} + + dependency-graph@0.11.0: {} + + destroy@1.2.0: {} + + detect-libc@2.1.2: + optional: true + + devtools-protocol@0.0.1475386: {} + + diff@5.2.0: {} + + diff@7.0.0: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + emoji-regex@8.0.0: {} + + encodeurl@1.0.2: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + errorstacks@2.4.1: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + esbuild@0.25.9: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.9 + '@esbuild/android-arm': 0.25.9 + '@esbuild/android-arm64': 0.25.9 + '@esbuild/android-x64': 0.25.9 + '@esbuild/darwin-arm64': 0.25.9 + '@esbuild/darwin-x64': 0.25.9 + '@esbuild/freebsd-arm64': 0.25.9 + '@esbuild/freebsd-x64': 0.25.9 + '@esbuild/linux-arm': 0.25.9 + '@esbuild/linux-arm64': 0.25.9 + '@esbuild/linux-ia32': 0.25.9 + '@esbuild/linux-loong64': 0.25.9 + '@esbuild/linux-mips64el': 0.25.9 + '@esbuild/linux-ppc64': 0.25.9 + '@esbuild/linux-riscv64': 0.25.9 + '@esbuild/linux-s390x': 0.25.9 + '@esbuild/linux-x64': 0.25.9 + '@esbuild/netbsd-arm64': 0.25.9 + '@esbuild/netbsd-x64': 0.25.9 + '@esbuild/openbsd-arm64': 0.25.9 + '@esbuild/openbsd-x64': 0.25.9 + '@esbuild/openharmony-arm64': 0.25.9 + '@esbuild/sunos-x64': 0.25.9 + '@esbuild/win32-arm64': 0.25.9 + '@esbuild/win32-ia32': 0.25.9 + '@esbuild/win32-x64': 0.25.9 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@4.0.0: {} + + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + + esprima@4.0.1: {} + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + extract-zip@2.0.1: + dependencies: + debug: 4.4.1 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + + fast-fifo@1.3.2: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-replace@3.0.0: + dependencies: + array-back: 3.1.0 + + fresh@0.5.2: {} + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + optional: true + + fs.realpath@1.0.0: + optional: true + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gauge@3.0.2: + dependencies: + aproba: 2.1.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + object-assign: 4.1.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + optional: true + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@5.2.0: + dependencies: + pump: 3.0.3 + + get-stream@6.0.1: {} + + get-uri@6.0.5: + dependencies: + basic-ftp: 5.0.5 + data-uri-to-buffer: 6.0.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + optional: true + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.2.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + has-unicode@2.0.1: + optional: true + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + html-escaper@2.0.2: {} + + http-assert@1.5.0: + dependencies: + deep-equal: 1.0.1 + http-errors: 1.8.1 + + http-errors@1.6.3: + dependencies: + depd: 1.1.2 + inherits: 2.0.3 + setprototypeof: 1.1.0 + statuses: 1.5.0 + + http-errors@1.8.1: + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 1.5.0 + toidentifier: 1.0.1 + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + optional: true + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + human-signals@2.1.0: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + inflation@2.1.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + optional: true + + inherits@2.0.3: {} + + inherits@2.0.4: {} + + internal-ip@6.2.0: + dependencies: + default-gateway: 6.0.3 + ipaddr.js: 1.9.1 + is-ip: 3.1.0 + p-event: 4.2.0 + + ip-address@10.0.1: {} + + ip-regex@4.3.0: {} + + ipaddr.js@1.9.1: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-docker@2.2.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-ip@3.1.0: + dependencies: + ip-regex: 4.3.0 + + is-module@1.0.0: {} + + is-number@7.0.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-stream@2.0.1: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isbinaryfile@5.0.5: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + js-tokens@4.0.0: {} + + keygrip@1.1.0: + dependencies: + tsscmp: 1.0.6 + + koa-compose@4.1.0: {} + + koa-convert@2.0.0: + dependencies: + co: 4.6.0 + koa-compose: 4.1.0 + + koa-etag@4.0.0: + dependencies: + etag: 1.8.1 + + koa-send@5.0.1: + dependencies: + debug: 4.4.1 + http-errors: 1.8.1 + resolve-path: 1.4.0 + transitivePeerDependencies: + - supports-color + + koa-static@5.0.0: + dependencies: + debug: 3.2.7 + koa-send: 5.0.1 + transitivePeerDependencies: + - supports-color + + koa@2.16.2: + dependencies: + accepts: 1.3.8 + cache-content-type: 1.0.1 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookies: 0.9.1 + debug: 4.4.1 + delegates: 1.0.0 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + fresh: 0.5.2 + http-assert: 1.5.0 + http-errors: 1.8.1 + is-generator-function: 1.1.0 + koa-compose: 4.1.0 + koa-convert: 2.0.0 + on-finished: 2.4.1 + only: 0.0.2 + parseurl: 1.3.3 + statuses: 1.5.0 + type-is: 1.6.18 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + lighthouse-logger@1.4.2: + dependencies: + debug: 2.6.9 + marky: 1.3.0 + transitivePeerDependencies: + - supports-color + + lit-element@4.2.1: + dependencies: + '@lit-labs/ssr-dom-shim': 1.4.0 + '@lit/reactive-element': 2.1.1 + lit-html: 3.3.1 + + lit-html@3.3.1: + dependencies: + '@types/trusted-types': 2.0.7 + + lit@3.3.1: + dependencies: + '@lit/reactive-element': 2.1.1 + lit-element: 4.2.1 + lit-html: 3.3.1 + + lodash.camelcase@4.3.0: {} + + log-update@4.0.0: + dependencies: + ansi-escapes: 4.3.2 + cli-cursor: 3.1.0 + slice-ansi: 4.0.0 + wrap-ansi: 6.2.0 + + lru-cache@7.18.3: {} + + lru-cache@8.0.5: {} + + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + optional: true + + make-dir@4.0.0: + dependencies: + semver: 7.7.4 + + marky@1.3.0: {} + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@2.1.0: {} + + mimic-response@2.1.0: + optional: true + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.12 + optional: true + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + optional: true + + minipass@5.0.0: + optional: true + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + optional: true + + mitt@3.0.1: {} + + mkdirp@1.0.4: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + nan@2.26.2: + optional: true + + nanocolors@0.2.13: {} + + nanoid@3.3.11: {} + + negotiator@0.6.3: {} + + netmask@2.0.2: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + optional: true + + nopt@5.0.0: + dependencies: + abbrev: 1.1.1 + optional: true + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + npmlog@5.0.1: + dependencies: + are-we-there-yet: 2.0.0 + console-control-strings: 1.1.0 + gauge: 3.0.2 + set-blocking: 2.0.0 + optional: true + + object-assign@4.1.1: + optional: true + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + only@0.0.2: {} + + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + p-event@4.2.0: + dependencies: + p-timeout: 3.2.0 + + p-finally@1.0.0: {} + + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + + pac-proxy-agent@7.2.0: + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.4 + debug: 4.4.1 + get-uri: 6.0.5 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + pac-resolver@7.0.1: + dependencies: + degenerator: 5.0.1 + netmask: 2.0.2 + + parse5@6.0.1: {} + + parseurl@1.3.3: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-type@4.0.0: {} + + pend@1.2.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + portfinder@1.0.37: + dependencies: + async: 3.2.6 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + progress@2.0.3: {} + + proxy-agent@6.5.0: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 7.18.3 + pac-proxy-agent: 7.2.0 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + proxy-from-env@1.1.0: {} + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode@2.3.1: {} + + puppeteer-core@24.17.0: + dependencies: + '@puppeteer/browsers': 2.10.7 + chromium-bidi: 8.0.0(devtools-protocol@0.0.1475386) + debug: 4.4.1 + devtools-protocol: 0.0.1475386 + typed-query-selector: 2.12.0 + ws: 8.18.3 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - utf-8-validate + + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + + queue-microtask@1.2.3: {} + + raw-body@2.5.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + optional: true + + readdirp@4.1.2: {} + + require-directory@2.1.1: {} + + resemblejs@5.0.0: + optionalDependencies: + canvas: 2.11.2 + transitivePeerDependencies: + - encoding + - supports-color + + resolve-path@1.4.0: + dependencies: + http-errors: 1.6.3 + path-is-absolute: 1.0.1 + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + reusify@1.1.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + optional: true + + rollup@4.49.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.49.0 + '@rollup/rollup-android-arm64': 4.49.0 + '@rollup/rollup-darwin-arm64': 4.49.0 + '@rollup/rollup-darwin-x64': 4.49.0 + '@rollup/rollup-freebsd-arm64': 4.49.0 + '@rollup/rollup-freebsd-x64': 4.49.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.49.0 + '@rollup/rollup-linux-arm-musleabihf': 4.49.0 + '@rollup/rollup-linux-arm64-gnu': 4.49.0 + '@rollup/rollup-linux-arm64-musl': 4.49.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.49.0 + '@rollup/rollup-linux-ppc64-gnu': 4.49.0 + '@rollup/rollup-linux-riscv64-gnu': 4.49.0 + '@rollup/rollup-linux-riscv64-musl': 4.49.0 + '@rollup/rollup-linux-s390x-gnu': 4.49.0 + '@rollup/rollup-linux-x64-gnu': 4.49.0 + '@rollup/rollup-linux-x64-musl': 4.49.0 + '@rollup/rollup-win32-arm64-msvc': 4.49.0 + '@rollup/rollup-win32-ia32-msvc': 4.49.0 + '@rollup/rollup-win32-x64-msvc': 4.49.0 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@6.6.7: + dependencies: + tslib: 1.14.1 + + safe-buffer@5.2.1: {} + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + + semver@6.3.1: + optional: true + + semver@7.7.4: {} + + set-blocking@2.0.0: + optional: true + + setprototypeof@1.1.0: {} + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + simple-concat@1.0.1: + optional: true + + simple-get@3.1.1: + dependencies: + decompress-response: 4.2.1 + once: 1.4.0 + simple-concat: 1.0.1 + optional: true + + sinon@20.0.0: + dependencies: + '@sinonjs/commons': 3.0.1 + '@sinonjs/fake-timers': 13.0.5 + '@sinonjs/samsam': 8.0.3 + diff: 7.0.0 + supports-color: 7.2.0 + + slash@3.0.0: {} + + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + smart-buffer@4.2.0: {} + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + socks: 2.8.7 + transitivePeerDependencies: + - supports-color + + socks@2.8.7: + dependencies: + ip-address: 10.0.1 + smart-buffer: 4.2.0 + + source-map@0.6.1: + optional: true + + source-map@0.7.6: {} + + statuses@1.5.0: {} + + statuses@2.0.1: {} + + streamx@2.22.1: + dependencies: + fast-fifo: 1.3.2 + text-decoder: 1.2.3 + optionalDependencies: + bare-events: 2.6.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + optional: true + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-final-newline@2.0.0: {} + + style-mod@4.1.3: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + table-layout@4.1.1: + dependencies: + array-back: 6.2.2 + wordwrapjs: 5.1.0 + + tar-fs@3.1.0: + dependencies: + pump: 3.0.3 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 4.2.1 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-buffer + + tar-stream@3.1.7: + dependencies: + b4a: 1.6.7 + fast-fifo: 1.3.2 + streamx: 2.22.1 + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + optional: true + + text-decoder@1.2.3: + dependencies: + b4a: 1.6.7 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + tr46@0.0.3: + optional: true + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + + tslib@1.14.1: {} + + tslib@2.8.1: {} + + tsscmp@1.0.6: {} + + type-detect@4.0.8: {} + + type-detect@4.1.0: {} + + type-fest@0.21.3: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typed-query-selector@2.12.0: {} + + typical@4.0.0: {} + + typical@7.3.0: {} + + ua-parser-js@1.0.41: {} + + undici-types@7.10.0: {} + + unpipe@1.0.0: {} + + util-deprecate@1.0.2: + optional: true + + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.30 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + vary@1.1.2: {} + + w3c-keyname@2.2.8: {} + + webidl-conversions@3.0.1: + optional: true + + webidl-conversions@7.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + optional: true + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + optional: true + + wordwrapjs@5.1.0: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + ws@7.5.10: {} + + ws@8.18.3: {} + + y18n@5.0.8: {} + + yallist@4.0.0: + optional: true + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + ylru@1.4.0: {} + + zod@3.25.76: {}
diff --git a/plugins/pnpm-workspace.yaml b/plugins/pnpm-workspace.yaml new file mode 100644 index 0000000..7d40629 --- /dev/null +++ b/plugins/pnpm-workspace.yaml
@@ -0,0 +1 @@ +onlyBuiltDependencies: []
diff --git a/plugins/replication b/plugins/replication index 3e623c7..681d9ab 160000 --- a/plugins/replication +++ b/plugins/replication
@@ -1 +1 @@ -Subproject commit 3e623c75033c815b97b5aa9b112dcc75c17dc018 +Subproject commit 681d9ab03db4d2ac3b7bfc81f64e490ddaceebfb
diff --git a/plugins/reviewnotes b/plugins/reviewnotes index d94078c..df42ff9 160000 --- a/plugins/reviewnotes +++ b/plugins/reviewnotes
@@ -1 +1 @@ -Subproject commit d94078c4734085cff50e3b886d9cd1aa052a7ae4 +Subproject commit df42ff9f17c98e99a534c4cfaa21f66baf19f7cf
diff --git a/plugins/rollup.config.js b/plugins/rollup.config.js index 4ab9907..ae42721 100644 --- a/plugins/rollup.config.js +++ b/plugins/rollup.config.js
@@ -27,18 +27,19 @@ // so require(plugin_name) can't find a plugin. // To fix it, requirePlugin tries: // 1. resolve module id using default behavior, i.e. it starts from __dirname -// 2. if module not found - it tries to resolve module starting from rollupBin -// location. +// 2. if module not found - it tries to resolve module starting from +// tools/node_tools/node_modules // This workaround also gives us additional power - we can place .config.js // file anywhere in a source tree and add all plugins in the same package.json // file as rollup node module. function requirePlugin(id) { - const rollupBinDir = path.dirname(process.argv[1]); - const pluginPath = require.resolve(id, {paths: [__dirname, rollupBinDir] }); + const nodeToolsModulesDir = path.join(__dirname, '../tools/node_tools/node_modules'); + const pluginPath = require.resolve(id, {paths: [__dirname, nodeToolsModulesDir] }); return require(pluginPath); } const {nodeResolve} = requirePlugin('@rollup/plugin-node-resolve'); +const terser = requirePlugin('@rollup/plugin-terser'); export default { treeshake: false, @@ -51,7 +52,14 @@ }, // Context must be set to window to correctly process global variables context: 'window', - plugins: [nodeResolve({ - modulePaths: [path.join(process.cwd(), 'external/plugins_npm/node_modules')] - })], + plugins: [ + nodeResolve({ + modulePaths: [path.join(process.cwd(), 'external/plugins_npm/node_modules')] + }), + terser({ + format: { + comments: false, + }, + }), + ], };
diff --git a/plugins/singleusergroup b/plugins/singleusergroup index 4bee62c..a2575da 160000 --- a/plugins/singleusergroup +++ b/plugins/singleusergroup
@@ -1 +1 @@ -Subproject commit 4bee62cbbc21979b841843dd5faaf79470a35966 +Subproject commit a2575da832e41387947554d415de9f6de97c841d
diff --git a/plugins/tsconfig-plugins-base.json b/plugins/tsconfig-plugins-base.json index 605c69a..b41f410 100644 --- a/plugins/tsconfig-plugins-base.json +++ b/plugins/tsconfig-plugins-base.json
@@ -26,9 +26,9 @@ "skipLibCheck": true, /* Do not check node_modules */ /* Module Resolution Options */ - "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + "moduleResolution": "bundler", "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ - "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + "preserveSymlinks": false, /* Do not resolve the real path of symlinks. */ /* Advanced Options */ "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
diff --git a/plugins/webhooks b/plugins/webhooks index 13f28d1..8e9a401 160000 --- a/plugins/webhooks +++ b/plugins/webhooks
@@ -1 +1 @@ -Subproject commit 13f28d1927673b48e75b2eacd248f5d303bbb82f +Subproject commit 8e9a4015f1f3f1d7326d3ce015f9e8424ec8121f
diff --git a/plugins/yarn.lock b/plugins/yarn.lock index 4c5b62b..551be7b 100644 --- a/plugins/yarn.lock +++ b/plugins/yarn.lock
@@ -771,27 +771,6 @@ "@types/sinon-chai" "^3.2.3" chai-a11y-axe "^1.5.0" -"@polymer/decorators@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@polymer/decorators/-/decorators-3.0.0.tgz#e4212ac976d9abd1210f560b6e1be4165c1c0183" - integrity sha512-qh+VID9nDV9q3ABvIfWgm7/+udl7v2HKsMLPXFm8tj1fI7qr7yWJMFwS3xWBkMmuNPtmkS8MDP0vqLAQIEOWzg== - dependencies: - "@polymer/polymer" "^3.0.5" - -"@polymer/polymer@3.5.1": - version "3.5.1" - resolved "https://registry.yarnpkg.com/@polymer/polymer/-/polymer-3.5.1.tgz#4b5234e43b8876441022bcb91313ab3c4a29f0c8" - integrity sha512-JlAHuy+1qIC6hL1ojEUfIVD58fzTpJAoCxFwV5yr0mYTXV1H8bz5zy0+rC963Cgr9iNXQ4T9ncSjC2fkF9BQfw== - dependencies: - "@webcomponents/shadycss" "^1.9.1" - -"@polymer/polymer@^3.0.5": - version "3.5.2" - resolved "https://registry.yarnpkg.com/@polymer/polymer/-/polymer-3.5.2.tgz#af0e7e13976df53ace6728e841121c36aca351de" - integrity sha512-fWwImY/UH4bb2534DVSaX+Azs2yKg8slkMBHOyGeU2kKx7Xmxp6Lee0jP8p6B3d7c1gFUPB2Z976dTUtX81pQA== - dependencies: - "@webcomponents/shadycss" "^1.9.1" - "@puppeteer/browsers@2.10.7": version "2.10.7" resolved "https://registry.yarnpkg.com/@puppeteer/browsers/-/browsers-2.10.7.tgz#a28f622b2da0ee131c9a576d25d8c4e31fc24135" @@ -1399,11 +1378,6 @@ portfinder "^1.0.32" source-map "^0.7.3" -"@webcomponents/shadycss@^1.9.1": - version "1.11.2" - resolved "https://registry.yarnpkg.com/@webcomponents/shadycss/-/shadycss-1.11.2.tgz#7539b0ad29598aa2eafee8b341059e20ac9e1006" - integrity sha512-vRq+GniJAYSBmTRnhCYPAPq6THYqovJ/gzGThWbgEZUQaBccndGTi1hdiUP15HzEco0I6t4RCtXyX0rsSmwgPw== - abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..aeae959 --- /dev/null +++ b/pnpm-lock.yaml
@@ -0,0 +1,5381 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +overrides: + eslint: ^9.39.1 + '@typescript-eslint/eslint-plugin': ^8.49.0 + '@typescript-eslint/parser': ^8.49.0 + typescript: 5.8.2 + vscode-css-languageservice: ^6.3.9 + vscode-html-languageservice: ^5.6.1 + +importers: + + .: + dependencies: + '@typescript-eslint/parser': + specifier: ^8.49.0 + version: 8.49.0(eslint@9.39.1)(typescript@5.8.2) + devDependencies: + '@eslint/eslintrc': + specifier: ^3.3.1 + version: 3.3.3 + '@eslint/js': + specifier: ^9.26.0 + version: 9.39.1 + '@koa/cors': + specifier: ^5.0.0 + version: 5.0.0 + '@types/page': + specifier: ^1.11.9 + version: 1.11.9 + '@typescript-eslint/eslint-plugin': + specifier: ^8.49.0 + version: 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.8.2))(eslint@9.39.1)(typescript@5.8.2) + '@web/dev-server': + specifier: ^0.4.6 + version: 0.4.6 + '@web/dev-server-esbuild': + specifier: ^1.0.4 + version: 1.0.4 + eslint: + specifier: ^9.39.1 + version: 9.39.1 + eslint-config-google: + specifier: ^0.14.0 + version: 0.14.0(eslint@9.39.1) + eslint-plugin-es-x: + specifier: 8.0.0 + version: 8.0.0(eslint@9.39.1) + eslint-plugin-html: + specifier: ^8.1.3 + version: 8.1.3 + eslint-plugin-import: + specifier: ^2.31.0 + version: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.8.2))(eslint@9.39.1) + eslint-plugin-jsdoc: + specifier: ^50.6.11 + version: 50.8.0(eslint@9.39.1) + eslint-plugin-lit: + specifier: ^1.15.0 + version: 1.15.0(eslint@9.39.1) + eslint-plugin-n: + specifier: ^17.17.0 + version: 17.23.1(eslint@9.39.1)(typescript@5.8.2) + eslint-plugin-prettier: + specifier: ^5.4.0 + version: 5.5.4(eslint-config-prettier@9.1.0(eslint@9.39.1))(eslint@9.39.1)(prettier@2.8.8) + eslint-plugin-regex: + specifier: ^1.10.0 + version: 1.10.0(eslint@9.39.1) + gts: + specifier: ^6.0.2 + version: 6.0.2(typescript@5.8.2) + lit-analyzer: + specifier: ^2.0.3 + version: 2.0.3 + npm-run-all: + specifier: ^4.1.5 + version: 4.1.5 + prettier: + specifier: ^2.8.8 + version: 2.8.8 + rollup: + specifier: ^4.49.0 + version: 4.53.3 + terser: + specifier: ~5.39.0 + version: 5.39.2 + ts-lit-plugin: + specifier: ^2.0.2 + version: 2.0.2 + typescript: + specifier: 5.8.2 + version: 5.8.2 + +packages: + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/runtime@7.28.3': + resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==} + engines: {node: '>=6.9.0'} + + '@es-joy/jsdoccomment@0.50.2': + resolution: {integrity: sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==} + engines: {node: '>=18'} + + '@esbuild/aix-ppc64@0.25.9': + resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.9': + resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.9': + resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.9': + resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.9': + resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.9': + resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.9': + resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.9': + resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.9': + resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.9': + resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.9': + resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.9': + resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.9': + resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.9': + resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.9': + resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.9': + resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.9': + resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.9': + resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.9': + resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.9': + resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.9': + resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.9': + resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.9': + resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.9': + resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.9': + resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.9': + resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^9.39.1 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.3': + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.1': + resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.30': + resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + + '@koa/cors@5.0.0': + resolution: {integrity: sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==} + engines: {node: '>= 14.0.0'} + + '@mdn/browser-compat-data@4.2.1': + resolution: {integrity: sha512-EWUguj2kd7ldmrF9F+vI5hUOralPd+sdsUnYbRy33vZTuZkduC1shE9TtEMEjAQwyfyMb4ole5KtjF8MsnQOlA==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgr/core@0.1.2': + resolution: {integrity: sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@rollup/plugin-node-resolve@15.3.1': + resolution: {integrity: sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@5.2.0': + resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.53.3': + resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.53.3': + resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.53.3': + resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.53.3': + resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.53.3': + resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.53.3': + resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.53.3': + resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.53.3': + resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.53.3': + resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openharmony-arm64@4.53.3': + resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.53.3': + resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.53.3': + resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} + cpu: [x64] + os: [win32] + + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + + '@types/accepts@1.3.7': + resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/command-line-args@5.2.3': + resolution: {integrity: sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/content-disposition@0.5.9': + resolution: {integrity: sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==} + + '@types/cookies@0.9.1': + resolution: {integrity: sha512-E/DPgzifH4sM1UMadJMWd6mO2jOd4g1Ejwzx8/uRCDpJis1IrlyQEcGAYEomtAqRYmD5ORbNXMeI9U0RiVGZbg==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/express-serve-static-core@5.0.7': + resolution: {integrity: sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==} + + '@types/express@5.0.3': + resolution: {integrity: sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==} + + '@types/http-assert@1.5.6': + resolution: {integrity: sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/keygrip@1.0.6': + resolution: {integrity: sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==} + + '@types/koa-compose@3.2.8': + resolution: {integrity: sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==} + + '@types/koa@2.15.0': + resolution: {integrity: sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==} + + '@types/koa@3.0.0': + resolution: {integrity: sha512-MOcVYdVYmkSutVHZZPh8j3+dAjLyR5Tl59CN0eKgpkE1h/LBSmPAsQQuWs+bKu7WtGNn+hKfJH9Gzml+PulmDg==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/minimist@1.2.5': + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + + '@types/node@24.3.0': + resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==} + + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + + '@types/page@1.11.9': + resolution: {integrity: sha512-Ki8IZMwg63i7+tF3UpfDIl4rwBN1B1kWQjZCUzaWoohfMB0m9CYap/dExbz7W21uS2WPoA/8lvlDuwX0X/YfIQ==} + + '@types/parse5@6.0.3': + resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} + + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/resolve@1.20.2': + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + + '@types/send@0.17.5': + resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==} + + '@types/serve-static@1.15.8': + resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} + + '@types/ws@7.4.7': + resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} + + '@typescript-eslint/eslint-plugin@8.49.0': + resolution: {integrity: sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.49.0 + eslint: ^9.39.1 + typescript: 5.8.2 + + '@typescript-eslint/parser@8.49.0': + resolution: {integrity: sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^9.39.1 + typescript: 5.8.2 + + '@typescript-eslint/project-service@8.49.0': + resolution: {integrity: sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: 5.8.2 + + '@typescript-eslint/scope-manager@8.49.0': + resolution: {integrity: sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.49.0': + resolution: {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: 5.8.2 + + '@typescript-eslint/type-utils@8.49.0': + resolution: {integrity: sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^9.39.1 + typescript: 5.8.2 + + '@typescript-eslint/types@8.49.0': + resolution: {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.49.0': + resolution: {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: 5.8.2 + + '@typescript-eslint/utils@8.49.0': + resolution: {integrity: sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^9.39.1 + typescript: 5.8.2 + + '@typescript-eslint/visitor-keys@8.49.0': + resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vscode/l10n@0.0.18': + resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} + + '@vscode/web-custom-data@0.4.13': + resolution: {integrity: sha512-2ZUIRfhofZ/npLlf872EBnPmn27Kt4M2UssmQIfnJvgGgMYZJ5fvtHEDnttBBf2hnVtBgNCqZMVHJA+wsFVqTA==} + + '@web/config-loader@0.3.3': + resolution: {integrity: sha512-ilzeQzrPpPLWZhzFCV+4doxKDGm7oKVfdKpW9wiUNVgive34NSzCw+WzXTvjE4Jgr5CkyTDIObEmMrqQEjhT0g==} + engines: {node: '>=18.0.0'} + + '@web/dev-server-core@0.7.5': + resolution: {integrity: sha512-Da65zsiN6iZPMRuj4Oa6YPwvsmZmo5gtPWhW2lx3GTUf5CAEapjVpZVlUXnKPL7M7zRuk72jSsIl8lo+XpTCtw==} + engines: {node: '>=18.0.0'} + + '@web/dev-server-esbuild@1.0.4': + resolution: {integrity: sha512-ia1LxBwwRiQBYhJ7/RtLenHyPjzle3SvTw3jOZaeGv8UGXVPOkQV8fR05caOtW/DPPZaZovNAybzRKVnNiYIZg==} + engines: {node: '>=18.0.0'} + + '@web/dev-server-rollup@0.6.4': + resolution: {integrity: sha512-sJZfTGCCrdku5xYnQQG51odGI092hKY9YFM0X3Z0tRY3iXKXcYRaLZrErw5KfCxr6g0JRuhe4BBhqXTA5Q2I3Q==} + engines: {node: '>=18.0.0'} + + '@web/dev-server@0.4.6': + resolution: {integrity: sha512-jj/1bcElAy5EZet8m2CcUdzxT+CRvUjIXGh8Lt7vxtthkN9PzY9wlhWx/9WOs5iwlnG1oj0VGo6f/zvbPO0s9w==} + engines: {node: '>=18.0.0'} + hasBin: true + + '@web/parse5-utils@2.1.0': + resolution: {integrity: sha512-GzfK5disEJ6wEjoPwx8AVNwUe9gYIiwc+x//QYxYDAFKUp4Xb1OJAGLc2l2gVrSQmtPGLKrTRcW90Hv4pEq1qA==} + engines: {node: '>=18.0.0'} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + are-docs-informative@0.0.2: + resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} + engines: {node: '>=14'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-back@3.1.0: + resolution: {integrity: sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==} + engines: {node: '>=6'} + + array-back@6.2.2: + resolution: {integrity: sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==} + engines: {node: '>=12.17'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + builtins@5.1.0: + resolution: {integrity: sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==} + + cache-content-type@1.0.1: + resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} + engines: {node: '>= 6.0.0'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-keys@6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + chalk-template@0.4.0: + resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==} + engines: {node: '>=12'} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-width@3.0.0: + resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} + engines: {node: '>= 10'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + command-line-args@5.2.1: + resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==} + engines: {node: '>=4.0.0'} + + command-line-usage@7.0.3: + resolution: {integrity: sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==} + engines: {node: '>=12.20.0'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + comment-parser@1.4.1: + resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} + engines: {node: '>= 12.0.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookies@0.9.1: + resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} + engines: {node: '>= 0.8'} + + cross-spawn@6.0.6: + resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} + engines: {node: '>=4.8'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + debounce@1.2.1: + resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize-keys@1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + deep-equal@1.0.1: + resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + default-gateway@6.0.3: + resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==} + engines: {node: '>= 10'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + + depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + didyoumean2@4.1.0: + resolution: {integrity: sha512-qTBmfQoXvhKO75D/05C8m+fteQmn4U46FWYiLhXtZQInzitXLWY0EQ/2oKnpAz9g2lQWW8jYcLcT+hPJGT+kig==} + engines: {node: '>=10.13'} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-abstract@1.24.0: + resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + esbuild@0.25.9: + resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-compat-utils@0.5.1: + resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} + engines: {node: '>=12'} + peerDependencies: + eslint: ^9.39.1 + + eslint-config-google@0.14.0: + resolution: {integrity: sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==} + engines: {node: '>=0.10.0'} + peerDependencies: + eslint: ^9.39.1 + + eslint-config-prettier@9.1.0: + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: ^9.39.1 + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-module-utils@2.12.1: + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-es-x@7.8.0: + resolution: {integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: ^9.39.1 + + eslint-plugin-es-x@8.0.0: + resolution: {integrity: sha512-kPIagK5FxZBDwVxZXCsxmZUjU2aYGeTs4/wfAauI2FAThsbeLgred5b+6S0x7Hhx04GPzrB4j0h60bnsyLpzEA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: ^9.39.1 + + eslint-plugin-es@4.1.0: + resolution: {integrity: sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==} + engines: {node: '>=8.10.0'} + peerDependencies: + eslint: ^9.39.1 + + eslint-plugin-html@8.1.3: + resolution: {integrity: sha512-cnCdO7yb/jrvgSJJAfRkGDOwLu1AOvNdw8WCD6nh/2C4RnxuI4tz6QjMEAmmSiHSeugq/fXcIO8yBpIBQrMZCg==} + engines: {node: '>=16.0.0'} + + eslint-plugin-import@2.32.0: + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^9.39.1 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jsdoc@50.8.0: + resolution: {integrity: sha512-UyGb5755LMFWPrZTEqqvTJ3urLz1iqj+bYOHFNag+sw3NvaMWP9K2z+uIn37XfNALmQLQyrBlJ5mkiVPL7ADEg==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^9.39.1 + + eslint-plugin-lit@1.15.0: + resolution: {integrity: sha512-Yhr2MYNz6Ln8megKcX503aVZQln8wsywCG49g0heiJ/Qr5UjkE4pGr4Usez2anNcc7NvlvHbQWMYwWcgH3XRKA==} + engines: {node: '>= 12'} + peerDependencies: + eslint: ^9.39.1 + + eslint-plugin-n@15.7.0: + resolution: {integrity: sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==} + engines: {node: '>=12.22.0'} + peerDependencies: + eslint: ^9.39.1 + + eslint-plugin-n@17.23.1: + resolution: {integrity: sha512-68PealUpYoHOBh332JLLD9Sj7OQUDkFpmcfqt8R9sySfFSeuGJjMTJQvCRRB96zO3A/PELRLkPrzsHmzEFQQ5A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^9.39.1 + + eslint-plugin-prettier@5.2.1: + resolution: {integrity: sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: ^9.39.1 + eslint-config-prettier: '*' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-prettier@5.5.4: + resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: ^9.39.1 + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-regex@1.10.0: + resolution: {integrity: sha512-C8/qYKkkbIb0epxKzaz4aw7oVAOmm19fJpR/moUrUToq/vc4xW4sEKMlTQqH6EtNGpvLjYsbbZRlWNWwQGeTSA==} + engines: {node: '>=6.0.0'} + peerDependencies: + eslint: ^9.39.1 + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-utils@2.1.0: + resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} + engines: {node: '>=6'} + + eslint-utils@3.0.0: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: ^9.39.1 + + eslint-visitor-keys@1.3.0: + resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} + engines: {node: '>=4'} + + eslint-visitor-keys@2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.1: + resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-replace@3.0.0: + resolution: {integrity: sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==} + engines: {node: '>=4.0.0'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + gts@6.0.2: + resolution: {integrity: sha512-lp9+eDzzm6TYqiBpgGY00EInxBHFTJiU5brsVp11qXCJEw7Q6WNNngja0spZeqSFWSquaRuHQUuWxdZLaxnKmw==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: 5.8.2 + + hard-rejection@2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + + hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + + htmlparser2@10.0.0: + resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==} + + http-assert@1.5.0: + resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==} + engines: {node: '>= 0.8'} + + http-errors@1.6.3: + resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} + engines: {node: '>= 0.6'} + + http-errors@1.8.1: + resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} + engines: {node: '>= 0.6'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.3: + resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + inquirer@7.3.3: + resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} + engines: {node: '>=8.0.0'} + + internal-ip@6.2.0: + resolution: {integrity: sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg==} + engines: {node: '>=10'} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + ip-regex@4.3.0: + resolution: {integrity: sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==} + engines: {node: '>=8'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-ip@3.1.0: + resolution: {integrity: sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==} + engines: {node: '>=8'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isbinaryfile@5.0.5: + resolution: {integrity: sha512-vh9MWXjhhblwrHlt/yutrubDuBD01kKFscyVndE2/VEeEU5aAizrzuWAsEaCjo997k+IluhN6C4jgxfS69SCIw==} + engines: {node: '>= 18.0.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsdoc-type-pratt-parser@4.1.0: + resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} + engines: {node: '>=12.0.0'} + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keygrip@1.1.0: + resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} + engines: {node: '>= 0.6'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + koa-compose@4.1.0: + resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} + + koa-convert@2.0.0: + resolution: {integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==} + engines: {node: '>= 10'} + + koa-etag@4.0.0: + resolution: {integrity: sha512-1cSdezCkBWlyuB9l6c/IFoe1ANCDdPBxkDkRiaIup40xpUub6U/wwRXoKBZw/O5BifX9OlqAjYnDyzM6+l+TAg==} + + koa-send@5.0.1: + resolution: {integrity: sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==} + engines: {node: '>= 8'} + + koa-static@5.0.0: + resolution: {integrity: sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==} + engines: {node: '>= 7.6.0'} + + koa@2.16.2: + resolution: {integrity: sha512-+CCssgnrWKx9aI3OeZwroa/ckG4JICxvIFnSiOUyl2Uv+UTI+xIw0FfFrWS7cQFpoePpr9o8csss7KzsTzNL8Q==} + engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lit-analyzer@2.0.3: + resolution: {integrity: sha512-XiAjnwVipNrKav7r3CSEZpWt+mwYxrhPRVC7h8knDmn/HWTzzWJvPe+mwBcL2brn4xhItAMzZhFC8tzzqHKmiQ==} + hasBin: true + + load-json-file@4.0.0: + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash.deburr@4.1.0: + resolution: {integrity: sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + lru-cache@8.0.5: + resolution: {integrity: sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==} + engines: {node: '>=16.14'} + + map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + + map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + memorystream@0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + + meow@9.0.0: + resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==} + engines: {node: '>=10'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist-options@4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mute-stream@0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + + nanocolors@0.2.13: + resolution: {integrity: sha512-0n3mSAQLPpGLV9ORXT5+C/D4mwew7Ebws69Hx4E2sgz2ZA5+32Q80B9tL8PbL7XHnRDiAxH/pnrUJ9a4fkTNTA==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + ncp@2.0.0: + resolution: {integrity: sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==} + hasBin: true + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + nice-try@1.0.5: + resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} + + normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + + normalize-package-data@3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} + + npm-run-all@4.1.5: + resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==} + engines: {node: '>= 4'} + hasBin: true + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + only@0.0.2: + resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==} + + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-event@4.2.0: + resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==} + engines: {node: '>=8'} + + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-imports-exports@0.2.4: + resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==} + + parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-statements@1.0.11: + resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==} + + parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} + + parse5@5.1.0: + resolution: {integrity: sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==} + + parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@2.0.1: + resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} + engines: {node: '>=4'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-type@3.0.0: + resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} + engines: {node: '>=4'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pidtree@0.3.1: + resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==} + engines: {node: '>=0.10'} + hasBin: true + + pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + + portfinder@1.0.37: + resolution: {integrity: sha512-yuGIEjDAYnnOex9ddMnKZEMFE0CcGo6zbfzDklkmT1m5z734ss6JMzN9rNB3+RR7iS+F10D4/BVIaXOyh8PQKw==} + engines: {node: '>= 10.12'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + engines: {node: '>=14'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + quick-lru@4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} + + read-pkg-up@7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + + read-pkg@3.0.0: + resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} + engines: {node: '>=4'} + + read-pkg@5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + regexpp@3.2.0: + resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} + engines: {node: '>=8'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + requireindex@1.2.0: + resolution: {integrity: sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==} + engines: {node: '>=0.10.5'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-path@1.4.0: + resolution: {integrity: sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==} + engines: {node: '>= 0.8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup@4.53.3: + resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-async@2.4.1: + resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} + engines: {node: '>=0.12.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@6.6.7: + resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} + engines: {npm: '>=2.0.0'} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + setprototypeof@1.1.0: + resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-expression-parse@4.0.0: + resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} + + spdx-license-ids@3.0.22: + resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} + + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string.prototype.padend@3.1.6: + resolution: {integrity: sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==} + engines: {node: '>= 0.4'} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + synckit@0.11.11: + resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} + engines: {node: ^14.18.0 || >=16.0.0} + + synckit@0.9.3: + resolution: {integrity: sha512-JJoOEKTfL1urb1mDoEblhD9NhEbWmq9jHEMEnxoC4ujUaZ4itA8vKgwkFAyNClgxplLi9tsUKX+EduK0p/l7sg==} + engines: {node: ^14.18.0 || >=16.0.0} + + table-layout@4.1.1: + resolution: {integrity: sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==} + engines: {node: '>=12.17'} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + terser@5.39.2: + resolution: {integrity: sha512-yEPUmWve+VA78bI71BW70Dh0TuV4HHd+I5SHOAfS1+QBOmvmCiiffgjR8ryyEd3KIfvPGFqoADt8LdQ6XpXIvg==} + engines: {node: '>=10'} + hasBin: true + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + + trim-newlines@3.0.1: + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: 5.8.2 + + ts-declaration-location@1.0.7: + resolution: {integrity: sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==} + peerDependencies: + typescript: 5.8.2 + + ts-lit-plugin@2.0.2: + resolution: {integrity: sha512-DPXlVxhjWHxg8AyBLcfSYt2JXgpANV1ssxxwjY98o26gD8MzeiM68HFW9c2VeDd1CjoR3w7B/6/uKxwBQe+ioA==} + + ts-simple-type@2.0.0-next.0: + resolution: {integrity: sha512-A+hLX83gS+yH6DtzNAhzZbPfU+D9D8lHlTSd7GeoMRBjOt3GRylDqLTYbdmjA4biWvq2xSfpqfIDj2l0OA/BVg==} + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsscmp@1.0.6: + resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} + engines: {node: '>=0.6.x'} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.18.1: + resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + + type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typescript@5.8.2: + resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} + engines: {node: '>=14.17'} + hasBin: true + + typical@4.0.0: + resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==} + engines: {node: '>=8'} + + typical@7.3.0: + resolution: {integrity: sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==} + engines: {node: '>=12.17'} + + ua-parser-js@1.0.41: + resolution: {integrity: sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==} + hasBin: true + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@7.10.0: + resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vscode-css-languageservice@6.3.9: + resolution: {integrity: sha512-1tLWfp+TDM5ZuVWht3jmaY5y7O6aZmpeXLoHl5bv1QtRsRKt4xYGRMmdJa5Pqx/FTkgRbsna9R+Gn2xE+evVuA==} + + vscode-html-languageservice@5.6.1: + resolution: {integrity: sha512-5Mrqy5CLfFZUgkyhNZLA1Ye5g12Cb/v6VM7SxUzZUaRKWMDz4md+y26PrfRTSU0/eQAl3XpO9m2og+GGtDMuaA==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + web-component-analyzer@2.0.0: + resolution: {integrity: sha512-UEvwfpD+XQw99sLKiH5B1T4QwpwNyWJxp59cnlRwFfhUW6JsQpw5jMeMwi7580sNou8YL3kYoS7BWLm+yJ/jVQ==} + hasBin: true + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wordwrapjs@5.1.0: + resolution: {integrity: sha512-JNjcULU2e4KJwUNv6CHgI46UvDGitb6dGryHajXTDiLgg1/RiGoPSDw4kZfYnwGtEXf2ZMeIewDQgFGzkCB2Sg==} + engines: {node: '>=12.17'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + ylru@1.4.0: + resolution: {integrity: sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==} + engines: {node: '>= 4.0.0'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/runtime@7.28.3': {} + + '@es-joy/jsdoccomment@0.50.2': + dependencies: + '@types/estree': 1.0.8 + '@typescript-eslint/types': 8.49.0 + comment-parser: 1.4.1 + esquery: 1.6.0 + jsdoc-type-pratt-parser: 4.1.0 + + '@esbuild/aix-ppc64@0.25.9': + optional: true + + '@esbuild/android-arm64@0.25.9': + optional: true + + '@esbuild/android-arm@0.25.9': + optional: true + + '@esbuild/android-x64@0.25.9': + optional: true + + '@esbuild/darwin-arm64@0.25.9': + optional: true + + '@esbuild/darwin-x64@0.25.9': + optional: true + + '@esbuild/freebsd-arm64@0.25.9': + optional: true + + '@esbuild/freebsd-x64@0.25.9': + optional: true + + '@esbuild/linux-arm64@0.25.9': + optional: true + + '@esbuild/linux-arm@0.25.9': + optional: true + + '@esbuild/linux-ia32@0.25.9': + optional: true + + '@esbuild/linux-loong64@0.25.9': + optional: true + + '@esbuild/linux-mips64el@0.25.9': + optional: true + + '@esbuild/linux-ppc64@0.25.9': + optional: true + + '@esbuild/linux-riscv64@0.25.9': + optional: true + + '@esbuild/linux-s390x@0.25.9': + optional: true + + '@esbuild/linux-x64@0.25.9': + optional: true + + '@esbuild/netbsd-arm64@0.25.9': + optional: true + + '@esbuild/netbsd-x64@0.25.9': + optional: true + + '@esbuild/openbsd-arm64@0.25.9': + optional: true + + '@esbuild/openbsd-x64@0.25.9': + optional: true + + '@esbuild/openharmony-arm64@0.25.9': + optional: true + + '@esbuild/sunos-x64@0.25.9': + optional: true + + '@esbuild/win32-arm64@0.25.9': + optional: true + + '@esbuild/win32-ia32@0.25.9': + optional: true + + '@esbuild/win32-x64@0.25.9': + optional: true + + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': + dependencies: + eslint: 9.39.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.3': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.1': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.30 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.30': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@koa/cors@5.0.0': + dependencies: + vary: 1.1.2 + + '@mdn/browser-compat-data@4.2.1': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@pkgr/core@0.1.2': {} + + '@pkgr/core@0.2.9': {} + + '@rollup/plugin-node-resolve@15.3.1(rollup@4.53.3)': + dependencies: + '@rollup/pluginutils': 5.2.0(rollup@4.53.3) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-module: 1.0.0 + resolve: 1.22.10 + optionalDependencies: + rollup: 4.53.3 + + '@rollup/pluginutils@5.2.0(rollup@4.53.3)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.53.3 + + '@rollup/rollup-android-arm-eabi@4.53.3': + optional: true + + '@rollup/rollup-android-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-x64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-arm64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-x64@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-musl@4.53.3': + optional: true + + '@rollup/rollup-openharmony-arm64@4.53.3': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.53.3': + optional: true + + '@rtsao/scc@1.1.0': {} + + '@types/accepts@1.3.7': + dependencies: + '@types/node': 24.3.0 + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 24.3.0 + + '@types/command-line-args@5.2.3': {} + + '@types/connect@3.4.38': + dependencies: + '@types/node': 24.3.0 + + '@types/content-disposition@0.5.9': {} + + '@types/cookies@0.9.1': + dependencies: + '@types/connect': 3.4.38 + '@types/express': 5.0.3 + '@types/keygrip': 1.0.6 + '@types/node': 24.3.0 + + '@types/estree@1.0.8': {} + + '@types/express-serve-static-core@5.0.7': + dependencies: + '@types/node': 24.3.0 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.5 + + '@types/express@5.0.3': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.0.7 + '@types/serve-static': 1.15.8 + + '@types/http-assert@1.5.6': {} + + '@types/http-errors@2.0.5': {} + + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + + '@types/keygrip@1.0.6': {} + + '@types/koa-compose@3.2.8': + dependencies: + '@types/koa': 3.0.0 + + '@types/koa@2.15.0': + dependencies: + '@types/accepts': 1.3.7 + '@types/content-disposition': 0.5.9 + '@types/cookies': 0.9.1 + '@types/http-assert': 1.5.6 + '@types/http-errors': 2.0.5 + '@types/keygrip': 1.0.6 + '@types/koa-compose': 3.2.8 + '@types/node': 24.3.0 + + '@types/koa@3.0.0': + dependencies: + '@types/accepts': 1.3.7 + '@types/content-disposition': 0.5.9 + '@types/cookies': 0.9.1 + '@types/http-assert': 1.5.6 + '@types/http-errors': 2.0.5 + '@types/keygrip': 1.0.6 + '@types/koa-compose': 3.2.8 + '@types/node': 24.3.0 + + '@types/mime@1.3.5': {} + + '@types/minimist@1.2.5': {} + + '@types/node@24.3.0': + dependencies: + undici-types: 7.10.0 + + '@types/normalize-package-data@2.4.4': {} + + '@types/page@1.11.9': {} + + '@types/parse5@6.0.3': {} + + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + + '@types/resolve@1.20.2': {} + + '@types/send@0.17.5': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 24.3.0 + + '@types/serve-static@1.15.8': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 24.3.0 + '@types/send': 0.17.5 + + '@types/ws@7.4.7': + dependencies: + '@types/node': 24.3.0 + + '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.8.2))(eslint@9.39.1)(typescript@5.8.2)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.8.2) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.1)(typescript@5.8.2) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.8.2) + '@typescript-eslint/visitor-keys': 8.49.0 + eslint: 9.39.1 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.8.2) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.8.2)': + dependencies: + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.8.2) + '@typescript-eslint/visitor-keys': 8.49.0 + debug: 4.4.3 + eslint: 9.39.1 + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.49.0(typescript@5.8.2)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.8.2) + '@typescript-eslint/types': 8.49.0 + debug: 4.4.3 + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.49.0': + dependencies: + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/visitor-keys': 8.49.0 + + '@typescript-eslint/tsconfig-utils@8.49.0(typescript@5.8.2)': + dependencies: + typescript: 5.8.2 + + '@typescript-eslint/type-utils@8.49.0(eslint@9.39.1)(typescript@5.8.2)': + dependencies: + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.8.2) + '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.8.2) + debug: 4.4.3 + eslint: 9.39.1 + ts-api-utils: 2.1.0(typescript@5.8.2) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.49.0': {} + + '@typescript-eslint/typescript-estree@8.49.0(typescript@5.8.2)': + dependencies: + '@typescript-eslint/project-service': 8.49.0(typescript@5.8.2) + '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.8.2) + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/visitor-keys': 8.49.0 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.1.0(typescript@5.8.2) + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.49.0(eslint@9.39.1)(typescript@5.8.2)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@typescript-eslint/scope-manager': 8.49.0 + '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.8.2) + eslint: 9.39.1 + typescript: 5.8.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.49.0': + dependencies: + '@typescript-eslint/types': 8.49.0 + eslint-visitor-keys: 4.2.1 + + '@vscode/l10n@0.0.18': {} + + '@vscode/web-custom-data@0.4.13': {} + + '@web/config-loader@0.3.3': {} + + '@web/dev-server-core@0.7.5': + dependencies: + '@types/koa': 2.15.0 + '@types/ws': 7.4.7 + '@web/parse5-utils': 2.1.0 + chokidar: 4.0.3 + clone: 2.1.2 + es-module-lexer: 1.7.0 + get-stream: 6.0.1 + is-stream: 2.0.1 + isbinaryfile: 5.0.5 + koa: 2.16.2 + koa-etag: 4.0.0 + koa-send: 5.0.1 + koa-static: 5.0.0 + lru-cache: 8.0.5 + mime-types: 2.1.35 + parse5: 6.0.1 + picomatch: 2.3.1 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/dev-server-esbuild@1.0.4': + dependencies: + '@mdn/browser-compat-data': 4.2.1 + '@web/dev-server-core': 0.7.5 + esbuild: 0.25.9 + parse5: 6.0.1 + ua-parser-js: 1.0.41 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/dev-server-rollup@0.6.4': + dependencies: + '@rollup/plugin-node-resolve': 15.3.1(rollup@4.53.3) + '@web/dev-server-core': 0.7.5 + nanocolors: 0.2.13 + parse5: 6.0.1 + rollup: 4.53.3 + whatwg-url: 14.2.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/dev-server@0.4.6': + dependencies: + '@babel/code-frame': 7.27.1 + '@types/command-line-args': 5.2.3 + '@web/config-loader': 0.3.3 + '@web/dev-server-core': 0.7.5 + '@web/dev-server-rollup': 0.6.4 + camelcase: 6.3.0 + command-line-args: 5.2.1 + command-line-usage: 7.0.3 + debounce: 1.2.1 + deepmerge: 4.3.1 + internal-ip: 6.2.0 + nanocolors: 0.2.13 + open: 8.4.2 + portfinder: 1.0.37 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/parse5-utils@2.1.0': + dependencies: + '@types/parse5': 6.0.3 + parse5: 6.0.1 + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + are-docs-informative@0.0.2: {} + + argparse@2.0.1: {} + + array-back@3.1.0: {} + + array-back@6.2.2: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.findlastindex@1.2.6: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + arrify@1.0.1: {} + + async-function@1.0.0: {} + + async@3.2.6: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + balanced-match@1.0.2: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + buffer-from@1.1.2: {} + + builtins@5.1.0: + dependencies: + semver: 7.7.3 + + cache-content-type@1.0.1: + dependencies: + mime-types: 2.1.35 + ylru: 1.4.0 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase-keys@6.2.2: + dependencies: + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + chalk-template@0.4.0: + dependencies: + chalk: 4.1.2 + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chardet@0.7.0: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-width@3.0.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@2.1.2: {} + + co@4.6.0: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + command-line-args@5.2.1: + dependencies: + array-back: 3.1.0 + find-replace: 3.0.0 + lodash.camelcase: 4.3.0 + typical: 4.0.0 + + command-line-usage@7.0.3: + dependencies: + array-back: 6.2.2 + chalk-template: 0.4.0 + table-layout: 4.1.1 + typical: 7.3.0 + + commander@2.20.3: {} + + comment-parser@1.4.1: {} + + concat-map@0.0.1: {} + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookies@0.9.1: + dependencies: + depd: 2.0.0 + keygrip: 1.1.0 + + cross-spawn@6.0.6: + dependencies: + nice-try: 1.0.5 + path-key: 2.0.1 + semver: 5.7.2 + shebang-command: 1.2.0 + which: 1.3.1 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + debounce@1.2.1: {} + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decamelize-keys@1.1.1: + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + + decamelize@1.2.0: {} + + deep-equal@1.0.1: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + default-gateway@6.0.3: + dependencies: + execa: 5.1.1 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-lazy-prop@2.0.0: {} + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + delegates@1.0.0: {} + + depd@1.1.2: {} + + depd@2.0.0: {} + + destroy@1.2.0: {} + + didyoumean2@4.1.0: + dependencies: + '@babel/runtime': 7.28.3 + leven: 3.1.0 + lodash.deburr: 4.1.0 + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + emoji-regex@8.0.0: {} + + encodeurl@1.0.2: {} + + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + entities@4.5.0: {} + + entities@6.0.1: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-abstract@1.24.0: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + esbuild@0.25.9: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.9 + '@esbuild/android-arm': 0.25.9 + '@esbuild/android-arm64': 0.25.9 + '@esbuild/android-x64': 0.25.9 + '@esbuild/darwin-arm64': 0.25.9 + '@esbuild/darwin-x64': 0.25.9 + '@esbuild/freebsd-arm64': 0.25.9 + '@esbuild/freebsd-x64': 0.25.9 + '@esbuild/linux-arm': 0.25.9 + '@esbuild/linux-arm64': 0.25.9 + '@esbuild/linux-ia32': 0.25.9 + '@esbuild/linux-loong64': 0.25.9 + '@esbuild/linux-mips64el': 0.25.9 + '@esbuild/linux-ppc64': 0.25.9 + '@esbuild/linux-riscv64': 0.25.9 + '@esbuild/linux-s390x': 0.25.9 + '@esbuild/linux-x64': 0.25.9 + '@esbuild/netbsd-arm64': 0.25.9 + '@esbuild/netbsd-x64': 0.25.9 + '@esbuild/openbsd-arm64': 0.25.9 + '@esbuild/openbsd-x64': 0.25.9 + '@esbuild/openharmony-arm64': 0.25.9 + '@esbuild/sunos-x64': 0.25.9 + '@esbuild/win32-arm64': 0.25.9 + '@esbuild/win32-ia32': 0.25.9 + '@esbuild/win32-x64': 0.25.9 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + eslint-compat-utils@0.5.1(eslint@9.39.1): + dependencies: + eslint: 9.39.1 + semver: 7.7.3 + + eslint-config-google@0.14.0(eslint@9.39.1): + dependencies: + eslint: 9.39.1 + + eslint-config-prettier@9.1.0(eslint@9.39.1): + dependencies: + eslint: 9.39.1 + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.1 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.8.2) + eslint: 9.39.1 + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + + eslint-plugin-es-x@7.8.0(eslint@9.39.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint-community/regexpp': 4.12.2 + eslint: 9.39.1 + eslint-compat-utils: 0.5.1(eslint@9.39.1) + + eslint-plugin-es-x@8.0.0(eslint@9.39.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint-community/regexpp': 4.12.2 + eslint: 9.39.1 + eslint-compat-utils: 0.5.1(eslint@9.39.1) + + eslint-plugin-es@4.1.0(eslint@9.39.1): + dependencies: + eslint: 9.39.1 + eslint-utils: 2.1.0 + regexpp: 3.2.0 + + eslint-plugin-html@8.1.3: + dependencies: + htmlparser2: 10.0.0 + + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.8.2))(eslint@9.39.1): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.39.1 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.8.2) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-jsdoc@50.8.0(eslint@9.39.1): + dependencies: + '@es-joy/jsdoccomment': 0.50.2 + are-docs-informative: 0.0.2 + comment-parser: 1.4.1 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint: 9.39.1 + espree: 10.4.0 + esquery: 1.6.0 + parse-imports-exports: 0.2.4 + semver: 7.7.3 + spdx-expression-parse: 4.0.0 + transitivePeerDependencies: + - supports-color + + eslint-plugin-lit@1.15.0(eslint@9.39.1): + dependencies: + eslint: 9.39.1 + parse5: 6.0.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + requireindex: 1.2.0 + + eslint-plugin-n@15.7.0(eslint@9.39.1): + dependencies: + builtins: 5.1.0 + eslint: 9.39.1 + eslint-plugin-es: 4.1.0(eslint@9.39.1) + eslint-utils: 3.0.0(eslint@9.39.1) + ignore: 5.3.2 + is-core-module: 2.16.1 + minimatch: 3.1.2 + resolve: 1.22.10 + semver: 7.7.3 + + eslint-plugin-n@17.23.1(eslint@9.39.1)(typescript@5.8.2): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + enhanced-resolve: 5.18.3 + eslint: 9.39.1 + eslint-plugin-es-x: 7.8.0(eslint@9.39.1) + get-tsconfig: 4.13.0 + globals: 15.15.0 + globrex: 0.1.2 + ignore: 5.3.2 + semver: 7.7.3 + ts-declaration-location: 1.0.7(typescript@5.8.2) + transitivePeerDependencies: + - typescript + + eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@9.39.1))(eslint@9.39.1)(prettier@3.3.3): + dependencies: + eslint: 9.39.1 + prettier: 3.3.3 + prettier-linter-helpers: 1.0.0 + synckit: 0.9.3 + optionalDependencies: + eslint-config-prettier: 9.1.0(eslint@9.39.1) + + eslint-plugin-prettier@5.5.4(eslint-config-prettier@9.1.0(eslint@9.39.1))(eslint@9.39.1)(prettier@2.8.8): + dependencies: + eslint: 9.39.1 + prettier: 2.8.8 + prettier-linter-helpers: 1.0.0 + synckit: 0.11.11 + optionalDependencies: + eslint-config-prettier: 9.1.0(eslint@9.39.1) + + eslint-plugin-regex@1.10.0(eslint@9.39.1): + dependencies: + eslint: 9.39.1 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-utils@2.1.0: + dependencies: + eslint-visitor-keys: 1.3.0 + + eslint-utils@3.0.0(eslint@9.39.1): + dependencies: + eslint: 9.39.1 + eslint-visitor-keys: 2.1.0 + + eslint-visitor-keys@1.3.0: {} + + eslint-visitor-keys@2.1.0: {} + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.3 + '@eslint/js': 9.39.1 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + figures@3.2.0: + dependencies: + escape-string-regexp: 1.0.5 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-replace@3.0.0: + dependencies: + array-back: 3.1.0 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + fresh@0.5.2: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@6.0.1: {} + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@14.0.0: {} + + globals@15.15.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + globrex@0.1.2: {} + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + gts@6.0.2(typescript@5.8.2): + dependencies: + '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1)(typescript@5.8.2))(eslint@9.39.1)(typescript@5.8.2) + '@typescript-eslint/parser': 8.49.0(eslint@9.39.1)(typescript@5.8.2) + chalk: 4.1.2 + eslint: 9.39.1 + eslint-config-prettier: 9.1.0(eslint@9.39.1) + eslint-plugin-n: 15.7.0(eslint@9.39.1) + eslint-plugin-prettier: 5.2.1(eslint-config-prettier@9.1.0(eslint@9.39.1))(eslint@9.39.1)(prettier@3.3.3) + execa: 5.1.1 + inquirer: 7.3.3 + json5: 2.2.3 + meow: 9.0.0 + ncp: 2.0.0 + prettier: 3.3.3 + rimraf: 3.0.2 + typescript: 5.8.2 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - '@types/eslint' + - jiti + - supports-color + + hard-rejection@2.1.0: {} + + has-bigints@1.1.0: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hosted-git-info@2.8.9: {} + + hosted-git-info@4.1.0: + dependencies: + lru-cache: 6.0.0 + + htmlparser2@10.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 6.0.1 + + http-assert@1.5.0: + dependencies: + deep-equal: 1.0.1 + http-errors: 1.8.1 + + http-errors@1.6.3: + dependencies: + depd: 1.1.2 + inherits: 2.0.3 + setprototypeof: 1.1.0 + statuses: 1.5.0 + + http-errors@1.8.1: + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 1.5.0 + toidentifier: 1.0.1 + + human-signals@2.1.0: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.3: {} + + inherits@2.0.4: {} + + inquirer@7.3.3: + dependencies: + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 0.0.8 + run-async: 2.4.1 + rxjs: 6.6.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + + internal-ip@6.2.0: + dependencies: + default-gateway: 6.0.3 + ipaddr.js: 1.9.1 + is-ip: 3.1.0 + p-event: 4.2.0 + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + ip-regex@4.3.0: {} + + ipaddr.js@1.9.1: {} + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-arrayish@0.2.1: {} + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-docker@2.2.1: {} + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-ip@3.1.0: + dependencies: + ip-regex: 4.3.0 + + is-map@2.0.3: {} + + is-module@1.0.0: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-plain-obj@1.1.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-stream@2.0.1: {} + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isarray@2.0.5: {} + + isbinaryfile@5.0.5: {} + + isexe@2.0.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsdoc-type-pratt-parser@4.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-better-errors@1.0.2: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + json5@2.2.3: {} + + keygrip@1.1.0: + dependencies: + tsscmp: 1.0.6 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kind-of@6.0.3: {} + + koa-compose@4.1.0: {} + + koa-convert@2.0.0: + dependencies: + co: 4.6.0 + koa-compose: 4.1.0 + + koa-etag@4.0.0: + dependencies: + etag: 1.8.1 + + koa-send@5.0.1: + dependencies: + debug: 4.4.3 + http-errors: 1.8.1 + resolve-path: 1.4.0 + transitivePeerDependencies: + - supports-color + + koa-static@5.0.0: + dependencies: + debug: 3.2.7 + koa-send: 5.0.1 + transitivePeerDependencies: + - supports-color + + koa@2.16.2: + dependencies: + accepts: 1.3.8 + cache-content-type: 1.0.1 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookies: 0.9.1 + debug: 4.4.3 + delegates: 1.0.0 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + fresh: 0.5.2 + http-assert: 1.5.0 + http-errors: 1.8.1 + is-generator-function: 1.1.0 + koa-compose: 4.1.0 + koa-convert: 2.0.0 + on-finished: 2.4.1 + only: 0.0.2 + parseurl: 1.3.3 + statuses: 1.5.0 + type-is: 1.6.18 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lines-and-columns@1.2.4: {} + + lit-analyzer@2.0.3: + dependencies: + '@vscode/web-custom-data': 0.4.13 + chalk: 2.4.2 + didyoumean2: 4.1.0 + fast-glob: 3.3.3 + parse5: 5.1.0 + ts-simple-type: 2.0.0-next.0 + vscode-css-languageservice: 6.3.9 + vscode-html-languageservice: 5.6.1 + web-component-analyzer: 2.0.0 + + load-json-file@4.0.0: + dependencies: + graceful-fs: 4.2.11 + parse-json: 4.0.0 + pify: 3.0.0 + strip-bom: 3.0.0 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.camelcase@4.3.0: {} + + lodash.deburr@4.1.0: {} + + lodash.merge@4.6.2: {} + + lodash@4.17.21: {} + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + lru-cache@8.0.5: {} + + map-obj@1.0.1: {} + + map-obj@4.3.0: {} + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + memorystream@0.3.1: {} + + meow@9.0.0: + dependencies: + '@types/minimist': 1.2.5 + camelcase-keys: 6.2.2 + decamelize: 1.2.0 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.18.1 + yargs-parser: 20.2.9 + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@2.1.0: {} + + min-indent@1.0.1: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist-options@4.1.0: + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + + minimist@1.2.8: {} + + ms@2.1.3: {} + + mute-stream@0.0.8: {} + + nanocolors@0.2.13: {} + + natural-compare@1.4.0: {} + + ncp@2.0.0: {} + + negotiator@0.6.3: {} + + nice-try@1.0.5: {} + + normalize-package-data@2.5.0: + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.10 + semver: 5.7.2 + validate-npm-package-license: 3.0.4 + + normalize-package-data@3.0.3: + dependencies: + hosted-git-info: 4.1.0 + is-core-module: 2.16.1 + semver: 7.7.3 + validate-npm-package-license: 3.0.4 + + npm-run-all@4.1.5: + dependencies: + ansi-styles: 3.2.1 + chalk: 2.4.2 + cross-spawn: 6.0.6 + memorystream: 0.3.1 + minimatch: 3.1.2 + pidtree: 0.3.1 + read-pkg: 3.0.0 + shell-quote: 1.8.3 + string.prototype.padend: 3.1.6 + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + only@0.0.2: {} + + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + os-tmpdir@1.0.2: {} + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-event@4.2.0: + dependencies: + p-timeout: 3.2.0 + + p-finally@1.0.0: {} + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + + p-try@2.2.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-imports-exports@0.2.4: + dependencies: + parse-statements: 1.0.11 + + parse-json@4.0.0: + dependencies: + error-ex: 1.3.2 + json-parse-better-errors: 1.0.2 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.27.1 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-statements@1.0.11: {} + + parse5-htmlparser2-tree-adapter@6.0.1: + dependencies: + parse5: 6.0.1 + + parse5@5.1.0: {} + + parse5@6.0.1: {} + + parseurl@1.3.3: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@2.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-type@3.0.0: + dependencies: + pify: 3.0.0 + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pidtree@0.3.1: {} + + pify@3.0.0: {} + + portfinder@1.0.37: + dependencies: + async: 3.2.6 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + possible-typed-array-names@1.1.0: {} + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@2.8.8: {} + + prettier@3.3.3: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + quick-lru@4.0.1: {} + + read-pkg-up@7.0.1: + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + + read-pkg@3.0.0: + dependencies: + load-json-file: 4.0.0 + normalize-package-data: 2.5.0 + path-type: 3.0.0 + + read-pkg@5.2.0: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + + readdirp@4.1.2: {} + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + regexpp@3.2.0: {} + + require-directory@2.1.1: {} + + requireindex@1.2.0: {} + + resolve-from@4.0.0: {} + + resolve-path@1.4.0: + dependencies: + http-errors: 1.6.3 + path-is-absolute: 1.0.1 + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + reusify@1.1.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup@4.53.3: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.53.3 + '@rollup/rollup-android-arm64': 4.53.3 + '@rollup/rollup-darwin-arm64': 4.53.3 + '@rollup/rollup-darwin-x64': 4.53.3 + '@rollup/rollup-freebsd-arm64': 4.53.3 + '@rollup/rollup-freebsd-x64': 4.53.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 + '@rollup/rollup-linux-arm-musleabihf': 4.53.3 + '@rollup/rollup-linux-arm64-gnu': 4.53.3 + '@rollup/rollup-linux-arm64-musl': 4.53.3 + '@rollup/rollup-linux-loong64-gnu': 4.53.3 + '@rollup/rollup-linux-ppc64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-musl': 4.53.3 + '@rollup/rollup-linux-s390x-gnu': 4.53.3 + '@rollup/rollup-linux-x64-gnu': 4.53.3 + '@rollup/rollup-linux-x64-musl': 4.53.3 + '@rollup/rollup-openharmony-arm64': 4.53.3 + '@rollup/rollup-win32-arm64-msvc': 4.53.3 + '@rollup/rollup-win32-ia32-msvc': 4.53.3 + '@rollup/rollup-win32-x64-gnu': 4.53.3 + '@rollup/rollup-win32-x64-msvc': 4.53.3 + fsevents: 2.3.3 + + run-async@2.4.1: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@6.6.7: + dependencies: + tslib: 1.14.1 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-buffer@5.2.1: {} + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + + semver@5.7.2: {} + + semver@6.3.1: {} + + semver@7.7.3: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + setprototypeof@1.1.0: {} + + setprototypeof@1.2.0: {} + + shebang-command@1.2.0: + dependencies: + shebang-regex: 1.0.0 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@1.0.0: {} + + shebang-regex@3.0.0: {} + + shell-quote@1.8.3: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.22 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.22 + + spdx-expression-parse@4.0.0: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.22 + + spdx-license-ids@3.0.22: {} + + statuses@1.5.0: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string.prototype.padend@3.1.6: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-bom@3.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + synckit@0.11.11: + dependencies: + '@pkgr/core': 0.2.9 + + synckit@0.9.3: + dependencies: + '@pkgr/core': 0.1.2 + tslib: 2.8.1 + + table-layout@4.1.1: + dependencies: + array-back: 6.2.2 + wordwrapjs: 5.1.0 + + tapable@2.3.0: {} + + terser@5.39.2: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.15.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + through@2.3.8: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + + trim-newlines@3.0.1: {} + + ts-api-utils@2.1.0(typescript@5.8.2): + dependencies: + typescript: 5.8.2 + + ts-declaration-location@1.0.7(typescript@5.8.2): + dependencies: + picomatch: 4.0.3 + typescript: 5.8.2 + + ts-lit-plugin@2.0.2: + dependencies: + lit-analyzer: 2.0.3 + web-component-analyzer: 2.0.0 + + ts-simple-type@2.0.0-next.0: {} + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@1.14.1: {} + + tslib@2.8.1: {} + + tsscmp@1.0.6: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.18.1: {} + + type-fest@0.21.3: {} + + type-fest@0.6.0: {} + + type-fest@0.8.1: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript@5.8.2: {} + + typical@4.0.0: {} + + typical@7.3.0: {} + + ua-parser-js@1.0.41: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@7.10.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + vary@1.1.2: {} + + vscode-css-languageservice@6.3.9: + dependencies: + '@vscode/l10n': 0.0.18 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + vscode-html-languageservice@5.6.1: + dependencies: + '@vscode/l10n': 0.0.18 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-uri@3.1.0: {} + + web-component-analyzer@2.0.0: + dependencies: + fast-glob: 3.3.3 + ts-simple-type: 2.0.0-next.0 + typescript: 5.8.2 + yargs: 17.7.2 + + webidl-conversions@7.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@1.3.1: + dependencies: + isexe: 2.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wordwrapjs@5.1.0: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + ws@7.5.10: {} + + y18n@5.0.8: {} + + yallist@4.0.0: {} + + yargs-parser@20.2.9: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + ylru@1.4.0: {} + + yocto-queue@0.1.0: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..7d40629 --- /dev/null +++ b/pnpm-workspace.yaml
@@ -0,0 +1 @@ +onlyBuiltDependencies: []
diff --git a/polygerrit-ui/GEMINI.md b/polygerrit-ui/AGENTS.md similarity index 96% rename from polygerrit-ui/GEMINI.md rename to polygerrit-ui/AGENTS.md index 7478a77..089162d 100644 --- a/polygerrit-ui/GEMINI.md +++ b/polygerrit-ui/AGENTS.md
@@ -1,4 +1,4 @@ -# Gemini Project Profile: gerrit/polygerrit-ui +# AI Agent Project Profile: gerrit/polygerrit-ui This document provides a summary of the front-end development environment for the `polygerrit-ui` part of the gerrit project. @@ -44,6 +44,12 @@ - `yarn test:screenshot`: Run visual regression tests. - `yarn test:screenshot-update`: Run visual regression tests and update baseline images. +Bazel equivalents (what CI runs; all three are tagged `manual` and must be named explicitly): + +- `bazelisk test //polygerrit-ui:web_test_runner`: Unit tests only — same set as `yarn test`. +- `bazelisk test //polygerrit-ui:web_test_runner_screenshots`: Screenshot regression tests only — same set as `yarn test:screenshot`. +- `bazelisk test //polygerrit-ui:web_test_runner_all`: Both buckets in one invocation. + ## Running Single Tests Running the full test suite can be slow. For a faster feedback loop during development, you can run tests for a single file. Use the `test:single:nowatch` script with the path to the test file. This will run the test once and exit.
diff --git a/polygerrit-ui/BUILD b/polygerrit-ui/BUILD index a42e2dd..8cf523d 100644 --- a/polygerrit-ui/BUILD +++ b/polygerrit-ui/BUILD
@@ -1,6 +1,10 @@ -load("//tools/bzl:genrule2.bzl", "genrule2") +load("@com_googlesource_gerrit_bazlets//tools:genrule2.bzl", "genrule2") +load("@ui_dev_npm//:defs.bzl", "npm_link_all_packages") +load("@ui_dev_npm//polygerrit-ui:@web/test-runner/package_json.bzl", web_test_runner_npm_bin = "bin") load("//tools/bzl:js.bzl", "web_test_runner") +npm_link_all_packages(name = "node_modules") + package(default_visibility = ["//visibility:public"]) genrule2( @@ -22,24 +26,55 @@ # This is a dependency for web_test_runner rule in js.bzl that is only used by # plugins. -sh_binary( +web_test_runner_npm_bin.wtr_binary( name = "web_test_runner_bin", - srcs = ["@ui_dev_npm//:node_modules/@web/test-runner/dist/bin.js"], - data = [ - "@ui_dev_npm//@web/dev-server-esbuild", - "@ui_dev_npm//@web/test-runner", - "@ui_dev_npm//@web/test-runner-playwright", - "@ui_dev_npm//@web/test-runner-visual-regression", - ], ) web_test_runner( name = "web_test_runner", srcs = ["web_test_runner.sh"], data = [ + "//polygerrit-ui:node_modules", + "//polygerrit-ui/app:node_modules", + "//polygerrit-ui/app:web-test-runner_app-sources", + ], +) + +# Baseline PNGs used by *_screenshot_test.ts files. The failed/ directory is +# gitignored and intentionally excluded. +filegroup( + name = "screenshot_baselines", + srcs = glob(["screenshots/**/baseline/**"]), +) + +web_test_runner( + name = "web_test_runner_screenshots", + srcs = ["web_test_runner.sh"], + args = ["--run-screenshots"], + data = [ "//polygerrit-ui/app:web-test-runner_app-sources", "@ui_dev_npm//:node_modules", "@ui_npm//:node_modules", + ":screenshot_baselines", + # fonts.css references /polygerrit-ui/app/fonts/, which the runtime + # middleware in web-test-runner.config.mjs rewrites to /lib/fonts/. + # Pull the actual font files into the runfiles so that rewrite + # resolves to a real path; otherwise glyphs fall back to system + # fonts and every screenshot differs in height by a few pixels. + "//lib/fonts:robotofonts", + "//lib/fonts:material-icons", + ], +) + +# Convenience target that runs both the regular unit tests and the screenshot +# regression tests. Tagged manual so it is not picked up by `bazel test //...`; +# CI invokes it explicitly. +test_suite( + name = "web_test_runner_all", + tags = ["manual"], + tests = [ + ":web_test_runner", + ":web_test_runner_screenshots", ], )
diff --git a/polygerrit-ui/README.md b/polygerrit-ui/README.md index 8924d67..6baaa66 100644 --- a/polygerrit-ui/README.md +++ b/polygerrit-ui/README.md
@@ -2,16 +2,17 @@ Follow the [setup instructions for Gerrit backend developers](https://gerrit-review.googlesource.com/Documentation/dev-readme.html) -where applicable, the most important command is: +where applicable. The most important command is: ```sh git clone --recurse-submodules https://gerrit.googlesource.com/gerrit ``` -The --recurse-submodules option is needed on git clone to ensure that the core plugins, which are included as git submodules, are also cloned. +The `--recurse-submodules` option is needed on `git clone` to ensure that the +core plugins, which are included as git submodules, are also cloned. -Then make sure to install the commit-hook that will set up the `ChangeId` for -each push to gerrit-reviews. +Then make sure to install the commit hook that will set up the `Change-Id` for +each push to Gerrit: ```sh cd gerrit && ( @@ -24,113 +25,152 @@ Follow the instructions [here](https://gerrit-review.googlesource.com/Documentation/dev-bazel.html#_installation) -to get and install Bazel. The `npm install -g @bazel/bazelisk` method is -probably easiest since you will have npm as part of Nodejs. +to get and install Bazel. Using Bazelisk is usually the easiest option. -## Installing [Node.js](https://nodejs.org/en/download/) and npm packages +## Installing [Node.js](https://nodejs.org/en/download/) yarn and pnpm -At the time of writing (November 2023) you should use version 18 of nodejs. +Use a recent Node.js version that is supported by the repository. If in doubt, +use the version that is used in CI or documented in the root `package.json`. + +Examples: ```sh -# Debian experimental -sudo apt-get install nodejs -sudo apt-get install npm +# Debian / Ubuntu +sudo apt-get install nodejs npm -# OS X with Homebrew -brew install node@18 -brew install npm +# macOS with Homebrew +brew install node ``` All other platforms: -[download from nodejs.org](https://nodejs.org/en/download/). +[download from nodejs.org](https://nodejs.org/en/download/), or use [nvm - Node Version Manager](https://github.com/nvm-sh/nvm). -### Additional packages - -We have several bazel commands to install packages we may need for FE development. - -For first time users to get the local server up, `bazel build gerrit` should be enough and will take care of all of them for you. +Install Yarn: ```sh -# Install yarn package manager npm install -g yarn - -# Install packages from all packages.json files -yarn setup ``` -More information for installing and using nodejs rules can be found here https://bazelbuild.github.io/rules_nodejs/install.html - -### Upgrade to @bazel-scoped packages - -It might be necessary to run this command to upgrade to major `rules_nodejs` release: +Install pnpm: ```sh -yarn remove @bazel/... +npm install -g pnpm ``` -## Setup typescript support in the IDE +## Dependency Management (Yarn → pnpm Transition) -Modern IDEs should automatically handle typescript settings from the -`polygerrit-ui/app/tsconfig.json` files. The `tsc` compiler places compiled -files in the `.ts-out/pg` directory at the root of gerrit workspace and you can -configure the IDE to exclude the whole .ts-out directory. To do it in the -IntelliJ IDEA click on this directory and select "Mark Directory As > Excluded" -in the context menu. +- yarn.lock is authoritative +- pnpm-lock.yaml is generated via `pnpm import` +- Do NOT edit pnpm-lock.yaml manually -However, if you receive some errors from IDE, you can try to configure IDE +## Expected Bazel Behavior + +First run may fail with: + +pnpm-lock.yaml file updated. Please run your build again. + +Rerun the same command. + +## Workflow + +1. Edit package.json +2. Run: yarn install +3. Run: bazel build gerrit +4. If lock updated → rerun build + +## Why Yarn stays + +- gradual migration from `yarn` to `pnpm` +- stable dependency resolution +- avoids breaking workflows + +## DO NOT RUN + +```sh +pnpm install +``` + +It creates a different dependency graph. + +## Long-term + +Eventually: +- pnpm-lock.yaml becomes canonical +- yarn.lock removed + +## Setup TypeScript support in the IDE + +Modern IDEs should automatically handle TypeScript settings from +`polygerrit-ui/app/tsconfig.json`. The `tsc` compiler places compiled files in +the `.ts-out/pg` directory at the root of the Gerrit workspace, and you can +configure the IDE to exclude the whole `.ts-out` directory. In IntelliJ IDEA, +right-click the directory and select **Mark Directory As > Excluded**. + +If your IDE still reports errors, then you can try configuring TypeScript manually. For example, if IntelliJ IDEA shows -`Cannot find parent 'tsconfig.json'` error, you can try to setup typescript -options `--project polygerrit-ui/app/tsconfig.json` in the IDE settings. +`Cannot find parent 'tsconfig.json'`, then set the TypeScript options to: +```sh +--project polygerrit-ui/app/tsconfig.json +``` ## Developing locally The preferred method for development is to serve the web files locally using the -Web Dev Server and then view a running gerrit instance (local or otherwise) to +Web Dev Server and then view a running Gerrit instance (local or otherwise) and replace its web client with the local one using the Gerrit FE Dev Helper extension. ### Web Dev Server The [Web Dev Server](https://modern-web.dev/docs/dev-server/overview/) serves -the compiled web files and dependencies unbundled over localhost. Start it using -this command: +the compiled web files and dependencies unbundled over localhost. Start it with: ```sh -yarn start +pnpm start ``` -To inject plugins or other files, we use the [Gerrit FE Dev Helper](https://chrome.google.com/webstore/detail/gerrit-fe-dev-helper/jimgomcnodkialnpmienbomamgomglkd) Chrome extension. +To inject plugins or other files, we use the +[Gerrit FE Dev Helper](https://chrome.google.com/webstore/detail/gerrit-fe-dev-helper/jimgomcnodkialnpmienbomamgomglkd) +Chrome extension. -If any issues occured, please refer to the Troubleshooting section at the bottom or contact the team! - +If you run into problems, refer to the troubleshooting section below or contact +the team. ### Chrome extension: Gerrit FE Dev Helper -To be able to bypass the auth and also help improve the productivity of Gerrit FE developers, -we created this chrome extension: [Gerrit FE Dev Helper](https://chrome.google.com/webstore/detail/gerrit-fe-dev-helper/jimgomcnodkialnpmienbomamgomglkd). +To help frontend development, including bypassing auth in local dev workflows, +we created this Chrome extension: +[Gerrit FE Dev Helper](https://chrome.google.com/webstore/detail/gerrit-fe-dev-helper/jimgomcnodkialnpmienbomamgomglkd). -It basically works as a proxy that will block / redirect requests from current sites to any given url base on certain rules. +It basically works as a proxy that blocks or redirects requests from the current +site to a configured URL according to rules. -The source code is in [Gerrit - gerrit-fe-dev-helper](https://gerrit-review.googlesource.com/q/project:gerrit-fe-dev-helper), contributions are welcomed! +The source code is in +[Gerrit - gerrit-fe-dev-helper](https://gerrit-review.googlesource.com/q/project:gerrit-fe-dev-helper), +and contributions are welcome. -To use this extension, just follow its [readme here](https://gerrit.googlesource.com/gerrit-fe-dev-helper/+/master/README.md). +To use this extension, follow its +[README](https://gerrit.googlesource.com/gerrit-fe-dev-helper/+/master/README.md). ### Running locally against a Gerrit test site Set up a local test site once: 1. [Build Gerrit](https://gerrit-review.googlesource.com/Documentation/dev-bazel.html#_gerrit_development_war_file) -2. [Set up a local test site](https://gerrit-review.googlesource.com/Documentation/dev-readme.html#init). -3. Optionally [populate](https://gerrit.googlesource.com/gerrit/+/master/contrib/populate-fixture-data.py) your test site with some test data. +2. [Set up a local test site](https://gerrit-review.googlesource.com/Documentation/dev-readme.html#init) +3. Optionally [populate](https://gerrit.googlesource.com/gerrit/+/master/contrib/populate-fixture-data.py) + your test site with test data For running a locally built Gerrit war against your test instance use [this command](https://gerrit-review.googlesource.com/Documentation/dev-readme.html#run_daemon). -If you want to serve the Lit frontend directly from the sources in `polygerrit_ui/app/` instead of from the war: -1. Start [Web Dev Server](#web-dev-server) +If you want to serve the Lit frontend directly from the sources in +`polygerrit-ui/app/` instead of from the war: + +1. Start the [Web Dev Server](#web-dev-server) 2. Add the `--dev-cdn` option: ```sh @@ -142,316 +182,155 @@ --dev-cdn http://localhost:8081 ``` -The Web Dev Server is currently not serving fonts or other static assets. Follow -[Issue 40015119](https://issues.gerritcodereview.com/issues/40015119) for -fixing this issue. +The Web Dev Server currently does not serve fonts or some other static assets. +Follow +[Issue 40015119](https://issues.gerritcodereview.com/issues/40015119) +for updates. -*NOTE* You can use any other cdn here, for example: https://cdn.googlesource.com/polygerrit_ui/678.0 +*Note:* You can also use another CDN here, for example: +https://cdn.googlesource.com/polygerrit_ui/678.0 -## Running Tests +## Running tests For daily development you typically only want to run and debug individual tests. Our tests run using the -[Web Test Runner](https://modern-web.dev/docs/test-runner/overview/). There are -several ways to trigger tests: +[Web Test Runner](https://modern-web.dev/docs/test-runner/overview/). + +Common commands: * Run all tests once: + ```sh -yarn test +pnpm test ``` -* Run all tests and then watches for changes. Change a file will trigger all -tests affected by the changes. +* Run all tests in watch mode. Changing a file reruns affected tests: + ```sh -yarn test:watch +pnpm test:watch ``` -* Run all tests once under bazel: +* Run all tests once under Bazel: + ```sh ./polygerrit-ui/app/run_test.sh ``` -* Run a single test file and rerun on any changes affecting it: -``` -yarn test:single "**/gr-comment_test.ts" +* Run a single test file and rerun when affected files change: + +```sh +pnpm test:single "**/gr-comment_test.ts" ``` -### Screenshot Tests +### Screenshot tests We use screenshot tests to prevent unintended visual regressions. To run the screenshot tests: + ```sh -yarn test:screenshot +pnpm test:screenshot ``` -If a test fails, it means the component's appearance has changed. New screenshots will be generated in the `polygerrit-ui/screenshots/Chromium/failed/` directory. In case of a mismatch with an existing baseline, a diff image will also be created there. - -If the change is intended, you need to approve the new screenshots as the baseline. To do this, move the new screenshot files from the `failed` directory to the `baseline` directory, overwriting the old ones. The diff images in the `failed` directory can be deleted. - +Or via Bazel, which matches what CI runs and uses the Bazel-managed +dependency tree: ```sh -# Move all failed screenshots at once: -mv polygerrit-ui/screenshots/Chromium/failed/*.png polygerrit-ui/screenshots/Chromium/baseline/ +bazelisk test //polygerrit-ui:web_test_runner_screenshots ``` -After moving the file(s), run `yarn test:screenshot` again to confirm that they pass. +The `//polygerrit-ui:web_test_runner_all` target runs both the unit and +screenshot buckets in a single invocation. Both screenshot targets are +tagged `manual`, so they are never picked up by a bare `bazel test //...` +and must be named explicitly. -Compiling code: +If a test fails, then the component's appearance has changed. New screenshots +are generated in the `polygerrit-ui/screenshots/Chromium/failed/` directory. +If an existing baseline differs, then a diff image is also created there. + +If the change is intended, then approve the new screenshots as the baseline by +moving the new screenshot files from `failed` to `baseline`, overwriting the old +ones. The diff images in `failed` can be deleted. + ```sh -# Compile frontend once to check for type errors: -yarn compile +mv polygerrit-ui/screenshots/Chromium/failed/*.png \ + polygerrit-ui/screenshots/Chromium/baseline/ +``` -# Watch mode: -yarn compile:watch +After moving the file(s), run `pnpm test:screenshot` again to confirm they pass. + +### Compiling code + +```sh +# Compile frontend once to check for type errors +pnpm compile + +# Watch mode +pnpm compile:watch ``` ## Style guide -We follow the [Google JavaScript Style Guide](https://google.github.io/styleguide/javascriptguide.xml) -with a few exceptions. When in doubt, remain consistent with the code around you. +We follow the +[Google JavaScript Style Guide](https://google.github.io/styleguide/javascriptguide.xml) +with a few exceptions. When in doubt, remain consistent with the surrounding +code. -In addition, we encourage the use of [ESLint](http://eslint.org/). -It is available as a command line utility, as well as a plugin for most editors -and IDEs. +In addition, we encourage the use of [ESLint](http://eslint.org/). It is +available as a command-line utility as well as a plugin for most editors and +IDEs. -`eslint-config-google` is a port of the Google JS Style Guide to an ESLint -config module, and `eslint-plugin-html` allows ESLint to lint scripts inside -HTML. -We have an eslint-bazel.config.js config file in the polygerrit-ui/ directory configured -to enforce the preferred style of the PolyGerrit project. -After installing, you can use `eslint` on any new file you create. -In addition, you can supply the `--fix` flag to apply some suggested fixes for -simple style issues. -If you modify JS inside of `<script>` tags, like for test suites, you may have -to supply the `--ext .html` flag. +We have an ESLint flat config in `polygerrit-ui/app/eslint-bazel.config.js` +configured to enforce the preferred style of the PolyGerrit project. Some useful commands: -* To run ESLint on the whole app, less some dependency code: +* Run ESLint on the whole app: ```sh -yarn eslint +pnpm eslint ``` -* To run ESLint and apply changes on the whole app: +* Run ESLint and apply automatic fixes: ```sh -yarn eslintfix +pnpm eslintfix ``` -* To run ESLint on just the subdirectory you modified: +* Run ESLint on a specific subdirectory: ```sh -node_modules/eslint/bin/eslint.js --ext .html,.js polygerrit-ui/app/$YOUR_DIR_HERE +pnpm exec eslint --config polygerrit-ui/app/eslint-bazel.config.js \ + --ext .html,.js,.ts polygerrit-ui/app/$YOUR_DIR_HERE ``` -* To run the linter on all of your local changes: +* Run ESLint on all locally changed frontend files: ```sh -git diff --name-only HEAD | xargs node_modules/eslint/bin/eslint.js --ext .html,.js +git diff --name-only HEAD | xargs pnpm exec eslint \ + --config polygerrit-ui/app/eslint-bazel.config.js \ + --ext .html,.js,.ts ``` -## Migrating tests to Typescript - -You can use the following steps for migrating tests to Typescript: - -1. Rename the `_test.js` file to `_test.ts` -2. Remove `.js` extensions from all imports: - ``` - // Before: - import ... from 'x/y/z.js` - - // After - import .. from 'x/y/z' - ``` -3. Fix typescript and eslint errors. - -Common errors and fixes are: - -* An object in the test doesn't have all required properties. You can use -existing helpers to create an object with all required properties: -``` -// Before: -sinon.stub(element.restApiService, 'getPreferences').returns( - Promise.resolve({default_diff_view: 'UNIFIED'})); - -// After: -Promise.resolve({ - ...createPreferences(), - default_diff_view: DiffViewMode.UNIFIED, -}) -``` - -Some helpers receive parameters: -``` -// Before -element._change = { - change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca', - revisions: { - rev1: {_number: 1, commit: {parents: []}}, - rev2: {_number: 2, commit: {parents: []}}, - }, - current_revision: 'rev1', - status: ChangeStatus.MERGED, - labels: {}, - actions: {}, -}; - -// After -element._change = { - ...createChange(), - // The change_id is set by createChange. - // The exact change_id is not important in the test, so it was removed. - revisions: { - rev1: createRevision(1), // _number is a parameter here - rev2: createRevision(2), // _number is a parameter here - }, - current_revision: 'rev1' as CommitId, - status: ChangeStatus.MERGED, - labels: {}, - actions: {}, -}; -``` -* Typescript reports some weird messages about `window` property - sometimes an -IDE adds wrong import. Just remove it. -``` -// The wrong import added by IDE, must be removed -import window = Mocha.reporters.Base.window; -``` - -* `TS2531: Object is possibly 'null'`. To fix use either non-null assertion -operator `!` or nullish coalescing operator `?.`: -``` -// Before: -const rows = element - .shadowRoot.querySelector('table') - .querySelectorAll('tbody tr'); -... -// The _robotCommentThreads declared as _robotCommentThreads?: CommentThread -assert.equal(element._robotCommentThreads.length, 2); - -// Fix with non-null assertion operator: -const rows = element - .shadowRoot!.querySelector('table')! // '!' after shadowRoot and querySelector - .querySelectorAll('tbody tr'); - -assert.equal(element._robotCommentThreads!.length, 2); - -// Fix with nullish coalescing operator: - assert.equal(element._robotCommentThreads?.length, 2); -``` -Usually the fix with `!` is preferable, because it gives more clear error -when an intermediate property is `null/undefined`. If the _robotComments is -`undefined` in the example above, the `element._robotCommentThreads!.length` -crashes with the error `Cannot read property 'length' of undefined`. At the -same time the fix with -`?.` doesn't distinct between 2 cases: _robotCommentThreads is `undefined` -and `length` is `undefined`. - -* `TS2339: Property '...' does not exist on type 'Element'.` for elements -returned by `querySelector/querySelectorAll`. To fix it, use generic versions -of those methods: -``` -// Before: -const radios = parentTable - .querySelectorAll('input[type=radio]'); -const radio = parentRow - .querySelector('input[type=radio]'); - -// After: -const radios = parentTable - .querySelectorAll<HTMLInputElement>('input[type=radio]'); -const radio = parentRow - .querySelector<HTMLInputElement>('input[type=radio]'); -``` - -* Sinon: `TS2339: Property 'lastCall' does not exist on type '...` (the same -for other sinon properties). Store stub/spy in a variable and then use the -variable: -``` -// Before: -const navService = testResolver(navigationToken); -sinon.stub(navService, 'setUrl'); -... -assert.equal(navService.setUrl.lastCall.firstArg, '/c/123'); - -// After: -const navService = testResolver(navigationToken); -const setUrlStub = sinon.stub(navService, 'setUrl'); -... -assert.equal(setUrlStub.lastCall.firstArg, '/c/123'); -``` - -If you need to define a type for such variable, you can use one of the following -options: -``` -suite('my suite', () => { - // Non static members, option 1 - let updateHeightSpy: SinonSpyMember<typeof element._updateRelatedChangeMaxHeight>; - // Non static members, option 2 - let updateHeightSpy_prototype: SinonSpyMember<typeof GrChangeView.prototype._updateRelatedChangeMaxHeight>; - // Static members - let setUrlStub: SinonStubbedMember<NavigationService['setUrl']>; - // For interfaces - let getMergeableStub: SinonStubbedMember<RestApiService['getMergeable']>; -}); -``` - -* Typescript reports errors when stubbing/faking methods: -``` -// The JS code: -const reloadStub = sinon - .stub(element, '_reload') - .callsFake(() => Promise.resolve()); - -stubRestApi('getDiffComments').returns(Promise.resolve({})); -stubRestApi('getDiffRobotComments').returns(Promise.resolve({})); -stubRestApi('getDiffDrafts').returns(Promise.resolve({})); -stubRestApi('_fetchSharedCacheURL').returns(Promise.resolve({})); -``` - -In such cases, validate the input and output of a stub/fake method. Quite often -tests return null instead of undefined or `[]` instead of `{}`, etc... -Fix types if they are not correct: -``` -const reloadStub = sinon - .stub(element, '_reload') - // GrChangeView._reload method returns an array - .callsFake(() => Promise.resolve([])); // return [] here - - ... - // Fix return type: - stubRestApi('_fetchSharedCacheURL').returns(Promise.resolve({} as ParsedJSON)); -}); -``` - -* If a test requires a `@types/...` library, install the required library -in the `polygerrit_ui/node_modules` and update the `typeRoots` in the -`polygerrit-ui/app/tsconfig_bazel_test.json` file. - -The same update should be done if a test requires a .d.ts file from a library -that already exists in `polygerrit_ui/node_modules`. - -**Note:** Types from a library located in `polygerrit_ui/app/node_modules` are -handle automatically. - -* If a test imports a library from `polygerrit_ui/node_modules` - update -`paths` in `polygerrit-ui/app/tsconfig_bazel_test.json`. +When running ESLint manually, invoke it from the repository root so that +repo-root-relative paths in the flat config resolve correctly. ## Contributing -Our users report bugs / feature requests related to the UI through the Gerrit -Tracker on the [WebFrontend](https://issues.gerritcodereview.com/issues?q=componentid:1369968) +Our users report bugs and feature requests related to the UI through the Gerrit +Tracker in the +[WebFrontend](https://issues.gerritcodereview.com/issues?q=componentid:1369968) component. -If you want to help, feel free to grab one from those `New` issues without -assignees and send us a change. +If you want to help, feel free to pick one of the `New` issues without assignees +and send us a change. -If you don't know who to assign to review your code change, you can use -this special account: `gerrit-fe-reviewers@api-project-164060093628.iam.gserviceaccount.com` -and just assign to that account, it will automatically pick two volunteers -from the queue we have for FE reviewers. +If you don't know who to assign as reviewer for your change, then you can use +this special account: -If you are willing to join the queue and help the community review changes, -you can create an issue through Monorail and request to join the queue! -We will review your request and start from there. +`gerrit-fe-reviewers@api-project-164060093628.iam.gserviceaccount.com` + +Assigning that account automatically picks two volunteers from the frontend +reviewer queue. + +If you are willing to join that queue and help review community changes, then +create an issue through Gerrit issue tracker and ask to join.
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD index 4d55ddc4..f2ae231 100644 --- a/polygerrit-ui/app/BUILD +++ b/polygerrit-ui/app/BUILD
@@ -1,8 +1,11 @@ -load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_test") -load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@aspect_rules_ts//ts:defs.bzl", "ts_config", "ts_project") +load("@npm//:lit-analyzer/package_json.bzl", lit_analyzer_bin = "bin") +load("@ui_npm//:defs.bzl", "npm_link_all_packages") load("//tools/js:eslint.bzl", "eslint") load(":rules.bzl", "polygerrit_bundle") +npm_link_all_packages(name = "node_modules") + package(default_visibility = ["//visibility:public"]) # This list must be in sync with the "include" list in the follwoing files: @@ -46,10 +49,9 @@ allow_js = True, incremental = True, out_dir = "_pg_ts_out", - tsc = "//tools/node_tools:tsc-bin", tsconfig = ":ts_config_bazel", deps = [ - "@ui_npm//:node_modules", + ":node_modules", ], ) @@ -80,11 +82,10 @@ allow_js = True, incremental = True, out_dir = "_pg_with_tests_out", - tsc = "//tools/node_tools:tsc-bin", tsconfig = ":ts_config_bazel_test", deps = [ - "@ui_dev_npm//:node_modules", - "@ui_npm//:node_modules", + ":node_modules", + "//polygerrit-ui:node_modules", ], ) @@ -109,8 +110,8 @@ "node_modules_licenses/**", ], ) + [ - "@ui_dev_npm//:node_modules", - "@ui_npm//:node_modules", + ":node_modules", + "//polygerrit-ui:node_modules", ], ) @@ -133,9 +134,9 @@ name = "test-srcs-fg", srcs = [ "rollup.config.js", + ":node_modules", ":pg_code", - "@ui_dev_npm//:node_modules", - "@ui_npm//:node_modules", + "//polygerrit-ui:node_modules", ], ) @@ -153,7 +154,7 @@ "tsconfig_eslint.json", # tsconfig_eslint.json extends tsconfig.json, pass it as a dependency "tsconfig.json", - "@npm//typescript", + "//:node_modules/typescript", ], extensions = [ ".html", @@ -161,17 +162,17 @@ ".ts", ], plugins = [ - "@npm//@typescript-eslint/eslint-plugin", - "@npm//eslint-config-google", - "@npm//eslint-plugin-html", - "@npm//eslint-plugin-import", - "@npm//eslint-plugin-jsdoc", - "@npm//eslint-plugin-lit", - "@npm//eslint-plugin-n", - "@npm//eslint-plugin-prettier", - "@npm//eslint-plugin-regex", - "@npm//eslint-plugin-es-x", - "@npm//gts", + "//:node_modules/@typescript-eslint/eslint-plugin", + "//:node_modules/eslint-config-google", + "//:node_modules/eslint-plugin-html", + "//:node_modules/eslint-plugin-import", + "//:node_modules/eslint-plugin-jsdoc", + "//:node_modules/eslint-plugin-lit", + "//:node_modules/eslint-plugin-n", + "//:node_modules/eslint-plugin-prettier", + "//:node_modules/eslint-plugin-regex", + "//:node_modules/eslint-plugin-es-x", + "//:node_modules/gts", ], ) @@ -184,47 +185,47 @@ "**/*_test.ts", ], ) + [ - "@npm//typescript", - "@ui_dev_npm//:node_modules", - "@ui_npm//:node_modules", + "//:node_modules/typescript", + "//polygerrit-ui:node_modules", ], ) -nodejs_test( +lit_analyzer_bin.lit_analyzer_test( name = "lit_analysis", + args = [ + "**/elements/**/*.ts", + "--strict", + "--rules.no-property-visibility-mismatch", + "off", + "--rules.no-incompatible-property-type", + "off", + "--rules.no-incompatible-type-binding", + "off", + "--rules.no-unknown-attribute", + "off", + "--rules.no-unknown-tag-name", + "off", + ], + chdir = package_name(), data = [ ":lit_analysis_src_code", - "@npm//lit-analyzer", ], - entry_point = "@npm//:node_modules/lit-analyzer/cli.js", tags = [ "local", "manual", ], - templated_args = [ - "**/elements/**/*.ts", - "--strict", - "--rules.no-property-visibility-mismatch off", - "--rules.no-incompatible-property-type off", - "--rules.no-incompatible-type-binding off", - # TODO: We would actually like to change this to `error`, but we also - # want to allow certain attributes, for example `aria-description`. This - # would be possible, if we would run the lit-analyzer as a ts plugin. - # In tsconfig.json there is an option `globalAttributes` that we could - # use. But that is not available when running lit-analyzer as cli. - "--rules.no-unknown-attribute off", - # TODO: enable once we migrate from iron elements to material-web - "--rules.no-unknown-tag-name off", - ], ) -# app code including tests and tsconfig.json +# app code including tests, tsconfig.json, and the CSS sheets the +# Web Test Runner HTML loads (main.css, fonts.css, material-icons.css +# under styles/). filegroup( name = "web-test-runner_app-sources", srcs = glob( [ "**/*.ts", "**/*.js", + "**/*.css", "**/tsconfig.json", ], exclude = ["node_modules/**/*"],
diff --git a/polygerrit-ui/app/api/BUILD_for_publishing_api_only b/polygerrit-ui/app/api/BUILD_for_publishing_api_only index c1bb6bd..a917916 100644 --- a/polygerrit-ui/app/api/BUILD_for_publishing_api_only +++ b/polygerrit-ui/app/api/BUILD_for_publishing_api_only
@@ -11,8 +11,8 @@ # are not visible anymore to the parent BUILD. And if ts_projects depend on each # other, then the api/ files would have to be imported with their full package # names. -load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm") -load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@aspect_rules_js//npm:defs.bzl", "npm_package") +load("@aspect_rules_ts//ts:defs.bzl", "ts_config", "ts_project") filegroup( name = "js_plugin_api_srcs", @@ -21,9 +21,9 @@ ts_config( name = "ts_config", - src = "tsconfig.json", + src = "tsconfig.publish.json", deps = [ - "//plugins:tsconfig-plugins-base.json", + ":tsconfig-plugins-base.json", ], ) @@ -31,24 +31,24 @@ name = "js_plugin_api_compiled", srcs = glob(["**/*.ts"]), incremental = True, - tsc = "//tools/node_tools:tsc-bin", tsconfig = ":ts_config", ) # Use this rule for publishing the js plugin api as a package to the npm repo. -pkg_npm( +npm_package( name = "js_plugin_api_npm_package", - package_name = "@gerritcodereview/typescript-api", + package = "@gerritcodereview/typescript-api", + publishable = True, srcs = glob( ["**/*"], exclude = [ "BUILD", "BUILD_for_publishing_api_only", "tsconfig.json", + "tsconfig.publish.json", "publish.sh", ], - ), - deps = [ + ) + [ ":js_plugin_api_compiled", "//plugins:tsconfig-plugins-base.json", ],
diff --git a/polygerrit-ui/app/api/ai-code-review.ts b/polygerrit-ui/app/api/ai-code-review.ts index eeac166..edf2711 100644 --- a/polygerrit-ui/app/api/ai-code-review.ts +++ b/polygerrit-ui/app/api/ai-code-review.ts
@@ -70,6 +70,11 @@ group_display_text?: string; external_contexts?: ContextItem[]; custom_action_source?: CustomActionSource; + + /** + * Optional URL pointing to the definition of this capability. + */ + capability_definition_url?: string; } export declare interface ChatRequest {
diff --git a/polygerrit-ui/app/api/annotation.ts b/polygerrit-ui/app/api/annotation.ts index 7cf200f..38c8141 100644 --- a/polygerrit-ui/app/api/annotation.ts +++ b/polygerrit-ui/app/api/annotation.ts
@@ -5,6 +5,7 @@ */ import { CoverageRange, + DiffLayer, FileRange, GrDiff, TokenHighlightEventDetails, @@ -39,6 +40,11 @@ highlight?: TokenHighlightEventDetails ) => void; +/** + * Factory function to create a DiffLayer. + */ +export type DiffLayerFactory = (details: DiffDetails) => DiffLayer; + export declare interface AnnotationPluginApi { /** * The specified function will be called when a gr-diff component is built, @@ -56,8 +62,11 @@ * * The callback receives details of the diff itself and of the highlighted * token. - * - * TODO: Replace with a more general addDiffLayer() endpoint. */ addTokenHoverListener(callback: TokenHoverListener): void; + + /** + * Register a factory that creates a DiffLayer for each diff view. + */ + addDiffLayer(factory: DiffLayerFactory): void; }
diff --git a/polygerrit-ui/app/api/diff.ts b/polygerrit-ui/app/api/diff.ts index d881426..45ca607 100644 --- a/polygerrit-ui/app/api/diff.ts +++ b/polygerrit-ui/app/api/diff.ts
@@ -226,6 +226,7 @@ // Hides the FILE and LOST diff rows. Default is TRUE. show_file_comment_button?: boolean; line_wrapping?: boolean; + responsive_mode?: DiffResponsiveMode; } /** @@ -384,11 +385,6 @@ path?: string; } -// TODO: Currently unused and not fired. -export declare interface RenderProgressEventDetail { - linesRendered: number; -} - /** * The detail of the 'copy-info' event dispatched by gr-diff. */
diff --git a/polygerrit-ui/app/api/flows.ts b/polygerrit-ui/app/api/flows.ts index 09d4e4c..150bfe4 100644 --- a/polygerrit-ui/app/api/flows.ts +++ b/polygerrit-ui/app/api/flows.ts
@@ -41,6 +41,16 @@ getCustomConditions(change: ChangeInfo): Promise<FlowCustomConditionInfo[]>; /** + * Returns a list of disabled custom conditions. + */ + getDisabledConditions?(): string[]; + + /** + * Returns a list of disabled actions. + */ + getDisabledActions?(): string[]; + + /** * Returns a string containing a link to the feature documentation. */ getDocumentation(): string;
diff --git a/polygerrit-ui/app/api/publish.sh b/polygerrit-ui/app/api/publish.sh index f930fb3..8903490 100755 --- a/polygerrit-ui/app/api/publish.sh +++ b/polygerrit-ui/app/api/publish.sh
@@ -30,7 +30,9 @@ if [ "$1" == "--pack" ]; then echo 'Creating npm package gerritcodereview-typescript-api-<version>.tgz' - ${bazel_bin} run //${api_path}:js_plugin_api_npm_package.pack + package_dir="$(${bazel_bin} info bazel-bin)/${api_path}/js_plugin_api_npm_package" + tgz="$(npm pack "${package_dir}" --pack-destination "${PWD}" | tail -n1)" + echo "Created ${PWD}/${tgz}" fi if [ "$1" == "--upload" ]; then
diff --git a/polygerrit-ui/app/api/rest-api.ts b/polygerrit-ui/app/api/rest-api.ts index dd01069..c0c20b7 100644 --- a/polygerrit-ui/app/api/rest-api.ts +++ b/polygerrit-ui/app/api/rest-api.ts
@@ -553,6 +553,7 @@ author?: AccountInfo; tag?: string; unresolved?: boolean; + is_ai?: boolean; change_message_id?: string; commit_id?: string; context_lines?: ContextLine[]; @@ -773,6 +774,7 @@ new_mode?: number; old_sha?: string; new_sha?: string; + diffs_too_expensive_to_compute?: boolean; } /** @@ -1323,6 +1325,7 @@ FAIL = 'FAIL', ERROR = 'ERROR', NOT_EVALUATED = 'NOT_EVALUATED', + TIMEOUT = 'TIMEOUT', } /** @@ -1336,6 +1339,7 @@ NOT_APPLICABLE = 'NOT_APPLICABLE', ERROR = 'ERROR', FORCED = 'FORCED', + TIMEOUT = 'TIMEOUT', } export type UrlEncodedRepoName = BrandType<string, '_urlEncodedRepoName'>;
diff --git a/polygerrit-ui/app/api/styles.ts b/polygerrit-ui/app/api/styles.ts index 764cc48..cc5924c 100644 --- a/polygerrit-ui/app/api/styles.ts +++ b/polygerrit-ui/app/api/styles.ts
@@ -32,6 +32,7 @@ subPage: Style; table: Style; modal: Style; + changeViewIntegration: Style; } /** Accessible via `window.Gerrit.install(plugin => {plugin.styleApi()})`. */
diff --git a/polygerrit-ui/app/api/tsconfig.publish.json b/polygerrit-ui/app/api/tsconfig.publish.json new file mode 100644 index 0000000..4402487 --- /dev/null +++ b/polygerrit-ui/app/api/tsconfig.publish.json
@@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig-plugins-base.json", + "include": ["./**/*.ts"] +}
diff --git a/polygerrit-ui/app/constants/constants.ts b/polygerrit-ui/app/constants/constants.ts index f51032c..fcc1b32 100644 --- a/polygerrit-ui/app/constants/constants.ts +++ b/polygerrit-ui/app/constants/constants.ts
@@ -283,6 +283,7 @@ ignore_whitespace: 'IGNORE_NONE', line_length: 100, line_wrapping: false, + responsive_mode: 'NONE', show_line_endings: true, show_tabs: true, show_whitespace_errors: true,
diff --git a/polygerrit-ui/app/constants/reporting.ts b/polygerrit-ui/app/constants/reporting.ts index 6d7eced..32e77ae 100644 --- a/polygerrit-ui/app/constants/reporting.ts +++ b/polygerrit-ui/app/constants/reporting.ts
@@ -103,6 +103,8 @@ COPY_TO_CLIPBOARD = 'CopyToClipboard', // Time to autocomplete a comment COMMENT_COMPLETION = 'CommentCompletion', + // Time for AI chat requests to complete + AI_CHAT_REQUEST = 'AiChatRequest', } export enum Interaction { @@ -173,22 +175,57 @@ // AI agent suggests comments/fixes to user. AI_AGENT_SUGGESTIONS_SHOWN = 'ai-agent-suggestions-shown', // AI agent suggestions are promoted to a draft comment by user. + // (Also includes when a user clicks "Please Fix" on checks comment). AI_AGENT_SUGGESTION_TO_COMMENT = 'ai-agent-suggestion-to-comment', + // AI agent suggestion copy interactions. + AI_AGENT_SUGGESTION_COPY_BUTTON_CLICKED = 'ai-agent-suggestion-copy-button-clicked', + AI_AGENT_SUGGESTION_CONTENT_COPIED = 'ai-agent-suggestion-content-copied', + // AI agent "Get AI Fix" button is clicked by user. + AI_AGENT_GET_FIX_CLICKED = 'ai-agent-get-fix-clicked', FLOWS_TAB_RENDERED = 'flows-tab-rendered', CREATE_FLOW_DIALOG_OPENED = 'create-flow-dialog-opened', FLOW_CREATED = 'flow-created', + // AI Chat interaction request failures + AI_CHAT_FAILURE = 'ai-chat-failure', } /** * EventDetails to be passed to the reportInteraction method for AI agent - * interactions. + * chat interactions. */ -export type AiAgentEventDetails = { +export type AiAgentChatEventDetails = { agentId: string; conversationId: string; // Each agent response in a conversation is a turn. turnIndex: number; // commentCount is 0 if agent ran but didn't suggest any comments/fixes. commentCount?: number; + // The response part ID which identifies the specific suggestion/comment in a turn. + suggestionId?: number; + // Unique ID of the saved comment draft promoted from the suggestion. + commentId?: string; }; + +/** + * EventDetails to be passed to the reportInteraction method for AI agent + * check interactions. + */ +export type AiAgentCheckEventDetails = { + // The name of the check that was run. + checkName: string; + // The description from the check + checkDescription: string; + // The raw external id for the run result + externalId: string; + // Unique ID of the saved comment draft promoted from the suggestion. + commentId?: string; +}; + +/** + * EventDetails to be passed to the reportInteraction method for AI agent + * interactions. + */ +export type AiAgentEventDetails = + | AiAgentChatEventDetails + | AiAgentCheckEventDetails;
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts index 59e0b40..5ef6959 100644 --- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts +++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.ts
@@ -57,10 +57,10 @@ repoName?: RepoName; // private but used in test - @state() branch = '' as BranchName; + @property({type: String}) branch = '' as BranchName; // private but used in test - @state() subject = ''; + @property({type: String}) subject = ''; // private but used in test @state() topic?: string;
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts index 2632ff5..afd1849 100644 --- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts +++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
@@ -96,7 +96,7 @@ @state() local?: EditableLocalAccessSectionInfo; // private but used in test - @state() editing = false; + @property({type: Boolean}) editing = false; // private but used in test @state() modified = false;
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts index 38f76de..9845490 100644 --- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts +++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
@@ -59,6 +59,8 @@ @property({type: Object}) params?: RepoViewState; + @property() newItemName = false; + @state() detailType?: RepoDetailView.BRANCHES | RepoDetailView.TAGS; @state() isOwner = false; @@ -79,8 +81,6 @@ @state() refName?: GitRef; - @state() newItemName = false; - @state() isEditing = false; @state() revisedRef?: GitRef;
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts index fbd5dc1..f914b55 100644 --- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts +++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
@@ -101,6 +101,9 @@ sectionName?: string; @property({type: Boolean}) + starsLoading = false; + + @property({type: Boolean}) showNumber = false; @property({type: String}) @@ -376,7 +379,10 @@ return html` <td class="cell star"> - <gr-change-star .change=${this.change}></gr-change-star> + <gr-change-star + .change=${this.change} + .loading=${this.starsLoading} + ></gr-change-star> </td> `; }
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section.ts index 0e9cf7b..d3731c9 100644 --- a/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section.ts +++ b/polygerrit-ui/app/elements/change-list/gr-change-list-section/gr-change-list-section.ts
@@ -77,6 +77,9 @@ @property({type: Boolean}) isCursorMoving = false; + @property({type: Boolean}) + starsLoading = false; + /** * The logged-in user's account, or an empty object if no user is logged * in. @@ -371,6 +374,7 @@ .selected=${selected} .change=${change} .sectionName=${this.changeSection.name} + .starsLoading=${this.starsLoading} .visibleChangeTableColumns=${columns} .showNumber=${!!this.showNumber} .usp=${this.usp}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts index 933d933..503d478 100644 --- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts +++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
@@ -17,7 +17,7 @@ import {getAppContext} from '../../../services/app-context'; import {sharedStyles} from '../../../styles/shared-styles'; import {css, html, LitElement, nothing, PropertyValues} from 'lit'; -import {customElement, query, state} from 'lit/decorators.js'; +import {customElement, property, query, state} from 'lit/decorators.js'; import { createSearchUrl, searchViewModelToken, @@ -49,7 +49,7 @@ @state() changesPerPage?: number; // private but used in test - @state() query = ''; + @property({type: String}) query = ''; // private but used in test @state() offset = 0;
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts index 061bd3d..1ce9a3b 100644 --- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts +++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
@@ -32,7 +32,10 @@ import {customElement, property, state} from 'lit/decorators.js'; import {Shortcut, ShortcutController} from '../../lit/shortcut-controller'; import {queryAll} from '../../../utils/common-util'; -import {GrChangeListSection} from '../gr-change-list-section/gr-change-list-section'; +import { + computeLabelShortcut, + GrChangeListSection, +} from '../gr-change-list-section/gr-change-list-section'; import {ValueChangedEvent} from '../../../types/events'; import {resolve} from '../../../models/dependency'; import {createChangeUrl} from '../../../models/views/change'; @@ -115,9 +118,15 @@ @property({type: Boolean}) showNumber?: boolean; // No default value to prevent flickering. + @property({type: Array}) + labelFilter: string[] = []; // empty = show all + @property({type: Boolean}) showReviewedState = false; + @property({type: Boolean}) + starsLoading = false; + @property({type: Array}) changeTableColumns?: string[]; @@ -277,6 +286,7 @@ .isCursorMoving=${this.isCursorMoving} .loggedInUser=${this.loggedInUser} .dashboardUser=${this.dashboardUser} + .starsLoading=${this.starsLoading} .selectedIndex=${computeRelativeIndex( this.selectedIndex, sectionIndex, @@ -359,26 +369,42 @@ this.visibleChangeTableColumns = Object.values(ColumnNames).filter(col => prefColumns.includes(col) ); + this.labelFilter = this.preferences.label_filter + ? this.preferences.label_filter + .split(',') + .map(s => s.trim()) + .filter(s => s.length > 0) + : []; } } - // private but used in test computeLabelNames(sections: ChangeListSection[]) { if (!sections) return []; + + let allLabels: string[]; if (this.config?.submit_requirement_dashboard_columns?.length) { - return this.config?.submit_requirement_dashboard_columns; - } - const changes = sections.map(section => section.results).flat(); - let labels: string[] = []; - if (this.config?.dashboard_show_all_labels) { - labels = changes.map(change => Object.keys(change.labels ?? {})).flat(); + allLabels = this.config.submit_requirement_dashboard_columns; } else { - labels = changes - .map(change => getRequirements(change)) - .flat() - .map(requirement => requirement.name); + const changes = sections.map(section => section.results).flat(); + let labels: string[] = []; + if (this.config?.dashboard_show_all_labels) { + labels = changes.map(change => Object.keys(change.labels ?? {})).flat(); + } else { + labels = changes + .map(change => getRequirements(change)) + .flat() + .map(requirement => requirement.name); + } + allLabels = orderSubmitRequirementNames(labels.filter(unique)); } - return orderSubmitRequirementNames(labels.filter(unique)); + + if (this.labelFilter.length === 0) return allLabels; + const normalizedFilter = this.labelFilter.map(f => f.toLowerCase()); + return allLabels.filter( + l => + normalizedFilter.includes(l.toLowerCase()) || + normalizedFilter.includes(computeLabelShortcut(l).toLowerCase()) + ); } private changesChanged() {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts index 477e75b..55a954f 100644 --- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts +++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts
@@ -22,6 +22,7 @@ } from '../../../test/test-utils'; import {Key} from '../../../utils/dom-util'; import { + ColumnNames, createDefaultPreferences, TimeFormat, } from '../../../constants/constants'; @@ -622,4 +623,115 @@ assert.isNotOk(query<HTMLElement>(element, '.bad')); }); + + test('show all labels when no label filter set', () => { + element.labelFilter = []; + element.config = createServerInfo(); + const sections: ChangeListSection[] = [ + { + results: [ + { + ...createChange(), + _number: 0 as NumericChangeId, + submit_requirements: [ + { + ...createSubmitRequirementResultInfo(), + name: 'Code-Review', + }, + { + ...createSubmitRequirementResultInfo(), + name: 'Verified', + }, + ], + }, + ], + }, + ]; + assert.deepEqual(element.computeLabelNames(sections), [ + 'Code-Review', + 'Verified', + ]); + }); + + test('show only filtered labels when label filter set', () => { + element.labelFilter = ['Code-Review']; + element.config = createServerInfo(); + const sections: ChangeListSection[] = [ + { + results: [ + { + ...createChange(), + _number: 0 as NumericChangeId, + submit_requirements: [ + { + ...createSubmitRequirementResultInfo(), + name: 'Code-Review', + }, + { + ...createSubmitRequirementResultInfo(), + name: 'Verified', + }, + ], + }, + ], + }, + ]; + assert.deepEqual(element.computeLabelNames(sections), ['Code-Review']); + }); + test('update labels shown according to user preference', async () => { + element.loggedInUser = {_account_id: 1001 as AccountId}; + element.config = createServerInfo(); + + // No label filter — show all + userModel.setPreferences({ + ...createDefaultPreferences(), + change_table: Object.values(ColumnNames), + }); + await element.updateComplete; + assert.deepEqual(element.labelFilter, []); + + // With label filter — show only specified labels + userModel.setPreferences({ + ...createDefaultPreferences(), + change_table: Object.values(ColumnNames), + label_filter: 'Code-Review,Verified', + }); + await element.updateComplete; + assert.deepEqual(element.labelFilter, ['Code-Review', 'Verified']); + + // Empty label filter — show all again + userModel.setPreferences({ + ...createDefaultPreferences(), + change_table: Object.values(ColumnNames), + label_filter: '', + }); + await element.updateComplete; + assert.deepEqual(element.labelFilter, []); + }); + test('label filter is case insensitive', () => { + element.labelFilter = ['code-review']; + element.config = createServerInfo(); + const sections: ChangeListSection[] = [ + { + results: [ + { + ...createChange(), + _number: 0 as NumericChangeId, + submit_requirements: [ + { + ...createSubmitRequirementResultInfo(), + name: 'Code-Review', + }, + { + ...createSubmitRequirementResultInfo(), + name: 'Verified', + }, + ], + }, + ], + }, + ]; + // 'code-review' matches 'Code-Review' case-insensitively + assert.deepEqual(element.computeLabelNames(sections), ['Code-Review']); + }); });
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts index 8a41a2d..db1d081 100644 --- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts +++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.ts
@@ -12,12 +12,17 @@ import '../gr-user-header/gr-user-header'; import '../../core/gr-notifications-prompt/gr-notifications-prompt'; import {getAppContext} from '../../../services/app-context'; -import {changeIsOpen} from '../../../utils/change-util'; +import { + changeIsOpen, + listChangesOptionsToHex, +} from '../../../utils/change-util'; +import {KnownExperimentId} from '../../../services/flags/flags'; import {parseDate} from '../../../utils/date-util'; import { AccountDetailInfo, ChangeInfo, DashboardId, + ListChangesOption, PreferencesInput, RepoName, UserId, @@ -104,6 +109,9 @@ // private but used in test @state() showNotificationsPrompt = false; + // private but used in test + @state() starsLoading = false; + private reporting = getAppContext().reportingService; private readonly restApiService = getAppContext().restApiService; @@ -150,6 +158,17 @@ this.shortcuts.addAbstract(Shortcut.UP_TO_DASHBOARD, () => this.reload()); } + override connectedCallback() { + super.connectedCallback(); + if ( + getAppContext().flagsService.isEnabled( + KnownExperimentId.DASHBOARD_LAZY_LOADING + ) + ) { + this.starsLoading = true; + } + } + static override get styles() { return [ a11yStyles, @@ -266,6 +285,7 @@ .preferences=${this.preferences} .sections=${this.results} .usp=${'dashboard'} + .starsLoading=${this.starsLoading} @toggle-star=${(e: CustomEvent<ChangeStarToggleStarDetail>) => { this.handleToggleStar(e); }} @@ -366,6 +386,13 @@ this.reporting.time(Timing.DASHBOARD_DISPLAYED); } this.firstTimeLoad = false; + if ( + getAppContext().flagsService.isEnabled( + KnownExperimentId.DASHBOARD_LAZY_LOADING + ) + ) { + this.starsLoading = true; + } const {project, type, dashboard, title, user, sections} = this.viewState; @@ -467,6 +494,50 @@ changelistSection.name === YOUR_TURN.name && changelistSection.results.length > 0 ).length !== 0; + + if ( + getAppContext().flagsService.isEnabled( + KnownExperimentId.DASHBOARD_LAZY_LOADING + ) + ) { + this.makeSecondRequestForStarredChanges(queries); + } + } + + private makeSecondRequestForStarredChanges(queries: string[]) { + this.starsLoading = true; + const secondRequestOptions = listChangesOptionsToHex( + ListChangesOption.STAR + ); + this.restApiService + .getChangesForDashboard( + undefined, + queries, + undefined, + secondRequestOptions + ) + .then(results => { + if (!results) { + console.error('loading star information failed'); + this.starsLoading = false; + return; + } + this.results = this.results?.map((section, i) => { + return { + ...section, + results: section.results.map((change, j) => { + return { + ...change, + starred: + results[i] && results[i][j] + ? results[i][j].starred + : change.starred, + }; + }), + }; + }); + this.starsLoading = false; + }); } /**
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts index 9a027a0..54b0f63 100644 --- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts +++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.ts
@@ -9,6 +9,7 @@ import {GrDashboardView} from './gr-dashboard-view'; import {GerritView} from '../../../services/router/router-model'; import {changeIsOpen} from '../../../utils/change-util'; +import {KnownExperimentId} from '../../../services/flags/flags'; import {ChangeStatus} from '../../../constants/constants'; import { createAccountDetailWithId, @@ -26,6 +27,7 @@ waitUntil, } from '../../../test/test-utils'; import { + ChangeInfo, ChangeInfoId, DashboardId, EmailAddress, @@ -159,6 +161,72 @@ }); }); + suite('lazy loading stars', () => { + setup(async () => { + stubFlags('isEnabled') + .withArgs(KnownExperimentId.DASHBOARD_LAZY_LOADING) + .returns(true); + element.loggedInUser = createAccountDetailWithId(1); + }); + + test('makeSecondRequestForStarredChanges is called', async () => { + const change1 = { + ...createChange(), + id: '1' as ChangeInfoId, + starred: false, + }; + const change1Starred = {...change1, starred: true}; + // The second array is for the 'owner:self limit:1' query + getChangesStub.onFirstCall().returns(Promise.resolve([[change1], []])); + getChangesStub + .onSecondCall() + .returns(Promise.resolve([[change1Starred], []])); + + element.viewState = { + view: GerritView.DASHBOARD, + type: DashboardType.CUSTOM, + user: 'self', + sections: [{name: 'test1', query: 'test1', hideIfEmpty: true}], + }; + await element.reload(); + await element.updateComplete; + + assert.isTrue(getChangesStub.calledTwice); + assert.isFalse(element.starsLoading); + assert.isDefined(element.results); + assert.isTrue(element.results[0].results[0].starred); + }); + + test('starsLoading is true during second request', async () => { + const change1 = { + ...createChange(), + id: '1' as ChangeInfoId, + starred: false, + }; + const firstRequestPromise = Promise.resolve([[change1], []]); + const secondRequestPromise = mockPromise<ChangeInfo[][] | undefined>(); + getChangesStub.onFirstCall().returns(firstRequestPromise); + getChangesStub.onSecondCall().returns(secondRequestPromise); + + element.viewState = { + view: GerritView.DASHBOARD, + type: DashboardType.CUSTOM, + user: 'self', + sections: [{name: 'test1', query: 'test1', hideIfEmpty: true}], + }; + const reloadPromise = element.reload(); + await firstRequestPromise; + // Wait for first request to finish and second request to start + await waitUntil(() => getChangesStub.calledTwice); + + assert.isTrue(element.starsLoading); + + secondRequestPromise.resolve([[{...change1, starred: true}], []]); + await reloadPromise; + assert.isFalse(element.starsLoading); + }); + }); + suite('drafts banner functionality', () => { setup(async () => { element.viewState = {
diff --git a/polygerrit-ui/app/elements/change/gr-ai-prompt-dialog/gr-ai-prompt-dialog.ts b/polygerrit-ui/app/elements/change/gr-ai-prompt-dialog/gr-ai-prompt-dialog.ts index e4597e6..f129503 100644 --- a/polygerrit-ui/app/elements/change/gr-ai-prompt-dialog/gr-ai-prompt-dialog.ts +++ b/polygerrit-ui/app/elements/change/gr-ai-prompt-dialog/gr-ai-prompt-dialog.ts
@@ -9,7 +9,7 @@ import {sharedStyles} from '../../../styles/shared-styles'; import {modalStyles} from '../../../styles/gr-modal-styles'; import {css, html, LitElement, PropertyValues} from 'lit'; -import {customElement, query, state} from 'lit/decorators.js'; +import {customElement, property, query, state} from 'lit/decorators.js'; import {GrButton} from '../../shared/gr-button/gr-button'; import {getAppContext} from '../../../services/app-context'; import {fireError} from '../../../utils/event-util'; @@ -81,17 +81,19 @@ @state() loading = false; - @state() selectedTemplate: PromptTemplateId = 'HELP_REVIEW'; + @property({type: String}) selectedTemplate: PromptTemplateId = 'HELP_REVIEW'; - @state() private context = 3; + @property({type: Number}) private context = 3; // private but used in tests - @state() threads: CommentThread[] = []; + @property({type: Array}) threads: CommentThread[] = []; @state() private promptContent = ''; @state() private promptSize = ''; + @state() private opened = false; + private readonly getChangeModel = resolve(this, changeModelToken); private readonly getCommentsModel = resolve(this, commentsModelToken); @@ -314,6 +316,7 @@ } override willUpdate(changedProperties: PropertyValues) { + if (!this.opened) return; if ( changedProperties.has('patchContent') || changedProperties.has('selectedTemplate') || @@ -327,6 +330,7 @@ } open() { + this.opened = true; if (this.getNumParents() === 1) { this.loadPatchContent(); } @@ -398,11 +402,10 @@ this.promptContent = template.prompt.replace( '{{patch}}', - sanitizedPatchContent + () => sanitizedPatchContent ); if (this.selectedTemplate === 'RESOLVE_COMMENTS') { - this.promptContent = this.promptContent.replace( - '{{comments}}', + this.promptContent = this.promptContent.replace('{{comments}}', () => this.getUnresolvedCommentsFormatted() ); } @@ -438,6 +441,10 @@ private handleCloseTap(e: Event) { e.preventDefault(); e.stopPropagation(); + this.opened = false; + this.patchContent = undefined; + this.promptContent = ''; + this.promptSize = ''; fire(this, 'close', {}); } }
diff --git a/polygerrit-ui/app/elements/change/gr-ai-prompt-dialog/gr-ai-prompt-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-ai-prompt-dialog/gr-ai-prompt-dialog_test.ts index 7c5268a..bc4ef0f 100644 --- a/polygerrit-ui/app/elements/change/gr-ai-prompt-dialog/gr-ai-prompt-dialog_test.ts +++ b/polygerrit-ui/app/elements/change/gr-ai-prompt-dialog/gr-ai-prompt-dialog_test.ts
@@ -7,11 +7,18 @@ import './gr-ai-prompt-dialog'; import {assert, fixture, html} from '@open-wc/testing'; import {GrAiPromptDialog} from './gr-ai-prompt-dialog'; -import {createParsedChange} from '../../../test/test-data-generators'; -import {CommitId, PatchSetNum} from '../../../api/rest-api'; +import { + createParsedChange, + createThread, +} from '../../../test/test-data-generators'; +import {AccountInfo, CommitId, PatchSetNum} from '../../../api/rest-api'; import {stubRestApi, waitUntil} from '../../../test/test-utils'; import {testResolver} from '../../../test/common-test-setup'; import {commentsModelToken} from '../../../models/comments/comments-model'; +import { + ChangeModel, + changeModelToken, +} from '../../../models/change/change-model'; import {of} from 'rxjs'; suite('gr-ai-prompt-dialog test', () => { @@ -28,6 +35,7 @@ element = await fixture(html`<gr-ai-prompt-dialog></gr-ai-prompt-dialog>`); element.change = createParsedChange(); + element.change.current_revision = 'abc' as CommitId; element.change.revisions['abc'].commit!.parents = [ { commit: 'def' as CommitId, @@ -35,9 +43,9 @@ }, ]; element.patchNum = 1 as PatchSetNum; - element.patchContent = 'test code'; element.selectedTemplate = 'PATCH_ONLY'; - await element.updateComplete; + element.open(); + await waitUntil(() => !!element.patchContent); }); test('renders', async () => { @@ -93,27 +101,42 @@ label="Context" value="3" > - <md-select-option md-menu-item=""> + <md-select-option + md-menu-item="" + tabindex="0" + > <div slot="headline"> 3 lines (default) </div> </md-select-option> - <md-select-option md-menu-item=""> + <md-select-option + md-menu-item="" + tabindex="-1" + > <div slot="headline"> 10 lines </div> </md-select-option> - <md-select-option md-menu-item=""> + <md-select-option + md-menu-item="" + tabindex="-1" + > <div slot="headline"> 25 lines </div> </md-select-option> - <md-select-option md-menu-item=""> + <md-select-option + md-menu-item="" + tabindex="-1" + > <div slot="headline"> 50 lines </div> </md-select-option> - <md-select-option md-menu-item=""> + <md-select-option + md-menu-item="" + tabindex="-1" + > <div slot="headline"> 100 lines </div> @@ -188,8 +211,7 @@ element.selectedTemplate = 'HELP_REVIEW'; await element.updateComplete; assert.include( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (element as any).promptContent, + Reflect.get(element, 'promptContent') as string, 'You are a highly experienced code reviewer' ); }); @@ -197,33 +219,103 @@ test('renders resolve comments prompt', async () => { element.selectedTemplate = 'RESOLVE_COMMENTS'; await element.updateComplete; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - assert.include((element as any).promptContent, 'No unresolved comments.'); + assert.include( + Reflect.get(element, 'promptContent') as string, + 'No unresolved comments.' + ); }); test('renders resolve comments prompt with comments', async () => { element.threads = [ { - comments: [ - { - message: 'test comment', - author: {name: 'Tester'}, - updated: '2025-01-01 10:00:00.000000000', - unresolved: true, - }, - ], + ...createThread({ + message: 'test comment', + author: {name: 'Tester'} as AccountInfo, + unresolved: true, + }), path: 'test.txt', line: 1, - rootId: '1', }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ] as any[]; + ]; element.selectedTemplate = 'RESOLVE_COMMENTS'; await element.updateComplete; const expected = `* File: test.txt (Line 1) Tester: test comment`; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - assert.include((element as any).promptContent, expected); + assert.include(Reflect.get(element, 'promptContent') as string, expected); + }); + + test('preserves dollar signs in patch content', async () => { + const expected = '+IMAGE="${SCRIPT_NAME}_$$"'; + element.patchContent = expected; + element.selectedTemplate = 'PATCH_ONLY'; + await element.updateComplete; + + const promptContent = Reflect.get(element, 'promptContent') as string; + assert.include(promptContent, expected); + }); + + suite('eager loading prevention', () => { + let changeModel: ChangeModel; + + setup(() => { + getPatchContentStub.resetHistory(); + changeModel = testResolver(changeModelToken); + }); + + test('does not load patch content on initialization', async () => { + const change = createParsedChange(); + change.revisions['abc'].commit!.parents = [ + { + commit: 'def' as CommitId, + subject: 'Parent', + }, + ]; + Object.defineProperty(changeModel, 'change$', { + value: of(change), + writable: true, + }); + Object.defineProperty(changeModel, 'patchNum$', { + value: of(1 as PatchSetNum), + writable: true, + }); + + const testElement = await fixture<GrAiPromptDialog>( + html`<gr-ai-prompt-dialog></gr-ai-prompt-dialog>` + ); + await testElement.updateComplete; + + assert.isFalse(getPatchContentStub.called); + }); + + test('loads patch content when open is called', async () => { + const change = createParsedChange(); + change.revisions['abc'].commit!.parents = [ + { + commit: 'def' as CommitId, + subject: 'Parent', + }, + ]; + Object.defineProperty(changeModel, 'change$', { + value: of(change), + writable: true, + }); + Object.defineProperty(changeModel, 'patchNum$', { + value: of(1 as PatchSetNum), + writable: true, + }); + + const testElement = await fixture<GrAiPromptDialog>( + html`<gr-ai-prompt-dialog></gr-ai-prompt-dialog>` + ); + await testElement.updateComplete; + + assert.isFalse(getPatchContentStub.called); + + testElement.open(); + await testElement.updateComplete; + + assert.isTrue(getPatchContentStub.called); + }); }); });
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts index 49f27d9..5f274bc 100644 --- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts +++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.ts
@@ -101,7 +101,7 @@ import {changeModelToken} from '../../../models/change/change-model'; import {sharedStyles} from '../../../styles/shared-styles'; import {css, html, LitElement, nothing, PropertyValues} from 'lit'; -import {customElement, query, state} from 'lit/decorators.js'; +import {customElement, property, query, state} from 'lit/decorators.js'; import {ifDefined} from 'lit/directives/if-defined.js'; import {assertIsDefined, queryAll, uuid} from '../../../utils/common-util'; import {Interaction} from '../../../constants/reporting'; @@ -171,9 +171,10 @@ payload?: RequestPayload; } -const QUICK_APPROVE_ACTION: QuickApproveUIActionInfo = { +export const QUICK_APPROVE_ACTION: QuickApproveUIActionInfo = { __key: 'review', __type: ActionType.CHANGE, + __primary: true, enabled: true, key: 'review', label: 'Quick approve', @@ -392,7 +393,7 @@ @state() change?: ParsedChangeInfo; - @state() actions: ActionNameToActionInfoMap = {}; + @property({type: Object}) actions: ActionNameToActionInfoMap = {}; @state() primaryActionKeys: PrimaryActionKey[] = [ ChangeActions.READY, @@ -482,7 +483,7 @@ @state() actionPriorityOverrides: ActionPriorityOverride[] = []; - @state() additionalActions: UIActionInfo[] = []; + @property({type: Array}) additionalActions: UIActionInfo[] = []; @state() hiddenActions: string[] = []; @@ -878,6 +879,7 @@ } private renderUIAction(action: UIActionInfo) { + const disabled = this.calculateDisabled(action); return html` <gr-tooltip-content title=${ifDefined(action.title)} @@ -885,11 +887,12 @@ ?position-below=${true} > <gr-button - link + ?link=${!action.__primary || disabled} class=${action.__key} data-action-key=${action.__key} data-label=${action.label} - ?disabled=${this.calculateDisabled(action)} + ?primary=${action.__primary} + ?disabled=${disabled} @click=${(e: MouseEvent) => this.handleActionTap(e, action.__key, action.__type)} > @@ -2062,7 +2065,7 @@ cherrypickChangeInfo._number, cherrypickChangeInfo.project ); - const reachable = this.waitForChangeReachable( + const reachable = await this.waitForChangeReachable( cherrypickChangeInfo._number ); if (!reachable) return; @@ -2102,6 +2105,9 @@ break; } default: + if (isQuickApproveAction(action)) { + this.getPluginLoader().jsApiService.handleReplySent(); + } this.getChangeModel().navigateToChangeResetReload(); break; } @@ -2214,7 +2220,8 @@ const query = `topic: "${this.change.topic}"`; const options = listChangesOptionsToHex( ListChangesOption.MESSAGES, - ListChangesOption.ALL_REVISIONS + ListChangesOption.ALL_REVISIONS, + ListChangesOption.SKIP_DIFFSTAT ); return this.restApiService .getChanges(0, query, undefined, options)
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts index 86c755d..c571675 100644 --- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts +++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.ts
@@ -31,6 +31,7 @@ import { assertUIActionInfo, GrChangeActions, + QUICK_APPROVE_ACTION, REMOVE_DELTE_ACCOUNTS_MESSAGE, } from './gr-change-actions'; import { @@ -79,6 +80,12 @@ >; let chatModel: ChatModel; + teardown(() => { + for (const el of document.body.querySelectorAll('gr-tooltip')) { + el.remove(); + } + }); + suite('basic tests', () => { setup(async () => { stubRestApi('getChangeRevisionActions').returns( @@ -169,7 +176,7 @@ class="submit" data-action-key="submit" data-label="Submit" - link="" + primary="" role="button" tabindex="0" > @@ -2228,10 +2235,10 @@ assert.isTrue(element._hideQuickApproveAction); }); - test('is first in list of secondary actions', () => { + test('is first in list of primary actions', () => { const approveButton = queryAndAssert<HTMLElement>( element, - '#secondaryActions' + '#primaryActions' ).querySelector('gr-button'); assert.equal(approveButton!.getAttribute('data-label'), 'foo+1'); }); @@ -2330,6 +2337,16 @@ assert.deepEqual((payload as ReviewInput).labels, {foo: 1}); }); + test('calls handleReplySent on quick approve success', async () => { + const handleReplySentStub = sinon.stub( + testResolver(pluginLoaderToken).jsApiService, + 'handleReplySent' + ); + + await element.handleResponse(QUICK_APPROVE_ACTION, new Response()); + assert.isTrue(handleReplySentStub.called); + }); + test('not added when multiple labels are required without code review', async () => { element.change = { ...createChangeViewChange(),
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts index aa0a8ca..4c05e68 100644 --- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts +++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
@@ -6,7 +6,6 @@ import '../../../styles/shared-styles'; import '../../../styles/gr-font-styles'; import '../../../styles/gr-change-metadata-shared-styles'; -import '../../../styles/gr-change-view-integration-shared-styles'; import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator'; import '../../plugins/gr-endpoint-param/gr-endpoint-param'; import '../../shared/gr-account-chip/gr-account-chip'; @@ -148,7 +147,7 @@ @state() repoConfig?: ConfigInfo; - @state() mutable = false; + @property({type: Boolean}) mutable = false; @state() readonly notCurrentMessage = NOT_CURRENT_MESSAGE;
diff --git a/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts b/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts index 00a5cda..4439344 100644 --- a/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts +++ b/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
@@ -342,6 +342,10 @@ gr-checks-chip { z-index: 2; } + gr-checks-chip:hover, + gr-checks-chip:focus-within { + z-index: 10; + } `, ]; }
diff --git a/polygerrit-ui/app/elements/change/gr-change-summary/gr-checks-chip.ts b/polygerrit-ui/app/elements/change/gr-change-summary/gr-checks-chip.ts index 1315385..1875fa5 100644 --- a/polygerrit-ui/app/elements/change/gr-change-summary/gr-checks-chip.ts +++ b/polygerrit-ui/app/elements/change/gr-change-summary/gr-checks-chip.ts
@@ -54,6 +54,10 @@ position: relative; top: 2px; } + :host(:hover), + :host(:focus-within) { + z-index: 10; + } .checksChip.hoverFullLength { position: absolute; z-index: 1;
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts index 06adefb..bad9f1e 100644 --- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts +++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -124,6 +124,7 @@ import {resolve} from '../../../models/dependency'; import {checksModelToken} from '../../../models/checks/checks-model'; import {changeModelToken} from '../../../models/change/change-model'; +import {chatModelToken} from '../../../models/chat/chat-model'; import {css, html, LitElement, nothing} from 'lit'; import {a11yStyles} from '../../../styles/gr-a11y-styles'; import {materialStyles} from '../../../styles/gr-material-styles'; @@ -410,6 +411,8 @@ private readonly getChangeModel = resolve(this, changeModelToken); + private readonly getChatModel = resolve(this, chatModelToken); + private readonly getCommentsModel = resolve(this, commentsModelToken); private readonly getConfigModel = resolve(this, configModelToken); @@ -488,6 +491,10 @@ 'close-chat-panel', () => (this.showSidebarChat = false) ); + this.addEventListener('explain-code-requested', e => { + this.showSidebarChat = true; + this.getChatModel().processChatRequest(e.detail); + }); } private setupShortcuts() { @@ -974,7 +981,7 @@ flex: 1; overflow-x: hidden; } - .relatedChanges { + .commitAside { flex: 0 1 auto; overflow: hidden; padding: var(--spacing-l) 0; @@ -998,6 +1005,16 @@ margin: var(--spacing-l) 0; padding: 0 var(--spacing-l); } + .commitAside gr-endpoint-decorator[name='change-view-commit-aside'] { + display: none; + } + .commitAside + gr-endpoint-decorator[name='change-view-commit-aside']:has( + :not(gr-endpoint-param):not([hidden]) + ) { + display: block; + margin-bottom: var(--spacing-l); + } .showOnEdit { display: none; } @@ -1048,10 +1065,10 @@ position: relative; } @media screen and (max-width: 75em) { - .relatedChanges { + .commitAside { padding: 0; } - .relatedChanges gr-related-changes-list { + .commitAside gr-related-changes-list { padding-top: var(--spacing-l); } #commitAndRelated { @@ -1313,7 +1330,7 @@ { label: 'URL and title', shortcut: 'r', - value: `${changeURL}: ${this.change?.subject}`, + value: `${changeURL} - ${this.change?.subject}`, }, { label: 'Markdown', @@ -1418,7 +1435,13 @@ </gr-endpoint-param> </gr-endpoint-decorator> </div> - <div class="relatedChanges"> + <div class="commitAside"> + <gr-endpoint-decorator name="change-view-commit-aside"> + <gr-endpoint-param name="change" .value=${this.change}> + </gr-endpoint-param> + <gr-endpoint-param name="revision" .value=${this.revision}> + </gr-endpoint-param> + </gr-endpoint-decorator> <gr-related-changes-list></gr-related-changes-list> </div> <div class="emptySpace"></div> @@ -1537,6 +1560,7 @@ this.requestUpdate(); }} @file-action-tap=${this.handleFileActionTap} + @open-download-dialog=${this.handleOpenDownloadDialog} > </gr-file-list> </div> @@ -1915,7 +1939,7 @@ const fileIndex = this.fileList.files.findIndex(f => f.__path === path); if (fileIndex !== -1) { this.fileList.fileCursor.setCursorAtIndex(fileIndex, true); - const isExpanded = this.fileList.expandedFiles.some(f => f.path === path); + const isExpanded = this.fileList.expandedFiles.has(path); if (!isExpanded) { this.fileList.toggleFileExpandedByIndex(fileIndex); await this.fileList.updateComplete;
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_screenshot_test.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_screenshot_test.ts index 713a1ea..97568af 100644 --- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_screenshot_test.ts +++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_screenshot_test.ts
@@ -323,6 +323,15 @@ revert_of: 12345 as NumericChangeId, submittable: true, subject: 'Reland "Add initial jj support to `gclient sync`."', + actions: { + ...element.change?.actions, + submit: { + method: HttpMethod.POST, + label: 'Submit', + title: 'Submit the change', + enabled: true, + }, + }, }); await element.updateComplete; @@ -348,6 +357,10 @@ ); await visualDiff(container, 'gr-change-view-wrapped-statuses-801px'); + await visualDiffDarkTheme( + container, + 'gr-change-view-wrapped-statuses-801px' + ); } finally { document.body.removeChild(container); }
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts index 83fff44..7e11970 100644 --- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts +++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
@@ -64,6 +64,8 @@ ChangeModel, changeModelToken, } from '../../../models/change/change-model'; +import {chatModelToken} from '../../../models/chat/chat-model'; +import {GrContentWithSidebar} from '../../shared/gr-content-with-sidebar/gr-content-with-sidebar'; import {FocusTarget} from '../gr-reply-dialog/gr-reply-dialog'; import {GrChangeStar} from '../../shared/gr-change-star/gr-change-star'; import {GrThreadList} from '../gr-thread-list/gr-thread-list'; @@ -353,7 +355,12 @@ </gr-endpoint-param> </gr-endpoint-decorator> </div> - <div class="relatedChanges"> + <div class="commitAside"> + <gr-endpoint-decorator name="change-view-commit-aside"> + <gr-endpoint-param name="change"> </gr-endpoint-param> + <gr-endpoint-param name="revision"> + </gr-endpoint-param> + </gr-endpoint-decorator> <gr-related-changes-list> </gr-related-changes-list> </div> <div class="emptySpace"></div> @@ -1362,6 +1369,91 @@ }); }); + suite('change-view-commit-aside visibility', () => { + let decorator: HTMLElement; + + setup(async () => { + element.change = {...createChangeViewChange(), labels: {}}; + element.revision = createRevision(); + await element.updateComplete; + decorator = element.shadowRoot!.querySelector( + 'gr-endpoint-decorator[name="change-view-commit-aside"]' + )!; + }); + + test('hidden by default', () => { + assert.equal(getComputedStyle(decorator).display, 'none'); + }); + + test('hidden if plugin component is hidden', async () => { + const promise = mockPromise(); + window.Gerrit.install( + promise.resolve, + '0.1', + 'http://some/plugins/url.js' + ); + const plugin = (await promise) as PluginApi; + + const dummyTagName = 'dummy-aside-component-hidden'; + if (!customElements.get(dummyTagName)) { + customElements.define( + dummyTagName, + class extends HTMLElement { + connectedCallback() { + this.setAttribute('hidden', ''); + } + } + ); + } + + plugin.registerCustomComponent('change-view-commit-aside', dummyTagName); + + await new Promise<void>(resolve => { + const observer = new MutationObserver(() => { + if (decorator.querySelector(dummyTagName)) { + observer.disconnect(); + resolve(); + } + }); + observer.observe(decorator, {childList: true}); + }); + + await element.updateComplete; + assert.equal(getComputedStyle(decorator).display, 'none'); + }); + + test('visible if plugin component is visible', async () => { + const promise = mockPromise(); + window.Gerrit.install( + promise.resolve, + '0.1', + 'http://some/plugins/url.js' + ); + const plugin = (await promise) as PluginApi; + + const dummyTagName = 'dummy-aside-component-visible'; + if (!customElements.get(dummyTagName)) { + customElements.define(dummyTagName, class extends HTMLElement {}); + } + + plugin.registerCustomComponent('change-view-commit-aside', dummyTagName); + + await new Promise<void>(resolve => { + const observer = new MutationObserver(() => { + if (decorator.querySelector(dummyTagName)) { + observer.disconnect(); + resolve(); + } + }); + observer.observe(decorator, {childList: true}); + }); + + await element.updateComplete; + assert.equal(getComputedStyle(decorator).display, 'block'); + assert.notEqual(getComputedStyle(decorator).marginBottom, '0px'); + }); + }); + test('handleToggleStar called when star is tapped', async () => { element.change = { ...createChangeViewChange(), @@ -1499,4 +1591,25 @@ value: 'http://localhost:9876/review/c/test-project/+/42', }); }); + + test('explain-code-requested opens sidebar and starts chat', async () => { + const chatModel = testResolver(chatModelToken); + const processChatRequestStub = sinon.stub(chatModel, 'processChatRequest'); + const contentWithSidebar = queryAndAssert<GrContentWithSidebar>( + element, + 'gr-content-with-sidebar' + ); + assert.isTrue(contentWithSidebar.hideSide); + + const event = new CustomEvent('explain-code-requested', { + detail: {prompt: 'Explain this code'}, + bubbles: true, + composed: true, + }); + element.dispatchEvent(event); + await element.updateComplete; + + assert.isFalse(contentWithSidebar.hideSide); + assert.isTrue(processChatRequestStub.calledOnceWithExactly(event.detail)); + }); });
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts index c87b878..8a1070f 100644 --- a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts +++ b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog.ts
@@ -246,7 +246,7 @@ if (!this.change) return; const url = createChangeUrl({ change: this.change, - edit: true, + patchNum: EDIT, forceReload: true, }); this.getNavigation().setUrl(url);
diff --git a/polygerrit-ui/app/elements/change/gr-copy-links/gr-copy-links.ts b/polygerrit-ui/app/elements/change/gr-copy-links/gr-copy-links.ts index 72050dc..be82140 100644 --- a/polygerrit-ui/app/elements/change/gr-copy-links/gr-copy-links.ts +++ b/polygerrit-ui/app/elements/change/gr-copy-links/gr-copy-links.ts
@@ -7,7 +7,7 @@ import '../../shared/gr-copy-clipboard/gr-copy-clipboard'; import {css, html, LitElement, nothing, PropertyValues} from 'lit'; import {createRef, ref, Ref} from 'lit/directives/ref.js'; -import {customElement, property, query, state} from 'lit/decorators.js'; +import {customElement, property, query} from 'lit/decorators.js'; import {strToClassName} from '../../../utils/dom-util'; import {copyToClipboard, queryAndAssert} from '../../../utils/common-util'; import {formStyles} from '../../../styles/form-styles'; @@ -43,7 +43,7 @@ @property({type: Number}) verticalOffset = 10; - @state() isDropdownOpen = false; + @property({type: Boolean}) isDropdownOpen = false; // private but used in screenshot tests @query('md-menu') dropdown?: MdMenu;
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts index ee3e307..f255cb4 100644 --- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts +++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
@@ -269,7 +269,7 @@ // Private but used in tests. @state() - expandedFiles: PatchSetFile[] = []; + expandedFiles: Set<string> = new Set(); // Private but used in tests. @state() @@ -642,6 +642,11 @@ display: inline-block; color: var(--deemphasized-text-color); } + .expensiveDiff { + color: var(--warning-foreground); + font-weight: var(--font-weight-bold); + margin-left: var(--spacing-m); + } @container (max-width: 1200px) { gr-endpoint-decorator.extra-col { @@ -1326,6 +1331,12 @@ `; } + private handleDownloadLocally(e: Event) { + e.preventDefault(); + e.stopPropagation(); + fire(this, 'open-download-dialog', {}); + } + private renderFilePath(file: NormalizedFileInfo, previousFilePath?: string) { return html` <span class="path" role="gridcell"> @@ -1346,6 +1357,18 @@ ></gr-copy-clipboard> </a> ${when( + file.diffs_too_expensive_to_compute, + () => html` + <span class="expensiveDiff"> + <gr-icon icon="warning"></gr-icon> + Diff too expensive to compute. + <gr-button link @click=${this.handleDownloadLocally}> + Please download locally to review + </gr-button> + </span> + ` + )} + ${when( file.old_path, () => html` <div class="oldPath" title=${ifDefined(file.old_path)}> @@ -1470,29 +1493,42 @@ "Commit message" row content with incorrect column headers. --> <div class=${this.computeClass('', file.__path)}> - <span - class="removed" - tabindex="0" - aria-label=${`${file.lines_deleted} removed`} - ?hidden=${!!file.binary} - > - -${file.lines_deleted} - </span> - <span - class="added" - tabindex="0" - aria-label=${`${file.lines_inserted} added`} - ?hidden=${!!file.binary} - > - +${file.lines_inserted} - </span> - <span - class=${ifDefined(this.computeBinaryClass(file.size_delta))} - ?hidden=${!file.binary} - > - ${formatBytes(file.size_delta)} - ${this.formatPercentage(file.size, file.size_delta)} - </span> + ${when( + file.diffs_too_expensive_to_compute, + () => html` + <gr-tooltip-content + title="Diff too expensive to compute" + has-tooltip + > + <gr-icon icon="warning" class="warning"></gr-icon> + </gr-tooltip-content> + `, + () => html` + <span + class="removed" + tabindex="0" + aria-label=${`${file.lines_deleted} removed`} + ?hidden=${!!file.binary} + > + -${file.lines_deleted} + </span> + <span + class="added" + tabindex="0" + aria-label=${`${file.lines_inserted} added`} + ?hidden=${!!file.binary} + > + +${file.lines_inserted} + </span> + <span + class=${ifDefined(this.computeBinaryClass(file.size_delta))} + ?hidden=${!file.binary} + > + ${formatBytes(file.size_delta)} + ${this.formatPercentage(file.size, file.size_delta)} + </span> + ` + )} </div> </div>`; } @@ -1940,29 +1976,26 @@ } // private but used in test - toggleFileExpanded(file: PatchSetFile) { + toggleFileExpanded(path: string) { // Is the path in the list of expanded diffs? If so, remove it, otherwise // add it to the list. - const indexInExpanded = this.expandedFiles.findIndex( - f => f.path === file.path - ); - if (indexInExpanded === -1) { + const newExpandedFiles = new Set(this.expandedFiles); + if (!newExpandedFiles.has(path)) { this.reporting.reportInteraction(Interaction.FILE_LIST_DIFF_EXPANDED); - this.expandedFiles = this.expandedFiles.concat([file]); + newExpandedFiles.add(path); } else { this.reporting.reportInteraction(Interaction.FILE_LIST_DIFF_COLLAPSED); - this.expandedFiles = this.expandedFiles.filter( - (_val, idx) => idx !== indexInExpanded - ); + newExpandedFiles.delete(path); } - const indexInAll = this.files.findIndex(f => f.__path === file.path); + this.expandedFiles = newExpandedFiles; + const indexInAll = this.files.findIndex(f => f.__path === path); this.shadowRoot!.querySelectorAll(`.${FILE_ROW_CLASS}`)[ indexInAll ].scrollIntoView({block: 'nearest'}); } toggleFileExpandedByIndex(index: number) { - this.toggleFileExpanded(this.computePatchSetFile(this.files[index])); + this.toggleFileExpanded(this.files[index].__path); } // Private but used in tests. @@ -1971,23 +2004,22 @@ return; } // Re-render all expanded diffs sequentially. - this.renderInOrder(this.expandedFiles, this.diffs); + this.renderInOrder([...this.expandedFiles], this.diffs); } expandAllDiffs() { - const newFiles = this.files - .slice(0, this.numFilesShown) - // TODO(b/419187980): Refactor expandedFiles to use a Set for efficiency. - .filter(file => !this.expandedFiles.some(f => f.path === file.__path)) - .map(file => this.computePatchSetFile(file)); + const newExpandedFiles = new Set(this.expandedFiles); + this.files.slice(0, this.numFilesShown).forEach(file => { + newExpandedFiles.add(file.__path); + }); this.reporting.reportInteraction(Interaction.FILE_LIST_ALL_DIFFS_EXPANDED); - this.expandedFiles = newFiles.concat(this.expandedFiles); + this.expandedFiles = newExpandedFiles; } collapseAllDiffs() { this.reporting.reportInteraction(Interaction.FILE_LIST_ALL_DIFFS_COLLAPSED); - this.expandedFiles = []; + this.expandedFiles = new Set(); } /** @@ -2083,8 +2115,8 @@ this.fileActionClick(e, file => this.reviewFile(file.path)); } - private expandedClick(e: MouseEvent | KeyboardEvent) { - this.fileActionClick(e, file => this.toggleFileExpanded(file)); + expandedClick(e: MouseEvent | KeyboardEvent) { + this.fileActionClick(e, file => this.toggleFileExpanded(file.path)); } /** @@ -2116,7 +2148,7 @@ e.preventDefault(); this.fileCursor.setCursor(fileRow.element); - this.toggleFileExpanded(file); + this.toggleFileExpanded(path); } private getFileRowFromEvent(e: Event): FileRow | null { @@ -2388,7 +2420,7 @@ } async filesChanged() { - if (this.expandedFiles.length > 0) this.expandedFiles = []; + if (this.expandedFiles.size > 0) this.expandedFiles = new Set(); await this.updateCleanlyMergedPaths(); if (!this.files || this.files.length === 0) return; await this.updateComplete; @@ -2454,18 +2486,18 @@ return val ? 'true' : 'false'; } - private isFileExpanded(path: string | undefined) { - return this.expandedFiles.some(f => f.path === path); + isFileExpanded(path: string | undefined) { + return path !== undefined && this.expandedFiles.has(path); } - private isFileExpandedStr(path: string | undefined) { + isFileExpandedStr(path: string | undefined) { return this.booleanToString(this.isFileExpanded(path)); } private computeExpandedFiles(): FilesExpandedState { - if (this.expandedFiles.length === 0) { + if (this.expandedFiles.size === 0) { return FilesExpandedState.NONE; - } else if (this.expandedFiles.length === this.files.length) { + } else if (this.expandedFiles.size === this.files.length) { return FilesExpandedState.ALL; } return FilesExpandedState.SOME; @@ -2480,18 +2512,21 @@ * @param newFiles The new files that have been added. * Private but used in tests. */ - async expandedFilesChanged(oldFiles: Array<PatchSetFile>) { + async expandedFilesChanged(oldFiles?: Set<string>) { this.filesExpanded = this.computeExpandedFiles(); - const newFiles = this.expandedFiles.filter( - file => (oldFiles ?? []).findIndex(f => f.path === file.path) === -1 - ); + const newPaths: string[] = []; + for (const path of this.expandedFiles) { + if (!oldFiles || !oldFiles.has(path)) { + newPaths.push(path); + } + } // Required so that the newly created diff view is included in this.diffs. await this.updateComplete; - if (newFiles.length) { - await this.renderInOrder(newFiles, this.diffs); + if (newPaths.length) { + await this.renderInOrder(newPaths, this.diffs); } this.updateDiffCursor(); this.diffCursor?.reInitAndUpdateStops(); @@ -2506,11 +2541,10 @@ * * @param initialCount The total number of paths in the pass. */ - async renderInOrder(files: PatchSetFile[], diffElements: GrDiffHost[]) { + async renderInOrder(paths: string[], diffElements: GrDiffHost[]) { this.reporting.time(Timing.FILE_EXPAND_ALL); - for (const file of files) { - const path = file.path; + for (const path of paths) { const diffElem = this.findDiffByPath(path, diffElements); if (!diffElem) { this.reporting.error( @@ -2522,8 +2556,7 @@ diffElem.prefetchDiff(); } - await asyncForeach(files, async (file, cancel) => { - const path = file.path; + await asyncForeach(paths, async (path, cancel) => { this.cancelForEachDiff = cancel; const diffElem = this.findDiffByPath(path, diffElements); @@ -2546,7 +2579,7 @@ if ( this.loggedIn && !this.diffPrefs.manual_review && - files.length === 1 + paths.length === 1 ) { await this.reviewFile(path, true); } @@ -2555,7 +2588,7 @@ this.cancelForEachDiff = undefined; this.reporting.timeEnd(Timing.FILE_EXPAND_ALL, { - count: files.length, + count: paths.length, height: this.clientHeight, }); /*
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts index 684df6e..4c6bf52 100644 --- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts +++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.ts
@@ -411,6 +411,37 @@ assert.notOk(fileMode); }); + test('renders warning icon for expensive diffs', async () => { + element.files = [ + { + __path: 'expensive.txt', + lines_inserted: 0, + lines_deleted: 0, + diffs_too_expensive_to_compute: true, + size: 0, + size_delta: 0, + }, + ]; + await element.updateComplete; + const fileRows = queryAll<HTMLDivElement>(element, '.file-row'); + const statsCol = queryAndAssert(fileRows?.[0], '.stats'); + assert.dom.equal( + statsCol, + /* HTML */ ` + <div class="stats" role="gridcell"> + <div class=""> + <gr-tooltip-content + has-tooltip="" + title="Diff too expensive to compute" + > + <gr-icon class="warning" icon="warning"> </gr-icon> + </gr-tooltip-content> + </div> + </div> + ` + ); + }); + test('renders file status column header', async () => { element.files = createFiles(1, {lines_inserted: 9}); element.filesLeftBase = createFiles(1, {lines_inserted: 9}); @@ -1036,41 +1067,41 @@ element.fileCursor.setCursorAtIndex(0); await element.updateComplete; assert.equal(element.diffs.length, 0); - assert.equal(element.expandedFiles.length, 0); + assert.equal(element.expandedFiles.size, 0); pressKey(element, 'i'); await element.updateComplete; assert.equal(element.diffs.length, 1); assert.equal(element.diffs[0].path, paths[0]); - assert.equal(element.expandedFiles.length, 1); - assert.equal(element.expandedFiles[0].path, paths[0]); + assert.equal(element.expandedFiles.size, 1); + assert.isTrue(element.expandedFiles.has(paths[0])); pressKey(element, 'i'); await element.updateComplete; assert.equal(element.diffs.length, 0); - assert.equal(element.expandedFiles.length, 0); + assert.equal(element.expandedFiles.size, 0); element.fileCursor.setCursorAtIndex(1); pressKey(element, 'i'); await element.updateComplete; assert.equal(element.diffs.length, 1); assert.equal(element.diffs[0].path, paths[1]); - assert.equal(element.expandedFiles.length, 1); - assert.equal(element.expandedFiles[0].path, paths[1]); + assert.equal(element.expandedFiles.size, 1); + assert.isTrue(element.expandedFiles.has(paths[1])); pressKey(element, 'I'); await element.updateComplete; assert.equal(element.diffs.length, paths.length); - assert.equal(element.expandedFiles.length, paths.length); + assert.equal(element.expandedFiles.size, paths.length); for (const diff of element.diffs) { - assert.isTrue(element.expandedFiles.some(f => f.path === diff.path)); + assert.isTrue(element.expandedFiles.has(diff.path!)); } // since _expandedFilesChanged is stubbed element.filesExpanded = FilesExpandedState.ALL; pressKey(element, 'I'); await element.updateComplete; assert.equal(element.diffs.length, 0); - assert.equal(element.expandedFiles.length, 0); + assert.equal(element.expandedFiles.size, 0); }); test('r key sets reviewed flag', async () => { @@ -1309,10 +1340,7 @@ await element.updateComplete; assert.equal(showHideCheck!.getAttribute('aria-checked'), 'true'); - assert.notEqual( - element.expandedFiles.findIndex(f => f.path === 'myfile.txt'), - -1 - ); + assert.isTrue(element.expandedFiles.has('myfile.txt')); }); test('diff mode correctly toggles the diffs', async () => { @@ -1382,8 +1410,8 @@ queryAndAssert<GrIcon>(element, 'gr-icon').icon, 'expand_more' ); - assert.equal(element.expandedFiles.length, 0); - element.toggleFileExpanded({path}); + assert.equal(element.expandedFiles.size, 0); + element.toggleFileExpanded(path); await element.updateComplete; // Wait for expandedFilesChanged to finish. await waitEventLoop(); @@ -1394,8 +1422,8 @@ ); assert.equal(renderSpy.callCount, 1); - assert.isTrue(element.expandedFiles.some(f => f.path === path)); - element.toggleFileExpanded({path}); + assert.isTrue(element.expandedFiles.has(path)); + element.toggleFileExpanded(path); await element.updateComplete; // Wait for expandedFilesChanged to finish. await waitEventLoop(); @@ -1405,7 +1433,7 @@ 'expand_more' ); assert.equal(renderSpy.callCount, 1); - assert.isFalse(element.expandedFiles.some(f => f.path === path)); + assert.isFalse(element.expandedFiles.has(path)); }); test('expandAllDiffs and collapseAllDiffs', async () => { @@ -1428,7 +1456,7 @@ await element.updateComplete; // Wait for expandedFilesChanged to finish. await waitEventLoop(); - assert.equal(element.expandedFiles.length, 0); + assert.equal(element.expandedFiles.size, 0); assert.equal(element.filesExpanded, FilesExpandedState.NONE); }); @@ -1459,7 +1487,7 @@ }, ]; sinon.stub(element, 'diffs').get(() => diffs); - element.expandedFiles = element.expandedFiles.concat([{path}]); + element.expandedFiles = new Set([path]); await element.updateComplete; await waitEventLoop(); await promise; @@ -1469,12 +1497,12 @@ element.files = [normalize({}, 'foo.bar'), normalize({}, 'baz.bar')]; await element.updateComplete; assert.equal(element.filesExpanded, FilesExpandedState.NONE); - element.expandedFiles.push({path: 'baz.bar'}); - element.expandedFilesChanged([{path: 'baz.bar'}]); + element.expandedFiles.add('baz.bar'); + element.expandedFilesChanged(new Set(['baz.bar'])); await element.updateComplete; assert.equal(element.filesExpanded, FilesExpandedState.SOME); - element.expandedFiles.push({path: 'foo.bar'}); - element.expandedFilesChanged([{path: 'foo.bar'}]); + element.expandedFiles.add('foo.bar'); + element.expandedFilesChanged(new Set(['baz.bar', 'foo.bar'])); await element.updateComplete; assert.equal(element.filesExpanded, FilesExpandedState.ALL); element.collapseAllDiffs(); @@ -1521,10 +1549,7 @@ }, // eslint-disable-next-line @typescript-eslint/no-explicit-any ] as any; - await element.renderInOrder( - [{path: 'p2'}, {path: 'p1'}, {path: 'p0'}], - diffs - ); + await element.renderInOrder(['p2', 'p1', 'p0'], diffs); await element.updateComplete; assert.isFalse(reviewStub.called); }); @@ -1543,7 +1568,7 @@ }, // eslint-disable-next-line @typescript-eslint/no-explicit-any ] as any; - await element.renderInOrder([{path: 'p2'}], diffs); + await element.renderInOrder(['p2'], diffs); await element.updateComplete; assert.equal(reviewStub.callCount, 1); }); @@ -1579,11 +1604,11 @@ // eslint-disable-next-line @typescript-eslint/no-explicit-any ] as any; - await element.renderInOrder([{path: 'p'}], diffs); + await element.renderInOrder(['p'], diffs); await element.updateComplete; assert.isFalse(reviewStub.called); delete element.diffPrefs.manual_review; - await element.renderInOrder([{path: 'p'}], diffs); + await element.renderInOrder(['p'], diffs); await element.updateComplete; // Wait for renderInOrder to finish await waitEventLoop();
diff --git a/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow.ts b/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow.ts index ae1b582..f9e6999 100644 --- a/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow.ts +++ b/polygerrit-ui/app/elements/change/gr-flows/gr-create-flow.ts
@@ -57,6 +57,7 @@ import {fireAlert} from '../../../utils/event-util'; import {MdOutlinedSelect} from '@material/web/select/outlined-select.js'; import {Interaction} from '../../../constants/reporting'; +import {isDefined} from '../../../types/types'; const MAX_AUTOCOMPLETE_RESULTS = 10; @@ -107,7 +108,7 @@ @state() private serverConfig?: ServerInfo; - @state() flowString = ''; + @property({type: String}) flowString = ''; @state() // private but used in tests @@ -146,6 +147,8 @@ private customConditions: FlowCustomConditionInfo[] = []; + @state() private disabledActions: string[] = []; + private readonly accountSuggestions: SuggestionProvider = ( predicate, expression @@ -242,6 +245,14 @@ ); const allConditions = await Promise.all(conditionsPromises); this.customConditions = allConditions.flat(); + + const disabledActions = providers + .map( + provider => + provider.getDisabledActions && provider.getDisabledActions() + ) + .flat(); + this.disabledActions = Array.from(disabledActions).filter(isDefined); } ); @@ -406,9 +417,9 @@ private async getFlowActions() { if (!this.changeNum) return; const actions = await this.restApiService.listFlowActions(this.changeNum); - this.flowActions = (actions ?? []).sort((a, b) => - a.name.localeCompare(b.name) - ); + this.flowActions = (actions ?? []) + .filter(action => !this.disabledActions.includes(action.name)) + .sort((a, b) => a.name.localeCompare(b.name)); } private renderStages() {
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_screenshot_test.ts b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_screenshot_test.ts index 134ecc0..8b738e6 100644 --- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_screenshot_test.ts +++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_screenshot_test.ts
@@ -82,12 +82,14 @@ container.appendChild(element); document.body.appendChild(container); - await visualDiff(container, 'gr-label-scores-long-trigger-vote-label'); - await visualDiffDarkTheme( - container, - 'gr-label-scores-long-trigger-vote-label' - ); - - document.body.removeChild(container); + try { + await visualDiff(container, 'gr-label-scores-long-trigger-vote-label'); + await visualDiffDarkTheme( + container, + 'gr-label-scores-long-trigger-vote-label' + ); + } finally { + document.body.removeChild(container); + } }); });
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts index 5e23cbbd..c675ff7 100644 --- a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts +++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
@@ -348,6 +348,7 @@ .account=${this.author} .change=${this.change} class="authorLabel" + ?is-ai=${this.hasAiComments()} ></gr-account-label> <gr-message-scores .labelExtremes=${this.labelExtremes} @@ -773,4 +774,11 @@ if (!this.message) return; this.message = {...this.message, expanded: !this.message.expanded}; } + + private hasAiComments(): boolean { + if (!this.commentThreads || this.commentThreads.length === 0) return false; + return this.commentThreads.every(thread => + thread.comments.every(comment => !!comment.is_ai) + ); + } }
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_screenshot_test.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message_screenshot_test.ts new file mode 100644 index 0000000..2cd4d18 --- /dev/null +++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_screenshot_test.ts
@@ -0,0 +1,112 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ +import '../../../test/common-test-setup'; +import {fixture, html} from '@open-wc/testing'; +// Until https://github.com/modernweb-dev/web/issues/2804 is fixed +// @ts-ignore +import {visualDiff} from '@web/test-runner-visual-regression'; +import {visualDiffDarkTheme} from '../../../test/test-utils'; +import {GrMessage} from './gr-message'; +import './gr-message'; +import {ChangeMessage, CommentThread, Timestamp} from '../../../types/common'; +import { + createAccountDetailWithId, + createChangeMessage, + createCommentThread, +} from '../../../test/test-data-generators'; + +const author = createAccountDetailWithId(1); + +const msg: ChangeMessage = { + ...createChangeMessage(), + author, + message: 'Review comments published', + date: '2021-11-01 10:11:12.000000000' as Timestamp, +}; + +const msg_with_scores: ChangeMessage = { + ...createChangeMessage(), + author, + message: 'Patch Set 1: Verified+1 Code-Review-2 Trybot-Label3+1', + date: '2021-11-01 10:11:12.000000000' as Timestamp, +}; + +const labelExtremes = { + Verified: {max: 1, min: -1}, + 'Code-Review': {max: 2, min: -2}, + 'Trybot-Label3': {max: 3, min: 0}, +}; + +const thread_normal: CommentThread = createCommentThread([ + { + author, + message: 'This is a normal comment', + updated: '2021-11-01 10:11:12.000000000' as Timestamp, + }, +]); + +const thread_ai: CommentThread = createCommentThread([ + { + author, + message: 'This is an AI comment', + updated: '2021-11-01 10:11:12.000000000' as Timestamp, + is_ai: true, + }, +]); + +suite('gr-message screenshot tests', () => { + let element: GrMessage; + + setup(async () => { + element = await fixture(html`<gr-message></gr-message>`); + element.message = msg; + }); + + test('collapsed normal', async () => { + element.commentThreads = [thread_normal]; + element.message = {...msg, expanded: false}; + await element.updateComplete; + + await visualDiff(element, 'gr-message-collapsed-normal'); + await visualDiffDarkTheme(element, 'gr-message-collapsed-normal'); + }); + + test('collapsed AI', async () => { + element.commentThreads = [thread_ai]; + element.message = {...msg, expanded: false}; + await element.updateComplete; + + await visualDiff(element, 'gr-message-collapsed-ai'); + await visualDiffDarkTheme(element, 'gr-message-collapsed-ai'); + }); + + test('expanded AI', async () => { + element.commentThreads = [thread_ai]; + element.message = {...msg, expanded: true}; + await element.updateComplete; + + await visualDiff(element, 'gr-message-expanded-ai'); + await visualDiffDarkTheme(element, 'gr-message-expanded-ai'); + }); + + test('collapsed with scores', async () => { + element.message = {...msg_with_scores, expanded: false}; + element.labelExtremes = labelExtremes; + await element.updateComplete; + + await visualDiff(element, 'gr-message-collapsed-scores'); + await visualDiffDarkTheme(element, 'gr-message-collapsed-scores'); + }); + + test('expanded with scores', async () => { + element.message = {...msg_with_scores, expanded: true}; + element.labelExtremes = labelExtremes; + await element.updateComplete; + + await visualDiff(element, 'gr-message-expanded-scores'); + await visualDiffDarkTheme(element, 'gr-message-expanded-scores'); + }); +});
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts index 23d2867..445f41f 100644 --- a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts +++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts
@@ -793,4 +793,75 @@ ); }); }); + + suite('hasAiComments', () => { + setup(async () => { + element = await fixture<GrMessage>(html`<gr-message></gr-message>`); + element.message = createChangeMessage(); + await element.updateComplete; + }); + + test('is false when there are no comment threads', async () => { + element.commentThreads = []; + await element.updateComplete; + + const accountLabel = queryAndAssert<HTMLElement>(element, '.authorLabel'); + assert.isFalse(accountLabel.hasAttribute('is-ai')); + }); + + test('is false when all comments are human', async () => { + element.commentThreads = [ + createCommentThread([ + createComment({message: 'hello 1'}), + createComment({message: 'hello 2'}), + ]), + ]; + await element.updateComplete; + + const accountLabel = queryAndAssert<HTMLElement>(element, '.authorLabel'); + assert.isFalse(accountLabel.hasAttribute('is-ai')); + }); + + test('is false when some comments are AI but some are human', async () => { + element.commentThreads = [ + createCommentThread([ + createComment({ + id: '111' as UrlEncodedCommentId, + message: 'hello 1', + is_ai: true, + }), + createComment({ + in_reply_to: '111' as UrlEncodedCommentId, + message: 'hello 2', + }), + ]), + ]; + await element.updateComplete; + + const accountLabel = queryAndAssert<HTMLElement>(element, '.authorLabel'); + assert.isFalse(accountLabel.hasAttribute('is-ai')); + }); + + test('is true when all comments in all threads are AI', async () => { + element.commentThreads = [ + createCommentThread([ + createComment({ + id: '111' as UrlEncodedCommentId, + message: 'hello 1', + is_ai: true, + }), + createComment({ + in_reply_to: '111' as UrlEncodedCommentId, + message: 'hello 2', + is_ai: true, + }), + ]), + createCommentThread([createComment({message: 'hello 3', is_ai: true})]), + ]; + await element.updateComplete; + + const accountLabel = queryAndAssert<HTMLElement>(element, '.authorLabel'); + assert.isTrue(accountLabel.hasAttribute('is-ai')); + }); + }); });
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts index 03c79a8..65fc242 100644 --- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts +++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
@@ -22,6 +22,7 @@ VotingRangeInfo, } from '../../../types/common'; import {GrMessage, MessageAnchorTapDetail} from '../gr-message/gr-message'; +import {isServiceUser} from '../../../utils/account-util'; import {getVotingRange} from '../../../utils/label-util'; import { FormattedReviewerUpdateInfo, @@ -225,10 +226,28 @@ * Autogenerated messages are unimportant, if there is a message with the same * tag and a higher revision number. */ +function isReviewerUpdateMessage(message: CombinedMessage): boolean { + return ( + (message as FormattedReviewerUpdateInfo).type === 'REVIEWER_UPDATE' || + message.tag === MessageTag.TAG_REVIEWER_UPDATE + ); +} + function computeIsImportant( message: CombinedMessage, allMessages: CombinedMessage[] ) { + const author = message.author; + const realAuthor = + (message as ChangeMessageInfo).real_author ?? + (message as FormattedReviewerUpdateInfo).realAuthor; + if ( + (isServiceUser(author) || isServiceUser(realAuthor)) && + isReviewerUpdateMessage(message) + ) { + return false; + } + if (!message.tag) return true; const hasSameTag = (m: CombinedMessage) => m.tag === message.tag;
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.ts b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.ts index 66a1951..8ec1bde 100644 --- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.ts +++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.ts
@@ -7,7 +7,7 @@ import '../../../test/common-test-setup'; import './gr-messages-list'; import {CombinedMessage, GrMessagesList, TEST_ONLY} from './gr-messages-list'; -import {MessageTag} from '../../../constants/constants'; +import {AccountTag, MessageTag} from '../../../constants/constants'; import { query, queryAll, @@ -59,7 +59,7 @@ }; }; -const randomMessage = function (params?: ChangeMessageInfo) { +const randomMessage = function (params?: Partial<ChangeMessageInfo>) { params = params || ({} as ChangeMessageInfo); const author1 = { _account_id: 1115495 as AccountId, @@ -488,6 +488,68 @@ assert.isFalse(TEST_ONLY.computeIsImportant(m3, [m1, m2, m3])); }); + test('isImportant service user reviewer update vs other messages', () => { + const reviewerUpdateFromBot = { + ...randomMessage(), + author: { + _account_id: 123 as AccountId, + tags: [AccountTag.SERVICE_USER], + }, + type: 'REVIEWER_UPDATE' as const, + tag: MessageTag.TAG_REVIEWER_UPDATE as ReviewInputTag, + }; + const reviewerUpdateWithRealAuthorBot = { + ...randomMessage(), + author: { + _account_id: 456 as AccountId, + }, + real_author: { + _account_id: 123 as AccountId, + tags: [AccountTag.SERVICE_USER], + }, + type: 'REVIEWER_UPDATE' as const, + tag: MessageTag.TAG_REVIEWER_UPDATE as ReviewInputTag, + }; + const formattedReviewerUpdateWithRealAuthorBot = { + author: { + _account_id: 456 as AccountId, + }, + realAuthor: { + _account_id: 123 as AccountId, + tags: [AccountTag.SERVICE_USER], + }, + date: '2020-01-01 00:00:00.000000000' as Timestamp, + type: 'REVIEWER_UPDATE' as const, + tag: MessageTag.TAG_REVIEWER_UPDATE as const, + updates: [], + }; + const commentFromBot = randomMessage({ + author: { + _account_id: 123 as AccountId, + tags: [AccountTag.SERVICE_USER], + }, + message: 'Build succeeded: 10 tests passed', + }); + assert.isFalse( + TEST_ONLY.computeIsImportant(reviewerUpdateFromBot, [ + reviewerUpdateFromBot, + ]) + ); + assert.isFalse( + TEST_ONLY.computeIsImportant(reviewerUpdateWithRealAuthorBot, [ + reviewerUpdateWithRealAuthorBot, + ]) + ); + assert.isFalse( + TEST_ONLY.computeIsImportant(formattedReviewerUpdateWithRealAuthorBot, [ + formattedReviewerUpdateWithRealAuthorBot, + ]) + ); + assert.isTrue( + TEST_ONLY.computeIsImportant(commentFromBot, [commentFromBot]) + ); + }); + test('isImportant is evaluated after tag update', async () => { const m1 = randomMessage({ ...randomMessage(),
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts index a10e118..a56c329 100644 --- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts +++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -202,7 +202,7 @@ @property({type: Boolean, reflect: true}) disabled = false; - @state() + @property({type: Array}) draftCommentThreads: CommentThread[] = []; @property({type: Object}) @@ -229,7 +229,7 @@ @state() private docsBaseUrl = ''; - @state() + @property({type: String}) patchsetLevelDraftMessage = ''; @state() @@ -290,13 +290,13 @@ @state() mentionedUsers: AccountInput[] = []; - @state() + @property({type: Array}) mentionedUsersInUnresolvedDrafts: AccountInfo[] = []; @state() attentionCcsCount = 0; - @state() + @property({type: Object}) ccPendingConfirmation: SuggestedReviewerGroupInfo | null = null; @state() @@ -308,15 +308,15 @@ @state() pendingConfirmationDetails: SuggestedReviewerGroupInfo | null = null; - @state() + @property({type: Boolean}) includeComments = true; @state() autosubmitChecked = false; - @state() reviewers: AccountInput[] = []; + @property({type: Array}) reviewers: AccountInput[] = []; - @state() + @property({type: Object}) reviewerPendingConfirmation: SuggestedReviewerGroupInfo | null = null; @state() @@ -330,7 +330,7 @@ * not yet voted on a label) if a selected vote is different from the default * vote. */ - @state() + @property({type: Boolean}) labelsChanged = false; @state() @@ -1614,8 +1614,11 @@ // timer will be ended. this.reporting.time(Timing.SEND_REPLY); this.getNavigation().blockNavigation('sending review'); + const networkTimer = this.reporting.getTimer('SendReply - network'); return this.saveReview(reviewInput, errFn) .then(result => { + networkTimer.end(); + // change-info is not set only if request resulted in error. if (!result?.change_info) { return; @@ -1634,9 +1637,11 @@ current_revision: this.change?.current_revision, current_revision_number: this.change?.current_revision_number, }; + const parseTimer = this.reporting.getTimer('SendReply - parse'); this.getChangeModel().updateStateChange( GrReviewerUpdatesParser.parse(updatedChange as ChangeViewChangeInfo) ); + parseTimer.end(); if (reloadRequired) { fireReload(this); } else { @@ -1649,7 +1654,11 @@ this.includeComments = true; fireNoBubble(this, 'send', {}); fireIronAnnounce(this, 'Reply sent'); + const pluginAfterTimer = this.reporting.getTimer( + 'SendReply - pluginAfter' + ); this.getPluginLoader().jsApiService.handleReplySent(); + pluginAfterTimer.end(); }) .finally(async () => { this.getNavigation().releaseNavigation('sending review'); @@ -2251,6 +2260,13 @@ if (this.commentEditing || this.disabled) { return true; } + // If the user is creating an autosubmit flow then allow sending reply + if ( + this.autosubmitChecked && + !this.getFlowsModel().hasAutosubmitFlowAlready() + ) { + return false; + } if (this.canBeStarted === true) { return false; }
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts index a8f89d5..eed15b9 100644 --- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts +++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.ts
@@ -2347,6 +2347,20 @@ assert.isTrue(element.isSendDisabled()); }); + test('isSendDisabled_autosubmit', () => { + element.canBeStarted = false; + element.draftCommentThreads = []; + element.patchsetLevelDraftMessage = ''; + element.reviewersMutated = false; + element.labelsChanged = false; + element.includeComments = false; + element.disabled = false; + element.commentEditing = false; + element.account = makeAccount(); + element.autosubmitChecked = true; + assert.isFalse(element.isSendDisabled()); + }); + test('isSendDisabled_draftCommentsSend', () => { // Mock nonempty comment draft array; with sending comments. element.canBeStarted = false;
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts b/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts index a39917d..9ed3848 100644 --- a/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts +++ b/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts
@@ -109,7 +109,11 @@ div.section { margin: 0 var(--spacing-xl) var(--spacing-m) var(--spacing-xl); display: flex; - align-items: center; + align-items: flex-start; + } + .sectionContent { + flex: 1; + min-width: 0; } div.sectionIcon { flex: 0 0 30px; @@ -120,7 +124,6 @@ .section.condition > .sectionContent { background-color: var(--gray-background); padding: var(--spacing-m); - flex-grow: 1; } .button ~ .condition { margin-top: var(--spacing-m);
diff --git a/polygerrit-ui/app/elements/change/gr-trigger-vote-hovercard/gr-trigger-vote-hovercard.ts b/polygerrit-ui/app/elements/change/gr-trigger-vote-hovercard/gr-trigger-vote-hovercard.ts index d35855d..8678a8e 100644 --- a/polygerrit-ui/app/elements/change/gr-trigger-vote-hovercard/gr-trigger-vote-hovercard.ts +++ b/polygerrit-ui/app/elements/change/gr-trigger-vote-hovercard/gr-trigger-vote-hovercard.ts
@@ -47,6 +47,10 @@ div.sectionIcon { flex: 0 0 30px; } + .sectionContent { + flex: 1; + min-width: 0; + } div.sectionIcon gr-icon { position: relative; font-size: 20px; @@ -66,7 +70,7 @@ </div> <div class="section"> <div class="sectionIcon"> - <gr-icon icon="info" class="small"></gr-icon></span> + <gr-icon icon="info" class="small"></gr-icon> </div> <div class="sectionContent"> <div class="row">
diff --git a/polygerrit-ui/app/elements/chat-panel/chat-panel.ts b/polygerrit-ui/app/elements/chat-panel/chat-panel.ts index 62a8918..eb9db6b 100644 --- a/polygerrit-ui/app/elements/chat-panel/chat-panel.ts +++ b/polygerrit-ui/app/elements/chat-panel/chat-panel.ts
@@ -12,7 +12,13 @@ import './user-message'; import {css, html, LitElement} from 'lit'; -import {customElement, query, queryAll, state} from 'lit/decorators.js'; +import { + customElement, + property, + query, + queryAll, + state, +} from 'lit/decorators.js'; import { chatModelToken, @@ -37,7 +43,7 @@ @queryAll('gemini-message') private geminiMessages?: NodeListOf<HTMLElement>; - @state() turns: readonly Turn[] = []; + @property({type: Array}) turns: readonly Turn[] = []; @state() conversationId?: string;
diff --git a/polygerrit-ui/app/elements/chat-panel/gemini-message.ts b/polygerrit-ui/app/elements/chat-panel/gemini-message.ts index 753c6d6..ed4251f 100644 --- a/polygerrit-ui/app/elements/chat-panel/gemini-message.ts +++ b/polygerrit-ui/app/elements/chat-panel/gemini-message.ts
@@ -56,7 +56,7 @@ */ @property({type: Boolean}) isBackgroundRequest = false; - @state() turns: readonly Turn[] = []; + @property({type: Array}) turns: readonly Turn[] = []; @state() fileEntities: {[path: string]: NormalizedFileInfo} = {}; @@ -239,9 +239,9 @@ if (draft.range && draft.range.end_line < draft.range.start_line) { draft.range.end_line = draft.range.start_line; } - await this.getCommentsModel().saveDraft(draft); + const savedDraft = await this.getCommentsModel().saveDraft(draft); this.getCommentsModel().reloadAllComments(); - this.reportSuggestionToComment(); + this.reportSuggestionToComment(part.id, savedDraft.id); } private onRetry() { @@ -337,88 +337,91 @@ ${when( !message.errorMessage && responseParts.length > 0, () => html` - ${textParts.map( - responsePart => html` - <p class="text-content text-response"> - <gr-formatted-text - .markdown=${true} - .content=${responsePart.content} - ></gr-formatted-text> - </p> - ` - )} - ${when(!this.isBackgroundRequest, () => - this.sortedComments().map(comment => { - const displayLine = computeDisplayLine(comment.comment); - const lineNum = - typeof displayLine === 'string' && displayLine.startsWith('#') - ? Number(displayLine.substring(1)) - : typeof displayLine === 'number' - ? displayLine - : undefined; - return html` - ${when( - comment.comment.path, - () => html` - <button - class="comment-path link-button" - @click=${() => - this.handleFileClick( - comment.comment.path as string, - lineNum - )} - > - <gr-icon icon="description"></gr-icon> - ${comment.comment.path} - </button> - ` - )} - ${when( - displayLine, - () => html` - <button - class="comment-line link-button" - @click=${() => - this.handleFileClick( - comment.comment.path as string, - lineNum - )} - > - <gr-icon icon="code"></gr-icon> - ${displayLine} - </button> - ` - )} - <div class="suggested-comment"> - <p class="suggested-comment-message"> - <gr-formatted-text - .markdown=${true} - .content=${comment.comment.message} - ></gr-formatted-text> - </p> - <gr-button - primary - class="add-as-comment-button" - @click=${() => this.onAddAsComment(comment)} - >Add as Comment - </gr-button> - </div> - `; - }) - )} - ${when( - message.responseComplete && !this.isBackgroundRequest, - () => html` - <citations-box .turnIndex=${this.turnIndex}></citations-box> - <references-dropdown - .turnIndex=${this.turnIndex} - ></references-dropdown> - <message-actions - .turnId=${this.turnId()} - .isLatest=${this.isLatest} - ></message-actions> - ` - )} + <div @copy=${this.reportContentCopied}> + ${textParts.map( + responsePart => html` + <p class="text-content text-response"> + <gr-formatted-text + .markdown=${true} + .content=${responsePart.content} + ></gr-formatted-text> + </p> + ` + )} + ${when(!this.isBackgroundRequest, () => + this.sortedComments().map(comment => { + const displayLine = computeDisplayLine(comment.comment); + const lineNum = + typeof displayLine === 'string' && displayLine.startsWith('#') + ? Number(displayLine.substring(1)) + : typeof displayLine === 'number' + ? displayLine + : undefined; + return html` + ${when( + comment.comment.path, + () => html` + <button + class="comment-path link-button" + @click=${() => + this.handleFileClick( + comment.comment.path as string, + lineNum + )} + > + <gr-icon icon="description"></gr-icon> + ${comment.comment.path} + </button> + ` + )} + ${when( + displayLine, + () => html` + <button + class="comment-line link-button" + @click=${() => + this.handleFileClick( + comment.comment.path as string, + lineNum + )} + > + <gr-icon icon="code"></gr-icon> + ${displayLine} + </button> + ` + )} + <div class="suggested-comment"> + <p class="suggested-comment-message"> + <gr-formatted-text + .markdown=${true} + .content=${comment.comment.message} + ></gr-formatted-text> + </p> + <gr-button + primary + class="add-as-comment-button" + @click=${() => this.onAddAsComment(comment)} + >Add as Comment + </gr-button> + </div> + `; + }) + )} + ${when( + message.responseComplete && !this.isBackgroundRequest, + () => html` + <citations-box .turnIndex=${this.turnIndex}></citations-box> + <references-dropdown + .turnIndex=${this.turnIndex} + ></references-dropdown> + <message-actions + .turnId=${this.turnId()} + .isLatest=${this.isLatest} + @item-copied=${this.reportCopyButtonClicked} + ></message-actions> + ` + )} + </div> ` )} `; @@ -447,12 +450,17 @@ }; } - getAiAgentReportingDetails(): AiAgentEventDetails { + getAiAgentReportingDetails( + suggestionId?: number, + commentId?: string + ): AiAgentEventDetails { const agentId = this.turns[this.turnIndex]?.userMessage?.actionId ?? ''; return { agentId, conversationId: this.conversationId ?? '', turnIndex: this.turnIndex, + suggestionId, + commentId, }; } @@ -469,9 +477,23 @@ ); } - private reportSuggestionToComment() { + private reportSuggestionToComment(suggestionId: number, commentId?: string) { this.reportingService.reportInteraction( Interaction.AI_AGENT_SUGGESTION_TO_COMMENT, + this.getAiAgentReportingDetails(suggestionId, commentId) + ); + } + + private reportCopyButtonClicked() { + this.reportingService.reportInteraction( + Interaction.AI_AGENT_SUGGESTION_COPY_BUTTON_CLICKED, + this.getAiAgentReportingDetails() + ); + } + + private reportContentCopied() { + this.reportingService.reportInteraction( + Interaction.AI_AGENT_SUGGESTION_CONTENT_COPIED, this.getAiAgentReportingDetails() ); }
diff --git a/polygerrit-ui/app/elements/chat-panel/gemini-message_test.ts b/polygerrit-ui/app/elements/chat-panel/gemini-message_test.ts index a23a199..5618b06 100644 --- a/polygerrit-ui/app/elements/chat-panel/gemini-message_test.ts +++ b/polygerrit-ui/app/elements/chat-panel/gemini-message_test.ts
@@ -26,7 +26,7 @@ import {chatProvider, createChange} from '../../test/test-data-generators'; import {ParsedChangeInfo} from '../../types/types'; import {CommentsModel} from '../../models/comments/comments-model'; -import {AiAgentEventDetails, Interaction} from '../../constants/reporting'; +import {AiAgentChatEventDetails, Interaction} from '../../constants/reporting'; import {getAppContext} from '../../services/app-context'; suite('gemini-message tests', () => { @@ -260,7 +260,7 @@ reportStub.firstCall.args[0], Interaction.AI_AGENT_SUGGESTIONS_SHOWN ); - const details = reportStub.firstCall.args[1] as AiAgentEventDetails; + const details = reportStub.firstCall.args[1] as AiAgentChatEventDetails; assert.equal(details.conversationId, 'test-conversation-id'); assert.equal(details.agentId, 'custom-agent-id'); assert.equal(details.commentCount, 1); @@ -295,7 +295,7 @@ assert.isOk(commentContainer); sinon.stub(commentsModel, 'reloadAllComments'); - saveDraftStub.resolves({}); + saveDraftStub.resolves({id: 'test-comment-id'}); const button = commentContainer?.querySelector('gr-button'); assert.isOk(button); @@ -308,9 +308,163 @@ .find(c => c.args[0] === Interaction.AI_AGENT_SUGGESTION_TO_COMMENT); assert.isOk(call, 'Expected AI_AGENT_SUGGESTION_TO_COMMENT to be reported'); - const details = call.args[1] as AiAgentEventDetails; + const details = call.args[1] as AiAgentChatEventDetails; assert.equal(details.conversationId, 'test-conversation-id'); assert.equal(details.agentId, 'custom-agent-id'); + assert.equal(details.commentId, 'test-comment-id'); assert.isUndefined(details.commentCount); }); + + test('reports AI_AGENT_SUGGESTION_COPY_BUTTON_CLICKED interaction', async () => { + chatModel.updateState({ + ...chatModel.getState(), + id: 'test-conversation-id', + selectedModelId: 'gemini-model-id', + }); + const reportStub = sinon.stub( + getAppContext().reportingService, + 'reportInteraction' + ); + const turn = createTurn({ + responseComplete: true, + responseParts: [RESPONSE_TEXT], + }); + const updatedTurn = { + ...turn, + userMessage: {...turn.userMessage, actionId: 'custom-agent-id'}, + }; + chatModel.updateState({...chatModel.getState(), turns: [updatedTurn]}); + element.isLatest = true; + await element.updateComplete; + const messageActions = element.shadowRoot?.querySelector('message-actions'); + assert.isOk(messageActions); + messageActions?.dispatchEvent( + new CustomEvent('item-copied', {bubbles: true, composed: true}) + ); + + const call = reportStub + .getCalls() + .find( + c => c.args[0] === Interaction.AI_AGENT_SUGGESTION_COPY_BUTTON_CLICKED + ); + assert.isOk( + call, + 'Expected AI_AGENT_SUGGESTION_COPY_BUTTON_CLICKED to be reported' + ); + + const details = call.args[1] as AiAgentChatEventDetails; + assert.equal(details.conversationId, 'test-conversation-id'); + assert.equal(details.agentId, 'custom-agent-id'); + }); + + test('reports AI_AGENT_SUGGESTION_CONTENT_COPIED on text response copy', async () => { + chatModel.updateState({ + ...chatModel.getState(), + id: 'test-conversation-id', + selectedModelId: 'gemini-model-id', + }); + const reportStub = sinon.stub( + getAppContext().reportingService, + 'reportInteraction' + ); + const turn = createTurn({ + responseComplete: true, + responseParts: [RESPONSE_TEXT], + }); + const updatedTurn = { + ...turn, + userMessage: {...turn.userMessage, actionId: 'custom-agent-id'}, + }; + chatModel.updateState({...chatModel.getState(), turns: [updatedTurn]}); + await element.updateComplete; + const textResponse = element.shadowRoot?.querySelector('.text-response'); + assert.isOk(textResponse); + + textResponse?.dispatchEvent( + new CustomEvent('copy', {bubbles: true, composed: true}) + ); + + const call = reportStub + .getCalls() + .find(c => c.args[0] === Interaction.AI_AGENT_SUGGESTION_CONTENT_COPIED); + assert.isOk( + call, + 'Expected AI_AGENT_SUGGESTION_CONTENT_COPIED to be reported' + ); + }); + + test('reports AI_AGENT_SUGGESTION_CONTENT_COPIED on suggested comment copy', async () => { + chatModel.updateState({ + ...chatModel.getState(), + id: 'test-conversation-id', + selectedModelId: 'gemini-model-id', + }); + const reportStub = sinon.stub( + getAppContext().reportingService, + 'reportInteraction' + ); + const turn = createTurn({ + responseComplete: true, + responseParts: [RESPONSE_CREATE_COMMENT], + }); + const updatedTurn = { + ...turn, + userMessage: {...turn.userMessage, actionId: 'custom-agent-id'}, + }; + chatModel.updateState({...chatModel.getState(), turns: [updatedTurn]}); + await element.updateComplete; + const commentContainer = + element.shadowRoot?.querySelector('.suggested-comment'); + assert.isOk(commentContainer); + + commentContainer?.dispatchEvent( + new CustomEvent('copy', {bubbles: true, composed: true}) + ); + + const call = reportStub + .getCalls() + .find(c => c.args[0] === Interaction.AI_AGENT_SUGGESTION_CONTENT_COPIED); + assert.isOk( + call, + 'Expected AI_AGENT_SUGGESTION_CONTENT_COPIED to be reported' + ); + }); + + test('reports AI_AGENT_SUGGESTION_CONTENT_COPIED on citations copy', async () => { + chatModel.updateState({ + ...chatModel.getState(), + id: 'test-conversation-id', + selectedModelId: 'gemini-model-id', + }); + const reportStub = sinon.stub( + getAppContext().reportingService, + 'reportInteraction' + ); + const turn = createTurn({ + responseComplete: true, + responseParts: [RESPONSE_TEXT], + citations: ['http://example.com'], + }); + const updatedTurn = { + ...turn, + userMessage: {...turn.userMessage, actionId: 'custom-agent-id'}, + }; + chatModel.updateState({...chatModel.getState(), turns: [updatedTurn]}); + element.isLatest = true; + await element.updateComplete; + const citationsBox = element.shadowRoot?.querySelector('citations-box'); + assert.isOk(citationsBox); + + citationsBox?.dispatchEvent( + new CustomEvent('copy', {bubbles: true, composed: true}) + ); + + const call = reportStub + .getCalls() + .find(c => c.args[0] === Interaction.AI_AGENT_SUGGESTION_CONTENT_COPIED); + assert.isOk( + call, + 'Expected AI_AGENT_SUGGESTION_CONTENT_COPIED to be reported' + ); + }); });
diff --git a/polygerrit-ui/app/elements/chat-panel/prompt-box.ts b/polygerrit-ui/app/elements/chat-panel/prompt-box.ts index 4290371..b1325fb 100644 --- a/polygerrit-ui/app/elements/chat-panel/prompt-box.ts +++ b/polygerrit-ui/app/elements/chat-panel/prompt-box.ts
@@ -54,7 +54,7 @@ @state() selectedModel?: ModelInfo; - @state() userInput = ''; + @property({type: String}) userInput = ''; @state() previousMessageIndex = -1; @@ -70,7 +70,7 @@ @state() showAllContextItems = false; - @state() contextItemTypes: readonly ContextItemType[] = []; + @property({type: Array}) contextItemTypes: readonly ContextItemType[] = []; @state() private change?: ParsedChangeInfo;
diff --git a/polygerrit-ui/app/elements/chat-panel/splash-page-action.ts b/polygerrit-ui/app/elements/chat-panel/splash-page-action.ts index ca69b5e..5313f38 100644 --- a/polygerrit-ui/app/elements/chat-panel/splash-page-action.ts +++ b/polygerrit-ui/app/elements/chat-panel/splash-page-action.ts
@@ -6,10 +6,11 @@ import '@material/web/iconbutton/icon-button.js'; import '@material/web/progress/circular-progress.js'; -import {css, html, LitElement} from 'lit'; +import {css, html, LitElement, nothing} from 'lit'; import {customElement, property, query, state} from 'lit/decorators.js'; import {classMap} from 'lit/directives/class-map.js'; import {when} from 'lit/directives/when.js'; +import {ifDefined} from 'lit/directives/if-defined.js'; import '../shared/gr-icon/gr-icon'; import '../shared/gr-button/gr-button'; @@ -18,6 +19,7 @@ import {Action, ContextItemType} from '../../api/ai-code-review'; import {chatModelToken} from '../../models/chat/chat-model'; import {parseLink} from '../../models/chat/context-item-util'; + import {resolve} from '../../models/dependency'; import {isDefined} from '../../types/types'; import {fireAlert} from '../../utils/event-util'; @@ -32,6 +34,8 @@ */ @customElement('splash-page-action') export class SplashPageAction extends LitElement { + private static readonly COLLAPSED_HEIGHT = 100; + @property({type: Object}) action?: Action; @property({type: Boolean}) isFirst = false; @@ -40,6 +44,12 @@ @state() contextItemTypes: readonly ContextItemType[] = []; + @state() private isInstructionExpanded = false; + + @state() private isFilesExpanded = false; + + @state() private showExpandButton = false; + @query('#detailsModal') private detailsModal?: HTMLDialogElement; private readonly getChatModel = resolve(this, chatModelToken); @@ -177,7 +187,9 @@ padding: var(--spacing-m) var(--spacing-xl); background-color: var(--dialog-background-color); flex: 1; - overflow: auto; + display: flex; + flex-direction: column; + overflow: hidden; } .info-button:hover { background-color: var(--hover-background-color, rgba(0, 0, 0, 0.08)); @@ -202,8 +214,7 @@ #detailsModal { width: calc(72ch + 2px + 2 * var(--spacing-m) + 0.4px); max-width: 90vw; - height: 300px; - max-height: 90vh; + max-height: 80vh; } #detailsModal > div { display: flex; @@ -234,6 +245,51 @@ .modal-row-text { color: var(--primary-text-color); } + .modal-row-text a { + color: var(--info-foreground); + } + .instruction-row { + flex: 1; + min-height: 0; + } + .instruction-row .modal-row-content { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; + } + .instruction-text { + flex: 1; + } + .instruction-text.collapsed { + max-height: ${SplashPageAction.COLLAPSED_HEIGHT}px; + overflow: hidden; + } + .instruction-text.expanded { + max-height: none; + overflow-y: auto; + } + .expand-button { + color: var(--link-color); + cursor: pointer; + padding: var(--spacing-xs) 0; + font-size: var(--font-size-small); + background: none; + border: none; + text-align: left; + } + .expand-button:hover { + text-decoration: underline; + } + .file-list { + display: flex; + flex-direction: column; + gap: var(--spacing-xs); + } + .file-item { + word-break: break-all; + color: var(--primary-text-color); + } .link-row { display: flex; align-items: center; @@ -309,13 +365,69 @@ ${when( this.action?.initial_user_prompt, () => html` - <div class="modal-row"> + <div class="modal-row instruction-row"> <gr-icon icon="terminal"></gr-icon> <div class="modal-row-content"> <div class="modal-row-title">Instruction:</div> - <div class="modal-row-text"> + <div + class="modal-row-text instruction-text ${this + .isInstructionExpanded + ? 'expanded' + : 'collapsed'}" + > ${this.action?.initial_user_prompt} </div> + ${when( + this.showExpandButton, + () => html` + <button + class="expand-button" + aria-expanded=${this.isInstructionExpanded} + @click=${() => + (this.isInstructionExpanded = + !this.isInstructionExpanded)} + > + ${this.isInstructionExpanded + ? 'Show less' + : 'Show more'} + </button> + ` + )} + </div> + </div> + ` + )} + ${when( + this.action?.matched_files && + this.action.matched_files.length > 0, + () => html` + <div class="modal-row matched-files-row"> + <gr-icon icon="folder"></gr-icon> + <div class="modal-row-content"> + <div class="modal-row-text"> + ${this.renderMatchedFiles()} + </div> + </div> + </div> + ` + )} + ${when( + this.action?.capability_definition_url, + () => html` + <div class="modal-row"> + <gr-icon icon="link"></gr-icon> + <div class="modal-row-content"> + <div class="modal-row-text"> + <a + href=${ifDefined( + this.action?.capability_definition_url + )} + target="_blank" + rel="noopener noreferrer" + > + Capability Definition + </a> + </div> </div> </div> ` @@ -365,9 +477,46 @@ } } - private displayDetailsCard(event: MouseEvent) { + private renderMatchedFiles() { + const files = this.action?.matched_files || []; + if (files.length === 0) return nothing; + + const showAll = this.isFilesExpanded || files.length <= 4; + const filesToDisplay = showAll ? files : files.slice(0, 4); + + return html` + <div class="modal-row-title">Matched files:</div> + <div class="file-list ${this.isFilesExpanded ? 'expanded' : ''}"> + ${filesToDisplay.map( + file => html`<div class="file-item">${file}</div>` + )} + </div> + ${when( + files.length > 4, + () => html` + <button + class="expand-button" + aria-expanded=${this.isFilesExpanded} + @click=${() => (this.isFilesExpanded = !this.isFilesExpanded)} + > + ${this.isFilesExpanded ? 'Show less' : 'Show more'} + </button> + ` + )} + `; + } + + private async displayDetailsCard(event: MouseEvent) { event.stopPropagation(); + this.isInstructionExpanded = false; + this.isFilesExpanded = false; this.detailsModal?.showModal(); + await this.updateComplete; + const textEl = this.shadowRoot?.querySelector('.instruction-text'); + if (textEl) { + this.showExpandButton = + textEl.scrollHeight > SplashPageAction.COLLAPSED_HEIGHT; + } } }
diff --git a/polygerrit-ui/app/elements/chat-panel/splash-page-action_screenshot_test.ts b/polygerrit-ui/app/elements/chat-panel/splash-page-action_screenshot_test.ts index 5dd2f4f..20f5891 100644 --- a/polygerrit-ui/app/elements/chat-panel/splash-page-action_screenshot_test.ts +++ b/polygerrit-ui/app/elements/chat-panel/splash-page-action_screenshot_test.ts
@@ -39,6 +39,7 @@ id: 'test-action', display_text: 'Test Action', initial_user_prompt: 'Test prompt', + capability_definition_url: 'http://cs/depot/google3/some/file.ts', }; element.action = action; await element.updateComplete; @@ -59,4 +60,58 @@ await visualDiff(modal, 'splash-page-action-details-modal'); await visualDiffDarkTheme(modal, 'splash-page-action-details-modal'); }); + + test('details modal rendering with long instructions and matched files', async () => { + const action: Action = { + id: 'test-action', + display_text: 'Test Action', + initial_user_prompt: + 'This is a long instruction text. We want to test that it collapses properly and can be expanded. ' + + 'By forcing the state in the test, we guarantee that the button appears regardless of font rendering differences.', + capability_definition_url: 'http://cs/depot/google3/some/file.ts', + matched_files: [ + 'file1.txt', + 'file2.txt', + 'file3.txt', + 'file4.txt', + 'file5.txt', + ], + }; + element.action = action; + await element.updateComplete; + + // Trigger the modal to open + const infoButton = element.shadowRoot?.querySelector( + '.info-button' + ) as HTMLElement; + assert.isOk(infoButton); + infoButton.click(); + + await element.updateComplete; + + const modal = element.shadowRoot?.querySelector( + '#detailsModal' + ) as HTMLElement; + assert.isOk(modal); + + // Force the showExpandButton state to true to make the test robust + (element as unknown as {showExpandButton: boolean}).showExpandButton = true; + await element.updateComplete; + + await visualDiff(modal, 'splash-page-action-details-modal-long'); + await visualDiffDarkTheme(modal, 'splash-page-action-details-modal-long'); + + // Force the isInstructionExpanded and isFilesExpanded state to true to test expanded rendering + ( + element as unknown as {isInstructionExpanded: boolean} + ).isInstructionExpanded = true; + (element as unknown as {isFilesExpanded: boolean}).isFilesExpanded = true; + await element.updateComplete; + + await visualDiff(modal, 'splash-page-action-details-modal-expanded'); + await visualDiffDarkTheme( + modal, + 'splash-page-action-details-modal-expanded' + ); + }); });
diff --git a/polygerrit-ui/app/elements/chat-panel/splash-page-action_test.ts b/polygerrit-ui/app/elements/chat-panel/splash-page-action_test.ts index 803694a..07bb480 100644 --- a/polygerrit-ui/app/elements/chat-panel/splash-page-action_test.ts +++ b/polygerrit-ui/app/elements/chat-panel/splash-page-action_test.ts
@@ -89,11 +89,13 @@ <div role="dialog" aria-labelledby="detailsTitle"> <h3 class="heading-3 modalHeader" id="detailsTitle">Test Action</h3> <div class="detailsContent"> - <div class="modal-row"> + <div class="modal-row instruction-row"> <gr-icon icon="terminal"></gr-icon> <div class="modal-row-content"> <div class="modal-row-title">Instruction:</div> - <div class="modal-row-text">Test prompt</div> + <div class="modal-row-text instruction-text collapsed"> + Test prompt + </div> </div> </div> </div> @@ -166,4 +168,96 @@ const turns = chatModel.getState().turns; assert.lengthOf(turns, 0); }); + test('renders capability definition link when URL is present', async () => { + const action: Action = { + id: 'test-action', + display_text: 'Test Action', + capability_definition_url: 'https://example.com', + }; + element.action = action; + await element.updateComplete; + + const modal = element.shadowRoot?.querySelector('#detailsModal'); + assert.isOk(modal); + + const sourceLink = modal?.querySelector( + '.modal-row-text a' + ) as HTMLAnchorElement; + assert.isOk(sourceLink); + assert.equal(sourceLink.getAttribute('href'), 'https://example.com'); + assert.equal(sourceLink.innerText.trim(), 'Capability Definition'); + }); + + test('does not render link when URL is absent', async () => { + const action: Action = { + id: 'test-action', + display_text: 'Test Action', + }; + element.action = action; + await element.updateComplete; + + const modal = element.shadowRoot?.querySelector('#detailsModal'); + assert.isOk(modal); + + const sourceLink = modal?.querySelector('.modal-row-text a'); + assert.isNull(sourceLink); + }); + + test('renders with matched_files', async () => { + const action: Action = { + id: 'test-action', + display_text: 'Test Action', + matched_files: ['file1.txt', 'file2.txt'], + }; + element.action = action; + await element.updateComplete; + + const modal = element.shadowRoot?.querySelector('#detailsModal'); + assert.isOk(modal); + + const fileItems = modal?.querySelectorAll('.file-item'); + assert.equal(fileItems?.length, 2); + assert.equal(fileItems?.[0].textContent?.trim(), 'file1.txt'); + assert.equal(fileItems?.[1].textContent?.trim(), 'file2.txt'); + + const expandButton = modal?.querySelector( + '.matched-files-row .expand-button' + ); + assert.isNotOk(expandButton); + }); + + test('renders with many matched_files and expands', async () => { + const action: Action = { + id: 'test-action', + display_text: 'Test Action', + matched_files: [ + 'file1.txt', + 'file2.txt', + 'file3.txt', + 'file4.txt', + 'file5.txt', + ], + }; + element.action = action; + await element.updateComplete; + + const modal = element.shadowRoot?.querySelector('#detailsModal'); + assert.isOk(modal); + + let fileItems = modal?.querySelectorAll('.file-item'); + assert.equal(fileItems?.length, 4); + + const expandButton = modal?.querySelector( + '.matched-files-row .expand-button' + ) as HTMLElement; + assert.isOk(expandButton); + assert.equal(expandButton.innerText.trim(), 'Show more'); + + expandButton.click(); + await element.updateComplete; + + fileItems = modal?.querySelectorAll('.file-item'); + assert.equal(fileItems?.length, 5); + assert.equal(expandButton.innerText.trim(), 'Show less'); + }); });
diff --git a/polygerrit-ui/app/elements/chat-panel/splash-page.ts b/polygerrit-ui/app/elements/chat-panel/splash-page.ts index f44c838..dc69086 100644 --- a/polygerrit-ui/app/elements/chat-panel/splash-page.ts +++ b/polygerrit-ui/app/elements/chat-panel/splash-page.ts
@@ -10,6 +10,7 @@ import '@material/web/progress/circular-progress.js'; import './gemini-message'; import './splash-page-action'; +import '../plugins/gr-endpoint-decorator/gr-endpoint-decorator'; import {css, html, LitElement} from 'lit'; import {customElement, property, state} from 'lit/decorators.js'; @@ -20,6 +21,12 @@ import {chatModelToken, Turn} from '../../models/chat/chat-model'; import {resolve} from '../../models/dependency'; import {userModelToken} from '../../models/user/user-model'; +import { + CheckRun, + checksModelToken, + RunResult, +} from '../../models/checks/checks-model'; +import {changeModelToken} from '../../models/change/change-model'; import {AccountDetailInfo, ServerInfo} from '../../types/common'; import {subscribe} from '../lit/subscription-controller'; import {getDisplayName} from '../../utils/display-name-util'; @@ -46,10 +53,20 @@ @property({type: Boolean}) isChangePrivate = false; + @state() runs: readonly CheckRun[] = []; + + @state() results: readonly RunResult[] = []; + + @state() changeNum?: number; + private readonly getChatModel = resolve(this, chatModelToken); private readonly getUserModel = resolve(this, userModelToken); + private readonly getChecksModel = resolve(this, checksModelToken); + + private readonly getChangeModel = resolve(this, changeModelToken); + constructor() { super(); subscribe( @@ -87,6 +104,21 @@ () => this.getUserModel().account$, x => (this.account = x) ); + subscribe( + this, + () => this.getChecksModel().allRunsSelectedPatchset$, + x => (this.runs = x ?? []) + ); + subscribe( + this, + () => this.getChecksModel().allResultsSelected$, + x => (this.results = x ?? []) + ); + subscribe( + this, + () => this.getChangeModel().changeNum$, + x => (this.changeNum = x) + ); } private get currentTurn(): Turn | undefined { @@ -255,8 +287,19 @@ `; } return html` - ${this.renderBackgroundRequest()} ${this.renderCustomActions()} - ${this.renderActions()} + ${this.renderBackgroundRequest()} + <gr-endpoint-decorator name="chat-panel-splash-extra"> + <gr-endpoint-param name="runs" .value=${this.runs}></gr-endpoint-param> + <gr-endpoint-param + name="results" + .value=${this.results} + ></gr-endpoint-param> + <gr-endpoint-param + name="changeNum" + .value=${this.changeNum} + ></gr-endpoint-param> + </gr-endpoint-decorator> + ${this.renderCustomActions()} ${this.renderActions()} `; }
diff --git a/polygerrit-ui/app/elements/chat-panel/splash-page_test.ts b/polygerrit-ui/app/elements/chat-panel/splash-page_test.ts index 825eb01..28099c2 100644 --- a/polygerrit-ui/app/elements/chat-panel/splash-page_test.ts +++ b/polygerrit-ui/app/elements/chat-panel/splash-page_test.ts
@@ -53,6 +53,11 @@ <div class="splash-container"> <h1 class="splash-greeting">Hello,</h1> <p class="splash-question">How can I help you today?</p> + <gr-endpoint-decorator name="chat-panel-splash-extra"> + <gr-endpoint-param name="runs"></gr-endpoint-param> + <gr-endpoint-param name="results"></gr-endpoint-param> + <gr-endpoint-param name="changeNum"></gr-endpoint-param> + </gr-endpoint-decorator> <div class="action-container-title suggested-actions-title"> Capabilities </div>
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts index 020be1b..219d0c0 100644 --- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts +++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -18,6 +18,7 @@ import {customElement, property, query, state} from 'lit/decorators.js'; import './gr-checks-action'; import './gr-hovercard-run'; +import './gr-checks-tag'; import '../shared/gr-tooltip-content/gr-tooltip-content'; import {KnownExperimentId} from '../../services/flags/flags'; import { @@ -27,7 +28,6 @@ LinkIcon, NOT_USEFUL, RunStatus, - Tag, USEFUL, } from '../../api/checks'; import {sharedStyles} from '../../styles/shared-styles'; @@ -49,6 +49,8 @@ LATEST_ATTEMPT, otherPrimaryLinks, rectifyFix, + reportAiAgentGetAIFix, + reportAiAgentSuggestionCopy, secondaryLinks, sortAttemptChoices, stringToAttemptChoice, @@ -314,33 +316,7 @@ tr.detailsRow.collapsed { display: none; } - td .summary-cell .tags .tag { - color: var(--primary-text-color); - display: inline-block; - border-radius: 20px; - background-color: var(--tag-background); - padding: 0 var(--spacing-m); - margin-left: var(--spacing-s); - cursor: pointer; - } - td .summary-cell .tag.gray { - background-color: var(--tag-gray); - } - td .summary-cell .tag.yellow { - background-color: var(--tag-yellow); - } - td .summary-cell .tag.pink { - background-color: var(--tag-pink); - } - td .summary-cell .tag.purple { - background-color: var(--tag-purple); - } - td .summary-cell .tag.cyan { - background-color: var(--tag-cyan); - } - td .summary-cell .tag.brown { - background-color: var(--tag-brown); - } + .actions-shown-on-collapsed gr-checks-action, .actions gr-checks-action, .actions gr-dropdown { @@ -410,6 +386,11 @@ ); } + private handleCopy() { + if (!this.result) return; + reportAiAgentSuggestionCopy(this.reporting, this.result); + } + override render() { if (!this.result) return ''; if (!this.shouldRender) { @@ -427,7 +408,10 @@ ? html`<gr-icon small icon="ai"></gr-icon>` : nothing; return html` - <tr class=${classMap({container: true, collapsed: !this.isExpanded})}> + <tr + class=${classMap({container: true, collapsed: !this.isExpanded})} + @copy=${this.handleCopy} + > <td class="nameCol" @click=${this.toggleExpandedClick}> <div class="flex"> <gr-hovercard-run .run=${this.result}></gr-hovercard-run> @@ -453,7 +437,12 @@ </div> ${this.renderLinks()} ${this.renderActions()} <div class="tags"> - ${(this.result.tags ?? []).map(t => this.renderTag(t))} + ${(this.result.tags ?? []).map( + t => html`<gr-checks-tag + .tag=${t} + @click=${(e: MouseEvent) => this.tagClick(e, t.name)} + ></gr-checks-tag>` + )} </div> ${this.renderLabel()} </div> @@ -476,7 +465,10 @@ </div> </td> </tr> - <tr class=${classMap({detailsRow: true, collapsed: !this.isExpanded})}> + <tr + class=${classMap({detailsRow: true, collapsed: !this.isExpanded})} + @copy=${this.handleCopy} + > <td class="expandedCol" colspan="3">${this.renderExpanded()}</td> </tr> `; @@ -591,7 +583,7 @@ renderLink(link?: Link) { // The expanded state renders all links in more detail. Hide in summary. if (this.isExpanded) return; - if (!link) return; + if (!link?.url?.trim()) return; const tooltipText = link.tooltip ?? tooltipForLink(link.icon); const icon = iconForLink(link.icon); return html`<gr-tooltip-content @@ -701,22 +693,6 @@ ></gr-checks-action>`; } - renderTag(tag: Tag) { - return html`<gr-tooltip-content - has-tooltip - ?position-below=${true} - title=${tag.tooltip ?? - 'A category tag for this check result. Click to filter.'} - > - <button - class="tag ${tag.color}" - @click=${(e: MouseEvent) => this.tagClick(e, tag.name)} - > - <span>${tag.name}</span> - </button> - </gr-tooltip-content>`; - } - private renderSuggestionPreview() { if (!this.suggestion) return nothing; return html`<gr-checks-fix-preview @@ -730,6 +706,7 @@ if (!this.result || !this.result.message || !codePointer) return; this.suggestionLoading = true; + reportAiAgentGetAIFix(this.reporting, this.result); let suggestion: FixSuggestionInfo | undefined; try { suggestion = await this.getSuggestionsService().generateSuggestedFix({ @@ -804,6 +781,9 @@ .ai-generated { font-weight: var(--font-weight-medium); } + .header-content { + margin-bottom: var(--spacing-m); + } `, ]; } @@ -811,9 +791,11 @@ override render() { if (!this.result) return ''; return html` - ${this.renderFirstPrimaryLink()} ${this.renderOtherPrimaryLinks()} - ${this.renderSecondaryLinks()} ${this.renderCodePointers()} - ${this.renderAiLabel()} + <div class="header-content"> + ${this.renderFirstPrimaryLink()} ${this.renderOtherPrimaryLinks()} + ${this.renderSecondaryLinks()} ${this.renderCodePointers()} + ${this.renderAiLabel()} + </div> <gr-endpoint-decorator name="check-result-expanded" .targetPlugin=${this.result.pluginName} @@ -829,7 +811,14 @@ .content=${this.result.message ?? ''} ></gr-formatted-text> </gr-endpoint-decorator> - ${this.renderFix()} ${this.renderNotUseful()} + ${this.renderFix()} + <gr-endpoint-decorator name="check-result-feedback"> + <gr-endpoint-param + name="result" + .value=${this.result} + ></gr-endpoint-param> + ${this.renderNotUseful()} + </gr-endpoint-decorator> `; } @@ -934,7 +923,7 @@ } private renderLink(link?: Link, targetBlank = true) { - if (!link) return; + if (!link?.url?.trim()) return; const text = link.tooltip ?? tooltipForLink(link.icon); const target = targetBlank ? '_blank' : undefined; const icon = iconForLink(link.icon); @@ -973,7 +962,7 @@ @query('#filterInput') filterInput?: HTMLInputElement; - @state() + @property({type: String}) filterRegExp = ''; /** All runs. Shown should only the selected/filtered ones. */ @@ -1161,6 +1150,10 @@ .headerBottomRow a { margin-right: var(--spacing-l); } + #moreActions { + --gr-dropdown-position: relative; + --gr-dropdown-z-index: 120; + } #moreActions gr-icon { color: var(--link-color); } @@ -1421,7 +1414,7 @@ } private renderLink(link?: Link) { - if (!link) return; + if (!link?.url?.trim()) return; const tooltipText = link.tooltip ?? tooltipForLink(link.icon); const icon = iconForLink(link.icon); return html`<gr-tooltip-content
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results_test.ts b/polygerrit-ui/app/elements/checks/gr-checks-results_test.ts index 0c93d1f..9324c74 100644 --- a/polygerrit-ui/app/elements/checks/gr-checks-results_test.ts +++ b/polygerrit-ui/app/elements/checks/gr-checks-results_test.ts
@@ -11,19 +11,25 @@ GrResultRow, } from './gr-checks-results'; import {html} from 'lit'; -import {assert, fixture} from '@open-wc/testing'; +import {assert, fixture, waitUntil} from '@open-wc/testing'; import {checksModelToken, RunResult} from '../../models/checks/checks-model'; import { checkRun0, checkRun1, + checkRun6, setAllcheckRuns, } from '../../test/test-data-generators'; import {resolve} from '../../models/dependency'; import {createLabelInfo} from '../../test/test-data-generators'; import {assertIsDefined, query, queryAndAssert} from '../../utils/common-util'; import {stubFlags} from '../../test/test-utils'; -import {PatchSetNumber} from '../../api/rest-api'; +import {Interaction} from '../../constants/reporting'; +import {FixId, NumericChangeId, PatchSetNumber} from '../../api/rest-api'; import {GrDropdownList} from '../shared/gr-dropdown-list/gr-dropdown-list'; +import {getAppContext} from '../../services/app-context'; +import {suggestionsServiceToken} from '../../services/suggestions/suggestions-service'; +import {testResolver} from '../../test/common-test-setup'; +import {Link} from '../../api/checks'; suite('gr-result-row test', () => { let element: GrResultRow; @@ -101,24 +107,8 @@ </div> <div class="message"></div> <div class="tags"> - <gr-tooltip-content - has-tooltip="" - position-below="" - title="A category tag for this check result. Click to filter." - > - <button class="tag"> - <span> OBSOLETE </span> - </button> - </gr-tooltip-content> - <gr-tooltip-content - has-tooltip="" - position-below="" - title="A category tag for this check result. Click to filter." - > - <button class="tag"> - <span> E2E </span> - </button> - </gr-tooltip-content> + <gr-checks-tag> </gr-checks-tag> + <gr-checks-tag> </gr-checks-tag> </div> </div> <div @@ -147,8 +137,7 @@ await element.updateComplete; assert.isFalse(element.isExpanded); - const summaryDiv: HTMLElement = - element.shadowRoot!.querySelector('.summary')!; + const summaryDiv = queryAndAssert<HTMLElement>(element, '.summary'); summaryDiv.click(); await element.updateComplete; assert.isTrue(element.isExpanded); @@ -157,6 +146,101 @@ await element.updateComplete; assert.isFalse(element.isExpanded); }); + + test('reports AI_AGENT_GET_FIX_CLICKED when AI fix clicked', async () => { + const reportingStub = sinon.stub( + getAppContext().reportingService, + 'reportInteraction' + ); + const checksModel = testResolver(checksModelToken); + checksModel.changeNum = 123 as NumericChangeId; + const suggestionsService = testResolver(suggestionsServiceToken); + sinon + .stub(suggestionsService, 'isGeneratedSuggestedFixEnabled') + .returns(true); + sinon.stub(suggestionsService, 'generateSuggestedFix').resolves({ + description: 'AI suggested fix', + replacements: [], + fix_id: '1' as FixId, + }); + + element.isOwner = true; + element.result = { + ...element.result!, + message: 'Test message', + externalId: JSON.stringify({ + agentId: 'test-agent', + conversationId: 'test-conv', + turnIndex: 1, + }), + codePointers: [{path: 'test/path', range: {start_line: 1, end_line: 1}}], + } as RunResult; + // Mock the button or just call handleAIFix + // In gr-result-row, handleAIFix is private, but it's triggered by 'get-ai-fix-for-check-result' event + element.dispatchEvent(new CustomEvent('get-ai-fix-for-check-result')); + await waitUntil(() => + reportingStub.calledWith( + Interaction.AI_AGENT_GET_FIX_CLICKED, + sinon.match({ + agentId: 'test-agent', + conversationId: 'test-conv', + turnIndex: 1, + }) + ) + ); + }); + + test('reports AI_AGENT_SUGGESTION_CONTENT_COPIED when result is copied', async () => { + const reportingStub = sinon.stub( + getAppContext().reportingService, + 'reportInteraction' + ); + element.result = { + ...element.result!, + externalId: JSON.stringify({ + agentId: 'test-agent', + conversationId: 'test-conv', + turnIndex: 1, + }), + } as RunResult; + await element.updateComplete; + + const containerTr = queryAndAssert(element, 'tr.container'); + containerTr.dispatchEvent(new Event('copy')); + + assert.isTrue( + reportingStub.calledWith( + Interaction.AI_AGENT_SUGGESTION_CONTENT_COPIED, + sinon.match({ + agentId: 'test-agent', + conversationId: 'test-conv', + turnIndex: 1, + }) + ) + ); + + reportingStub.resetHistory(); + + const detailsTr = queryAndAssert(element, 'tr.detailsRow'); + detailsTr.dispatchEvent(new Event('copy')); + + assert.isTrue( + reportingStub.calledWith( + Interaction.AI_AGENT_SUGGESTION_CONTENT_COPIED, + sinon.match({ + agentId: 'test-agent', + conversationId: 'test-conv', + turnIndex: 1, + }) + ) + ); + }); + + test('renderLink returns undefined when url is empty, whitespace, or missing', () => { + assert.isUndefined(element.renderLink({url: ''} as Link)); + assert.isUndefined(element.renderLink({url: ' '} as Link)); + assert.isUndefined(element.renderLink(undefined)); + }); }); suite('gr-result-expanded test', () => { @@ -176,97 +260,102 @@ assert.shadowDom.equal( element, /* HTML */ ` - <div class="links"> - <a - href="https://google.com" - rel="noopener noreferrer" - target="_blank" - > - <gr-icon class="link" icon="download"> </gr-icon> - <span> Download </span> - </a> - <a - href="https://google.com" - rel="noopener noreferrer" - target="_blank" - > - <gr-icon class="link" icon="system_update"> </gr-icon> - <span> Download </span> - </a> - <a - href="https://google.com" - rel="noopener noreferrer" - target="_blank" - > - <gr-icon class="link" filled="" icon="image"> </gr-icon> - <span> Link to image </span> - </a> - <a - href="https://google.com" - rel="noopener noreferrer" - target="_blank" - > - <gr-icon class="link" filled="" icon="image"> </gr-icon> - <span> Link to image </span> - </a> - <a - href="https://google.com" - rel="noopener noreferrer" - target="_blank" - > - <gr-icon class="link" filled="" icon="bug_report"> </gr-icon> - <span> Link for reporting a problem </span> - </a> - <a - href="https://google.com" - rel="noopener noreferrer" - target="_blank" - > - <gr-icon class="link" icon="help"> </gr-icon> - <span> Link to help page </span> - </a> - <a - href="https://google.com" - rel="noopener noreferrer" - target="_blank" - > - <gr-icon class="link" icon="history"> </gr-icon> - <span> Link to result history </span> - </a> - </div> - <div class="links"> - <a - href="https://google.com" - rel="noopener noreferrer" - target="_blank" - > - <gr-icon class="link" icon="open_in_new"> </gr-icon> - <span> Link to details </span> - </a> - <a - href="https://google.com" - rel="noopener noreferrer" - target="_blank" - > - <gr-icon class="link" filled="" icon="image"> </gr-icon> - <span> Link to image </span> - </a> - </div> - <div> - <gr-icon custom="" icon="ai" small=""> </gr-icon> - <span class="ai-generated"> AI Generated </span> - by FAKE Error Finder Finder Finder Finder Finder Finder Finder + <div class="header-content"> + <div class="links"> + <a + href="https://google.com" + rel="noopener noreferrer" + target="_blank" + > + <gr-icon class="link" icon="download"> </gr-icon> + <span> Download </span> + </a> + <a + href="https://google.com" + rel="noopener noreferrer" + target="_blank" + > + <gr-icon class="link" icon="system_update"> </gr-icon> + <span> Download </span> + </a> + <a + href="https://google.com" + rel="noopener noreferrer" + target="_blank" + > + <gr-icon class="link" filled="" icon="image"> </gr-icon> + <span> Link to image </span> + </a> + <a + href="https://google.com" + rel="noopener noreferrer" + target="_blank" + > + <gr-icon class="link" filled="" icon="image"> </gr-icon> + <span> Link to image </span> + </a> + <a + href="https://google.com" + rel="noopener noreferrer" + target="_blank" + > + <gr-icon class="link" filled="" icon="bug_report"> </gr-icon> + <span> Link for reporting a problem </span> + </a> + <a + href="https://google.com" + rel="noopener noreferrer" + target="_blank" + > + <gr-icon class="link" icon="help"> </gr-icon> + <span> Link to help page </span> + </a> + <a + href="https://google.com" + rel="noopener noreferrer" + target="_blank" + > + <gr-icon class="link" icon="history"> </gr-icon> + <span> Link to result history </span> + </a> + </div> + <div class="links"> + <a + href="https://google.com" + rel="noopener noreferrer" + target="_blank" + > + <gr-icon class="link" icon="open_in_new"> </gr-icon> + <span> Link to details </span> + </a> + <a + href="https://google.com" + rel="noopener noreferrer" + target="_blank" + > + <gr-icon class="link" filled="" icon="image"> </gr-icon> + <span> Link to image </span> + </a> + </div> + <div> + <gr-icon custom="" icon="ai" small=""> </gr-icon> + <span class="ai-generated"> AI Generated </span> + by FAKE Error Finder Finder Finder Finder Finder Finder Finder + </div> </div> <gr-endpoint-decorator name="check-result-expanded"> <gr-endpoint-param name="run"> </gr-endpoint-param> <gr-endpoint-param name="result"> </gr-endpoint-param> <gr-formatted-text class="message"> </gr-formatted-text> </gr-endpoint-decorator> - <div class="useful"> - <div class="title">Was this helpful?</div> - <gr-checks-action icon="thumb_up"> </gr-checks-action> - <gr-checks-action icon="thumb_down"> </gr-checks-action> - </div> + <gr-endpoint-decorator name="check-result-feedback"> + <gr-endpoint-param name="result"> </gr-endpoint-param> + <div class="useful"> + <div class="title">Was this helpful?</div> + <gr-checks-action icon="thumb_up"> </gr-checks-action> + <gr-checks-action icon="thumb_down"> </gr-checks-action> + </div> + </gr-endpoint-decorator> ` ); }); @@ -278,11 +367,13 @@ assert.shadowDom.equal( element, /* HTML */ ` - <div class="links"></div> - <div> - <gr-icon custom="" icon="ai" small=""> </gr-icon> - <span class="ai-generated"> AI Generated </span> - by FAKE Super Check + <div class="header-content"> + <div class="links"></div> + <div> + <gr-icon custom="" icon="ai" small=""> </gr-icon> + <span class="ai-generated"> AI Generated </span> + by FAKE Super Check + </div> </div> <gr-endpoint-decorator name="check-result-expanded"> <gr-endpoint-param name="run"> </gr-endpoint-param> @@ -290,11 +381,47 @@ <gr-formatted-text class="message"> </gr-formatted-text> </gr-endpoint-decorator> <gr-checks-fix-preview> </gr-checks-fix-preview> - <div class="useful"> - <div class="title">Was this helpful?</div> - <gr-checks-action icon="thumb_up"> </gr-checks-action> - <gr-checks-action icon="thumb_down"> </gr-checks-action> + <gr-endpoint-decorator name="check-result-feedback"> + <gr-endpoint-param name="result"> </gr-endpoint-param> + <div class="useful"> + <div class="title">Was this helpful?</div> + <gr-checks-action icon="thumb_up"> </gr-checks-action> + <gr-checks-action icon="thumb_down"> </gr-checks-action> + </div> + </gr-endpoint-decorator> + ` + ); + }); + + test('renderLink returns undefined when url is empty, whitespace, or missing', async () => { + element.result = {...checkRun6, ...checkRun6.results![0]} as RunResult; + await element.updateComplete; + + assert.shadowDom.equal( + element, + /* HTML */ ` + <div class="header-content"> + <div class="links"></div> + <div class="links"> + <a + href="https://google.com" + rel="noopener noreferrer" + target="_blank" + > + <gr-icon class="link" icon="download"> </gr-icon> + <span> Download </span> + </a> + </div> + <div class="links"></div> </div> + <gr-endpoint-decorator name="check-result-expanded"> + <gr-endpoint-param name="run"> </gr-endpoint-param> + <gr-endpoint-param name="result"> </gr-endpoint-param> + <gr-formatted-text class="message"> </gr-formatted-text> + </gr-endpoint-decorator> + <gr-endpoint-decorator name="check-result-feedback"> + <gr-endpoint-param name="result"> </gr-endpoint-param> + </gr-endpoint-decorator> ` ); });
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts index a9a4147..15f4bd4 100644 --- a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts +++ b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
@@ -228,7 +228,20 @@ ); } + override willUpdate(changedProperties: PropertyValues) { + super.willUpdate(changedProperties); + if (changedProperties.has('run') && this.run) { + if ( + this.run.status === RunStatus.RUNNING || + this.run.status === RunStatus.SCHEDULED + ) { + this.shouldRender = true; + } + } + } + override firstUpdated() { + if (this.shouldRender) return; assertIsDefined(this.chipElement, 'chip element'); whenVisible(this.chipElement, () => (this.shouldRender = true), 200); } @@ -346,8 +359,10 @@ if (this.run.status !== RunStatus.RUNNING) return; if (!this.run.finishedTimestamp) return; const now = new Date(); - if (this.run.finishedTimestamp.getTime() < now.getTime()) return; - const eta = durationString(new Date(), this.run.finishedTimestamp); + const finished = new Date(this.run.finishedTimestamp); + if (isNaN(finished.getTime())) return; + if (finished.getTime() < now.getTime()) return; + const eta = durationString(new Date(), finished); return html`<span class="eta">ETA: ${eta}</span>`; }
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-runs_test.ts b/polygerrit-ui/app/elements/checks/gr-checks-runs_test.ts index 88a1b0b..7fe60a9 100644 --- a/polygerrit-ui/app/elements/checks/gr-checks-runs_test.ts +++ b/polygerrit-ui/app/elements/checks/gr-checks-runs_test.ts
@@ -199,6 +199,29 @@ ); }); + test('renders running check immediately', async () => { + element.run = { + ...checkRun0, + status: RunStatus.RUNNING, + }; + await element.updateComplete; + assert.isTrue(element.shouldRender); + }); + + test('renders running check with number finishedTimestamp without crashing', async () => { + element.run = { + ...checkRun0, + status: RunStatus.RUNNING, + // 10 seconds in the future + finishedTimestamp: (new Date().getTime() + 10000) as unknown as Date, + }; + await element.updateComplete; + assert.isTrue(element.shouldRender); + const eta = element.shadowRoot?.querySelector('.eta'); + assert.isOk(eta); + assert.include(eta?.textContent, 'ETA:'); + }); + test('renders checkRun0', async () => { element.shouldRender = true; element.run = checkRun0;
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-tag.ts b/polygerrit-ui/app/elements/checks/gr-checks-tag.ts new file mode 100644 index 0000000..4cce3fe --- /dev/null +++ b/polygerrit-ui/app/elements/checks/gr-checks-tag.ts
@@ -0,0 +1,70 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ +import {css, html, LitElement, nothing} from 'lit'; +import {customElement, property} from 'lit/decorators.js'; +import {Tag} from '../../api/checks'; +import '../shared/gr-tooltip-content/gr-tooltip-content'; + +@customElement('gr-checks-tag') +export class GrChecksTag extends LitElement { + @property({type: Object}) + tag?: Tag; + + static override get styles() { + return [ + css` + .tag { + color: var(--primary-text-color); + display: inline-block; + border-radius: 20px; + background-color: var(--tag-background); + padding: 0 var(--spacing-m); + margin-left: var(--spacing-s); + cursor: pointer; + border: none; + } + .tag.gray { + background-color: var(--tag-gray); + } + .tag.yellow { + background-color: var(--tag-yellow); + } + .tag.pink { + background-color: var(--tag-pink); + } + .tag.purple { + background-color: var(--tag-purple); + } + .tag.cyan { + background-color: var(--tag-cyan); + } + .tag.brown { + background-color: var(--tag-brown); + } + `, + ]; + } + + override render() { + if (!this.tag) return nothing; + return html`<gr-tooltip-content + has-tooltip + ?position-below=${true} + title=${this.tag.tooltip ?? + 'A category tag for this check result. Click to filter.'} + > + <button class="tag ${this.tag.color}"> + <span>${this.tag.name}</span> + </button> + </gr-tooltip-content>`; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'gr-checks-tag': GrChecksTag; + } +}
diff --git a/polygerrit-ui/app/elements/checks/gr-diff-check-result.ts b/polygerrit-ui/app/elements/checks/gr-diff-check-result.ts index ef9c082..c9d3b1a 100644 --- a/polygerrit-ui/app/elements/checks/gr-diff-check-result.ts +++ b/polygerrit-ui/app/elements/checks/gr-diff-check-result.ts
@@ -13,10 +13,14 @@ createFixAction, createPleaseFixComment, iconFor, + reportAiAgentCommentDraft, + reportAiAgentGetAIFix, + reportAiAgentSuggestionCopy, } from '../../models/checks/checks-util'; import {modifierPressed} from '../../utils/dom-util'; import './gr-checks-results'; import './gr-hovercard-run'; +import './gr-checks-tag'; import {fontStyles} from '../../styles/gr-font-styles'; import {Action, Category} from '../../api/checks'; import {assertIsDefined} from '../../utils/common-util'; @@ -153,9 +157,23 @@ position: relative; top: 2px; } + div.footer { + display: flex; + justify-content: space-between; + margin-top: var(--spacing-m); + } + div.tags { + display: flex; + justify-content: flex-start; + align-items: center; + } div.actions { display: flex; justify-content: flex-end; + gap: var(--spacing-m); + } + .action { + margin-left: var(--spacing-s); } .ai-icon-wrapper { margin-right: var(--spacing-s); @@ -206,7 +224,7 @@ </div>` : nothing; return html` - <div class="${cat} container font-normal"> + <div class="${cat} container font-normal" @copy=${this.handleCopy}> <div class="header" @click=${this.toggleExpandedClick}> <div class="icon"> <gr-icon icon=${icon.name} ?filled=${!!icon.filled}></gr-icon> @@ -231,9 +249,8 @@ </div> ${this.renderToggle()} </div> - <div class="details"> - ${this.renderExpanded()}${this.renderActions()} - </div> + <div class="details">${this.renderExpanded()}</div> + <div class="footer">${this.renderTags()} ${this.renderActions()}</div> </div> `; } @@ -277,6 +294,13 @@ ></gr-checks-fix-preview>`; } + private renderTags() { + const tags = this.result?.tags ?? []; + return html`<div class="tags"> + ${tags.map(tag => html`<gr-checks-tag .tag=${tag}></gr-checks-tag>`)} + </div>`; + } + private renderActions() { return html`<div class="actions"> ${this.renderAIFixButton()} @@ -337,10 +361,14 @@ private renderPleaseFixButton() { const action: Action = { name: 'Please Fix', - callback: () => { - assertIsDefined(this.result, 'result'); - this.getCommentsModel().saveDraft(createPleaseFixComment(this.result)); - return undefined; + callback: async () => { + const result = this.result; + assertIsDefined(result, 'result'); + const savedDraft = await this.getCommentsModel().saveDraft( + createPleaseFixComment(result) + ); + reportAiAgentCommentDraft(this.reporting, result, savedDraft.id); + return {}; }, }; return html` @@ -395,12 +423,18 @@ this.isExpanded = !this.isExpanded; } + private handleCopy() { + if (!this.result) return; + reportAiAgentSuggestionCopy(this.reporting, this.result); + } + private async handleAIFix(): Promise<void> { const codePointer = this.result?.codePointers?.[0]; if (!this.result || !this.result.message || !codePointer || !this.isOwner) return; this.suggestionLoading = true; + reportAiAgentGetAIFix(this.reporting, this.result); let suggestion: FixSuggestionInfo | undefined; try { suggestion = await this.getSuggestionsService().generateSuggestedFix({
diff --git a/polygerrit-ui/app/elements/checks/gr-diff-check-result_test.ts b/polygerrit-ui/app/elements/checks/gr-diff-check-result_test.ts index 6fad64e..71085f2 100644 --- a/polygerrit-ui/app/elements/checks/gr-diff-check-result_test.ts +++ b/polygerrit-ui/app/elements/checks/gr-diff-check-result_test.ts
@@ -3,7 +3,7 @@ * Copyright 2021 Google LLC * SPDX-License-Identifier: Apache-2.0 */ -import {assert} from '@open-wc/testing'; +import {assert, waitUntil} from '@open-wc/testing'; import {checkRun1} from '../../test/test-data-generators'; import {RunResult} from '../../models/checks/checks-model'; import '../../test/common-test-setup'; @@ -12,16 +12,32 @@ import './gr-diff-check-result'; import {GrDiffCheckResult} from './gr-diff-check-result'; import {GrButton} from '../shared/gr-button/gr-button'; -import {suggestionsServiceToken} from '../../services/suggestions/suggestions-service'; +import { + SuggestionsService, + suggestionsServiceToken, +} from '../../services/suggestions/suggestions-service'; import {testResolver} from '../../test/common-test-setup'; +import {checksModelToken} from '../../models/checks/checks-model'; +import {commentsModelToken} from '../../models/comments/comments-model'; +import {SavingState} from '../../types/common'; +import {Interaction} from '../../constants/reporting'; +import {getAppContext} from '../../services/app-context'; +import {FixId, NumericChangeId, UrlEncodedCommentId} from '../../api/rest-api'; suite('gr-diff-check-result tests', () => { let element: GrDiffCheckResult; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let suggestionsService: any; + + let reportingStub: sinon.SinonStub; + let suggestionsService: SuggestionsService; setup(async () => { suggestionsService = testResolver(suggestionsServiceToken); + reportingStub = sinon.stub( + getAppContext().reportingService, + 'reportInteraction' + ); + const checksModel = testResolver(checksModelToken); + checksModel.changeNum = 123 as NumericChangeId; sinon .stub(suggestionsService, 'isGeneratedSuggestedFixEnabled') @@ -29,6 +45,7 @@ stubFlags('isEnabled').returns(true); sinon.stub(suggestionsService, 'generateSuggestedFix').resolves({ description: 'AI suggested fix', + fix_id: '1' as FixId, replacements: [ { path: 'test/path', @@ -89,6 +106,12 @@ </div> </div> <div class="details"> + </div> + <div class="footer"> + <div class="tags"> + <gr-checks-tag></gr-checks-tag> + <gr-checks-tag></gr-checks-tag> + </div> <div class="actions"> <gr-checks-action id="please-fix" @@ -112,12 +135,6 @@ /* HTML */ ` <div class="details"> <gr-result-expanded hidecodepointers=""></gr-result-expanded> - <div class="actions"> - <gr-checks-action - id="please-fix" - context="diff-fix" - ></gr-checks-action> - </div> </div> ` ); @@ -138,6 +155,12 @@ category: 'ERROR', summary: 'Test Summary', message: 'Test Message', + isAiPowered: true, + externalId: JSON.stringify({ + agentId: 'test-agent', + conversationId: 'test-conv', + turnIndex: 1, + }), codePointers: [ { path: 'test/path', @@ -162,10 +185,83 @@ // Click the AI fix button const aiFixButton = queryAndAssert<GrButton>(element, '#aiFixBtn'); aiFixButton.click(); - await element.updateComplete; + await waitUntil(() => element.isExpanded); - // Should be expanded after suggestion is found - assert.isTrue(element.isExpanded); + assert.isTrue( + reportingStub.calledWith( + Interaction.AI_AGENT_GET_FIX_CLICKED, + sinon.match({ + agentId: 'test-agent', + conversationId: 'test-conv', + turnIndex: 1, + }) + ) + ); }); }); + test('reports AI_AGENT_SUGGESTION_TO_COMMENT when please-fix clicked', async () => { + element.result = { + ...checkRun1, + ...checkRun1.results?.[0], + externalId: JSON.stringify({ + agentId: 'test-agent', + conversationId: 'test-conv', + turnIndex: 1, + }), + } as RunResult; + element.isOwner = true; + await element.updateComplete; + + const commentsModel = testResolver(commentsModelToken); + sinon.stub(commentsModel, 'saveDraft').resolves({ + id: 'test-please-fix-draft-id' as UrlEncodedCommentId, + savingState: SavingState.OK, + }); + + const pleaseFixButton = queryAndAssert(element, '#please-fix'); + const button = queryAndAssert<GrButton>(pleaseFixButton, 'gr-button'); + button.click(); + + await new Promise(resolve => setTimeout(resolve, 0)); + + assert.isTrue( + reportingStub.calledWith( + Interaction.AI_AGENT_SUGGESTION_TO_COMMENT, + sinon.match({ + agentId: 'test-agent', + conversationId: 'test-conv', + turnIndex: 1, + commentId: 'test-please-fix-draft-id', + }) + ) + ); + }); + + test('reports AI_AGENT_SUGGESTION_CONTENT_COPIED when container is copied', async () => { + element.result = { + ...checkRun1, + ...checkRun1.results?.[0], + externalId: JSON.stringify({ + agentId: 'test-agent', + conversationId: 'test-conv', + turnIndex: 1, + }), + } as RunResult; + element.isOwner = true; + await element.updateComplete; + + const container = queryAndAssert(element, '.container'); + container.dispatchEvent(new Event('copy')); + + assert.isTrue( + reportingStub.calledWith( + Interaction.AI_AGENT_SUGGESTION_CONTENT_COPIED, + sinon.match({ + agentId: 'test-agent', + conversationId: 'test-conv', + turnIndex: 1, + }) + ) + ); + }); });
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts index 907c764..0a6538b 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
@@ -35,6 +35,7 @@ PARENT, PatchRange, PatchSetNum, + PreferencesInfo, RepoName, RevisionPatchSetNum, } from '../../../types/common'; @@ -71,6 +72,7 @@ RangeSelectedEventDetail, RenderPreferences, } from '../../../api/diff'; +import {DiffDetails} from '../../../api/annotation'; import {resolve} from '../../../models/dependency'; import {browserModelToken} from '../../../models/browser/browser-model'; import {commentsModelToken} from '../../../models/comments/comments-model'; @@ -287,6 +289,10 @@ @state() private layers: DiffLayer[] = []; + private layersComputedWithPlugins = false; + + private enableTokenHighlight?: boolean; + @state() private renderPrefs: RenderPreferences = { num_lines_rendered_at_once: 128, @@ -376,6 +382,15 @@ this.prefs = diffPreferences; } ); + subscribe( + this, + () => this.getPluginLoader().pluginsModel.pluginsLoaded$, + async pluginsLoaded => { + if (pluginsLoaded) { + await this.computeLayersWithPlugins(); + } + } + ); } override connectedCallback() { @@ -446,6 +461,18 @@ } } + override updated(changedProperties: PropertyValues) { + super.updated(changedProperties); + const pathChanged = changedProperties.has('path'); + const diffElementChanged = + this._layersComputedForDiffElement !== this.diffElement; + const pluginsLoaded = + this.getPluginLoader().pluginsModel.getState().pluginsLoaded; + if ((pathChanged || diffElementChanged) && pluginsLoaded) { + this.computeLayersWithPlugins(); + } + } + async waitForReloadToRender(): Promise<void> { await this.updateComplete; if (this.reloadPromise) { @@ -523,13 +550,45 @@ ); } + private _layersComputedForPath?: string; + + private _layersComputedForDiffElement?: GrDiff; + + private calculateEnableTokenHighlight(prefs?: PreferencesInfo): boolean { + return !prefs?.disable_token_highlighting; + } + + private async computeLayersWithPlugins(): Promise<void> { + if ( + this._layersComputedForPath !== this.path || + this._layersComputedForDiffElement !== this.diffElement + ) { + this.layersComputedWithPlugins = false; + this._layersComputedForPath = this.path; + this._layersComputedForDiffElement = this.diffElement; + } + + if (!this.path || !this.diffElement || this.layersComputedWithPlugins) { + return; + } + if (this.enableTokenHighlight === undefined) { + const prefs = await this.restApiService.getPreferences(); + this.enableTokenHighlight = this.calculateEnableTokenHighlight(prefs); + } + this.layers = this.getLayers(this.enableTokenHighlight); + this.layersComputedWithPlugins = true; + } + async initLayers() { const preferencesPromise = this.restApiService.getPreferences(); const prefs = await preferencesPromise; - const enableTokenHighlight = !prefs?.disable_token_highlighting; + this.enableTokenHighlight = this.calculateEnableTokenHighlight(prefs); assertIsDefined(this.path, 'path'); - this.layers = this.getLayers(enableTokenHighlight); + this.layers = this.getLayers(this.enableTokenHighlight); + this.layersComputedWithPlugins = + !!this.diffElement && + this.getPluginLoader().pluginsModel.getState().pluginsLoaded; this.coverageRanges = []; // We kick off fetching the data here, but we don't return the promise, // so awaiting initLayers() will not wait for coverage data to be @@ -668,6 +727,26 @@ }; } + private get diffDetails(): DiffDetails | undefined { + if ( + !this.change || + !this.patchRange || + !this.file || + !this.path || + !this.diffElement + ) { + return undefined; + } + return { + change: this.change, + basePatchNum: this.patchRange.basePatchNum, + patchNum: this.patchRange.patchNum, + fileRange: this.file, + path: this.path, + diffElement: this.diffElement, + }; + } + private getLayers(enableTokenHighlight: boolean): DiffLayer[] { const layers = []; if (enableTokenHighlight) { @@ -690,12 +769,29 @@ }) ); } + // Add layers from plugins + const details = this.diffDetails; + if (details) { + for (const plugin of this.getPluginLoader().pluginsModel.getState() + .diffLayerPlugins) { + try { + layers.push(plugin.factory(details)); + } catch (e) { + console.error( + `Error creating diff layer from plugin ${plugin.pluginName}:`, + e + ); + } + } + } layers.push(this.syntaxLayer); return layers; } clear() { this.layers = []; + this.layersComputedWithPlugins = false; + this.enableTokenHighlight = undefined; } /**
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.ts index 00a627b..958d2fb 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.ts
@@ -1474,4 +1474,75 @@ }); }); }); + + suite('computeLayersWithPlugins and token highlighting', () => { + let getPreferencesStub: sinon.SinonStub; + setup(() => { + getPreferencesStub = stubRestApi('getPreferences'); + }); + test('initLayers sets enableTokenHighlight correctly', async () => { + getPreferencesStub.returns( + Promise.resolve({disable_token_highlighting: true}) + ); + await element.initLayers(); + assert.isFalse(element['enableTokenHighlight']); + getPreferencesStub.returns( + Promise.resolve({disable_token_highlighting: false}) + ); + await element.initLayers(); + assert.isTrue(element['enableTokenHighlight']); + getPreferencesStub.returns(Promise.resolve(undefined)); + await element.initLayers(); + assert.isTrue(element['enableTokenHighlight']); + }); + test('clear resets enableTokenHighlight', async () => { + getPreferencesStub.returns( + Promise.resolve({disable_token_highlighting: true}) + ); + await element.initLayers(); + assert.isFalse(element['enableTokenHighlight']); + element.clear(); + assert.isUndefined(element['enableTokenHighlight']); + }); + test('computeLayersWithPlugins sets enableTokenHighlight if undefined', async () => { + getPreferencesStub.returns( + Promise.resolve({disable_token_highlighting: true}) + ); + element['enableTokenHighlight'] = undefined; + element['layersComputedWithPlugins'] = false; + + await element['computeLayersWithPlugins'](); + + assert.isFalse(element['enableTokenHighlight']); + assert.isTrue(element['layersComputedWithPlugins']); + assert.isTrue(getPreferencesStub.calledOnce); + }); + test('computeLayersWithPlugins does not fetch preferences if enableTokenHighlight is already set', async () => { + getPreferencesStub.returns( + Promise.resolve({disable_token_highlighting: true}) + ); + element['enableTokenHighlight'] = true; + element['layersComputedWithPlugins'] = false; + + await element['computeLayersWithPlugins'](); + + assert.isTrue(element['enableTokenHighlight']); + assert.isTrue(element['layersComputedWithPlugins']); + assert.isFalse(getPreferencesStub.called); + }); + test('pluginsLoaded triggers computeLayersWithPlugins', async () => { + // @ts-expect-error + const computeSpy = sinon.spy(element, 'computeLayersWithPlugins'); + + const pluginsModel = testResolver(pluginLoaderToken).pluginsModel; + pluginsModel.updateState({pluginsLoaded: false}); + await element.updateComplete; + + pluginsModel.updateState({pluginsLoaded: true}); + + await new Promise(resolve => setTimeout(resolve, 0)); + + assert.isTrue(computeSpy.called); + }); + }); });
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts index 7631ef0..6adc5c6 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
@@ -8,7 +8,7 @@ import '../../shared/gr-tooltip-content/gr-tooltip-content'; import {DiffViewMode} from '../../../constants/constants'; import {customElement, property, state} from 'lit/decorators.js'; -import {fireIronAnnounce} from '../../../utils/event-util'; +import {fireAlert} from '../../../utils/event-util'; import {browserModelToken} from '../../../models/browser/browser-model'; import {resolve} from '../../../models/dependency'; import {css, html, LitElement} from 'lit'; @@ -120,7 +120,7 @@ announcement = 'Changed diff view to side by side'; } if (announcement) { - fireIronAnnounce(this, announcement); + fireAlert(this, announcement); } }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog_test.ts b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog_test.ts index bc8e26e..f3f96fc 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog_test.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog_test.ts
@@ -16,102 +16,167 @@ import {DiffPreferencesInfo} from '../../../api/diff'; import {GrButton} from '../../shared/gr-button/gr-button'; import {assert, fixture, html} from '@open-wc/testing'; +import {MdOutlinedSelect} from '@material/web/select/outlined-select'; suite('gr-diff-preferences-dialog', () => { let element: GrDiffPreferencesDialog; let originalDiffPrefs: DiffPreferencesInfo; - setup(async () => { - originalDiffPrefs = { - ...createDefaultDiffPrefs(), - line_wrapping: true, - }; + suite('standard', () => { + setup(async () => { + originalDiffPrefs = { + ...createDefaultDiffPrefs(), + responsive_mode: 'FULL_RESPONSIVE', + }; - stubRestApi('getDiffPreferences').returns( - Promise.resolve(originalDiffPrefs) - ); + stubRestApi('getDiffPreferences').returns( + Promise.resolve(originalDiffPrefs) + ); - element = await fixture<GrDiffPreferencesDialog>(html` - <gr-diff-preferences-dialog></gr-diff-preferences-dialog> - `); - }); + element = await fixture<GrDiffPreferencesDialog>(html` + <gr-diff-preferences-dialog></gr-diff-preferences-dialog> + `); + }); - test('render', () => { - assert.shadowDom.equal( - element, - /* HTML */ ` - <dialog id="diffPrefsModal" tabindex="-1"> - <div aria-labelledby="diffPreferencesTitle" role="dialog"> - <h3 class="diffHeader heading-3" id="diffPreferencesTitle"> - Diff Preferences - </h3> - <gr-diff-preferences id="diffPreferences"> </gr-diff-preferences> - <div class="diffActions"> - <gr-button - aria-disabled="false" - id="cancelButton" - link="" - role="button" - tabindex="0" - > - Cancel - </gr-button> - <gr-button - aria-disabled="true" - disabled="" - id="saveButton" - link="" - primary="" - role="button" - tabindex="-1" - > - Save - </gr-button> + test('render', () => { + assert.shadowDom.equal( + element, + /* HTML */ ` + <dialog id="diffPrefsModal" tabindex="-1"> + <div aria-labelledby="diffPreferencesTitle" role="dialog"> + <h3 class="diffHeader heading-3" id="diffPreferencesTitle"> + Diff Preferences + </h3> + <gr-diff-preferences id="diffPreferences"> </gr-diff-preferences> + <div class="diffActions"> + <gr-button + aria-disabled="false" + id="cancelButton" + link="" + role="button" + tabindex="0" + > + Cancel + </gr-button> + <gr-button + aria-disabled="true" + disabled="" + id="saveButton" + link="" + primary="" + role="button" + tabindex="-1" + > + Save + </gr-button> + </div> </div> - </div> - </dialog> - ` - ); + </dialog> + ` + ); + }); + + test('changes applies only on save', async () => { + element.open(); + await element.updateComplete; + assert.isUndefined(element.diffPrefsChanged); + assert.equal( + queryAndAssert<MdOutlinedSelect>( + queryAndAssert(element, '#diffPreferences'), + '#lineWrappingSelect' + ).value, + 'FULL_RESPONSIVE' + ); + + const select = queryAndAssert<MdOutlinedSelect>( + queryAndAssert(element, '#diffPreferences'), + '#lineWrappingSelect' + ); + select.value = 'NONE'; + select.dispatchEvent(new Event('change')); + await element.updateComplete; + assert.equal( + queryAndAssert<MdOutlinedSelect>( + queryAndAssert(element, '#diffPreferences'), + '#lineWrappingSelect' + ).value, + 'NONE' + ); + assert.isTrue(element.diffPrefsChanged); + assert.equal(originalDiffPrefs.responsive_mode, 'FULL_RESPONSIVE'); + + stubRestApi('saveDiffPreferences').resolves( + new Response( + makePrefixedJSON({ + ...originalDiffPrefs, + responsive_mode: 'NONE', + }) + ) + ); + + queryAndAssert<GrButton>(element, '#saveButton').click(); + await element.updateComplete; + // Original prefs must remains unchanged, dialog must expose a new object + assert.equal(originalDiffPrefs.responsive_mode, 'FULL_RESPONSIVE'); + await waitUntil(() => element.diffPrefsChanged === false); + }); }); - test('changes applies only on save', async () => { - element.open(); - await element.updateComplete; - assert.isUndefined(element.diffPrefsChanged); - assert.isTrue( + suite('legacy tests', () => { + let element: GrDiffPreferencesDialog; + let legacyPrefs: DiffPreferencesInfo; + + setup(async () => { + legacyPrefs = { + ...createDefaultDiffPrefs(), + responsive_mode: undefined, + line_wrapping: true, + }; + stubRestApi('getDiffPreferences').returns(Promise.resolve(legacyPrefs)); + element = await fixture<GrDiffPreferencesDialog>(html` + <gr-diff-preferences-dialog></gr-diff-preferences-dialog> + `); + }); + + test('legacy changes applies only on save', async () => { + element.open(); + await element.updateComplete; + assert.isUndefined(element.diffPrefsChanged); + assert.isTrue( + queryAndAssert<HTMLInputElement>( + queryAndAssert(element, '#diffPreferences'), + '#lineWrappingInput' + ).checked + ); + queryAndAssert<HTMLInputElement>( queryAndAssert(element, '#diffPreferences'), '#lineWrappingInput' - ).checked - ); + ).click(); + await element.updateComplete; + assert.isFalse( + queryAndAssert<HTMLInputElement>( + queryAndAssert(element, '#diffPreferences'), + '#lineWrappingInput' + ).checked + ); + assert.isTrue(element.diffPrefsChanged); + assert.isTrue(legacyPrefs.line_wrapping); - queryAndAssert<HTMLInputElement>( - queryAndAssert(element, '#diffPreferences'), - '#lineWrappingInput' - ).click(); - await element.updateComplete; - assert.isFalse( - queryAndAssert<HTMLInputElement>( - queryAndAssert(element, '#diffPreferences'), - '#lineWrappingInput' - ).checked - ); - assert.isTrue(element.diffPrefsChanged); - assert.isTrue(originalDiffPrefs.line_wrapping); + stubRestApi('saveDiffPreferences').resolves( + new Response( + makePrefixedJSON({ + ...legacyPrefs, + line_wrapping: false, + }) + ) + ); - stubRestApi('saveDiffPreferences').resolves( - new Response( - makePrefixedJSON({ - ...originalDiffPrefs, - line_wrapping: false, - }) - ) - ); - - queryAndAssert<GrButton>(element, '#saveButton').click(); - await element.updateComplete; - // Original prefs must remains unchanged, dialog must expose a new object - assert.isTrue(originalDiffPrefs.line_wrapping); - await waitUntil(() => element.diffPrefsChanged === false); + queryAndAssert<GrButton>(element, '#saveButton').click(); + await element.updateComplete; + // Original prefs must remains unchanged, dialog must expose a new object + assert.isTrue(legacyPrefs.line_wrapping); + await waitUntil(() => element.diffPrefsChanged === false); + }); }); });
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts index 85cf6cb..93bf666 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -86,6 +86,7 @@ import {a11yStyles} from '../../../styles/gr-a11y-styles'; import {sharedStyles} from '../../../styles/shared-styles'; import {ifDefined} from 'lit/directives/if-defined.js'; +import {classMap} from 'lit/directives/class-map.js'; import {when} from 'lit/directives/when.js'; import {styleMap} from 'lit/directives/style-map.js'; import { @@ -724,6 +725,18 @@ --md-checkbox-container-size: 15px; --md-checkbox-icon-size: 15px; } + .expensiveDiff { + color: var(--warning-foreground); + font-weight: var(--font-weight-bold); + padding: var(--spacing-m) var(--spacing-xl); + display: flex; + align-items: center; + justify-content: center; + gap: var(--spacing-s); + } + gr-endpoint-decorator:has([replace-content]) gr-diff-host { + display: none; + } `, ]; } @@ -843,25 +856,45 @@ return html` ${this.renderStickyHeader()} <h2 class="assistive-tech-only">Diff view</h2> - <div class="diffContainer ${this.shownSidebar && 'sidebarOpen'}"> - <gr-diff-host - id="diffHost" - .changeNum=${this.changeNum} - .change=${this.change} - .patchRange=${this.patchRange} - .file=${file} - .lineOfInterest=${this.getLineOfInterest()} - .path=${this.path} - .projectName=${this.change?.project} - @is-blame-loaded-changed=${this.onIsBlameLoadedChanged} - @comment-anchor-tap=${this.onCommentAnchorTap} - @line-selected=${this.onLineSelected} - @diff-changed=${this.onDiffChanged} - @edit-weblinks-changed=${this.onEditWeblinksChanged} - @files-weblinks-changed=${this.onFilesWeblinksChanged} - @render=${this.reInitCursor} - > - </gr-diff-host> + ${when( + this.file?.diffs_too_expensive_to_compute, + () => html` + <div class="expensiveDiff"> + <gr-icon icon="warning"></gr-icon> + Diff too expensive to compute. + <gr-button link @click=${this.handleOpenDownloadDialog}> + Please download locally to review + </gr-button> + </div> + ` + )} + <div + class=${classMap({ + diffContainer: true, + sidebarOpen: !!this.shownSidebar, + hidden: !!this.file?.diffs_too_expensive_to_compute, + })} + > + <gr-endpoint-decorator name="diff-content"> + <gr-diff-host + id="diffHost" + .changeNum=${this.changeNum} + .change=${this.change} + .patchRange=${this.patchRange} + .file=${file} + .lineOfInterest=${this.getLineOfInterest()} + .path=${this.path} + .projectName=${this.change?.project} + @is-blame-loaded-changed=${this.onIsBlameLoadedChanged} + @comment-anchor-tap=${this.onCommentAnchorTap} + @line-selected=${this.onLineSelected} + @diff-changed=${this.onDiffChanged} + @edit-weblinks-changed=${this.onEditWeblinksChanged} + @files-weblinks-changed=${this.onFilesWeblinksChanged} + @render=${this.reInitCursor} + > + </gr-diff-host> + </gr-endpoint-decorator> </div> ${this.renderDialogs()} `; @@ -1092,6 +1125,9 @@ private renderRightControls() { const diffModeSelectorClass = !this.diff || this.diff.binary ? 'hide' : ''; return html` <div class="rightControls"> + <gr-endpoint-decorator + name="diff-header-controls" + ></gr-endpoint-decorator> ${this.renderSidebarTriggers()} ${this.renderShowEntireFileButton()} ${this.renderBlameButton()} ${when( @@ -1417,7 +1453,8 @@ this.diffHost.toggleLeftDiff(); } - private handleOpenDownloadDialog() { + // private but used in tests + handleOpenDownloadDialog() { assertIsDefined(this.downloadModal, 'downloadModal'); this.downloadModal.showModal(); whenVisible(this.downloadModal, () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts index f8fd10e..b66542e 100644 --- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts +++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.ts
@@ -182,6 +182,28 @@ sinon.restore(); }); + test('expensive diff warning', async () => { + const openDownloadDialogStub = sinon.stub( + element, + 'handleOpenDownloadDialog' + ); + element.file = { + ...createFileInfo(), + __path: 'some/path.txt', + diffs_too_expensive_to_compute: true, + }; + await element.updateComplete; + const warning = queryAndAssert(element, '.expensiveDiff'); + assert.include(warning.textContent, 'Diff too expensive to compute'); + + const diffContainer = queryAndAssert(element, '.diffContainer'); + assert.isTrue(diffContainer?.classList.contains('hidden')); + + const downloadButton = queryAndAssert<GrButton>(warning, 'gr-button'); + downloadButton.click(); + await waitUntil(() => openDownloadDialogStub.called); + }); + test('toggle left diff with a hotkey', () => { assertIsDefined(element.diffHost); const toggleLeftDiffStub = sinon.stub(element.diffHost, 'toggleLeftDiff'); @@ -281,6 +303,8 @@ </span> </div> <div class="rightControls"> + <gr-endpoint-decorator name="diff-header-controls"> + </gr-endpoint-decorator> <div class="sidebarTriggerContainer"> <gr-endpoint-decorator name="sidebarTrigger"> <gr-endpoint-param name="onTrigger"> </gr-endpoint-param> @@ -378,7 +402,9 @@ </div> <h2 class="assistive-tech-only">Diff view</h2> <div class="diffContainer"> - <gr-diff-host id="diffHost"> </gr-diff-host> + <gr-endpoint-decorator name="diff-content"> + <gr-diff-host id="diffHost"> </gr-diff-host> + </gr-endpoint-decorator> </div> <gr-apply-fix-dialog id="applyFixDialog"> </gr-apply-fix-dialog> <gr-diff-preferences-dialog id="diffPreferencesDialog">
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-constants.ts b/polygerrit-ui/app/elements/edit/gr-edit-constants.ts index f94b885..da72996 100644 --- a/polygerrit-ui/app/elements/edit/gr-edit-constants.ts +++ b/polygerrit-ui/app/elements/edit/gr-edit-constants.ts
@@ -12,7 +12,7 @@ export const GrEditConstants = { // Order corresponds to order in the UI. Actions: { - OPEN: {label: 'Add/Open/Upload', id: 'open'}, + OPEN: {label: 'Edit', id: 'open'}, DELETE: {label: 'Delete', id: 'delete'}, RENAME: {label: 'Rename', id: 'rename'}, RESTORE: {label: 'Restore', id: 'restore'},
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts index 57b6524..b2052bf 100644 --- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts +++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.ts
@@ -85,7 +85,7 @@ role="button" tabindex="0" > - Add/Open/Upload + Edit </gr-button> <gr-button aria-disabled="false"
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts index 7184e00..778a17c 100644 --- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts +++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.ts
@@ -6,6 +6,7 @@ import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator'; import '../../plugins/gr-endpoint-param/gr-endpoint-param'; import '../../shared/gr-button/gr-button'; +import '../../shared/gr-dialog/gr-dialog'; import '../../shared/gr-editable-label/gr-editable-label'; import '../../shared/gr-tooltip-content/gr-tooltip-content'; import '../gr-default-editor/gr-default-editor'; @@ -28,6 +29,7 @@ import {changeIsAbandoned, changeIsMerged} from '../../../utils/change-util'; import {Modifier} from '../../../utils/dom-util'; import {sharedStyles} from '../../../styles/shared-styles'; +import {modalStyles} from '../../../styles/gr-modal-styles'; import {css, html, LitElement, nothing, PropertyValues} from 'lit'; import {customElement, query, state} from 'lit/decorators.js'; import {subscribe} from '../../lit/subscription-controller'; @@ -66,6 +68,9 @@ @query('#editPreferencesDialog') editPreferencesDialog?: GrEditPreferencesDialog; + @query('#confirmCloseModal') + private confirmCloseModal?: HTMLDialogElement; + @state() viewState?: ChangeViewState; // private but used in test @@ -171,6 +176,7 @@ static override get styles() { return [ sharedStyles, + modalStyles, css` :host { background-color: var(--view-background-color); @@ -291,6 +297,18 @@ @has-edit-pref-change-saved=${this.handleEditPrefChangeSaved} > </gr-edit-preferences-dialog> + <dialog id="confirmCloseModal" tabindex="-1"> + <gr-dialog + id="confirmCloseDialog" + confirm-label="Discard" + cancel-label="Keep Editing" + @confirm=${this.handleConfirmClose} + @cancel=${this.handleCancelClose} + > + <div class="header" slot="header">Discard unsaved changes?</div> + <div class="main" slot="main">Your modifications will be lost.</div> + </gr-dialog> + </dialog> `; } @@ -507,8 +525,11 @@ // private but used in test handleCloseTap = () => { - // TODO(kaspern): Add a confirm dialog if there are unsaved changes. - this.viewEditInChangeView(); + if ((this.content ?? '') !== this.newContent) { + this.confirmCloseModal?.showModal(); + } else { + this.viewEditInChangeView(); + } }; private handleSaveTap = () => { @@ -610,6 +631,16 @@ // We have to fire a reload so the change takes effect within a plugin. fireReload(this); } + + private handleConfirmClose = () => { + this.getStorage().eraseEditableContentItem(this.storageKey); + this.confirmCloseModal?.close(); + this.viewEditInChangeView(); + }; + + private handleCancelClose = () => { + this.confirmCloseModal?.close(); + }; } declare global {
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_screenshot_test.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_screenshot_test.ts new file mode 100644 index 0000000..ec1bd6a --- /dev/null +++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_screenshot_test.ts
@@ -0,0 +1,61 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ +import '../../../test/common-test-setup'; +import './gr-editor-view'; +import {GrEditorView} from './gr-editor-view'; +import {fixture, html} from '@open-wc/testing'; +// Until https://github.com/modernweb-dev/web/issues/2804 is fixed +// @ts-ignore +import {visualDiff} from '@web/test-runner-visual-regression'; +import { + query, + stubRestApi, + visualDiffDarkTheme, +} from '../../../test/test-utils'; +import {createEditViewState} from '../../../test/test-data-generators'; +import {NumericChangeId, RevisionPatchSetNum} from '../../../types/common'; +import {GrButton} from '../../shared/gr-button/gr-button'; + +suite('gr-editor-view screenshot tests', () => { + let element: GrEditorView; + + setup(async () => { + stubRestApi('getFileContent').resolves({ + ok: true, + type: 'text/javascript', + content: 'original content', + }); + element = await fixture<GrEditorView>( + html`<gr-editor-view></gr-editor-view>` + ); + element.viewState = { + ...createEditViewState(), + changeNum: 42 as NumericChangeId, + patchNum: 1 as RevisionPatchSetNum, + editView: {path: 'foo/bar.baz'}, + }; + element.latestPatchsetNumber = 1 as RevisionPatchSetNum; + element.content = 'original content'; + element.newContent = 'original content'; + await element.updateComplete; + }); + + test('editor view', async () => { + await visualDiff(element, 'gr-editor-view-normal'); + await visualDiffDarkTheme(element, 'gr-editor-view-normal'); + }); + + test('cancel modal open', async () => { + element.newContent = 'modified content'; + await element.updateComplete; + + query<GrButton>(element, '#close')!.click(); + await element.updateComplete; + + await visualDiff(element, 'gr-editor-view-cancel-modal'); + await visualDiffDarkTheme(element, 'gr-editor-view-cancel-modal'); + }); +});
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts index 690180c..090c521 100644 --- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts +++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.ts
@@ -127,6 +127,17 @@ </div> <gr-edit-preferences-dialog id="editPreferencesDialog"> </gr-edit-preferences-dialog> + <dialog id="confirmCloseModal" tabindex="-1"> + <gr-dialog + cancel-label="Keep Editing" + confirm-label="Discard" + id="confirmCloseDialog" + role="dialog" + > + <div class="header" slot="header">Discard unsaved changes?</div> + <div class="main" slot="main">Your modifications will be lost.</div> + </gr-dialog> + </dialog> <div class="textareaWrapper"> <gr-endpoint-decorator id="editorEndpoint" name="editor"> <gr-endpoint-param name="fileContent"> </gr-endpoint-param> @@ -216,6 +227,11 @@ const newText = 'file text changed'; setup(async () => { + stubRestApi('getFileContent').resolves({ + ok: true, + type: 'text/javascript', + content: originalText, + }); element.viewState = {...createEditViewState()}; element.content = originalText; element.newContent = originalText; @@ -351,17 +367,89 @@ }); }); - test('file modification and close', async () => { - const closeSpy = sinon.spy(element, 'handleCloseTap'); + test('file modification and close show dialog, then discard', async () => { + storageService.setEditableContentItem( + element.storageKey, + 'cached content' + ); + assert.equal( + storageService.getEditableContentItem(element.storageKey)?.message, + 'cached content' + ); + element.newContent = newText; await element.updateComplete; - assert.isFalse( - query<GrButton>(element, '#save')!.hasAttribute('disabled') - ); + const dialog = query<HTMLDialogElement>(element, '#confirmCloseModal')!; + assert.isFalse(dialog.hasAttribute('open')); query<GrButton>(element, '#close')!.click(); - assert.isTrue(closeSpy.called); + assert.isFalse(saveFileStub.called); + assert.isFalse(navigateStub.called); + assert.isTrue(dialog.hasAttribute('open')); + + query(element, '#confirmCloseDialog')!.dispatchEvent( + new CustomEvent('confirm') + ); + await element.updateComplete; + + assert.isFalse(dialog.hasAttribute('open')); + assert.isTrue(navigateStub.called); + assert.isNull(storageService.getEditableContentItem(element.storageKey)); + }); + + test('file modification and close show dialog, then keep editing', async () => { + element.newContent = newText; + await element.updateComplete; + + const dialog = query<HTMLDialogElement>(element, '#confirmCloseModal')!; + assert.isFalse(dialog.hasAttribute('open')); + + query<GrButton>(element, '#close')!.click(); + assert.isFalse(saveFileStub.called); + assert.isFalse(navigateStub.called); + assert.isTrue(dialog.hasAttribute('open')); + + query(element, '#confirmCloseDialog')!.dispatchEvent( + new CustomEvent('cancel') + ); + await element.updateComplete; + + assert.isFalse(dialog.hasAttribute('open')); + assert.isFalse(navigateStub.called); + }); + + test('render open dialog', async () => { + element.newContent = newText; + await element.updateComplete; + + query<GrButton>(element, '#close')!.click(); + await element.updateComplete; + + const dialog = query<HTMLDialogElement>(element, '#confirmCloseModal')!; + assert.isTrue(dialog.hasAttribute('open')); + + assert.lightDom.equal( + dialog, + /* HTML */ ` + <gr-dialog + cancel-label="Keep Editing" + confirm-label="Discard" + id="confirmCloseDialog" + role="dialog" + > + <div class="header" slot="header">Discard unsaved changes?</div> + <div class="main" slot="main">Your modifications will be lost.</div> + </gr-dialog> + ` + ); + }); + + test('close when content is not modified', async () => { + element.newContent = originalText; + await element.updateComplete; + + query<GrButton>(element, '#close')!.click(); assert.isFalse(saveFileStub.called); assert.isTrue(navigateStub.called); });
diff --git a/polygerrit-ui/app/elements/gr-app-global-var-init.ts b/polygerrit-ui/app/elements/gr-app-global-var-init.ts index a82eed4..305eaf6 100644 --- a/polygerrit-ui/app/elements/gr-app-global-var-init.ts +++ b/polygerrit-ui/app/elements/gr-app-global-var-init.ts
@@ -23,6 +23,7 @@ initWebVitals, } from '../services/gr-reporting/gr-reporting_impl'; import {Finalizable} from '../types/types'; +import {css, html, LitElement} from 'lit'; export function initGlobalVariables( appContext: AppContext & Finalizable, @@ -39,6 +40,9 @@ initInteractionReporter(reportingService); } window.GrPluginActionContext = GrPluginActionContext; + window.LitElement = LitElement; + window.html = html; + window.css = css; } export function initGerrit(pluginLoader: PluginLoader) {
diff --git a/polygerrit-ui/app/elements/gr-app.ts b/polygerrit-ui/app/elements/gr-app.ts index bdfb052..59a0f4a 100644 --- a/polygerrit-ui/app/elements/gr-app.ts +++ b/polygerrit-ui/app/elements/gr-app.ts
@@ -3,24 +3,8 @@ * Copyright 2015 Google LLC * SPDX-License-Identifier: Apache-2.0 */ -import {safeTypesBridge} from '../utils/safe-types-util'; import './font-roboto-local-loader'; import '../types/globals'; -// Sets up global Polymer variable, because plugins requires it. -import '../scripts/bundled-polymer'; - -/** - * setCancelSyntheticClickEvents is set to true by - * default which will cancel synthetic click events - * on older touch device. - * See https://github.com/Polymer/polymer/issues/5289 - */ -import { - setCancelSyntheticClickEvents, - setPassiveTouchGestures, -} from '@polymer/polymer/lib/utils/settings'; -setCancelSyntheticClickEvents(false); -setPassiveTouchGestures(true); import {initGerrit, initGlobalVariables} from './gr-app-global-var-init'; import './gr-app-element'; @@ -30,7 +14,6 @@ provide, Provider, } from '../models/dependency'; -import {installPolymerResin} from '../scripts/polymer-resin-install'; import { createAppContext, @@ -49,8 +32,6 @@ initGlobalVariables(createAppContext(), true); -installPolymerResin(safeTypesBridge); - @customElement('gr-app') export class GrApp extends LitElement { private finalizables: Finalizable[] = [];
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts index f8c368d..8636dae 100644 --- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts +++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
@@ -38,16 +38,16 @@ @property({type: Boolean}) hasUnsavedChanges = false; // private but used in test - @state() hasNameChange = false; + @property({type: Boolean}) hasNameChange = false; // private but used in test - @state() hasUsernameChange = false; + @property({type: Boolean}) hasUsernameChange = false; // private but used in test - @state() hasDisplayNameChange = false; + @property({type: Boolean}) hasDisplayNameChange = false; // private but used in test - @state() hasStatusChange = false; + @property({type: Boolean}) hasStatusChange = false; // private but used in test @state() loading = false;
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts index 55ce032..fdcc2b0 100644 --- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts +++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor.ts
@@ -41,6 +41,8 @@ // private but used in test @state() showNumber?: boolean; + @state() labelFilterInput: string = ''; // comma-separated + private readonly getConfigModel = resolve(this, configModelToken); private readonly getUserModel = resolve(this, userModelToken); @@ -57,10 +59,17 @@ width: auto; } #changeCols .visibleHeader { - text-align: center; + text-align: left; } .checkboxContainer { - text-align: center; + text-align: left; + } + .labelsFilterInput { + width: 20em; + display: block; + } + .labelsFilterCell { + width: auto; } `, ]; @@ -85,6 +94,7 @@ this.prefs = prefs; this.showNumber = !!prefs.legacycid_in_change_table; this.localChangeTableColumns = changeTablePrefs(prefs); + this.labelFilterInput = prefs.label_filter ?? ''; } ); } @@ -122,6 +132,18 @@ </tr> ${this.defaultColumns.map(col => this.renderRow(col))} + <tr> + <td><label for="labelsFilter">Shown Labels</label></td> + <td class="labelsFilterCell"> + <md-outlined-text-field + id="labelsFilter" + class="showBlueFocusBorder labelsFilterInput" + placeholder="CR,V (leave empty to see all labels)" + .value=${this.labelFilterInput} + @input=${this.handleLabelsFilterInput} + ></md-outlined-text-field> + </td> + </tr> </tbody> </table> <gr-button @@ -167,6 +189,14 @@ } /** + * Handle input in the labels filter text field and update the labelFilterInput property + * accordingly. + */ + private handleLabelsFilterInput(e: Event) { + this.labelFilterInput = (e.target as HTMLInputElement).value; + } + + /** * Handle a click on a displayed column checkboxes (excluding number) and * update the localChangeTableColumns property accordingly. */ @@ -196,26 +226,24 @@ // private but used in test async handleSaveChangeTable() { - const newPrefs = { + await this.getUserModel().updatePreferences({ ...this.prefs, change_table: this.localChangeTableColumns, legacycid_in_change_table: this.showNumber, - }; - - await this.getUserModel().updatePreferences(newPrefs); + label_filter: this.labelFilterInput.trim(), + }); } private hasUnsavedChanges(): boolean { const prefsColumns = changeTablePrefs(this.prefs); - const columnsChanged = prefsColumns.length !== this.localChangeTableColumns.length || prefsColumns.some(c => !this.localChangeTableColumns.includes(c)); - const numberChanged = !!this.prefs.legacycid_in_change_table !== !!this.showNumber; - - return columnsChanged || numberChanged; + const savedFilter = this.prefs.label_filter ?? ''; + const labelsChanged = savedFilter !== this.labelFilterInput.trim(); + return columnsChanged || numberChanged || labelsChanged; } }
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.ts b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.ts index 1c5c589..ef2a680 100644 --- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.ts +++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.ts
@@ -126,6 +126,19 @@ <md-checkbox id="Status" name="Status"> </md-checkbox> </td> </tr> + <tr> + <td> + <label for="labelsFilter"> Shown Labels </label> + </td> + <td class="labelsFilterCell"> + <md-outlined-text-field + class="labelsFilterInput showBlueFocusBorder" + id="labelsFilter" + placeholder="CR,V (leave empty to see all labels)" + > + </md-outlined-text-field> + </td> + </tr> </tbody> </table> <gr-button @@ -145,9 +158,9 @@ const rows = queryAndAssert(element, 'tbody').querySelectorAll('tr'); let tds; - // The `+ 1` is for the number column, which isn't included in the change - // table behavior's list. - assert.equal(rows.length, element.defaultColumns.length + 1); + // The `+ 2` is for the number column and the labels column, which aren't + // included in the change table behavior's list. + assert.equal(rows.length, element.defaultColumns.length + 2); for (let i = 0; i < element.defaultColumns.length; i++) { tds = rows[i + 1].querySelectorAll('td'); assert.equal(tds[0].textContent, element.defaultColumns[i]);
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.ts b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.ts index f85b029..db7e38f 100644 --- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.ts +++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.ts
@@ -39,9 +39,9 @@ @property() settingsUrl?: string; - @state() account: Partial<AccountDetailInfo> = {}; + @property({type: Object}) account: Partial<AccountDetailInfo> = {}; - @state() loading = true; + @property({type: Boolean}) loading = true; @state() saving = false;
diff --git a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts index 9fe856b..dfddbef 100644 --- a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts +++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.ts
@@ -10,7 +10,7 @@ } from '../gr-autocomplete/gr-autocomplete'; import {sharedStyles} from '../../../styles/shared-styles'; import {css, html, LitElement, PropertyValues} from 'lit'; -import {customElement, property, query, state} from 'lit/decorators.js'; +import {customElement, property, query} from 'lit/decorators.js'; import { AddAccountEvent, AutocompleteCommitEvent, @@ -41,7 +41,7 @@ querySuggestions: AutocompleteQuery<SuggestedReviewerInfo> = () => Promise.resolve([]); - @state() private inputText = ''; + @property({type: String}) private inputText = ''; static override get styles() { return [
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts index d39d0f4..94beb5b 100644 --- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts +++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
@@ -51,6 +51,9 @@ @property({type: Boolean}) forceAttention = false; + @property({type: Boolean, attribute: 'is-ai'}) + isAi = false; + /** * Only show the first name in the account label. */ @@ -198,6 +201,11 @@ :host([clickable]) a.ownerLink:hover .name { text-decoration: underline; } + .ai-icon { + color: var(--primary-text-color); + vertical-align: top; + --gr-icon-size: 16px; + } `, ]; } @@ -310,6 +318,13 @@ > ${this.computeName(account, this.firstName, this.config)} </span> + ${this.isAi + ? html`<gr-icon + icon="spark" + class="ai-icon" + title="AI designation" + ></gr-icon>` + : ''} ${this.renderAccountStatusPlugins()} </span> `)}
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts index 7f2cf17..cc51a93 100644 --- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts +++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.ts
@@ -160,7 +160,7 @@ @property({type: String}) label? = ''; - @state() suggestions: AutocompleteSuggestion[] = []; + @property({type: Array}) suggestions: AutocompleteSuggestion[] = []; @state() queryStatus?: AutocompleteQueryStatus;
diff --git a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.ts b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.ts index 972a580..7846c57 100644 --- a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.ts +++ b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.ts
@@ -39,6 +39,9 @@ @property({type: Object}) change?: ChangeInfo; + @property({type: Boolean}) + loading = false; + private readonly getShortcutsService = resolve(this, shortcutsServiceToken); static override get styles() { @@ -53,11 +56,19 @@ visibility: hidden; display: block !important; } + .loadingSpin { + display: block; + width: 16px; + height: 16px; + } `, ]; } override render() { + if (this.loading) { + return html`<span class="loadingSpin"></span>`; + } return html` <button role="checkbox"
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts index 0e0dde7..53811e9 100644 --- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts +++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
@@ -246,7 +246,7 @@ * We are listening on the <gr-comment> of the draft, so we even know when the * checkbox is checked, even if not yet saved. */ - @state() + @property({type: Boolean}) unresolved = true; /** @@ -379,7 +379,7 @@ padding: var(--spacing-s) var(--spacing-m); } .comment-box { - width: 80ch; + width: var(--gr-comment-thread-width, 80ch); max-width: 100%; background-color: var(--comment-background-color); color: var(--comment-text-color); @@ -431,7 +431,7 @@ border: 1px solid var(--border-color); flex-grow: 1; flex-shrink: 1; - max-width: 1200px; + max-width: var(--gr-comment-thread-diff-max-width, 1200px); } .view-diff-button { margin: var(--spacing-s) var(--spacing-m);
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_screenshot_test.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_screenshot_test.ts index 5bd04d8..9731c4e8 100644 --- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_screenshot_test.ts +++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_screenshot_test.ts
@@ -55,6 +55,15 @@ savingState: SavingState.OK, }; +const c_ai: CommentInfo = { + author: createAccountDetailWithId(3), + id: 'the-ai-comment' as UrlEncodedCommentId, + message: 'I am an AI assistant, here to help!', + updated: '2021-11-04 10:11:12.000000000' as Timestamp, + in_reply_to: 'the-reply' as UrlEncodedCommentId, + is_ai: true, +}; + suite('gr-comment-thread screenshot tests', () => { let element: GrCommentThread; @@ -96,4 +105,24 @@ await visualDiff(element, 'gr-comment-thread-with-draft'); await visualDiffDarkTheme(element, 'gr-comment-thread-with-draft'); }); + + test('with AI comment', async () => { + element.thread = createThread(c1, c2, c_ai); + await element.updateComplete; + + await visualDiff(element, 'gr-comment-thread-with-ai'); + await visualDiffDarkTheme(element, 'gr-comment-thread-with-ai'); + }); + + test('unresolved inline code review', async () => { + // Simulate inline context by setting custom properties + element.style.setProperty('--gr-comment-thread-width', '100%'); + element.style.setProperty('--gr-comment-thread-diff-max-width', '100%'); + + element.thread = createThread(c1, {...c2, unresolved: true}); + await element.updateComplete; + + await visualDiff(element, 'gr-comment-thread-unresolved-inline'); + await visualDiffDarkTheme(element, 'gr-comment-thread-unresolved-inline'); + }); });
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts index 28ebd79..816054b 100644 --- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts +++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts
@@ -173,6 +173,30 @@ ); }); + test('comment box spans 100% of container width', async () => { + const wrapper = await fixture(html` + <div style="width: 500px;"> + <gr-comment-thread></gr-comment-thread> + </div> + `); + const commentThread = wrapper.querySelector( + 'gr-comment-thread' + ) as GrCommentThread; + commentThread.thread = createThread(c1); + await commentThread.updateComplete; + + const commentBox = queryAndAssert<HTMLElement>( + commentThread, + '.comment-box' + ); + const computedWidth = getComputedStyle(commentBox).width; + assert.isTrue( + computedWidth.endsWith('px') && + Math.abs(parseFloat(computedWidth) - 500) < 30, + `Expected comment box width to span container width, but got: ${computedWidth}` + ); + }); + test('renders with actions resolved', async () => { element.thread = createThread(c1, c2); await element.updateComplete;
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts index 14187b8..418ddb0 100644 --- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts +++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -219,14 +219,14 @@ @state() changeNum?: NumericChangeId; - @state() + @property({type: Boolean}) editing = false; @state() repoName?: RepoName; /* The 'dirty' state of the comment.message, which will be saved on demand. */ - @state() + @property({type: String}) messageText = ''; /** @@ -242,8 +242,12 @@ readonly autocompleteCache = new AutocompleteCache(); + private autocompletePromise?: Promise<AutocompletionContext | undefined>; + + private generateSuggestionPromise?: Promise<FixSuggestionInfo | undefined>; + /* The 'dirty' state of !comment.unresolved, which will be saved on demand. */ - @state() + @property({type: Boolean}) unresolved = true; @state() @@ -762,11 +766,18 @@ private renderAuthor() { if (isDraft(this.comment)) return; return html` - <gr-account-label .account=${this.comment?.author ?? this.account}> + <gr-account-label + .account=${this.comment?.author ?? this.account} + ?is-ai=${this.isAiComment()} + > </gr-account-label> `; } + private isAiComment(): boolean { + return !!this.comment?.is_ai; + } + private renderPortedCommentMessage() { if (!this.showPortedComment) return; if (!this.comment?.patch_set) return; @@ -1215,7 +1226,7 @@ ></md-checkbox> Attach AI-suggested fix ${when( - this.suggestionLoading, + this.generateSuggestion && this.suggestionLoading, () => html`<span class="loadingSpin"></span>`, () => html`${this.getNumberOfSuggestions()}` )} @@ -1255,19 +1266,27 @@ this.wasSuggestionEdited ) return; + + if (this.generateSuggestionPromise) { + return; + } + this.generatedSuggestionId = uuid(); this.suggestionLoading = true; + this.generateSuggestionPromise = + this.getSuggestionsService().generateSuggestedFixForComment( + this.comment, + this.messageText, + this.generatedSuggestionId, + ReportSource.FIX_FOR_REVIEWER_COMMENT + ); + let suggestion: FixSuggestionInfo | undefined; try { - suggestion = - await this.getSuggestionsService().generateSuggestedFixForComment( - this.comment, - this.messageText, - this.generatedSuggestionId, - ReportSource.FIX_FOR_REVIEWER_COMMENT - ); + suggestion = await this.generateSuggestionPromise; } finally { this.suggestionLoading = false; + this.generateSuggestionPromise = undefined; } if (!suggestion) return; @@ -1279,12 +1298,25 @@ // private but visible for testing async autocompleteComment() { if (!this.autocompleteEnabled) return; + + if (this.autocompletePromise) { + return; + } + const commentText = this.messageText; - const context = await this.getSuggestionsService().autocompleteComment( + this.autocompletePromise = this.getSuggestionsService().autocompleteComment( this.comment, this.messageText, this.comments ); + + let context: AutocompletionContext | undefined; + try { + context = await this.autocompletePromise; + } finally { + this.autocompletePromise = undefined; + } + if (!context) return; this.reportHintInteraction( Interaction.COMMENT_COMPLETION_SUGGESTION_FETCHED, @@ -1634,15 +1666,19 @@ private rawSave(options: {showToast: boolean}) { assert(isDraft(this.comment), 'only drafts are editable'); assert(!isSaving(this.comment), 'saving already in progress'); + const draft: DraftInfo = { ...this.comment, message: this.messageText.trimEnd(), unresolved: this.unresolved, }; + if (this.isFixSuggestionChanged()) { draft.fix_suggestions = this.getFixSuggestions(); } + this.reportHintInteractionSaved(); + return this.getCommentsModel().saveDraft(draft, options.showToast); }
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_screenshot_test.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_screenshot_test.ts new file mode 100644 index 0000000..4c2e825 --- /dev/null +++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_screenshot_test.ts
@@ -0,0 +1,57 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ +import '../../../test/common-test-setup'; +import {fixture, html} from '@open-wc/testing'; +// Until https://github.com/modernweb-dev/web/issues/2804 is fixed +// @ts-ignore +import {visualDiff} from '@web/test-runner-visual-regression'; +import {visualDiffDarkTheme} from '../../../test/test-utils'; +import {GrComment} from './gr-comment'; +import './gr-comment'; +import { + CommentInfo, + Timestamp, + UrlEncodedCommentId, +} from '../../../types/common'; +import { + createAccountDetailWithId, + createComment, +} from '../../../test/test-data-generators'; + +const c_normal: CommentInfo = { + ...createComment(), + author: createAccountDetailWithId(1), + id: 'normal-comment' as UrlEncodedCommentId, + message: 'This is a normal comment', + updated: '2021-11-01 10:11:12.000000000' as Timestamp, +}; + +const c_ai: CommentInfo = { + ...createComment(), + author: createAccountDetailWithId(2), + id: 'ai-comment' as UrlEncodedCommentId, + message: 'This is an AI generated comment', + updated: '2021-11-02 10:11:12.000000000' as Timestamp, + is_ai: true, +}; + +suite('gr-comment screenshot tests', () => { + let element: GrComment; + + test('normal comment', async () => { + element = await fixture( + html`<gr-comment .comment=${c_normal}></gr-comment>` + ); + await visualDiff(element, 'gr-comment-normal'); + await visualDiffDarkTheme(element, 'gr-comment-normal'); + }); + + test('AI comment', async () => { + element = await fixture(html`<gr-comment .comment=${c_ai}></gr-comment>`); + await visualDiff(element, 'gr-comment-ai'); + await visualDiffDarkTheme(element, 'gr-comment-ai'); + }); +});
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts index 3d57456..58a64bd 100644 --- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts +++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
@@ -52,6 +52,8 @@ import {GrSuggestionDiffPreview} from '../gr-suggestion-diff-preview/gr-suggestion-diff-preview'; import {ResponseCode} from '../../../api/suggestions'; import {suggestionsServiceToken} from '../../../services/suggestions/suggestions-service'; +import {AutocompletionContext} from '../../../utils/autocomplete-cache'; +import {FixSuggestionInfo} from '../../../api/rest-api'; suite('gr-comment tests', () => { let element: GrComment; @@ -974,6 +976,39 @@ assert.include(label.textContent, '(1)'); }); + test('does not show spinner when loading but unchecked', async () => { + const comment: DraftInfo = { + ...createDraft(), + author: { + name: 'Mr. Peanutbutter', + email: 'tenn1sballchaser@aol.com' as EmailAddress, + }, + line: 5, + path: 'test', + savingState: SavingState.OK, + message: 'hello world', + }; + element = await fixture( + html`<gr-comment + .account=${account} + .showPatchset=${true} + .comment=${comment} + .initiallyCollapsed=${false} + ></gr-comment>` + ); + element.editing = true; + sinon.stub(element, 'showGeneratedSuggestion').returns(true); + element.generateSuggestion = false; + element.suggestionLoading = true; + await element.updateComplete; + + const label = queryAndAssert<HTMLLabelElement>( + element, + 'label.suggestEdit' + ); + assert.isFalse(!!query(label, '.loadingSpin')); + }); + test('renders suggestions in comment', async () => { const comment = { ...createComment(), @@ -1213,5 +1248,77 @@ assert.isFalse(setStub.called); assert.isUndefined(element.autocompleteHint); }); + + test('drops concurrent request', async () => { + const suggestionsService = testResolver(suggestionsServiceToken); + const promise1 = mockPromise<AutocompletionContext>(); + const stub = sinon.stub(suggestionsService, 'autocompleteComment'); + stub.onCall(0).returns(promise1); + + const p1 = element.autocompleteComment(); + assert.isTrue(stub.calledOnce); + + await element.autocompleteComment(); + assert.isTrue(stub.calledOnce); + + promise1.resolve({ + draftContent: 'test', + commentCompletion: ' completion 1', + responseCode: ResponseCode.OK, + }); + await p1; + }); + }); + + suite('generateSuggestEdit', () => { + test('drops concurrent request', async () => { + const suggestionsService = testResolver(suggestionsServiceToken); + const promise1 = mockPromise<FixSuggestionInfo>(); + const stub = sinon.stub( + suggestionsService, + 'generateSuggestedFixForComment' + ); + stub.onCall(0).returns(promise1); + + element.generateSuggestion = true; + stubFlags('isEnabled') + .withArgs(KnownExperimentId.ML_SUGGESTED_EDIT_V2) + .returns(true); + sinon.stub(element, 'showGeneratedSuggestion').returns(true); + element.messageText = 'test message'; + await element.updateComplete; + + const p1 = element.generateSuggestEdit(); + assert.isTrue(stub.calledOnce); + + await element.generateSuggestEdit(); + assert.isTrue(stub.calledOnce); + + promise1.resolve({ + description: 'suggestion 1', + fix_id: '1' as FixId, + replacements: [], + }); + await p1; + }); + + test('calls service even when generateSuggestion is false', async () => { + const suggestionsService = testResolver(suggestionsServiceToken); + const stub = sinon.stub( + suggestionsService, + 'generateSuggestedFixForComment' + ); + + element.generateSuggestion = false; + stubFlags('isEnabled') + .withArgs(KnownExperimentId.ML_SUGGESTED_EDIT_V2) + .returns(true); + sinon.stub(element, 'showGeneratedSuggestion').returns(true); + element.messageText = 'test message'; + await element.updateComplete; + + await element.generateSuggestEdit(); + assert.isTrue(stub.called); + }); }); });
diff --git a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.ts b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.ts index 225d730..f516076 100644 --- a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.ts +++ b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences.ts
@@ -5,11 +5,16 @@ */ import '../../../styles/shared-styles'; import '../gr-button/gr-button'; -import {DiffPreferencesInfo, IgnoreWhitespaceType} from '../../../types/diff'; +import { + DiffPreferencesInfo, + DiffResponsiveMode, + IgnoreWhitespaceType, +} from '../../../types/diff'; import {subscribe} from '../../lit/subscription-controller'; import {grFormStyles} from '../../../styles/gr-form-styles'; import {sharedStyles} from '../../../styles/shared-styles'; import {css, html, LitElement} from 'lit'; +import {when} from 'lit/directives/when.js'; import {customElement, query, state} from 'lit/decorators.js'; import {convertToString} from '../../../utils/string-util'; import {fire} from '../../../utils/event-util'; @@ -122,16 +127,50 @@ </md-outlined-select> </span> </section> - <section> - <label for="lineWrappingInput" class="title">Fit to screen</label> - <span class="value"> - <md-checkbox - id="lineWrappingInput" - ?checked=${!!this.diffPrefs?.line_wrapping} - @change=${this.handleLineWrappingTap} - ></md-checkbox> - </span> - </section> + ${ + // TODO: Remove this conditional logic once backend support is guaranteed. + when( + this.diffPrefs?.responsive_mode !== undefined, + () => html` + <section> + <label for="lineWrappingSelect" class="title" + >Fit to screen</label + > + <span class="value"> + <md-outlined-select + id="lineWrappingSelect" + value=${this.getResponsiveModeValue()} + @change=${this.handleResponsiveModeChange} + > + <md-select-option value="NONE"> + <div slot="headline">None</div> + </md-select-option> + <md-select-option value="SHRINK_ONLY"> + <div slot="headline">Shrink only</div> + </md-select-option> + <md-select-option value="FULL_RESPONSIVE"> + <div slot="headline">Full responsive</div> + </md-select-option> + </md-outlined-select> + </span> + </section> + `, + () => html` + <section> + <label for="lineWrappingInput" class="title" + >Fit to screen</label + > + <span class="value"> + <md-checkbox + id="lineWrappingInput" + ?checked=${!!this.diffPrefs?.line_wrapping} + @change=${this.handleLineWrappingTap} + ></md-checkbox> + </span> + </section> + ` + ) + } <section> <label for="columnsInput" class="title">Diff width</label> <span class="value"> @@ -278,7 +317,27 @@ } private readonly handleLineWrappingTap = () => { - this.diffPrefs!.line_wrapping = this.lineWrappingInput!.checked; + if (!this.diffPrefs) return; + this.diffPrefs.line_wrapping = this.lineWrappingInput!.checked; + fire(this, 'has-unsaved-changes-changed', { + value: this.hasUnsavedChanges(), + }); + }; + + private getResponsiveModeValue() { + if (this.diffPrefs?.responsive_mode) { + return this.diffPrefs.responsive_mode; + } + return this.diffPrefs?.line_wrapping ? 'FULL_RESPONSIVE' : 'NONE'; + } + + private readonly handleResponsiveModeChange = (e: Event) => { + if (!this.diffPrefs) return; + const select = e.target as MdOutlinedSelect; + this.diffPrefs.responsive_mode = select.value as DiffResponsiveMode; + // Keep line_wrapping in sync for backward compatibility. + this.diffPrefs.line_wrapping = select.value === 'FULL_RESPONSIVE'; + this.requestUpdate(); fire(this, 'has-unsaved-changes-changed', { value: this.hasUnsavedChanges(), }); @@ -344,6 +403,8 @@ this.originalDiffPrefs?.context !== this.diffPrefs?.context || Boolean(this.originalDiffPrefs?.line_wrapping) !== Boolean(this.diffPrefs?.line_wrapping) || + this.originalDiffPrefs?.responsive_mode !== + this.diffPrefs?.responsive_mode || this.originalDiffPrefs?.line_length !== this.diffPrefs?.line_length || this.originalDiffPrefs?.tab_size !== this.diffPrefs?.tab_size || this.originalDiffPrefs?.font_size !== this.diffPrefs?.font_size ||
diff --git a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.ts b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.ts index 092c63f..6699146 100644 --- a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.ts +++ b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.ts
@@ -36,264 +36,331 @@ assert.fail(`element with title ${title} not found`); } - setup(async () => { - diffPreferences = createDefaultDiffPrefs(); + suite('standard preferences', () => { + setup(async () => { + diffPreferences = createDefaultDiffPrefs(); - stubRestApi('getDiffPreferences').returns(Promise.resolve(diffPreferences)); + stubRestApi('getDiffPreferences').returns( + Promise.resolve(diffPreferences) + ); - element = await fixture(html`<gr-diff-preferences></gr-diff-preferences>`); + element = await fixture( + html`<gr-diff-preferences></gr-diff-preferences>` + ); - await element.updateComplete; - }); + await element.updateComplete; + }); - test('renders', () => { - assert.shadowDom.equal( - element, - /* HTML */ `<div class="gr-form-styles" id="diffPreferences"> - <section> - <label class="title" for="contextLineSelect"> Context </label> - <span class="value"> - <md-outlined-select id="contextSelect" value="10"> - <md-select-option md-menu-item="" tabindex="0" value="3"> - <div slot="headline">3 lines</div> - </md-select-option> - <md-select-option - data-aria-selected="true" - md-menu-item="" - tabindex="-1" - value="10" - > - <div slot="headline">10 lines</div> - </md-select-option> - <md-select-option md-menu-item="" tabindex="-1" value="25"> - <div slot="headline">25 lines</div> - </md-select-option> - <md-select-option md-menu-item="" tabindex="-1" value="50"> - <div slot="headline">50 lines</div> - </md-select-option> - <md-select-option md-menu-item="" tabindex="-1" value="75"> - <div slot="headline">75 lines</div> - </md-select-option> - <md-select-option md-menu-item="" tabindex="-1" value="100"> - <div slot="headline">100 lines</div> - </md-select-option> - <md-select-option md-menu-item="" tabindex="-1" value="-1"> - <div slot="headline">Whole file</div> - </md-select-option> - </md-outlined-select> - </span> - </section> - <section> - <label class="title" for="lineWrappingInput"> Fit to screen </label> - <span class="value"> - <md-checkbox id="lineWrappingInput"> </md-checkbox> - </span> - </section> - <section> - <label class="title" for="columnsInput"> Diff width </label> - <span class="value"> - <md-outlined-text-field - autocomplete="" - class="showBlueFocusBorder" - id="columnsInput" - inputmode="" - step="1" - type="number" - > - </md-outlined-text-field> - </span> - </section> - <section> - <label class="title" for="tabSizeInput"> Tab width </label> - <span class="value"> - <md-outlined-text-field - autocomplete="" - class="showBlueFocusBorder" - id="tabSizeInput" - inputmode="" - step="1" - type="number" - > - </md-outlined-text-field> - </span> - </section> - <section> - <label class="title" for="fontSizeInput"> Font size </label> - <span class="value"> - <md-outlined-text-field - autocomplete="" - class="showBlueFocusBorder" - id="fontSizeInput" - inputmode="" - step="1" - type="number" - > - </md-outlined-text-field> - </span> - </section> - <section> - <label class="title" for="showTabsInput"> Show tabs </label> - <span class="value"> - <md-checkbox checked="" id="showTabsInput"> </md-checkbox> - </span> - </section> - <section> - <label class="title" for="showTrailingWhitespaceInput"> - Show trailing whitespace - </label> - <span class="value"> - <md-checkbox checked="" id="showTrailingWhitespaceInput"> - </md-checkbox> - </span> - </section> - <section> - <label class="title" for="syntaxHighlightInput"> - Syntax highlighting - </label> - <span class="value"> - <md-checkbox checked="" id="syntaxHighlightInput"> </md-checkbox> - </span> - </section> - <section> - <label class="title" for="automaticReviewInput"> - Automatically mark viewed files reviewed - </label> - <span class="value"> - <md-checkbox checked="" id="automaticReviewInput"> </md-checkbox> - </span> - </section> - <section> - <div class="pref"> - <label class="title" for="ignoreWhiteSpace"> - Ignore Whitespace - </label> + test('renders', () => { + assert.shadowDom.equal( + element, + /* HTML */ `<div class="gr-form-styles" id="diffPreferences"> + <section> + <label class="title" for="contextLineSelect"> Context </label> <span class="value"> - <md-outlined-select id="contextSelect" value="IGNORE_NONE"> + <md-outlined-select id="contextSelect" value="10"> + <md-select-option md-menu-item="" tabindex="0" value="3"> + <div slot="headline">3 lines</div> + </md-select-option> <md-select-option data-aria-selected="true" md-menu-item="" - tabindex="0" - value="IGNORE_NONE" + tabindex="-1" + value="10" > + <div slot="headline">10 lines</div> + </md-select-option> + <md-select-option md-menu-item="" tabindex="-1" value="25"> + <div slot="headline">25 lines</div> + </md-select-option> + <md-select-option md-menu-item="" tabindex="-1" value="50"> + <div slot="headline">50 lines</div> + </md-select-option> + <md-select-option md-menu-item="" tabindex="-1" value="75"> + <div slot="headline">75 lines</div> + </md-select-option> + <md-select-option md-menu-item="" tabindex="-1" value="100"> + <div slot="headline">100 lines</div> + </md-select-option> + <md-select-option md-menu-item="" tabindex="-1" value="-1"> + <div slot="headline">Whole file</div> + </md-select-option> + </md-outlined-select> + </span> + </section> + <section> + <label class="title" for="lineWrappingSelect"> + Fit to screen + </label> + <span class="value"> + <md-outlined-select id="lineWrappingSelect" value="NONE"> + <md-select-option md-menu-item="" tabindex="0" value="NONE"> <div slot="headline">None</div> </md-select-option> <md-select-option md-menu-item="" tabindex="-1" - value="IGNORE_TRAILING" + value="SHRINK_ONLY" > - <div slot="headline">Trailing</div> + <div slot="headline">Shrink only</div> </md-select-option> <md-select-option md-menu-item="" tabindex="-1" - value="IGNORE_LEADING_AND_TRAILING" + value="FULL_RESPONSIVE" > - <div slot="headline">Leading & trailing</div> - </md-select-option> - <md-select-option - md-menu-item="" - tabindex="-1" - value="IGNORE_ALL" - > - <div slot="headline">All</div> + <div slot="headline">Full responsive</div> </md-select-option> </md-outlined-select> </span> - </div> - </section> - </div>` - ); + </section> + <section> + <label class="title" for="columnsInput"> Diff width </label> + <span class="value"> + <md-outlined-text-field + autocomplete="" + class="showBlueFocusBorder" + id="columnsInput" + inputmode="" + step="1" + type="number" + > + </md-outlined-text-field> + </span> + </section> + <section> + <label class="title" for="tabSizeInput"> Tab width </label> + <span class="value"> + <md-outlined-text-field + autocomplete="" + class="showBlueFocusBorder" + id="tabSizeInput" + inputmode="" + step="1" + type="number" + > + </md-outlined-text-field> + </span> + </section> + <section> + <label class="title" for="fontSizeInput"> Font size </label> + <span class="value"> + <md-outlined-text-field + autocomplete="" + class="showBlueFocusBorder" + id="fontSizeInput" + inputmode="" + step="1" + type="number" + > + </md-outlined-text-field> + </span> + </section> + <section> + <label class="title" for="showTabsInput"> Show tabs </label> + <span class="value"> + <md-checkbox checked="" id="showTabsInput"> </md-checkbox> + </span> + </section> + <section> + <label class="title" for="showTrailingWhitespaceInput"> + Show trailing whitespace + </label> + <span class="value"> + <md-checkbox checked="" id="showTrailingWhitespaceInput"> + </md-checkbox> + </span> + </section> + <section> + <label class="title" for="syntaxHighlightInput"> + Syntax highlighting + </label> + <span class="value"> + <md-checkbox checked="" id="syntaxHighlightInput"> </md-checkbox> + </span> + </section> + <section> + <label class="title" for="automaticReviewInput"> + Automatically mark viewed files reviewed + </label> + <span class="value"> + <md-checkbox checked="" id="automaticReviewInput"> </md-checkbox> + </span> + </section> + <section> + <div class="pref"> + <label class="title" for="ignoreWhiteSpace"> + Ignore Whitespace + </label> + <span class="value"> + <md-outlined-select id="contextSelect" value="IGNORE_NONE"> + <md-select-option + data-aria-selected="true" + md-menu-item="" + tabindex="0" + value="IGNORE_NONE" + > + <div slot="headline">None</div> + </md-select-option> + <md-select-option + md-menu-item="" + tabindex="-1" + value="IGNORE_TRAILING" + > + <div slot="headline">Trailing</div> + </md-select-option> + <md-select-option + md-menu-item="" + tabindex="-1" + value="IGNORE_LEADING_AND_TRAILING" + > + <div slot="headline">Leading & trailing</div> + </md-select-option> + <md-select-option + md-menu-item="" + tabindex="-1" + value="IGNORE_ALL" + > + <div slot="headline">All</div> + </md-select-option> + </md-outlined-select> + </span> + </div> + </section> + </div>` + ); + }); + + test('renders preferences', () => { + // Rendered with the expected preferences selected. + const contextInput = valueOf('Context', 'diffPreferences') + .firstElementChild as MdOutlinedSelect; + assert.equal(contextInput.value, `${diffPreferences.context}`); + + const lineWrappingInput = valueOf('Fit to screen', 'diffPreferences') + .firstElementChild as MdOutlinedSelect; + assert.equal(lineWrappingInput.value, diffPreferences.responsive_mode); + + const lineLengthInput = valueOf('Diff width', 'diffPreferences') + .firstElementChild as MdOutlinedTextField; + assert.equal(lineLengthInput.value, `${diffPreferences.line_length}`); + + const tabSizeInput = valueOf('Tab width', 'diffPreferences') + .firstElementChild as MdOutlinedTextField; + assert.equal(tabSizeInput.value, `${diffPreferences.tab_size}`); + + const fontSizeInput = valueOf('Font size', 'diffPreferences') + .firstElementChild as MdOutlinedTextField; + assert.equal(fontSizeInput.value, `${diffPreferences.font_size}`); + + const showTabsInput = valueOf('Show tabs', 'diffPreferences') + .firstElementChild as HTMLInputElement; + assert.equal(showTabsInput.checked, diffPreferences.show_tabs); + + const showWhitespaceErrorsInput = valueOf( + 'Show trailing whitespace', + 'diffPreferences' + ).firstElementChild as HTMLInputElement; + assert.equal( + showWhitespaceErrorsInput.checked, + diffPreferences.show_whitespace_errors + ); + + const syntaxHighlightingInput = valueOf( + 'Syntax highlighting', + 'diffPreferences' + ).firstElementChild as HTMLInputElement; + assert.equal( + syntaxHighlightingInput.checked, + diffPreferences.syntax_highlighting + ); + + const manualReviewInput = valueOf( + 'Automatically mark viewed files reviewed', + 'diffPreferences' + ).firstElementChild as HTMLInputElement; + assert.equal(manualReviewInput.checked, !diffPreferences.manual_review); + + const ignoreWhitespaceInput = valueOf( + 'Ignore Whitespace', + 'diffPreferences' + ).firstElementChild as MdOutlinedSelect; + assert.equal( + ignoreWhitespaceInput.value, + diffPreferences.ignore_whitespace + ); + + assert.isFalse(element.hasUnsavedChanges()); + }); + + test('save changes', async () => { + assert.isTrue(element.diffPrefs!.show_whitespace_errors); + + const showTrailingWhitespaceCheckbox = valueOf( + 'Show trailing whitespace', + 'diffPreferences' + ).firstElementChild as HTMLInputElement; + showTrailingWhitespaceCheckbox.checked = false; + element.handleShowTrailingWhitespaceTap(); + + assert.isTrue(element.hasUnsavedChanges()); + + const savePrefStub = stubRestApi('saveDiffPreferences').resolves( + new Response(makePrefixedJSON(element.diffPrefs)) + ); + + await element.save(); + // Wait for model state update, since this is not awaited by element.save() + await waitUntil( + () => + !element.getUserModel().getState().diffPreferences + ?.show_whitespace_errors + ); + + assert.isTrue(savePrefStub.called); + assert.isFalse(element.diffPrefs!.show_whitespace_errors); + assert.isFalse(element.hasUnsavedChanges()); + }); }); - test('renders preferences', () => { - // Rendered with the expected preferences selected. - const contextInput = valueOf('Context', 'diffPreferences') - .firstElementChild as MdOutlinedSelect; - assert.equal(contextInput.value, `${diffPreferences.context}`); + suite('legacy preferences', () => { + let element: GrDiffPreferences; + let diffPreferences: DiffPreferencesInfo; - const lineWrappingInput = valueOf('Fit to screen', 'diffPreferences') - .firstElementChild as HTMLInputElement; - assert.equal(lineWrappingInput.checked, diffPreferences.line_wrapping); + setup(async () => { + diffPreferences = { + ...createDefaultDiffPrefs(), + responsive_mode: undefined, + line_wrapping: true, + }; - const lineLengthInput = valueOf('Diff width', 'diffPreferences') - .firstElementChild as MdOutlinedTextField; - assert.equal(lineLengthInput.value, `${diffPreferences.line_length}`); + stubRestApi('getDiffPreferences').returns( + Promise.resolve(diffPreferences) + ); - const tabSizeInput = valueOf('Tab width', 'diffPreferences') - .firstElementChild as MdOutlinedTextField; - assert.equal(tabSizeInput.value, `${diffPreferences.tab_size}`); + element = await fixture( + html`<gr-diff-preferences></gr-diff-preferences>` + ); - const fontSizeInput = valueOf('Font size', 'diffPreferences') - .firstElementChild as MdOutlinedTextField; - assert.equal(fontSizeInput.value, `${diffPreferences.font_size}`); + await element.updateComplete; + }); - const showTabsInput = valueOf('Show tabs', 'diffPreferences') - .firstElementChild as HTMLInputElement; - assert.equal(showTabsInput.checked, diffPreferences.show_tabs); - - const showWhitespaceErrorsInput = valueOf( - 'Show trailing whitespace', - 'diffPreferences' - ).firstElementChild as HTMLInputElement; - assert.equal( - showWhitespaceErrorsInput.checked, - diffPreferences.show_whitespace_errors - ); - - const syntaxHighlightingInput = valueOf( - 'Syntax highlighting', - 'diffPreferences' - ).firstElementChild as HTMLInputElement; - assert.equal( - syntaxHighlightingInput.checked, - diffPreferences.syntax_highlighting - ); - - const manualReviewInput = valueOf( - 'Automatically mark viewed files reviewed', - 'diffPreferences' - ).firstElementChild as HTMLInputElement; - assert.equal(manualReviewInput.checked, !diffPreferences.manual_review); - - const ignoreWhitespaceInput = valueOf( - 'Ignore Whitespace', - 'diffPreferences' - ).firstElementChild as MdOutlinedSelect; - assert.equal( - ignoreWhitespaceInput.value, - diffPreferences.ignore_whitespace - ); - - assert.isFalse(element.hasUnsavedChanges()); - }); - - test('save changes', async () => { - assert.isTrue(element.diffPrefs!.show_whitespace_errors); - - const showTrailingWhitespaceCheckbox = valueOf( - 'Show trailing whitespace', - 'diffPreferences' - ).firstElementChild as HTMLInputElement; - showTrailingWhitespaceCheckbox.checked = false; - element.handleShowTrailingWhitespaceTap(); - - assert.isTrue(element.hasUnsavedChanges()); - - const savePrefStub = stubRestApi('saveDiffPreferences').resolves( - new Response(makePrefixedJSON(element.diffPrefs)) - ); - - await element.save(); - // Wait for model state update, since this is not awaited by element.save() - await waitUntil( - () => - !element.getUserModel().getState().diffPreferences - ?.show_whitespace_errors - ); - - assert.isTrue(savePrefStub.called); - assert.isFalse(element.diffPrefs!.show_whitespace_errors); - assert.isFalse(element.hasUnsavedChanges()); + test('renders legacy checkbox', () => { + const sections = queryAll(element, '#diffPreferences section') ?? []; + let lineWrappingSection: Element | undefined; + for (let i = 0; i < sections.length; i++) { + if ( + sections[i].querySelector('.title')?.textContent?.trim() === + 'Fit to screen' + ) { + lineWrappingSection = sections[i]; + break; + } + } + assert.isDefined(lineWrappingSection); + const checkbox = lineWrappingSection.querySelector('md-checkbox'); + assert.isDefined(checkbox); + assert.isTrue(checkbox!.checked); + }); }); });
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts index c913c8c..c42fdd1 100644 --- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts +++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts
@@ -96,7 +96,7 @@ @state() selectedIndex = 0; - @state() + @property({type: Boolean}) private opened = false; @state() private hadKeyboardEvent = false;
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts index 65efb57..140f0ff 100644 --- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts +++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.ts
@@ -65,6 +65,11 @@ text-decoration: none; width: 100%; } + /* Allow the dropdown to be positioned correctly near side panels. */ + .dropdown-menu { + position: var(--gr-dropdown-position, static); + z-index: var(--gr-dropdown-z-index, auto); + } .dropdown-content { min-width: 112px; max-width: 280px; @@ -167,7 +172,7 @@ @property({type: Number, attribute: 'vertical-offset'}) verticalOffset = 0; - @state() + @property({type: Boolean}) private opened = false; /** @@ -268,32 +273,34 @@ > <slot></slot> </gr-button> - <md-menu - default-focus="none" - id="dropdown" - anchor="trigger" - tabindex="-1" - .menuCorner=${this.horizontalAlign === 'left' - ? 'start-start' - : this.horizontalAlign === 'center' - ? 'start-end' - : 'end-start'} - .yOffset=${this.verticalOffset} - ?quick=${true} - .skipRestoreFocus=${true} - @opened=${() => { - this.opened = true; - }} - @closing=${this.handleMenuClosing} - @closed=${() => { - this.opened = false; - this.hadKeyboardEvent = false; - // This is an ugly hack but works. - this.cursor.target?.removeAttribute('selected'); - }} - > - ${this.renderDropdownContent()} - </md-menu> + <div class="dropdown-menu"> + <md-menu + default-focus="none" + id="dropdown" + anchor="trigger" + tabindex="-1" + .menuCorner=${this.horizontalAlign === 'left' + ? 'start-start' + : this.horizontalAlign === 'center' + ? 'start-end' + : 'end-start'} + .yOffset=${this.verticalOffset} + ?quick=${true} + .skipRestoreFocus=${true} + @opened=${() => { + this.opened = true; + }} + @closing=${this.handleMenuClosing} + @closed=${() => { + this.opened = false; + this.hadKeyboardEvent = false; + // This is an ugly hack but works. + this.cursor.target?.removeAttribute('selected'); + }} + > + ${this.renderDropdownContent()} + </md-menu> + </div> </div>`; }
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.ts b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.ts index 3230e48..16757a8 100644 --- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.ts +++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.ts
@@ -40,41 +40,58 @@ > <slot> </slot> </gr-button> - <md-menu - anchor="trigger" - aria-hidden="true" - default-focus="none" - id="dropdown" - quick="" - tabindex="-1" - > - <div class="dropdown-content"> - <gr-tooltip-content has-tooltip="" title="hello"> - <span class="itemAction" data-id="foo" tabindex="-1"> - <md-menu-item - active="" - data-index="0" - md-menu-item="" - selected="" - > - item one - </md-menu-item> - </span> - </gr-tooltip-content> - <md-divider role="separator" tabindex="-1"> </md-divider> - <gr-tooltip-content> - <a class="itemAction" href="http://bar" tabindex="-1"> - <md-menu-item data-index="1" md-menu-item=""> - item two - </md-menu-item> - </a> - </gr-tooltip-content> - </div> - </md-menu> + <div class="dropdown-menu"> + <md-menu + anchor="trigger" + aria-hidden="true" + default-focus="none" + id="dropdown" + quick="" + tabindex="-1" + > + <div class="dropdown-content"> + <gr-tooltip-content has-tooltip="" title="hello"> + <span class="itemAction" data-id="foo" tabindex="-1"> + <md-menu-item + active="" + data-index="0" + md-menu-item="" + selected="" + > + item one + </md-menu-item> + </span> + </gr-tooltip-content> + <md-divider role="separator" tabindex="-1"> </md-divider> + <gr-tooltip-content> + <a class="itemAction" href="http://bar" tabindex="-1"> + <md-menu-item data-index="1" md-menu-item=""> + item two + </md-menu-item> + </a> + </gr-tooltip-content> + </div> + </md-menu> + </div> </div>` ); }); + test('css variables control positioning', async () => { + const dropdownMenu = element.shadowRoot?.querySelector('.dropdown-menu'); + assert.isOk(dropdownMenu); + let styles = window.getComputedStyle(dropdownMenu); + assert.equal(styles.position, 'static'); + assert.equal(styles.zIndex, 'auto'); + + element.style.setProperty('--gr-dropdown-position', 'relative'); + element.style.setProperty('--gr-dropdown-z-index', '120'); + + styles = window.getComputedStyle(dropdownMenu); + assert.equal(styles.position, 'relative'); + assert.equal(styles.zIndex, '120'); + }); + test('tap on trigger opens menu, then closes', () => { sinon.stub(element, 'dropdownTriggerTapHandler').callsFake(() => { assertIsDefined(element.dropdown);
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts index 4b88159..6a6118c 100644 --- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts +++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.ts
@@ -109,7 +109,7 @@ /** If false, then the "Show more" button was used to expand. */ @state() commitCollapsed = true; - @state() newContent = ''; + @property({type: String}) newContent = ''; @state() emails: EmailInfo[] = [];
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts index be5c2ee..47b1620 100644 --- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts +++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
@@ -3,6 +3,7 @@ * Copyright 2022 Google LLC * SPDX-License-Identifier: Apache-2.0 */ + import * as sinon from 'sinon'; import '../../../test/common-test-setup'; import {assert, fixture, html} from '@open-wc/testing'; @@ -63,7 +64,7 @@ suffix: '$3', }, }); - self.CANONICAL_PATH = 'http://localhost'; + self.CANONICAL_PATH = ''; element = ( await fixture( wrapInProvider( @@ -128,7 +129,7 @@ /* HTML */ ` <gr-endpoint-decorator name="formatted-text-endpoint"> <pre class="plaintext"> - FOO<a href="a.b.c" rel="noopener noreferrer" target="_blank">foo</a> + FOO<a href="a.b.c">foo</a> </pre> </gr-endpoint-decorator> ` @@ -159,10 +160,10 @@ <gr-endpoint-decorator name="formatted-text-endpoint"> <pre class="plaintext"> Start: - <a href="bug/123" rel="noopener noreferrer" target="_blank"> + <a href="bug/123"> bug/123 </a> - <a href="bug/456" rel="noopener noreferrer" target="_blank"> + <a href="bug/456"> bug/456 </a> </pre> @@ -200,13 +201,7 @@ LinkRewriteMe </a> text with complex link: A - <a - href="http://localhost/page?id=12" - rel="noopener noreferrer" - target="_blank" - > - Link 12 - </a> + <a href="/page?id=12">Link 12</a> </pre> </gr-endpoint-decorator> ` @@ -372,13 +367,7 @@ <p>text without a link: NotA Link 15 cats</p> <p> text with complex link: A - <a - href="http://localhost/page?id=12" - rel="noopener noreferrer" - target="_blank" - > - Link 12 - </a> + <a href="/page?id=12">Link 12</a> </p> </div> </gr-marked-element> @@ -420,13 +409,7 @@ </a> text without a link: NotA Link 15 cats text with complex link: A - <a - href="http://localhost/page?id=12" - rel="noopener noreferrer" - target="_blank" - > - Link 12 - </a> + <a href="/page?id=12">Link 12</a> </pre> </gr-endpoint-decorator> ` @@ -953,6 +936,45 @@ await checkLinking('google.com.blah/path', false); }); + test('reproduce b/519426997: linkification in loose list', async () => { + element.content = '1. A Link 1234\n\n2. aaa'; + await element.updateComplete; + await new Promise<void>(resolve => { + const listener = () => { + element.removeEventListener('marked-render-complete', listener); + resolve(); + }; + element.addEventListener('marked-render-complete', listener); + setTimeout(() => { + element.removeEventListener('marked-render-complete', listener); + resolve(); + }, 100); + }); + + assert.shadowDom.equal( + element, + /* HTML */ ` + <gr-endpoint-decorator name="formatted-text-endpoint"> + <gr-marked-element> + <div slot="markdown-html" class="markdown-html"> + <ol> + <li> + <p> + A + <a href="/page?id=1234">Link 1234</a> + </p> + </li> + <li> + <p>aaa</p> + </li> + </ol> + </div> + </gr-marked-element> + </gr-endpoint-decorator> + ` + ); + }); + suite('user suggest fix', () => { setup(async () => { const flagsService = getAppContext().flagsService;
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts index b185ca2..9b21725 100644 --- a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts +++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents.ts
@@ -5,6 +5,7 @@ */ import '../gr-avatar/gr-avatar'; import '../gr-button/gr-button'; +import '../gr-copy-clipboard/gr-copy-clipboard'; import '../gr-icon/gr-icon'; import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator'; import '../../plugins/gr-endpoint-param/gr-endpoint-param'; @@ -137,6 +138,12 @@ .email { color: var(--deemphasized-text-color); } + .email { + display: flex; + align-items: center; + gap: var(--spacing-xs); + color: var(--deemphasized-text-color); + } .action { border-top: 1px solid var(--border-color); padding: var(--spacing-s) var(--spacing-l); @@ -189,7 +196,18 @@ </div> <div class="account"> <h3 class="name heading-3">${this.account.name}</h3> - <div class="email">${this.account.email}</div> + <div class="email"> + <span>${this.account.email}</span> + ${this.account.email + ? html`<gr-copy-clipboard + hideInput + .text=${this.account.email} + copyTargetName="Email" + hasTooltip + buttonTitle="Copy email to clipboard" + ></gr-copy-clipboard>` + : nothing} + </div> </div> </div> ${this.renderAccountStatusPlugins()} ${this.renderAccountStatus()}
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents_test.ts b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents_test.ts index 6f3fe36..925ec17 100644 --- a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents_test.ts +++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account-contents_test.ts
@@ -68,7 +68,16 @@ </div> <div class="account"> <h3 class="heading-3 name">Kermit The Frog</h3> - <div class="email">kermit@gmail.com</div> + <div class="email"> + <span>kermit@gmail.com</span> + <gr-copy-clipboard + buttontitle="Copy email to clipboard" + copytargetname="Email" + hastooltip="" + hideinput="" + > + </gr-copy-clipboard> + </div> </div> </div> <gr-endpoint-decorator name="hovercard-status"> @@ -110,7 +119,16 @@ </div> <div class="account"> <h3 class="heading-3 name">Kermit The Frog</h3> - <div class="email">kermit@gmail.com</div> + <div class="email"> + <span>kermit@gmail.com</span> + <gr-copy-clipboard + buttontitle="Copy email to clipboard" + copytargetname="Email" + hastooltip="" + hideinput="" + > + </gr-copy-clipboard> + </div> </div> </div> <gr-endpoint-decorator name="hovercard-status"> @@ -213,6 +231,17 @@ assert.equal(voteableEl.innerText, 'Bar: +1'); }); + test('copy email clipboard is shown when email exists', () => { + const copyClipboard = queryAndAssert(element, 'gr-copy-clipboard'); + assert.isOk(copyClipboard); + }); + + test('copy email clipboard is not shown without email', async () => { + element.account = {...ACCOUNT, email: undefined}; + await element.updateComplete; + assert.isUndefined(query(element, 'gr-copy-clipboard')); + }); + test('remove reviewer', async () => { element.change = { ...createChange(),
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts index 0250d82..5abf4fc 100644 --- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts +++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.ts
@@ -6,6 +6,7 @@ import { AnnotationPluginApi, CoverageProvider, + DiffLayerFactory, TokenHoverListener, } from '../../../api/annotation'; import {PluginApi} from '../../../api/plugin'; @@ -36,4 +37,12 @@ listener, }); } + + addDiffLayer(factory: DiffLayerFactory): void { + this.reporting.trackApi(this.plugin, 'annotation', 'addDiffLayer'); + this.pluginsModel.diffLayerRegister({ + pluginName: this.plugin.getPluginName(), + factory, + }); + } }
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts index 70952da..5074405 100644 --- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts +++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.ts
@@ -30,6 +30,7 @@ import {modalStyles} from '../../../styles/gr-modal-styles'; import {Finalizable} from '../../../types/types'; import {materialStyles} from '../../../styles/gr-material-styles'; +import {changeViewIntegrationStyles} from '../../../styles/gr-change-view-integration-shared-styles'; enum PluginState { /** State that indicates the plugin is pending to be loaded. */ @@ -90,6 +91,7 @@ subPage: subpageStyles, table: tableStyles, modal: modalStyles, + changeViewIntegration: changeViewIntegrationStyles, }; private pluginListLoaded = false;
diff --git a/polygerrit-ui/app/elements/shared/gr-lib-loader/resemble-types.d.ts b/polygerrit-ui/app/elements/shared/gr-lib-loader/resemble-types.d.ts new file mode 100644 index 0000000..ce03092 --- /dev/null +++ b/polygerrit-ui/app/elements/shared/gr-lib-loader/resemble-types.d.ts
@@ -0,0 +1,28 @@ +/** + * @license + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Resemble from 'resemblejs'; + +// @types/resemblejs does not expose a global variable resemble and instead +// exposes the namespace Resemble. Because Resemble.js should remain an +// optional dependency, we define a global variable in a separate .d.ts file; +// otherwise, the TS compiler tries to import the JS library too and fails. +declare global { + interface Window { + resemble?: typeof Resemble; + } +}
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.ts b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.ts index 7de9701..6ee3bf3 100644 --- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.ts +++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.ts
@@ -18,6 +18,8 @@ FetchRequest as FetchRequestBase, } from '../../../../types/types'; import {ErrorCallback} from '../../../../api/rest'; +import {getAppContext} from '../../../../services/app-context'; +import {Timer} from '../../../../services/gr-reporting/gr-reporting'; import {Scheduler, Task} from '../../../../services/scheduler/scheduler'; import {RetryError} from '../../../../services/scheduler/retry-scheduler'; @@ -214,6 +216,7 @@ // TODO(kamilm): Consider changing the default to true. It makes more sense to // only skip the check if the caller wants to prosess status themselves. reportServerError?: boolean; + isHighPriority?: boolean; } export interface FetchOptionsInit { @@ -261,11 +264,15 @@ private readonly writeScheduler: Scheduler<Response> ) {} - private schedule(method: string, task: Task<Response>): Promise<Response> { + private schedule( + method: string, + task: Task<Response>, + name?: string + ): Promise<Response> { if (method === 'PUT' || method === 'POST' || method === 'DELETE') { - return this.writeScheduler.schedule(task); + return this.writeScheduler.schedule(task, name); } else { - return this.readScheduler.schedule(task); + return this.readScheduler.schedule(task, name); } } @@ -276,7 +283,45 @@ private fetchImpl(req: FetchRequest): Promise<Response> { const method = req.fetchOptions?.method ?? HttpMethod.GET; const startTime = Date.now(); + + const isWrite = + method === 'PUT' || method === 'POST' || method === 'DELETE'; + const origin = req.fetchOptions?.headers?.get(REQUEST_ORIGIN_HEADER); + const pluginName = origin?.startsWith('plugin:') ? origin : undefined; + const requestName = `${method} - ${ + req.anonymizedUrl || pluginName || 'unknown' + }`; + const debugRequestName = `${method} - ${req.anonymizedUrl || req.url}`; + let schedulerTimer: Timer | undefined; + let activeCount = 0; + + if (isWrite && !req.isHighPriority) { + activeCount = this.writeScheduler.activeCount; + console.info( + '[SchedulerWait] request:', + debugRequestName, + 'count:', + activeCount, + 'active:', + this.writeScheduler.activeRequests + ); + // 5 matches the max in flight limit of the write scheduler. + if (activeCount >= 5) { + schedulerTimer = getAppContext().reportingService.getTimer( + 'scheduler-waiting-time' + ); + } + } + const task = async () => { + if (schedulerTimer) { + schedulerTimer.end({ + count: activeCount, + request: requestName, + activeRequests: this.writeScheduler.activeRequests, + }); + schedulerTimer = undefined; + } const res = await this._auth.fetch(req.url, req.fetchOptions); // Check for "too many requests" error and throw RetryError to cause a // retry in this case, if the scheduler attempts retries. @@ -284,7 +329,9 @@ return res; }; - const resPromise = this.schedule(method, task).catch((err: unknown) => { + const resPromise = ( + req.isHighPriority ? task() : this.schedule(method, task, requestName) + ).catch((err: unknown) => { if (err instanceof RetryError) { return err.payload; } else {
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.ts b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.ts index ec49dd7..92feb24 100644 --- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.ts +++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.ts
@@ -157,6 +157,21 @@ response.status, ]); }); + + test('fetch falls back to requestOrigin for scheduler reporting', async () => { + const scheduleStub = sinon + .stub(readScheduler, 'schedule') + .resolves(new Response()); + + await helper.fetch({ + fetchOptions: getFetchOptions({requestOrigin: 'plugin:my-plugin'}), + url: '/dummy/url', + }); + + assert.isTrue(scheduleStub.calledOnce); + const name = scheduleStub.lastCall.args[1]; + assert.equal(name, 'GET - plugin:my-plugin'); + }); }); suite('fetchJSON()', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-suggestion-textarea/gr-suggestion-textarea.ts b/polygerrit-ui/app/elements/shared/gr-suggestion-textarea/gr-suggestion-textarea.ts index ffdfa25..4c594b9 100644 --- a/polygerrit-ui/app/elements/shared/gr-suggestion-textarea/gr-suggestion-textarea.ts +++ b/polygerrit-ui/app/elements/shared/gr-suggestion-textarea/gr-suggestion-textarea.ts
@@ -118,7 +118,7 @@ @state() suggestions: (Item | EmojiSuggestion)[] = []; - @state() private isDragging = false; + @property({type: Boolean}) private isDragging = false; @state() private allowMarkdownBase64ImagesInComments = false;
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight.ts b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight.ts index ed218aa..1ade0bc 100644 --- a/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight.ts +++ b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight.ts
@@ -53,6 +53,9 @@ export interface DiffBuilderInterface { getContentTdByLineEl(lineEl?: Element): Element | undefined; diffModel: DiffModel; + diffSelection?: { + getSelectedText?: (side: Side) => string; + }; } /** @@ -64,6 +67,7 @@ export class GrDiffHighlight { selectedRange?: SidedRange; + // visible for testing private diffBuilder?: DiffBuilderInterface; private diffTable?: HTMLElement; @@ -316,18 +320,18 @@ * positioning the tooltip. */ // visible for testing - positionActionBox( + async positionActionBox( actionBox: GrSelectionActionBox, startLine: number, range: Text | Element | Range - ) { + ): Promise<void> { if (startLine > 1) { actionBox.positionBelow = false; - actionBox.placeAbove(range); + await actionBox.placeAbove(range); return; } actionBox.positionBelow = true; - actionBox.placeBelow(range); + await actionBox.placeBelow(range); } private isRangeValid(range: NormalizedRange | null) { @@ -344,7 +348,10 @@ } // visible for testing - handleSelection(selection: Selection | Range, isMouseUp: boolean) { + async handleSelection( + selection: Selection | Range, + isMouseUp: boolean + ): Promise<void> { /* On Safari, the selection events may return a null range that should be ignored */ if (!selection) return; @@ -405,18 +412,33 @@ let actionBox = this.diffTable.querySelector('gr-selection-action-box'); if (!actionBox) { actionBox = document.createElement('gr-selection-action-box'); - this.diffTable.appendChild(actionBox); } + // eslint-disable-next-line @typescript-eslint/require-await + actionBox.getSelectionContext = async () => { + const path = this.diffBuilder?.diffModel?.getState()?.path; + const diffSelection = this.diffBuilder?.diffSelection; + const text = + diffSelection?.getSelectedText?.(side) ?? domRange.toString(); + return { + path, + side, + range, + text, + }; + }; const hoverCardText = this.diffBuilder?.diffModel.getState().actionHoverCardText; if (hoverCardText) { - actionBox.setAttribute('hoverCardText', hoverCardText); + actionBox.hoverCardText = hoverCardText; + } + if (!actionBox.parentElement) { + this.diffTable.appendChild(actionBox); } if (start.line === end.line) { - this.positionActionBox(actionBox, start.line, domRange); + await this.positionActionBox(actionBox, start.line, domRange); } else if (start.node instanceof Text) { if (start.column) { - this.positionActionBox( + await this.positionActionBox( actionBox, start.line, start.node.splitText(start.column) @@ -429,9 +451,13 @@ (start.node.firstChild instanceof Element || start.node.firstChild instanceof Text) ) { - this.positionActionBox(actionBox, start.line, start.node.firstChild); + await this.positionActionBox( + actionBox, + start.line, + start.node.firstChild + ); } else if (start.node instanceof Element || start.node instanceof Text) { - this.positionActionBox(actionBox, start.line, start.node); + await this.positionActionBox(actionBox, start.line, start.node); } else { console.warn('Failed to position comment action box.'); this.removeActionBox();
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight_test.ts index f44a251..2156d7e 100644 --- a/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight_test.ts +++ b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight_test.ts
@@ -22,7 +22,7 @@ waitUntil, } from '../../../test/test-utils'; import {GrSelectionActionBox} from '../gr-selection-action-box/gr-selection-action-box'; -import {DiffModel} from '../gr-diff-model/gr-diff-model'; +import {DiffModel, DiffState} from '../gr-diff-model/gr-diff-model'; // Splitting long lines in html into shorter rows breaks tests: // zero-length text nodes and new lines are not expected in some places @@ -319,6 +319,36 @@ assert.notOk(actionBox.positionBelow); }); + test('actionBox properties populated correctly', async () => { + const content = stubContent(138, Side.LEFT); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const diffModel = (element as any).diffBuilder.diffModel; + const getStateStub = sinon.stub(diffModel, 'getState').returns({ + path: 'foo/bar.txt', + } as unknown as DiffState); + + if (!content?.firstChild) assert.fail('content first child not found'); + emulateSelection(content.firstChild, 5, content.firstChild, 12); + + const actionBox = await waitQueryAndAssert<GrSelectionActionBox>( + diff, + 'gr-selection-action-box' + ); + + assert.isDefined(actionBox.getSelectionContext); + const context = await actionBox.getSelectionContext(); + assert.equal(context.path, 'foo/bar.txt'); + assert.equal(context.side, Side.LEFT); + assert.deepEqual(context.range, { + start_line: 138, + start_character: 5, + end_line: 138, + end_character: 12, + }); + assert.equal(context.text, 'Nam cum'); + getStateStub.restore(); + }); + test('multiline', () => { const startContent = stubContent(119, Side.RIGHT); const endContent = stubContent(120, Side.RIGHT);
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-styles.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-styles.ts index 894187c..dd41bec 100644 --- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-styles.ts +++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-styles.ts
@@ -346,6 +346,10 @@ display: block; margin-top: var(--spacing-xs); } + ::slotted(.comment-thread) { + --gr-comment-thread-width: 100%; + --gr-comment-thread-diff-max-width: 100%; + } gr-diff-row td.content div.contentText { background-color: var(--view-background-color); }
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts index 1423efb..73c792a 100644 --- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts +++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
@@ -46,6 +46,9 @@ if (renderPrefs?.responsive_mode) { return renderPrefs.responsive_mode; } + if (prefs?.responsive_mode) { + return prefs.responsive_mode; + } // Backwards compatibility to the line_wrapping param. if (prefs?.line_wrapping) { return 'FULL_RESPONSIVE';
diff --git a/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box.ts b/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box.ts index 2e73565..a803d5f 100644 --- a/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box.ts +++ b/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box.ts
@@ -8,6 +8,19 @@ import {fire} from '../../../utils/event-util'; import {html, LitElement} from 'lit'; import {customElement, property, query, state} from 'lit/decorators.js'; +import {Side} from '../../../api/diff'; +import {CommentRange} from '../../../api/rest-api'; + +export interface SelectionContext { + path?: string; + side?: Side; + range?: CommentRange; + text?: string; +} + +export interface SelectionActionBoxVisibleEventDetail { + getSelectionContext?: () => Promise<SelectionContext>; +} declare global { interface HTMLElementTagNameMap { @@ -18,7 +31,7 @@ 'create-comment-requested': CustomEvent<{}>; /** Fired when the selection action box is visible. */ - 'selection-action-box-visible': CustomEvent<{}>; + 'selection-action-box-visible': CustomEvent<SelectionActionBoxVisibleEventDetail>; } } @@ -37,6 +50,9 @@ @property({type: String}) hoverCardText = 'Press c to comment'; + @property({type: Object}) + getSelectionContext?: () => Promise<SelectionContext>; + /** * We need to absolutely position the element before we can show it. So * initially the tooltip must be invisible. @@ -86,6 +102,7 @@ // TODO(b/315277651): This is very similar in purpose to gr-tooltip-content. // We should figure out a way to reuse as much of the logic as possible. async placeAbove(el: Text | Element | Range) { + await this.updateComplete; if (!this.tooltip) return; await this.tooltip.updateComplete; const rect = this.getTargetBoundingRect(el); @@ -99,10 +116,13 @@ rect.left - parentRect.left + (rect.width - boxRect.width) / 2 }px`; this.invisible = false; - fire(this, 'selection-action-box-visible', {}); + fire(this, 'selection-action-box-visible', { + getSelectionContext: this.getSelectionContext, + }); } async placeBelow(el: Text | Element | Range) { + await this.updateComplete; if (!this.tooltip) return; await this.tooltip.updateComplete; const rect = this.getTargetBoundingRect(el); @@ -116,7 +136,9 @@ rect.left - parentRect.left + (rect.width - boxRect.width) / 2 }px`; this.invisible = false; - fire(this, 'selection-action-box-visible', {}); + fire(this, 'selection-action-box-visible', { + getSelectionContext: this.getSelectionContext, + }); } private getParentBoundingClientRect() {
diff --git a/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box_test.ts b/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box_test.ts index bd42135..4b0d4dd 100644 --- a/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box_test.ts +++ b/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box_test.ts
@@ -7,8 +7,9 @@ import '../../../test/common-test-setup'; import './gr-selection-action-box'; import {GrSelectionActionBox} from './gr-selection-action-box'; -import {queryAndAssert} from '../../../test/test-utils'; +import {listenOnce, queryAndAssert} from '../../../test/test-utils'; import {assert, fixture, html} from '@open-wc/testing'; +import {Side} from '../../../api/diff'; suite('gr-selection-action-box', () => { let container: HTMLDivElement; @@ -122,6 +123,138 @@ ); }); + test('fires selection-action-box-visible event with correct target and properties', async () => { + dispatchEventStub.restore(); + const visibleEventPromise = listenOnce<CustomEvent>( + document, + 'selection-action-box-visible' + ); + + element.getSelectionContext = () => + Promise.resolve({ + path: 'test-path.txt', + side: Side.LEFT, + range: { + start_line: 1, + start_character: 2, + end_line: 3, + end_character: 4, + }, + text: 'selected text', + }); + + await element.placeAbove(target); + + const ev = await visibleEventPromise; + + const targetEl = ev.target as HTMLElement; + assert.equal(targetEl, element); + assert.isDefined(ev.detail.getSelectionContext); + const context = await ev.detail.getSelectionContext!(); + assert.equal(context.path, 'test-path.txt'); + assert.equal(context.side, Side.LEFT); + assert.deepEqual(context.range, { + start_line: 1, + start_character: 2, + end_line: 3, + end_character: 4, + }); + assert.equal(context.text, 'selected text'); + }); + + test('event retargeting in shadow DOM', async () => { + dispatchEventStub.restore(); + + // Create a host element with shadow DOM + const host = document.createElement('div'); + const shadow = host.attachShadow({mode: 'open'}); + + // Put the action box and a target inside shadow DOM + const box = document.createElement('gr-selection-action-box'); + const shadowTarget = document.createElement('div'); + shadowTarget.textContent = 'shadow text'; + shadow.appendChild(box); + shadow.appendChild(shadowTarget); + + // Append host to body so it is in the document + document.body.appendChild(host); + try { + await box.updateComplete; + + // Stub necessary methods on the new box instance + sinon + .stub(box, 'tooltip' as keyof GrSelectionActionBox) + .value(element.tooltip); + sinon.stub(box, 'getTargetBoundingRect').returns({ + top: 42, + bottom: 20, + left: 30, + right: 40, + width: 100, + height: 60, + } as DOMRect); + + let capturedPath: EventTarget[] = []; + const visibleEventPromise = new Promise<CustomEvent>(resolve => { + const listener = (e: Event) => { + capturedPath = e.composedPath(); + document.removeEventListener( + 'selection-action-box-visible', + listener + ); + resolve(e as CustomEvent); + }; + document.addEventListener('selection-action-box-visible', listener); + }); + + box.getSelectionContext = () => + Promise.resolve({ + path: 'shadow-path.txt', + side: Side.RIGHT, + range: { + start_line: 5, + start_character: 6, + end_line: 7, + end_character: 8, + }, + text: 'shadow selected text', + }); + + await box.placeAbove(shadowTarget); + + const ev = await visibleEventPromise; + + // ev.target should be the host 'div' because of retargeting! + assert.equal(ev.target, host); + assert.isUndefined( + (ev.target as unknown as Record<string, unknown>).path + ); + assert.isUndefined( + (ev.target as unknown as Record<string, unknown>).side + ); + + // Assert on event detail (should be preserved despite retargeting!) + assert.isDefined(ev.detail.getSelectionContext); + const context = await ev.detail.getSelectionContext!(); + assert.equal(context.path, 'shadow-path.txt'); + assert.equal(context.side, Side.RIGHT); + assert.deepEqual(context.range, { + start_line: 5, + start_character: 6, + end_line: 7, + end_character: 8, + }); + assert.equal(context.text, 'shadow selected text'); + + // The actual target can be found in capturedPath + assert.isAbove(capturedPath.length, 0); + const actualTarget = capturedPath[0] as HTMLElement; + assert.equal(actualTarget, box); + } finally { + host.remove(); + } + }); + test('placeAbove for Element argument', async () => { await element.placeAbove(target); assert.equal(element.style.top, '25px');
diff --git a/polygerrit-ui/app/embed/gr-diff-entry-point.ts b/polygerrit-ui/app/embed/gr-diff-entry-point.ts new file mode 100644 index 0000000..ab02a0f --- /dev/null +++ b/polygerrit-ui/app/embed/gr-diff-entry-point.ts
@@ -0,0 +1,8 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +// DO NOT EXPORT ANYTHING FROM THIS FILE! +import './gr-diff';
diff --git a/polygerrit-ui/app/embed/gr-diff.ts b/polygerrit-ui/app/embed/gr-diff.ts index 68293f4..c7966a9 100644 --- a/polygerrit-ui/app/embed/gr-diff.ts +++ b/polygerrit-ui/app/embed/gr-diff.ts
@@ -4,7 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ import '../api/embed'; -import '../scripts/bundled-polymer'; import './diff/gr-diff/gr-diff'; import './gr-textarea'; import './diff/gr-diff-cursor/gr-diff-cursor';
diff --git a/polygerrit-ui/app/eslint-bazel.config.js b/polygerrit-ui/app/eslint-bazel.config.js index 9724124..53167ae 100644 --- a/polygerrit-ui/app/eslint-bazel.config.js +++ b/polygerrit-ui/app/eslint-bazel.config.js
@@ -76,6 +76,11 @@ } module.exports = defineConfig([ + { + linterOptions: { + reportUnusedDisableDirectives: 'error', + }, + }, globalIgnores([ '**/node_modules', '**/rollup.config.js',
diff --git a/polygerrit-ui/app/models/change/change-model.ts b/polygerrit-ui/app/models/change/change-model.ts index 9bbd23c..f00ddf4 100644 --- a/polygerrit-ui/app/models/change/change-model.ts +++ b/polygerrit-ui/app/models/change/change-model.ts
@@ -1071,7 +1071,8 @@ ? listChangesOptionsToHex( ListChangesOption.MESSAGES, ListChangesOption.ALL_REVISIONS, - ListChangesOption.REVIEWER_UPDATES + ListChangesOption.REVIEWER_UPDATES, + ListChangesOption.SKIP_DIFFSTAT ) : undefined )) as ChangeViewChangeInfo | undefined;
diff --git a/polygerrit-ui/app/models/change/change-model_test.ts b/polygerrit-ui/app/models/change/change-model_test.ts index e415729..1fec7f4 100644 --- a/polygerrit-ui/app/models/change/change-model_test.ts +++ b/polygerrit-ui/app/models/change/change-model_test.ts
@@ -711,7 +711,7 @@ assert.deepEqual(getChangeStub.lastCall.args, [ 42 as NumericChangeId, undefined, - '80204', + '880204', ]); assert.deepEqual(result.newMessages, { ...createChangeMessageInfo(),
diff --git a/polygerrit-ui/app/models/chat/chat-model.ts b/polygerrit-ui/app/models/chat/chat-model.ts index 619424b..781f67d 100644 --- a/polygerrit-ui/app/models/chat/chat-model.ts +++ b/polygerrit-ui/app/models/chat/chat-model.ts
@@ -37,6 +37,8 @@ import {contextItemEquals} from './context-item-util'; import {FilesModel, NormalizedFileInfo} from '../change/files-model'; import {isMagicPath} from '../../utils/path-list-util'; +import {getAppContext} from '../../services/app-context'; +import {Interaction, Timing} from '../../constants/reporting'; /** The available display modes in the chat panel. */ export enum ChatPanelMode { @@ -228,6 +230,10 @@ }, }; +export interface ChatTriggerParams { + prompt?: string; +} + export const chatModelToken = define<ChatModel>('chat-model'); export class ChatModel extends Model<ChatState> { @@ -616,6 +622,19 @@ }); }, emitError: (errorMessage: string) => { + getAppContext().reportingService.timeEnd(Timing.AI_CHAT_REQUEST, { + modelName: request.model_name, + actionId: action.id, + error: errorMessage, + }); + getAppContext().reportingService.reportInteraction( + Interaction.AI_CHAT_FAILURE, + { + modelName: request.model_name, + actionId: action.id, + error: errorMessage, + } + ); const state = this.getState(); if (state.id !== conversationId) return; const turns: readonly Turn[] = state.turns; @@ -630,6 +649,10 @@ }); }, done: () => { + getAppContext().reportingService.timeEnd(Timing.AI_CHAT_REQUEST, { + modelName: request.model_name, + actionId: action.id, + }); const state = this.getState(); if (state.id !== conversationId) return; assert(turnIndex < state.turns.length, 'turn index out of bounds'); @@ -642,6 +665,7 @@ }); }, }; + getAppContext().reportingService.time(Timing.AI_CHAT_REQUEST); this.plugin?.chat?.(request, listener); } @@ -700,6 +724,12 @@ if (userInput) this.sendChatRequest(0); } + processChatRequest(params: ChatTriggerParams) { + if (params.prompt) { + this.startNewChatWithUserInput(params.prompt, undefined, [], false); + } + } + addContextItem(contextItem: ContextItem) { const state = this.getState(); const currentItems = state.draftUserMessage.contextItems;
diff --git a/polygerrit-ui/app/models/chat/chat-model_test.ts b/polygerrit-ui/app/models/chat/chat-model_test.ts index 9f70830..c8d4fc0 100644 --- a/polygerrit-ui/app/models/chat/chat-model_test.ts +++ b/polygerrit-ui/app/models/chat/chat-model_test.ts
@@ -16,6 +16,8 @@ import sinon from 'sinon'; import {ParsedChangeInfo} from '../../types/types'; +import {getAppContext} from '../../services/app-context'; +import {Interaction, Timing} from '../../constants/reporting'; suite('chat-model tests', () => { let model: ChatModel; @@ -345,4 +347,107 @@ const state = model.getState(); assert.equal(state.turns[0].geminiMessage.regenerationIndex, 0); }); + + test('processChatRequest delegates to startNewChatWithUserInput', () => { + const startNewChatStub = sinon.stub(model, 'startNewChatWithUserInput'); + model.processChatRequest({prompt: 'Explain this code'}); + assert.isTrue( + startNewChatStub.calledOnceWith('Explain this code', undefined, [], false) + ); + }); + + suite('telemetry reporting', () => { + let timeStub: sinon.SinonStub; + let timeEndStub: sinon.SinonStub; + let reportInteractionStub: sinon.SinonStub; + + setup(() => { + timeStub = sinon.stub(getAppContext().reportingService, 'time'); + timeEndStub = sinon.stub(getAppContext().reportingService, 'timeEnd'); + reportInteractionStub = sinon.stub( + getAppContext().reportingService, + 'reportInteraction' + ); + + // Set up a change, models, and actions + const models = { + models: [ + { + model_id: 'test-model', + full_display_text: 'Test Model', + short_text: 'Test', + }, + ], + default_model_id: 'test-model', + }; + const actions = { + actions: [ + { + id: 'test-action', + display_text: 'Test Action', + initial_user_prompt: 'Test Prompt', + }, + ], + default_action_id: 'test-action', + }; + (provider.getActions as sinon.SinonStub).resolves(actions); + (provider.getModels as sinon.SinonStub).resolves(models); + + changeModel.updateStateChange(createParsedChange()); + }); + + test('chat request starts a timer', async () => { + await new Promise(resolve => setTimeout(resolve, 0)); + + model.updateUserInput('hello'); + model.chat('hello', 'test-action', 0); + + assert.isTrue(timeStub.calledOnceWith(Timing.AI_CHAT_REQUEST)); + }); + + test('chat request success stops the timer', async () => { + await new Promise(resolve => setTimeout(resolve, 0)); + + (provider.chat as sinon.SinonStub).callsFake((_, listener) => { + listener.done(); + }); + + model.updateUserInput('hello'); + model.chat('hello', 'test-action', 0); + + assert.isTrue( + timeEndStub.calledOnceWith(Timing.AI_CHAT_REQUEST, { + modelName: 'test-model', + actionId: 'test-action', + }) + ); + }); + + test('chat request failure stops the timer and logs interaction', async () => { + await new Promise(resolve => setTimeout(resolve, 0)); + + (provider.chat as sinon.SinonStub).callsFake((_, listener) => { + listener.emitError('some error'); + }); + + model.updateUserInput('hello'); + model.chat('hello', 'test-action', 0); + + assert.isTrue( + timeEndStub.calledOnceWith(Timing.AI_CHAT_REQUEST, { + modelName: 'test-model', + actionId: 'test-action', + error: 'some error', + }) + ); + + assert.isTrue( + reportInteractionStub.calledOnceWith(Interaction.AI_CHAT_FAILURE, { + modelName: 'test-model', + actionId: 'test-action', + error: 'some error', + }) + ); + }); + }); });
diff --git a/polygerrit-ui/app/models/checks/checks-util.ts b/polygerrit-ui/app/models/checks/checks-util.ts index f997b11..239059b 100644 --- a/polygerrit-ui/app/models/checks/checks-util.ts +++ b/polygerrit-ui/app/models/checks/checks-util.ts
@@ -23,6 +23,13 @@ import {DraftInfo, FixSuggestionInfo} from '../../types/common'; import {OpenFixPreviewEventDetail} from '../../types/events'; import {isDefined} from '../../types/types'; +import { + AiAgentChatEventDetails, + AiAgentCheckEventDetails, + AiAgentEventDetails, + Interaction, +} from '../../constants/reporting'; +import {ReportingService as Reporting} from '../../services/gr-reporting/gr-reporting'; import {createNew, PROVIDED_FIX_ID} from '../../utils/comment-util'; import {assert, assertIsDefined, assertNever} from '../../utils/common-util'; import {fire} from '../../utils/event-util'; @@ -93,10 +100,12 @@ } } -function pleaseFixMessage(result: RunResult) { - return `Please fix this ${result.category} reported by ${result.checkName}: ${result.summary} - -${result.message}`; +export function pleaseFixMessage(result: RunResult) { + const message = + result.summary === result.message + ? result.message + : `${result.summary}\n\n${result.message}`; + return `Please fix this ${result.category} reported by ${result.checkName}: ${message}`; } /** @@ -592,3 +601,116 @@ const hasFixes = (result?.fixes ?? []).length > 0; return hasMessage || hasMultipleLinks || hasPointers || hasFixes; } + +function getAiAgentChatEventDetails( + runResult: RunResult, + commentId?: string +): AiAgentChatEventDetails | undefined { + const externalId = runResult.externalId; + if (!externalId) return; + // Use JSON.parse. We expect agentId, conversationId, turnIndex. + try { + const parsed = JSON.parse(externalId); + const agentId = parsed['agentId']; + const conversationId = parsed['conversationId']; + const turnIndex = parsed['turnIndex']; + const suggestionId = parsed['suggestionId']; + if ( + !agentId || + !conversationId || + turnIndex === null || + turnIndex === undefined + ) { + return; + } + /* eslint-disable object-shorthand */ + // prettier-ignore + const eventDetails: AiAgentChatEventDetails = { + 'agentId': agentId, + 'conversationId': conversationId, + 'turnIndex': Number(turnIndex), + 'suggestionId': suggestionId, + 'commentId': commentId, + }; + return eventDetails; + /* eslint-enable object-shorthand */ + } catch (e) { + return undefined; + } +} + +function getAiAgentCheckEventDetails( + runResult: RunResult, + commentId?: string +): AiAgentCheckEventDetails | undefined { + if (!runResult.externalId) return; + if (!runResult.isAiPowered) return; + if (!runResult.checkName) return; + if (!runResult.checkDescription) return; + + /* eslint-disable object-shorthand */ + // prettier-ignore + const eventDetails: AiAgentCheckEventDetails = { + 'checkName': runResult.checkName, + 'checkDescription': runResult.checkDescription, + 'externalId': runResult.externalId, + 'commentId': commentId, + }; + return eventDetails; + /* eslint-enable object-shorthand */ +} + +function getAiAgentEventDetails( + runResult: RunResult, + commentId?: string +): AiAgentEventDetails | undefined { + const chatDetails = getAiAgentChatEventDetails(runResult, commentId); + if (chatDetails) return chatDetails; + return getAiAgentCheckEventDetails(runResult, commentId); +} + +/** + * Reports a "Get AI Fix" click interaction on a Code Review Agent check. + */ +export function reportAiAgentGetAIFix( + reporting: Reporting, + runResult: RunResult +) { + const eventDetails = getAiAgentEventDetails(runResult); + if (!eventDetails) return; + reporting.reportInteraction( + Interaction.AI_AGENT_GET_FIX_CLICKED, + eventDetails + ); +} + +/** + * Reports a "Please Fix" click interaction on a Code Review Agent check. + */ +export function reportAiAgentCommentDraft( + reporting: Reporting, + runResult: RunResult, + commentId?: string +) { + const eventDetails = getAiAgentEventDetails(runResult, commentId); + if (!eventDetails) return; + reporting.reportInteraction( + Interaction.AI_AGENT_SUGGESTION_TO_COMMENT, + eventDetails + ); +} + +/** + * Reports a "manual copy" interaction on a Code Review Agent check. + */ +export function reportAiAgentSuggestionCopy( + reporting: Reporting, + runResult: RunResult +) { + const eventDetails = getAiAgentEventDetails(runResult); + if (!eventDetails) return; + reporting.reportInteraction( + Interaction.AI_AGENT_SUGGESTION_CONTENT_COPIED, + eventDetails + ); +}
diff --git a/polygerrit-ui/app/models/checks/checks-util_test.ts b/polygerrit-ui/app/models/checks/checks-util_test.ts index e88721a..7359381 100644 --- a/polygerrit-ui/app/models/checks/checks-util_test.ts +++ b/polygerrit-ui/app/models/checks/checks-util_test.ts
@@ -11,14 +11,19 @@ AttemptChoice, computeIsExpandable, LATEST_ATTEMPT, + pleaseFixMessage, rectifyFix, + reportAiAgentCommentDraft, + reportAiAgentGetAIFix, sortAttemptChoices, stringToAttemptChoice, toComment, } from './checks-util'; -import {Fix, Replacement} from '../../api/checks'; +import {Interaction} from '../../constants/reporting'; +import {Category, Fix, Replacement} from '../../api/checks'; import {PROVIDED_FIX_ID} from '../../utils/comment-util'; import {CommentRange, RevisionPatchSetNum} from '../../api/rest-api'; +import {ReportingService} from '../../services/gr-reporting/gr-reporting'; import { createCheckFix, createCheckLink, @@ -245,4 +250,206 @@ assert.isUndefined(comment.line); }); }); + + suite('AI agent reporting', () => { + let reportInteractionStub: sinon.SinonStub; + + setup(() => { + reportInteractionStub = sinon.stub(); + }); + + test('reportAiAgentGetAIFix', () => { + const reporting = { + reportInteraction: reportInteractionStub, + } as unknown as ReportingService; + const runResult = { + ...createRunResult(), + isAiPowered: true, + checkName: 'test-check-name', + checkDescription: 'test-description', + externalId: JSON.stringify({ + agentId: 'test-agent', + conversationId: 'test-conv', + turnIndex: 1, + suggestionId: 1, + }), + }; + + reportAiAgentGetAIFix(reporting, runResult); + + assert.isTrue(reportInteractionStub.calledOnce); + assert.equal( + reportInteractionStub.lastCall.args[0], + Interaction.AI_AGENT_GET_FIX_CLICKED + ); + assert.deepEqual(reportInteractionStub.lastCall.args[1], { + agentId: 'test-agent', + conversationId: 'test-conv', + turnIndex: 1, + suggestionId: 1, + commentId: undefined, + }); + }); + + test('reportAiAgentCommentDraft', () => { + const reporting = { + reportInteraction: reportInteractionStub, + } as unknown as ReportingService; + const runResult = { + ...createRunResult(), + isAiPowered: true, + checkName: 'test-check-name', + checkDescription: 'test-description', + externalId: JSON.stringify({ + agentId: 'test-agent', + conversationId: 'test-conv', + turnIndex: 2, + }), + }; + + reportAiAgentCommentDraft(reporting, runResult, 'test-comment-id'); + + assert.isTrue(reportInteractionStub.calledOnce); + assert.equal( + reportInteractionStub.lastCall.args[0], + Interaction.AI_AGENT_SUGGESTION_TO_COMMENT + ); + assert.deepEqual(reportInteractionStub.lastCall.args[1], { + agentId: 'test-agent', + conversationId: 'test-conv', + turnIndex: 2, + suggestionId: undefined, + commentId: 'test-comment-id', + }); + }); + + test('does not report if externalId is missing', () => { + const reporting = { + reportInteraction: reportInteractionStub, + } as unknown as ReportingService; + const runResult = { + ...createRunResult(), + isAiPowered: true, + }; + + reportAiAgentGetAIFix(reporting, runResult); + assert.isFalse(reportInteractionStub.called); + + reportAiAgentCommentDraft(reporting, runResult); + assert.isFalse(reportInteractionStub.called); + }); + + test('does not report if isAiPowered is false or missing', () => { + const reporting = { + reportInteraction: reportInteractionStub, + } as unknown as ReportingService; + const runResult = { + ...createRunResult(), + isAiPowered: false, + externalId: 'some-id', + }; + + reportAiAgentGetAIFix(reporting, runResult); + assert.isFalse(reportInteractionStub.called); + + const runResultMissing = { + ...createRunResult(), + isAiPowered: undefined, + externalId: 'some-id', + }; + + reportAiAgentCommentDraft(reporting, runResultMissing); + assert.isFalse(reportInteractionStub.called); + }); + + test('reports check event details if externalId is plain string (not chat JSON)', () => { + const reporting = { + reportInteraction: reportInteractionStub, + } as unknown as ReportingService; + const runResult = { + ...createRunResult(), + isAiPowered: true, + checkName: 'test-check-name', + checkDescription: 'test-description', + externalId: 'plain-string-external-id', + }; + + reportAiAgentGetAIFix(reporting, runResult); + assert.isTrue(reportInteractionStub.calledOnce); + assert.deepEqual(reportInteractionStub.lastCall.args[1], { + checkName: 'test-check-name', + checkDescription: 'test-description', + externalId: 'plain-string-external-id', + commentId: undefined, + }); + }); + + test('reports check event details if externalId is JSON but missing chat fields', () => { + const reporting = { + reportInteraction: reportInteractionStub, + } as unknown as ReportingService; + const runResult = { + ...createRunResult(), + isAiPowered: true, + checkName: 'test-check-name', + checkDescription: 'test-description', + externalId: JSON.stringify({agentId: 'test-agent'}), + }; + + reportAiAgentGetAIFix(reporting, runResult); + assert.isTrue(reportInteractionStub.calledOnce); + assert.deepEqual(reportInteractionStub.lastCall.args[1], { + checkName: 'test-check-name', + checkDescription: 'test-description', + externalId: runResult.externalId, + commentId: undefined, + }); + }); + + test('does not report check event if checkDescription is missing', () => { + const reporting = { + reportInteraction: reportInteractionStub, + } as unknown as ReportingService; + const runResult = { + ...createRunResult(), + isAiPowered: true, + checkName: 'test-check-name', + checkDescription: undefined, + externalId: 'plain-string-external-id', + }; + + reportAiAgentGetAIFix(reporting, runResult); + assert.isFalse(reportInteractionStub.called); + }); + }); + + suite('pleaseFixMessage', () => { + test('when summary and message are the same', () => { + const result: RunResult = { + ...createRunResult(), + category: Category.WARNING, + checkName: 'test-check-name', + summary: 'this is the warning text', + message: 'this is the warning text', + }; + assert.equal( + pleaseFixMessage(result), + 'Please fix this WARNING reported by test-check-name: this is the warning text' + ); + }); + + test('when summary and message are not the same', () => { + const result: RunResult = { + ...createRunResult(), + category: Category.ERROR, + checkName: 'test-check-name', + summary: 'this is the summary text', + message: 'this is the message body text', + }; + assert.equal( + pleaseFixMessage(result), + 'Please fix this ERROR reported by test-check-name: this is the summary text\n\nthis is the message body text' + ); + }); + }); });
diff --git a/polygerrit-ui/app/models/comments/comments-model.ts b/polygerrit-ui/app/models/comments/comments-model.ts index f180a8e..db15121 100644 --- a/polygerrit-ui/app/models/comments/comments-model.ts +++ b/polygerrit-ui/app/models/comments/comments-model.ts
@@ -67,7 +67,7 @@ import {isDefined} from '../../types/types'; import {ChangeViewModel} from '../views/change'; import {NavigationService} from '../../elements/core/gr-navigation/gr-navigation'; -import {readJSONResponsePayload} from '../../elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper'; +import {parsePrefixedJSON} from '../../elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper'; export interface CommentState { /** undefined means 'still loading' */ @@ -747,22 +747,38 @@ if (showToast) this.showStartRequest(); const timing = isNew(draft) ? Timing.DRAFT_CREATE : Timing.DRAFT_UPDATE; const timer = this.reporting.getTimer(timing); - let savedComment; try { + const networkTimer = this.reporting.getTimer(`${timing} - network`); const result = await this.restApiService.saveDiffDraft( changeNum, draft.patch_set, convertToCommentInput(draft) ); + networkTimer.end(); if (changeNum !== this.changeNum) return draft; if (!result.ok) throw new Error('request failed'); - savedComment = (await readJSONResponsePayload(result)) - .parsed as unknown as CommentInfo; + + const parseTimer = this.reporting.getTimer(`${timing} - parse`); + const textTimer = this.reporting.getTimer(`${timing} - parse - text`); + const text = await result.text(); + textTimer.end(); + + const jsonTimer = this.reporting.getTimer(`${timing} - parse - json`); + try { + savedComment = parsePrefixedJSON(text) as unknown as CommentInfo; + } catch (_) { + throw new Error( + `Response payload is not prefixed json. Payload: ${text}` + ); + } + jsonTimer.end(); + parseTimer.end(); } catch (error) { if (showToast) this.handleFailedDraftRequest(); const draftError: DraftInfo = {...draft, savingState: SavingState.ERROR}; this.modifyState(s => setDraft(s, draftError)); + timer.end({error: true}); return draftError; }
diff --git a/polygerrit-ui/app/models/plugins/plugins-model.ts b/polygerrit-ui/app/models/plugins/plugins-model.ts index 8ed7cfe..9074c21 100644 --- a/polygerrit-ui/app/models/plugins/plugins-model.ts +++ b/polygerrit-ui/app/models/plugins/plugins-model.ts
@@ -12,7 +12,11 @@ } from '../../api/checks'; import {Model} from '../base/model'; import {select} from '../../utils/observable-util'; -import {CoverageProvider, TokenHoverListener} from '../../api/annotation'; +import { + CoverageProvider, + DiffLayerFactory, + TokenHoverListener, +} from '../../api/annotation'; import {SuggestionsProvider} from '../../api/suggestions'; import {ChangeUpdatesPublisher} from '../../api/change-updates'; import {AiCodeReviewProvider} from '../../api/ai-code-review'; @@ -59,6 +63,11 @@ listener: TokenHoverListener; } +export interface DiffLayerPlugin { + pluginName: string; + factory: DiffLayerFactory; +} + export interface ChecksUpdate { pluginName: string; run: CheckRun; @@ -111,6 +120,11 @@ * annotationApi().addTokenHoverListener(). */ tokenHighlightPlugins: TokenHoverListenerPlugin[]; + + /** + * List of plugins that have registered a diff layer factory. + */ + diffLayerPlugins: DiffLayerPlugin[]; } export class PluginsModel extends Model<PluginsState> { @@ -154,6 +168,11 @@ state => state.suggestionsPlugins ); + public diffLayerPlugins$ = select( + this.state$, + state => state.diffLayerPlugins + ); + public pluginsLoaded$ = select(this.state$, state => state.pluginsLoaded); constructor() { @@ -167,6 +186,7 @@ flowsAutosubmitPlugins: [], suggestionsPlugins: [], tokenHighlightPlugins: [], + diffLayerPlugins: [], }); } @@ -302,6 +322,22 @@ this.setState(nextState); } + diffLayerRegister(plugin: DiffLayerPlugin) { + const nextState = {...this.getState()}; + nextState.diffLayerPlugins = [...nextState.diffLayerPlugins]; + const alreadyRegistered = nextState.diffLayerPlugins.some( + p => p.pluginName === plugin.pluginName + ); + if (alreadyRegistered) { + console.warn( + `${plugin.pluginName} tried to register twice as a diff layer provider. Ignored.` + ); + return; + } + nextState.diffLayerPlugins.push(plugin); + this.setState(nextState); + } + checksUpdate(update: ChecksUpdate) { const plugins = this.getState().checksPlugins; const plugin = plugins.find(p => p.pluginName === update.pluginName);
diff --git a/polygerrit-ui/app/models/plugins/plugins-model_test.ts b/polygerrit-ui/app/models/plugins/plugins-model_test.ts index 7aabae7..bdcb8b1 100644 --- a/polygerrit-ui/app/models/plugins/plugins-model_test.ts +++ b/polygerrit-ui/app/models/plugins/plugins-model_test.ts
@@ -4,9 +4,15 @@ * SPDX-License-Identifier: Apache-2.0 */ import '../../test/common-test-setup'; +import * as sinon from 'sinon'; import './plugins-model'; import {ChecksApiConfig, ChecksProvider, ResponseCode} from '../../api/checks'; -import {ChecksPlugin, ChecksUpdate, PluginsModel} from './plugins-model'; +import { + ChecksPlugin, + ChecksUpdate, + DiffLayerPlugin, + PluginsModel, +} from './plugins-model'; import {createRun, createRunResult} from '../../test/test-data-generators'; import {assert} from '@open-wc/testing'; @@ -29,6 +35,7 @@ suite('plugins-model tests', () => { let model: PluginsModel; let checksPlugins: ChecksPlugin[] = []; + let diffLayerPlugins: DiffLayerPlugin[] = []; const register = function () { model.checksRegister({ pluginName: PLUGIN_NAME, @@ -41,6 +48,7 @@ model = new PluginsModel(); model.state$.subscribe(s => { checksPlugins = s.checksPlugins; + diffLayerPlugins = s.diffLayerPlugins; }); }); @@ -81,4 +89,33 @@ assert.equal(update?.pluginName, PLUGIN_NAME); }); + + test('diffLayerRegister', async () => { + assert.isFalse(diffLayerPlugins.some(p => p.pluginName === PLUGIN_NAME)); + + const factory = () => { + return { + annotate: () => {}, + }; + }; + model.diffLayerRegister({ + pluginName: PLUGIN_NAME, + factory, + }); + + assert.isTrue(diffLayerPlugins.some(p => p.pluginName === PLUGIN_NAME)); + assert.equal( + diffLayerPlugins.find(p => p.pluginName === PLUGIN_NAME)?.factory, + factory + ); + + // Try to register again + const consoleWarnStub = sinon.stub(console, 'warn'); + model.diffLayerRegister({ + pluginName: PLUGIN_NAME, + factory, + }); + assert.isTrue(consoleWarnStub.calledOnce); + consoleWarnStub.restore(); + }); });
diff --git a/polygerrit-ui/app/node_modules_licenses/BUILD b/polygerrit-ui/app/node_modules_licenses/BUILD index 77400c6..a1c869d 100644 --- a/polygerrit-ui/app/node_modules_licenses/BUILD +++ b/polygerrit-ui/app/node_modules_licenses/BUILD
@@ -1,23 +1,16 @@ -load("@npm//@bazel/concatjs:index.bzl", "ts_library") -load("//tools/node_tools/node_modules_licenses:node_modules_licenses.bzl", "node_modules_licenses") +load("@aspect_rules_js//js:defs.bzl", "js_run_binary") +load("@aspect_rules_ts//ts:defs.bzl", "ts_project") +load("@com_googlesource_gerrit_bazlets//tools:genrule2.bzl", "genrule2") -filegroup( - name = "licenses-texts", - srcs = glob(["licenses/*.txt"]), -) - -# TODO: Would be nice to use `ts_project` from @bazel/typescript instead. -# We would prefer to not depend on @bazel/concatjs ... -ts_library( +ts_project( name = "licenses-config", srcs = [ "licenses.ts", ], - compiler = "//tools/node_tools:tsc_wrapped-bin", tsconfig = "tsconfig.json", deps = [ + "//tools/node_tools:node_modules", "//tools/node_tools/node_modules_licenses:licenses-map", - "@tools_npm//:node_modules", ], ) @@ -42,20 +35,50 @@ cmd = "cp $< $@", ) -# filegroup is enough (instead of rollup-bundle), because we are not going to run licenses.ts file filegroup( name = "licenses-config-js", srcs = [":licenses-config"], - output_group = "es5_sources", +) + +genrule2( + name = "node_modules_files", + srcs = ["//polygerrit-ui/app:node_modules"], + outs = ["package_json.properties"], + cmd = "find -L $(locations //polygerrit-ui/app:node_modules) -type f -print | grep '.aspect_rules_js' > $$ROOT/$@", +) + +filegroup( + name = "licenses-texts", + srcs = glob(["licenses/*.txt"]), +) + +genrule2( + name = "licenses-text-properties", + srcs = [":licenses-texts"], + outs = ["licenses.properties"], + cmd = "find ./polygerrit-ui/app/node_modules_licenses/licenses -name '*.txt' > $$ROOT/$@", ) # Generate polygerrit-licenses.json for files in @ui_npm workspace. # For details - see comments for node_modules_licenses rule and # tools/node_tools/node_modules_licenses/license-map-generator.ts file -node_modules_licenses( +js_run_binary( name = "polygerrit-licenses", - licenses_config = "licenses-config-js", - licenses_texts = [":licenses-texts"], - node_modules = "@ui_npm//:node_modules", + srcs = [ + "//polygerrit-ui/app:node_modules", + "//polygerrit-ui/app/node_modules_licenses:licenses-config-js", + "//polygerrit-ui/app/node_modules_licenses:licenses-text-properties", + "//polygerrit-ui/app/node_modules_licenses:licenses-texts", + "//polygerrit-ui/app/node_modules_licenses:node_modules_files", + ], + outs = ["polygerrit-licenses.json"], + args = [ + "$(location //polygerrit-ui/app/node_modules_licenses:licenses-config-js)", + "$(location //polygerrit-ui/app/node_modules_licenses:node_modules_files)", + "$(location //polygerrit-ui/app/node_modules_licenses:licenses-text-properties)", + "$@", + ], + chdir = "", + tool = "//tools/node_tools/node_modules_licenses:license-map-generator-bin", visibility = ["//visibility:public"], )
diff --git a/polygerrit-ui/app/node_modules_licenses/licenses.ts b/polygerrit-ui/app/node_modules_licenses/licenses.ts index 3189bcb..d5880dc 100644 --- a/polygerrit-ui/app/node_modules_licenses/licenses.ts +++ b/polygerrit-ui/app/node_modules_licenses/licenses.ts
@@ -44,30 +44,12 @@ sharedLicenseFile: 'lit.txt', }; - public static Polymer2014: LicenseInfo = { - name: 'Polymer-2014', - type: LicenseTypes.Bsd3, - sharedLicenseFile: 'polymer-2014.txt', - }; - public static Polymer2015: LicenseInfo = { name: 'Polymer-2015', type: LicenseTypes.Bsd3, sharedLicenseFile: 'polymer-2015.txt', }; - public static Polymer2017: LicenseInfo = { - name: 'Polymer-2017', - type: LicenseTypes.Bsd3, - sharedLicenseFile: 'polymer-2017.txt', - }; - - public static Polymer2018: LicenseInfo = { - name: 'Polymer-2018', - type: LicenseTypes.Bsd3, - sharedLicenseFile: 'polymer-2018.txt', - }; - public static IsArray: LicenseInfo = { name: 'isarray', type: LicenseTypes.Mit, @@ -99,17 +81,9 @@ license: SharedLicenses.Lit, }, { - name: '@polymer/decorators', - license: SharedLicenses.Polymer2017, - }, - { - name: '@polymer/font-roboto', - license: SharedLicenses.Polymer2015, - }, - { name: '@polymer/font-roboto-local', license: SharedLicenses.Polymer2015, - filesFilter: fileName => + filesFilter: (fileName: string) => !fontsRobotoFilter(fileName) && !fontsRobotomonoFilter(fileName), }, { @@ -131,10 +105,6 @@ filesFilter: fontsRobotomonoFilter, }, { - name: '@polymer/polymer', - license: SharedLicenses.Polymer2017, - }, - { name: '@material/web', license: { name: '@material/web', @@ -168,11 +138,7 @@ }, { name: '@webcomponents/shadycss', - license: SharedLicenses.Polymer2017, - }, - { - name: '@webcomponents/webcomponentsjs', - license: SharedLicenses.Polymer2018, + license: SharedLicenses.Polymer2015, }, { name: 'isarray', @@ -199,14 +165,6 @@ }, }, { - name: 'polymer-resin', - license: SharedLicenses.Polymer2018, - }, - { - name: 'polymer-bridges', - license: SharedLicenses.Polymer2018, - }, - { name: 'polygerrit-gr-page', license: SharedLicenses.Page, }, @@ -332,7 +290,7 @@ license: { name: 'marked', type: LicenseTypes.Mit, - packageLicenseFile: 'LICENSE.md', + packageLicenseFile: 'LICENSE', }, }, {
diff --git a/polygerrit-ui/app/node_modules_licenses/licenses/polymer-2014.txt b/polygerrit-ui/app/node_modules_licenses/licenses/polymer-2014.txt deleted file mode 100644 index 9a35430..0000000 --- a/polygerrit-ui/app/node_modules_licenses/licenses/polymer-2014.txt +++ /dev/null
@@ -1,34 +0,0 @@ -Copyright (c) 2014 The Polymer Project Authors. All rights reserved. - -This code may only be used under the BSD style license found at -http://polymer.github.io/LICENSE.txt The complete set of authors may be found at -http://polymer.github.io/AUTHORS.txt The complete set of contributors may be -found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as -part of the polymer project is also subject to an additional IP rights grant -found at http://polymer.github.io/PATENTS.txt - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/polygerrit-ui/app/node_modules_licenses/licenses/polymer-2017.txt b/polygerrit-ui/app/node_modules_licenses/licenses/polymer-2017.txt deleted file mode 100644 index 440f70f..0000000 --- a/polygerrit-ui/app/node_modules_licenses/licenses/polymer-2017.txt +++ /dev/null
@@ -1,34 +0,0 @@ -Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - -This code may only be used under the BSD style license found at -http://polymer.github.io/LICENSE.txt The complete set of authors may be found at -http://polymer.github.io/AUTHORS.txt The complete set of contributors may be -found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as -part of the polymer project is also subject to an additional IP rights grant -found at http://polymer.github.io/PATENTS.txt - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/polygerrit-ui/app/node_modules_licenses/licenses/polymer-2018.txt b/polygerrit-ui/app/node_modules_licenses/licenses/polymer-2018.txt deleted file mode 100644 index 9381974..0000000 --- a/polygerrit-ui/app/node_modules_licenses/licenses/polymer-2018.txt +++ /dev/null
@@ -1,34 +0,0 @@ -Copyright (c) 2018 The Polymer Project Authors. All rights reserved. - -This code may only be used under the BSD style license found at -http://polymer.github.io/LICENSE.txt The complete set of authors may be found at -http://polymer.github.io/AUTHORS.txt The complete set of contributors may be -found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as -part of the polymer project is also subject to an additional IP rights grant -found at http://polymer.github.io/PATENTS.txt - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/polygerrit-ui/app/node_modules_licenses/tsconfig.json b/polygerrit-ui/app/node_modules_licenses/tsconfig.json index de1eb24..eb5ee35 100644 --- a/polygerrit-ui/app/node_modules_licenses/tsconfig.json +++ b/polygerrit-ui/app/node_modules_licenses/tsconfig.json
@@ -6,8 +6,11 @@ "esModuleInterop": true, "strict": true, "moduleResolution": "node", - "outDir": "../../../.ts-out/polygerrit-ui/node_modules_licenses", // Not used in bazel - "types": ["node"] + "types": ["node"], + "typeRoots": [ + "../../../tools/node_tools/node_modules/@types", + "../../../tools/node_tools/node_modules_licenses" + ] }, "include": ["**/*.ts"] }
diff --git a/polygerrit-ui/app/package.json b/polygerrit-ui/app/package.json index d456a58..974f4ff 100644 --- a/polygerrit-ui/app/package.json +++ b/polygerrit-ui/app/package.json
@@ -4,13 +4,10 @@ "browser": true, "dependencies": { "@material/web": "^2.4.1", - "@polymer/decorators": "^3.0.0", "@polymer/font-roboto-local": "^3.0.2", - "@polymer/polymer": "3.5.2", "@types/resemblejs": "^4.1.3", "@types/resize-observer-browser": "^0.1.11", "@webcomponents/shadycss": "^1.11.2", - "@webcomponents/webcomponentsjs": "^2.8.0", "highlight.js": "^11.11.1", "highlightjs-closure-templates": "https://github.com/highlightjs/highlightjs-closure-templates#02fb0646e0499084f96a99b8c6f4a0d7bd1d33ba", "highlightjs-epp": "https://github.com/highlightjs/highlightjs-epp#9f9e1a92f37c217c68899c7d3bdccb4d134681b9", @@ -19,25 +16,12 @@ "highlightjs-vue": "https://github.com/paladox/highlightjs-vue#44eed074ea0110d1ad03d2cbd77d27027cf7bb04", "immer": "^9.0.21", "lit": "^3.3.1", - "marked": "^17.0.1", - "polymer-bridges": "file:../../polymer-bridges", - "polymer-resin": "^2.0.1", + "marked": "^18.0.3", "resemblejs": "rsmbl/Resemble.js#66a55c5bfc3bda2303ad632ee8ce3c727b415917", "rxjs": "^6.6.7", "safevalues": "^1.2.0", "web-vitals": "^5.1.0" }, - "dependencies // comments": { - "@polymer/polymer": [ - "There is a an issue with release 3.5.2. Tests are failing with:", - "NotSupportedError: Failed to execute 'define' on 'CustomElementRegistry':", - "the name 'dom-module' has already been used with this registry at ...", - "We fix this by forcing all packages that use polymer to use 3.5.2." - ] - }, - "resolutions": { - "@polymer/polymer": "3.5.2" - }, "license": "Apache-2.0", "private": true -} \ No newline at end of file +}
diff --git a/polygerrit-ui/app/pnpm-lock.yaml b/polygerrit-ui/app/pnpm-lock.yaml new file mode 100644 index 0000000..da97189 --- /dev/null +++ b/polygerrit-ui/app/pnpm-lock.yaml
@@ -0,0 +1,535 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@material/web': + specifier: ^2.4.1 + version: 2.4.1 + '@polymer/font-roboto-local': + specifier: ^3.0.2 + version: 3.0.2 + '@types/resemblejs': + specifier: ^4.1.3 + version: 4.1.3 + '@types/resize-observer-browser': + specifier: ^0.1.11 + version: 0.1.11 + '@webcomponents/shadycss': + specifier: ^1.11.2 + version: 1.11.2 + highlight.js: + specifier: ^11.11.1 + version: 11.11.1 + highlightjs-closure-templates: + specifier: https://github.com/highlightjs/highlightjs-closure-templates#02fb0646e0499084f96a99b8c6f4a0d7bd1d33ba + version: https://codeload.github.com/highlightjs/highlightjs-closure-templates/tar.gz/02fb0646e0499084f96a99b8c6f4a0d7bd1d33ba + highlightjs-epp: + specifier: https://github.com/highlightjs/highlightjs-epp#9f9e1a92f37c217c68899c7d3bdccb4d134681b9 + version: https://codeload.github.com/highlightjs/highlightjs-epp/tar.gz/9f9e1a92f37c217c68899c7d3bdccb4d134681b9 + highlightjs-structured-text: + specifier: https://github.com/highlightjs/highlightjs-structured-text#e68dd7aa829529fb6c40d6287585f43273605a9e + version: https://codeload.github.com/highlightjs/highlightjs-structured-text/tar.gz/e68dd7aa829529fb6c40d6287585f43273605a9e + highlightjs-ttcn3: + specifier: https://gitea.osmocom.org/ttcn3/highlightjs-ttcn3.git#6daccff309fca1e7561a43984d42fa4f829ce06d + version: git+https://gitea.osmocom.org/ttcn3/highlightjs-ttcn3.git#6daccff309fca1e7561a43984d42fa4f829ce06d + highlightjs-vue: + specifier: https://github.com/paladox/highlightjs-vue#44eed074ea0110d1ad03d2cbd77d27027cf7bb04 + version: https://codeload.github.com/paladox/highlightjs-vue/tar.gz/44eed074ea0110d1ad03d2cbd77d27027cf7bb04 + immer: + specifier: ^9.0.21 + version: 9.0.21 + lit: + specifier: ^3.3.1 + version: 3.3.1 + marked: + specifier: ^18.0.3 + version: 18.0.3 + resemblejs: + specifier: rsmbl/Resemble.js#66a55c5bfc3bda2303ad632ee8ce3c727b415917 + version: https://codeload.github.com/rsmbl/Resemble.js/tar.gz/66a55c5bfc3bda2303ad632ee8ce3c727b415917 + rxjs: + specifier: ^6.6.7 + version: 6.6.7 + safevalues: + specifier: ^1.2.0 + version: 1.2.0 + web-vitals: + specifier: ^5.1.0 + version: 5.1.0 + +packages: + + '@lit-labs/ssr-dom-shim@1.4.0': + resolution: {integrity: sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==} + + '@lit/reactive-element@2.1.1': + resolution: {integrity: sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==} + + '@material/web@2.4.1': + resolution: {integrity: sha512-0sk9t25acJ72Qv3r0n9r0lgDbPaAKnpm0p+QmEAAwYyZomHxuVbgrrAdtNXaRm7jFyGh+WsTr8bhtvCnpPRFjw==} + + '@polymer/font-roboto-local@3.0.2': + resolution: {integrity: sha512-mCd9TcjwnCxU+7uVHCkbREGU+OmzStvYh3ru5DSaftOQDnMrLAzernEv/QCcfSPRgTMHij+pIUN4tcaGeDGcYg==} + + '@types/resemblejs@4.1.3': + resolution: {integrity: sha512-p0NA5aACdWCK+I4NJbwUvFoixwYxvfLu+UqaiZt/J3+3PJavMYOxRrdbeXbbiKiMGdKdDFjoxlFWkkaMU7SDxA==} + + '@types/resize-observer-browser@0.1.11': + resolution: {integrity: sha512-cNw5iH8JkMkb3QkCoe7DaZiawbDQEUX8t7iuQaRTyLOyQCR2h+ibBD4GJt7p5yhUHrlOeL7ZtbxNHeipqNsBzQ==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@webcomponents/shadycss@1.11.2': + resolution: {integrity: sha512-vRq+GniJAYSBmTRnhCYPAPq6THYqovJ/gzGThWbgEZUQaBccndGTi1hdiUP15HzEco0I6t4RCtXyX0rsSmwgPw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + canvas@3.2.0: + resolution: {integrity: sha512-jk0GxrLtUEmW/TmFsk2WghvgHe8B0pxGilqCL21y8lHkPUGa6FTsnCNtHPOzT8O3y+N+m3espawV80bbBlgfTA==} + engines: {node: ^18.12.0 || >= 20.9.0} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + + highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} + engines: {node: '>=12.0.0'} + + highlightjs-closure-templates@https://codeload.github.com/highlightjs/highlightjs-closure-templates/tar.gz/02fb0646e0499084f96a99b8c6f4a0d7bd1d33ba: + resolution: {tarball: https://codeload.github.com/highlightjs/highlightjs-closure-templates/tar.gz/02fb0646e0499084f96a99b8c6f4a0d7bd1d33ba} + version: 0.0.1 + + highlightjs-epp@https://codeload.github.com/highlightjs/highlightjs-epp/tar.gz/9f9e1a92f37c217c68899c7d3bdccb4d134681b9: + resolution: {tarball: https://codeload.github.com/highlightjs/highlightjs-epp/tar.gz/9f9e1a92f37c217c68899c7d3bdccb4d134681b9} + version: 0.0.1 + + highlightjs-structured-text@https://codeload.github.com/highlightjs/highlightjs-structured-text/tar.gz/e68dd7aa829529fb6c40d6287585f43273605a9e: + resolution: {tarball: https://codeload.github.com/highlightjs/highlightjs-structured-text/tar.gz/e68dd7aa829529fb6c40d6287585f43273605a9e} + version: 1.4.9 + + highlightjs-ttcn3@git+https://gitea.osmocom.org/ttcn3/highlightjs-ttcn3.git#6daccff309fca1e7561a43984d42fa4f829ce06d: + resolution: {commit: 6daccff309fca1e7561a43984d42fa4f829ce06d, repo: https://gitea.osmocom.org/ttcn3/highlightjs-ttcn3.git, type: git} + version: 0.0.1 + + highlightjs-vue@https://codeload.github.com/paladox/highlightjs-vue/tar.gz/44eed074ea0110d1ad03d2cbd77d27027cf7bb04: + resolution: {tarball: https://codeload.github.com/paladox/highlightjs-vue/tar.gz/44eed074ea0110d1ad03d2cbd77d27027cf7bb04} + version: 1.1.0 + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + immer@9.0.21: + resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + lit-element@4.2.1: + resolution: {integrity: sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==} + + lit-html@3.3.1: + resolution: {integrity: sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA==} + + lit@3.3.1: + resolution: {integrity: sha512-Ksr/8L3PTapbdXJCk+EJVB78jDodUMaP54gD24W186zGRARvwrsPfS60wae/SSCTCNZVPd1chXqio1qHQmu4NA==} + + marked@18.0.3: + resolution: {integrity: sha512-7VT90JOkDeaRWpfjOReRGPEKn0ecdARBkDGL+tT1wZY0efPPqkUxLUSmzy/C7TIylQYJC9STISEsCHrqb/7VIA==} + engines: {node: '>= 20'} + hasBin: true + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + + node-abi@3.85.0: + resolution: {integrity: sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==} + engines: {node: '>=10'} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. + hasBin: true + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + resemblejs@https://codeload.github.com/rsmbl/Resemble.js/tar.gz/66a55c5bfc3bda2303ad632ee8ce3c727b415917: + resolution: {tarball: https://codeload.github.com/rsmbl/Resemble.js/tar.gz/66a55c5bfc3bda2303ad632ee8ce3c727b415917} + version: 5.0.0 + + rxjs@6.6.7: + resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} + engines: {npm: '>=2.0.0'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safevalues@1.2.0: + resolution: {integrity: sha512-zIsuhjYvJCjfsfjoim2ab6gLKFYAnTiDSJGh0cC3T44L/4kNLL90hBG2BzrXPrHA3f8Ms8FSJ1mljKH5dVR1cw==} + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + web-vitals@5.1.0: + resolution: {integrity: sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg==} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + +snapshots: + + '@lit-labs/ssr-dom-shim@1.4.0': {} + + '@lit/reactive-element@2.1.1': + dependencies: + '@lit-labs/ssr-dom-shim': 1.4.0 + + '@material/web@2.4.1': + dependencies: + lit: 3.3.1 + tslib: 2.8.1 + + '@polymer/font-roboto-local@3.0.2': {} + + '@types/resemblejs@4.1.3': {} + + '@types/resize-observer-browser@0.1.11': {} + + '@types/trusted-types@2.0.7': {} + + '@webcomponents/shadycss@1.11.2': {} + + base64-js@1.5.1: + optional: true + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + optional: true + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + optional: true + + canvas@3.2.0: + dependencies: + node-addon-api: 7.1.1 + prebuild-install: 7.1.3 + optional: true + + chownr@1.1.4: + optional: true + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + optional: true + + deep-extend@0.6.0: + optional: true + + detect-libc@2.1.2: + optional: true + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + optional: true + + expand-template@2.0.3: + optional: true + + fs-constants@1.0.0: + optional: true + + github-from-package@0.0.0: + optional: true + + highlight.js@10.7.3: {} + + highlight.js@11.11.1: {} + + highlightjs-closure-templates@https://codeload.github.com/highlightjs/highlightjs-closure-templates/tar.gz/02fb0646e0499084f96a99b8c6f4a0d7bd1d33ba: + dependencies: + highlight.js: 11.11.1 + + highlightjs-epp@https://codeload.github.com/highlightjs/highlightjs-epp/tar.gz/9f9e1a92f37c217c68899c7d3bdccb4d134681b9: + dependencies: + highlight.js: 11.11.1 + + highlightjs-structured-text@https://codeload.github.com/highlightjs/highlightjs-structured-text/tar.gz/e68dd7aa829529fb6c40d6287585f43273605a9e: + dependencies: + highlight.js: 10.7.3 + + highlightjs-ttcn3@git+https://gitea.osmocom.org/ttcn3/highlightjs-ttcn3.git#6daccff309fca1e7561a43984d42fa4f829ce06d: + dependencies: + highlight.js: 11.11.1 + + highlightjs-vue@https://codeload.github.com/paladox/highlightjs-vue/tar.gz/44eed074ea0110d1ad03d2cbd77d27027cf7bb04: {} + + ieee754@1.2.1: + optional: true + + immer@9.0.21: {} + + inherits@2.0.4: + optional: true + + ini@1.3.8: + optional: true + + lit-element@4.2.1: + dependencies: + '@lit-labs/ssr-dom-shim': 1.4.0 + '@lit/reactive-element': 2.1.1 + lit-html: 3.3.1 + + lit-html@3.3.1: + dependencies: + '@types/trusted-types': 2.0.7 + + lit@3.3.1: + dependencies: + '@lit/reactive-element': 2.1.1 + lit-element: 4.2.1 + lit-html: 3.3.1 + + marked@18.0.3: {} + + mimic-response@3.1.0: + optional: true + + minimist@1.2.8: + optional: true + + mkdirp-classic@0.5.3: + optional: true + + napi-build-utils@2.0.0: + optional: true + + node-abi@3.85.0: + dependencies: + semver: 7.7.3 + optional: true + + node-addon-api@7.1.1: + optional: true + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + optional: true + + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.85.0 + pump: 3.0.3 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + optional: true + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + optional: true + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + optional: true + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + optional: true + + resemblejs@https://codeload.github.com/rsmbl/Resemble.js/tar.gz/66a55c5bfc3bda2303ad632ee8ce3c727b415917: + optionalDependencies: + canvas: 3.2.0 + + rxjs@6.6.7: + dependencies: + tslib: 1.14.1 + + safe-buffer@5.2.1: + optional: true + + safevalues@1.2.0: {} + + semver@7.7.3: + optional: true + + simple-concat@1.0.1: + optional: true + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + optional: true + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + optional: true + + strip-json-comments@2.0.1: + optional: true + + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.3 + tar-stream: 2.2.0 + optional: true + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + optional: true + + tslib@1.14.1: {} + + tslib@2.8.1: {} + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + optional: true + + util-deprecate@1.0.2: + optional: true + + web-vitals@5.1.0: {} + + wrappy@1.0.2: + optional: true
diff --git a/polygerrit-ui/app/pnpm-workspace.yaml b/polygerrit-ui/app/pnpm-workspace.yaml new file mode 100644 index 0000000..962cec9 --- /dev/null +++ b/polygerrit-ui/app/pnpm-workspace.yaml
@@ -0,0 +1,4 @@ +packages: + - 'polygerrit-ui/app' + +onlyBuiltDependencies: []
diff --git a/polygerrit-ui/app/rollup.config.js b/polygerrit-ui/app/rollup.config.js index ab43ef0..237df57 100644 --- a/polygerrit-ui/app/rollup.config.js +++ b/polygerrit-ui/app/rollup.config.js
@@ -17,14 +17,14 @@ // so require(plugin_name) can't find a plugin. // To fix it, requirePlugin tries: // 1. resolve module id using default behavior, i.e. it starts from __dirname -// 2. if module not found - it tries to resolve module starting from rollupBin -// location. +// 2. if module not found - it tries to resolve module starting from +// tools/node_tools/node_modules // This workaround also gives us additional power - we can place .config.js // file anywhere in a source tree and add all plugins in the same package.json // file as rollup node module. function requirePlugin(id) { - const rollupBinDir = path.dirname(process.argv[1]); - const pluginPath = require.resolve(id, {paths: [__dirname, rollupBinDir]}); + const nodeToolsModulesDir = path.join(__dirname, '../../tools/node_tools/node_modules'); + const pluginPath = require.resolve(id, {paths: [__dirname, nodeToolsModulesDir] }); return require(pluginPath); }
diff --git a/polygerrit-ui/app/rules.bzl b/polygerrit-ui/app/rules.bzl index 593c898..5f815d7 100644 --- a/polygerrit-ui/app/rules.bzl +++ b/polygerrit-ui/app/rules.bzl
@@ -1,5 +1,7 @@ -load("@npm//@bazel/rollup:index.bzl", "rollup_bundle") -load("//tools/bzl:genrule2.bzl", "genrule2") +"""Build rules for polygerrit.""" + +load("@aspect_rules_rollup//rollup:defs.bzl", "rollup") +load("@com_googlesource_gerrit_bazlets//tools:genrule2.bzl", "genrule2") def polygerrit_bundle(name, srcs, outs, entry_point, app_name): """Build .zip bundle from source code @@ -16,58 +18,61 @@ native.filegroup( name = app_name + "-full-src", srcs = srcs + [ - "@ui_npm//:node_modules", + "//polygerrit-ui/app:node_modules", ], ) - rollup_bundle( + rollup( name = app_name + "-bundle-js", srcs = [app_name + "-full-src"], + node_modules = "//tools/node_tools:node_modules", args = [ "--bundleConfigAsCjs=true", ], config_file = ":rollup.config.js", entry_point = entry_point, - rollup_bin = "//tools/node_tools:rollup-bin", silent = True, sourcemap = "hidden", deps = [ - "@tools_npm//@rollup/plugin-replace", - "@tools_npm//@rollup/plugin-node-resolve", + "//tools/node_tools:node_modules/@rollup/plugin-replace", + "//tools/node_tools:node_modules/@rollup/plugin-node-resolve", + "//tools/node_tools:node_modules/@rollup/plugin-terser", ], ) - rollup_bundle( + rollup( name = "syntax-worker", srcs = [app_name + "-full-src"], + node_modules = "//tools/node_tools:node_modules", args = [ "--bundleConfigAsCjs=true", ], config_file = ":rollup.config.js", entry_point = "_pg_ts_out/workers/syntax-worker.js", - rollup_bin = "//tools/node_tools:rollup-bin", silent = True, sourcemap = "hidden", deps = [ - "@tools_npm//@rollup/plugin-replace", - "@tools_npm//@rollup/plugin-node-resolve", + "//tools/node_tools:node_modules/@rollup/plugin-replace", + "//tools/node_tools:node_modules/@rollup/plugin-node-resolve", + "//tools/node_tools:node_modules/@rollup/plugin-terser", ], ) - rollup_bundle( + rollup( name = "service-worker", srcs = [app_name + "-full-src"], + node_modules = "//tools/node_tools:node_modules", args = [ "--bundleConfigAsCjs=true", ], config_file = ":rollup.config.js", entry_point = "_pg_ts_out/workers/service-worker.js", - rollup_bin = "//tools/node_tools:rollup-bin", silent = True, sourcemap = "hidden", deps = [ - "@tools_npm//@rollup/plugin-replace", - "@tools_npm//@rollup/plugin-node-resolve", + "//tools/node_tools:node_modules/@rollup/plugin-replace", + "//tools/node_tools:node_modules/@rollup/plugin-node-resolve", + "//tools/node_tools:node_modules/@rollup/plugin-terser", ], ) @@ -111,17 +116,14 @@ "//lib/fonts:robotofonts", "//lib/js:highlightjs__files", "//lib/js:emojis__files", - "@ui_npm//@webcomponents/webcomponentsjs", - "@ui_npm//:node_modules/@webcomponents/webcomponentsjs/package.json", - "@ui_npm//:node_modules/resemblejs/resemble.js", - "@ui_npm//@polymer/font-roboto-local", - "@ui_npm//:node_modules/@polymer/font-roboto-local/package.json", + "//polygerrit-ui/app:node_modules/resemblejs/dir", + "//polygerrit-ui/app:node_modules/@polymer/font-roboto-local/dir", ], outs = outs, cmd = " && ".join([ - "FONT_DIR=$$(dirname $(location @ui_npm//:node_modules/@polymer/font-roboto-local/package.json))/fonts", - "WEBCOMPONENTJS_DIR=$$(dirname $(location @ui_npm//:node_modules/@webcomponents/webcomponentsjs/package.json))", - "mkdir -p $$TMP/polygerrit_ui/{workers,styles/themes,fonts/{roboto,robotomono},bower_components/{emojis,highlightjs,webcomponentsjs,webcomponentsjs/bundles,resemblejs},elements}", + "FONT_DIR=$(location //polygerrit-ui/app:node_modules/@polymer/font-roboto-local/dir)/fonts", + "RESEMBLEJS_DIR=$(location //polygerrit-ui/app:node_modules/resemblejs/dir)", + "mkdir -p $$TMP/polygerrit_ui/{workers,styles/themes,fonts/{roboto,robotomono},bower_components/{emojis,highlightjs,resemblejs},elements}", "for f in $(locations " + name + "_app_sources); do ext=$${f##*.}; cp -p $$f $$TMP/polygerrit_ui/elements/" + app_name + ".$$ext; done", "cp $(locations //lib/fonts:robotofonts) $$TMP/polygerrit_ui/fonts/", "cp $(locations //lib/fonts:material-icons) $$TMP/polygerrit_ui/fonts/", @@ -130,9 +132,7 @@ "for f in $(locations " + name + "_worker_sources); do cp $$f $$TMP/polygerrit_ui/workers; done", "for f in $(locations //lib/js:highlightjs__files); do cp $$f $$TMP/polygerrit_ui/bower_components/highlightjs/ ; done", "for f in $(locations //lib/js:emojis__files); do cp $$f $$TMP/polygerrit_ui/bower_components/emojis/ ; done", - "cp $$WEBCOMPONENTJS_DIR/webcomponents-loader.js $$TMP/polygerrit_ui/bower_components/webcomponentsjs/webcomponents-loader.js", - "cp $$WEBCOMPONENTJS_DIR/bundles/* $$TMP/polygerrit_ui/bower_components/webcomponentsjs/bundles/", - "cp $(location @ui_npm//:node_modules/resemblejs/resemble.js) $$TMP/polygerrit_ui/bower_components/resemblejs/resemble.js", + "cp $$RESEMBLEJS_DIR/resemble.js $$TMP/polygerrit_ui/bower_components/resemblejs/resemble.js", "cp $$FONT_DIR/roboto/*.ttf $$TMP/polygerrit_ui/fonts/roboto/", "cp $$FONT_DIR/robotomono/*.ttf $$TMP/polygerrit_ui/fonts/robotomono/", "cd $$TMP",
diff --git a/polygerrit-ui/app/run_template_test.sh b/polygerrit-ui/app/run_template_test.sh deleted file mode 100755 index d2b6989..0000000 --- a/polygerrit-ui/app/run_template_test.sh +++ /dev/null
@@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -if [[ -z "${TEMPLATE_NO_DEFAULT}" ]]; then -bazel test \ - --test_env="HOME=$HOME" \ - //polygerrit-ui/app:all \ - --test_tag_filters=template \ - "$@" \ - --test_output errors \ - --nocache_test_results -else -bazel test \ - --test_env="HOME=$HOME" \ - "$@" \ - --test_output errors \ - --nocache_test_results -fi
diff --git a/polygerrit-ui/app/scripts/bundled-polymer.ts b/polygerrit-ui/app/scripts/bundled-polymer.ts deleted file mode 100644 index ad183c1..0000000 --- a/polygerrit-ui/app/scripts/bundled-polymer.ts +++ /dev/null
@@ -1,17 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -// This file is a replacement for the -// polymer-bridges/polymer/polymer.html file. The polymer.html file loads -// other scripts to setup different global variables. Because plugins -// expects that Polymer is available we must setup all Polymer global -// variables -// -// The bundled-polymer.js imports all scripts in the same order as the -// polymer.html does and must be imported in all es6-modules instead -// of the polymer.html file. - -import './js/bundled-polymer-bridges';
diff --git a/polygerrit-ui/app/scripts/js/bundled-polymer-bridges.d.ts b/polygerrit-ui/app/scripts/js/bundled-polymer-bridges.d.ts deleted file mode 100644 index 01ecf50..0000000 --- a/polygerrit-ui/app/scripts/js/bundled-polymer-bridges.d.ts +++ /dev/null
@@ -1,8 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -// We can't convert bundled-polymer.js to ts. To allow import -// bundled-polymer.js from .ts files we should add this .d.ts file
diff --git a/polygerrit-ui/app/scripts/js/bundled-polymer-bridges.js b/polygerrit-ui/app/scripts/js/bundled-polymer-bridges.js deleted file mode 100644 index 494acd9..0000000 --- a/polygerrit-ui/app/scripts/js/bundled-polymer-bridges.js +++ /dev/null
@@ -1,60 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -// This file can't be converted to TS - it imports some .js file which -// can't be imported into typescript - -// This file is a replacement for the -// polymer-bridges/polymer/polymer.html file. The polymer.html file loads -// other scripts to setup different global variables. Because plugins -// expects that Polymer is available we must setup all Polymer global -// variables -// -// The bundled-polymer.js imports all scripts in the same order as the -// polymer.html does and must be imported in all es6-modules instead -// of the polymer.html file. - -import 'polymer-bridges/polymer/lib/utils/boot_bridge.js'; -import 'polymer-bridges/polymer/lib/utils/resolve-url_bridge.js'; -import 'polymer-bridges/polymer/lib/utils/settings_bridge.js'; -import 'polymer-bridges/polymer/lib/utils/mixin_bridge.js'; -import 'polymer-bridges/polymer/lib/elements/dom-module_bridge.js'; -import 'polymer-bridges/polymer/lib/utils/style-gather_bridge.js'; -import 'polymer-bridges/polymer/lib/utils/path_bridge.js'; -import 'polymer-bridges/polymer/lib/utils/case-map_bridge.js'; -import 'polymer-bridges/polymer/lib/utils/async_bridge.js'; -import 'polymer-bridges/polymer/lib/utils/wrap_bridge.js'; -import 'polymer-bridges/polymer/lib/mixins/properties-changed_bridge.js'; -import 'polymer-bridges/polymer/lib/mixins/property-accessors_bridge.js'; -import 'polymer-bridges/polymer/lib/mixins/template-stamp_bridge.js'; -import 'polymer-bridges/polymer/lib/mixins/property-effects_bridge.js'; -import 'polymer-bridges/polymer/lib/utils/telemetry_bridge.js'; -import 'polymer-bridges/polymer/lib/mixins/properties-mixin_bridge.js'; -import 'polymer-bridges/polymer/lib/utils/debounce_bridge.js'; -import 'polymer-bridges/polymer/lib/utils/gestures_bridge.js'; -import 'polymer-bridges/polymer/lib/mixins/gesture-event-listeners_bridge.js'; -import 'polymer-bridges/polymer/lib/mixins/dir-mixin_bridge.js'; -import 'polymer-bridges/polymer/lib/utils/render-status_bridge.js'; -import 'polymer-bridges/polymer/lib/utils/unresolved_bridge.js'; -import 'polymer-bridges/polymer/lib/utils/array-splice_bridge.js'; -import 'polymer-bridges/polymer/lib/utils/flattened-nodes-observer_bridge.js'; -import 'polymer-bridges/polymer/lib/utils/flush_bridge.js'; -import 'polymer-bridges/polymer/lib/legacy/polymer.dom_bridge.js'; -import 'polymer-bridges/polymer/lib/legacy/legacy-element-mixin_bridge.js'; -import 'polymer-bridges/polymer/lib/legacy/class_bridge.js'; -import 'polymer-bridges/polymer/lib/legacy/polymer-fn_bridge.js'; -import 'polymer-bridges/polymer/lib/mixins/mutable-data_bridge.js'; -import 'polymer-bridges/polymer/lib/utils/templatize_bridge.js'; -import 'polymer-bridges/polymer/lib/legacy/templatizer-behavior_bridge.js'; -import 'polymer-bridges/polymer/lib/elements/dom-bind_bridge.js'; -import 'polymer-bridges/polymer/lib/utils/html-tag_bridge.js'; -import 'polymer-bridges/polymer/polymer-element_bridge.js'; -import 'polymer-bridges/polymer/lib/elements/dom-repeat_bridge.js'; -import 'polymer-bridges/polymer/lib/elements/dom-if_bridge.js'; -import 'polymer-bridges/polymer/lib/elements/array-selector_bridge.js'; -import 'polymer-bridges/polymer/lib/elements/custom-style_bridge.js'; -import 'polymer-bridges/polymer/lib/legacy/mutable-data-behavior_bridge.js'; -import 'polymer-bridges/polymer/polymer-legacy_bridge.js';
diff --git a/polygerrit-ui/app/scripts/polymer-resin-install.ts b/polygerrit-ui/app/scripts/polymer-resin-install.ts deleted file mode 100644 index 584d83a..0000000 --- a/polygerrit-ui/app/scripts/polymer-resin-install.ts +++ /dev/null
@@ -1,65 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ -import 'polymer-resin/standalone/polymer-resin'; - -export type SafeTypeBridge = ( - value: unknown, - type: string, - fallback: unknown -) => unknown; - -export type ReportHandler = ( - isDisallowedValue: boolean, - printfFormatString: string, - ...printfArgs: unknown[] -) => void; - -declare global { - interface Window { - security: { - polymer_resin: { - SafeType: { - CONSTANT: string; - HTML: string; - JAVASCRIPT: string; - RESOURCE_URL: string; - /** Unprivileged but possibly wrapped string. */ - STRING: string; - STYLE: string; - URL: string; - }; - CONSOLE_LOGGING_REPORT_HANDLER: ReportHandler; - install(options: { - UNSAFE_passThruDisallowedValues?: boolean; - allowedIdentifierPrefixes?: string[]; - reportHandler?: ReportHandler; - safeTypesBridge?: SafeTypeBridge; - }): void; - }; - }; - } -} - -const security = window.security; - -export const _testOnly_defaultResinReportHandler = - security.polymer_resin.CONSOLE_LOGGING_REPORT_HANDLER; - -let resinInstalled = false; -export function installPolymerResin( - safeTypesBridge: SafeTypeBridge, - reportHandler = security.polymer_resin.CONSOLE_LOGGING_REPORT_HANDLER -) { - if (resinInstalled) { - return; - } - window.security.polymer_resin.install({ - allowedIdentifierPrefixes: [''], - reportHandler, - safeTypesBridge, - }); - resinInstalled = true; -}
diff --git a/polygerrit-ui/app/services/flags/flags.ts b/polygerrit-ui/app/services/flags/flags.ts index e36b94f..8fb85b5 100644 --- a/polygerrit-ui/app/services/flags/flags.ts +++ b/polygerrit-ui/app/services/flags/flags.ts
@@ -22,4 +22,5 @@ ML_SUGGESTED_EDIT_FEEDBACK = 'UiFeature__ml_suggested_edit_feedback', ML_SUGGESTED_EDIT_EDITABLE_SUGGESTION = 'UiFeature__ml_suggested_edit_editable_suggestion', ML_SUGGESTED_EDIT_GET_FIX = 'UiFeature__ml_suggested_edit_get_fix', + DASHBOARD_LAZY_LOADING = 'UiFeature__dashboard_lazy_loading', }
diff --git a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts index 2188e70..92a9c02 100644 --- a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts +++ b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
@@ -1335,7 +1335,8 @@ if (!changeNum) return; const optionsHex = listChangesOptionsToHex( ListChangesOption.ALL_REVISIONS, - ListChangesOption.ALL_FILES + ListChangesOption.ALL_FILES, + ListChangesOption.SKIP_DIFFSTAT ); const change = await this.getChange(changeNum, undefined, optionsHex); @@ -2211,7 +2212,8 @@ const options = listChangesOptionsToHex( ListChangesOption.CURRENT_REVISION, ListChangesOption.CURRENT_COMMIT, - ListChangesOption.SUBMITTABLE + ListChangesOption.SUBMITTABLE, + ListChangesOption.SKIP_DIFFSTAT ); const params = { O: options, @@ -2231,7 +2233,8 @@ ): Promise<ChangeInfo[] | undefined> { const options = listChangesOptionsToHex( ListChangesOption.CURRENT_REVISION, - ListChangesOption.CURRENT_COMMIT + ListChangesOption.CURRENT_COMMIT, + ListChangesOption.SKIP_DIFFSTAT ); const query = [ `project:${repo}`, @@ -2262,7 +2265,8 @@ ListChangesOption.CURRENT_REVISION, ListChangesOption.CURRENT_COMMIT, ListChangesOption.DETAILED_LABELS, - ListChangesOption.SUBMITTABLE + ListChangesOption.SUBMITTABLE, + ListChangesOption.SKIP_DIFFSTAT ); const queryTerms = [`topic:${escapeAndWrapSearchOperatorValue(topic)}`]; if (options?.openChangesOnly) { @@ -2368,6 +2372,7 @@ }), url, errFn, + isHighPriority: true, }) ) as unknown as Promise<ReviewResult | undefined>; }
diff --git a/polygerrit-ui/app/services/scheduler/fake-scheduler.ts b/polygerrit-ui/app/services/scheduler/fake-scheduler.ts index d4df3ce..c9038c9 100644 --- a/polygerrit-ui/app/services/scheduler/fake-scheduler.ts +++ b/polygerrit-ui/app/services/scheduler/fake-scheduler.ts
@@ -9,7 +9,15 @@ export class FakeScheduler<T> implements Scheduler<T> { readonly scheduled: Array<FakeTask> = []; - schedule(task: Task<T>) { + get activeCount() { + return this.scheduled.length; + } + + get activeRequests() { + return []; + } + + schedule(task: Task<T>, _name?: string) { return new Promise<T>((resolve, reject) => { this.scheduled.push(async (error?: unknown) => { if (error) {
diff --git a/polygerrit-ui/app/services/scheduler/max-in-flight-scheduler.ts b/polygerrit-ui/app/services/scheduler/max-in-flight-scheduler.ts index 1febcb6..65e9e13 100644 --- a/polygerrit-ui/app/services/scheduler/max-in-flight-scheduler.ts +++ b/polygerrit-ui/app/services/scheduler/max-in-flight-scheduler.ts
@@ -8,22 +8,36 @@ export class MaxInFlightScheduler<T> implements Scheduler<T> { private inflight = 0; - private waiting: Array<Task<void>> = []; + private waiting: Array<{task: Task<void>; name?: string}> = []; + + private readonly running: string[] = []; + + get activeCount(): number { + return this.running.length + this.waiting.length; + } + + get activeRequests(): string[] { + const waitingNames = this.waiting.map(w => w.name || 'unknown'); + return [...this.running, ...waitingNames]; + } constructor( private readonly base: Scheduler<T>, private maxInflight: number = 10 ) {} - async schedule(task: Task<T>): Promise<T> { + async schedule(task: Task<T>, name?: string): Promise<T> { return new Promise<T>((resolve, reject) => { - this.waiting.push(async () => { - try { - const result = await this.base.schedule(task); - resolve(result); - } catch (e: unknown) { - reject(e); - } + this.waiting.push({ + task: async () => { + try { + const result = await this.base.schedule(task, name); + resolve(result); + } catch (e: unknown) { + reject(e); + } + }, + name, }); this.next(); }); @@ -32,10 +46,14 @@ private next() { if (this.inflight >= this.maxInflight) return; if (this.waiting.length === 0) return; - const task = this.waiting.shift() as Task<void>; + const {task, name} = this.waiting.shift()!; ++this.inflight; + const taskName = name || 'unknown'; + this.running.push(taskName); task().finally(() => { --this.inflight; + const index = this.running.indexOf(taskName); + if (index > -1) this.running.splice(index, 1); this.next(); }); }
diff --git a/polygerrit-ui/app/services/scheduler/retry-scheduler.ts b/polygerrit-ui/app/services/scheduler/retry-scheduler.ts index d96fc0b..67842a0 100644 --- a/polygerrit-ui/app/services/scheduler/retry-scheduler.ts +++ b/polygerrit-ui/app/services/scheduler/retry-scheduler.ts
@@ -33,15 +33,26 @@ private backoffFactor: number = 1.618 ) {} - async schedule(task: Task<T>): Promise<T> { + get activeCount(): number { + return this.base.activeCount; + } + + get activeRequests(): string[] { + return this.base.activeRequests; + } + + async schedule(task: Task<T>, name?: string): Promise<T> { let tries = 0; let timeout = this.backoffIntervalMs; const worker: Task<T> = async () => { try { - return await this.base.schedule(task); + return await this.base.schedule(task, name); } catch (e: unknown) { if (e instanceof RetryError && tries++ < this.maxRetry) { + console.info( + `[RetryScheduler] Retrying task, try ${tries} after ${timeout}ms` + ); await untilTimeout(timeout); timeout = timeout * this.backoffFactor; return await worker();
diff --git a/polygerrit-ui/app/services/scheduler/scheduler.ts b/polygerrit-ui/app/services/scheduler/scheduler.ts index b834ab3..da0c3e4 100644 --- a/polygerrit-ui/app/services/scheduler/scheduler.ts +++ b/polygerrit-ui/app/services/scheduler/scheduler.ts
@@ -5,10 +5,20 @@ */ export type Task<T> = () => Promise<T>; export interface Scheduler<T> { - schedule(task: Task<T>): Promise<T>; + schedule(task: Task<T>, name?: string): Promise<T>; + get activeCount(): number; + get activeRequests(): string[]; } export class BaseScheduler<T> implements Scheduler<T> { - schedule(task: Task<T>) { + schedule(task: Task<T>, _name?: string) { return task(); } + + get activeCount() { + return 0; + } + + get activeRequests() { + return []; + } }
diff --git a/polygerrit-ui/app/styles/gr-change-view-integration-shared-styles.ts b/polygerrit-ui/app/styles/gr-change-view-integration-shared-styles.ts index 67ee146..72351d7 100644 --- a/polygerrit-ui/app/styles/gr-change-view-integration-shared-styles.ts +++ b/polygerrit-ui/app/styles/gr-change-view-integration-shared-styles.ts
@@ -4,58 +4,40 @@ * SPDX-License-Identifier: Apache-2.0 */ -// Mark the file as a module. Otherwise typescript assumes this is a script -// and $_documentContainer is a global variable. -// See: https://www.typescriptlang.org/docs/handbook/modules.html -export {}; +import {css} from 'lit'; -const $_documentContainer = document.createElement('template'); - -/* - These are shared styles for change-view-integration endpoints. - All plugins that registered that endpoint should include this in - the component to have a consistent UX: - - <style include="gr-change-view-integration-shared-styles"></style> - - And use those defined class to apply these styles. -*/ -$_documentContainer.innerHTML = `<dom-module id="gr-change-view-integration-shared-styles"> - <template> - <style include="shared-styles"> - /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */ - </style> - <style> - :host { - border-top: 1px solid var(--border-color); - display: block; - } - .header { - color: var(--primary-text-color); - background-color: var(--table-header-background-color); - justify-content: space-between; - padding: var(--spacing-m) var(--spacing-l); - border-bottom: 1px solid var(--border-color); - } - .header .label { - font-family: var(--header-font-family); - font-size: var(--font-size-h3); - font-weight: var(--font-weight-h3); - line-height: var(--line-height-h3); - margin: 0 var(--spacing-l) 0 0; - } - .header .note { - color: var(--deemphasized-text-color); - } - .content { - background-color: var(--view-background-color); - } - .header a, - .content a { - color: var(--link-color); - } - </style> - </template> -</dom-module>`; - -document.head.appendChild($_documentContainer.content); +/** + * Shared styles for change-view integration. + * This provides the core styling and overrides used by external + * plugins or components that integrate closely with the Change View. + */ +export const changeViewIntegrationStyles = css` + :host { + border-top: 1px solid var(--border-color); + display: block; + } + .header { + color: var(--primary-text-color); + background-color: var(--table-header-background-color); + justify-content: space-between; + padding: var(--spacing-m) var(--spacing-l); + border-bottom: 1px solid var(--border-color); + } + .header .label { + font-family: var(--header-font-family); + font-size: var(--font-size-h3); + font-weight: var(--font-weight-h3); + line-height: var(--line-height-h3); + margin: 0 var(--spacing-l) 0 0; + } + .header .note { + color: var(--deemphasized-text-color); + } + .content { + background-color: var(--view-background-color); + } + .header a, + .content a { + color: var(--link-color); + } +`;
diff --git a/polygerrit-ui/app/test/common-test-setup.ts b/polygerrit-ui/app/test/common-test-setup.ts index dc77649..9846589 100644 --- a/polygerrit-ui/app/test/common-test-setup.ts +++ b/polygerrit-ui/app/test/common-test-setup.ts
@@ -3,9 +3,6 @@ * Copyright 2017 Google LLC * SPDX-License-Identifier: Apache-2.0 */ -// TODO(dmfilippov): remove bundled-polymer.js imports when the following issue -// https://github.com/Polymer/polymer-resin/issues/9 is resolved. -import '../scripts/bundled-polymer'; import {getAppContext} from '../services/app-context'; import { createTestAppContext, @@ -17,16 +14,11 @@ getCleanupsCount, removeThemeStyles, } from './test-utils'; -import {safeTypesBridge} from '../utils/safe-types-util'; import { initGerrit, initGlobalVariables, } from '../elements/gr-app-global-var-init'; import {assert, fixtureCleanup} from '@open-wc/testing'; -import { - _testOnly_defaultResinReportHandler, - installPolymerResin, -} from '../scripts/polymer-resin-install'; import {_testOnly_allTasks} from '../utils/async-util'; import {cleanUpStorage} from '../services/storage/gr-storage_mock'; import { @@ -54,19 +46,6 @@ window.litIssuedWarnings = window.litIssuedWarnings || new Set(); window.litIssuedWarnings.add('dev-mode'); -installPolymerResin(safeTypesBridge, (isViolation, fmt, ...args) => { - // Suppress 'initResin' log message from polymer-resin. - if (fmt === 'initResin') { - return; - } - const log = _testOnly_defaultResinReportHandler; - log(isViolation, fmt, ...args); - if (isViolation) { - // This will cause the test to fail if there is a data binding violation. - throw new Error('polymer-resin violation: ' + fmt + JSON.stringify(args)); - } -}); - let testSetupTimestampMs = 0; let currentTestName = ''; @@ -103,7 +82,8 @@ evt.callback(() => testResolver(evt.dependency)); } -setup(function () { +setup(async function () { + await document.fonts?.ready; testSetupTimestampMs = new Date().getTime(); currentTestName = this.currentTest?.title || 'unknown test';
diff --git a/polygerrit-ui/app/test/test-data-generators.ts b/polygerrit-ui/app/test/test-data-generators.ts index a2d8694..a22d0a9 100644 --- a/polygerrit-ui/app/test/test-data-generators.ts +++ b/polygerrit-ui/app/test/test-data-generators.ts
@@ -1840,6 +1840,29 @@ attemptDetails: [], }; +export const checkRun6: CheckRun = { + pluginName: 'f6', + internalRunId: 'f6', + checkName: 'FAKE Run with no link URL', + status: RunStatus.SCHEDULED, + isSingleAttempt: true, + isLatestAttempt: true, + attemptDetails: [], + results: [ + { + internalResultId: 'f0r0', + category: Category.ERROR, + summary: 'I would like to point out this error: 1 is not equal to 2!', + links: [ + {primary: true, url: '', icon: LinkIcon.EXTERNAL}, + {primary: false, url: ' ', icon: LinkIcon.EXTERNAL}, + {primary: true, url: 'https://google.com', icon: LinkIcon.DOWNLOAD}, + ], + tags: [{name: 'OBSOLETE'}, {name: 'E2E'}], + }, + ], +}; + export function setAllcheckRuns(model: ChecksModel) { model.updateStateSetProvider('f0', ChecksPatchset.LATEST); model.updateStateSetProvider('f1', ChecksPatchset.LATEST);
diff --git a/polygerrit-ui/app/test/test-utils.ts b/polygerrit-ui/app/test/test-utils.ts index a70d02b..4ada9b8 100644 --- a/polygerrit-ui/app/test/test-utils.ts +++ b/polygerrit-ui/app/test/test-utils.ts
@@ -329,7 +329,10 @@ ) { applyDarkTheme(); document.documentElement.classList.add('darkTheme'); - await visualDiff(element, `${name}-dark`); - removeDarkTheme(); - document.documentElement.classList.remove('darkTheme'); + try { + await visualDiff(element, `${name}-dark`); + } finally { + removeDarkTheme(); + document.documentElement.classList.remove('darkTheme'); + } }
diff --git a/polygerrit-ui/app/tsconfig.json b/polygerrit-ui/app/tsconfig.json index eab8759..d4290aa 100644 --- a/polygerrit-ui/app/tsconfig.json +++ b/polygerrit-ui/app/tsconfig.json
@@ -34,7 +34,7 @@ /* Module Resolution Options */ "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ - "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + "preserveSymlinks": false, /* Do not resolve the real path of symlinks. */ /* Advanced Options */ "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
diff --git a/polygerrit-ui/app/tsconfig_bazel_test.json b/polygerrit-ui/app/tsconfig_bazel_test.json index c6a940b..7d74ffa 100644 --- a/polygerrit-ui/app/tsconfig_bazel_test.json +++ b/polygerrit-ui/app/tsconfig_bazel_test.json
@@ -2,14 +2,12 @@ "extends": "./tsconfig_bazel.json", "compilerOptions": { "typeRoots": [ - "../../external/ui_npm/node_modules/@types", - "../../external/ui_dev_npm/node_modules/@types" - ] + "node_modules/@types", + "../node_modules/@types" + ], + "types": ["mocha"] }, "include": [ - // Items below must be in sync with the src_dirs list in the BUILD file - // Also items must be in sync with tsconfig.json, tsconfig_test.json - // (include and exclude arrays are overridden when extends) "api/**/*", "constants/**/*", "elements/**/*",
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts index 435be97..a6a8e1b 100644 --- a/polygerrit-ui/app/types/common.ts +++ b/polygerrit-ui/app/types/common.ts
@@ -125,7 +125,7 @@ VotingRangeInfo, WebLinkInfo, } from '../api/rest-api'; -import {DiffInfo, IgnoreWhitespaceType} from './diff'; +import {DiffInfo, DiffResponsiveMode, IgnoreWhitespaceType} from './diff'; import {LineNumber, PatchRange} from '../api/diff'; export type { @@ -163,6 +163,7 @@ ContextLine, ContributorAgreementInfo, DetailedLabelInfo, + DiffResponsiveMode, DownloadInfo, DownloadSchemeInfo, EditPatchSet, @@ -947,6 +948,7 @@ message?: string; tag?: string; unresolved?: boolean; + is_ai?: boolean; fix_suggestions?: FixSuggestionInfo[]; } @@ -1004,6 +1006,7 @@ tab_size?: number; font_size?: number; line_wrapping?: boolean; + responsive_mode?: DiffResponsiveMode; indent_with_tabs?: boolean; } @@ -1077,6 +1080,7 @@ allow_autocompleting_comments?: boolean; diff_page_sidebar?: DiffPageSidebar; ai_chat_selected_model?: string; + label_filter?: string; } /**
diff --git a/polygerrit-ui/app/types/events.ts b/polygerrit-ui/app/types/events.ts index 8dde4f4..52a4794 100644 --- a/polygerrit-ui/app/types/events.ts +++ b/polygerrit-ui/app/types/events.ts
@@ -43,6 +43,7 @@ 'confirm': CustomEvent<{}>; // prettier-ignore 'drop': DropEvent; + 'explain-code-requested': ExplainCodeRequestedEvent; 'hide-alert': CustomEvent<{}>; 'location-change': LocationChangeEvent; 'iron-announce': IronAnnounceEvent; @@ -262,3 +263,9 @@ export type TitleChangeEvent = CustomEvent<TitleChangeEventDetail>; export type ValueChangedEvent<T = string> = CustomEvent<{value: T}>; + +export interface ExplainCodeRequestedEventDetail { + prompt?: string; +} +export type ExplainCodeRequestedEvent = + CustomEvent<ExplainCodeRequestedEventDetail>;
diff --git a/polygerrit-ui/app/types/globals.ts b/polygerrit-ui/app/types/globals.ts index 37f31b1..8c6ed4e 100644 --- a/polygerrit-ui/app/types/globals.ts +++ b/polygerrit-ui/app/types/globals.ts
@@ -5,6 +5,7 @@ */ import {ParsedJSON} from './common'; import {HighlightJS} from './types'; +import {css, html, LitElement} from 'lit'; export {}; @@ -12,6 +13,9 @@ interface Window { CANONICAL_PATH?: string; INITIAL_DATA?: {[key: string]: ParsedJSON}; + LitElement?: typeof LitElement; + html?: typeof html; + css?: typeof css; HTMLImports?: {whenReady: (cb: () => void) => void}; linkify( text: string,
diff --git a/polygerrit-ui/app/utils/comment-util.ts b/polygerrit-ui/app/utils/comment-util.ts index 4b0c1ab..2419783 100644 --- a/polygerrit-ui/app/utils/comment-util.ts +++ b/polygerrit-ui/app/utils/comment-util.ts
@@ -96,15 +96,6 @@ const path1 = c1.path ?? ''; const path2 = c2.path ?? ''; if (path1 !== path2) { - // TODO: Why is this logic not part of specialFilePathCompare()? - // '/PATCHSET' will not come before '/COMMIT' when sorting - // alphabetically so move it to the front explicitly - if (path1 === SpecialFilePath.PATCHSET_LEVEL_COMMENTS) { - return -1; - } - if (path2 === SpecialFilePath.PATCHSET_LEVEL_COMMENTS) { - return 1; - } return specialFilePathCompare(path1, path2); }
diff --git a/polygerrit-ui/app/utils/label-util.ts b/polygerrit-ui/app/utils/label-util.ts index ee7aa3a..7347402 100644 --- a/polygerrit-ui/app/utils/label-util.ts +++ b/polygerrit-ui/app/utils/label-util.ts
@@ -342,6 +342,8 @@ return {icon: 'error', filled: true}; case SubmitRequirementStatus.FORCED: return {icon: 'check_circle', filled: true}; + case SubmitRequirementStatus.TIMEOUT: + return {icon: 'block'}; default: assertNever(status, `Unsupported status: ${status}`); }
diff --git a/polygerrit-ui/app/utils/link-util.ts b/polygerrit-ui/app/utils/link-util.ts index 2f12bd7..86f9f55 100644 --- a/polygerrit-ui/app/utils/link-util.ts +++ b/polygerrit-ui/app/utils/link-util.ts
@@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ import {CommentLinkInfo, CommentLinks} from '../types/common'; -import {getBaseUrl} from './url-util'; +import {getBaseUrl, sameOrigin} from './url-util'; /** * Finds links within the base string and convert them to HTML. Config-based @@ -115,15 +115,23 @@ const replacementHref = rewrite.link.startsWith('/') ? `${getBaseUrl()}${rewrite.link}` : rewrite.link; - const regexp = new RegExp(rewrite.match, 'g'); - return matchedText.replace( - regexp, - createLinkTemplate( - replacementHref, - rewrite.text ?? '$&', - rewrite.prefix, - rewrite.suffix - ) + // Use regexp without 'g' flag for replacement to avoid lastIndex issues + // when running replace multiple times. + const regexp = new RegExp(rewrite.match); + const resolvedHref = matchedText.replace(regexp, replacementHref); + const resolvedText = matchedText.replace(regexp, rewrite.text ?? '$&'); + const resolvedPrefix = rewrite.prefix + ? matchedText.replace(regexp, rewrite.prefix) + : undefined; + const resolvedSuffix = rewrite.suffix + ? matchedText.replace(regexp, rewrite.suffix) + : undefined; + + return createLinkTemplate( + resolvedHref, + resolvedText, + resolvedPrefix, + resolvedSuffix ); } @@ -133,9 +141,10 @@ prefix?: string, suffix?: string ) { - return `${ - prefix ?? '' - }<a href="${href}" rel="noopener noreferrer" target="_blank">${displayText}</a>${ + const attributes = sameOrigin(href) + ? '' + : ' rel="noopener noreferrer" target="_blank"'; + return `${prefix ?? ''}<a href="${href}"${attributes}>${displayText}</a>${ suffix ?? '' }`; }
diff --git a/polygerrit-ui/app/utils/link-util_test.ts b/polygerrit-ui/app/utils/link-util_test.ts index 7b09320..085d204 100644 --- a/polygerrit-ui/app/utils/link-util_test.ts +++ b/polygerrit-ui/app/utils/link-util_test.ts
@@ -12,16 +12,20 @@ return `<a href="${href}" rel="noopener noreferrer" target="_blank">${text}</a>`; } + function internalLink(text: string, href: string) { + return `<a href="${href}">${text}</a>`; + } + suite('link rewrites', () => { test('without text', () => { assert.equal( linkifyUrlsAndApplyRewrite('foo', { fooLinkWithoutText: { match: 'foo', - link: 'foo.gov', + link: 'http://foo.gov', }, }), - link('foo', 'foo.gov') + link('foo', 'http://foo.gov') ); }); @@ -30,11 +34,11 @@ linkifyUrlsAndApplyRewrite('foo', { fooLinkWithText: { match: 'foo', - link: 'foo.gov', + link: 'http://foo.gov', text: 'foo site', }, }), - link('foo site', 'foo.gov') + link('foo site', 'http://foo.gov') ); }); @@ -43,13 +47,13 @@ linkifyUrlsAndApplyRewrite('there are 12 foos here', { fooLinkWithText: { match: '(.*)(bug|foo)s(.*)', - link: '$2.gov', + link: 'http://$2.gov', text: '$2 list', prefix: '$1on the ', suffix: '$3', }, }), - `there are 12 on the ${link('foo list', 'foo.gov')} here` + `there are 12 on the ${link('foo list', 'http://foo.gov')} here` ); }); @@ -58,10 +62,10 @@ linkifyUrlsAndApplyRewrite('foo foo', { foo: { match: 'foo', - link: 'foo.gov', + link: 'http://foo.gov', }, }), - `${link('foo', 'foo.gov')} ${link('foo', 'foo.gov')}` + `${link('foo', 'http://foo.gov')} ${link('foo', 'http://foo.gov')}` ); }); }); @@ -71,18 +75,18 @@ linkifyUrlsAndApplyRewrite('foobarbaz', { foo: { match: 'foo', - link: 'foo.gov', + link: 'http://foo.gov', }, foobarbaz: { match: 'foobarbaz', - link: 'foobarbaz.gov', + link: 'http://foobarbaz.gov', }, foobar: { match: 'foobar', - link: 'foobar.gov', + link: 'http://foobar.gov', }, }), - link('foobarbaz', 'foobarbaz.gov') + link('foobarbaz', 'http://foobarbaz.gov') ); }); @@ -91,18 +95,18 @@ linkifyUrlsAndApplyRewrite('foobarbaz', { foo: { match: 'baz', - link: 'Baz.gov', + link: 'http://Baz.gov', }, foobarbaz: { match: 'foobarbaz', - link: 'FooBarBaz.gov', + link: 'http://FooBarBaz.gov', }, foobar: { match: 'barbaz', - link: 'BarBaz.gov', + link: 'http://BarBaz.gov', }, }), - link('foobarbaz', 'FooBarBaz.gov') + link('foobarbaz', 'http://FooBarBaz.gov') ); }); @@ -111,18 +115,18 @@ linkifyUrlsAndApplyRewrite('foobarbaz', { foo: { match: 'foo', - link: 'FOO', + link: 'http://FOO', }, oobarba: { match: 'oobarba', - link: 'OOBARBA', + link: 'http://OOBARBA', }, baz: { match: 'baz', - link: 'BAZ', + link: 'http://BAZ', }, }), - `${link('foo', 'FOO')}bar${link('baz', 'BAZ')}` + `${link('foo', 'http://FOO')}bar${link('baz', 'http://BAZ')}` ); }); @@ -148,10 +152,10 @@ text: 'bug/$4', }, }), - `bugs: ${link('bug/123', 'bug/123')} ${link('bug/234', 'bug/234')} ${link( - 'bug/345', - 'bug/345' - )}` + `bugs: ${internalLink('bug/123', 'bug/123')} ${internalLink( + 'bug/234', + 'bug/234' + )} ${internalLink('bug/345', 'bug/345')}` ); }); });
diff --git a/polygerrit-ui/app/utils/path-list-util.ts b/polygerrit-ui/app/utils/path-list-util.ts index 5c523b3..31e17c9 100644 --- a/polygerrit-ui/app/utils/path-list-util.ts +++ b/polygerrit-ui/app/utils/path-list-util.ts
@@ -8,6 +8,14 @@ import {hasOwnProperty} from './common-util'; export function specialFilePathCompare(a: string, b: string) { + // Patchset-level comments always go first. + if (a === SpecialFilePath.PATCHSET_LEVEL_COMMENTS) { + return -1; + } + if (b === SpecialFilePath.PATCHSET_LEVEL_COMMENTS) { + return 1; + } + // The commit message always goes first. if (a === SpecialFilePath.COMMIT_MESSAGE) { return -1;
diff --git a/polygerrit-ui/app/utils/path-list-util_test.ts b/polygerrit-ui/app/utils/path-list-util_test.ts index cdd8182..a7c1e53 100644 --- a/polygerrit-ui/app/utils/path-list-util_test.ts +++ b/polygerrit-ui/app/utils/path-list-util_test.ts
@@ -21,12 +21,14 @@ const testFiles = [ '/a.h', '/MERGE_LIST', + SpecialFilePath.PATCHSET_LEVEL_COMMENTS, '/a.cpp', '/COMMIT_MSG', '/asdasd', '/mrPeanutbutter.py', ]; assert.deepEqual(testFiles.sort(specialFilePathCompare), [ + SpecialFilePath.PATCHSET_LEVEL_COMMENTS, '/COMMIT_MSG', '/MERGE_LIST', '/a.h',
diff --git a/polygerrit-ui/app/utils/safe-types-util.ts b/polygerrit-ui/app/utils/safe-types-util.ts deleted file mode 100644 index e5c33bb..0000000 --- a/polygerrit-ui/app/utils/safe-types-util.ts +++ /dev/null
@@ -1,57 +0,0 @@ -/** - * @license - * Copyright 2018 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -const SAFE_URL_PATTERN = /^(https?:\/\/|mailto:|[^:/?#]*(?:[/?#]|$))/i; - -/** - * Wraps a string to be used as a URL. An error is thrown if the string cannot - * be considered safe. - */ -class SafeUrl { - private readonly _url: string; - - constructor(url: string) { - if (!SAFE_URL_PATTERN.test(url)) { - throw new Error(`URL not marked as safe: ${url}`); - } - this._url = url; - } - - toString() { - return this._url; - } -} - -export const _testOnly_SafeUrl = SafeUrl; - -/** - * Get the string representation of the safe URL. - */ -export function safeTypesBridge(value: unknown, type: string): unknown { - // If the value is being bound to a URL, ensure the value is wrapped in the - // SafeUrl type first. If the URL is not safe, allow the SafeUrl constructor - // to surface the error. - if (type === 'URL') { - let safeValue = null; - if (value instanceof SafeUrl) { - safeValue = value; - } else if (typeof value === 'string') { - safeValue = new SafeUrl(value); - } - if (safeValue) { - return safeValue.toString(); - } - } - - // If the value is being bound to a string or a constant, then the string - // can be used as is. - if (type === 'STRING' || type === 'CONSTANT') { - return value; - } - - // Otherwise fail. - throw new Error(`Refused to bind value as ${type}: ${value}`); -}
diff --git a/polygerrit-ui/app/utils/safe-types-util_test.ts b/polygerrit-ui/app/utils/safe-types-util_test.ts deleted file mode 100644 index 2c8bb70..0000000 --- a/polygerrit-ui/app/utils/safe-types-util_test.ts +++ /dev/null
@@ -1,79 +0,0 @@ -/** - * @license - * Copyright 2018 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ -import {assert} from '@open-wc/testing'; -import '../test/common-test-setup'; -import {_testOnly_SafeUrl, safeTypesBridge} from './safe-types-util'; - -suite('safe-types-util tests', () => { - test('SafeUrl accepts valid urls', () => { - function accepts(url: string) { - const safeUrl = new _testOnly_SafeUrl(url); - assert.isOk(safeUrl); - assert.equal(url, safeUrl.toString()); - } - accepts('http://www.google.com/'); - accepts('https://www.google.com/'); - accepts('HtTpS://www.google.com/'); - accepts('//www.google.com/'); - accepts('/c/1234/file/path.html@45'); - accepts('#hash-url'); - accepts('mailto:name@example.com'); - }); - - test('SafeUrl rejects invalid urls', () => { - function rejects(url: string) { - assert.throws(() => { - new _testOnly_SafeUrl(url); - }); - } - rejects('javascript://alert("evil");'); - rejects('ftp:example.com'); - rejects('data:text/html,scary business'); - }); - - suite('safeTypesBridge', () => { - function acceptsString(value: string, type: string) { - assert.equal(safeTypesBridge(value, type), value); - } - - function rejects(value: unknown, type: string) { - assert.throws(() => { - safeTypesBridge(value, type); - }); - } - - test('accepts valid URL strings', () => { - acceptsString('/foo/bar', 'URL'); - acceptsString('#baz', 'URL'); - }); - - test('rejects invalid URL strings', () => { - rejects('javascript://void();', 'URL'); - }); - - test('accepts SafeUrl values', () => { - const url = '/abc/123'; - const safeUrl = new _testOnly_SafeUrl(url); - assert.equal(safeTypesBridge(safeUrl, 'URL'), url); - }); - - test('rejects non-string or non-SafeUrl types', () => { - rejects(3.1415926, 'URL'); - }); - - test('accepts any binding to STRING or CONSTANT', () => { - acceptsString('foo/bar/baz', 'STRING'); - acceptsString('lorem ipsum dolor', 'CONSTANT'); - }); - - test('rejects all other types', () => { - rejects('foo', 'JAVASCRIPT'); - rejects('foo', 'HTML'); - rejects('foo', 'RESOURCE_URL'); - rejects('foo', 'STYLE'); - }); - }); -});
diff --git a/polygerrit-ui/app/yarn.lock b/polygerrit-ui/app/yarn.lock index c67ebb4..5ee0d56 100644 --- a/polygerrit-ui/app/yarn.lock +++ b/polygerrit-ui/app/yarn.lock
@@ -22,25 +22,11 @@ lit "^2.8.0 || ^3.0.0" tslib "^2.4.0" -"@polymer/decorators@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@polymer/decorators/-/decorators-3.0.0.tgz#e4212ac976d9abd1210f560b6e1be4165c1c0183" - integrity sha512-qh+VID9nDV9q3ABvIfWgm7/+udl7v2HKsMLPXFm8tj1fI7qr7yWJMFwS3xWBkMmuNPtmkS8MDP0vqLAQIEOWzg== - dependencies: - "@polymer/polymer" "^3.0.5" - "@polymer/font-roboto-local@^3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@polymer/font-roboto-local/-/font-roboto-local-3.0.2.tgz#563cd6cabbcaef54999d654c0f3d476bcc49ce58" integrity sha512-mCd9TcjwnCxU+7uVHCkbREGU+OmzStvYh3ru5DSaftOQDnMrLAzernEv/QCcfSPRgTMHij+pIUN4tcaGeDGcYg== -"@polymer/polymer@3.5.2", "@polymer/polymer@^3.0.2", "@polymer/polymer@^3.0.5": - version "3.5.2" - resolved "https://registry.yarnpkg.com/@polymer/polymer/-/polymer-3.5.2.tgz#af0e7e13976df53ace6728e841121c36aca351de" - integrity sha512-fWwImY/UH4bb2534DVSaX+Azs2yKg8slkMBHOyGeU2kKx7Xmxp6Lee0jP8p6B3d7c1gFUPB2Z976dTUtX81pQA== - dependencies: - "@webcomponents/shadycss" "^1.9.1" - "@types/resemblejs@^4.1.3": version "4.1.3" resolved "https://registry.yarnpkg.com/@types/resemblejs/-/resemblejs-4.1.3.tgz#46d16888952e377b9143484c206b63f6da56e91e" @@ -56,16 +42,11 @@ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== -"@webcomponents/shadycss@^1.11.2", "@webcomponents/shadycss@^1.9.1": +"@webcomponents/shadycss@^1.11.2": version "1.11.2" resolved "https://registry.yarnpkg.com/@webcomponents/shadycss/-/shadycss-1.11.2.tgz#7539b0ad29598aa2eafee8b341059e20ac9e1006" integrity sha512-vRq+GniJAYSBmTRnhCYPAPq6THYqovJ/gzGThWbgEZUQaBccndGTi1hdiUP15HzEco0I6t4RCtXyX0rsSmwgPw== -"@webcomponents/webcomponentsjs@^2.0.3", "@webcomponents/webcomponentsjs@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.8.0.tgz#ab21f027594fa827c1889e8b646da7be27c7908a" - integrity sha512-loGD63sacRzOzSJgQnB9ZAhaQGkN7wl2Zuw7tsphI5Isa0irijrRo6EnJii/GgjGefIFO8AIO7UivzRhFaEk9w== - base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -223,10 +204,10 @@ lit-element "^4.2.0" lit-html "^3.3.0" -marked@^17.0.1: - version "17.0.1" - resolved "https://registry.yarnpkg.com/marked/-/marked-17.0.1.tgz#9db34197ac145e5929572ee49ef701e37ee9b2e6" - integrity sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg== +marked@^18.0.3: + version "18.0.3" + resolved "https://registry.yarnpkg.com/marked/-/marked-18.0.3.tgz#278b5ba89f1c7ccbaf0422f3ee8955928489220b" + integrity sha512-7VT90JOkDeaRWpfjOReRGPEKn0ecdARBkDGL+tT1wZY0efPPqkUxLUSmzy/C7TIylQYJC9STISEsCHrqb/7VIA== mimic-response@^3.1.0: version "3.1.0" @@ -267,17 +248,6 @@ dependencies: wrappy "1" -"polymer-bridges@file:../../polymer-bridges": - version "1.0.0" - -polymer-resin@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/polymer-resin/-/polymer-resin-2.0.1.tgz#08abcf0ea47bac746450cefa96caab1ebea199a8" - integrity sha512-Vak/4JiuToOKzvvjzcpISoFAlI7AwbOk79bZhqWNAZcqdzqEeFwos5hytjLV90d00TzVbff1LREaFpWR35zOjg== - dependencies: - "@polymer/polymer" "^3.0.2" - "@webcomponents/webcomponentsjs" "^2.0.3" - prebuild-install@^7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec"
diff --git a/polygerrit-ui/package.json b/polygerrit-ui/package.json index 1707aa3..208cc89 100644 --- a/polygerrit-ui/package.json +++ b/polygerrit-ui/package.json
@@ -6,15 +6,22 @@ "@types/sinon": "^17.0.4" }, "devDependencies": { - "@types/mocha": "^10.0.10", "@open-wc/semantic-dom-diff": "^0.20.1", "@open-wc/testing": "^4.0.0", + "@open-wc/testing-helpers": "3.0.1", + "@types/mocha": "^10.0.10", "@web/dev-server-esbuild": "^1.0.4", "@web/test-runner": "^0.20.2", + "@web/test-runner-commands": "0.9.0", "@web/test-runner-playwright": "^0.11.1", "@web/test-runner-visual-regression": "^0.10.0", "accessibility-developer-tools": "^2.12.0", + "lit": "3.3.2", + "lit-element": "4.2.2", + "lit-html": "3.3.2", "mocha": "^10.2.0", + "pixelmatch": "^7.1.0", + "pngjs": "^7.0.0", "sinon": "^20.0.0", "source-map-support": "^0.5.21" }, @@ -32,4 +39,4 @@ }, "license": "Apache-2.0", "private": true -} \ No newline at end of file +}
diff --git a/polygerrit-ui/pnpm-lock.yaml b/polygerrit-ui/pnpm-lock.yaml new file mode 100644 index 0000000..ac1685a --- /dev/null +++ b/polygerrit-ui/pnpm-lock.yaml
@@ -0,0 +1,3886 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@types/sinon': + specifier: ^17.0.4 + version: 17.0.4 + devDependencies: + '@open-wc/semantic-dom-diff': + specifier: ^0.20.1 + version: 0.20.1 + '@open-wc/testing': + specifier: ^4.0.0 + version: 4.0.0 + '@open-wc/testing-helpers': + specifier: 3.0.1 + version: 3.0.1 + '@types/mocha': + specifier: ^10.0.10 + version: 10.0.10 + '@web/dev-server-esbuild': + specifier: ^1.0.4 + version: 1.0.4 + '@web/test-runner': + specifier: ^0.20.2 + version: 0.20.2 + '@web/test-runner-commands': + specifier: 0.9.0 + version: 0.9.0 + '@web/test-runner-playwright': + specifier: ^0.11.1 + version: 0.11.1 + '@web/test-runner-visual-regression': + specifier: ^0.10.0 + version: 0.10.0 + accessibility-developer-tools: + specifier: ^2.12.0 + version: 2.12.0 + lit: + specifier: 3.3.2 + version: 3.3.2 + lit-element: + specifier: 4.2.2 + version: 4.2.2 + lit-html: + specifier: 3.3.2 + version: 3.3.2 + mocha: + specifier: ^10.2.0 + version: 10.8.2 + pixelmatch: + specifier: ^7.1.0 + version: 7.2.0 + pngjs: + specifier: ^7.0.0 + version: 7.0.0 + sinon: + specifier: ^20.0.0 + version: 20.0.0 + source-map-support: + specifier: ^0.5.21 + version: 0.5.21 + +packages: + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.25.9': + resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.9': + resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.9': + resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.9': + resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.9': + resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.9': + resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.9': + resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.9': + resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.9': + resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.9': + resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.9': + resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.9': + resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.9': + resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.9': + resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.9': + resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.9': + resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.9': + resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.9': + resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.9': + resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.9': + resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.9': + resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.9': + resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.9': + resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.9': + resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.9': + resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.9': + resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esm-bundle/chai@4.3.4-fix.0': + resolution: {integrity: sha512-26SKdM4uvDWlY8/OOOxSB1AqQWeBosCX3wRYUZO7enTAj03CtVxIiCimYVG2WpULcyV51qapK4qTovwkUr5Mlw==} + + '@hapi/bourne@3.0.0': + resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.30': + resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + + '@lit-labs/ssr-dom-shim@1.4.0': + resolution: {integrity: sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==} + + '@lit-labs/ssr-dom-shim@1.6.0': + resolution: {integrity: sha512-VHb0ALPMTlgKjM6yIxxoQNnpKyUKLD04VzeQdsiXkMqkvYlAHxq9glGLmgbb889/1GsohSOAjvQYoiBppXFqrQ==} + + '@lit/reactive-element@2.1.1': + resolution: {integrity: sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==} + + '@mdn/browser-compat-data@4.2.1': + resolution: {integrity: sha512-EWUguj2kd7ldmrF9F+vI5hUOralPd+sdsUnYbRy33vZTuZkduC1shE9TtEMEjAQwyfyMb4ole5KtjF8MsnQOlA==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@open-wc/dedupe-mixin@2.0.1': + resolution: {integrity: sha512-+R4VxvceUxHAUJXJQipkkoV9fy10vNo+OnUnGKZnVmcwxMl460KLzytnUM4S35SI073R0yZQp9ra0MbPUwVcEA==} + + '@open-wc/scoped-elements@3.0.6': + resolution: {integrity: sha512-w1ayJaUUmBw8tALtqQ6cBueld+op+bufujzbrOdH0uCTXnSQkONYZzOH+9jyQ8auVgKLqcxZ8oU6SzfqQhQkPg==} + + '@open-wc/semantic-dom-diff@0.20.1': + resolution: {integrity: sha512-mPF/RPT2TU7Dw41LEDdaeP6eyTOWBD4z0+AHP4/d0SbgcfJZVRymlIB6DQmtz0fd2CImIS9kszaMmwMt92HBPA==} + + '@open-wc/testing-helpers@3.0.1': + resolution: {integrity: sha512-hyNysSatbgT2FNxHJsS3rGKcLEo6+HwDFu1UQL6jcSQUabp/tj3PyX7UnXL3H5YGv0lJArdYLSnvjLnjn3O2fw==} + + '@open-wc/testing@4.0.0': + resolution: {integrity: sha512-KI70O0CJEpBWs3jrTju4BFCy7V/d4tFfYWkg8pMzncsDhD7TYNHLw5cy+s1FHXIgVFetnMDhPpwlKIPvtTQW7w==} + + '@puppeteer/browsers@2.10.7': + resolution: {integrity: sha512-wHWLkQWBjHtajZeqCB74nsa/X70KheyOhySYBRmVQDJiNj0zjZR/naPCvdWjMhcG1LmjaMV/9WtTo5mpe8qWLw==} + engines: {node: '>=18'} + hasBin: true + + '@rollup/plugin-node-resolve@15.3.1': + resolution: {integrity: sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@5.2.0': + resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.49.0': + resolution: {integrity: sha512-rlKIeL854Ed0e09QGYFlmDNbka6I3EQFw7iZuugQjMb11KMpJCLPFL4ZPbMfaEhLADEL1yx0oujGkBQ7+qW3eA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.49.0': + resolution: {integrity: sha512-cqPpZdKUSQYRtLLr6R4X3sD4jCBO1zUmeo3qrWBCqYIeH8Q3KRL4F3V7XJ2Rm8/RJOQBZuqzQGWPjjvFUcYa/w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.49.0': + resolution: {integrity: sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.49.0': + resolution: {integrity: sha512-y8cXoD3wdWUDpjOLMKLx6l+NFz3NlkWKcBCBfttUn+VGSfgsQ5o/yDUGtzE9HvsodkP0+16N0P4Ty1VuhtRUGg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.49.0': + resolution: {integrity: sha512-3mY5Pr7qv4GS4ZvWoSP8zha8YoiqrU+e0ViPvB549jvliBbdNLrg2ywPGkgLC3cmvN8ya3za+Q2xVyT6z+vZqA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.49.0': + resolution: {integrity: sha512-C9KzzOAQU5gU4kG8DTk+tjdKjpWhVWd5uVkinCwwFub2m7cDYLOdtXoMrExfeBmeRy9kBQMkiyJ+HULyF1yj9w==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.49.0': + resolution: {integrity: sha512-OVSQgEZDVLnTbMq5NBs6xkmz3AADByCWI4RdKSFNlDsYXdFtlxS59J+w+LippJe8KcmeSSM3ba+GlsM9+WwC1w==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.49.0': + resolution: {integrity: sha512-ZnfSFA7fDUHNa4P3VwAcfaBLakCbYaxCk0jUnS3dTou9P95kwoOLAMlT3WmEJDBCSrOEFFV0Y1HXiwfLYJuLlA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.49.0': + resolution: {integrity: sha512-Z81u+gfrobVK2iV7GqZCBfEB1y6+I61AH466lNK+xy1jfqFLiQ9Qv716WUM5fxFrYxwC7ziVdZRU9qvGHkYIJg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.49.0': + resolution: {integrity: sha512-zoAwS0KCXSnTp9NH/h9aamBAIve0DXeYpll85shf9NJ0URjSTzzS+Z9evmolN+ICfD3v8skKUPyk2PO0uGdFqg==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loongarch64-gnu@4.49.0': + resolution: {integrity: sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-gnu@4.49.0': + resolution: {integrity: sha512-k9aEmOWt+mrMuD3skjVJSSxHckJp+SiFzFG+v8JLXbc/xi9hv2icSkR3U7uQzqy+/QbbYY7iNB9eDTwrELo14g==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.49.0': + resolution: {integrity: sha512-rDKRFFIWJ/zJn6uk2IdYLc09Z7zkE5IFIOWqpuU0o6ZpHcdniAyWkwSUWE/Z25N/wNDmFHHMzin84qW7Wzkjsw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.49.0': + resolution: {integrity: sha512-FkkhIY/hYFVnOzz1WeV3S9Bd1h0hda/gRqvZCMpHWDHdiIHn6pqsY3b5eSbvGccWHMQ1uUzgZTKS4oGpykf8Tw==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.49.0': + resolution: {integrity: sha512-gRf5c+A7QiOG3UwLyOOtyJMD31JJhMjBvpfhAitPAoqZFcOeK3Kc1Veg1z/trmt+2P6F/biT02fU19GGTS529A==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.49.0': + resolution: {integrity: sha512-BR7+blScdLW1h/2hB/2oXM+dhTmpW3rQt1DeSiCP9mc2NMMkqVgjIN3DDsNpKmezffGC9R8XKVOLmBkRUcK/sA==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.49.0': + resolution: {integrity: sha512-hDMOAe+6nX3V5ei1I7Au3wcr9h3ktKzDvF2ne5ovX8RZiAHEtX1A5SNNk4zt1Qt77CmnbqT+upb/umzoPMWiPg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-win32-arm64-msvc@4.49.0': + resolution: {integrity: sha512-wkNRzfiIGaElC9kXUT+HLx17z7D0jl+9tGYRKwd8r7cUqTL7GYAvgUY++U2hK6Ar7z5Z6IRRoWC8kQxpmM7TDA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.49.0': + resolution: {integrity: sha512-gq5aW/SyNpjp71AAzroH37DtINDcX1Qw2iv9Chyz49ZgdOP3NV8QCyKZUrGsYX9Yyggj5soFiRCgsL3HwD8TdA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.49.0': + resolution: {integrity: sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==} + cpu: [x64] + os: [win32] + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@13.0.5': + resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==} + + '@sinonjs/samsam@8.0.3': + resolution: {integrity: sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==} + + '@tootallnate/quickjs-emscripten@0.23.0': + resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + + '@types/accepts@1.3.7': + resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==} + + '@types/babel__code-frame@7.0.6': + resolution: {integrity: sha512-Anitqkl3+KrzcW2k77lRlg/GfLZLWXBuNgbEcIOU6M92yw42vsd3xV/Z/yAHEj8m+KUjL6bWOVOFqX8PFPJ4LA==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/chai-dom@1.11.3': + resolution: {integrity: sha512-EUEZI7uID4ewzxnU7DJXtyvykhQuwe+etJ1wwOiJyQRTH/ifMWKX+ghiXkxCUvNJ6IQDodf0JXhuP6zZcy2qXQ==} + + '@types/chai@4.3.20': + resolution: {integrity: sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==} + + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + + '@types/co-body@6.1.3': + resolution: {integrity: sha512-UhuhrQ5hclX6UJctv5m4Rfp52AfG9o9+d9/HwjxhVB5NjXxr5t9oKgJxN8xRHgr35oo8meUEHUPFWiKg6y71aA==} + + '@types/command-line-args@5.2.3': + resolution: {integrity: sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/content-disposition@0.5.9': + resolution: {integrity: sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==} + + '@types/convert-source-map@2.0.3': + resolution: {integrity: sha512-ag0BfJLZf6CQz8VIuRIEYQ5Ggwk/82uvTQf27RcpyDNbY0Vw49LIPqAxk5tqYfrCs9xDaIMvl4aj7ZopnYL8bA==} + + '@types/cookies@0.9.1': + resolution: {integrity: sha512-E/DPgzifH4sM1UMadJMWd6mO2jOd4g1Ejwzx8/uRCDpJis1IrlyQEcGAYEomtAqRYmD5ORbNXMeI9U0RiVGZbg==} + + '@types/debounce@1.2.4': + resolution: {integrity: sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/express-serve-static-core@5.0.7': + resolution: {integrity: sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==} + + '@types/express@5.0.3': + resolution: {integrity: sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==} + + '@types/http-assert@1.5.6': + resolution: {integrity: sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/keygrip@1.0.6': + resolution: {integrity: sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==} + + '@types/koa-compose@3.2.8': + resolution: {integrity: sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==} + + '@types/koa@2.15.0': + resolution: {integrity: sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==} + + '@types/koa@3.0.0': + resolution: {integrity: sha512-MOcVYdVYmkSutVHZZPh8j3+dAjLyR5Tl59CN0eKgpkE1h/LBSmPAsQQuWs+bKu7WtGNn+hKfJH9Gzml+PulmDg==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/mkdirp@1.0.2': + resolution: {integrity: sha512-o0K1tSO0Dx5X6xlU5F1D6625FawhC3dU3iqr25lluNv/+/QIVH8RLNEiVokgIZo+mz+87w/3Mkg/VvQS+J51fQ==} + + '@types/mocha@10.0.10': + resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} + + '@types/node@24.3.0': + resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==} + + '@types/parse5@6.0.3': + resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} + + '@types/pixelmatch@5.2.6': + resolution: {integrity: sha512-wC83uexE5KGuUODn6zkm9gMzTwdY5L0chiK+VrKcDfEjzxh1uadlWTvOmAbCpnM9zx/Ww3f8uKlYQVnO/TrqVg==} + + '@types/pngjs@6.0.5': + resolution: {integrity: sha512-0k5eKfrA83JOZPppLtS2C7OUtyNAl2wKNxfyYl9Q5g9lPkgBl/9hNyAu6HuEH2J4XmIv2znEpkDd0SaZVxW6iQ==} + + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/resolve@1.20.2': + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + + '@types/send@0.17.5': + resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==} + + '@types/serve-static@1.15.8': + resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} + + '@types/sinon-chai@3.2.12': + resolution: {integrity: sha512-9y0Gflk3b0+NhQZ/oxGtaAJDvRywCa5sIyaVnounqLvmf93yBF4EgIRspePtkMs3Tr844nCclYMlcCNmLCvjuQ==} + + '@types/sinon@17.0.4': + resolution: {integrity: sha512-RHnIrhfPO3+tJT0s7cFaXGZvsL4bbR3/k7z3P312qMS4JaS2Tk+KiwiLx1S0rQ56ERj00u1/BtdyVd0FY+Pdew==} + + '@types/sinonjs__fake-timers@8.1.5': + resolution: {integrity: sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/ws@7.4.7': + resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} + + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + + '@web/browser-logs@0.4.1': + resolution: {integrity: sha512-ypmMG+72ERm+LvP+loj9A64MTXvWMXHUOu773cPO4L1SV/VWg6xA9Pv7vkvkXQX+ItJtCJt+KQ+U6ui2HhSFUw==} + engines: {node: '>=18.0.0'} + + '@web/config-loader@0.3.3': + resolution: {integrity: sha512-ilzeQzrPpPLWZhzFCV+4doxKDGm7oKVfdKpW9wiUNVgive34NSzCw+WzXTvjE4Jgr5CkyTDIObEmMrqQEjhT0g==} + engines: {node: '>=18.0.0'} + + '@web/dev-server-core@0.7.5': + resolution: {integrity: sha512-Da65zsiN6iZPMRuj4Oa6YPwvsmZmo5gtPWhW2lx3GTUf5CAEapjVpZVlUXnKPL7M7zRuk72jSsIl8lo+XpTCtw==} + engines: {node: '>=18.0.0'} + + '@web/dev-server-esbuild@1.0.4': + resolution: {integrity: sha512-ia1LxBwwRiQBYhJ7/RtLenHyPjzle3SvTw3jOZaeGv8UGXVPOkQV8fR05caOtW/DPPZaZovNAybzRKVnNiYIZg==} + engines: {node: '>=18.0.0'} + + '@web/dev-server-rollup@0.6.4': + resolution: {integrity: sha512-sJZfTGCCrdku5xYnQQG51odGI092hKY9YFM0X3Z0tRY3iXKXcYRaLZrErw5KfCxr6g0JRuhe4BBhqXTA5Q2I3Q==} + engines: {node: '>=18.0.0'} + + '@web/dev-server@0.4.6': + resolution: {integrity: sha512-jj/1bcElAy5EZet8m2CcUdzxT+CRvUjIXGh8Lt7vxtthkN9PzY9wlhWx/9WOs5iwlnG1oj0VGo6f/zvbPO0s9w==} + engines: {node: '>=18.0.0'} + hasBin: true + + '@web/parse5-utils@2.1.0': + resolution: {integrity: sha512-GzfK5disEJ6wEjoPwx8AVNwUe9gYIiwc+x//QYxYDAFKUp4Xb1OJAGLc2l2gVrSQmtPGLKrTRcW90Hv4pEq1qA==} + engines: {node: '>=18.0.0'} + + '@web/test-runner-chrome@0.18.1': + resolution: {integrity: sha512-eO6ctCaqSguGM6G3cFobGHnrEs9wlv9Juj/Akyr4XLjeEMTheNULdvOXw9Bygi+QC/ir/0snMmt+/YKnfy8rYA==} + engines: {node: '>=18.0.0'} + + '@web/test-runner-commands@0.9.0': + resolution: {integrity: sha512-zeLI6QdH0jzzJMDV5O42Pd8WLJtYqovgdt0JdytgHc0d1EpzXDsc7NTCJSImboc2NcayIsWAvvGGeRF69SMMYg==} + engines: {node: '>=18.0.0'} + + '@web/test-runner-core@0.13.4': + resolution: {integrity: sha512-84E1025aUSjvZU1j17eCTwV7m5Zg3cZHErV3+CaJM9JPCesZwLraIa0ONIQ9w4KLgcDgJFw9UnJ0LbFf42h6tg==} + engines: {node: '>=18.0.0'} + + '@web/test-runner-coverage-v8@0.8.0': + resolution: {integrity: sha512-PskiucYpjUtgNfR2zF2AWqWwjXL7H3WW/SnCAYmzUrtob7X9o/+BjdyZ4wKbOxWWSbJO4lEdGIDLu+8X2Xw+lA==} + engines: {node: '>=18.0.0'} + + '@web/test-runner-mocha@0.9.0': + resolution: {integrity: sha512-ZL9F6FXd0DBQvo/h/+mSfzFTSRVxzV9st/AHhpgABtUtV/AIpVE9to6+xdkpu6827kwjezdpuadPfg+PlrBWqQ==} + engines: {node: '>=18.0.0'} + + '@web/test-runner-playwright@0.11.1': + resolution: {integrity: sha512-l9tmX0LtBqMaKAApS4WshpB87A/M8sOHZyfCobSGuYqnREgz5rqQpX314yx+4fwHXLLTa5N64mTrawsYkLjliw==} + engines: {node: '>=18.0.0'} + + '@web/test-runner-visual-regression@0.10.0': + resolution: {integrity: sha512-fz+OPvPXpGlQdkyEKeIRr1t1/hIWb11JmmFUZWKgrifvheuLlLGko0VwOxIOQ7Ht11RcepsMXl4PzTuxkPv6rA==} + engines: {node: '>=18.0.0'} + + '@web/test-runner@0.20.2': + resolution: {integrity: sha512-zfEGYEDnS0EI8qgoWFjmtkIXhqP15W40NW3dCaKtbxj5eU0a7E53f3GV/tZGD0GlZKF8d4Fyw+AFrwOJU9Z4GA==} + engines: {node: '>=18.0.0'} + hasBin: true + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + accessibility-developer-tools@2.12.0: + resolution: {integrity: sha512-ltexLD/Bzwr1tDskQQFi88L4akbn8zFLIFIc00vFkH3G4hNEHruuJVcJuJTeUXLxms9dSon+cHSCmfFThnowFQ==} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-back@3.1.0: + resolution: {integrity: sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==} + engines: {node: '>=6'} + + array-back@6.2.2: + resolution: {integrity: sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==} + engines: {node: '>=12.17'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + ast-types@0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + axe-core@4.10.3: + resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==} + engines: {node: '>=4'} + + b4a@1.6.7: + resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + bare-events@2.6.1: + resolution: {integrity: sha512-AuTJkq9XmE6Vk0FJVNq5QxETrSA/vKHarWVBG5l/JbdCL1prJemiyJqUS0jrlXO0MftuPq4m3YVYhoNc5+aE/g==} + + bare-fs@4.2.1: + resolution: {integrity: sha512-mELROzV0IhqilFgsl1gyp48pnZsaV9xhQapHLDsvn4d4ZTfbFhcghQezl7FTEDNBcGqLUnNI3lUlm6ecrLWdFA==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-os@3.6.2: + resolution: {integrity: sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==} + engines: {bare: '>=1.14.0'} + + bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + + bare-stream@2.7.0: + resolution: {integrity: sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==} + peerDependencies: + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + bare-events: + optional: true + + basic-ftp@5.0.5: + resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} + engines: {node: '>=10.0.0'} + deprecated: Security vulnerability fixed in 5.2.1, please upgrade + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cache-content-type@1.0.1: + resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} + engines: {node: '>= 6.0.0'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + chai-a11y-axe@1.5.0: + resolution: {integrity: sha512-V/Vg/zJDr9aIkaHJ2KQu7lGTQQm5ZOH4u1k5iTMvIXuSVlSuUo0jcSpSqf9wUn9zl6oQXa4e4E0cqH18KOgKlQ==} + + chalk-template@0.4.0: + resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==} + engines: {node: '>=12'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chrome-launcher@0.15.2: + resolution: {integrity: sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==} + engines: {node: '>=12.13.0'} + hasBin: true + + chromium-bidi@8.0.0: + resolution: {integrity: sha512-d1VmE0FD7lxZQHzcDUCKZSNRtRwISXDsdg4HjdTR5+Ll5nQ/vzU12JeNmupD6VWffrPSlrnGhEWlLESKH3VO+g==} + peerDependencies: + devtools-protocol: '*' + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + + co-body@6.2.0: + resolution: {integrity: sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA==} + engines: {node: '>=8.0.0'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + command-line-args@5.2.1: + resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==} + engines: {node: '>=4.0.0'} + + command-line-usage@7.0.3: + resolution: {integrity: sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==} + engines: {node: '>=12.20.0'} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookies@0.9.1: + resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} + engines: {node: '>= 0.8'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + data-uri-to-buffer@6.0.2: + resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} + engines: {node: '>= 14'} + + debounce@1.2.1: + resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + + deep-equal@1.0.1: + resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + default-gateway@6.0.3: + resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==} + engines: {node: '>= 10'} + + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + + degenerator@5.0.1: + resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} + engines: {node: '>= 14'} + + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + + depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dependency-graph@0.11.0: + resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==} + engines: {node: '>= 0.6.0'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + devtools-protocol@0.0.1475386: + resolution: {integrity: sha512-RQ809ykTfJ+dgj9bftdeL2vRVxASAuGU+I9LEx9Ij5TXU5HrgAQVmzi72VA+mkzscE12uzlRv5/tWWv9R9J1SA==} + + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + + diff@7.0.0: + resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==} + engines: {node: '>=0.3.1'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + errorstacks@2.4.1: + resolution: {integrity: sha512-jE4i0SMYevwu/xxAuzhly/KTwtj0xDhbzB6m1xPImxTkw8wcCbgarOQPfCVMi5JKVyW7in29pNJCCJrry3Ynnw==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + esbuild@0.25.9: + resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-replace@3.0.0: + resolution: {integrity: sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==} + engines: {node: '>=4.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-uri@6.0.5: + resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} + engines: {node: '>= 14'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-assert@1.5.0: + resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==} + engines: {node: '>= 0.8'} + + http-errors@1.6.3: + resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} + engines: {node: '>= 0.6'} + + http-errors@1.8.1: + resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} + engines: {node: '>= 0.6'} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + inflation@2.1.0: + resolution: {integrity: sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==} + engines: {node: '>= 0.8.0'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.3: + resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + internal-ip@6.2.0: + resolution: {integrity: sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg==} + engines: {node: '>=10'} + + ip-address@10.0.1: + resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} + engines: {node: '>= 12'} + + ip-regex@4.3.0: + resolution: {integrity: sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==} + engines: {node: '>=8'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-ip@3.1.0: + resolution: {integrity: sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==} + engines: {node: '>=8'} + + is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isbinaryfile@5.0.5: + resolution: {integrity: sha512-vh9MWXjhhblwrHlt/yutrubDuBD01kKFscyVndE2/VEeEU5aAizrzuWAsEaCjo997k+IluhN6C4jgxfS69SCIw==} + engines: {node: '>= 18.0.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + keygrip@1.1.0: + resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} + engines: {node: '>= 0.6'} + + koa-compose@4.1.0: + resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} + + koa-convert@2.0.0: + resolution: {integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==} + engines: {node: '>= 10'} + + koa-etag@4.0.0: + resolution: {integrity: sha512-1cSdezCkBWlyuB9l6c/IFoe1ANCDdPBxkDkRiaIup40xpUub6U/wwRXoKBZw/O5BifX9OlqAjYnDyzM6+l+TAg==} + + koa-send@5.0.1: + resolution: {integrity: sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==} + engines: {node: '>= 8'} + + koa-static@5.0.0: + resolution: {integrity: sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==} + engines: {node: '>= 7.6.0'} + + koa@2.16.2: + resolution: {integrity: sha512-+CCssgnrWKx9aI3OeZwroa/ckG4JICxvIFnSiOUyl2Uv+UTI+xIw0FfFrWS7cQFpoePpr9o8csss7KzsTzNL8Q==} + engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4} + + lighthouse-logger@1.4.2: + resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} + + lit-element@4.2.2: + resolution: {integrity: sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==} + + lit-html@3.3.2: + resolution: {integrity: sha512-Qy9hU88zcmaxBXcc10ZpdK7cOLXvXpRoBxERdtqV9QOrfpMZZ6pSYP91LhpPtap3sFMUiL7Tw2RImbe0Al2/kw==} + + lit@3.3.2: + resolution: {integrity: sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + log-update@4.0.0: + resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} + engines: {node: '>=10'} + + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + + lru-cache@8.0.5: + resolution: {integrity: sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==} + engines: {node: '>=16.14'} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + marky@1.3.0: + resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + mocha@10.8.2: + resolution: {integrity: sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==} + engines: {node: '>= 14.0.0'} + hasBin: true + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanocolors@0.2.13: + resolution: {integrity: sha512-0n3mSAQLPpGLV9ORXT5+C/D4mwew7Ebws69Hx4E2sgz2ZA5+32Q80B9tL8PbL7XHnRDiAxH/pnrUJ9a4fkTNTA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + netmask@2.0.2: + resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} + engines: {node: '>= 0.4.0'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + only@0.0.2: + resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==} + + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + + p-event@4.2.0: + resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==} + engines: {node: '>=8'} + + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + + pac-proxy-agent@7.2.0: + resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} + engines: {node: '>= 14'} + + pac-resolver@7.0.1: + resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} + engines: {node: '>= 14'} + + parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pixelmatch@5.3.0: + resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==} + hasBin: true + + pixelmatch@7.2.0: + resolution: {integrity: sha512-xhcb4yHu9sM/G7foGzoLtXYcC0zHEaOXXjRKhGup0fw78Nf2Tkiapv4EQyMzrbcmQPsllAI7DbFY2UT7PlI9Pg==} + hasBin: true + + playwright-core@1.55.0: + resolution: {integrity: sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.55.0: + resolution: {integrity: sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==} + engines: {node: '>=18'} + hasBin: true + + pngjs@6.0.0: + resolution: {integrity: sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==} + engines: {node: '>=12.13.0'} + + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + + portfinder@1.0.37: + resolution: {integrity: sha512-yuGIEjDAYnnOex9ddMnKZEMFE0CcGo6zbfzDklkmT1m5z734ss6JMzN9rNB3+RR7iS+F10D4/BVIaXOyh8PQKw==} + engines: {node: '>= 10.12'} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + proxy-agent@6.5.0: + resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} + engines: {node: '>= 14'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + puppeteer-core@24.17.0: + resolution: {integrity: sha512-RYOBKFiF+3RdwIZTEacqNpD567gaFcBAOKTT7742FdB1icXudrPI7BlZbYTYWK2wgGQUXt9Zi1Yn+D5PmCs4CA==} + engines: {node: '>=18'} + + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resolve-path@1.4.0: + resolution: {integrity: sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==} + engines: {node: '>= 0.8'} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.49.0: + resolution: {integrity: sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + setprototypeof@1.1.0: + resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + sinon@20.0.0: + resolution: {integrity: sha512-+FXOAbdnj94AQIxH0w1v8gzNxkawVvNqE3jUzRLptR71Oykeu2RrQXXl/VQjKay+Qnh73fDt/oDfMo6xMeDQbQ==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + streamx@2.22.1: + resolution: {integrity: sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + table-layout@4.1.1: + resolution: {integrity: sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==} + engines: {node: '>=12.17'} + + tar-fs@3.1.0: + resolution: {integrity: sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==} + + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + + text-decoder@1.2.3: + resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsscmp@1.0.6: + resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} + engines: {node: '>=0.6.x'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-detect@4.1.0: + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} + engines: {node: '>=4'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typed-query-selector@2.12.0: + resolution: {integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==} + + typical@4.0.0: + resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==} + engines: {node: '>=8'} + + typical@7.3.0: + resolution: {integrity: sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==} + engines: {node: '>=12.17'} + + ua-parser-js@1.0.41: + resolution: {integrity: sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==} + hasBin: true + + undici-types@7.10.0: + resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wordwrapjs@5.1.0: + resolution: {integrity: sha512-JNjcULU2e4KJwUNv6CHgI46UvDGitb6dGryHajXTDiLgg1/RiGoPSDw4kZfYnwGtEXf2ZMeIewDQgFGzkCB2Sg==} + engines: {node: '>=12.17'} + + workerpool@6.5.1: + resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + ylru@1.4.0: + resolution: {integrity: sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==} + engines: {node: '>= 4.0.0'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +snapshots: + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.27.1': {} + + '@esbuild/aix-ppc64@0.25.9': + optional: true + + '@esbuild/android-arm64@0.25.9': + optional: true + + '@esbuild/android-arm@0.25.9': + optional: true + + '@esbuild/android-x64@0.25.9': + optional: true + + '@esbuild/darwin-arm64@0.25.9': + optional: true + + '@esbuild/darwin-x64@0.25.9': + optional: true + + '@esbuild/freebsd-arm64@0.25.9': + optional: true + + '@esbuild/freebsd-x64@0.25.9': + optional: true + + '@esbuild/linux-arm64@0.25.9': + optional: true + + '@esbuild/linux-arm@0.25.9': + optional: true + + '@esbuild/linux-ia32@0.25.9': + optional: true + + '@esbuild/linux-loong64@0.25.9': + optional: true + + '@esbuild/linux-mips64el@0.25.9': + optional: true + + '@esbuild/linux-ppc64@0.25.9': + optional: true + + '@esbuild/linux-riscv64@0.25.9': + optional: true + + '@esbuild/linux-s390x@0.25.9': + optional: true + + '@esbuild/linux-x64@0.25.9': + optional: true + + '@esbuild/netbsd-arm64@0.25.9': + optional: true + + '@esbuild/netbsd-x64@0.25.9': + optional: true + + '@esbuild/openbsd-arm64@0.25.9': + optional: true + + '@esbuild/openbsd-x64@0.25.9': + optional: true + + '@esbuild/openharmony-arm64@0.25.9': + optional: true + + '@esbuild/sunos-x64@0.25.9': + optional: true + + '@esbuild/win32-arm64@0.25.9': + optional: true + + '@esbuild/win32-ia32@0.25.9': + optional: true + + '@esbuild/win32-x64@0.25.9': + optional: true + + '@esm-bundle/chai@4.3.4-fix.0': + dependencies: + '@types/chai': 4.3.20 + + '@hapi/bourne@3.0.0': {} + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.30': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@lit-labs/ssr-dom-shim@1.4.0': {} + + '@lit-labs/ssr-dom-shim@1.6.0': {} + + '@lit/reactive-element@2.1.1': + dependencies: + '@lit-labs/ssr-dom-shim': 1.4.0 + + '@mdn/browser-compat-data@4.2.1': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@open-wc/dedupe-mixin@2.0.1': {} + + '@open-wc/scoped-elements@3.0.6': + dependencies: + '@open-wc/dedupe-mixin': 2.0.1 + lit: 3.3.2 + + '@open-wc/semantic-dom-diff@0.20.1': + dependencies: + '@types/chai': 4.3.20 + '@web/test-runner-commands': 0.9.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@open-wc/testing-helpers@3.0.1': + dependencies: + '@open-wc/scoped-elements': 3.0.6 + lit: 3.3.2 + lit-html: 3.3.2 + + '@open-wc/testing@4.0.0': + dependencies: + '@esm-bundle/chai': 4.3.4-fix.0 + '@open-wc/semantic-dom-diff': 0.20.1 + '@open-wc/testing-helpers': 3.0.1 + '@types/chai-dom': 1.11.3 + '@types/sinon-chai': 3.2.12 + chai-a11y-axe: 1.5.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@puppeteer/browsers@2.10.7': + dependencies: + debug: 4.4.1(supports-color@8.1.1) + extract-zip: 2.0.1 + progress: 2.0.3 + proxy-agent: 6.5.0 + semver: 7.7.2 + tar-fs: 3.1.0 + yargs: 17.7.2 + transitivePeerDependencies: + - bare-buffer + - supports-color + + '@rollup/plugin-node-resolve@15.3.1(rollup@4.49.0)': + dependencies: + '@rollup/pluginutils': 5.2.0(rollup@4.49.0) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-module: 1.0.0 + resolve: 1.22.10 + optionalDependencies: + rollup: 4.49.0 + + '@rollup/pluginutils@5.2.0(rollup@4.49.0)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.49.0 + + '@rollup/rollup-android-arm-eabi@4.49.0': + optional: true + + '@rollup/rollup-android-arm64@4.49.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.49.0': + optional: true + + '@rollup/rollup-darwin-x64@4.49.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.49.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.49.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.49.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.49.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.49.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.49.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.49.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.49.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.49.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.49.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.49.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.49.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.49.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.49.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.49.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.49.0': + optional: true + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@13.0.5': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@sinonjs/samsam@8.0.3': + dependencies: + '@sinonjs/commons': 3.0.1 + type-detect: 4.1.0 + + '@tootallnate/quickjs-emscripten@0.23.0': {} + + '@types/accepts@1.3.7': + dependencies: + '@types/node': 24.3.0 + + '@types/babel__code-frame@7.0.6': {} + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 24.3.0 + + '@types/chai-dom@1.11.3': + dependencies: + '@types/chai': 5.2.2 + + '@types/chai@4.3.20': {} + + '@types/chai@5.2.2': + dependencies: + '@types/deep-eql': 4.0.2 + + '@types/co-body@6.1.3': + dependencies: + '@types/node': 24.3.0 + '@types/qs': 6.14.0 + + '@types/command-line-args@5.2.3': {} + + '@types/connect@3.4.38': + dependencies: + '@types/node': 24.3.0 + + '@types/content-disposition@0.5.9': {} + + '@types/convert-source-map@2.0.3': {} + + '@types/cookies@0.9.1': + dependencies: + '@types/connect': 3.4.38 + '@types/express': 5.0.3 + '@types/keygrip': 1.0.6 + '@types/node': 24.3.0 + + '@types/debounce@1.2.4': {} + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/express-serve-static-core@5.0.7': + dependencies: + '@types/node': 24.3.0 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.5 + + '@types/express@5.0.3': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.0.7 + '@types/serve-static': 1.15.8 + + '@types/http-assert@1.5.6': {} + + '@types/http-errors@2.0.5': {} + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/keygrip@1.0.6': {} + + '@types/koa-compose@3.2.8': + dependencies: + '@types/koa': 3.0.0 + + '@types/koa@2.15.0': + dependencies: + '@types/accepts': 1.3.7 + '@types/content-disposition': 0.5.9 + '@types/cookies': 0.9.1 + '@types/http-assert': 1.5.6 + '@types/http-errors': 2.0.5 + '@types/keygrip': 1.0.6 + '@types/koa-compose': 3.2.8 + '@types/node': 24.3.0 + + '@types/koa@3.0.0': + dependencies: + '@types/accepts': 1.3.7 + '@types/content-disposition': 0.5.9 + '@types/cookies': 0.9.1 + '@types/http-assert': 1.5.6 + '@types/http-errors': 2.0.5 + '@types/keygrip': 1.0.6 + '@types/koa-compose': 3.2.8 + '@types/node': 24.3.0 + + '@types/mime@1.3.5': {} + + '@types/mkdirp@1.0.2': + dependencies: + '@types/node': 24.3.0 + + '@types/mocha@10.0.10': {} + + '@types/node@24.3.0': + dependencies: + undici-types: 7.10.0 + + '@types/parse5@6.0.3': {} + + '@types/pixelmatch@5.2.6': + dependencies: + '@types/node': 24.3.0 + + '@types/pngjs@6.0.5': + dependencies: + '@types/node': 24.3.0 + + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + + '@types/resolve@1.20.2': {} + + '@types/send@0.17.5': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 24.3.0 + + '@types/serve-static@1.15.8': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 24.3.0 + '@types/send': 0.17.5 + + '@types/sinon-chai@3.2.12': + dependencies: + '@types/chai': 5.2.2 + '@types/sinon': 17.0.4 + + '@types/sinon@17.0.4': + dependencies: + '@types/sinonjs__fake-timers': 8.1.5 + + '@types/sinonjs__fake-timers@8.1.5': {} + + '@types/trusted-types@2.0.7': {} + + '@types/ws@7.4.7': + dependencies: + '@types/node': 24.3.0 + + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 24.3.0 + optional: true + + '@web/browser-logs@0.4.1': + dependencies: + errorstacks: 2.4.1 + + '@web/config-loader@0.3.3': {} + + '@web/dev-server-core@0.7.5': + dependencies: + '@types/koa': 2.15.0 + '@types/ws': 7.4.7 + '@web/parse5-utils': 2.1.0 + chokidar: 4.0.3 + clone: 2.1.2 + es-module-lexer: 1.7.0 + get-stream: 6.0.1 + is-stream: 2.0.1 + isbinaryfile: 5.0.5 + koa: 2.16.2 + koa-etag: 4.0.0 + koa-send: 5.0.1 + koa-static: 5.0.0 + lru-cache: 8.0.5 + mime-types: 2.1.35 + parse5: 6.0.1 + picomatch: 2.3.1 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/dev-server-esbuild@1.0.4': + dependencies: + '@mdn/browser-compat-data': 4.2.1 + '@web/dev-server-core': 0.7.5 + esbuild: 0.25.9 + parse5: 6.0.1 + ua-parser-js: 1.0.41 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/dev-server-rollup@0.6.4': + dependencies: + '@rollup/plugin-node-resolve': 15.3.1(rollup@4.49.0) + '@web/dev-server-core': 0.7.5 + nanocolors: 0.2.13 + parse5: 6.0.1 + rollup: 4.49.0 + whatwg-url: 14.2.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/dev-server@0.4.6': + dependencies: + '@babel/code-frame': 7.27.1 + '@types/command-line-args': 5.2.3 + '@web/config-loader': 0.3.3 + '@web/dev-server-core': 0.7.5 + '@web/dev-server-rollup': 0.6.4 + camelcase: 6.3.0 + command-line-args: 5.2.1 + command-line-usage: 7.0.3 + debounce: 1.2.1 + deepmerge: 4.3.1 + internal-ip: 6.2.0 + nanocolors: 0.2.13 + open: 8.4.2 + portfinder: 1.0.37 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/parse5-utils@2.1.0': + dependencies: + '@types/parse5': 6.0.3 + parse5: 6.0.1 + + '@web/test-runner-chrome@0.18.1': + dependencies: + '@web/test-runner-core': 0.13.4 + '@web/test-runner-coverage-v8': 0.8.0 + chrome-launcher: 0.15.2 + puppeteer-core: 24.17.0 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - utf-8-validate + + '@web/test-runner-commands@0.9.0': + dependencies: + '@web/test-runner-core': 0.13.4 + mkdirp: 1.0.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/test-runner-core@0.13.4': + dependencies: + '@babel/code-frame': 7.27.1 + '@types/babel__code-frame': 7.0.6 + '@types/co-body': 6.1.3 + '@types/convert-source-map': 2.0.3 + '@types/debounce': 1.2.4 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@web/browser-logs': 0.4.1 + '@web/dev-server-core': 0.7.5 + chokidar: 4.0.3 + cli-cursor: 3.1.0 + co-body: 6.2.0 + convert-source-map: 2.0.0 + debounce: 1.2.1 + dependency-graph: 0.11.0 + globby: 11.1.0 + internal-ip: 6.2.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + log-update: 4.0.0 + nanocolors: 0.2.13 + nanoid: 3.3.11 + open: 8.4.2 + picomatch: 2.3.1 + source-map: 0.7.6 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/test-runner-coverage-v8@0.8.0': + dependencies: + '@web/test-runner-core': 0.13.4 + istanbul-lib-coverage: 3.2.2 + lru-cache: 8.0.5 + picomatch: 2.3.1 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/test-runner-mocha@0.9.0': + dependencies: + '@web/test-runner-core': 0.13.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/test-runner-playwright@0.11.1': + dependencies: + '@web/test-runner-core': 0.13.4 + '@web/test-runner-coverage-v8': 0.8.0 + playwright: 1.55.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/test-runner-visual-regression@0.10.0': + dependencies: + '@types/mkdirp': 1.0.2 + '@types/pixelmatch': 5.2.6 + '@types/pngjs': 6.0.5 + '@web/test-runner-commands': 0.9.0 + '@web/test-runner-core': 0.13.4 + mkdirp: 1.0.4 + pixelmatch: 5.3.0 + pngjs: 7.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@web/test-runner@0.20.2': + dependencies: + '@web/browser-logs': 0.4.1 + '@web/config-loader': 0.3.3 + '@web/dev-server': 0.4.6 + '@web/test-runner-chrome': 0.18.1 + '@web/test-runner-commands': 0.9.0 + '@web/test-runner-core': 0.13.4 + '@web/test-runner-mocha': 0.9.0 + camelcase: 6.3.0 + command-line-args: 5.2.1 + command-line-usage: 7.0.3 + convert-source-map: 2.0.0 + diff: 5.2.0 + globby: 11.1.0 + nanocolors: 0.2.13 + portfinder: 1.0.37 + source-map: 0.7.6 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - utf-8-validate + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + accessibility-developer-tools@2.12.0: {} + + agent-base@7.1.4: {} + + ansi-colors@4.1.3: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + argparse@2.0.1: {} + + array-back@3.1.0: {} + + array-back@6.2.2: {} + + array-union@2.1.0: {} + + ast-types@0.13.4: + dependencies: + tslib: 2.8.1 + + astral-regex@2.0.0: {} + + async@3.2.6: {} + + axe-core@4.10.3: {} + + b4a@1.6.7: {} + + balanced-match@1.0.2: {} + + bare-events@2.6.1: + optional: true + + bare-fs@4.2.1: + dependencies: + bare-events: 2.6.1 + bare-path: 3.0.0 + bare-stream: 2.7.0(bare-events@2.6.1) + optional: true + + bare-os@3.6.2: + optional: true + + bare-path@3.0.0: + dependencies: + bare-os: 3.6.2 + optional: true + + bare-stream@2.7.0(bare-events@2.6.1): + dependencies: + streamx: 2.22.1 + optionalDependencies: + bare-events: 2.6.1 + optional: true + + basic-ftp@5.0.5: {} + + binary-extensions@2.3.0: {} + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browser-stdout@1.3.1: {} + + buffer-crc32@0.2.13: {} + + buffer-from@1.1.2: {} + + bytes@3.1.2: {} + + cache-content-type@1.0.1: + dependencies: + mime-types: 2.1.35 + ylru: 1.4.0 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + camelcase@6.3.0: {} + + chai-a11y-axe@1.5.0: + dependencies: + axe-core: 4.10.3 + + chalk-template@0.4.0: + dependencies: + chalk: 4.1.2 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + chrome-launcher@0.15.2: + dependencies: + '@types/node': 24.3.0 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.4.2 + transitivePeerDependencies: + - supports-color + + chromium-bidi@8.0.0(devtools-protocol@0.0.1475386): + dependencies: + devtools-protocol: 0.0.1475386 + mitt: 3.0.1 + zod: 3.25.76 + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@2.1.2: {} + + co-body@6.2.0: + dependencies: + '@hapi/bourne': 3.0.0 + inflation: 2.1.0 + qs: 6.14.0 + raw-body: 2.5.2 + type-is: 1.6.18 + + co@4.6.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + command-line-args@5.2.1: + dependencies: + array-back: 3.1.0 + find-replace: 3.0.0 + lodash.camelcase: 4.3.0 + typical: 4.0.0 + + command-line-usage@7.0.3: + dependencies: + array-back: 6.2.2 + chalk-template: 0.4.0 + table-layout: 4.1.1 + typical: 7.3.0 + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + convert-source-map@2.0.0: {} + + cookies@0.9.1: + dependencies: + depd: 2.0.0 + keygrip: 1.1.0 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + data-uri-to-buffer@6.0.2: {} + + debounce@1.2.1: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.4.1(supports-color@8.1.1): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + + decamelize@4.0.0: {} + + deep-equal@1.0.1: {} + + deepmerge@4.3.1: {} + + default-gateway@6.0.3: + dependencies: + execa: 5.1.1 + + define-lazy-prop@2.0.0: {} + + degenerator@5.0.1: + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + + delegates@1.0.0: {} + + depd@1.1.2: {} + + depd@2.0.0: {} + + dependency-graph@0.11.0: {} + + destroy@1.2.0: {} + + devtools-protocol@0.0.1475386: {} + + diff@5.2.0: {} + + diff@7.0.0: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + emoji-regex@8.0.0: {} + + encodeurl@1.0.2: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + errorstacks@2.4.1: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + esbuild@0.25.9: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.9 + '@esbuild/android-arm': 0.25.9 + '@esbuild/android-arm64': 0.25.9 + '@esbuild/android-x64': 0.25.9 + '@esbuild/darwin-arm64': 0.25.9 + '@esbuild/darwin-x64': 0.25.9 + '@esbuild/freebsd-arm64': 0.25.9 + '@esbuild/freebsd-x64': 0.25.9 + '@esbuild/linux-arm': 0.25.9 + '@esbuild/linux-arm64': 0.25.9 + '@esbuild/linux-ia32': 0.25.9 + '@esbuild/linux-loong64': 0.25.9 + '@esbuild/linux-mips64el': 0.25.9 + '@esbuild/linux-ppc64': 0.25.9 + '@esbuild/linux-riscv64': 0.25.9 + '@esbuild/linux-s390x': 0.25.9 + '@esbuild/linux-x64': 0.25.9 + '@esbuild/netbsd-arm64': 0.25.9 + '@esbuild/netbsd-x64': 0.25.9 + '@esbuild/openbsd-arm64': 0.25.9 + '@esbuild/openbsd-x64': 0.25.9 + '@esbuild/openharmony-arm64': 0.25.9 + '@esbuild/sunos-x64': 0.25.9 + '@esbuild/win32-arm64': 0.25.9 + '@esbuild/win32-ia32': 0.25.9 + '@esbuild/win32-x64': 0.25.9 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@4.0.0: {} + + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + + esprima@4.0.1: {} + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + extract-zip@2.0.1: + dependencies: + debug: 4.4.1(supports-color@8.1.1) + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + + fast-fifo@1.3.2: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-replace@3.0.0: + dependencies: + array-back: 3.1.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat@5.0.2: {} + + fresh@0.5.2: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.2: + optional: true + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@5.2.0: + dependencies: + pump: 3.0.3 + + get-stream@6.0.1: {} + + get-uri@6.0.5: + dependencies: + basic-ftp: 5.0.5 + data-uri-to-buffer: 6.0.2 + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.2.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + html-escaper@2.0.2: {} + + http-assert@1.5.0: + dependencies: + deep-equal: 1.0.1 + http-errors: 1.8.1 + + http-errors@1.6.3: + dependencies: + depd: 1.1.2 + inherits: 2.0.3 + setprototypeof: 1.1.0 + statuses: 1.5.0 + + http-errors@1.8.1: + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 1.5.0 + toidentifier: 1.0.1 + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + human-signals@2.1.0: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + inflation@2.1.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.3: {} + + inherits@2.0.4: {} + + internal-ip@6.2.0: + dependencies: + default-gateway: 6.0.3 + ipaddr.js: 1.9.1 + is-ip: 3.1.0 + p-event: 4.2.0 + + ip-address@10.0.1: {} + + ip-regex@4.3.0: {} + + ipaddr.js@1.9.1: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-docker@2.2.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-ip@3.1.0: + dependencies: + ip-regex: 4.3.0 + + is-module@1.0.0: {} + + is-number@7.0.0: {} + + is-plain-obj@2.1.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-stream@2.0.1: {} + + is-unicode-supported@0.1.0: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isbinaryfile@5.0.5: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + keygrip@1.1.0: + dependencies: + tsscmp: 1.0.6 + + koa-compose@4.1.0: {} + + koa-convert@2.0.0: + dependencies: + co: 4.6.0 + koa-compose: 4.1.0 + + koa-etag@4.0.0: + dependencies: + etag: 1.8.1 + + koa-send@5.0.1: + dependencies: + debug: 4.4.1(supports-color@8.1.1) + http-errors: 1.8.1 + resolve-path: 1.4.0 + transitivePeerDependencies: + - supports-color + + koa-static@5.0.0: + dependencies: + debug: 3.2.7 + koa-send: 5.0.1 + transitivePeerDependencies: + - supports-color + + koa@2.16.2: + dependencies: + accepts: 1.3.8 + cache-content-type: 1.0.1 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookies: 0.9.1 + debug: 4.4.1(supports-color@8.1.1) + delegates: 1.0.0 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + fresh: 0.5.2 + http-assert: 1.5.0 + http-errors: 1.8.1 + is-generator-function: 1.1.0 + koa-compose: 4.1.0 + koa-convert: 2.0.0 + on-finished: 2.4.1 + only: 0.0.2 + parseurl: 1.3.3 + statuses: 1.5.0 + type-is: 1.6.18 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + lighthouse-logger@1.4.2: + dependencies: + debug: 2.6.9 + marky: 1.3.0 + transitivePeerDependencies: + - supports-color + + lit-element@4.2.2: + dependencies: + '@lit-labs/ssr-dom-shim': 1.6.0 + '@lit/reactive-element': 2.1.1 + lit-html: 3.3.2 + + lit-html@3.3.2: + dependencies: + '@types/trusted-types': 2.0.7 + + lit@3.3.2: + dependencies: + '@lit/reactive-element': 2.1.1 + lit-element: 4.2.2 + lit-html: 3.3.2 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.camelcase@4.3.0: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + log-update@4.0.0: + dependencies: + ansi-escapes: 4.3.2 + cli-cursor: 3.1.0 + slice-ansi: 4.0.0 + wrap-ansi: 6.2.0 + + lru-cache@7.18.3: {} + + lru-cache@8.0.5: {} + + make-dir@4.0.0: + dependencies: + semver: 7.7.2 + + marky@1.3.0: {} + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@2.1.0: {} + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + + mitt@3.0.1: {} + + mkdirp@1.0.4: {} + + mocha@10.8.2: + dependencies: + ansi-colors: 4.1.3 + browser-stdout: 1.3.1 + chokidar: 3.6.0 + debug: 4.4.1(supports-color@8.1.1) + diff: 5.2.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 8.1.0 + he: 1.2.0 + js-yaml: 4.1.0 + log-symbols: 4.1.0 + minimatch: 5.1.6 + ms: 2.1.3 + serialize-javascript: 6.0.2 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 6.5.1 + yargs: 16.2.0 + yargs-parser: 20.2.9 + yargs-unparser: 2.0.0 + + ms@2.0.0: {} + + ms@2.1.3: {} + + nanocolors@0.2.13: {} + + nanoid@3.3.11: {} + + negotiator@0.6.3: {} + + netmask@2.0.2: {} + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + only@0.0.2: {} + + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + p-event@4.2.0: + dependencies: + p-timeout: 3.2.0 + + p-finally@1.0.0: {} + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + + pac-proxy-agent@7.2.0: + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.4 + debug: 4.4.1(supports-color@8.1.1) + get-uri: 6.0.5 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + pac-resolver@7.0.1: + dependencies: + degenerator: 5.0.1 + netmask: 2.0.2 + + parse5@6.0.1: {} + + parseurl@1.3.3: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-type@4.0.0: {} + + pend@1.2.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pixelmatch@5.3.0: + dependencies: + pngjs: 6.0.0 + + pixelmatch@7.2.0: + dependencies: + pngjs: 7.0.0 + + playwright-core@1.55.0: {} + + playwright@1.55.0: + dependencies: + playwright-core: 1.55.0 + optionalDependencies: + fsevents: 2.3.2 + + pngjs@6.0.0: {} + + pngjs@7.0.0: {} + + portfinder@1.0.37: + dependencies: + async: 3.2.6 + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + progress@2.0.3: {} + + proxy-agent@6.5.0: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1(supports-color@8.1.1) + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 7.18.3 + pac-proxy-agent: 7.2.0 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + proxy-from-env@1.1.0: {} + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode@2.3.1: {} + + puppeteer-core@24.17.0: + dependencies: + '@puppeteer/browsers': 2.10.7 + chromium-bidi: 8.0.0(devtools-protocol@0.0.1475386) + debug: 4.4.1(supports-color@8.1.1) + devtools-protocol: 0.0.1475386 + typed-query-selector: 2.12.0 + ws: 8.18.3 + transitivePeerDependencies: + - bare-buffer + - bufferutil + - supports-color + - utf-8-validate + + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + + queue-microtask@1.2.3: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + raw-body@2.5.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + readdirp@4.1.2: {} + + require-directory@2.1.1: {} + + resolve-path@1.4.0: + dependencies: + http-errors: 1.6.3 + path-is-absolute: 1.0.1 + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + reusify@1.1.0: {} + + rollup@4.49.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.49.0 + '@rollup/rollup-android-arm64': 4.49.0 + '@rollup/rollup-darwin-arm64': 4.49.0 + '@rollup/rollup-darwin-x64': 4.49.0 + '@rollup/rollup-freebsd-arm64': 4.49.0 + '@rollup/rollup-freebsd-x64': 4.49.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.49.0 + '@rollup/rollup-linux-arm-musleabihf': 4.49.0 + '@rollup/rollup-linux-arm64-gnu': 4.49.0 + '@rollup/rollup-linux-arm64-musl': 4.49.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.49.0 + '@rollup/rollup-linux-ppc64-gnu': 4.49.0 + '@rollup/rollup-linux-riscv64-gnu': 4.49.0 + '@rollup/rollup-linux-riscv64-musl': 4.49.0 + '@rollup/rollup-linux-s390x-gnu': 4.49.0 + '@rollup/rollup-linux-x64-gnu': 4.49.0 + '@rollup/rollup-linux-x64-musl': 4.49.0 + '@rollup/rollup-win32-arm64-msvc': 4.49.0 + '@rollup/rollup-win32-ia32-msvc': 4.49.0 + '@rollup/rollup-win32-x64-msvc': 4.49.0 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.2.1: {} + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + + semver@7.7.2: {} + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + setprototypeof@1.1.0: {} + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + sinon@20.0.0: + dependencies: + '@sinonjs/commons': 3.0.1 + '@sinonjs/fake-timers': 13.0.5 + '@sinonjs/samsam': 8.0.3 + diff: 7.0.0 + supports-color: 7.2.0 + + slash@3.0.0: {} + + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + smart-buffer@4.2.0: {} + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1(supports-color@8.1.1) + socks: 2.8.7 + transitivePeerDependencies: + - supports-color + + socks@2.8.7: + dependencies: + ip-address: 10.0.1 + smart-buffer: 4.2.0 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.7.6: {} + + statuses@1.5.0: {} + + statuses@2.0.1: {} + + streamx@2.22.1: + dependencies: + fast-fifo: 1.3.2 + text-decoder: 1.2.3 + optionalDependencies: + bare-events: 2.6.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-final-newline@2.0.0: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + table-layout@4.1.1: + dependencies: + array-back: 6.2.2 + wordwrapjs: 5.1.0 + + tar-fs@3.1.0: + dependencies: + pump: 3.0.3 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 4.2.1 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-buffer + + tar-stream@3.1.7: + dependencies: + b4a: 1.6.7 + fast-fifo: 1.3.2 + streamx: 2.22.1 + + text-decoder@1.2.3: + dependencies: + b4a: 1.6.7 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + + tslib@2.8.1: {} + + tsscmp@1.0.6: {} + + type-detect@4.0.8: {} + + type-detect@4.1.0: {} + + type-fest@0.21.3: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typed-query-selector@2.12.0: {} + + typical@4.0.0: {} + + typical@7.3.0: {} + + ua-parser-js@1.0.41: {} + + undici-types@7.10.0: {} + + unpipe@1.0.0: {} + + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.30 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + vary@1.1.2: {} + + webidl-conversions@7.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wordwrapjs@5.1.0: {} + + workerpool@6.5.1: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + ws@7.5.10: {} + + ws@8.18.3: {} + + y18n@5.0.8: {} + + yargs-parser@20.2.9: {} + + yargs-parser@21.1.1: {} + + yargs-unparser@2.0.0: + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + ylru@1.4.0: {} + + yocto-queue@0.1.0: {} + + zod@3.25.76: {}
diff --git a/polygerrit-ui/pnpm-workspace.yaml b/polygerrit-ui/pnpm-workspace.yaml new file mode 100644 index 0000000..7d40629 --- /dev/null +++ b/polygerrit-ui/pnpm-workspace.yaml
@@ -0,0 +1 @@ +onlyBuiltDependencies: []
diff --git a/polygerrit-ui/polygerrit.MODULE.bazel b/polygerrit-ui/polygerrit.MODULE.bazel new file mode 100644 index 0000000..d839cc8 --- /dev/null +++ b/polygerrit-ui/polygerrit.MODULE.bazel
@@ -0,0 +1,128 @@ +# npm packages are split into different node_modules directories based on their +# usage. +# 1. @npm (node_modules) - contains packages to run tests, check code, etc... +# It is expected that @npm is used ONLY to run tools. No packages from @npm +# are used by other code in gerrit. +# 2. @tools_npm (tools/node_tools/node_modules) - the tools/node_tools folder +# contains self-written tools which are run for building and/or testing. The +# @tools_npm directory contains all the packages needed to run this tools. +# 3. @ui_npm (polygerrit-ui/app/node_modules) - packages with source code which +# are necessary to run polygerrit and to bundle it. Only code from these +# packages can be included in the final bundle for polygerrit. @ui_npm folder +# must not have devDependencies. All devDependencies must be placed in +# @ui_dev_npm. +# 4. @ui_dev_npm (polygerrit-ui/node_modules) - devDependencies for polygerrit. +# The packages from these folder can be used for testing, but must not be +# included in the final bundle. +# 5. @plugins_npm (plugins/node_modules) - plugin dependencies for polygerrit +# plugins. The packages here are expected to be used in plugins. +# Note: separation between @ui_npm and @ui_dev_npm is necessary because with +# rules_nodejs we can't generate two external repositories from the same +# package.json. At the same time we want to avoid accidental usages of code +# from devDependencies in polygerrit bundle. + +rules_ts_ext = use_extension("@aspect_rules_ts//ts:extensions.bzl", "ext", dev_dependency = True) +rules_ts_ext.deps( + ts_version_from = "//:package.json", +) +use_repo(rules_ts_ext, "npm_typescript") + +NODE_24_REPO = { + "24.14.1-darwin_arm64": ("node-v24.14.1-darwin-arm64.tar.gz", "node-v24.14.1-darwin-arm64", "25495ff85bd89e2d8a24d88566d7e2f827c6b0d3d872b2cebf75371f93fcb1fe"), + "24.14.1-darwin_amd64": ("node-v24.14.1-darwin-x64.tar.gz", "node-v24.14.1-darwin-x64", "2526230ad7d922be82d4fdb1e7ee1e84303e133e3b4b0ec4c2897ab31de0253d"), + "24.14.1-linux_arm64": ("node-v24.14.1-linux-arm64.tar.xz", "node-v24.14.1-linux-arm64", "71e427e28b78846f201d4d5ecc30cb13d1508ca099ef3871889a1256c7d6f67e"), + "24.14.1-linux_ppc64le": ("node-v24.14.1-linux-ppc64le.tar.xz", "node-v24.14.1-linux-ppc64le", "95bf0c8dbb73144edb79a57399f03c70af6995b78e1c632926e53e6404662ef5"), + "24.14.1-linux_s390x": ("node-v24.14.1-linux-s390x.tar.xz", "node-v24.14.1-linux-s390x", "ed3bfbc0ff418b0ec4633f23d53a12a691717a34b041c3fbdb296c8774e5a98a"), + "24.14.1-linux_amd64": ("node-v24.14.1-linux-x64.tar.xz", "node-v24.14.1-linux-x64", "84d38715d449447117d05c3e71acd78daa49d5b1bfa8aacf610303920c3322be"), + "24.14.1-windows_amd64": ("node-v24.14.1-win-x64.zip", "node-v24.14.1-win-x64", "6e50ce5498c0cebc20fd39ab3ff5df836ed2f8a31aa093cecad8497cff126d70"), +} + +node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node") +node.toolchain( + name = "nodejs", + node_repositories = NODE_24_REPO, + node_version = "24.14.1", +) +use_repo(node, "nodejs_toolchains") +use_repo(node, "nodejs_darwin_arm64") +use_repo(node, "nodejs_darwin_amd64") +use_repo(node, "nodejs_linux_arm64") +use_repo(node, "nodejs_linux_ppc64le") +use_repo(node, "nodejs_linux_s390x") +use_repo(node, "nodejs_linux_amd64") +use_repo(node, "nodejs_windows_amd64") + +pnpm = use_extension("@aspect_rules_js//npm:extensions.bzl", "pnpm") +pnpm.pnpm(pnpm_version = "10.33.3") +use_repo(pnpm, "pnpm") + +npm = use_extension("@aspect_rules_js//npm:extensions.bzl", "npm") +npm.npm_translate_lock( + name = "npm", + data = [ + "//:package.json", + "//:pnpm-workspace.yaml", + ], + pnpm_lock = "//:pnpm-lock.yaml", + update_pnpm_lock = True, + yarn_lock = "//:yarn.lock", +) +use_repo(npm, "npm") + +# Keep no_optional=True. +# ui_npm is the browser/runtime dependency set for PolyGerrit. +# If this is removed, aspect_rules_js npm_translate_lock defaults to installing +# optionalDependencies from the full transitive graph (not just from our root +# package.json). In this repo that pulls in Resemble.js -> canvas -> +# prebuild-install/native helper packages, which changes the realized +# node_modules tree and breaks license generation. +# This differs from normal pnpm behavior, which may skip platform-unneeded +# optional deps. +npm.npm_translate_lock( + name = "ui_npm", + data = [ + "//polygerrit-ui/app:package.json", + "//polygerrit-ui/app:pnpm-workspace.yaml", + ], + no_optional = True, + pnpm_lock = "//polygerrit-ui/app:pnpm-lock.yaml", + update_pnpm_lock = True, + yarn_lock = "//polygerrit-ui/app:yarn.lock", +) +use_repo(npm, "ui_npm") + +npm.npm_translate_lock( + name = "ui_dev_npm", + data = [ + "//polygerrit-ui:package.json", + "//polygerrit-ui:pnpm-workspace.yaml", + ], + pnpm_lock = "//polygerrit-ui:pnpm-lock.yaml", + update_pnpm_lock = True, + yarn_lock = "//polygerrit-ui:yarn.lock", +) +use_repo(npm, "ui_dev_npm") + +npm.npm_translate_lock( + name = "tools_npm", + data = [ + "//tools/node_tools:package.json", + "//tools/node_tools:pnpm-workspace.yaml", + ], + pnpm_lock = "//tools/node_tools:pnpm-lock.yaml", + update_pnpm_lock = True, + yarn_lock = "//tools/node_tools:yarn.lock", +) +use_repo(npm, "tools_npm") + +npm.npm_translate_lock( + name = "plugins_npm", + data = [ + "//plugins:package.json", + "//plugins:pnpm-workspace.yaml", + ], + pnpm_lock = "//plugins:pnpm-lock.yaml", + update_pnpm_lock = True, + yarn_lock = "//plugins:yarn.lock", +) +use_repo(npm, "plugins_npm")
diff --git a/polygerrit-ui/resultdb-reporter.mjs b/polygerrit-ui/resultdb-reporter.mjs new file mode 100644 index 0000000..7464754 --- /dev/null +++ b/polygerrit-ui/resultdb-reporter.mjs
@@ -0,0 +1,229 @@ +import fs from 'fs'; +import path from 'path'; +import { URL } from 'url'; + +const uiDir = path.dirname(new URL(import.meta.url).pathname); + +function getExistingDir(dirPath) { + if (fs.existsSync(dirPath)) return dirPath; + const lowerChromium = dirPath.replace('screenshots/Chromium', 'screenshots/chromium'); + if (fs.existsSync(lowerChromium)) return lowerChromium; + return null; +} + +function attachVisualDiff(test, artifacts) { + const match = test.error.message.match(/See diff for details: (\S+)/); + if (!match || !match[1]) return ''; + const diffPath = match[1]; + if (!fs.existsSync(diffPath)) return ''; + try { + artifacts['image_diff'] = { + contents: fs.readFileSync(diffPath).toString('base64'), + contentType: 'image/png', + }; + return ''; + } catch (e) { + console.error('Failed to read visual diff artifact', e); + return ''; + } +} + +function attachSideBySideScreenshots(test, testFile, artifacts) { + try { + const failedDir = getExistingDir(path.join(uiDir, 'screenshots/Chromium/failed')); + if (!failedDir) return ''; + + const files = fs.readdirSync(failedDir).filter(f => f.endsWith('.png') && !f.endsWith('-diff.png')); + if (files.length === 0) return ''; + + let bestFile = null; + let bestScore = -9999; + const testBase = path.basename(testFile, path.extname(testFile)).replace('_screenshot_test', ''); + const testNameLower = `${testBase} ${test.suiteName ? test.suiteName : ''} ${test.name}`.toLowerCase(); + + for (const file of files) { + const fileTokens = file.replace(/\.png$/, '').split(/[-_]/).filter(Boolean); + let matchedCount = 0; + for (const token of fileTokens) { + if (testNameLower.includes(token.toLowerCase())) matchedCount++; + } + if (matchedCount > 0) { + const score = matchedCount * 100 - fileTokens.length; + if (score > bestScore) { + bestScore = score; + bestFile = file; + } + } + } + + if (!bestFile) return ''; + + const actualPath = path.join(failedDir, bestFile); + const baselineDir = getExistingDir(path.join(uiDir, 'screenshots/Chromium/baseline')); + const baselinePath = baselineDir ? path.join(baselineDir, bestFile) : null; + const diffPath = path.join(failedDir, bestFile.replace(/\.png$/, '-diff.png')); + + if (!artifacts['image_diff'] && fs.existsSync(diffPath)) { + artifacts['image_diff'] = { + contents: fs.readFileSync(diffPath).toString('base64'), + contentType: 'image/png', + }; + } + if (fs.existsSync(actualPath)) { + artifacts['actual_image'] = { + contents: fs.readFileSync(actualPath).toString('base64'), + contentType: 'image/png', + }; + } + if (baselinePath && fs.existsSync(baselinePath)) { + artifacts['expected_image'] = { + contents: fs.readFileSync(baselinePath).toString('base64'), + contentType: 'image/png', + }; + } + + return ''; + } catch (e) { + console.error('Failed to find screenshot artifacts', e); + return ''; + } +} + +function escapeHtml(str) { + if (!str) return ''; + return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); +} + +export function resultDbReporter() { + let sinkCtx = null; + + // 1. Read LUCI_CONTEXT to get the Result Sink address and token + const luciCtxFile = process.env['LUCI_CONTEXT']; + if (luciCtxFile) { + try { + const luciCtx = JSON.parse(fs.readFileSync(luciCtxFile, 'utf8')); + sinkCtx = luciCtx.result_sink; + } catch (e) { + console.error('Failed to read LUCI_CONTEXT', e); + } + } + + async function uploadToResultSink(testResult) { + if (!sinkCtx) return; + + const url = `http://${sinkCtx.address}/prpc/luci.resultsink.v1.Sink/ReportTestResults`; + const headers = { + 'Content-Type': 'application/json', + 'Authorization': `ResultSink ${sinkCtx.auth_token}`, + }; + + const body = JSON.stringify({ + testResults: [testResult], + }); + + try { + const res = await fetch(url, { method: 'POST', headers, body }); + if (!res.ok) { + console.error('Failed to report to ResultDB', await res.text()); + } + } catch (e) { + console.error('Error reporting to ResultDB', e); + } + } + + let pendingUploads = Promise.resolve(); + + return { + onTestRunFinished({ testRun, sessions }) { + if (!sinkCtx) return; + + pendingUploads = pendingUploads.then(async () => { + for (const session of sessions) { + const testFile = path.basename(session.testFile); + + // 1. Report session-level hook / setup errors if any + if (session.errors && session.errors.length > 0) { + for (let i = 0; i < session.errors.length; i++) { + const err = session.errors[i]; + const testId = `gerrit > polygerrit-ui > ${testFile} > setup error ${i + 1}`; + const summaryHtml = `<pre>${escapeHtml(err.message)}\n${escapeHtml(err.stack)}</pre>`; + await uploadToResultSink({ + testId, + status: 'FAIL', + expected: false, + summaryHtml, + failureReason: err.message ? { primaryErrorMessage: err.message } : undefined, + }); + } + } + + if (!session.testResults) continue; + + // 2. Flatten the nested Mocha suites/tests + const tests = []; + function collectTests(suite, parentName = '') { + const name = suite.name ? (parentName ? `${parentName} > ${suite.name}` : suite.name) : parentName; + if (suite.tests) { + for (const t of suite.tests) { + tests.push({ ...t, suiteName: name }); + } + } + if (suite.suites) { + for (const s of suite.suites) { + collectTests(s, name); + } + } + } + collectTests(session.testResults); + + for (const test of tests) { + const testName = test.suiteName ? `${test.suiteName} > ${test.name}` : test.name; + const testId = `gerrit > polygerrit-ui > ${testFile} > ${testName}`; + + let status = 'PASS'; + let expected = true; + if (test.skipped) { + status = 'SKIP'; + expected = true; + } else if (!test.passed) { + status = 'FAIL'; + expected = false; + } + + let summaryHtml = ''; + if (test.error) { + summaryHtml = `<pre>${escapeHtml(test.error.message)}\n${escapeHtml(test.error.stack)}</pre>`; + } + const artifacts = {}; + + // 3. If visual diff failed or dimension mismatched, extract images and upload them + if (!test.passed && test.error) { + const isVisualDiff = test.error.message.includes('Visual diff failed'); + const isDimMismatch = test.error.message.includes('Screenshot is not the same width and height as the baseline'); + + if (isVisualDiff || isDimMismatch) { + if (isVisualDiff) { + attachVisualDiff(test, artifacts); + } + attachSideBySideScreenshots(test, testFile, artifacts); + } + } + + await uploadToResultSink({ + testId, + status, + expected, + summaryHtml: summaryHtml || undefined, + artifacts: Object.keys(artifacts).length > 0 ? artifacts : undefined, + duration: test.duration ? `${(test.duration / 1000).toFixed(9)}s` : undefined, + failureReason: test.error?.message ? { primaryErrorMessage: test.error.message } : undefined, + }); + } + } + }); + }, + async stop() { + await pendingUploads; + } + }; +}
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-dark.png index 3054927..8cb2891 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-citations-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-citations-dark.png index 0ed2eb4..4d0d695 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-citations-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-citations-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-citations.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-citations.png index f2b9ccd..4234037 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-citations.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-citations.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-comment-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-comment-dark.png index 8ff88e4..48b2792 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-comment-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-comment-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-comment.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-comment.png index 74a06c8..c92f2b7 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-comment.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-comment.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-error-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-error-dark.png index 22ad904..090b2f0 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-error-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-error-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-error.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-error.png index 2c454b2..e8b5847 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-error.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-error.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-references-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-references-dark.png index 68831df..f5f5b48 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-references-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-references-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-references.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-references.png index 8ac50b1a..8f2cd95 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-references.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode-with-references.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode.png index 5cf48ac..1a9f4b7 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-chat-mode.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-history-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-history-dark.png index c86ef7d..0054460 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-history-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-history-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-history-scrolling-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-history-scrolling-dark.png index 1212b06..9d9fd5b 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-history-scrolling-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-history-scrolling-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-history-scrolling.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-history-scrolling.png index dff82cb..06b2bfb 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-history-scrolling.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-history-scrolling.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-history.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-history.png index 6b6ac64..2882d27 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-history.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-history.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-models-menu-open-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-models-menu-open-dark.png index f548ba7..c8ea2f2 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-models-menu-open-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-models-menu-open-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-models-menu-open.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-models-menu-open.png index a6425b7..78867a1 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-models-menu-open.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-models-menu-open.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-prompt-box-suggested-items-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-prompt-box-suggested-items-dark.png index 7068925..c6f5cde 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-prompt-box-suggested-items-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-prompt-box-suggested-items-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-prompt-box-suggested-items.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-prompt-box-suggested-items.png index 9f5f2ee..dfb0372 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-prompt-box-suggested-items.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-prompt-box-suggested-items.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-custom-actions-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-custom-actions-dark.png index c6f1614..b67cb3d 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-custom-actions-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-custom-actions-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-custom-actions.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-custom-actions.png index fa1e93c..d22e746 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-custom-actions.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-custom-actions.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-dark.png index 245a38c..0f738db 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-private-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-private-dark.png index 3a7d864..d210860 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-private-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-private-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-private.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-private.png index 9436246..3904879 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-private.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page-private.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page.png b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page.png index 1e89cee..a52afda 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/chat-panel-splash-page.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-account-info-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-account-info-dark.png index 9fa51fc..2ff1605 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-account-info-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-account-info-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-account-info.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-account-info.png index d1227fd..ad28ceb 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-account-info.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-account-info.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-admin-group-list-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-admin-group-list-dark.png index cab7aca..9d9a80e 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-admin-group-list-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-admin-group-list-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-admin-group-list.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-admin-group-list.png index 508e19b..7890866 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-admin-group-list.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-admin-group-list.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-list-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-list-dark.png index 01c139a..a33a656 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-list-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-list-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-list.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-list.png index cf6fc7a..2cdb5aa 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-list.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-list.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-summary-with-ai-review-prompt-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-summary-with-ai-review-prompt-dark.png index d85c67c..7bbdc5b 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-summary-with-ai-review-prompt-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-summary-with-ai-review-prompt-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-summary-with-ai-review-prompt.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-summary-with-ai-review-prompt.png index 7406562..ff592ae 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-summary-with-ai-review-prompt.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-summary-with-ai-review-prompt.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-summary-with-chips-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-summary-with-chips-dark.png index d85c67c..782a764 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-summary-with-chips-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-summary-with-chips-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-summary-with-chips.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-summary-with-chips.png index 7406562..1368d3d 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-summary-with-chips.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-summary-with-chips.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-1280px-chat-open-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-1280px-chat-open-dark.png index 79a6c45..ac50c8e 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-1280px-chat-open-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-1280px-chat-open-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-1280px-chat-open.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-1280px-chat-open.png index bbdd523..dd975a0 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-1280px-chat-open.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-1280px-chat-open.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-801px-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-801px-dark.png index d1a268f..10dbc5a 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-801px-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-801px-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-801px.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-801px.png index 4d753b1..ff4d272 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-801px.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-801px.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-wrapped-statuses-801px-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-wrapped-statuses-801px-dark.png new file mode 100644 index 0000000..d1deb28 --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-wrapped-statuses-801px-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-wrapped-statuses-801px.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-wrapped-statuses-801px.png index bf2f836..842ab66 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-wrapped-statuses-801px.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-change-view-wrapped-statuses-801px.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-checks-results-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-checks-results-dark.png index 2e46be7..bd67930 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-checks-results-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-checks-results-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-checks-results.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-checks-results.png index 92d1d7e..9257bef 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-checks-results.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-checks-results.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-ai-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-ai-dark.png new file mode 100644 index 0000000..09b4b0f --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-ai-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-ai.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-ai.png new file mode 100644 index 0000000..c109a72 --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-ai.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-normal-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-normal-dark.png new file mode 100644 index 0000000..8eab5f2 --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-normal-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-normal.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-normal.png new file mode 100644 index 0000000..87c0c65 --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-normal.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-resolved-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-resolved-dark.png index d6b63bf..359ec4b 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-resolved-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-resolved-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-resolved.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-resolved.png index e9cfdd2..b047d58 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-resolved.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-resolved.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-unresolved-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-unresolved-dark.png index cdfebe6..81b6312 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-unresolved-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-unresolved-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-unresolved-inline-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-unresolved-inline-dark.png new file mode 100644 index 0000000..60eb357 --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-unresolved-inline-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-unresolved-inline.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-unresolved-inline.png new file mode 100644 index 0000000..3cde291 --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-unresolved-inline.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-unresolved.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-unresolved.png index a3c2cda..27a87d5 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-unresolved.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-unresolved.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-with-ai-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-with-ai-dark.png new file mode 100644 index 0000000..2dce03b --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-with-ai-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-with-ai.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-with-ai.png new file mode 100644 index 0000000..0c25dd7 --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-with-ai.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-with-draft-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-with-draft-dark.png index f1db32b..131e4f3 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-with-draft-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-with-draft-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-with-draft.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-with-draft.png index c8c9517..a000c37 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-with-draft.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-comment-thread-with-draft.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-content-with-sidebar.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-content-with-sidebar.png index 91a71b4..2e0fb8c 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-content-with-sidebar.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-content-with-sidebar.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-copy-links.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-copy-links.png index e41749f..77f60fc 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-copy-links.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-copy-links.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-create-flow-dialog-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-create-flow-dialog-dark.png index 201edda..8a4c591 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-create-flow-dialog-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-create-flow-dialog-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-create-flow-dialog.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-create-flow-dialog.png index 5844c3c..713ba32 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-create-flow-dialog.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-create-flow-dialog.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-diff-check-result-collapsed-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-diff-check-result-collapsed-dark.png index 5b7b70f..c9b50fc 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-diff-check-result-collapsed-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-diff-check-result-collapsed-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-diff-check-result-collapsed.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-diff-check-result-collapsed.png index 96bea54..44ce9b7 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-diff-check-result-collapsed.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-diff-check-result-collapsed.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-diff-check-result-expanded-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-diff-check-result-expanded-dark.png index 484426d..a6be554 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-diff-check-result-expanded-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-diff-check-result-expanded-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-diff-check-result-expanded.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-diff-check-result-expanded.png index 49002e6..f79baa2 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-diff-check-result-expanded.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-diff-check-result-expanded.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-editor-view-cancel-modal-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-editor-view-cancel-modal-dark.png new file mode 100644 index 0000000..fe699cb --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-editor-view-cancel-modal-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-editor-view-cancel-modal.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-editor-view-cancel-modal.png new file mode 100644 index 0000000..d6df16b --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-editor-view-cancel-modal.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-editor-view-normal-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-editor-view-normal-dark.png new file mode 100644 index 0000000..dc9928d --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-editor-view-normal-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-editor-view-normal.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-editor-view-normal.png new file mode 100644 index 0000000..c4b94d4 --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-editor-view-normal.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-file-list-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-file-list-dark.png index 76b57e8..c8d002c 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-file-list-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-file-list-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-file-list.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-file-list.png index 4b1184a..36b36e9 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-file-list.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-file-list.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-dark.png index 28a2960..b2e532a 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-empty-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-empty-dark.png index 7a7b5b5..58498e3 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-empty-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-empty-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-empty.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-empty.png index 1c793ac..f62f699 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-empty.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-empty.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-loading-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-loading-dark.png index 054acc6..1efc38a 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-loading-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-loading-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-loading.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-loading.png index 310219c..560729a 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-loading.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-loading.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-multiple-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-multiple-dark.png index 07d1c36..f7458c8 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-multiple-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-multiple-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-multiple.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-multiple.png index 32200a5..3550255 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-multiple.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-multiple.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-not-uploader-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-not-uploader-dark.png index 24a7544..55c4a35 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-not-uploader-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-not-uploader-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-not-uploader.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-not-uploader.png index 948d1b1..94d75a4 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-not-uploader.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows-not-uploader.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows.png index 2f308d1..d2bb4df 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-flows.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-flows.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-label-score-row-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-label-score-row-dark.png index a26b67f..9116995 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-label-score-row-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-label-score-row-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-label-score-row.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-label-score-row.png index ab28bdf..8fdccb3 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-label-score-row.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-label-score-row.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-label-scores-long-trigger-vote-label-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-label-scores-long-trigger-vote-label-dark.png index a8f0a98..550e0e6 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-label-scores-long-trigger-vote-label-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-label-scores-long-trigger-vote-label-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-label-scores-long-trigger-vote-label.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-label-scores-long-trigger-vote-label.png index 8847764..aade8f9 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-label-scores-long-trigger-vote-label.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-label-scores-long-trigger-vote-label.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-message-collapsed-ai-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-collapsed-ai-dark.png new file mode 100644 index 0000000..71511b4 --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-collapsed-ai-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-message-collapsed-ai.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-collapsed-ai.png new file mode 100644 index 0000000..066e0b0 --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-collapsed-ai.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-message-collapsed-normal-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-collapsed-normal-dark.png new file mode 100644 index 0000000..584aa0d --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-collapsed-normal-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-message-collapsed-normal.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-collapsed-normal.png new file mode 100644 index 0000000..8585f88 --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-collapsed-normal.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-message-collapsed-scores-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-collapsed-scores-dark.png new file mode 100644 index 0000000..599409d --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-collapsed-scores-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-message-collapsed-scores.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-collapsed-scores.png new file mode 100644 index 0000000..cb223a3 --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-collapsed-scores.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-message-expanded-ai-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-expanded-ai-dark.png new file mode 100644 index 0000000..0297d2c --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-expanded-ai-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-message-expanded-ai.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-expanded-ai.png new file mode 100644 index 0000000..83e88b6 --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-expanded-ai.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-message-expanded-scores-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-expanded-scores-dark.png new file mode 100644 index 0000000..14f8001 --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-expanded-scores-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-message-expanded-scores.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-expanded-scores.png new file mode 100644 index 0000000..4f27dd6 --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-message-expanded-scores.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-reply-dialog-autosubmit-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-reply-dialog-autosubmit-dark.png index 69e0c32..3326727 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-reply-dialog-autosubmit-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-reply-dialog-autosubmit-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-reply-dialog-autosubmit.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-reply-dialog-autosubmit.png index 8dfa9ed..58681ee 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-reply-dialog-autosubmit.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-reply-dialog-autosubmit.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-rule-editor-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-rule-editor-dark.png index 58fffae..0a14b00 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-rule-editor-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-rule-editor-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-rule-editor.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-rule-editor.png index 94b2f6b5..a3b6300 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-rule-editor.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-rule-editor.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-user-suggestion-fix-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-user-suggestion-fix-dark.png index 34fca7b..0e0b230 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-user-suggestion-fix-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-user-suggestion-fix-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/gr-user-suggestion-fix.png b/polygerrit-ui/screenshots/Chromium/baseline/gr-user-suggestion-fix.png index e308091..3ae5bc2 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/gr-user-suggestion-fix.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/gr-user-suggestion-fix.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-card-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-card-dark.png index 5decdca..45aff22 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-card-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-card-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-card.png b/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-card.png index 027888a..4a9a251 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-card.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-card.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal-dark.png index 0762253..aa9c104 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal-dark.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal-expanded-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal-expanded-dark.png new file mode 100644 index 0000000..6a145ea --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal-expanded-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal-expanded.png b/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal-expanded.png new file mode 100644 index 0000000..1497230 --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal-expanded.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal-long-dark.png b/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal-long-dark.png new file mode 100644 index 0000000..cdce50d --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal-long-dark.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal-long.png b/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal-long.png new file mode 100644 index 0000000..c58b770 --- /dev/null +++ b/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal-long.png Binary files differ
diff --git a/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal.png b/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal.png index bca5f67..5956d3b 100644 --- a/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal.png +++ b/polygerrit-ui/screenshots/Chromium/baseline/splash-page-action-details-modal.png Binary files differ
diff --git a/polygerrit-ui/web-test-runner.config.mjs b/polygerrit-ui/web-test-runner.config.mjs index 893d0fb..d510213 100644 --- a/polygerrit-ui/web-test-runner.config.mjs +++ b/polygerrit-ui/web-test-runner.config.mjs
@@ -2,12 +2,14 @@ import fs from 'fs'; import { esbuildPlugin } from '@web/dev-server-esbuild'; import { defaultReporter, summaryReporter } from '@web/test-runner'; +import { resultDbReporter } from './resultdb-reporter.mjs'; import { visualRegressionPlugin } from '@web/test-runner-visual-regression/plugin'; import pixelmatch from 'pixelmatch'; import { PNG } from 'pngjs'; import { playwrightLauncher } from '@web/test-runner-playwright'; const runUnderBazel = !!process.env['RUNFILES_DIR']; +const diffThreshold = 0.01; function testRunnerHtmlFactory(prefix) { return (testFramework) => ` @@ -34,11 +36,11 @@ ]; } - const runfilesRoot = path.dirname(process.cwd()); + const cwd = process.cwd(); return [ - path.join(runfilesRoot, 'plugins_npm', 'node_modules'), - path.join(runfilesRoot, 'ui_npm', 'node_modules'), - path.join(runfilesRoot, 'ui_dev_npm', 'node_modules'), + path.join(cwd, 'plugins/node_modules'), + path.join(cwd, 'polygerrit-ui/node_modules'), + path.join(cwd, 'polygerrit-ui/app/node_modules'), ]; } @@ -65,6 +67,12 @@ // When running under Bazel, we also need strictly fully qualified paths. const stylePathPrefix = 'polygerrit-ui/'; +const chromeExecutablePath = [ + process.env['CHROME_BIN'], + process.env['CI_CHROME_BIN'], + '/usr/bin/google-chrome', +].find(p => p && fs.existsSync(p)); + /** @type {import('@web/test-runner').TestRunnerConfig} */ const config = { // Default is CPU cores / 2. Use default @@ -78,10 +86,16 @@ playwrightLauncher({ product: 'chromium', launchOptions: { + ...(chromeExecutablePath ? { executablePath: chromeExecutablePath } : {}), args: [ + '--no-sandbox', + '--disable-dev-shm-usage', '--disable-background-timer-throttling', '--disable-backgrounding-occluded-windows', '--disable-renderer-backgrounding', + '--font-render-hinting=none', + '--disable-font-subpixel-rendering', + '--disable-lcd-text', ], }, }), @@ -104,7 +118,14 @@ nodeResolve: { modulePaths: getModulesDir(), - dedupe: ['lit', 'lit-html', 'lit-element'], + dedupe: [ + 'lit', + 'lit-html', + 'lit-element', + '@open-wc/testing', + '@open-wc/testing-helpers', + 'sinon', + ], }, testFramework: { @@ -127,11 +148,12 @@ tsconfig: tsConfig, }), visualRegressionPlugin({ + baseDir: pathPrefix ? `${pathPrefix}screenshots` : 'screenshots', // TODO(milutin): Tweak these values - diffOptions threshold is for color change // and failureThreshold is for pixel change. We need to find a balance to allow // CI to pass, but also catch regressions. diffOptions: { threshold: 0.6 }, - failureThreshold: 2, + failureThreshold: diffThreshold, failureThresholdType: 'percent', update: process.argv.includes('--update-screenshots'), // The visual regression plugin by default blindly overwrites all goldens @@ -157,7 +179,7 @@ try { basePng = PNG.sync.read(oldContent); newPng = PNG.sync.read(content); - } catch(e) { + } catch (e) { console.warn('Failed to parse PNGs for diff checking', e); } @@ -172,7 +194,7 @@ ); const diffPercentage = (numDiffPixels / (basePng.width * basePng.height)) * 100; - if (diffPercentage <= 2) { + if (diffPercentage <= diffThreshold) { return; } } @@ -188,7 +210,11 @@ // /lib/fonts/ for screenshots tests, see middleware. rootDir: runUnderBazel ? rootDir : '..', - reporters: [defaultReporter(), summaryReporter()], + reporters: [ + defaultReporter(), + summaryReporter(), + resultDbReporter(), + ], middleware: [ // Fonts are in /lib/fonts/, but css tries to load from
diff --git a/polymer-bridges b/polymer-bridges deleted file mode 160000 index 855f478..0000000 --- a/polymer-bridges +++ /dev/null
@@ -1 +0,0 @@ -Subproject commit 855f4781b702de120953a64da5c277ea4908deaa
diff --git a/prologtests/examples/BUILD b/prologtests/examples/BUILD index 83c98e2..6b4749f 100644 --- a/prologtests/examples/BUILD +++ b/prologtests/examples/BUILD
@@ -1,3 +1,5 @@ +load("@rules_shell//shell:sh_test.bzl", "sh_test") + package(default_visibility = ["//visibility:public"]) sh_test(
diff --git a/proto/BUILD b/proto/BUILD index 4b3eae4..d523718 100644 --- a/proto/BUILD +++ b/proto/BUILD
@@ -1,5 +1,5 @@ load("@protobuf//bazel:java_proto_library.bzl", "java_proto_library") -load("@rules_proto//proto:defs.bzl", "proto_library") +load("@protobuf//bazel:proto_library.bzl", "proto_library") proto_library( name = "cache_proto",
diff --git a/proto/cache.proto b/proto/cache.proto index 19046a5..fbdce93 100644 --- a/proto/cache.proto +++ b/proto/cache.proto
@@ -761,6 +761,7 @@ string new_mode = 14; // ENUM as string bytes old_sha = 15; bytes new_sha = 16; + bool diffs_too_expensive_to_compute = 17; } // Serialized form of com.google.gerrit.server.ReviewerSet
diff --git a/proto/entities.proto b/proto/entities.proto index e0367b6..9790cb0 100644 --- a/proto/entities.proto +++ b/proto/entities.proto
@@ -134,15 +134,15 @@ COMPUTED_BASE = 2; ONE_SIDED_MERGE_STRATEGY = 3; NO_MERGE_PERFORMED = 4; - } +} // Serialized form of com.google.gerrit.extensions.common.MergeInput. // Next ID: 5 message MergeInput { - optional string source = 1; - optional string source_branch = 2; - optional string strategy = 3; - optional bool allow_conflicts = 4; + optional string source = 1; + optional string source_branch = 2; + optional string strategy = 3; + optional bool allow_conflicts = 4; } // Serialized form of com.google.gerrit.extensions.api.changes.ApplyPatchInput. @@ -309,7 +309,8 @@ // Next ID: 2 message ObjectId { // Hex string representation of the ID. - optional string name = 1 [default="0000000000000000000000000000000000000000"]; + optional string name = 1 + [default = "0000000000000000000000000000000000000000"]; } // Serialized form of a continuation token used for pagination. @@ -321,7 +322,7 @@ // Proto representation of the User preferences classes // Next ID: 4 message UserPreferences { - // Next ID: 26 + // Next ID: 28 message GeneralPreferencesInfo { // Number of changes to show in a screen. optional int32 changes_per_page = 1 [default = 25]; @@ -404,10 +405,11 @@ optional bool allow_autocompleting_comments = 25 [default = true]; optional string diff_page_sidebar = 23 [default = "NONE"]; optional string ai_chat_selected_model = 26; + optional string label_filter = 27; } optional GeneralPreferencesInfo general_preferences_info = 1; - // Next ID: 25 + // Next ID: 26 message DiffPreferencesInfo { optional int32 context = 1 [default = 10]; optional int32 tab_size = 2 [default = 8]; @@ -441,6 +443,13 @@ optional bool skip_deleted = 22; optional bool skip_unchanged = 23; optional bool skip_uncommented = 24; + + enum ResponsiveMode { + NONE = 0; + SHRINK_ONLY = 1; + FULL_RESPONSIVE = 2; + } + optional ResponsiveMode responsive_mode = 25; } optional DiffPreferencesInfo diff_preferences_info = 2; @@ -540,8 +549,9 @@ // Next Id: 4 message FixSuggestion { optional string fix_id = 1; - optional string description = 2; + optional string description = 2; repeated FixReplacement replacements = 3; } repeated FixSuggestion fix_suggestions = 13; + optional bool is_ai = 14; }
diff --git a/proto/testing/BUILD b/proto/testing/BUILD index ee01e38..4428d27 100644 --- a/proto/testing/BUILD +++ b/proto/testing/BUILD
@@ -1,5 +1,5 @@ load("@protobuf//bazel:java_proto_library.bzl", "java_proto_library") -load("@rules_proto//proto:defs.bzl", "proto_library") +load("@protobuf//bazel:proto_library.bzl", "proto_library") proto_library( name = "test_proto",
diff --git a/resources/BUILD b/resources/BUILD index d4d0df3..7235cf2 100644 --- a/resources/BUILD +++ b/resources/BUILD
@@ -1,5 +1,5 @@ load("@rules_java//java:defs.bzl", "java_import") -load("//tools/bzl:genrule2.bzl", "genrule2") +load("@com_googlesource_gerrit_bazlets//tools:genrule2.bzl", "genrule2") java_import( name = "log4j-config",
diff --git a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy index 8335b76..06f07b5 100644 --- a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy +++ b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
@@ -26,7 +26,6 @@ {@param? faviconPath: ?} {@param? manifestPath: ?} {@param? versionInfo: ?} - {@param? polyfillCE: ?} {@param? useGoogleFonts: ?} {@param? changeNum: ?} {@param? changeRequestsPath: ?} @@ -66,7 +65,6 @@ {if $versionInfo}window.VERSION_INFO = '{$versionInfo}';{/if} {if $staticResourcePath != ''}window.STATIC_RESOURCE_PATH = '{$staticResourcePath}';{/if} {if $assetsPath}window.ASSETS_PATH = '{$assetsPath}';{/if} - {if $polyfillCE}if (window.customElements) window.customElements.forcePolyfill = true;{/if} {if $gerritInitialData} // INITIAL_DATA is a string that represents a JSON map. It's inlined here so that we can // spare calls to the API when starting up the app. @@ -89,7 +87,9 @@ {else} <link rel="icon" type="image/x-icon" href="{$canonicalPath}/favicon.ico">{\n} {/if} - <link rel="manifest" href="{$manifestPath}">{\n} + {if $manifestPath} + <link rel="manifest" href="{$manifestPath}">{\n} + {/if} {if $changeRequestsPath} {if $defaultChangeDetailHex} <link rel="preload" href="{$canonicalPath}/{$changeRequestsPath}/detail?O={$defaultChangeDetailHex}" as="fetch" type="application/json" crossorigin="anonymous"/>{\n} @@ -146,14 +146,8 @@ {/if} <link rel="preload" as="style" href="{$staticResourcePath}/styles/main.css">{\n} - <script src="{$staticResourcePath}/bower_components/webcomponentsjs/webcomponents-loader.js"></script>{\n} - - // Content between webcomponents-loader and the load of the main app element - // run before polymer-resin is installed so may have security consequences. - // Contact your local security engineer if you have any questions, and - // CC them on any changes that load content before gr-app.js. - // - // github.com/Polymer/polymer-resin/blob/master/getting-started.md#integrating + // Optional host-specific asset bundle loaded before gr-app.js. + // Be careful when changing this, because it affects early page startup. {if $assetsPath && $assetsBundle} <link rel="import" href="{$assetsPath}/{$assetsBundle}">{\n} {/if}
diff --git a/resources/com/google/gerrit/server/BUILD b/resources/com/google/gerrit/server/BUILD index e92c4e1..a64ed82 100644 --- a/resources/com/google/gerrit/server/BUILD +++ b/resources/com/google/gerrit/server/BUILD
@@ -1,3 +1,5 @@ +load("@rules_shell//shell:sh_test.bzl", "sh_test") + filegroup( name = "server", srcs = glob(
diff --git a/tools/BUILD b/tools/BUILD index 9529ec7..8b3429d 100644 --- a/tools/BUILD +++ b/tools/BUILD
@@ -2,8 +2,8 @@ "@bazel_tools//tools/jdk:default_java_toolchain.bzl", "default_java_toolchain", ) +load("@protobuf//bazel/toolchains:proto_lang_toolchain.bzl", "proto_lang_toolchain") load("@rules_java//java:defs.bzl", "java_package_configuration") -load("@rules_proto//proto:defs.bzl", "proto_lang_toolchain") exports_files([ "nongoogle.toml", @@ -29,8 +29,8 @@ target_version = VERSION, visibility = ["//visibility:public"], ) for VERSION in [ - "17", "21", + "25", ]] # Error Prone errors enabled by default; see ../.bazelrc for how this is
diff --git a/tools/bazlets.MODULE.bazel b/tools/bazlets.MODULE.bazel index 63bd93c..8bf405d 100644 --- a/tools/bazlets.MODULE.bazel +++ b/tools/bazlets.MODULE.bazel
@@ -1,12 +1,12 @@ # Plugin packaging support kept outside the root module declaration: # - bazlets dependency pin # - generated Gerrit API version repo for artifact versioning and stamping -GERRIT_VERSION = "3.14.2-SNAPSHOT" +GERRIT_VERSION = "3.15.0-SNAPSHOT" bazel_dep(name = "com_googlesource_gerrit_bazlets") git_override( module_name = "com_googlesource_gerrit_bazlets", - commit = "31e8579d49bc4b7f2cdc3bed3367449c606586c4", + commit = "2366eccc72aac1a966ef3446c98a80230a7b5d89", remote = "https://gerrit.googlesource.com/bazlets", )
diff --git a/tools/bzl/BUILD b/tools/bzl/BUILD index 62f0adb..7e09203 100644 --- a/tools/bzl/BUILD +++ b/tools/bzl/BUILD
@@ -1,3 +1,5 @@ +load("@rules_shell//shell:sh_test.bzl", "sh_test") + exports_files([ "diff_allowlist.sh", "license-map.py",
diff --git a/tools/bzl/genrule2.bzl b/tools/bzl/genrule2.bzl index d0b0969..4dd7ac1 100644 --- a/tools/bzl/genrule2.bzl +++ b/tools/bzl/genrule2.bzl
@@ -12,18 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Gerrit-specific genrule helpers.""" + # Syntactic sugar for native genrule() rule: # expose ROOT shell variable # expose TMP shell variable -def genrule2(cmd, **kwargs): - cmd = " && ".join([ - "ROOT=$$PWD", - "TMP=$$(mktemp -d || mktemp -d -t bazel-tmp)", - "(" + cmd + ")", - "rm -rf $$TMP", - ]) - native.genrule( - cmd = cmd, - **kwargs - ) +load("@com_googlesource_gerrit_bazlets//tools:genrule2.bzl", _genrule2 = "genrule2") + +def genrule2(*args, **kwargs): + _genrule2(*args, **kwargs)
diff --git a/tools/bzl/js.bzl b/tools/bzl/js.bzl index 0827458..bb5a34a 100644 --- a/tools/bzl/js.bzl +++ b/tools/bzl/js.bzl
@@ -1,153 +1,16 @@ -load("@npm//@bazel/rollup:index.bzl", "rollup_bundle") -load("@npm//@bazel/terser:index.bzl", "terser_minified") -load("//tools/bzl:genrule2.bzl", "genrule2") +"""Gerrit-specific rules for JavaScript.""" -ComponentInfo = provider() - -def _js_component(ctx): - dir = ctx.outputs.zip.path + ".dir" - name = ctx.outputs.zip.basename - if name.endswith(".zip"): - name = name[:-4] - dest = "%s/%s" % (dir, name) - cmd = " && ".join([ - "TZ=UTC", - "export TZ", - "mkdir -p %s" % dest, - "cp %s %s/" % (" ".join([s.path for s in ctx.files.srcs]), dest), - "cd %s" % dir, - "find . -exec touch -t 198001010000 '{}' ';'", - "zip -Xqr ../%s *" % ctx.outputs.zip.basename, - ]) - - ctx.actions.run_shell( - inputs = ctx.files.srcs, - outputs = [ctx.outputs.zip], - command = cmd, - mnemonic = "GenJsComponentZip", - ) - - licenses = [] - if ctx.file.license: - licenses.append(ctx.file.license) - - return [ - ComponentInfo( - transitive_licenses = depset(licenses), - transitive_versions = depset(), - transitive_zipfiles = list([ctx.outputs.zip]), - ), - ] - -js_component = rule( - _js_component, - attrs = { - "srcs": attr.label_list(allow_files = [".js"]), - "license": attr.label(allow_single_file = True), - }, - outputs = { - "zip": "%{name}.zip", - }, +load( + "@com_googlesource_gerrit_bazlets//js:defs.bzl", + _gerrit_js_bundle = "gerrit_js_bundle", + _js_component = "js_component", ) +load("@rules_shell//shell:sh_test.bzl", "sh_test") -def polygerrit_plugin(name, app, plugin_name = None): - """Produces plugin file set with minified javascript. +gerrit_js_bundle = _gerrit_js_bundle +js_component = _js_component - This rule minifies a plugin javascript file, potentially renames it, and produces a file set. - Output of this rule is a FileSet with "${plugin_name}.js". - - Args: - name: String, rule name. - app: String, the main or root source file. This must be single JavaScript file. - plugin_name: String, plugin name. ${name} is used if not provided. - """ - if not plugin_name: - plugin_name = name - - terser_minified( - name = plugin_name + ".min", - sourcemap = False, - src = app, - ) - - native.genrule( - name = name + "_rename_js", - srcs = [plugin_name + ".min"], - outs = [plugin_name + ".js"], - cmd = "cp $< $@", - output_to_bindir = True, - ) - - native.filegroup( - name = name, - srcs = [plugin_name + ".js"], - ) - -def gerrit_js_bundle(name, entry_point, srcs = []): - """Produces a Gerrit JavaScript bundle archive. - - This rule bundles and minifies the javascript files of a frontend plugin and - produces a file archive. - Output of this rule is an archive with "${name}.jar" with specific layout for - Gerrit frontend plugins. That archive should be provided to gerrit_plugin - rule as resource_jars attribute. - - Args: - name: Rule name. - srcs: Plugin sources. - entry_point: Plugin entry_point. - """ - - bundle = name + "-bundle" - minified = name + ".min" - main = name + ".js" - - rollup_bundle( - name = bundle, - srcs = srcs + [ - "@plugins_npm//:node_modules", - ], - args = [ - "--bundleConfigAsCjs=true", - ], - entry_point = entry_point, - format = "iife", - rollup_bin = "//tools/node_tools:rollup-bin", - silent = True, - sourcemap = "hidden", - config_file = "//plugins:rollup.config.js", - deps = [ - "@tools_npm//@rollup/plugin-node-resolve", - ], - ) - - terser_minified( - name = minified, - sourcemap = False, - src = bundle, - ) - - native.genrule( - name = name + "_rename_js", - srcs = [minified], - outs = [main], - cmd = "cp $< $@", - output_to_bindir = True, - ) - - genrule2( - name = name, - srcs = [main], - outs = [name + ".jar"], - cmd = " && ".join([ - "mkdir $$TMP/static", - "cp $(SRCS) $$TMP/static", - "cd $$TMP", - "zip -Drq $$ROOT/$@ -g .", - ]), - ) - -def web_test_runner(name, srcs, data): +def web_test_runner(name, srcs, data, args = []): """Creates a Web Test Runner test target. It can be used both for the main Gerrit js bundle, but also for plugins. So @@ -162,17 +25,21 @@ srcs: The shell script to invoke, where you can set command line arguments for Web Test Runner and its config. data: The bundle of JavaScript files with the tests included. + args: Additional command-line arguments appended after the runner bin + and config paths. Useful for enabling test modes such as + --run-screenshots. """ - native.sh_test( + sh_test( name = name, size = "enormous", srcs = srcs, args = [ "$(location //polygerrit-ui:web_test_runner_bin)", "$(location //polygerrit-ui:web-test-runner.config.mjs)", - ], + ] + args, data = data + [ + "//polygerrit-ui:resultdb-reporter.mjs", "//polygerrit-ui:web_test_runner_bin", "//polygerrit-ui:web-test-runner.config.mjs", ],
diff --git a/tools/bzl/license.bzl b/tools/bzl/license.bzl index f2ae73f..c8d0130 100644 --- a/tools/bzl/license.bzl +++ b/tools/bzl/license.bzl
@@ -1,3 +1,5 @@ +load("@rules_shell//shell:sh_test.bzl", "sh_test") + """This file contains rules to generate and test the license map""" def normalize_target_name(target): @@ -75,7 +77,7 @@ # the license files themselves from this list. expression = 'rdeps(%s, "%s", 1) - rdeps(%s, "%s", 0)' % (target, forbidden, target, forbidden), ) - native.sh_test( + sh_test( name = name, srcs = ["//tools/bzl:test_license.sh"], args = ["$(location :%s)" % txt],
diff --git a/tools/bzl/maven_jar.bzl b/tools/bzl/maven_jar.bzl deleted file mode 100644 index e269632..0000000 --- a/tools/bzl/maven_jar.bzl +++ /dev/null
@@ -1,189 +0,0 @@ -GERRIT = "GERRIT:" - -GERRIT_API = "GERRIT_API:" - -MAVEN_CENTRAL = "MAVEN_CENTRAL:" - -MAVEN_LOCAL = "MAVEN_LOCAL:" - -ECLIPSE = "ECLIPSE:" - -MAVEN_SNAPSHOT = "https://ossrh-staging-api.central.sonatype.com/content/repositories/snapshots" - -SNAPSHOT = "-SNAPSHOT-" - -def _maven_release(ctx, parts): - """induce jar and url name from maven coordinates.""" - if len(parts) not in [3, 4]: - fail('%s:\nexpected id="groupId:artifactId:version[:classifier]"' % - ctx.attr.artifact) - if len(parts) == 4: - group, artifact, version, classifier = parts - file_version = version + "-" + classifier - else: - group, artifact, version = parts - file_version = version - - repository = ctx.attr.repository - - if "-SNAPSHOT-" in version: - start = version.index(SNAPSHOT) - end = start + len(SNAPSHOT) - 1 - - # file version without snapshot constant, but with post snapshot suffix - file_version = version[:start] + version[end:] - - # version without post snapshot suffix - version = version[:end] - - # overwrite the repository with Maven snapshot repository - repository = MAVEN_SNAPSHOT - - jar = artifact.lower() + "-" + file_version - - url = "/".join([ - repository, - group.replace(".", "/"), - artifact, - version, - artifact + "-" + file_version, - ]) - - return jar, url - -# Creates a struct containing the different parts of an artifact's FQN -def _create_coordinates(fully_qualified_name): - parts = fully_qualified_name.split(":") - packaging = None - classifier = None - - if len(parts) == 3: - group_id, artifact_id, version = parts - elif len(parts) == 4: - group_id, artifact_id, version, classifier = parts - elif len(parts) == 5: - group_id, artifact_id, version, packaging, classifier = parts - else: - fail("Invalid fully qualified name for artifact: %s" % fully_qualified_name) - - return struct( - fully_qualified_name = fully_qualified_name, - group_id = group_id, - artifact_id = artifact_id, - packaging = packaging, - classifier = classifier, - version = version, - ) - -def _format_deps(attr, deps): - formatted_deps = "" - if deps: - if len(deps) == 1: - formatted_deps += "%s = [\'%s\']," % (attr, deps[0]) - else: - formatted_deps += "%s = [\n" % attr - for dep in deps: - formatted_deps += " \'%s\',\n" % dep - formatted_deps += " ]," - return formatted_deps - -def _generate_build_files(ctx, binjar, srcjar): - header = "# DO NOT EDIT: automatically generated BUILD file for maven_jar rule %s" % ctx.name - srcjar_attr = "" - if srcjar: - srcjar_attr = 'srcjar = "%s",' % srcjar - contents = """ -{header} -load("@rules_java//java:defs.bzl", "java_import") -package(default_visibility = ['//visibility:public']) -java_import( - name = 'jar', - jars = ['{binjar}'], - {srcjar_attr} - {deps} - {exports} -) -java_import( - name = 'neverlink', - jars = ['{binjar}'], - neverlink = 1, - {deps} - {exports} -) -\n""".format( - srcjar_attr = srcjar_attr, - header = header, - binjar = binjar, - deps = _format_deps("deps", ctx.attr.deps), - exports = _format_deps("exports", ctx.attr.exports), - ) - if srcjar: - contents += """ -java_import( - name = 'src', - jars = ['{srcjar}'], -) -""".format(srcjar = srcjar) - ctx.file("%s/BUILD" % ctx.path("jar"), contents, False) - -def _maven_jar_impl(ctx): - """rule to download a Maven archive.""" - coordinates = _create_coordinates(ctx.attr.artifact) - - name = ctx.name - sha1 = ctx.attr.sha1 - - parts = ctx.attr.artifact.split(":") - - jar, url = _maven_release(ctx, parts) - - binjar = jar + ".jar" - binjar_path = ctx.path("/".join(["jar", binjar])) - binurl = url + ".jar" - - python = ctx.which("python3") - script = ctx.path(ctx.attr._download_script) - - args = [python, script, "-o", binjar_path, "-u", binurl] - if ctx.attr.sha1: - args.extend(["-v", sha1]) - for x in ctx.attr.exclude: - args.extend(["-x", x]) - - out = ctx.execute(args) - - if out.return_code: - fail("failed %s: %s" % (args, out.stderr)) - - srcjar = None - if ctx.attr.src_sha1 or ctx.attr.attach_source: - srcjar = jar + "-src.jar" - srcurl = url - if coordinates.classifier != None: - srcurl = url.replace("-" + coordinates.classifier, "") - srcurl += "-sources.jar" - srcjar_path = ctx.path("jar/" + srcjar) - args = [python, script, "-o", srcjar_path, "-u", srcurl] - if ctx.attr.src_sha1: - args.extend(["-v", ctx.attr.src_sha1]) - out = ctx.execute(args) - if out.return_code: - fail("failed %s: %s" % (args, out.stderr)) - - _generate_build_files(ctx, binjar, srcjar) - -maven_jar = repository_rule( - attrs = { - "artifact": attr.string(mandatory = True), - "attach_source": attr.bool(default = True), - "exclude": attr.string_list(), - "repository": attr.string(default = MAVEN_CENTRAL), - "sha1": attr.string(), - "src_sha1": attr.string(), - "exports": attr.string_list(), - "deps": attr.string_list(), - "_download_script": attr.label(default = Label("//tools:download_file.py")), - }, - local = True, - implementation = _maven_jar_impl, -)
diff --git a/tools/bzl/plugin.bzl b/tools/bzl/plugin.bzl index 399f1d6..24bd3b3 100644 --- a/tools/bzl/plugin.bzl +++ b/tools/bzl/plugin.bzl
@@ -15,7 +15,6 @@ def gerrit_plugin( name, deps = [], - provided_deps = [], srcs = [], resources = [], resource_jars = [], @@ -33,7 +32,6 @@ _gerrit_plugin( name = name, deps = deps, - provided_deps = provided_deps, srcs = srcs, resources = resources, resource_jars = resource_jars,
diff --git a/tools/bzl/war_checks.bzl b/tools/bzl/war_checks.bzl index c121b7f..59acd24 100644 --- a/tools/bzl/war_checks.bzl +++ b/tools/bzl/war_checks.bzl
@@ -1,3 +1,5 @@ +load("@rules_shell//shell:sh_test.bzl", "sh_test") + """Reusable checks for WAR content guardrails.""" def war_jars_allowlist_test(name, war_jars_manifest, allowlist, **kwargs): @@ -9,7 +11,7 @@ allowlist: label of the checked-in allowlist file **kwargs: forwarded to sh_test """ - native.sh_test( + sh_test( name = name, srcs = ["//tools/bzl:diff_allowlist.sh"], args = [
diff --git a/tools/deps.toml b/tools/deps.toml index 75ffd76..6444daf 100644 --- a/tools/deps.toml +++ b/tools/deps.toml
@@ -2,7 +2,7 @@ antlr = "3.5.2" autoValueGson = "1.3.1" bouncyCastle = "1.84" -byteBuddy = "1.18.8" +byteBuddy = "1.18.10" caffeine = "2.9.2" commonmark = "0.24.0" gitiles = "1.6.0"
diff --git a/tools/download_file.py b/tools/download_file.py index 2af2c07..7abbd1b 100755 --- a/tools/download_file.py +++ b/tools/download_file.py
@@ -86,11 +86,11 @@ parser.add_argument('--exclude_java_sources', action='store_true') args = parser.parse_args() -root_dir = args.o +root_dir = path.abspath(path.dirname(args.o)) while root_dir and path.dirname(root_dir) != root_dir: - root_dir, n = path.split(root_dir) - if n == 'WORKSPACE': + if path.exists(path.join(root_dir, 'MODULE.bazel')): break + root_dir = path.dirname(root_dir) redirects = download_properties(root_dir) cache_ent = cache_entry(args)
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py index 3fcc085..6bd0763 100755 --- a/tools/eclipse/project.py +++ b/tools/eclipse/project.py
@@ -24,7 +24,7 @@ MAIN = '//tools/eclipse:classpath' AUTO_COLLECT = '//tools/eclipse:autovalue_classpath_collect' -def JRE(java_vers = '21'): +def JRE(java_vers = '25'): return '/'.join([ 'org.eclipse.jdt.launching.JRE_CONTAINER', 'org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType', @@ -48,7 +48,7 @@ opts.add_argument('-b', '--batch', action='store_true', dest='batch', help='Bazel batch option') opts.add_argument('-j', '--java', action='store', - dest='java', help='Post Java 21') + dest='java', help='Java version to use') opts.add_argument('--bazel', help=('name of the bazel executable. Defaults to using' ' bazelisk if found, or bazel if bazelisk is not'
diff --git a/tools/gjf.sh b/tools/gjf.sh index 0209d7e..b346032 100755 --- a/tools/gjf.sh +++ b/tools/gjf.sh
@@ -90,6 +90,10 @@ sha1="3b55f08a70d53984ac4b3e7796dc992858d6bdd8" tag_prefix=v ;; + 1.35.0) + sha1="c8e59165e3c971509d98018b98bf4f85a56a0ff9" + tag_prefix=v + ;; *) echo "unknown google-java-format version: $version" exit 1 @@ -152,7 +156,7 @@ # MAIN -SUPPORTED_VERSIONS=(1.7 1.22.0 1.24.0) +SUPPORTED_VERSIONS=(1.7 1.22.0 1.24.0 1.35.0) HELP_TEXT=" Usage: @@ -164,8 +168,8 @@ " -# Keep the default version in sync with dev-contributing.txt. -DEFAULT_VERSION="1.24.0" +# Keep the default version in sync with dev-crafting-changes.txt. +DEFAULT_VERSION="1.35.0" VERSION=${2:-$DEFAULT_VERSION} verify_version $VERSION
diff --git a/tools/js/BUILD b/tools/js/BUILD index d696496..e69de29 100644 --- a/tools/js/BUILD +++ b/tools/js/BUILD
@@ -1 +0,0 @@ -exports_files(["eslint-chdir.js"])
diff --git a/tools/js/eslint-chdir.js b/tools/js/eslint-chdir.js deleted file mode 100644 index d9fd479..0000000 --- a/tools/js/eslint-chdir.js +++ /dev/null
@@ -1,132 +0,0 @@ -/** - * @license - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// ESLint resolves relative paths from the current working directory. -// -// In workspace mode (`lint_bin`), this works with the regular Node.js module -// layout under `polygerrit-ui/app/node_modules`. -// -// In Bazel test mode (`lint_test`), npm dependencies are exposed through -// multiple runfiles trees instead. Typed linting with TypeScript does not see -// the same module and type environment from that layout as from the workspace -// layout. -// -// To align `lint_test` with `lint_bin`, synthesize a local -// `polygerrit-ui/app/node_modules` by symlinking entries from the runfiles -// npm trees, then prepend it to NODE_PATH so resolution matches workspace mode. - -const fs = require('fs'); -const Module = require('module'); -const path = require('path'); - -function pathExists(filePath) { - try { - return fs.existsSync(filePath); - } catch { - return false; - } -} - -function readDirEntries(dirPath) { - try { - return fs.readdirSync(dirPath, {withFileTypes: true}); - } catch { - return []; - } -} - -function ensureDir(dirPath) { - fs.mkdirSync(dirPath, {recursive: true}); -} - -function symlinkIfMissing(targetPath, linkPath) { - if (pathExists(linkPath)) return; - - try { - fs.symlinkSync(targetPath, linkPath); - } catch { - // Ignore races and pre-existing entries. - } -} - -function mergeNodeModules(destinationDir, sourceDirs) { - ensureDir(destinationDir); - - for (const sourceDir of sourceDirs) { - for (const entry of readDirEntries(sourceDir)) { - const sourceEntry = path.join(sourceDir, entry.name); - const destEntry = path.join(destinationDir, entry.name); - - if (entry.name.startsWith('@') && entry.isDirectory()) { - ensureDir(destEntry); - - for (const scopedEntry of readDirEntries(sourceEntry)) { - symlinkIfMissing( - path.join(sourceEntry, scopedEntry.name), - path.join(destEntry, scopedEntry.name) - ); - } - continue; - } - - symlinkIfMissing(sourceEntry, destEntry); - } - } -} - -function getRunfilesRoot() { - return process.env.RUNFILES_DIR || process.env.TEST_SRCDIR || ''; -} - -function getRunfilesNodeModules(runfilesRoot) { - return [ - path.join(runfilesRoot, 'ui_dev_npm/node_modules'), - path.join(runfilesRoot, 'ui_npm/node_modules'), - path.join(runfilesRoot, '_main/node_modules'), - ].filter(pathExists); -} - -function prependNodePath(paths) { - const existing = process.env.NODE_PATH - ? process.env.NODE_PATH.split(path.delimiter).filter(Boolean) - : []; - - process.env.NODE_PATH = [...paths, ...existing].join(path.delimiter); - Module._initPaths(); -} - -function getConfigDirFromArgv(argv) { - const configArgIndex = argv.findIndex(arg => arg === '-c' || arg === '--config'); - if (configArgIndex < 0 || configArgIndex + 1 >= argv.length) return ''; - - return path.dirname(argv[configArgIndex + 1]); -} - -const configDir = getConfigDirFromArgv(process.argv); -if (!configDir) return; - -process.chdir(configDir); - -const runfilesRoot = getRunfilesRoot(); -if (!runfilesRoot) return; - -const runfilesNodeModules = getRunfilesNodeModules(runfilesRoot); -if (runfilesNodeModules.length === 0) return; - -const localNodeModules = path.join(process.cwd(), 'node_modules'); -mergeNodeModules(localNodeModules, runfilesNodeModules); -prependNodePath([localNodeModules, ...runfilesNodeModules]);
diff --git a/tools/js/eslint.bzl b/tools/js/eslint.bzl index 91090d2..737d01f 100644 --- a/tools/js/eslint.bzl +++ b/tools/js/eslint.bzl
@@ -14,7 +14,7 @@ """This file contains macro to run eslint and define a eslint test rule.""" -load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary", "nodejs_test") +load("@npm//:eslint/package_json.bzl", eslint_bin = "bin") def plugin_eslint(): """ Convenience wrapper macro of eslint() for Gerrit js plugins @@ -28,22 +28,20 @@ config = "eslint.config.js", data = [ "tsconfig.json", - "//plugins:eslint.config.js", - "//plugins:.prettierrc.js", - "//plugins:tsconfig-plugins-base.json", - "@npm//typescript", + "//plugins:plugins-config-lib", + "//:node_modules/typescript", ], extensions = [".ts"], plugins = [ - "@npm//eslint-config-google", - "@npm//eslint-plugin-html", - "@npm//eslint-plugin-import", - "@npm//eslint-plugin-jsdoc", - "@npm//eslint-plugin-lit", - "@npm//eslint-plugin-n", - "@npm//eslint-plugin-prettier", - "@npm//eslint-plugin-regex", - "@npm//gts", + "//:node_modules/eslint-config-google", + "//:node_modules/eslint-plugin-html", + "//:node_modules/eslint-plugin-import", + "//:node_modules/eslint-plugin-jsdoc", + "//:node_modules/eslint-plugin-lit", + "//:node_modules/eslint-plugin-n", + "//:node_modules/eslint-plugin-prettier", + "//:node_modules/eslint-plugin-regex", + "//:node_modules/gts", ], ) @@ -52,8 +50,8 @@ Args: name: name of the rule - plugins: list of npm dependencies with plugins, for example "@npm//eslint-config-google" - srcs: list of files to be checked (ignored in {name}_bin rule) + plugins: list of npm dependencies with plugins, for example "//:node_modules/eslint-config-google" + srcs: list of files to be checked config: eslint config file size: eslint test size, supported values are: small, medium, large and enormous, with implied timeout labels: short, moderate, long, and eternal @@ -67,35 +65,40 @@ {name}_test rule - runs eslint tests. You can run this rule with 'bazel test {name}_test' command. The rule tests all files from srcs with specified extensions inside the package where eslint macro is called. - {name}_bin rule - runs eslint with specified settings; ignores srcs. To use this rule - you must pass a folder to check, for example: - bazel run {name}_test -- --fix $(pwd)/polygerrit-ui/app - """ - entry_point = "@npm//:node_modules/eslint/bin/eslint.js" + {name}_bin rule - runs eslint with specified settings. To use this rule + pass repo-root-relative or absolute paths, for example: + bazel run //polygerrit-ui/app:lint_bin -- polygerrit-ui/app + bazel run //polygerrit-ui/app:lint_bin -- \\ + polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts + Note: {name}_bin is intended for read-only linting. Do not use it with '--fix', + because ESLint runs on Bazel runfiles paths, which are not writable. + """ + + #TODO(Thomas): Use rules_lint bin_data = [ - "@npm//eslint:eslint", config, - "//tools/js:eslint-chdir.js", + "//:node_modules/@eslint/eslintrc", + "//:node_modules/@eslint/js", + "//:node_modules/eslint", ] + plugins + data + common_templated_args = [ - "--node_options=--require=$$(rlocation $(rootpath //tools/js:eslint-chdir.js))", "--ext", ",".join(extensions), - "-c", - # Use rlocation/rootpath instead of location. - # See note and example here: - # https://bazelbuild.github.io/rules_nodejs/Built-ins.html#nodejs_binary - "$$(rlocation $(rootpath {}))".format(config), ] - nodejs_test( + + eslint_bin.eslint_test( name = name + "_test", - entry_point = entry_point, data = bin_data + srcs, + chdir = native.package_name(), # Bazel generates 2 .js files, where names of the files are generated from the name # of the rule: {name}_test_require_patch.js and {name}_test_loader.js # Ignore these 2 files, for simplicity do not use {name} in the patterns. - templated_args = common_templated_args + [ + expand_args = True, + fixed_args = common_templated_args + [ + "-c", + config, "--ignore-pattern", "*_test_require_patch.js", "--ignore-pattern", @@ -112,14 +115,24 @@ size = size, ) - nodejs_binary( + # Run from the workspace root so repo-root-relative paths remain inside ESLint's + # effective base path. Running from the package directory breaks linting of paths + # outside that directory with flat config: + # https://github.com/eslint/eslint/issues/19118 + # + # Note that this rule is for read-only linting only. '--fix' is not supported here, + # because ESLint operates on Bazel runfiles paths, which are not writable. + eslint_bin.eslint_binary( name = name + "_bin", - entry_point = "@npm//:node_modules/eslint/bin/eslint.js", - data = bin_data, + data = bin_data + srcs, + chdir = "", # Bazel generates 2 .js files, where names of the files are generated from the name # of the rule: {name}_bin_require_patch.js and {name}_bin_loader.js # Ignore these 2 files, for simplicity do not use {name} in the patterns. - templated_args = common_templated_args + [ + expand_args = True, + fixed_args = common_templated_args + [ + "-c", + native.package_name() + "/" + config, "--ignore-pattern", "*_bin_require_patch.js", "--ignore-pattern",
diff --git a/tools/maven/gerrit-acceptance-framework_pom.xml b/tools/maven/gerrit-acceptance-framework_pom.xml index 9cfaf84..b8cf89c 100644 --- a/tools/maven/gerrit-acceptance-framework_pom.xml +++ b/tools/maven/gerrit-acceptance-framework_pom.xml
@@ -2,7 +2,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>com.google.gerrit</groupId> <artifactId>gerrit-acceptance-framework</artifactId> - <version>3.14.2-SNAPSHOT</version> + <version>3.15.0-SNAPSHOT</version> <packaging>jar</packaging> <name>Gerrit Code Review - Acceptance Test Framework</name> <description>Framework for Gerrit's acceptance tests</description>
diff --git a/tools/maven/gerrit-extension-api_pom.xml b/tools/maven/gerrit-extension-api_pom.xml index 8baecef..16a13ab 100644 --- a/tools/maven/gerrit-extension-api_pom.xml +++ b/tools/maven/gerrit-extension-api_pom.xml
@@ -2,7 +2,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>com.google.gerrit</groupId> <artifactId>gerrit-extension-api</artifactId> - <version>3.14.2-SNAPSHOT</version> + <version>3.15.0-SNAPSHOT</version> <packaging>jar</packaging> <name>Gerrit Code Review - Extension API</name> <description>API for Gerrit Extensions</description>
diff --git a/tools/maven/gerrit-plugin-api_pom.xml b/tools/maven/gerrit-plugin-api_pom.xml index 002bdb2..d78532d 100644 --- a/tools/maven/gerrit-plugin-api_pom.xml +++ b/tools/maven/gerrit-plugin-api_pom.xml
@@ -2,7 +2,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>com.google.gerrit</groupId> <artifactId>gerrit-plugin-api</artifactId> - <version>3.14.2-SNAPSHOT</version> + <version>3.15.0-SNAPSHOT</version> <packaging>jar</packaging> <name>Gerrit Code Review - Plugin API</name> <description>API for Gerrit Plugins</description>
diff --git a/tools/maven/gerrit-war_pom.xml b/tools/maven/gerrit-war_pom.xml index 4c20969..4a04f28 100644 --- a/tools/maven/gerrit-war_pom.xml +++ b/tools/maven/gerrit-war_pom.xml
@@ -2,7 +2,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>com.google.gerrit</groupId> <artifactId>gerrit-war</artifactId> - <version>3.14.2-SNAPSHOT</version> + <version>3.15.0-SNAPSHOT</version> <packaging>war</packaging> <name>Gerrit Code Review - WAR</name> <description>Gerrit WAR</description>
diff --git a/tools/node_tools/BUILD b/tools/node_tools/BUILD index a36d3f8..22e016d 100644 --- a/tools/node_tools/BUILD +++ b/tools/node_tools/BUILD
@@ -1,52 +1,3 @@ -load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") +load("@tools_npm//:defs.bzl", "npm_link_all_packages") -package(default_visibility = ["//visibility:public"]) - -# By default, rollup_bundle rule uses rollup from @npm workspace -# and it expects that all plugins are installed in the same workspace. -# This rule defines another rollup-bin from @tools_npm workspace. -# Usage: rollup_bundle(rollup_bin = "//tools/node_tools:rollup-bin, ...) -nodejs_binary( - name = "rollup-bin", - # Define only minimal required dependencies. - # Otherwise remote build execution fails with the too many - # files error when it builds :release target. - data = [ - "@tools_npm//@rollup/plugin-terser", - "@tools_npm//rollup", - ], - # The entry point must be "@tools_npm:node_modules/rollup/dist/bin/rollup", - # But bazel doesn't run it correctly with the following command line: - # bazel test --test_env=GERRIT_NOTEDB=ON --spawn_strategy=standalone \ - # --genrule_strategy=standalone --test_output errors --test_summary detailed \ - # --flaky_test_attempts 3 --test_verbose_timeout_warnings --build_tests_only \ - # --subcommands //... - # This command line appears in Gerrit CI. - # For details, see comment in rollup-runner.js file - entry_point = "//tools/node_tools:rollup-runner.js", -) - -# Create a tsc_wrapped compiler rule to use in the ts_library -# compiler attribute when using self-managed dependencies -# TODO: Would be nice to just use `tsc-bin` below instead. -# We would prefer to not depend on @bazel/concatjs ... -nodejs_binary( - name = "tsc_wrapped-bin", - # Point bazel to your node_modules to find the entry point - data = [ - "@tools_npm//@bazel/concatjs", - "@tools_npm//typescript", - ], - # It seems, bazel uses different approaches to compile ts files (it runs some - # ts service in background). It works without any workaround. - entry_point = "@tools_npm//:node_modules/@bazel/concatjs/internal/tsc_wrapped/tsc_wrapped.js", -) - -# Wrap a typescript into a tsc-bin binary. -# The tsc-bin can be used as a tool to compile typescript code. -nodejs_binary( - name = "tsc-bin", - # Point bazel to your node_modules to find the entry point - data = ["@tools_npm//typescript"], - entry_point = "@tools_npm//:node_modules/typescript/lib/tsc.js", -) +npm_link_all_packages(name = "node_modules")
diff --git a/tools/node_tools/node_modules_licenses/BUILD b/tools/node_tools/node_modules_licenses/BUILD index f80b3ee..c9fb69a 100644 --- a/tools/node_tools/node_modules_licenses/BUILD +++ b/tools/node_tools/node_modules_licenses/BUILD
@@ -1,47 +1,39 @@ -load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") -load("@npm//@bazel/concatjs:index.bzl", "ts_library") -load("@npm//@bazel/rollup:index.bzl", "rollup_bundle") +load("@aspect_rules_js//js:defs.bzl", "js_binary") +load("@aspect_rules_ts//ts:defs.bzl", "ts_project") +load("@aspect_rules_rollup//rollup:defs.bzl", "rollup") package(default_visibility = ["//visibility:public"]) -# TODO: Would be nice to use `ts_project` from @bazel/typescript instead. -# We would prefer to not depend on @bazel/concatjs ... -ts_library( +ts_project( name = "licenses-map", srcs = glob(["*.ts"]), - compiler = "//tools/node_tools:tsc_wrapped-bin", + declaration = True, tsconfig = "tsconfig.json", deps = [ - "@tools_npm//:node_modules", + "//tools/node_tools:node_modules", ], ) -# rollup_bundle - workaround for https://github.com/bazelbuild/rules_nodejs/issues/1522 -# The ts_library rule ("license-map") transpiles each .ts file to .js file. -# The "license-map" rule includes multiple .ts files and produces multiple output files. -# The nodejs_binary requires only one file as an entry_point. It is expected, that other -# .js files from ts_library are also available, but because of the bug they are not available. -# As a workaround we are using rollup_bundle to group all files together. -rollup_bundle( +rollup( name = "license-map-generator-bundle", args = [ "--bundleConfigAsCjs=true", ], config_file = "rollup.config.js", - entry_point = "license-map-generator.ts", + entry_point = "license-map-generator.js", format = "cjs", - rollup_bin = "//tools/node_tools:rollup-bin", + node_modules = "//tools/node_tools:node_modules", silent = True, deps = [ ":licenses-map", - "@tools_npm//@rollup/plugin-node-resolve", - "@tools_npm//rollup", + "//tools/node_tools:node_modules/@rollup/plugin-node-resolve", + "//tools/node_tools:node_modules/rollup", ], ) -nodejs_binary( +js_binary( name = "license-map-generator-bin", - entry_point = "license-map-generator-bundle.js", + entry_point = ":license-map-generator-bundle", ) # (TODO)dmfilippov Find a better way to fix it (another workaround or submit a bug to
diff --git a/tools/node_tools/node_modules_licenses/installed-node-modules-map.ts b/tools/node_tools/node_modules_licenses/installed-node-modules-map.ts index 49beda3..bdd9a5f 100644 --- a/tools/node_tools/node_modules_licenses/installed-node-modules-map.ts +++ b/tools/node_tools/node_modules_licenses/installed-node-modules-map.ts
@@ -15,7 +15,7 @@ * limitations under the License. */ -import { PackageName, PackageVersion, DirPath, FilePath } from "./base-types"; +import {PackageName, PackageVersion, DirPath, FilePath} from "./base-types"; import {fail} from "./utils"; import * as path from "path"; import * as fs from "fs"; @@ -42,19 +42,22 @@ * It is expected, that the addPackageJson method is called first for * all package.json files first and then the addFile method is called for all files (including package.json) */ -export class InsalledPackagesBuilder { +export class InstalledPackagesBuilder { private readonly rootPathToPackageMap: Map<DirPath, InstalledPackage> = new Map(); - public constructor(private readonly nonPackages: Set<string>) { - } + public constructor(private readonly nonPackages: Set<string>) {} public addPackageJson(packageJsonPath: string) { const pack = this.createInstalledPackage(packageJsonPath); if (!pack) return; - this.rootPathToPackageMap.set(pack.rootPath, pack) + this.rootPathToPackageMap.set(pack.rootPath, pack); } + public addFile(file: string) { - const pack = this.findPackageForFile(file)!; + const pack = file.includes("@file+") + ? this.findLocalPackageForFile(file) + : this.findPackageForFile(file); + pack.files.push(path.relative(pack.rootPath, file)); } @@ -68,42 +71,89 @@ const nameParts: Array<string> = []; const rootPath = path.dirname(packageJsonFile); let currentDir = rootPath; - while(currentDir != "") { + while (currentDir !== "") { const partName = path.basename(currentDir); - if(partName === "node_modules") { + if (partName === "node_modules") { const packageName = nameParts.reverse().join("/"); - const version = JSON.parse(fs.readFileSync(packageJsonFile, {encoding: 'utf-8'}))["version"]; - if(!version) { + const version = JSON.parse( + fs.readFileSync(packageJsonFile, {encoding: "utf-8"}) + )["version"]; + if (!version) { if (this.nonPackages.has(packageName)) { return undefined; } - fail(`Can't get version for ${packageJsonFile}`) + fail(`Can't get version for ${packageJsonFile}`); } return { name: packageName, rootPath: rootPath, version: version, - files: [] + files: [], }; } nameParts.push(partName); currentDir = path.dirname(currentDir); } - fail(`Can't create package info for '${packageJsonFile}'`) + fail(`Can't create package info for '${packageJsonFile}'`); } private findPackageForFile(filePath: FilePath): InstalledPackage { let currentDir = path.dirname(filePath); - while(currentDir != "") { - if(this.rootPathToPackageMap.has(currentDir)) { + + while (true) { + if (this.rootPathToPackageMap.has(currentDir)) { return this.rootPathToPackageMap.get(currentDir)!; } - currentDir = path.dirname(currentDir); + + const nextDir = path.dirname(currentDir); + if (nextDir === currentDir) { + break; + } + currentDir = nextDir; } + fail(`Can't find package for '${filePath}'`); } + private findLocalPackageForFile(filePath: FilePath): InstalledPackage { + let currentDir = path.dirname(filePath); + currentDir = this.getPackagePathRelativeToNodeModules(currentDir); + + const parts = currentDir.split("/"); + const pack = + parts[0].startsWith("@") && parts.length > 1 + ? `${parts[0]}/${parts[1]}` + : parts[0]; + + if (this.rootPathToPackageMap.has(pack)) { + return this.rootPathToPackageMap.get(pack)!; + } + + const packIndex = filePath.lastIndexOf(pack); + if (packIndex === -1) { + fail(`Can't determine package root for '${filePath}'`); + } + + const installedPack: InstalledPackage = { + name: pack, + version: "Snapshot", + rootPath: filePath.substring(0, packIndex + pack.length), + files: [], + }; + this.rootPathToPackageMap.set(pack, installedPack); + return installedPack; + } + public build(): InstalledPackage[] { return [...this.rootPathToPackageMap.values()]; } + + private getPackagePathRelativeToNodeModules(filePath: string): string { + const nodeModulesPathPart = "/node_modules/"; + const index = filePath.lastIndexOf(nodeModulesPathPart); + if (index === -1) { + fail(`Path does not contain '${nodeModulesPathPart}': '${filePath}'`); + } + return filePath.substring(index + nodeModulesPathPart.length); + } }
diff --git a/tools/node_tools/node_modules_licenses/license-map-generator.ts b/tools/node_tools/node_modules_licenses/license-map-generator.ts index c0c2315..fd31fea 100644 --- a/tools/node_tools/node_modules_licenses/license-map-generator.ts +++ b/tools/node_tools/node_modules_licenses/license-map-generator.ts
@@ -54,12 +54,24 @@ packages: PackageInfo[]; } +function resolveExecrootPath(filePath: string): string { + if (path.isAbsolute(filePath)) return filePath; + + const bazelBindir = process.env["BAZEL_BINDIR"]; + if (bazelBindir && process.cwd().endsWith(bazelBindir)) { + const execroot = process.cwd().slice(0, process.cwd().length - bazelBindir.length); + return path.join(execroot, filePath); + } + + return path.join(process.cwd(), filePath); +} + function parseArguments(argv: string[]): LicenseMapCommandLineArgs { if(argv.length < 6) { fail("Invalid command line parameters\n" + "\tUsage:\n\tnode license-map-generator config.js node-modules-files.txt shared-licenses.txt json-output.json"); } - const packages: PackageInfo[] = require(path.join(process.cwd(), argv[2])).default; + const packages: PackageInfo[] = require(resolveExecrootPath(argv[2])).default; const nodeModulesFiles = readMultilineParamFile(argv[3]); const sharedLicensesFiles = readMultilineParamFile(argv[4]); @@ -69,16 +81,21 @@ nodeModulesFiles, sharedLicensesFiles, }, - outputJsonPath: argv[5] - } + outputJsonPath: resolveExecrootPath(argv[5]), + }; } function main() { const args = parseArguments(process.argv); - const generator = new LicenseMapGenerator(args.generatorParams.packages, new SharedLicensesProvider(args.generatorParams.sharedLicensesFiles)); + const generator = new LicenseMapGenerator( + args.generatorParams.packages, + new SharedLicensesProvider(args.generatorParams.sharedLicensesFiles) + ); const licenseMap = generator.generateMap(args.generatorParams.nodeModulesFiles); // JSON is quite small, so there are no reasons to minify it. // Write it as multiline file with tabs (spaces). + + fs.mkdirSync(path.dirname(args.outputJsonPath), {recursive: true}); fs.writeFileSync(args.outputJsonPath, JSON.stringify(licenseMap, null, 2), "utf-8"); }
diff --git a/tools/node_tools/node_modules_licenses/licenses-map.ts b/tools/node_tools/node_modules_licenses/licenses-map.ts index b63685d..55fb004 100644 --- a/tools/node_tools/node_modules_licenses/licenses-map.ts +++ b/tools/node_tools/node_modules_licenses/licenses-map.ts
@@ -19,7 +19,7 @@ import * as fs from "fs"; import {isSharedFileLicenseInfo, LicenseInfo, PackageInfo} from "./package-license-info"; import {LicenseName, PackageName, FilePath, PackageVersion} from "./base-types"; -import { InstalledPackage, InsalledPackagesBuilder } from "./installed-node-modules-map"; +import { InstalledPackage, InstalledPackagesBuilder } from "./installed-node-modules-map"; import {SharedLicensesProvider} from "./shared-licenses-provider"; import {fail} from "./utils"; @@ -225,7 +225,7 @@ fullNonPackageNames.push(...p.nonPackages.map(name => `${p.name}/${name}`)); } } - const builder = new InsalledPackagesBuilder(new Set(fullNonPackageNames)); + const builder = new InstalledPackagesBuilder(new Set(fullNonPackageNames)); // Register all package.json files - such files exists in the root folder of each module let packageJsonFiles = nodeModulesFiles.filter(f => path.basename(f) === "package.json"); // The `safevalue` node module has an unusual setup: It also includes (meaningless) package.json
diff --git a/tools/node_tools/node_modules_licenses/node_modules_licenses.bzl b/tools/node_tools/node_modules_licenses/node_modules_licenses.bzl deleted file mode 100644 index 64c8b79..0000000 --- a/tools/node_tools/node_modules_licenses/node_modules_licenses.bzl +++ /dev/null
@@ -1,42 +0,0 @@ -"""This file contains the rule to generate a license map for node modules""" - -def _node_modules_licenses_impl(ctx): - """Wrapper for the license-map-generator command-line tool""" - - node_modules_args = ctx.actions.args() - node_modules_args.add_all(ctx.files.node_modules) - node_modules_args.use_param_file("%s", use_always = True) - - licenses_texts_args = ctx.actions.args() - licenses_texts_args.add_all(ctx.files.licenses_texts) - licenses_texts_args.use_param_file("%s", use_always = True) - - ctx.actions.run( - executable = ctx.executable._license_map_generator, - arguments = [ctx.file.licenses_config.path, node_modules_args, licenses_texts_args, ctx.outputs.json.path], - outputs = [ctx.outputs.json], - inputs = depset([ctx.file.licenses_config] + ctx.files.licenses_texts, transitive = [ctx.attr.node_modules.files]), - ) - -# Rule to run license-map-generator.ts -# node_modules - label of npm workspace in the format @npm//:node_modules -# The output contains information about licenses for the workspace. -# licenses_texts is a list of shared licenses -# For details - see comments in the -# tools/node_tools/node_modules_licenses/license-map-generator.ts file -node_modules_licenses = rule( - implementation = _node_modules_licenses_impl, - attrs = { - "node_modules": attr.label(mandatory = True), - "licenses_texts": attr.label_list(allow_files = True, mandatory = True), - "licenses_config": attr.label(allow_single_file = True, mandatory = True), - "_license_map_generator": attr.label( - default = Label("//tools/node_tools/node_modules_licenses:license-map-generator-bin"), - executable = True, - cfg = "host", - ), - }, - outputs = { - "json": "%{name}.json", - }, -)
diff --git a/tools/node_tools/node_modules_licenses/tsconfig.json b/tools/node_tools/node_modules_licenses/tsconfig.json index 9a76949..3301307 100644 --- a/tools/node_tools/node_modules_licenses/tsconfig.json +++ b/tools/node_tools/node_modules_licenses/tsconfig.json
@@ -8,14 +8,17 @@ ] } ], + "declaration": true, "target": "es2021", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ "module": "es2020", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ "allowSyntheticDefaultImports": true, "esModuleInterop": true, "strict": true, "moduleResolution": "node", - "outDir": "../../../.ts-out/tools/node_modules_licenses", // Not used in bazel, - "types": ["node"] + "types": ["node"], + "typeRoots": [ + "../node_modules/@types", + ], }, "include": ["*.ts"] }
diff --git a/tools/node_tools/node_modules_licenses/utils.ts b/tools/node_tools/node_modules_licenses/utils.ts index e73bf96..555571a 100644 --- a/tools/node_tools/node_modules_licenses/utils.ts +++ b/tools/node_tools/node_modules_licenses/utils.ts
@@ -15,6 +15,7 @@ * limitations under the License. */ +import * as path from "path"; import * as fs from "fs"; export function fail(message: string): never { @@ -28,9 +29,22 @@ str.slice(1, -1) : str; } -export function readMultilineParamFile(path: string): string[] { - return fs.readFileSync(path, {encoding: 'utf-8'}) +function resolveExecrootPath(filePath: string): string { + if (path.isAbsolute(filePath)) return filePath; + + const bazelBindir = process.env["BAZEL_BINDIR"]; + if (bazelBindir && process.cwd().endsWith(bazelBindir)) { + const execroot = process.cwd().slice(0, process.cwd().length - bazelBindir.length); + return path.join(execroot, filePath); + } + + return path.join(process.cwd(), filePath); +} + +export function readMultilineParamFile(filePath: string): string[] { + return fs.readFileSync(resolveExecrootPath(filePath), {encoding: 'utf-8'}) .split(/\r?\n/) .filter(f => f.length > 0) - .map(removeSurrondedQuotes); + .map(removeSurrondedQuotes) + .map(resolveExecrootPath); }
diff --git a/tools/node_tools/package.json b/tools/node_tools/package.json index 4016d1f..42b4759 100644 --- a/tools/node_tools/package.json +++ b/tools/node_tools/package.json
@@ -3,14 +3,7 @@ "description": "Gerrit Build Tools", "browser": false, "dependencies": { - "@bazel/rollup": "^5.8.1", - "@bazel/typescript": "^5.8.1", - "@bazel/concatjs": "^5.8.1", "@types/node": "^22.15.2", - "@types/parse5": "^4.0.0", - "@types/parse5-html-rewriting-stream": "^5.1.2", - "dom5": "^3.0.1", - "parse5-html-rewriting-stream": "^5.1.1", "rollup": "^4.40.0", "@rollup/plugin-commonjs": "^28.0.3", "@rollup/plugin-replace": "^6.0.2", @@ -22,4 +15,4 @@ "devDependencies": {}, "license": "Apache-2.0", "private": true -} \ No newline at end of file +}
diff --git a/tools/node_tools/pnpm-lock.yaml b/tools/node_tools/pnpm-lock.yaml new file mode 100644 index 0000000..9a5aa77 --- /dev/null +++ b/tools/node_tools/pnpm-lock.yaml
@@ -0,0 +1,573 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@rollup/plugin-commonjs': + specifier: ^28.0.3 + version: 28.0.3(rollup@4.40.2) + '@rollup/plugin-node-resolve': + specifier: ^16.0.1 + version: 16.0.1(rollup@4.40.2) + '@rollup/plugin-replace': + specifier: ^6.0.2 + version: 6.0.2(rollup@4.40.2) + '@rollup/plugin-terser': + specifier: ^0.4.4 + version: 0.4.4(rollup@4.40.2) + '@types/node': + specifier: ^22.15.2 + version: 22.15.18 + rollup: + specifier: ^4.40.0 + version: 4.40.2 + terser: + specifier: ~5.39.0 + version: 5.39.2 + typescript: + specifier: ^5.8.3 + version: 5.8.3 + +packages: + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.6': + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@rollup/plugin-commonjs@28.0.3': + resolution: {integrity: sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==} + engines: {node: '>=16.0.0 || 14 >= 14.17'} + peerDependencies: + rollup: ^2.68.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-node-resolve@16.0.1': + resolution: {integrity: sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-replace@6.0.2': + resolution: {integrity: sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-terser@0.4.4': + resolution: {integrity: sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@5.1.4': + resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.40.2': + resolution: {integrity: sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.40.2': + resolution: {integrity: sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.40.2': + resolution: {integrity: sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.40.2': + resolution: {integrity: sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.40.2': + resolution: {integrity: sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.40.2': + resolution: {integrity: sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.40.2': + resolution: {integrity: sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.40.2': + resolution: {integrity: sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.40.2': + resolution: {integrity: sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.40.2': + resolution: {integrity: sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loongarch64-gnu@4.40.2': + resolution: {integrity: sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-powerpc64le-gnu@4.40.2': + resolution: {integrity: sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.40.2': + resolution: {integrity: sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.40.2': + resolution: {integrity: sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.40.2': + resolution: {integrity: sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.40.2': + resolution: {integrity: sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.40.2': + resolution: {integrity: sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-win32-arm64-msvc@4.40.2': + resolution: {integrity: sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.40.2': + resolution: {integrity: sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.40.2': + resolution: {integrity: sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + + '@types/node@22.15.18': + resolution: {integrity: sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg==} + + '@types/resolve@1.20.2': + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + engines: {node: '>=0.4.0'} + hasBin: true + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + fdir@6.4.4: + resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + + is-reference@1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + rollup@4.40.2: + resolution: {integrity: sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + smob@1.5.0: + resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + terser@5.39.2: + resolution: {integrity: sha512-yEPUmWve+VA78bI71BW70Dh0TuV4HHd+I5SHOAfS1+QBOmvmCiiffgjR8ryyEd3KIfvPGFqoADt8LdQ6XpXIvg==} + engines: {node: '>=10'} + hasBin: true + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + +snapshots: + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/source-map@0.3.6': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@rollup/plugin-commonjs@28.0.3(rollup@4.40.2)': + dependencies: + '@rollup/pluginutils': 5.1.4(rollup@4.40.2) + commondir: 1.0.1 + estree-walker: 2.0.2 + fdir: 6.4.4(picomatch@4.0.2) + is-reference: 1.2.1 + magic-string: 0.30.17 + picomatch: 4.0.2 + optionalDependencies: + rollup: 4.40.2 + + '@rollup/plugin-node-resolve@16.0.1(rollup@4.40.2)': + dependencies: + '@rollup/pluginutils': 5.1.4(rollup@4.40.2) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-module: 1.0.0 + resolve: 1.22.10 + optionalDependencies: + rollup: 4.40.2 + + '@rollup/plugin-replace@6.0.2(rollup@4.40.2)': + dependencies: + '@rollup/pluginutils': 5.1.4(rollup@4.40.2) + magic-string: 0.30.17 + optionalDependencies: + rollup: 4.40.2 + + '@rollup/plugin-terser@0.4.4(rollup@4.40.2)': + dependencies: + serialize-javascript: 6.0.2 + smob: 1.5.0 + terser: 5.39.2 + optionalDependencies: + rollup: 4.40.2 + + '@rollup/pluginutils@5.1.4(rollup@4.40.2)': + dependencies: + '@types/estree': 1.0.7 + estree-walker: 2.0.2 + picomatch: 4.0.2 + optionalDependencies: + rollup: 4.40.2 + + '@rollup/rollup-android-arm-eabi@4.40.2': + optional: true + + '@rollup/rollup-android-arm64@4.40.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.40.2': + optional: true + + '@rollup/rollup-darwin-x64@4.40.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.40.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.40.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.40.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.40.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.40.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.40.2': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.40.2': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.40.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.40.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.40.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.40.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.40.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.40.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.40.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.40.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.40.2': + optional: true + + '@types/estree@1.0.7': {} + + '@types/node@22.15.18': + dependencies: + undici-types: 6.21.0 + + '@types/resolve@1.20.2': {} + + acorn@8.14.1: {} + + buffer-from@1.1.2: {} + + commander@2.20.3: {} + + commondir@1.0.1: {} + + deepmerge@4.3.1: {} + + estree-walker@2.0.2: {} + + fdir@6.4.4(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-module@1.0.0: {} + + is-reference@1.2.1: + dependencies: + '@types/estree': 1.0.7 + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + path-parse@1.0.7: {} + + picomatch@4.0.2: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + rollup@4.40.2: + dependencies: + '@types/estree': 1.0.7 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.40.2 + '@rollup/rollup-android-arm64': 4.40.2 + '@rollup/rollup-darwin-arm64': 4.40.2 + '@rollup/rollup-darwin-x64': 4.40.2 + '@rollup/rollup-freebsd-arm64': 4.40.2 + '@rollup/rollup-freebsd-x64': 4.40.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.40.2 + '@rollup/rollup-linux-arm-musleabihf': 4.40.2 + '@rollup/rollup-linux-arm64-gnu': 4.40.2 + '@rollup/rollup-linux-arm64-musl': 4.40.2 + '@rollup/rollup-linux-loongarch64-gnu': 4.40.2 + '@rollup/rollup-linux-powerpc64le-gnu': 4.40.2 + '@rollup/rollup-linux-riscv64-gnu': 4.40.2 + '@rollup/rollup-linux-riscv64-musl': 4.40.2 + '@rollup/rollup-linux-s390x-gnu': 4.40.2 + '@rollup/rollup-linux-x64-gnu': 4.40.2 + '@rollup/rollup-linux-x64-musl': 4.40.2 + '@rollup/rollup-win32-arm64-msvc': 4.40.2 + '@rollup/rollup-win32-ia32-msvc': 4.40.2 + '@rollup/rollup-win32-x64-msvc': 4.40.2 + fsevents: 2.3.3 + + safe-buffer@5.2.1: {} + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + smob@1.5.0: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + supports-preserve-symlinks-flag@1.0.0: {} + + terser@5.39.2: + dependencies: + '@jridgewell/source-map': 0.3.6 + acorn: 8.14.1 + commander: 2.20.3 + source-map-support: 0.5.21 + + typescript@5.8.3: {} + + undici-types@6.21.0: {}
diff --git a/tools/node_tools/pnpm-workspace.yaml b/tools/node_tools/pnpm-workspace.yaml new file mode 100644 index 0000000..7d40629 --- /dev/null +++ b/tools/node_tools/pnpm-workspace.yaml
@@ -0,0 +1 @@ +onlyBuiltDependencies: []
diff --git a/tools/node_tools/polygerrit_app_preprocessor/.gitignore b/tools/node_tools/polygerrit_app_preprocessor/.gitignore deleted file mode 100644 index 6a3417b..0000000 --- a/tools/node_tools/polygerrit_app_preprocessor/.gitignore +++ /dev/null
@@ -1 +0,0 @@ -/out/
diff --git a/tools/node_tools/polygerrit_app_preprocessor/BUILD b/tools/node_tools/polygerrit_app_preprocessor/BUILD deleted file mode 100644 index 52f6a3e..0000000 --- a/tools/node_tools/polygerrit_app_preprocessor/BUILD +++ /dev/null
@@ -1,83 +0,0 @@ -load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") -load("@npm//@bazel/rollup:index.bzl", "rollup_bundle") -load("@npm//@bazel/concatjs:index.bzl", "ts_library") - -package(default_visibility = ["//visibility:public"]) - -# TODO: Would be nice to use `ts_project` from @bazel/typescript instead. -# We would prefer to not depend on @bazel/concatjs ... -ts_library( - name = "preprocessor", - srcs = glob(["*.ts"]), - tsconfig = "tsconfig.json", - deps = [ - "//tools/node_tools/utils", - "@tools_npm//:node_modules", - ], -) - -#rollup_bundle - workaround for https://github.com/bazelbuild/rules_nodejs/issues/1522 -rollup_bundle( - name = "preprocessor-bundle", - args = [ - "--bundleConfigAsCjs=true", - ], - config_file = "rollup.config.js", - entry_point = "preprocessor.ts", - format = "cjs", - rollup_bin = "//tools/node_tools:rollup-bin", - silent = True, - deps = [ - ":preprocessor", - "@tools_npm//@rollup/plugin-node-resolve", - ], -) - -rollup_bundle( - name = "links-updater-bundle", - args = [ - "--bundleConfigAsCjs=true", - ], - config_file = "rollup.config.js", - entry_point = "links-updater.ts", - format = "cjs", - rollup_bin = "//tools/node_tools:rollup-bin", - silent = True, - deps = [ - ":preprocessor", - "@tools_npm//@rollup/plugin-node-resolve", - ], -) - -nodejs_binary( - name = "preprocessor-bin", - data = ["@tools_npm//:node_modules"], - entry_point = "preprocessor-bundle.js", -) - -nodejs_binary( - name = "links-updater-bin", - data = ["@tools_npm//:node_modules"], - entry_point = "links-updater-bundle.js", -) - -# TODO(dmfilippov): Find a better way to fix it (another workaround or submit a bug to -# Bazel IJ plugin's) authors or to a ts_config rule author). -# The following genrule is a workaround for a bazel intellij plugin's bug. -# According to the documentation, the ts_config_rules section should be added -# to a .bazelproject file if a project uses typescript -# (https://ij.bazel.build/docs/dynamic-languages-typescript.html) -# Unfortunately, this doesn't work. It seems, that the plugin expects some output from -# the ts_config rule, but the rule doesn't produce any output. -# To workaround the issue, the tsconfig_editor genrule was added. The genrule only copies -# input file to the output file, but this is enough to make bazel IJ plugins works. -# So, if you have any problem a typescript editor (import errors, types not found, etc...) - -# try to build this rule from the command line -# (bazel build tools/node_tools/node_modules/licenses:tsconfig_editor) and then sync bazel project -# in intellij. -genrule( - name = "tsconfig_editor", - srcs = ["tsconfig.json"], - outs = ["tsconfig_editor.json"], - cmd = "cp $< $@", -)
diff --git a/tools/node_tools/polygerrit_app_preprocessor/README.md b/tools/node_tools/polygerrit_app_preprocessor/README.md deleted file mode 100644 index 91f2a2b..0000000 --- a/tools/node_tools/polygerrit_app_preprocessor/README.md +++ /dev/null
@@ -1,9 +0,0 @@ -This directory contains bazel rules and CLI tools to preprocess HTML and JS files before bundling. - -There are 2 different tools here: -* links-updater (and update_links rule) - updates link in HTML files. - Receives list of input and output files as well as a redirect.json file with information - about redirects. -* preprocessor (and prepare_for_bundling rule) - split each HTML files to a pair of one HTML - and one JS files. The output HTML doesn't contain `<script>` tags and JS file contains - all scripts and imports from HTML file. For more details see source code.
diff --git a/tools/node_tools/polygerrit_app_preprocessor/links-updater.ts b/tools/node_tools/polygerrit_app_preprocessor/links-updater.ts deleted file mode 100644 index 9f872cc..0000000 --- a/tools/node_tools/polygerrit_app_preprocessor/links-updater.ts +++ /dev/null
@@ -1,133 +0,0 @@ -/** - * @license - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as fs from "fs"; -import RewritingStream from "parse5-html-rewriting-stream"; -import * as dom5 from "dom5"; -import {HtmlFileUtils, RedirectsResolver} from "./utils"; -import {Node} from 'dom5'; -import {readMultilineParamFile} from "../utils/command-line"; -import { fail } from "../utils/common"; -import {JSONRedirects} from "./redirects"; - -/** Update links in HTML file - * input_output_param_files - is a list of paths; each path is placed on a separate line - * The first line is the path to a first input file (relative to process working directory) - * The second line is the path to the output file (relative to process working directory) - * The next 2 lines describe the second file and so on. - * redirectFile.json describes how to update links (see {@link JSONRedirects} for exact format) - * Additionaly, update some test links (related to web-component-tester) - */ - -async function main() { - if (process.argv.length < 4) { - console.info("Usage:\n\tnode links_updater.js input_output_param_files redirectFile.json\n"); - process.exit(1); - } - - const jsonRedirects: JSONRedirects = JSON.parse(fs.readFileSync(process.argv[3], {encoding: "utf-8"})) as JSONRedirects; - const redirectsResolver = new RedirectsResolver(jsonRedirects.redirects); - - const input = readMultilineParamFile(process.argv[2]); - const updater = new HtmlFileUpdater(redirectsResolver); - for(let i = 0; i < input.length; i += 2) { - const srcFile = input[i]; - const targetFile = input[i + 1]; - await updater.updateFile(srcFile, targetFile); - } -} - -/** Update all links in HTML file based on redirects. - * Additionally, update references to web-component-tester */ -class HtmlFileUpdater { - private static readonly Predicates = { - isScriptWithSrcTag: (node: Node) => node.tagName === "script" && dom5.hasAttribute(node, "src"), - - isWebComponentTesterImport: (node: Node) => HtmlFileUpdater.Predicates.isScriptWithSrcTag(node) && - dom5.getAttribute(node, "src")!.endsWith("/bower_components/web-component-tester/browser.js"), - - isHtmlImport: (node: Node) => node.tagName === "link" && dom5.getAttribute(node, "rel") === "import" && - dom5.hasAttribute(node, "href") - }; - - public constructor(private readonly redirectsResolver: RedirectsResolver) { - } - - public async updateFile(srcFile: string, targetFile: string) { - const html = fs.readFileSync(srcFile, "utf-8"); - const readStream = fs.createReadStream(srcFile, {encoding: "utf-8"}); - const rewriterOutput = srcFile === targetFile ? targetFile + ".tmp" : targetFile; - const writeStream = fs.createWriteStream(rewriterOutput, {encoding: "utf-8"}); - const rewriter = new RewritingStream(); - (rewriter as any).tokenizer.preprocessor.bufferWaterline = Infinity; - rewriter.on("startTag", (tag: any) => { - if (HtmlFileUpdater.Predicates.isWebComponentTesterImport(tag)) { - dom5.setAttribute(tag, "src", "/components/wct-browser-legacy/browser.js"); - } else if (HtmlFileUpdater.Predicates.isHtmlImport(tag)) { - this.updateRefAttribute(tag, srcFile, "href"); - } else if (HtmlFileUpdater.Predicates.isScriptWithSrcTag(tag)) { - this.updateRefAttribute(tag, srcFile, "src"); - } else { - const location = tag.sourceCodeLocation; - const raw = html.substring(location.startOffset, location.endOffset); - rewriter.emitRaw(raw); - return; - } - rewriter.emitStartTag(tag); - }); - return new Promise<void>((resolve, reject) => { - writeStream.on("close", () => { - writeStream.close(); - if (rewriterOutput !== targetFile) { - fs.renameSync(rewriterOutput, targetFile); - } - resolve(); - }); - readStream.pipe(rewriter).pipe(writeStream); - }); - } - - private getResolvedPath(parentHtml: string, href: string) { - const originalPath = '/' + HtmlFileUtils.getPathRelativeToRoot(parentHtml, href); - - const resolvedInfo = this.redirectsResolver.resolve(originalPath, true); - if (!resolvedInfo.insideNodeModules && resolvedInfo.target === originalPath) { - return href; - } - if (resolvedInfo.insideNodeModules) { - return '/node_modules/' + resolvedInfo.target; - } - if (href.startsWith('/')) { - return resolvedInfo.target; - } - return HtmlFileUtils.getPathRelativeToRoot(parentHtml, resolvedInfo.target); - } - - private updateRefAttribute(node: Node, parentHtml: string, attributeName: string) { - const ref = dom5.getAttribute(node, attributeName); - if (!ref) { - fail(`Internal error - ${node} in ${parentHtml} doesn't have attribute ${attributeName}`); - } - const newRef = this.getResolvedPath(parentHtml, ref); - if (newRef === ref) { - return; - } - dom5.setAttribute(node, attributeName, newRef); - } -} - -main();
diff --git a/tools/node_tools/polygerrit_app_preprocessor/preprocessor.ts b/tools/node_tools/polygerrit_app_preprocessor/preprocessor.ts deleted file mode 100644 index a886373..0000000 --- a/tools/node_tools/polygerrit_app_preprocessor/preprocessor.ts +++ /dev/null
@@ -1,352 +0,0 @@ -/** - * @license - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as fs from "fs"; -import * as parse5 from "parse5"; -import * as dom5 from "dom5"; -import * as path from "path"; -import {Node} from 'dom5'; -import {fail, unexpectedSwitchValue} from "../utils/common"; -import {readMultilineParamFile} from "../utils/command-line"; -import { - HtmlSrcFilePath, - JsSrcFilePath, - HtmlTargetFilePath, - JsTargetFilePath, - FileUtils, - FilePath -} from "../utils/file-utils"; -import { - AbsoluteWebPath, - getRelativeImport, - NodeModuleImportPath, - SrcWebSite -} from "../utils/web-site-utils"; - -/** - * Update source code by moving all scripts out of HTML files. - * Input: - * input_output_html_param_file - list of file paths, each file path on a separate line - * The first 3 line contains the path to the first input HTML file and 2 output paths - * (for HTML and JS files) - * The second 3 line contains paths for the second HTML file, and so on. - * - * input_output_js_param_file - similar to input_output_html_param_file, but has only 2 lines - * per file (input JS file and output JS file) - * - * input_web_root_path - path (in filesystem) which should be treated as a web-site root path. - - * For each HTML file it creates 2 output files - HTML and JS file. - * HTML file contains everything from HTML input file, except <script> tags. - * JS file contains (in the same order, as in original HTML): - * - inline javascript code from HTML file - * - each <script src = "path/to/file.js" > from HTML is converted to - * import 'path/to/output/file.js' - * statement. Such import statement run all side-effects in file.js (i.e. it run all # - * global code). - * - each <link rel="import" href = "path/to/file.html"> adds to .js file as - * import 'path/to/output/file.html.js - * i.e. instead of html, the .js script imports - * Because output JS keeps the order of imports, all global variables are - * initialized in a correct order (this is important for gerrit; it is impossible to use - * AMD modules here). - */ - -enum RefType { - Html, - InlineJS, - JSFile -} - -type LinkOrScript = HtmlFileRef | HtmlFileNodeModuleRef | JsFileReference | JsFileNodeModuleReference | InlineJS; - -interface HtmlFileRef { - type: RefType.Html, - path: HtmlSrcFilePath; - isNodeModule: false; -} - -interface HtmlFileNodeModuleRef { - type: RefType.Html, - path: NodeModuleImportPath; - isNodeModule: true; -} - - -function isHtmlFileRef(ref: LinkOrScript): ref is HtmlFileRef { - return ref.type === RefType.Html; -} - -interface JsFileReference { - type: RefType.JSFile, - path: JsSrcFilePath; - isModule: boolean; - isNodeModule: false; -} - -interface JsFileNodeModuleReference { - type: RefType.JSFile, - path: NodeModuleImportPath; - isModule: boolean; - isNodeModule: true; -} - -interface InlineJS { - type: RefType.InlineJS, - isModule: boolean; - content: string; -} - -interface HtmlOutputs { - html: HtmlTargetFilePath; - js: JsTargetFilePath; -} - -interface JsOutputs { - js: JsTargetFilePath; -} - -type HtmlSrcToOutputMap = Map<HtmlSrcFilePath, HtmlOutputs>; -type JsSrcToOutputMap = Map<JsSrcFilePath, JsOutputs>; - -interface HtmlFileInfo { - src: HtmlSrcFilePath; - ast: parse5.AST.Document; - linksAndScripts: LinkOrScript[] -} - -/** HtmlScriptAndLinksCollector walks through HTML file and collect - * all links and inline scripts. - */ -class HtmlScriptAndLinksCollector { - public constructor(private readonly webSite: SrcWebSite) { - } - public collect(src: HtmlSrcFilePath): HtmlFileInfo { - const ast = HtmlScriptAndLinksCollector.getAst(src); - const isHtmlImport = (node: Node) => node.tagName == "link" && - dom5.getAttribute(node, "rel") == "import"; - const isScriptTag = (node: Node) => node.tagName == "script"; - - const linksAndScripts: LinkOrScript[] = dom5 - .nodeWalkAll(ast as Node, (node) => isHtmlImport(node) || isScriptTag(node)) - .map((node) => { - if (isHtmlImport(node)) { - const href = dom5.getAttribute(node, "href"); - if (!href) { - fail(`Tag <link rel="import...> in the file '${src}' doesn't have href attribute`); - } - if(this.webSite.isNodeModuleReference(href)) { - return { - type: RefType.Html, - path: this.webSite.getNodeModuleImport(href), - isNodeModule: true, - } - } else { - return { - type: RefType.Html, - path: this.webSite.resolveHtmlImport(src, href), - isNodeModule: false, - } - } - } else { - const isModule = dom5.getAttribute(node, "type") === "module"; - if (dom5.hasAttribute(node, "src")) { - let srcPath = dom5.getAttribute(node, "src")!; - if(this.webSite.isNodeModuleReference(srcPath)) { - return { - type: RefType.JSFile, - isModule: isModule, - path: this.webSite.getNodeModuleImport(srcPath), - isNodeModule: true - }; - } else { - return { - type: RefType.JSFile, - isModule: isModule, - path: this.webSite.resolveScriptSrc(src, srcPath), - isNodeModule: false - }; - } - } - return { - type: RefType.InlineJS, - isModule: isModule, - content: dom5.getTextContent(node) - }; - } - }); - return { - src, - ast, - linksAndScripts - }; - }; - - private static getAst(file: string): parse5.AST.Document { - const html = fs.readFileSync(file, "utf-8"); - return parse5.parse(html, {locationInfo: true}); - } - -} - -/** Generate js files */ -class ScriptGenerator { - public constructor(private readonly pathMapper: SrcToTargetPathMapper) { - } - public generateFromJs(src: JsSrcFilePath) { - FileUtils.copyFile(src, this.pathMapper.getJsTargetForJs(src)); - } - - public generateFromHtml(html: HtmlFileInfo) { - const content: string[] = []; - const src = html.src; - const targetJsFile: JsTargetFilePath = this.pathMapper.getJsTargetForHtml(src); - html.linksAndScripts.forEach((linkOrScript) => { - switch (linkOrScript.type) { - case RefType.Html: - if(linkOrScript.isNodeModule) { - const importPath = this.pathMapper.getJsTargetForHtmlInNodeModule(linkOrScript.path) - content.push(`import '${importPath}';`); - } else { - const importPath = this.pathMapper.getJsTargetForHtml(linkOrScript.path); - const htmlRelativePath = getRelativeImport(targetJsFile, importPath); - content.push(`import '${htmlRelativePath}';`); - } - break; - case RefType.JSFile: - if(linkOrScript.isNodeModule) { - content.push(`import '${linkOrScript.path}'`); - } else { - const importFromJs = this.pathMapper.getJsTargetForJs(linkOrScript.path); - const scriptRelativePath = getRelativeImport(targetJsFile, importFromJs); - content.push(`import '${scriptRelativePath}';`); - } - break; - case RefType.InlineJS: - content.push(linkOrScript.content); - break; - default: - unexpectedSwitchValue(linkOrScript); - } - }); - FileUtils.writeContent(targetJsFile, content.join("\n")); - } -} - -/** Generate html files*/ -class HtmlGenerator { - constructor(private readonly pathMapper: SrcToTargetPathMapper) { - } - public generateFromHtml(html: HtmlFileInfo) { - const ast = html.ast; - dom5.nodeWalkAll(ast as Node, (node) => node.tagName === "script") - .forEach((scriptNode) => dom5.remove(scriptNode)); - const newContent = parse5.serialize(ast); - if(newContent.indexOf("<script") >= 0) { - fail(`Has content ${html.src}`); - } - FileUtils.writeContent(this.pathMapper.getHtmlTargetForHtml(html.src), newContent); - } -} - -function readHtmlSrcToTargetMap(paramFile: string): HtmlSrcToOutputMap { - const htmlSrcToTarget: HtmlSrcToOutputMap = new Map(); - const input = readMultilineParamFile(paramFile); - for(let i = 0; i < input.length; i += 3) { - const srcHtmlFile = path.resolve(input[i]) as HtmlSrcFilePath; - const targetHtmlFile = path.resolve(input[i + 1]) as HtmlTargetFilePath; - const targetJsFile = path.resolve(input[i + 2]) as JsTargetFilePath; - htmlSrcToTarget.set(srcHtmlFile, { - html: targetHtmlFile, - js: targetJsFile - }); - } - return htmlSrcToTarget; -} - -function readJsSrcToTargetMap(paramFile: string): JsSrcToOutputMap { - const jsSrcToTarget: JsSrcToOutputMap = new Map(); - const input = readMultilineParamFile(paramFile); - for(let i = 0; i < input.length; i += 2) { - const srcJsFile = path.resolve(input[i]) as JsSrcFilePath; - const targetJsFile = path.resolve(input[i + 1]) as JsTargetFilePath; - jsSrcToTarget.set(srcJsFile as JsSrcFilePath, { - js: targetJsFile as JsTargetFilePath - }); - } - return jsSrcToTarget; -} - -class SrcToTargetPathMapper { - public constructor( - private readonly htmlSrcToTarget: HtmlSrcToOutputMap, - private readonly jsSrcToTarget: JsSrcToOutputMap) { - } - public getJsTargetForHtmlInNodeModule(file: NodeModuleImportPath): JsTargetFilePath { - return `${file}_gen.js` as JsTargetFilePath; - } - - public getJsTargetForHtml(html: HtmlSrcFilePath): JsTargetFilePath { - return this.getHtmlOutputs(html).js; - } - public getHtmlTargetForHtml(html: HtmlSrcFilePath): HtmlTargetFilePath { - return this.getHtmlOutputs(html).html; - } - public getJsTargetForJs(js: JsSrcFilePath): JsTargetFilePath { - return this.getJsOutputs(js).js; - } - - private getHtmlOutputs(html: HtmlSrcFilePath): HtmlOutputs { - if(!this.htmlSrcToTarget.has(html)) { - fail(`There are no outputs for the file '${html}'`); - } - return this.htmlSrcToTarget.get(html)!; - } - private getJsOutputs(js: JsSrcFilePath): JsOutputs { - if(!this.jsSrcToTarget.has(js)) { - fail(`There are no outputs for the file '${js}'`); - } - return this.jsSrcToTarget.get(js)!; - } -} - -function main() { - if(process.argv.length < 5) { - const execFileName = path.basename(__filename); - fail(`Usage:\nnode ${execFileName} input_web_root_path input_output_html_param_file input_output_js_param_file\n`); - } - - const srcWebSite = new SrcWebSite(path.resolve(process.argv[2]) as FilePath); - const htmlSrcToTarget: HtmlSrcToOutputMap = readHtmlSrcToTargetMap(process.argv[3]); - const jsSrcToTarget: JsSrcToOutputMap = readJsSrcToTargetMap(process.argv[4]); - const pathMapper = new SrcToTargetPathMapper(htmlSrcToTarget, jsSrcToTarget); - - const scriptGenerator = new ScriptGenerator(pathMapper); - const htmlGenerator = new HtmlGenerator(pathMapper); - const scriptAndLinksCollector = new HtmlScriptAndLinksCollector(srcWebSite); - - htmlSrcToTarget.forEach((targets, src) => { - const htmlFileInfo = scriptAndLinksCollector.collect(src); - scriptGenerator.generateFromHtml(htmlFileInfo); - htmlGenerator.generateFromHtml(htmlFileInfo); - }); - jsSrcToTarget.forEach((targets, src) => { - scriptGenerator.generateFromJs(src); - }); -} - -main();
diff --git a/tools/node_tools/polygerrit_app_preprocessor/redirects.ts b/tools/node_tools/polygerrit_app_preprocessor/redirects.ts deleted file mode 100644 index 0ccd78f..0000000 --- a/tools/node_tools/polygerrit_app_preprocessor/redirects.ts +++ /dev/null
@@ -1,62 +0,0 @@ -/** - * @license - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** redirects.json schema*/ -export interface JSONRedirects { - /** Short text description. Do not used anywhere*/ - description?: string; - /** List of redirects, from the highest to lower priority. */ - redirects: Redirect[]; -} - -/** Redirect - describes one redirect. - * Each link in the html file is converted to a path relative to site root - * Redirect is applied, if converted link started with 'from' - * */ -export interface Redirect { - /** from - path prefix. The '/' is added to the end of string if not present */ - from: string; - /** New location - can be either other directory or node module*/ - to: PathRedirect; -} - -export type PathRedirect = RedirectToDir | RedirectToNodeModule; - -/** RedirectToDir - use another dir instead of original one*/ -export interface RedirectToDir { - /** New dir (relative to site root)*/ - dir: string; - /** Redirects for files inside directory - * Key is the original relative path, value is the new relative path (relative to new dir) */ - files?: { [name: string]: string } -} - -export interface RedirectToNodeModule { - /** Import from this node module instead of directory*/ - npm_module: string; - /** Redirects for files inside node module - * Key is the original relative path, value is the new relative path (relative to npm_module) */ - files?: { [name: string]: string } -} - -export function isRedirectToNodeModule(redirect: PathRedirect): redirect is RedirectToNodeModule { - return (redirect as RedirectToNodeModule).npm_module !== undefined; -} - -export function isRedirectToDir(redirect: PathRedirect): redirect is RedirectToDir { - return (redirect as RedirectToDir).dir !== undefined; -}
diff --git a/tools/node_tools/polygerrit_app_preprocessor/rollup.config.js b/tools/node_tools/polygerrit_app_preprocessor/rollup.config.js deleted file mode 100644 index bf0f8511..0000000 --- a/tools/node_tools/polygerrit_app_preprocessor/rollup.config.js +++ /dev/null
@@ -1,27 +0,0 @@ -/** - * @license - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export default { - external: ['fs', 'path', 'parse5', 'dom5', 'parse5-html-rewriting-stream'], - onwarn: warn => { - // Typescript adds helper methods for await and promise which look like - // var __awaiter = (this && this.__awaiter) - // It is safe to ignore this warning - if(warn.code === 'THIS_IS_UNDEFINED') { return; } - throw new Error(warn.message); - } -};
diff --git a/tools/node_tools/polygerrit_app_preprocessor/tsconfig.json b/tools/node_tools/polygerrit_app_preprocessor/tsconfig.json deleted file mode 100644 index 56e8b2d..0000000 --- a/tools/node_tools/polygerrit_app_preprocessor/tsconfig.json +++ /dev/null
@@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "target": "es2021", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021' or 'ESNEXT'. */ - "module": "es2020", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "strict": true, - "moduleResolution": "node", - "outDir": "out" - }, - "include": ["*.ts"] -}
diff --git a/tools/node_tools/polygerrit_app_preprocessor/utils.ts b/tools/node_tools/polygerrit_app_preprocessor/utils.ts deleted file mode 100644 index 4163d26..0000000 --- a/tools/node_tools/polygerrit_app_preprocessor/utils.ts +++ /dev/null
@@ -1,111 +0,0 @@ -/** - * @license - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as fs from "fs"; -import * as path from "path"; -import {FileUtils} from "../utils/file-utils"; -import { - Redirect, - isRedirectToNodeModule, - isRedirectToDir, - RedirectToNodeModule, - PathRedirect -} from "./redirects"; - -export class HtmlFileUtils { - public static getPathRelativeToRoot(parentHtml: string, fileHref: string): string { - if (fileHref.startsWith('/')) { - return fileHref.substring(1); - } - return path.join(path.dirname(parentHtml), fileHref); - } - - public static getImportPathRelativeToParent(rootDir: string, parentFile: string, importPath: string) { - if (importPath.startsWith('/')) { - importPath = importPath.substr(1); - } - const parentDir = path.dirname( - path.resolve(path.join(rootDir, parentFile))); - const fullImportPath = path.resolve(path.join(rootDir, importPath)); - const relativePath = path.relative(parentDir, fullImportPath); - return relativePath.startsWith('../') ? - relativePath : "./" + relativePath; - } -} -interface RedirectForFile { - to: PathRedirect; - pathToFile: string; -} - -interface ResolvedPath { - target: string; - insideNodeModules: boolean; -} - -/** RedirectsResolver based on the list of redirects, calculates - * new import path - */ -export class RedirectsResolver { - public constructor(private readonly redirects: Redirect[]) { - } - - /** resolve returns new path instead of pathRelativeToRoot; */ - public resolve(pathRelativeToRoot: string, resolveNodeModules: boolean): ResolvedPath { - const redirect = this.findRedirect(pathRelativeToRoot); - if (!redirect) { - return {target: pathRelativeToRoot, insideNodeModules: false}; - } - if (isRedirectToNodeModule(redirect.to)) { - return { - target: resolveNodeModules ? RedirectsResolver.resolveNodeModuleFile(redirect.to, - redirect.pathToFile) : pathRelativeToRoot, - insideNodeModules: resolveNodeModules - }; - } - if (isRedirectToDir(redirect.to)) { - let newDir = redirect.to.dir; - if (!newDir.endsWith('/')) { - newDir = newDir + '/'; - } - return {target: `${newDir}${redirect.pathToFile}`, insideNodeModules: false} - } - throw new Error(`Invalid redirect for path: ${pathRelativeToRoot}`); - } - - private static resolveNodeModuleFile(npmRedirect: RedirectToNodeModule, pathToFile: string): string { - if(npmRedirect.files && npmRedirect.files[pathToFile]) { - pathToFile = npmRedirect.files[pathToFile]; - } - return `${npmRedirect.npm_module}/${pathToFile}`; - } - - private findRedirect(relativePathToRoot: string): RedirectForFile | undefined { - if(!relativePathToRoot.startsWith('/')) { - relativePathToRoot = '/' + relativePathToRoot; - } - for(const redirect of this.redirects) { - const normalizedFrom = redirect.from + (redirect.from.endsWith('/') ? '' : '/'); - if(relativePathToRoot.startsWith(normalizedFrom)) { - return { - to: redirect.to, - pathToFile: relativePathToRoot.substring(normalizedFrom.length) - }; - } - } - return undefined; - } -}
diff --git a/tools/node_tools/utils/BUILD b/tools/node_tools/utils/BUILD deleted file mode 100644 index 0a6e768..0000000 --- a/tools/node_tools/utils/BUILD +++ /dev/null
@@ -1,14 +0,0 @@ -load("@npm//@bazel/concatjs:index.bzl", "ts_library") - -package(default_visibility = ["//visibility:public"]) - -# TODO: Would be nice to use `ts_project` from @bazel/typescript instead. -# We would prefer to not depend on @bazel/concatjs ... -ts_library( - name = "utils", - srcs = glob(["*.ts"]), - tsconfig = "tsconfig.json", - deps = [ - "@tools_npm//:node_modules", - ], -)
diff --git a/tools/node_tools/utils/command-line.ts b/tools/node_tools/utils/command-line.ts deleted file mode 100644 index 48e3c87..0000000 --- a/tools/node_tools/utils/command-line.ts +++ /dev/null
@@ -1,22 +0,0 @@ -/** - * @license - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as fs from "fs"; - -export function readMultilineParamFile(path: string): string[] { - return fs.readFileSync(path, {encoding: 'utf-8'}).split(/\r?\n/).filter(f => f.length > 0); -}
diff --git a/tools/node_tools/utils/common.ts b/tools/node_tools/utils/common.ts deleted file mode 100644 index 9b976ba..0000000 --- a/tools/node_tools/utils/common.ts +++ /dev/null
@@ -1,25 +0,0 @@ -/** - * @license - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export function fail(message: string): never { - console.error(message); - process.exit(1); -} - -export function unexpectedSwitchValue(_: never): never { - fail(`Internal error - unexpected switch value`); -}
diff --git a/tools/node_tools/utils/file-utils.ts b/tools/node_tools/utils/file-utils.ts deleted file mode 100644 index d8e1581..0000000 --- a/tools/node_tools/utils/file-utils.ts +++ /dev/null
@@ -1,56 +0,0 @@ -/** - * @license - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as path from "path"; -import * as fs from "fs"; - -export type FilePath = string & {__filePath: undefined}; -export type TypedFilePath<T> = FilePath & { __type?: T, __typedFilePath: undefined }; - -export enum FileType{ - HtmlSrc, - HtmlTarget, - JsSrc, - JsTarget -} - -export type HtmlSrcFilePath = TypedFilePath<FileType.HtmlSrc>; -export type HtmlTargetFilePath = TypedFilePath<FileType.HtmlTarget>; -export type JsSrcFilePath = TypedFilePath<FileType.JsSrc>; -export type JsTargetFilePath = TypedFilePath<FileType.JsTarget>; - -export class FileUtils { - public static ensureDirExistsForFile(filePath: string) { - const dirName = path.dirname(filePath); - if (!fs.existsSync(dirName)) { - fs.mkdirSync(dirName, {recursive: true, mode: 0o744}); - } - } - - public static writeContent(file: string, content: string) { - if(fs.existsSync(file) && fs.lstatSync(file).isSymbolicLink()) { - throw new Error(`Output file '${file}' is a symbolic link. Inplace update for links are not supported.`); - } - FileUtils.ensureDirExistsForFile(file); - fs.writeFileSync(file, content); - } - - public static copyFile(src: string, dst: string) { - FileUtils.ensureDirExistsForFile(dst); - fs.copyFileSync(src, dst); - } -}
diff --git a/tools/node_tools/utils/tsconfig.json b/tools/node_tools/utils/tsconfig.json deleted file mode 100644 index 60cb677..0000000 --- a/tools/node_tools/utils/tsconfig.json +++ /dev/null
@@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "target": "es2021", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021' or 'ESNEXT'. */ - "module": "es2020", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "strict": true, - "moduleResolution": "node", - "outDir": "../../../.ts-out/tools/utils" // Not used in bazel - }, - "include": ["*.ts"] -}
diff --git a/tools/node_tools/utils/web-site-utils.ts b/tools/node_tools/utils/web-site-utils.ts deleted file mode 100644 index eb30ce4..0000000 --- a/tools/node_tools/utils/web-site-utils.ts +++ /dev/null
@@ -1,102 +0,0 @@ -/** - * @license - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as path from "path"; -import {fail} from "./common"; -import {FilePath, HtmlSrcFilePath, JsSrcFilePath} from "./file-utils"; - -export type AbsoluteWebPath = string & { __absoluteWebPath: undefined }; -export type RelativeWebPath = string & { __relativeWebPath: undefined }; -export type WebPath = AbsoluteWebPath | RelativeWebPath; - -export type NodeModuleImportPath = string & {__nodeModuleImportPath: undefined}; - -export type AbsoluteTypedWebPath<T> = AbsoluteWebPath & { __type?: T, __absoluteTypedFilePath: undefined }; -export type RelativeTypedWebPath<T> = RelativeWebPath & { __type?: T, __relativeTypedFilePath: undefined }; - -export type TypedWebPath<T> = AbsoluteTypedWebPath<T> | RelativeTypedWebPath<T>; - -export function isAbsoluteWebPath(path: WebPath): path is AbsoluteWebPath { - return path.startsWith("/"); -} - -export function isRelativeWebPath(path: WebPath): path is RelativeWebPath { - return !isAbsoluteWebPath(path); -} -const node_modules_path_prefix = "/node_modules/"; - -/** Contains method to resolve absolute and relative paths */ -export class SrcWebSite { - public constructor(private readonly webSiteRoot: FilePath) { - } - - public getFilePath(webPath: AbsoluteWebPath): FilePath { - return path.resolve(this.webSiteRoot, webPath.substr(1)) as FilePath; - } - - public getAbsoluteWebPathToFile(file: FilePath): AbsoluteWebPath { - const relativePath = path.relative(this.webSiteRoot, file); - if(relativePath.startsWith("..")) { - fail(`The file ${file} is not under webSiteRoot`); - } - return ("/" + relativePath) as AbsoluteWebPath; - } - - public static resolveReference(from: AbsoluteWebPath, to: WebPath): AbsoluteWebPath { - return isAbsoluteWebPath(to) ? to : path.resolve(from, to) as AbsoluteWebPath; - } - - public static getRelativePath(from: AbsoluteWebPath, to: AbsoluteWebPath): RelativeWebPath { - return path.relative(from, to) as RelativeWebPath; - } - - public resolveHtmlImport(from: HtmlSrcFilePath, href: string): HtmlSrcFilePath { - return this.resolveReferenceToAbsPath(from, href) as HtmlSrcFilePath; - - } - public resolveScriptSrc(from: HtmlSrcFilePath, src: string): JsSrcFilePath { - return this.resolveReferenceToAbsPath(from, src) as JsSrcFilePath; - } - - public isNodeModuleReference(ref: string): boolean { - return ref.startsWith(node_modules_path_prefix); - } - - public getNodeModuleImport(ref: string): NodeModuleImportPath { - if(!this.isNodeModuleReference(ref)) { - fail(`Internal error! ${ref} must be inside node modules`); - } - return ref.substr(node_modules_path_prefix.length) as NodeModuleImportPath; - } - - private resolveReferenceToAbsPath(from: string, ref: string): string { - if(ref.startsWith("/")) { - const relativeToRootPath = ref.substr(1); - return path.resolve(this.webSiteRoot, relativeToRootPath); - } - return path.resolve(path.dirname(from), ref); - } -} - -export function getRelativeImport(from: FilePath, ref: FilePath) { - const relativePath = path.relative(path.dirname(from), ref); - if(relativePath.startsWith("../")) { - return relativePath - } else { - return "./" + relativePath; - } -}
diff --git a/tools/node_tools/yarn.lock b/tools/node_tools/yarn.lock index 0e24b81..a37eac1 100644 --- a/tools/node_tools/yarn.lock +++ b/tools/node_tools/yarn.lock
@@ -288,45 +288,17 @@ resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== -"@types/node@*", "@types/node@^22.15.2": - version "22.15.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.18.tgz#2f8240f7e932f571c2d45f555ba0b6c3f7a75963" - integrity sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg== - dependencies: - undici-types "~6.21.0" - "@types/node@^10.1.0": version "10.17.60" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== -"@types/parse5-html-rewriting-stream@^5.1.2": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@types/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-5.1.2.tgz#919d5bbf69ef61e11d873e7195891c3811491a03" - integrity sha512-7CHY6QlayurvYRST5xatE/ipIueph5V+EW2xU12P0CsNucuwygnuiE4foYsdQUEkhnKrTU62KmikANPnoxiGrg== +"@types/node@^22.15.2": + version "22.15.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.18.tgz#2f8240f7e932f571c2d45f555ba0b6c3f7a75963" + integrity sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg== dependencies: - "@types/parse5-sax-parser" "*" - -"@types/parse5-sax-parser@*": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@types/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz#6b6fd46ef05c3c58392fc4d915f07969f7ce1a90" - integrity sha512-wR3hG33tMsLOdqtcJT0XuGi1Ik3dTP+/ZWN1AckYmEagO/SWoL99GPyv21ZI/FAHOtYWB02TEV3+Y5zqkC4L+Q== - dependencies: - parse5-sax-parser "*" - -"@types/parse5@^2.2.34": - version "2.2.34" - resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-2.2.34.tgz#e3870a10e82735a720f62d71dcd183ba78ef3a9d" - integrity sha512-p3qOvaRsRpFyEmaS36RtLzpdxZZnmxGuT1GMgzkTtTJVFuEw7KFjGK83MFODpJExgX1bEzy9r0NYjMC3IMfi7w== - dependencies: - "@types/node" "*" - -"@types/parse5@^4.0.0": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-4.0.1.tgz#ec53c3f948f284be08454f622ba83765efdd06d7" - integrity sha512-CgJIkoNLclXUUg5cEo/SybyhxgVuKGqGcw8BPBpkKWX7wGcNfDlO2Ot+5O9u5E6k3NDg6RmJzm5w5N2prDIE8Q== - dependencies: - "@types/node" "*" + undici-types "~6.21.0" "@types/resolve@1.20.2": version "1.20.2" @@ -343,11 +315,6 @@ resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -clone@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" - integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== - commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -363,20 +330,6 @@ resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== -dom5@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dom5/-/dom5-3.0.1.tgz#cdfc7331f376e284bf379e6ea054afc136702944" - integrity sha512-JPFiouQIr16VQ4dX6i0+Hpbg3H2bMKPmZ+WZgBOSSvOPx9QHwwY8sPzeM2baUtViESYto6wC2nuZOMC/6gulcA== - dependencies: - "@types/parse5" "^2.2.34" - clone "^2.1.0" - parse5 "^4.0.0" - -entities@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-6.0.0.tgz#09c9e29cb79b0a6459a9b9db9efb418ac5bb8e51" - integrity sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw== - estree-walker@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" @@ -440,45 +393,6 @@ dependencies: "@jridgewell/sourcemap-codec" "^1.5.0" -parse5-html-rewriting-stream@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-5.1.1.tgz#fc18570ba0d09b5091250956d1c3f716ef0a07b7" - integrity sha512-rbXBeMlJ3pk3tKxLKAUaqvQTZM5KTohXmZvYEv2gU9sQC70w65BxPsh3PVVnwiVNCnNYDtNZRqCKmiMlfdG07Q== - dependencies: - parse5 "^5.1.1" - parse5-sax-parser "^5.1.1" - -parse5-sax-parser@*: - version "7.0.0" - resolved "https://registry.yarnpkg.com/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz#4c05064254f0488676aca75fb39ca069ec96dee5" - integrity sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg== - dependencies: - parse5 "^7.0.0" - -parse5-sax-parser@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse5-sax-parser/-/parse5-sax-parser-5.1.1.tgz#02834a9d08b23ea2d99584841c38be09d5247a15" - integrity sha512-9HIh6zd7bF1NJe95LPCUC311CekdOi55R+HWXNCsGY6053DWaMijVKOv1oPvdvPTvFicifZyimBVJ6/qvG039Q== - dependencies: - parse5 "^5.1.1" - -parse5@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" - integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== - -parse5@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" - integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== - -parse5@^7.0.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.3.0.tgz#d7e224fa72399c7a175099f45fc2ad024b05ec05" - integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== - dependencies: - entities "^6.0.0" - path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
diff --git a/tools/nongoogle.toml b/tools/nongoogle.toml index 6fdb88b..1e30866 100644 --- a/tools/nongoogle.toml +++ b/tools/nongoogle.toml
@@ -3,8 +3,8 @@ [versions] autoCommon = "1.2.2" -autoFactory = "1.0.1" -autoValue= "1.11.0" +autoFactory = "1.1.0" +autoValue= "1.11.1" flogger = "0.8" # Also update the GUAVA version in java/com/google/gerrit/extensions/BUILD # to keep Bazel and extension dependencies in sync. @@ -12,7 +12,7 @@ guice = "6.0.0" lucene = "10.4.0" slf4j = "2.0.18" -sshd = "2.17.1" +sshd = "2.18.0" truth = "1.4.4" [libraries] @@ -49,10 +49,10 @@ lucene-core = { module = "org.apache.lucene:lucene-core", version.ref = "lucene" } lucene-misc = { module = "org.apache.lucene:lucene-misc", version.ref = "lucene" } lucene-queryparser = { module = "org.apache.lucene:lucene-queryparser", version.ref = "lucene" } -mina-core = { module = "org.apache.mina:mina-core", version = "2.2.4" } +mina-core = { module = "org.apache.mina:mina-core", version = "2.2.9" } nekohtml = { module = "net.sourceforge.nekohtml:nekohtml", version = "1.9.10" } openid-consumer = { module = "org.openid4java:openid4java", version = "1.0.0" } -protobuf-java = { module = "com.google.protobuf:protobuf-java", version = "4.33.4" } +protobuf-java = { module = "com.google.protobuf:protobuf-java", version = "4.35.0" } soy = { module = "com.google.template:soy", version = "2024-01-30" } sshd-mina = { module = "org.apache.sshd:sshd-mina", version.ref = "sshd" } sshd-osgi = { module = "org.apache.sshd:sshd-osgi", version.ref = "sshd" }
diff --git a/tools/run_gjf.sh b/tools/run_gjf.sh deleted file mode 100755 index e5962a0..0000000 --- a/tools/run_gjf.sh +++ /dev/null
@@ -1,25 +0,0 @@ -# -# Copyright (C) 2017 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -NEW_SCRIPT=$(dirname $0)/gjf.sh -VERSION=${1:-""} - -echo -echo "WARNING:" -echo " Calling $0 is deprecated and $0 will be removed in 3.13. -echo " Call \"$NEW_SCRIPT run $VERSION\" instead"." -echo - -$NEW_SCRIPT run $VERSION
diff --git a/tools/setup_gjf.sh b/tools/setup_gjf.sh deleted file mode 100755 index 61a02db..0000000 --- a/tools/setup_gjf.sh +++ /dev/null
@@ -1,27 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2017 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -NEW_SCRIPT=$(dirname $0)/gjf.sh -VERSION=${1:-""} - -echo -echo "WARNING:" -echo " Calling $0 is deprecated and $0 will be removed in 3.13. -echo " Call \"$NEW_SCRIPT setup $VERSION\" instead"." -echo - -$NEW_SCRIPT setup $VERSION -
diff --git a/tools/util.py b/tools/util.py index 4c7c3b6..c77f353 100644 --- a/tools/util.py +++ b/tools/util.py
@@ -35,8 +35,7 @@ A special case is supported, when prefix neither exists in REPO_ROOTS, no in redirects set: the url is returned as is. - This enables plugins to pass custom maven_repository URL as is - directly to maven_jar(). + This enables plugins to pass custom maven_repository URL. Returns a resolved path for Maven artifact. """
diff --git a/web-dev-server.config.mjs b/web-dev-server.config.mjs index 8820ecd..463c471 100644 --- a/web-dev-server.config.mjs +++ b/web-dev-server.config.mjs
@@ -25,15 +25,7 @@ // polygerrit-ui/app/rules.bzl async (context, next) => { - if ( context.url.includes("/bower_components/webcomponentsjs/webcomponents-loader.js") ) { - context.response.redirect("/node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"); - } else if (context.url.includes("/bower_components/webcomponentsjs/bundles/")) { - const bundlePath = context.url.replace( - "/bower_components/webcomponentsjs/bundles/", - "/node_modules/@webcomponents/webcomponentsjs/bundles/" - ); - context.response.redirect(bundlePath); - } else if ( context.url.startsWith( "/fonts/" ) ) { + if ( context.url.startsWith( "/fonts/" ) ) { const fontFile = path.join( "lib/fonts", path.basename(context.url) ); context.body = fs.createReadStream( fontFile ); }
diff --git a/webapp/BUILD b/webapp/BUILD index f907be9b..8ea82d3 100644 --- a/webapp/BUILD +++ b/webapp/BUILD
@@ -1,4 +1,4 @@ -load("//tools/bzl:genrule2.bzl", "genrule2") +load("@com_googlesource_gerrit_bazlets//tools:genrule2.bzl", "genrule2") genrule2( name = "assets",