diff --git a/.github/workflows/branch_fast_forward_command.yaml b/.github/workflows/branch_fast_forward_command.yaml new file mode 100644 index 0000000..d2f2da6 --- /dev/null +++ b/.github/workflows/branch_fast_forward_command.yaml @@ -0,0 +1,68 @@ +name: branch / fast-forward command + +# `/genvm-fast-forward` lands a PR by moving its base branch to the PR head, +# without a merge or squash commit. The tool gates on approvals, required checks +# and E2E, since the deploy key it pushes with bypasses the base's rulesets. +# The resulting push triggers the usual executor branch sync + +on: + issue_comment: + types: [created] + +permissions: {} + +# One request per PR at a time; across PRs the non-force push keeps landings safe +concurrency: + group: fast-forward-command-${{ github.event.issue.number }} + cancel-in-progress: false + +defaults: + run: + shell: bash -exo pipefail {0} + +jobs: + fast-forward: + # The association only spares strangers the deploy key; the tool authorizes + if: >- + github.event.issue.pull_request && + github.event.comment.body == '/genvm-fast-forward' && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + checks: read + contents: read + issues: write + pull-requests: write + statuses: read + steps: + # issue_comment workflows run from the default branch. Keep that trusted + # checkout: the deploy key is in scope, so no PR code may run here. + # Full history, so git can show the push builds on the base + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Set up manager push access + env: + MANAGER_DEPLOY_KEY: ${{ secrets.GENVM_CI_PRIVATE_KEY }} + run: | + install -m700 -d ~/.ssh + key="${RUNNER_TEMP}/manager_push_key" + set +x + printf '%s\n' "$MANAGER_DEPLOY_KEY" > "$key" + set -x + chmod 600 "$key" + git config --global url."git@github.com:".insteadOf "https://github.com/" + echo "GIT_SSH_COMMAND=ssh -i $key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" >> "$GITHUB_ENV" + + - name: Verify and fast-forward + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.issue.number }} + COMMENT_ID: ${{ github.event.comment.id }} + COMMENT_BODY: ${{ github.event.comment.body }} + SENDER: ${{ github.event.sender.login }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: ./support/ci/run.sh tool fast-forward-command diff --git a/docs/contributing/howto/pr.md b/docs/contributing/howto/pr.md index 3bde9bf..87b52e5 100644 --- a/docs/contributing/howto/pr.md +++ b/docs/contributing/howto/pr.md @@ -30,6 +30,21 @@ reading the diff With `ci-safe` and write access, `/genvm-run-tests` links one run and reacts 👀 → 🚀/😕 without changing the sticky `run-full-tests` label +## Fast-Forward + +`/genvm-fast-forward` lands a PR without the App: it moves the base branch to +the PR head with a non-force push, so the PR's commits land as they are. It +refuses, listing every unmet gate, unless: + +1. The commenter is an admin, or a writer and the PR head carries an approval + from a writer who is neither the author nor the commenter, with no writer's + change request +2. The base is `v-dev`, or `v` for an admin; the PR is not from a fork +3. `E2E Tests` and every check the base requires succeeded on the head +4. The head is strictly ahead of the base and has no merge commits + +The push then syncs executor branches as any manager push does + ## Gates `queue.yaml` always runs the pre-commit hooks. On a `pull_request` event it also @@ -50,7 +65,8 @@ rerun manually against the live manager branch ## Authority -Needs a maintainer: merging, `--admin`, cross-repo E2E runs, and anything +Needs a maintainer: merging (a writer may `/genvm-fast-forward` an approved +PR), `--admin`, cross-repo E2E runs, and anything irreversible outside the PR — releases, deploys, messages to other teams. Never force-push a shared branch (`v`, `v-dev`); branch protection refuses it anyway. Force-pushing your own PR branch is routine and needs no one, and so diff --git a/docs/website/src/impl-spec/04-fees.rst b/docs/website/src/impl-spec/04-fees.rst index e6e6fd3..627d1df 100644 --- a/docs/website/src/impl-spec/04-fees.rst +++ b/docs/website/src/impl-spec/04-fees.rst @@ -299,15 +299,16 @@ The default v0.3 fee expressions reject external reservations and receipts with external messages without a matching allocation Chain entries, those with a concrete ``recipient``, carry the budget stored on -chain. Consensus resolves only keys with a nonzero budget, so a chain entry whose -``budget`` is zero is absent: the message falls through to the per-recipient -``call_key`` wildcard, and with no wildcard left an internal message fails with -``fee no_matching_allocation`` while an external one takes the unallocated path. -Synthetic recipient wildcards match independently of ``budget``; zero there means -an exhausted allocation. ``null`` removes the per-allocation cap while keeping the -execution's fee buckets. Local consumption is tracked separately: an internal key -exhausted by this execution's own emissions still wins and fails its budget check; -it never falls through. +chain. For internal messages, presence alone resolves a key: an entry whose +``budget`` is zero still shadows broader keys and, when its phase matches, fails +emission with ``out_of message_fee allocation_budget internal``, as does a key +exhausted by this execution's own emissions, tracked separately; neither falls +through. For external messages, consensus resolves only chain keys with a nonzero +budget, so a zero-budget entry is absent: the message falls through to the +per-recipient ``call_key`` wildcard, and with no wildcard left takes the +unallocated path. Synthetic recipient wildcards match independently of ``budget``; +zero there means an exhausted allocation. ``null`` removes the per-allocation cap +while keeping the execution's fee buckets. The host must preserve every existing pinned key and must not add recipient wildcards to a pinned tree. An empty list restricts internal diff --git a/docs/website/src/impl-spec/appendix/manager-api.yaml b/docs/website/src/impl-spec/appendix/manager-api.yaml index 17346a3..3bbddf3 100644 --- a/docs/website/src/impl-spec/appendix/manager-api.yaml +++ b/docs/website/src/impl-spec/appendix/manager-api.yaml @@ -889,7 +889,7 @@ components: description: Call key bytes, or null for a wildcard. budget: nullable: true - description: Stored chain budget, or the allowance of a synthetic recipient wildcard; null is uncapped. A zero-budget entry with a recipient is absent, as on chain. + description: Stored chain budget, or the allowance of a synthetic recipient wildcard; null is uncapped. A zero-budget entry with a recipient still matches internal messages and is absent for external ones, as on chain. on: type: string enum: [decided, finalized] diff --git a/docs/website/src/spec/appendix/internal-constants.rst b/docs/website/src/spec/appendix/internal-constants.rst index 24fa348..4c7a1fb 100644 --- a/docs/website/src/spec/appendix/internal-constants.rst +++ b/docs/website/src/spec/appendix/internal-constants.rst @@ -134,6 +134,13 @@ web_render_min_space Value: ``134217728`` +.. _gvm-def-consts-value-top-limits-exec-prompt-min-space: + +exec_prompt_min_space +~~~~~~~~~~~~~~~~~~~~~ + +Value: ``134217728`` + .. _gvm-def-consts-value-top-limits-max-fds: max_fds diff --git a/executors/v0.2.x b/executors/v0.2.x index b08736a..7cba87e 160000 --- a/executors/v0.2.x +++ b/executors/v0.2.x @@ -1 +1 @@ -Subproject commit b08736a2d9d1292c6a5c50a2d1b4b12530986a4d +Subproject commit 7cba87e0dd5278895b91fea744c75ace3b6bbab1 diff --git a/executors/v0.3.x b/executors/v0.3.x index 7e0936a..49aa574 160000 --- a/executors/v0.3.x +++ b/executors/v0.3.x @@ -1 +1 @@ -Subproject commit 7e0936a125f917a8071b66add10dd555c2a07c8f +Subproject commit 49aa5746603f9a1b41a0144a87698de62a28282a diff --git a/implementation/Cargo.lock b/implementation/Cargo.lock index ee5803e..d9a5ae6 100644 --- a/implementation/Cargo.lock +++ b/implementation/Cargo.lock @@ -29,24 +29,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "aligned" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" -dependencies = [ - "as-slice", -] - -[[package]] -name = "aligned-vec" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" -dependencies = [ - "equator", -] - [[package]] name = "allocator-api2" version = "0.2.21" @@ -118,12 +100,6 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" - [[package]] name = "archery" version = "1.2.2" @@ -133,32 +109,12 @@ dependencies = [ "triomphe", ] -[[package]] -name = "arg_enum_proc_macro" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -[[package]] -name = "as-slice" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" -dependencies = [ - "stable_deref_trait", -] - [[package]] name = "async-compression" version = "0.4.41" @@ -194,49 +150,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "av-scenechange" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" -dependencies = [ - "aligned", - "anyhow", - "arg_enum_proc_macro", - "arrayvec", - "log", - "num-rational", - "num-traits", - "pastey", - "rayon", - "thiserror", - "v_frame", - "y4m", -] - -[[package]] -name = "av1-grain" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" -dependencies = [ - "anyhow", - "arrayvec", - "log", - "nom", - "num-rational", - "v_frame", -] - -[[package]] -name = "avif-serialize" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375082f007bd67184fb9c0374614b29f9aaa604ec301635f72338bb65386a53d" -dependencies = [ - "arrayvec", -] - [[package]] name = "axum" version = "0.8.9" @@ -298,27 +211,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bit_field" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" - [[package]] name = "bitflags" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" -[[package]] -name = "bitstream-io" -version = "4.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" -dependencies = [ - "no_std_io2", -] - [[package]] name = "bitvec" version = "1.0.1" @@ -350,12 +248,6 @@ dependencies = [ "serde", ] -[[package]] -name = "built" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" - [[package]] name = "bumpalo" version = "3.20.2" @@ -402,8 +294,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", - "jobserver", - "libc", "shlex", ] @@ -473,12 +363,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - [[package]] name = "colorchoice" version = "1.0.5" @@ -787,26 +671,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" -[[package]] -name = "equator" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" -dependencies = [ - "equator-macro", -] - -[[package]] -name = "equator-macro" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -834,41 +698,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "exr" -version = "1.74.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" -dependencies = [ - "bit_field", - "half", - "lebe", - "miniz_oxide", - "rayon-core", - "smallvec", - "zune-inflate", -] - -[[package]] -name = "fax" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" -dependencies = [ - "fax_derive", -] - -[[package]] -name = "fax_derive" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "fdeflate" version = "0.3.7" @@ -1099,6 +928,7 @@ dependencies = [ "num-rational", "num-traits", "papaya", + "png", "primitive-types", "rand 0.9.4", "regex", @@ -1180,16 +1010,6 @@ dependencies = [ "wasip3", ] -[[package]] -name = "gif" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" -dependencies = [ - "color_quant", - "weezl", -] - [[package]] name = "h2" version = "0.4.13" @@ -1209,17 +1029,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "half" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" -dependencies = [ - "cfg-if", - "crunchy", - "zerocopy", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -1569,18 +1378,10 @@ checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" dependencies = [ "bytemuck", "byteorder-lite", - "color_quant", - "exr", - "gif", "image-webp", "moxcms", "num-traits", "png", - "qoi", - "ravif", - "rayon", - "rgb", - "tiff", "zune-core", "zune-jpeg", ] @@ -1595,12 +1396,6 @@ dependencies = [ "quick-error", ] -[[package]] -name = "imgref" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" - [[package]] name = "impl-codec" version = "0.7.1" @@ -1653,17 +1448,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "interpolate_name" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "ipconfig" version = "0.3.4" @@ -1699,31 +1483,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - [[package]] name = "js-sys" version = "0.3.95" @@ -1766,28 +1531,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" -[[package]] -name = "lebe" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" - [[package]] name = "libc" version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" -[[package]] -name = "libfuzzer-sys" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" -dependencies = [ - "arbitrary", - "cc", -] - [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -1827,15 +1576,6 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -[[package]] -name = "loop9" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" -dependencies = [ - "imgref", -] - [[package]] name = "lru-slab" version = "0.1.2" @@ -1867,16 +1607,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" -[[package]] -name = "maybe-rayon" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" -dependencies = [ - "cfg-if", - "rayon", -] - [[package]] name = "memchr" version = "2.8.0" @@ -1969,36 +1699,6 @@ dependencies = [ "pxfm", ] -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "no_std_io2" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b51ed7824b6e07d354605f4abb3d9d300350701299da96642ee084f5ce631550" -dependencies = [ - "memchr", -] - -[[package]] -name = "nom" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" -dependencies = [ - "memchr", -] - -[[package]] -name = "noop_proc_macro" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" - [[package]] name = "ntapi" version = "0.4.3" @@ -2025,17 +1725,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "num-integer" version = "0.1.46" @@ -2170,18 +1859,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pastey" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" - [[package]] name = "percent-encoding" version = "2.3.2" @@ -2283,25 +1960,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "profiling" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" -dependencies = [ - "profiling-procmacros", -] - -[[package]] -name = "profiling-procmacros" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "psl-types" version = "2.0.11" @@ -2324,15 +1982,6 @@ version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" -[[package]] -name = "qoi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" -dependencies = [ - "bytemuck", -] - [[package]] name = "quick-error" version = "2.0.1" @@ -2480,76 +2129,6 @@ dependencies = [ "getrandom 0.3.4", ] -[[package]] -name = "rav1e" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" -dependencies = [ - "aligned-vec", - "arbitrary", - "arg_enum_proc_macro", - "arrayvec", - "av-scenechange", - "av1-grain", - "bitstream-io", - "built", - "cfg-if", - "interpolate_name", - "itertools", - "libc", - "libfuzzer-sys", - "log", - "maybe-rayon", - "new_debug_unreachable", - "noop_proc_macro", - "num-derive", - "num-traits", - "paste", - "profiling", - "rand 0.9.4", - "rand_chacha 0.9.0", - "simd_helpers", - "thiserror", - "v_frame", - "wasm-bindgen", -] - -[[package]] -name = "ravif" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" -dependencies = [ - "avif-serialize", - "imgref", - "loop9", - "quick-error", - "rav1e", - "rayon", - "rgb", -] - -[[package]] -name = "rayon" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - [[package]] name = "redox_syscall" version = "0.5.18" @@ -2657,12 +2236,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" -[[package]] -name = "rgb" -version = "0.8.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" - [[package]] name = "ring" version = "0.17.14" @@ -3010,15 +2583,6 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" -[[package]] -name = "simd_helpers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" -dependencies = [ - "quote", -] - [[package]] name = "slab" version = "0.4.12" @@ -3153,20 +2717,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tiff" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" -dependencies = [ - "fax", - "flate2", - "half", - "quick-error", - "weezl", - "zune-jpeg", -] - [[package]] name = "time" version = "0.3.47" @@ -3548,17 +3098,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "v_frame" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" -dependencies = [ - "aligned-vec", - "num-traits", - "wasm-bindgen", -] - [[package]] name = "version_check" version = "0.9.5" @@ -3716,12 +3255,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "weezl" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" - [[package]] name = "which" version = "7.0.3" @@ -4216,12 +3749,6 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fb433233f2df9344722454bc7e96465c9d03bff9d77c248f9e7523fe79585b5" -[[package]] -name = "y4m" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" - [[package]] name = "yoke" version = "0.8.2" @@ -4337,15 +3864,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" -[[package]] -name = "zune-inflate" -version = "0.2.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" -dependencies = [ - "simd-adler32", -] - [[package]] name = "zune-jpeg" version = "0.5.15" diff --git a/implementation/Cargo.toml b/implementation/Cargo.toml index 80a3b90..8e52421 100644 --- a/implementation/Cargo.toml +++ b/implementation/Cargo.toml @@ -37,13 +37,18 @@ clap = { version = "4.5.35", features = ["derive"] } crossbeam = "0.8.4" futures-util = "0.3.31" hex = "0.4.3" -image = { version = "0.25.8", features = ["png", "jpeg", "webp"] } +image = { version = "0.25.8", default-features = false, features = [ + "png", + "jpeg", + "webp", +] } libc = "0.2" num-bigint = { version = "0.4.6", features = ["serde"] } num-integer = { version = "0.1.46" } num-rational = { version = "0.4.2" } num-traits = { version = "0.2.19" } papaya = "0.2.3" +png = { version = "0.18.1", default-features = false } primitive-types = { version = "0.13", features = ["impl-serde"] } rand = { version = "0.9.1", features = ["std_rng"] } regex = "1.11.1" diff --git a/implementation/src/llm/handler.rs b/implementation/src/llm/handler.rs index eb599f1..afbae74 100644 --- a/implementation/src/llm/handler.rs +++ b/implementation/src/llm/handler.rs @@ -115,7 +115,21 @@ impl crate::common::MessageHandler .into()); } - if prompt::ImageType::sniff(img.as_ref()).is_none() { + let decodes = match prompt::ImageType::sniff(img.as_ref()) { + None => false, + Some(kind) => { + let img = img.clone(); + // a decoder panic is the image's fault, not the manager's + match tokio::task::spawn_blocking(move || kind.decodes(&img)).await { + Ok(decodes) => decodes, + Err(error) => { + log_warn_into!(@user,&LoggerWithId, genvm_id:id = self.0.genvm_id.0, error:err = error; "image decoder failed"); + false + } + } + } + }; + if !decodes { return Err(ModuleError { causes: vec!["INVALID_IMAGE".into()], fatal: false, diff --git a/implementation/src/llm/prompt.rs b/implementation/src/llm/prompt.rs index c81ed35..a8af696 100644 --- a/implementation/src/llm/prompt.rs +++ b/implementation/src/llm/prompt.rs @@ -52,6 +52,12 @@ impl Internal { } } +/// Matches the largest side the strictest provider (Anthropic) accepts +pub const IMAGE_MAX_SIDE: u32 = 8000; +/// Fits an 8-bit RGB JPEG at [`IMAGE_MAX_SIDE`] squared. Independent of the +/// executor's `EXEC_PROMPT_MIN_SPACE`, which reserves contract memory instead +const IMAGE_MAX_ALLOC: u64 = 256 * 1024 * 1024; + #[derive(Serialize, Deserialize, Clone, Copy)] pub enum ImageType { PNG, @@ -69,6 +75,15 @@ impl ImageType { } } + /// Whether `data` fully decodes as `self` within [`IMAGE_MAX_SIDE`]; it is + /// CPU-bound, so call it off the async runtime + pub fn decodes(self, data: &[u8]) -> bool { + match self { + Self::JPG => jpeg_decodes(data), + Self::PNG => png_decodes(data).unwrap_or(false), + } + } + pub fn media_type(self) -> &'static str { match self { Self::JPG => "image/jpeg", @@ -77,6 +92,37 @@ impl ImageType { } } +/// Holds the whole bitmap in memory, bounded by [`IMAGE_MAX_ALLOC`] +fn jpeg_decodes(data: &[u8]) -> bool { + let mut limits = image::Limits::default(); + limits.max_image_width = Some(IMAGE_MAX_SIDE); + limits.max_image_height = Some(IMAGE_MAX_SIDE); + limits.max_alloc = Some(IMAGE_MAX_ALLOC); + + let mut reader = + image::ImageReader::with_format(std::io::Cursor::new(data), image::ImageFormat::Jpeg); + reader.limits(limits); + reader.decode().is_ok() +} + +/// Streams rows to the end of the file, so memory stays a few rows whatever the +/// declared size +fn png_decodes(data: &[u8]) -> Result { + let mut decoder = png::Decoder::new(std::io::Cursor::new(data)); + // expanding is what rejects a palette image that has no `PLTE` + decoder.set_transformations(png::Transformations::EXPAND); + + let header = decoder.read_header_info()?; + if header.width > IMAGE_MAX_SIDE || header.height > IMAGE_MAX_SIDE { + return Ok(false); + } + + let mut reader = decoder.read_info()?; + while reader.next_row()?.is_some() {} + reader.finish()?; + Ok(true) +} + #[derive(Serialize, Deserialize)] pub struct ImageLua(#[serde(with = "serde_bytes")] pub Vec); diff --git a/implementation/tests/data/progressive.jpg b/implementation/tests/data/progressive.jpg new file mode 100644 index 0000000..3617b8f Binary files /dev/null and b/implementation/tests/data/progressive.jpg differ diff --git a/implementation/tests/image_decode.rs b/implementation/tests/image_decode.rs new file mode 100644 index 0000000..437e6d1 --- /dev/null +++ b/implementation/tests/image_decode.rs @@ -0,0 +1,192 @@ +use genvm_modules::llm::prompt; + +fn hex(s: &str) -> Vec { + (0..s.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()) + .collect() +} + +fn encode(img: image::DynamicImage, format: image::ImageFormat) -> Vec { + let mut out = std::io::Cursor::new(Vec::new()); + img.write_to(&mut out, format).unwrap(); + out.into_inner() +} + +fn rgb() -> image::DynamicImage { + image::DynamicImage::ImageRgb8(image::RgbImage::from_pixel( + 64, + 64, + image::Rgb([200, 30, 30]), + )) +} + +/// Encoded row by row, so the test never holds the bitmap either +fn black_rgba16_png(side: u32) -> Vec { + let mut data = Vec::new(); + let mut encoder = png::Encoder::new(&mut data, side, side); + encoder.set_color(png::ColorType::Rgba); + encoder.set_depth(png::BitDepth::Sixteen); + encoder.set_compression(png::Compression::Fastest); + let mut writer = encoder.write_header().unwrap(); + let mut stream = writer.stream_writer().unwrap(); + let row = vec![0u8; side as usize * 8]; + for _ in 0..side { + std::io::Write::write_all(&mut stream, &row).unwrap(); + } + stream.finish().unwrap(); + writer.finish().unwrap(); + data +} + +fn accepts(data: &[u8]) -> bool { + let kind = prompt::ImageType::sniff(data).expect("test data must pass the header sniff"); + kind.decodes(data) +} + +// -- Well-formed images -- + +#[test] +fn accepts_png() { + assert!(accepts(&encode(rgb(), image::ImageFormat::Png))); +} + +#[test] +fn accepts_jpeg() { + assert!(accepts(&encode(rgb(), image::ImageFormat::Jpeg))); +} + +#[test] +fn accepts_progressive_jpeg() { + assert!(accepts(include_bytes!("data/progressive.jpg"))); +} + +#[test] +fn accepts_grey_jpeg() { + let img = image::DynamicImage::ImageLuma8(image::GrayImage::new(16, 16)); + assert!(accepts(&encode(img, image::ImageFormat::Jpeg))); +} + +#[test] +fn accepts_16_bit_rgba_png() { + let img = image::DynamicImage::ImageRgba16(image::ImageBuffer::new(16, 16)); + assert!(accepts(&encode(img, image::ImageFormat::Png))); +} + +#[test] +fn accepts_png_with_trailing_junk() { + let mut data = encode(rgb(), image::ImageFormat::Png); + data.extend_from_slice(&[0; 1000]); + assert!(accepts(&data)); +} + +// -- Header passes, body does not -- + +#[test] +fn rejects_png_magic_with_garbage() { + let mut data = b"\x89PNG\r\n\x1a\n".to_vec(); + data.extend(b"garbage".repeat(50)); + assert!(!accepts(&data)); +} + +#[test] +fn rejects_bare_png_magic() { + assert!(!accepts(b"\x89PNG\r\n\x1a\n")); +} + +#[test] +fn rejects_jfif_magic_with_garbage() { + let mut data = b"\xff\xd8\xff\xe0".to_vec(); + data.extend(b"garbage".repeat(50)); + assert!(!accepts(&data)); +} + +#[test] +fn rejects_truncated_png() { + let data = encode(rgb(), image::ImageFormat::Png); + assert!(!accepts(&data[..data.len() / 2])); +} + +#[test] +fn rejects_truncated_jpeg() { + let data = encode(rgb(), image::ImageFormat::Jpeg); + assert!(!accepts(&data[..data.len() / 2])); +} + +#[test] +fn rejects_jpeg_magic_over_png_body() { + let mut data = b"\xff\xd8\xff\xe0".to_vec(); + data.extend(encode(rgb(), image::ImageFormat::Png)); + assert!(!accepts(&data)); +} + +#[test] +fn rejects_png_magic_over_jpeg_body() { + let mut data = b"\x89PNG\r\n\x1a\n".to_vec(); + data.extend(encode(rgb(), image::ImageFormat::Jpeg)); + assert!(!accepts(&data)); +} + +// -- Malformed PNG structure -- + +#[test] +fn rejects_palette_png_without_plte() { + assert!(!accepts(&hex("89504e470d0a1a0a0000000d49484452000000040000000408030000009e2f6e4c0000000e4944415478da636004020654020000b40011197e69d80000000049454e44ae426082"))); +} + +#[test] +fn rejects_palette_png_with_16_bit_depth() { + assert!(!accepts(&hex("89504e470d0a1a0a0000000d4948445200000004000000041003000000cebfb20f00000003504c5445000000a77a3dda0000000b4944415478da6360200c000024000125c2a8e30000000049454e44ae426082"))); +} + +#[test] +fn rejects_png_with_invalid_color_type() { + assert!(!accepts(&hex("89504e470d0a1a0a0000000d4948445200000004000000040805000000bb4431900000000b4944415478da6360c0040000140001ee5a69090000000049454e44ae426082"))); +} + +#[test] +fn rejects_png_with_zero_dimensions() { + assert!(!accepts(&hex("89504e470d0a1a0a0000000d4948445200000000000000000802000000b4e9eb45000000084944415478da0300000000016fddc9910000000049454e44ae426082"))); +} + +#[test] +fn rejects_png_with_bad_header_crc() { + assert!(!accepts(&hex("89504e470d0a1a0a0000000d4948445200000004000000040802000000000000000000000e4944415478da636840020cc47100705218019eaebe6f0000000049454e44ae426082"))); +} + +// -- Dimension and allocation limits -- + +#[test] +fn rejects_png_declaring_huge_dimensions() { + assert!(!accepts(&hex("89504e470d0a1a0a0000000d494844527fffffff7fffffff08020000009bab9c310000000b4944415478da636040050000100001aa19f8820000000049454e44ae426082"))); +} + +#[test] +fn rejects_png_wider_than_the_limit() { + let img = image::DynamicImage::ImageLuma8(image::GrayImage::new(prompt::IMAGE_MAX_SIDE + 1, 1)); + assert!(!accepts(&encode(img, image::ImageFormat::Png))); +} + +#[test] +fn rejects_png_taller_than_the_limit() { + let img = image::DynamicImage::ImageLuma8(image::GrayImage::new(1, prompt::IMAGE_MAX_SIDE + 1)); + assert!(!accepts(&encode(img, image::ImageFormat::Png))); +} + +#[test] +fn rejects_jpeg_wider_than_the_limit() { + let img = image::DynamicImage::ImageLuma8(image::GrayImage::new(prompt::IMAGE_MAX_SIDE + 1, 8)); + assert!(!accepts(&encode(img, image::ImageFormat::Jpeg))); +} + +#[test] +fn accepts_16_bit_rgba_png_at_the_side_limit() { + // 512 MB decoded, which a whole-bitmap decode under the JPEG budget would refuse + assert!(accepts(&black_rgba16_png(prompt::IMAGE_MAX_SIDE))); +} + +#[test] +fn accepts_png_at_the_side_limit() { + let img = image::DynamicImage::ImageLuma8(image::GrayImage::new(prompt::IMAGE_MAX_SIDE, 1)); + assert!(accepts(&encode(img, image::ImageFormat::Png))); +} diff --git a/support/ci/__main__.py b/support/ci/__main__.py index 1ecd972..e76636f 100644 --- a/support/ci/__main__.py +++ b/support/ci/__main__.py @@ -8,6 +8,7 @@ from tools import ( branches, deploy_docs, + fast_forward_command, full_tests_command, make_release_notes, open_executor_prs, @@ -30,6 +31,7 @@ *versions.COMMANDS, *branches.COMMANDS, *deploy_docs.COMMANDS, + *fast_forward_command.COMMANDS, *full_tests_command.COMMANDS, *pr_branches.COMMANDS, *make_release_notes.COMMANDS, diff --git a/support/ci/tools/fast_forward_command.py b/support/ci/tools/fast_forward_command.py new file mode 100644 index 0000000..65fc649 --- /dev/null +++ b/support/ci/tools/fast_forward_command.py @@ -0,0 +1,308 @@ +#!/usr/bin/env python3 +""" +`/genvm-fast-forward`: move a PR's base branch to the PR's exact head. + +The handler runs from the default branch with a write deploy key in scope, so it +reads the PR only through the API and never executes its code. The deploy key +bypasses the base branch's rulesets, which is why every gate lives here: + +1. The commenter is an admin, or a writer and the head carries an approval from + a writer other than the author and the commenter, with no writer's change + request outstanding +2. The base is a dev branch, or a release branch and the commenter is an admin; + the PR is not from a fork +3. `E2E Tests` and every check the base's rulesets require concluded `success` + on the head, and the rulesets hold nothing else the push could violate +4. The head is strictly ahead of the base tip, without merge commits + +The push is a plain non-force push of the verified SHA, so the server enforces +the fast-forward again. Executor branches follow through the usual sync of a +manager branch push +""" + +import argparse +import json +import os +import re +import subprocess +import urllib.parse + +import ci_lib +import gh_common +from gh_common import pr_number, repo +from tools.full_tests_command import best_effort_add_reaction, delete_reaction + +COMMAND = '/genvm-fast-forward' +E2E_CHECK = 'E2E Tests' +E2E_APP = 2929262 +DEV_BRANCH_RE = re.compile(r'^v\d+\.\d+-dev$') +RELEASE_BRANCH_RE = re.compile(r'^v\d+\.\d+$') +# what the push itself must honor; everything else is the bypass's business +UNDERSTOOD_RULES = frozenset( + { + 'creation', + 'deletion', + 'non_fast_forward', + 'required_linear_history', + 'required_status_checks', + 'update', + } +) + + +class Refused(RuntimeError): + pass + + +def gh_json(path: str) -> dict: + return json.loads(gh_common.gh_manager('api', path)) + + +def gh_items(path: str, jq: str = '.[]') -> list[dict]: + out = gh_common.gh_manager('api', '--paginate', path, '--jq', f'{jq} | @json') + return [json.loads(line) for line in out.splitlines()] + + +def permission(login: str) -> str | None: + result = gh_common.gh( + 'api', + f'repos/{repo()}/collaborators/{urllib.parse.quote(login)}/permission', + token=gh_common.manager_token(), + check=False, + ) + if result.returncode == 0: + return json.loads(result.stdout).get('permission') + # anything but "not a collaborator" must not quietly drop a change request + if 'HTTP 403' in result.stderr or 'HTTP 404' in result.stderr: + return None + raise RuntimeError(f'could not read the permission of `{login}`') + + +def base_rules(base: str) -> list[dict]: + return gh_items(f'repos/{repo()}/rules/branches/{urllib.parse.quote(base, safe="")}') + + +def rule_failures(rules: list[dict]) -> list[str]: + return [ + f'the base has a `{rule["type"]}` rule this command does not enforce' + for rule in rules + if rule['type'] not in UNDERSTOOD_RULES + ] + + +def required_checks(rules: list[dict]) -> list[tuple[str, int | None]]: + required = [ + (check['context'], check.get('integration_id')) + for rule in rules + if rule['type'] == 'required_status_checks' + for check in rule['parameters']['required_status_checks'] + ] + if all(context != E2E_CHECK for context, _app in required): + required.append((E2E_CHECK, E2E_APP)) + return required + + +def check_failures( + required: list[tuple[str, int | None]], runs: list[dict], statuses: list[dict] +) -> list[str]: + failures = [] + for context, app in required: + matching = [ + run + for run in runs + if run['name'] == context and (app is None or run['app']['id'] == app) + ] + if matching: + # a rerun is a new check run, and GitHub judges the latest one + run = max(matching, key=lambda run: run['id']) + if run['status'] != 'completed': + failures.append(f'`{context}` is {run["status"].replace("_", " ")}') + elif run['conclusion'] != 'success': + failures.append(f'`{context}` concluded `{run["conclusion"]}`') + continue + # statuses come newest first and carry no app to match against + status = next((s for s in statuses if s['context'] == context), None) + if app is not None or status is None: + failures.append(f'`{context}` has not run') + elif status['state'] != 'success': + failures.append(f'`{context}` is `{status["state"]}`') + return failures + + +def review_failures( + *, + sender: str, + sender_permission: str | None, + author: str, + head_sha: str, + reviews: list[dict], + permission_of, +) -> list[str]: + if sender_permission == 'admin': + return [] + if sender_permission not in gh_common.WRITE_ROLES: + return [f'`{sender}` is neither an admin nor a writer'] + + # reviews come oldest first; a plain comment neither grants nor revokes + latest: dict[str, dict] = {} + for review in reviews: + login = (review.get('user') or {}).get('login') + if login and review['state'] in ('APPROVED', 'CHANGES_REQUESTED', 'DISMISSED'): + latest[login] = review + + failures = [] + approved = False + for login, review in latest.items(): + if ( + review['user'].get('type') != 'User' + or permission_of(login) not in gh_common.WRITE_ROLES + ): + continue + if review['state'] == 'CHANGES_REQUESTED': + failures.append(f'`{login}` requested changes') + elif ( + review['state'] == 'APPROVED' + and review['commit_id'] == head_sha + and login not in (author, sender) + ): + approved = True + if not approved: + failures.append( + f'no writer other than the author and `{sender}` approved `{head_sha[:12]}`' + ) + return failures + + +def gate(pr: dict, sender: str) -> list[str]: + if pr['state'] != 'open': + return ['the PR is not open'] + base = pr['base']['ref'] + head_sha = pr['head']['sha'] + failures = [] + if pr['draft']: + failures.append('the PR is a draft') + # a fork skips the executor precondition, so its gitlinks are unproven + head_repo = pr['head'].get('repo') + if head_repo is None or head_repo['full_name'] != repo(): + failures.append('the PR comes from a fork') + sender_permission = permission(sender) + if RELEASE_BRANCH_RE.fullmatch(base): + if sender_permission != 'admin': + failures.append(f'only an admin may fast-forward the release branch `{base}`') + elif not DEV_BRANCH_RE.fullmatch(base): + failures.append(f'`{base}` is neither a dev nor a release branch') + + rules = base_rules(base) + failures += rule_failures(rules) + failures += review_failures( + sender=sender, + sender_permission=sender_permission, + author=pr['user']['login'], + head_sha=head_sha, + reviews=gh_items(f'repos/{repo()}/pulls/{pr_number()}/reviews?per_page=100'), + permission_of=permission, + ) + failures += check_failures( + required_checks(rules), + gh_items( + f'repos/{repo()}/commits/{head_sha}/check-runs?per_page=100', '.check_runs[]' + ), + gh_items(f'repos/{repo()}/commits/{head_sha}/statuses?per_page=100'), + ) + + compared = gh_json( + f'repos/{repo()}/compare/{urllib.parse.quote(base, safe="")}...{head_sha}' + )['status'] + if compared == 'identical': + failures.append(f'`{base}` is already at `{head_sha[:12]}`') + elif compared != 'ahead': + failures.append(f'`{head_sha[:12]}` is {compared} relative to `{base}`; rebase it') + return failures + + +def git(*args: str) -> subprocess.CompletedProcess: + return ci_lib.run(['git', *args], check=False, capture_output=True) + + +def fast_forward(base: str, head_sha: str) -> None: + fetched = git( + 'fetch', + '--no-tags', + 'origin', + f'+refs/heads/{base}:refs/genvm-ff/base', + f'+refs/pull/{pr_number()}/head:refs/genvm-ff/head', + ) + if fetched.returncode != 0: + raise RuntimeError( + f'could not fetch `{base}` and the PR head: {fetched.stderr.strip()}' + ) + # a head pushed during the checks must not land unverified + if git('rev-parse', 'refs/genvm-ff/head').stdout.strip() != head_sha: + raise Refused('the head moved during verification; comment again') + merges = git('rev-list', '--min-parents=2', 'refs/genvm-ff/base..refs/genvm-ff/head') + if merges.returncode != 0 or merges.stdout.strip(): + raise Refused(f'`{base}` requires linear history, and the PR has merge commits') + pushed = git('push', 'origin', f'{head_sha}:refs/heads/{base}') + if pushed.returncode != 0: + raise Refused(f'`{base}` rejected the push:\n```\n{pushed.stderr.strip()}\n```') + + +def comment(body: str) -> None: + gh_common.gh_manager( + 'api', + '--method', + 'POST', + f'repos/{repo()}/issues/{pr_number()}/comments', + '-f', + f'body={body}', + retry=False, + ) + + +def run() -> int: + if os.environ.get('COMMENT_BODY', '').strip() != COMMAND: + print('comment is not an exact /genvm-fast-forward command; ignoring') + return 0 + request = os.environ['COMMENT_ID'] + sender = os.environ['SENDER'] + run_url = os.environ['RUN_URL'] + eyes = best_effort_add_reaction(request, 'eyes') + + def finish(reaction: str, body: str) -> None: + if eyes is not None: + delete_reaction(request, eyes) + best_effort_add_reaction(request, reaction) + comment(f'{body}\n\n[Open run]({run_url})') + + try: + pr = gh_json(f'repos/{repo()}/pulls/{pr_number()}') + base = pr['base']['ref'] + head_sha = pr['head']['sha'] + if failures := gate(pr, sender): + raise Refused('\n'.join(f'- {failure}' for failure in failures)) + fast_forward(base, head_sha) + except Refused as refusal: + finish('confused', f'😕 Not fast-forwarding:\n{refusal}') + return 1 + except Exception: + finish('confused', '😕 `/genvm-fast-forward` failed unexpectedly') + raise + finish('rocket', f'🚀 Fast-forwarded `{base}` to `{head_sha[:12]}`') + return 0 + + +class FastForwardCommand(ci_lib.Tool): + """Fast-forward a PR's base branch to its head on `/genvm-fast-forward`.""" + + def name(self) -> str: + return 'fast-forward-command' + + def add_to(self, parser: argparse.ArgumentParser) -> None: + gh_common.add_args(parser, executor_repo=False, head_ref=False) + + def handler(self, args: argparse.Namespace) -> int: + gh_common.set_ctx(gh_common.Ctx.from_args(args)) + return run() + + +COMMANDS = [FastForwardCommand()] diff --git a/support/ci/unit_tests/test_fast_forward_command.py b/support/ci/unit_tests/test_fast_forward_command.py new file mode 100644 index 0000000..41d063d --- /dev/null +++ b/support/ci/unit_tests/test_fast_forward_command.py @@ -0,0 +1,391 @@ +import subprocess + +import gh_common +import pytest +from tools import fast_forward_command as command + +HEAD = 'a' * 40 +OLD = 'b' * 40 +E2E_APP = 2929262 +ACTIONS_APP = 15368 +REQUIRED = [('validate-end', ACTIONS_APP), ('E2E Tests', E2E_APP)] + + +@pytest.fixture(autouse=True) +def context(): + gh_common.set_ctx( + gh_common.Ctx( + manager_repo='org/manager', + executor_repo='org/executor', + _pr_number='42', + _head_ref=None, + ) + ) + + +def check_run(name, app, conclusion='success', *, id=1, status='completed'): + return { + 'id': id, + 'name': name, + 'app': {'id': app}, + 'status': status, + 'conclusion': conclusion if status == 'completed' else None, + } + + +def green_runs(): + return [check_run('validate-end', ACTIONS_APP), check_run('E2E Tests', E2E_APP)] + + +def review(login, state, commit=HEAD, user_type='User'): + return { + 'user': {'login': login, 'type': user_type}, + 'state': state, + 'commit_id': commit, + } + + +PERMISSIONS = { + 'admin': 'admin', + 'writer': 'write', + 'other-writer': 'write', + 'reader': 'read', + 'coderabbitai[bot]': 'none', +} + + +def reviews_gate(reviews, sender='writer', author='author'): + return command.review_failures( + sender=sender, + sender_permission=PERMISSIONS.get(sender), + author=author, + head_sha=HEAD, + reviews=reviews, + permission_of=PERMISSIONS.get, + ) + + +# -- Checks -- + + +def test_green_required_checks_pass(): + assert command.check_failures(REQUIRED, green_runs(), []) == [] + + +def test_missing_e2e_fails(): + runs = [check_run('validate-end', ACTIONS_APP)] + assert command.check_failures(REQUIRED, runs, []) == ['`E2E Tests` has not run'] + + +def test_pending_check_fails(): + runs = [ + check_run('validate-end', ACTIONS_APP), + check_run('E2E Tests', E2E_APP, status='in_progress'), + ] + assert command.check_failures(REQUIRED, runs, []) == ['`E2E Tests` is in progress'] + + +def test_skipped_check_fails(): + runs = [ + check_run('validate-end', ACTIONS_APP, 'skipped'), + check_run('E2E Tests', E2E_APP), + ] + assert command.check_failures(REQUIRED, runs, []) == [ + '`validate-end` concluded `skipped`' + ] + + +def test_latest_rerun_decides(): + runs = [ + check_run('validate-end', ACTIONS_APP, 'failure', id=1), + check_run('validate-end', ACTIONS_APP, 'success', id=2), + check_run('E2E Tests', E2E_APP), + ] + assert command.check_failures(REQUIRED, runs, []) == [] + runs[0]['id'] = 3 + assert command.check_failures(REQUIRED, runs, []) == [ + '`validate-end` concluded `failure`' + ] + + +def test_same_name_from_another_app_does_not_count(): + runs = [check_run('validate-end', ACTIONS_APP), check_run('E2E Tests', ACTIONS_APP)] + assert command.check_failures(REQUIRED, runs, []) == ['`E2E Tests` has not run'] + + +def test_status_satisfies_a_check_without_app(): + required = [('CodeRabbit', None)] + statuses = [ + {'context': 'CodeRabbit', 'state': 'success'}, + {'context': 'CodeRabbit', 'state': 'pending'}, + ] + assert command.check_failures(required, [], statuses) == [] + statuses.reverse() + assert command.check_failures(required, [], statuses) == ['`CodeRabbit` is `pending`'] + + +def test_status_does_not_satisfy_a_check_bound_to_an_app(): + statuses = [{'context': 'E2E Tests', 'state': 'success'}] + runs = [check_run('validate-end', ACTIONS_APP)] + assert command.check_failures(REQUIRED, runs, statuses) == ['`E2E Tests` has not run'] + + +RULES = [ + { + 'type': 'required_status_checks', + 'parameters': { + 'required_status_checks': [ + {'context': 'validate-end', 'integration_id': ACTIONS_APP} + ] + }, + }, + {'type': 'non_fast_forward', 'parameters': None}, +] + + +def test_e2e_is_required_from_its_app_even_when_rules_omit_it(): + assert command.required_checks(RULES) == [ + ('validate-end', ACTIONS_APP), + ('E2E Tests', E2E_APP), + ] + + +def test_unknown_rule_fails_closed(): + rules = [*RULES, {'type': 'pull_request', 'parameters': {}}] + assert command.rule_failures(RULES) == [] + assert command.rule_failures(rules) == [ + 'the base has a `pull_request` rule this command does not enforce' + ] + + +# -- Reviews -- + + +def test_admin_needs_no_approval(): + assert reviews_gate([], sender='admin') == [] + + +def test_reader_is_refused(): + assert reviews_gate([review('other-writer', 'APPROVED')], sender='reader') == [ + '`reader` is neither an admin nor a writer' + ] + + +def test_writer_with_writer_approval_passes(): + assert reviews_gate([review('other-writer', 'APPROVED')]) == [] + + +def test_writer_without_approval_fails(): + assert reviews_gate([]) == [ + f'no writer other than the author and `writer` approved `{HEAD[:12]}`' + ] + + +def test_stale_approval_does_not_count(): + assert reviews_gate([review('other-writer', 'APPROVED', commit=OLD)]) != [] + + +def test_bot_and_reader_approvals_do_not_count(): + reviews = [ + review('coderabbitai[bot]', 'APPROVED', user_type='Bot'), + review('reader', 'APPROVED'), + ] + assert reviews_gate(reviews) != [] + + +def test_author_approval_does_not_count(): + assert reviews_gate([review('writer', 'APPROVED')], author='writer') != [] + + +def test_commenter_approval_does_not_count(): + assert reviews_gate([review('writer', 'APPROVED')], sender='writer') != [] + + +def test_later_comment_keeps_approval(): + assert ( + reviews_gate( + [review('other-writer', 'APPROVED'), review('other-writer', 'COMMENTED')] + ) + == [] + ) + + +def test_dismissed_approval_does_not_count(): + assert ( + reviews_gate( + [review('other-writer', 'APPROVED'), review('other-writer', 'DISMISSED')] + ) + != [] + ) + + +def test_writer_change_request_blocks_despite_approval(): + reviews = [ + review('other-writer', 'APPROVED'), + review('admin', 'CHANGES_REQUESTED', commit=OLD), + ] + assert reviews_gate(reviews) == ['`admin` requested changes'] + + +def test_reader_change_request_does_not_block(): + assert ( + reviews_gate( + [review('other-writer', 'APPROVED'), review('reader', 'CHANGES_REQUESTED')] + ) + == [] + ) + + +def test_deleted_reviewer_is_ignored(): + reviews = [ + {'user': None, 'state': 'APPROVED', 'commit_id': HEAD}, + review('other-writer', 'APPROVED'), + ] + assert reviews_gate(reviews) == [] + + +# -- Command -- + + +def pr(**overrides): + return { + 'state': 'open', + 'draft': False, + 'user': {'login': 'author'}, + 'base': {'ref': 'v0.6-dev', 'repo': {'default_branch': 'main'}}, + 'head': {'sha': HEAD, 'repo': {'full_name': 'org/manager'}}, + **overrides, + } + + +@pytest.fixture +def api(monkeypatch): + state = { + 'compare': 'ahead', + 'pr': pr(), + 'rules': RULES, + 'pushed': [], + 'comments': [], + 'reactions': [], + } + + def gh_json(path): + if '/compare/' in path: + return {'status': state['compare']} + return state['pr'] + + def gh_items(path, jq='.[]'): + if path.endswith('/reviews?per_page=100'): + return [review('other-writer', 'APPROVED')] + if '/check-runs' in path: + return green_runs() + return [] + + monkeypatch.setattr(command, 'gh_json', gh_json) + monkeypatch.setattr(command, 'gh_items', gh_items) + monkeypatch.setattr(command, 'base_rules', lambda _base: state['rules']) + monkeypatch.setattr(command, 'permission', PERMISSIONS.get) + monkeypatch.setattr( + command, 'fast_forward', lambda *args: state['pushed'].append(args) + ) + monkeypatch.setattr(command, 'comment', state['comments'].append) + monkeypatch.setattr( + command, + 'best_effort_add_reaction', + lambda _comment, content: state['reactions'].append(content) or '7', + ) + monkeypatch.setattr( + command, 'delete_reaction', lambda *_args: state['reactions'].append('-') + ) + monkeypatch.setenv('COMMENT_BODY', '/genvm-fast-forward\n') + monkeypatch.setenv('COMMENT_ID', '9001') + monkeypatch.setenv('SENDER', 'writer') + monkeypatch.setenv('RUN_URL', 'https://example.test/run/1') + return state + + +def test_green_pr_is_fast_forwarded(api): + assert command.run() == 0 + assert api['pushed'] == [('v0.6-dev', HEAD)] + assert api['reactions'] == ['eyes', '-', 'rocket'] + assert api['comments'][0].startswith(f'🚀 Fast-forwarded `v0.6-dev` to `{HEAD[:12]}`') + + +def test_other_comment_is_ignored(api, monkeypatch): + monkeypatch.setenv('COMMENT_BODY', '/genvm-fast-forward please') + assert command.run() == 0 + assert api['pushed'] == api['reactions'] == [] + + +def test_refusal_lists_every_failure(api): + api['pr'] = pr( + draft=True, + base={'ref': 'main', 'repo': {'default_branch': 'main'}}, + head={'sha': HEAD, 'repo': {'full_name': 'someone/manager'}}, + ) + api['compare'] = 'diverged' + assert command.run() == 1 + assert api['pushed'] == [] + assert api['reactions'] == ['eyes', '-', 'confused'] + body = api['comments'][0] + assert '- the PR is a draft' in body, body + assert '- the PR comes from a fork' in body, body + assert '- `main` is neither a dev nor a release branch' in body, body + assert f'- `{HEAD[:12]}` is diverged relative to `main`; rebase it' in body, body + + +@pytest.mark.parametrize(('sender', 'refused'), [('writer', True), ('admin', False)]) +def test_release_branch_needs_an_admin(api, monkeypatch, sender, refused): + monkeypatch.setenv('SENDER', sender) + api['pr'] = pr(base={'ref': 'v0.6', 'repo': {'default_branch': 'main'}}) + assert command.run() == int(refused) + assert (api['pushed'] == []) == refused + + +def test_unreadable_permission_raises(monkeypatch): + monkeypatch.setattr( + gh_common, + 'gh', + lambda *_args, **_kwargs: subprocess.CompletedProcess([], 1, '', 'HTTP 502'), + ) + with pytest.raises(RuntimeError): + command.permission('writer') + monkeypatch.setattr( + gh_common, + 'gh', + lambda *_args, **_kwargs: subprocess.CompletedProcess([], 1, '', 'HTTP 404'), + ) + assert command.permission('stranger') is None + + +def test_up_to_date_base_is_refused(api): + api['compare'] = 'identical' + assert command.run() == 1 + assert api['pushed'] == [] + + +def test_closed_pr_is_refused(api): + api['pr'] = pr(state='closed') + assert command.run() == 1 + assert '- the PR is not open' in api['comments'][0] + + +def test_rejected_push_is_reported(api, monkeypatch): + def reject(*_args): + raise command.Refused('`v0.6-dev` rejected the push') + + monkeypatch.setattr(command, 'fast_forward', reject) + assert command.run() == 1 + assert api['reactions'][-1] == 'confused' + assert 'rejected the push' in api['comments'][0] + + +def test_unexpected_error_is_reported_and_raised(api, monkeypatch): + def boom(_path): + raise RuntimeError('api down') + + monkeypatch.setattr(command, 'gh_json', boom) + with pytest.raises(RuntimeError): + command.run() + assert api['reactions'][-1] == 'confused' + assert 'failed unexpectedly' in api['comments'][0] diff --git a/webdriver/Dockerfile b/webdriver/Dockerfile index 1ad8d17..339f081 100644 --- a/webdriver/Dockerfile +++ b/webdriver/Dockerfile @@ -36,7 +36,7 @@ RUN \ xdg-utils \ chromium \ chromium-sandbox ; \ - curl -fsSL https://deb.nodesource.com/setup_20.x | bash - ; \ + curl -fsSL https://deb.nodesource.com/setup_24.x | bash - ; \ apt-get install -y nodejs ; \ apt-get clean ; \ rm -rf /var/lib/apt/lists/* ; \ diff --git a/webdriver/src/prj/package-lock.json b/webdriver/src/prj/package-lock.json index 7452abc..c740a04 100644 --- a/webdriver/src/prj/package-lock.json +++ b/webdriver/src/prj/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "dependencies": { "commander": "^11.0.0", - "puppeteer-core": "24.16.0" + "puppeteer-core": "25.12.0" }, "devDependencies": { "@types/node": "^20.0.0", @@ -17,7 +17,7 @@ "typescript": "^5.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=22.12.0" } }, "node_modules/@cspotcode/source-map-support": { @@ -62,32 +62,33 @@ } }, "node_modules/@puppeteer/browsers": { - "version": "2.10.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.6.tgz", - "integrity": "sha512-pHUn6ZRt39bP3698HFQlu2ZHCkS/lPcpv7fVQcGBSzNNygw171UXAKrCUhy+TEMw4lEttOKDgNpb04hwUAJeiQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-3.2.3.tgz", + "integrity": "sha512-2Bt3m6dDAJqmZehn0wiXSYyQmuhyiHHZ7FjvrVaS1W2RoFGV6eCK71UvKYkNJjkwC1Q9nobjhvTMhLQztFVINA==", "license": "Apache-2.0", "dependencies": { - "debug": "^4.4.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" + "modern-tar": "^0.8.4", + "yargs": "^18.0.0" }, "bin": { - "browsers": "lib/cjs/main-cli.js" + "browsers": "lib/main-cli.js" }, "engines": { - "node": ">=18" + "node": ">=22.12.0" + }, + "peerDependencies": { + "proxy-agent": ">=8.0.1", + "yauzl": "^2.10.0 || ^3.4.0" + }, + "peerDependenciesMeta": { + "proxy-agent": { + "optional": true + }, + "yauzl": { + "optional": true + } } }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "license": "MIT" - }, "node_modules/@tsconfig/node10": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", @@ -120,22 +121,12 @@ "version": "20.19.27", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -162,34 +153,25 @@ "node": ">=0.4.0" } }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" @@ -202,186 +184,53 @@ "dev": true, "license": "MIT" }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/b4a": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", - "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", - "license": "Apache-2.0", - "peerDependencies": { - "react-native-b4a": "*" - }, - "peerDependenciesMeta": { - "react-native-b4a": { - "optional": true - } - } - }, - "node_modules/bare-events": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", - "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", - "license": "Apache-2.0", - "peerDependencies": { - "bare-abort-controller": "*" - }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - } - } - }, - "node_modules/bare-fs": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.2.tgz", - "integrity": "sha512-veTnRzkb6aPHOvSKIOy60KzURfBdUflr5VReI+NSaPL6xf+XLdONQgZgpYvUuZLVQ8dCqxpBAudaOM1+KpAUxw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4", - "bare-url": "^2.2.2", - "fast-fifo": "^1.3.2" - }, - "engines": { - "bare": ">=1.16.0" - }, - "peerDependencies": { - "bare-buffer": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } - } - }, - "node_modules/bare-os": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", - "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "bare": ">=1.14.0" - } - }, - "node_modules/bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-os": "^3.0.1" - } - }, - "node_modules/bare-stream": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", - "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "streamx": "^2.21.0" - }, - "peerDependencies": { - "bare-buffer": "*", - "bare-events": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } - } - }, - "node_modules/bare-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", - "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-path": "^3.0.0" - } - }, - "node_modules/basic-ftp": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.0.tgz", - "integrity": "sha512-5K9eNNn7ywHPsYnFwjKgYH8Hf8B5emh7JKcPaVjjrMJFQQwGpwowEnZNEtHs7DfR7hCZsmaK3VA4HUK0YarT+w==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/chromium-bidi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-7.2.0.tgz", - "integrity": "sha512-gREyhyBstermK+0RbcJLbFhcQctg92AGgDe/h/taMJEOLRdtSswBAO9KmvltFSQWgM2LrwWu5SIuEUbdm3JsyQ==", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-17.0.2.tgz", + "integrity": "sha512-5v9GQFhTktFvotn/OFNJBmKLKRAb6n9r0bVCwf7sHgWc3/JryK0bj1nn93L3pHFrfgcsu6Be6EWsDi+1XHTGDg==", "license": "Apache-2.0", "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0" + }, "peerDependencies": { "devtools-protocol": "*" } }, "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "license": "ISC", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=12" + "node": ">=20" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, "node_modules/commander": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", @@ -398,50 +247,10 @@ "dev": true, "license": "MIT" }, - "node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "license": "MIT", - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/devtools-protocol": { - "version": "0.0.1475386", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1475386.tgz", - "integrity": "sha512-RQ809ykTfJ+dgj9bftdeL2vRVxASAuGU+I9LEx9Ij5TXU5HrgAQVmzi72VA+mkzscE12uzlRv5/tWWv9R9J1SA==", + "version": "0.0.1687809", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1687809.tgz", + "integrity": "sha512-t7kSb+UKYdyxcmqy98U2P86ne6rCve3DIJ4+lp54i/hhClgFuG35JrLj23rO14Jw3sWDsp7i+22QhNU0+/qk8g==", "license": "BSD-3-Clause" }, "node_modules/diff": { @@ -455,20 +264,11 @@ } }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "license": "MIT" }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -478,102 +278,6 @@ "node": ">=6" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/events-universal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", - "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", - "license": "Apache-2.0", - "dependencies": { - "bare-events": "^2.7.0" - } - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "license": "MIT" - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -583,88 +287,18 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", - "license": "MIT", - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -678,267 +312,61 @@ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "license": "MIT" }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "node_modules/modern-tar": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.8.5.tgz", + "integrity": "sha512-snEhs+6G5Tjd4I7tLCDOaoln2RgE0bD19RzEKgvgK2hZ5VKy3MpLhLTZ2fWpXSTg4K2cyPwp+VHATFJhxfnOeA==", "license": "MIT", - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "license": "MIT", - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "license": "MIT" - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "node": ">=18.0.0" } }, "node_modules/puppeteer-core": { - "version": "24.16.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.16.0.tgz", - "integrity": "sha512-tZ0tJiOYaDGTRzzr2giDpf8O/55JsoqkrafS1Xu4H6S8oP4eeL6RbZzY9OzjShSf5EQvx/zAc55QKpDqzXos/Q==", + "version": "25.12.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.12.0.tgz", + "integrity": "sha512-z6LQUt5SH7jwGtGA+nMtJuEjZIsqPiwQ+EFHCYuVDQrJpvifNmmGuyDvTzAKPyFzihxgHc5f3aMzu2kn8Ps37A==", "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.10.6", - "chromium-bidi": "7.2.0", - "debug": "^4.4.1", - "devtools-protocol": "0.0.1475386", - "typed-query-selector": "^2.12.0", - "ws": "^8.18.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "license": "MIT", - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" + "@puppeteer/browsers": "3.2.3", + "chromium-bidi": "17.0.2", + "devtools-protocol": "0.0.1687809", + "typed-query-selector": "^2.12.2", + "webdriver-bidi-protocol": "0.4.3", + "ws": "^8.21.3" }, "engines": { - "node": ">= 14" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/streamx": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", - "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", - "license": "MIT", - "dependencies": { - "events-universal": "^1.0.0", - "fast-fifo": "^1.3.2", - "text-decoder": "^1.1.0" + "node": ">=22.12.0" } }, "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.3.0.tgz", + "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" }, "engines": { - "node": ">=8" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">=8" - } - }, - "node_modules/tar-fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", - "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", - "license": "MIT", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" + "node": ">=12" }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" - } - }, - "node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "license": "MIT", - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, - "node_modules/text-decoder": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", - "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", - "license": "Apache-2.0", - "dependencies": { - "b4a": "^1.6.4" + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/ts-node": { @@ -985,16 +413,10 @@ } } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, "node_modules/typed-query-selector": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", - "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "version": "2.12.3", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.3.tgz", + "integrity": "sha512-/dtuMZyutHLlrxWhhC1uib00ELUr2dZ8C9B8YzZYsJmeE3V56OxRblrSM2UaF142pcMtak7ehZ6+poWGeehNIg==", "license": "MIT" }, "node_modules/typescript": { @@ -1015,7 +437,7 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/v8-compile-cache-lib": { @@ -1025,33 +447,50 @@ "dev": true, "license": "MIT" }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.3.tgz", + "integrity": "sha512-uuN0goWfxP22B7J/uAgBpOYNPttC+XVseYE+rSY5+rQ+YBeVz/VORw8WbmLVcqW78zNg5A4qnjNXYUWR3il2ig==", + "license": "Apache-2.0" + }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", + "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -1079,40 +518,29 @@ } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.2.0.tgz", + "integrity": "sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==", "license": "MIT", "dependencies": { - "cliui": "^8.0.1", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^8.2.1", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^22.0.0" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "license": "ISC", "engines": { - "node": ">=12" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yn": { diff --git a/webdriver/src/prj/package.json b/webdriver/src/prj/package.json index 7311501..c9a670b 100644 --- a/webdriver/src/prj/package.json +++ b/webdriver/src/prj/package.json @@ -12,7 +12,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "puppeteer-core": "24.16.0", + "puppeteer-core": "25.12.0", "commander": "^11.0.0" }, "devDependencies": { @@ -21,6 +21,6 @@ "ts-node": "^10.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=22.12.0" } }