From 9aa4e897e7c7e9e5345d3cc873f921713ee11de8 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Mon, 1 Jun 2026 14:05:09 -0700 Subject: [PATCH 01/40] chore(deps): update tool dependencies (#37965) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [github.com/air-verse/air](https://redirect.github.com/air-verse/air) | `v1.65.2` → `v1.65.3` | ![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fair-verse%2fair/v1.65.3?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fair-verse%2fair/v1.65.2/v1.65.3?slim=true) | | [github.com/editorconfig-checker/editorconfig-checker/v3](https://redirect.github.com/editorconfig-checker/editorconfig-checker) | `v3.6.1` → `v3.7.0` | ![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2feditorconfig-checker%2feditorconfig-checker%2fv3/v3.7.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2feditorconfig-checker%2feditorconfig-checker%2fv3/v3.6.1/v3.7.0?slim=true) | --- ### Release Notes
air-verse/air (github.com/air-verse/air) ### [`v1.65.3`](https://redirect.github.com/air-verse/air/releases/tag/v1.65.3) [Compare Source](https://redirect.github.com/air-verse/air/compare/v1.65.2...v1.65.3) ##### What's Changed - Extend stale workflow timeout by [@​xiantang](https://redirect.github.com/xiantang) in [#​903](https://redirect.github.com/air-verse/air/pull/903) - Increase stale workflow operation limit by [@​xiantang](https://redirect.github.com/xiantang) in [#​904](https://redirect.github.com/air-verse/air/pull/904) - Add review guidelines for coding agents by [@​xiantang](https://redirect.github.com/xiantang) in [#​905](https://redirect.github.com/air-verse/air/pull/905) - Add configurable color output mode by [@​xiantang](https://redirect.github.com/xiantang) in [#​907](https://redirect.github.com/air-verse/air/pull/907) - fix: rewatch files after atomic saves by [@​xiantang](https://redirect.github.com/xiantang) in [#​908](https://redirect.github.com/air-verse/air/pull/908) - follow-up: fix watcher recovery after atomic saves by [@​xiantang](https://redirect.github.com/xiantang) in [#​909](https://redirect.github.com/air-verse/air/pull/909) - Accept .config/air.toml by [@​bersace](https://redirect.github.com/bersace) in [#​716](https://redirect.github.com/air-verse/air/pull/716) - fix: keep built binary after app shutdown by [@​mariusvniekerk](https://redirect.github.com/mariusvniekerk) in [#​911](https://redirect.github.com/air-verse/air/pull/911) ##### New Contributors - [@​bersace](https://redirect.github.com/bersace) made their first contribution in [#​716](https://redirect.github.com/air-verse/air/pull/716) **Full Changelog**:
editorconfig-checker/editorconfig-checker (github.com/editorconfig-checker/editorconfig-checker/v3) ### [`v3.7.0`](https://redirect.github.com/editorconfig-checker/editorconfig-checker/releases/tag/v3.7.0) [Compare Source](https://redirect.github.com/editorconfig-checker/editorconfig-checker/compare/v3.6.1...v3.7.0) ##### Features - **files:** expand glob patterns in passed-file args ([#​190](https://redirect.github.com/editorconfig-checker/editorconfig-checker/issues/190)) ([#​558](https://redirect.github.com/editorconfig-checker/editorconfig-checker/issues/558)) ([4c0f326](https://redirect.github.com/editorconfig-checker/editorconfig-checker/commit/4c0f326cfa71fb0dd80c0c71b1844b2550ed799e)) ##### Bug Fixes - **cli:** auto-enable no-color when output format is github-actions ([#​557](https://redirect.github.com/editorconfig-checker/editorconfig-checker/issues/557)) ([9f4014c](https://redirect.github.com/editorconfig-checker/editorconfig-checker/commit/9f4014ce0944f601472e5cbfaec31f711890c780)) - detect binary files before decoding to prevent false text ([#​550](https://redirect.github.com/editorconfig-checker/editorconfig-checker/issues/550)) ([f47b30c](https://redirect.github.com/editorconfig-checker/editorconfig-checker/commit/f47b30c96713107bc4fe0b7a05e79a293c4874dd))
--- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://redirect.github.com/renovatebot/renovate). Co-authored-by: silverwind Co-authored-by: Lunny Xiao --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index b8cea6b891c..a74752b8d71 100644 --- a/Makefile +++ b/Makefile @@ -11,8 +11,8 @@ COMMA := , XGO_VERSION := go-1.26.x -AIR_PACKAGE ?= github.com/air-verse/air@v1.65.2 # renovate: datasource=go -EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.6.1 # renovate: datasource=go +AIR_PACKAGE ?= github.com/air-verse/air@v1.65.3 # renovate: datasource=go +EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.7.0 # renovate: datasource=go GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 # renovate: datasource=go GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 # renovate: datasource=go MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0 # renovate: datasource=go From ab2a72fe0420208a67039c3c98bfb08704d84e18 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Mon, 1 Jun 2026 16:32:32 -0700 Subject: [PATCH 02/40] fix(deps): update module github.com/google/go-github/v87 to v88 (#37971) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [github.com/google/go-github/v87](https://redirect.github.com/google/go-github) | `v87.0.0` → `v88.0.0` | ![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fgoogle%2fgo-github%2fv87/v88.0.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fgoogle%2fgo-github%2fv87/v87.0.0/v88.0.0?slim=true) | --- ### Release Notes
google/go-github (github.com/google/go-github/v87) ### [`v88.0.0`](https://redirect.github.com/google/go-github/releases/tag/v88.0.0) [Compare Source](https://redirect.github.com/google/go-github/compare/v87.0.0...v88.0.0) This release contains the following breaking API changes: - refactor!: Change app installation `Find*` methods to `Get*` ([#​4243](https://redirect.github.com/google/go-github/issues/4243)) BREAKING CHANGE: App installation methods are renamed from `Find*` to `Get*`. ...and the following additional changes: - chore: Bump version of go-github to v88.0.0 ([#​4245](https://redirect.github.com/google/go-github/issues/4245)) - chore: Update `openapi_operations.yaml` ([#​4242](https://redirect.github.com/google/go-github/issues/4242)) - feat: Add support for setting client URLs ([#​4240](https://redirect.github.com/google/go-github/issues/4240)) - refactor: Add constants for API versions ([#​4236](https://redirect.github.com/google/go-github/issues/4236)) - docs: Formatting and punctuation changes ([#​4235](https://redirect.github.com/google/go-github/issues/4235)) - feat: Add `GetParentIssue` for sub-issues ([#​4232](https://redirect.github.com/google/go-github/issues/4232)) - chore: Bump go-github from v86 to v87 in /scrape ([#​4234](https://redirect.github.com/google/go-github/issues/4234))
--- This PR has been generated by [Mend Renovate](https://redirect.github.com/renovatebot/renovate). --- assets/go-licenses.json | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- services/migrations/error.go | 2 +- services/migrations/github.go | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/assets/go-licenses.json b/assets/go-licenses.json index b2912a3ab3c..22dc1696246 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -660,8 +660,8 @@ "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, { - "name": "github.com/google/go-github/v87", - "path": "github.com/google/go-github/v87/LICENSE", + "name": "github.com/google/go-github/v88", + "path": "github.com/google/go-github/v88/LICENSE", "licenseText": "Copyright (c) 2013 The go-github AUTHORS. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, { diff --git a/go.mod b/go.mod index 0bac202baba..ead88bf5fb7 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,7 @@ require ( github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 github.com/golang-jwt/jwt/v5 v5.3.1 - github.com/google/go-github/v87 v87.0.0 + github.com/google/go-github/v88 v88.0.0 github.com/google/licenseclassifier/v2 v2.0.0 github.com/google/pprof v0.0.0-20260507013755-92041b743c96 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index 08d3d896b9b..521779a0278 100644 --- a/go.sum +++ b/go.sum @@ -380,8 +380,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-github/v87 v87.0.0 h1:9Ck3dcOxWJyfsN8tzdah4YvmqB/7ZsstMglv/PkOsl0= -github.com/google/go-github/v87 v87.0.0/go.mod h1:hGUoT5pwm/ck5uLL+wroSVQfg8mpe+buxllCcGV4VaM= +github.com/google/go-github/v88 v88.0.0 h1:dZA9IKkPK1eXZj4ypngnpRj5FwdpTv4whix2PrQMP7M= +github.com/google/go-github/v88 v88.0.0/go.mod h1:rufTDgn2N45wjhukLTyxmvc9nilSp3mr3Rgtt6b1MPw= github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= diff --git a/services/migrations/error.go b/services/migrations/error.go index d9a6b8f2675..2dd5540f0ec 100644 --- a/services/migrations/error.go +++ b/services/migrations/error.go @@ -7,7 +7,7 @@ package migrations import ( "errors" - "github.com/google/go-github/v87/github" + "github.com/google/go-github/v88/github" ) // ErrRepoNotCreated returns the error that repository not created diff --git a/services/migrations/github.go b/services/migrations/github.go index e31a2fa6057..b8bf6aee453 100644 --- a/services/migrations/github.go +++ b/services/migrations/github.go @@ -20,7 +20,7 @@ import ( "gitea.dev/modules/proxy" "gitea.dev/modules/structs" - "github.com/google/go-github/v87/github" + "github.com/google/go-github/v88/github" "golang.org/x/oauth2" ) From 798578115b8ed8ca764be7c2c3fe41d01ae0e7fd Mon Sep 17 00:00:00 2001 From: Giteabot Date: Mon, 1 Jun 2026 22:18:20 -0700 Subject: [PATCH 03/40] fix(deps): update npm dependencies, remove nolyfill (#37968) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [@eslint-community/eslint-plugin-eslint-comments](https://redirect.github.com/eslint-community/eslint-plugin-eslint-comments) | [`4.7.1` → `4.7.2`](https://renovatebot.com/diffs/npm/@eslint-community%2feslint-plugin-eslint-comments/4.7.1/4.7.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@eslint-community%2feslint-plugin-eslint-comments/4.7.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@eslint-community%2feslint-plugin-eslint-comments/4.7.1/4.7.2?slim=true) | | [@primer/octicons](https://primer.style/octicons) ([source](https://redirect.github.com/primer/octicons)) | [`19.26.0` → `19.27.0`](https://renovatebot.com/diffs/npm/@primer%2focticons/19.26.0/19.27.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@primer%2focticons/19.27.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@primer%2focticons/19.26.0/19.27.0?slim=true) | | [@typescript-eslint/parser](https://typescript-eslint.io/packages/parser) ([source](https://redirect.github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser)) | [`8.59.4` → `8.60.0`](https://renovatebot.com/diffs/npm/@typescript-eslint%2fparser/8.59.4/8.60.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@typescript-eslint%2fparser/8.60.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@typescript-eslint%2fparser/8.59.4/8.60.0?slim=true) | | [@vitest/eslint-plugin](https://redirect.github.com/vitest-dev/eslint-plugin-vitest) | [`1.6.17` → `1.6.18`](https://renovatebot.com/diffs/npm/@vitest%2feslint-plugin/1.6.17/1.6.18) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@vitest%2feslint-plugin/1.6.18?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitest%2feslint-plugin/1.6.17/1.6.18?slim=true) | | [dayjs](https://day.js.org) ([source](https://redirect.github.com/iamkun/dayjs)) | [`1.11.20` → `1.11.21`](https://renovatebot.com/diffs/npm/dayjs/1.11.20/1.11.21) | ![age](https://developer.mend.io/api/mc/badges/age/npm/dayjs/1.11.21?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/dayjs/1.11.20/1.11.21?slim=true) | | [katex](https://katex.org) ([source](https://redirect.github.com/KaTeX/KaTeX)) | [`0.16.47` → `0.17.0`](https://renovatebot.com/diffs/npm/katex/0.16.47/0.17.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/katex/0.17.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/katex/0.16.47/0.17.0?slim=true) | | [material-icon-theme](https://redirect.github.com/material-extensions/vscode-material-icon-theme/blob/main/README.md) ([source](https://redirect.github.com/material-extensions/vscode-material-icon-theme)) | [`5.34.0` → `5.35.0`](https://renovatebot.com/diffs/npm/material-icon-theme/5.34.0/5.35.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/material-icon-theme/5.35.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/material-icon-theme/5.34.0/5.35.0?slim=true) | | [pnpm](https://pnpm.io) ([source](https://redirect.github.com/pnpm/pnpm/tree/HEAD/pnpm)) | [`11.2.1` → `11.4.0`](https://renovatebot.com/diffs/npm/pnpm/11.2.1/11.4.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/pnpm/11.4.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/pnpm/11.2.1/11.4.0?slim=true) | | [rolldown-license-plugin](https://redirect.github.com/silverwind/rolldown-license-plugin) | [`3.0.7` → `3.0.8`](https://renovatebot.com/diffs/npm/rolldown-license-plugin/3.0.7/3.0.8) | ![age](https://developer.mend.io/api/mc/badges/age/npm/rolldown-license-plugin/3.0.8?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/rolldown-license-plugin/3.0.7/3.0.8?slim=true) | | [typescript-eslint](https://typescript-eslint.io/packages/typescript-eslint) ([source](https://redirect.github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint)) | [`8.59.4` → `8.60.0`](https://renovatebot.com/diffs/npm/typescript-eslint/8.59.4/8.60.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/typescript-eslint/8.60.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/typescript-eslint/8.59.4/8.60.0?slim=true) | | [updates](https://redirect.github.com/silverwind/updates) | [`17.16.13` → `17.17.2`](https://renovatebot.com/diffs/npm/updates/17.16.13/17.17.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/updates/17.17.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/updates/17.16.13/17.17.2?slim=true) | | [vite](https://vite.dev) ([source](https://redirect.github.com/vitejs/vite/tree/HEAD/packages/vite)) | [`8.0.13` → `8.0.14`](https://renovatebot.com/diffs/npm/vite/8.0.13/8.0.14) | ![age](https://developer.mend.io/api/mc/badges/age/npm/vite/8.0.14?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vite/8.0.13/8.0.14?slim=true) | | [vue](https://vuejs.org/) ([source](https://redirect.github.com/vuejs/core)) | [`3.5.34` → `3.5.35`](https://renovatebot.com/diffs/npm/vue/3.5.34/3.5.35) | ![age](https://developer.mend.io/api/mc/badges/age/npm/vue/3.5.35?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vue/3.5.34/3.5.35?slim=true) | | [vue-tsc](https://redirect.github.com/vuejs/language-tools) ([source](https://redirect.github.com/vuejs/language-tools/tree/HEAD/packages/tsc)) | [`3.3.1` → `3.3.2`](https://renovatebot.com/diffs/npm/vue-tsc/3.3.1/3.3.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/vue-tsc/3.3.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vue-tsc/3.3.1/3.3.2?slim=true) | --- ### Release Notes
eslint-community/eslint-plugin-eslint-comments (@​eslint-community/eslint-plugin-eslint-comments) ### [`v4.7.2`](https://redirect.github.com/eslint-community/eslint-plugin-eslint-comments/releases/tag/v4.7.2) [Compare Source](https://redirect.github.com/eslint-community/eslint-plugin-eslint-comments/compare/v4.7.1...v4.7.2) ##### Bug Fixes - **deps:** pin `modern-monaco` version to 0.4.0 ([#​320](https://redirect.github.com/eslint-community/eslint-plugin-eslint-comments/issues/320)) ([62a2c3a](https://redirect.github.com/eslint-community/eslint-plugin-eslint-comments/commit/62a2c3a4ee304a8383f170369c9999198d9bdac8)) - **docs:** use `modern-monaco` instead of `monaco-editor` ([#​311](https://redirect.github.com/eslint-community/eslint-plugin-eslint-comments/issues/311)) ([42919d0](https://redirect.github.com/eslint-community/eslint-plugin-eslint-comments/commit/42919d06d8a221e061de3ec98e35bf508ea2b5d2))
primer/octicons (@​primer/octicons) ### [`v19.27.0`](https://redirect.github.com/primer/octicons/blob/HEAD/CHANGELOG.md#19270) [Compare Source](https://redirect.github.com/primer/octicons/compare/v19.26.0...v19.27.0) ##### Minor Changes - [#​1203](https://redirect.github.com/primer/octicons/pull/1203) [`a69618e4`](https://redirect.github.com/primer/octicons/commit/a69618e4b64988784c9c0a06bbf809a3fa343642) Thanks [@​ericwbailey](https://redirect.github.com/ericwbailey)! - Add flag icon ##### Patch Changes - [#​1212](https://redirect.github.com/primer/octicons/pull/1212) [`02bd1ef8`](https://redirect.github.com/primer/octicons/commit/02bd1ef8d15abffaa45be8e00c5fbc896e276c54) Thanks [@​ericwbailey](https://redirect.github.com/ericwbailey)! - remove hardcoded fill from flag icon
typescript-eslint/typescript-eslint (@​typescript-eslint/parser) ### [`v8.60.0`](https://redirect.github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/parser/CHANGELOG.md#8600-2026-05-25) [Compare Source](https://redirect.github.com/typescript-eslint/typescript-eslint/compare/v8.59.4...v8.60.0) This was a version bump only for parser to align it with other projects, there were no code changes. See [GitHub Releases](https://redirect.github.com/typescript-eslint/typescript-eslint/releases/tag/v8.60.0) for more information. You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website.
vitest-dev/eslint-plugin-vitest (@​vitest/eslint-plugin) ### [`v1.6.18`](https://redirect.github.com/vitest-dev/eslint-plugin-vitest/releases/tag/v1.6.18) [Compare Source](https://redirect.github.com/vitest-dev/eslint-plugin-vitest/compare/v1.6.17...v1.6.18) #####    🐞 Bug Fixes - Correct `requiresTypeChecking` metadata for four rules  -  by [@​inglec-arista](https://redirect.github.com/inglec-arista) in [#​905](https://redirect.github.com/vitest-dev/eslint-plugin-vitest/issues/905) [(e06a3)](https://redirect.github.com/vitest-dev/eslint-plugin-vitest/commit/e06a3dc) #####     [View changes on GitHub](https://redirect.github.com/vitest-dev/eslint-plugin-vitest/compare/v1.6.17...v1.6.18)
iamkun/dayjs (dayjs) ### [`v1.11.21`](https://redirect.github.com/iamkun/dayjs/blob/HEAD/CHANGELOG.md#11121-2026-05-26) [Compare Source](https://redirect.github.com/iamkun/dayjs/compare/v1.11.20...v1.11.21) ##### Bug Fixes - preserve unsupported year tokens in format ([#​3015](https://redirect.github.com/iamkun/dayjs/issues/3015)) ([#​3016](https://redirect.github.com/iamkun/dayjs/issues/3016)) ([8fda602](https://redirect.github.com/iamkun/dayjs/commit/8fda602beac5abbc64230ddc49085aa532320f26))
KaTeX/KaTeX (katex) ### [`v0.17.0`](https://redirect.github.com/KaTeX/KaTeX/blob/HEAD/CHANGELOG.md#0170-2026-05-22) [Compare Source](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.47...v0.17.0) ##### Performance Improvements - simplify `defineFunction` to avoid destructuring, improve typing ([#​4222](https://redirect.github.com/KaTeX/KaTeX/issues/4222)) ([fb604e6](https://redirect.github.com/KaTeX/KaTeX/commit/fb604e6ba63e99809e242d37f9c8359209d55431)) ##### BREAKING CHANGES - The internal API for `__defineFunction` changed: you should no longer wrap properties in `props`. #### [0.16.47](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.46...v0.16.47) (2026-05-16) ##### Bug Fixes - correct size of `[` big delimiter ([#​4217](https://redirect.github.com/KaTeX/KaTeX/issues/4217)) ([7ba0027](https://redirect.github.com/KaTeX/KaTeX/commit/7ba0027d2f04abddd3b215362f867ab8260b09d7)), closes [#​4215](https://redirect.github.com/KaTeX/KaTeX/issues/4215) #### [0.16.46](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.45...v0.16.46) (2026-05-13) ##### Bug Fixes - preserve math font in some styling commands ([#​4214](https://redirect.github.com/KaTeX/KaTeX/issues/4214)) ([e9ee046](https://redirect.github.com/KaTeX/KaTeX/commit/e9ee0464ddb31da9bf9649eeb70e52236e7a974a)), closes [#​4213](https://redirect.github.com/KaTeX/KaTeX/issues/4213) #### [0.16.45](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.44...v0.16.45) (2026-04-05) ##### Bug Fixes - wrap vcenter mpadded in mrow for valid MathML ([#​4193](https://redirect.github.com/KaTeX/KaTeX/issues/4193)) ([ee66b78](https://redirect.github.com/KaTeX/KaTeX/commit/ee66b78d24340edbbd05b08a4a429ce9ed158b25)), closes [#​4078](https://redirect.github.com/KaTeX/KaTeX/issues/4078) #### [0.16.44](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.43...v0.16.44) (2026-03-27) ##### Bug Fixes - remove extra \jot space at bottom of align/gather/etc. ([#​4184](https://redirect.github.com/KaTeX/KaTeX/issues/4184)) ([3870ee9](https://redirect.github.com/KaTeX/KaTeX/commit/3870ee913e27fdde7bce244e4c6c5d63e2b28a62)) #### [0.16.43](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.42...v0.16.43) (2026-03-26) ##### Bug Fixes - use makeEm() consistently to truncate long CSS decimals ([#​4181](https://redirect.github.com/KaTeX/KaTeX/issues/4181)) ([0967dcc](https://redirect.github.com/KaTeX/KaTeX/commit/0967dcc0278f20d4501a93f01c7343c70abb3fcd)) #### [0.16.42](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.41...v0.16.42) (2026-03-24) ##### Features - \underbracket and \overbracket ([#​4147](https://redirect.github.com/KaTeX/KaTeX/issues/4147)) ([5be9abb](https://redirect.github.com/KaTeX/KaTeX/commit/5be9abb0b4d687a2a196b8adf9b5b9deeb60f7bc)) #### [0.16.41](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.40...v0.16.41) (2026-03-24) ##### Bug Fixes - \sout in text mode ([#​4173](https://redirect.github.com/KaTeX/KaTeX/issues/4173)) ([e748578](https://redirect.github.com/KaTeX/KaTeX/commit/e748578b63e07ad30d5e404e60b04e5e794c0a5a)) #### [0.16.40](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.39...v0.16.40) (2026-03-20) ##### Bug Fixes - **css:** specify position: relative for .katex ([#​4170](https://redirect.github.com/KaTeX/KaTeX/issues/4170)) ([020f0d8](https://redirect.github.com/KaTeX/KaTeX/commit/020f0d89567d59229bac5fc8d8f5832a9508a85f)) #### [0.16.39](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.38...v0.16.39) (2026-03-19) ##### Bug Fixes - middle dot in text mode ([#​4169](https://redirect.github.com/KaTeX/KaTeX/issues/4169)) ([edb45b0](https://redirect.github.com/KaTeX/KaTeX/commit/edb45b0b17c7b33349ce5142fe39156da05cb4d8)), closes [#​3641](https://redirect.github.com/KaTeX/KaTeX/issues/3641) #### [0.16.38](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.37...v0.16.38) (2026-03-08) ##### Bug Fixes - accent skew mixed with font specifiers ([#​4159](https://redirect.github.com/KaTeX/KaTeX/issues/4159)) ([aea3375](https://redirect.github.com/KaTeX/KaTeX/commit/aea33758d6c98896017007d0244885301773856a)), closes [#​4121](https://redirect.github.com/KaTeX/KaTeX/issues/4121) #### [0.16.37](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.36...v0.16.37) (2026-03-06) ##### Bug Fixes - negative-width `\hphantom` and symmetric `\smash` ([#​4153](https://redirect.github.com/KaTeX/KaTeX/issues/4153)) ([d4799ca](https://redirect.github.com/KaTeX/KaTeX/commit/d4799cae585d909e2a4e3dedbebdc2f142998ca9)) #### [0.16.36](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.35...v0.16.36) (2026-03-06) ##### Bug Fixes - contrib esm bloat ([#​4157](https://redirect.github.com/KaTeX/KaTeX/issues/4157)) ([2bde1ad](https://redirect.github.com/KaTeX/KaTeX/commit/2bde1adab2a23f61519145923329c915b04d4778)) #### [0.16.35](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.34...v0.16.35) (2026-03-05) ##### Bug Fixes - version number regression ([#​4155](https://redirect.github.com/KaTeX/KaTeX/issues/4155)) ([db26b73](https://redirect.github.com/KaTeX/KaTeX/commit/db26b733805f2d0d71e82596475b313c8706557e)) #### [0.16.34](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.33...v0.16.34) (2026-03-05) ##### Bug Fixes - emoji with variation selector ([#​4151](https://redirect.github.com/KaTeX/KaTeX/issues/4151)) ([c2606e5](https://redirect.github.com/KaTeX/KaTeX/commit/c2606e5db91ae199ee1ff0c8c2f7f9f70fcf589b)) #### [0.16.33](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.32...v0.16.33) (2026-02-23) ##### Bug Fixes - **scss:** forward variables to fonts module ([#​4146](https://redirect.github.com/KaTeX/KaTeX/issues/4146)) ([9349a64](https://redirect.github.com/KaTeX/KaTeX/commit/9349a64a051ca408da713baf061e32ade80ed22a)) #### [0.16.32](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.31...v0.16.32) (2026-02-22) ##### Bug Fixes - italic separation in \mathnormal ([#​4143](https://redirect.github.com/KaTeX/KaTeX/issues/4143)) ([71305a0](https://redirect.github.com/KaTeX/KaTeX/commit/71305a05140ca6203092bfdc14f689168b26ab8c)) #### [0.16.31](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.30...v0.16.31) (2026-02-22) ##### Bug Fixes - `\*frac` sizing ([#​4137](https://redirect.github.com/KaTeX/KaTeX/issues/4137)) ([ef51f18](https://redirect.github.com/KaTeX/KaTeX/commit/ef51f18ded4ab9ba54ba750f2866241c4676c41c)) #### [0.16.30](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.29...v0.16.30) (2026-02-22) ##### Bug Fixes - no line breaks after `\not` ([#​4140](https://redirect.github.com/KaTeX/KaTeX/issues/4140)) ([2d1ba86](https://redirect.github.com/KaTeX/KaTeX/commit/2d1ba86143bd45540d5a773cfa456081318f3f33)) #### [0.16.29](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.28...v0.16.29) (2026-02-22) ##### Bug Fixes - `\imath` and other `\html@mathml` macros in arguments ([#​4139](https://redirect.github.com/KaTeX/KaTeX/issues/4139)) ([a850cce](https://redirect.github.com/KaTeX/KaTeX/commit/a850cce7ccbf95a0b187313d1e54d8d40dfc7273)) #### [0.16.28](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.27...v0.16.28) (2026-01-25) ##### Bug Fixes - **type:** add missing types definition path to package.json ([#​4125](https://redirect.github.com/KaTeX/KaTeX/issues/4125)) ([0ef8921](https://redirect.github.com/KaTeX/KaTeX/commit/0ef8921d189346b0ff8f84a77f7f552349b76893)) #### [0.16.27](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.26...v0.16.27) (2025-12-07) ##### Features - support equals sign and surrounding whitespace in \htmlData attribute values ([#​4112](https://redirect.github.com/KaTeX/KaTeX/issues/4112)) ([c77aaec](https://redirect.github.com/KaTeX/KaTeX/commit/c77aaec00c766f5bb02e332a1dc416b82a65fe8f)) #### [0.16.26](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.25...v0.16.26) (2025-12-07) ##### Bug Fixes - \mathop followed by integral symbol ([6fbad18](https://redirect.github.com/KaTeX/KaTeX/commit/6fbad18857351e4d2a88ed3e3348bd76caad9be3)) #### [0.16.25](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.24...v0.16.25) (2025-10-13) ##### Features - **css:** provide `katex-swap.css` that uses `font-display: swap` ([#​3940](https://redirect.github.com/KaTeX/KaTeX/issues/3940)) ([b3f9ce6](https://redirect.github.com/KaTeX/KaTeX/commit/b3f9ce691e89a52dea7ec8f10cc6ed4ddc8fc161)), closes [#​2242](https://redirect.github.com/KaTeX/KaTeX/issues/2242) #### [0.16.24](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.23...v0.16.24) (2025-10-12) ##### Features - support hex colors with alpha ([#​4090](https://redirect.github.com/KaTeX/KaTeX/issues/4090)) ([8c9b306](https://redirect.github.com/KaTeX/KaTeX/commit/8c9b3063965acc0d6e6a0b6df4d051169de9e1a9)), closes [#​4067](https://redirect.github.com/KaTeX/KaTeX/issues/4067) [#fA6](https://redirect.github.com/KaTeX/KaTeX/issues/fA6) [#fA6f1](https://redirect.github.com/KaTeX/KaTeX/issues/fA6f1) #### [0.16.23](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.22...v0.16.23) (2025-10-03) ##### Bug Fixes - Support `\def` with arguments via `macros` option ([#​4087](https://redirect.github.com/KaTeX/KaTeX/issues/4087)) ([80a8158](https://redirect.github.com/KaTeX/KaTeX/commit/80a815856a8c26d78b3669e9c05fff00efe82247)) #### [0.16.22](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.21...v0.16.22) (2025-04-09) ##### Bug Fixes - \relax in base or exponent of super/subscript ([#​4045](https://redirect.github.com/KaTeX/KaTeX/issues/4045)) ([1f43c84](https://redirect.github.com/KaTeX/KaTeX/commit/1f43c84a175fb689f8c8d1d72b1e8b896a8b43d1)) #### [0.16.21](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.20...v0.16.21) (2025-01-17) ##### Bug Fixes - escape \htmlData attribute name ([57914ad](https://redirect.github.com/KaTeX/KaTeX/commit/57914ad91eff401357f44bf364b136d37eba04f8)) #### [0.16.20](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.19...v0.16.20) (2025-01-12) ##### Bug Fixes - \providecommand does not overwrite existing macro ([#​4000](https://redirect.github.com/KaTeX/KaTeX/issues/4000)) ([6d30fe4](https://redirect.github.com/KaTeX/KaTeX/commit/6d30fe47b06f9da9b836fe518d5cbbecf6a6a3a1)), closes [#​3928](https://redirect.github.com/KaTeX/KaTeX/issues/3928) #### [0.16.19](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.18...v0.16.19) (2024-12-29) ##### Bug Fixes - **types:** improve `strict` function type ([#​4009](https://redirect.github.com/KaTeX/KaTeX/issues/4009)) ([4228b4e](https://redirect.github.com/KaTeX/KaTeX/commit/4228b4eb529b8e35def66cc6e4fa467383b98c86)) #### [0.16.18](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.17...v0.16.18) (2024-12-18) ##### Bug Fixes - Actually publish TypeScript type definitions ([#​4008](https://redirect.github.com/KaTeX/KaTeX/issues/4008)) ([629b873](https://redirect.github.com/KaTeX/KaTeX/commit/629b87354fdfc04a3769f09b69f6bbadebcb9ae8)) #### [0.16.17](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.16...v0.16.17) (2024-12-17) ##### Bug Fixes - MathML combines multidigit numbers with sup/subscript, comma separators, and multicharacter text when outputting to DOM ([#​3999](https://redirect.github.com/KaTeX/KaTeX/issues/3999)) ([7d79e22](https://redirect.github.com/KaTeX/KaTeX/commit/7d79e220f465c42d4334dc95f1c41e333667e168)), closes [#​3995](https://redirect.github.com/KaTeX/KaTeX/issues/3995) #### [0.16.16](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.15...v0.16.16) (2024-12-17) ##### Features - ESM exports, TypeScript types ([#​3992](https://redirect.github.com/KaTeX/KaTeX/issues/3992)) ([ea9c173](https://redirect.github.com/KaTeX/KaTeX/commit/ea9c173a0de953b49b2ce5d131e88b785f5dffa1)) #### [0.16.15](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.14...v0.16.15) (2024-12-09) ##### Features - italic sans-serif in math mode via `\mathsfit` command ([#​3998](https://redirect.github.com/KaTeX/KaTeX/issues/3998)) ([2218901](https://redirect.github.com/KaTeX/KaTeX/commit/22189018b63c9312ec4ad126804514a7390d60b5)) #### [0.16.14](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.13...v0.16.14) (2024-12-08) ##### Features - \dddot and \ddddot support ([#​3834](https://redirect.github.com/KaTeX/KaTeX/issues/3834)) ([bda35cd](https://redirect.github.com/KaTeX/KaTeX/commit/bda35cdb0a6bbbc52dd27c79e4d984688be3b745)), closes [#​2744](https://redirect.github.com/KaTeX/KaTeX/issues/2744) #### [0.16.13](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.12...v0.16.13) (2024-12-08) ##### Bug Fixes - `\vdots` and `\rule` support in text mode ([#​3997](https://redirect.github.com/KaTeX/KaTeX/issues/3997)) ([0e08352](https://redirect.github.com/KaTeX/KaTeX/commit/0e0835262345d991df61a435800a16b069a4d5c7)), closes [#​3990](https://redirect.github.com/KaTeX/KaTeX/issues/3990) #### [0.16.12](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.11...v0.16.12) (2024-12-08) ##### Features - **css:** configurable margin for display math ([#​3638](https://redirect.github.com/KaTeX/KaTeX/issues/3638)) ([3405001](https://redirect.github.com/KaTeX/KaTeX/commit/3405001225b8ee0cf8b35b2e3a6c1fa2191e5fef)) #### [0.16.11](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.10...v0.16.11) (2024-07-02) ##### Features - add \emph ([#​3963](https://redirect.github.com/KaTeX/KaTeX/issues/3963)) ([9f34da4](https://redirect.github.com/KaTeX/KaTeX/commit/9f34da4b3cf228a7af8134c394394d780a089f2b)), closes [#​3566](https://redirect.github.com/KaTeX/KaTeX/issues/3566) #### [0.16.10](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.9...v0.16.10) (2024-03-24) ##### Bug Fixes - \edef bypassing maxExpand via exponential blowup ([e88b4c3](https://redirect.github.com/KaTeX/KaTeX/commit/e88b4c357f978b1bca8edfe3297f0aa309bcbe34)) - escape \includegraphics src and alt ([c5897fc](https://redirect.github.com/KaTeX/KaTeX/commit/c5897fcd1f73da9612a53e6b5544f1d776e17770)) - force protocol to be lowercase for better protocol filtering ([fc5af64](https://redirect.github.com/KaTeX/KaTeX/commit/fc5af64183a3ceb9be9d1c23a275999a728593de)), closes [/datatracker.ietf.org/doc/html/rfc3986#section-3](https://redirect.github.com//datatracker.ietf.org/doc/html/rfc3986/issues/section-3) - maxExpand limit with Unicode sub/superscripts ([085e21b](https://redirect.github.com/KaTeX/KaTeX/commit/085e21b5da05414efefa932570e7201a7c70e5b2)) #### [0.16.9](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.8...v0.16.9) (2023-10-02) ##### Features - Support bold Fraktur ([#​3777](https://redirect.github.com/KaTeX/KaTeX/issues/3777)) ([240d5ae](https://redirect.github.com/KaTeX/KaTeX/commit/240d5aede915e0303929a9328745b1060e12004a)) #### [0.16.8](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.7...v0.16.8) (2023-06-24) ##### Features - expose error length and raw error message on ParseError ([#​3820](https://redirect.github.com/KaTeX/KaTeX/issues/3820)) ([710774a](https://redirect.github.com/KaTeX/KaTeX/commit/710774aaebb38f43b1ec51c159fe9b9520c91424)) #### [0.16.7](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.6...v0.16.7) (2023-04-28) ##### Bug Fixes - **docs/support\_table.md:** delete redundant "varPsi" ([#​3814](https://redirect.github.com/KaTeX/KaTeX/issues/3814)) ([33a1b98](https://redirect.github.com/KaTeX/KaTeX/commit/33a1b98710c880d2d4a67aa0048f027a94b85702)) #### [0.16.6](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.5...v0.16.6) (2023-04-17) ##### Bug Fixes - Support `\let` via `macros` option ([#​3738](https://redirect.github.com/KaTeX/KaTeX/issues/3738)) ([bdb0be2](https://redirect.github.com/KaTeX/KaTeX/commit/bdb0be201794d22adaee05438b07a2830efea9da)), closes [#​3737](https://redirect.github.com/KaTeX/KaTeX/issues/3737) [#​3737](https://redirect.github.com/KaTeX/KaTeX/issues/3737) #### [0.16.5](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.4...v0.16.5) (2023-04-17) ##### Features - \_\_defineFunction API exposing internal defineFunction ([#​3805](https://redirect.github.com/KaTeX/KaTeX/issues/3805)) ([c7b1f84](https://redirect.github.com/KaTeX/KaTeX/commit/c7b1f84b7801a29dffdfa3db0ff35de289db80c0)), closes [#​3756](https://redirect.github.com/KaTeX/KaTeX/issues/3756) #### [0.16.4](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.3...v0.16.4) (2022-12-07) ##### Bug Fixes - space should prevent optional argument to \ ([#​3746](https://redirect.github.com/KaTeX/KaTeX/issues/3746)) ([a0deb34](https://redirect.github.com/KaTeX/KaTeX/commit/a0deb3410fd92340556fc4c9edb8ab586077e5bf)), closes [#​3745](https://redirect.github.com/KaTeX/KaTeX/issues/3745) #### [0.16.3](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.2...v0.16.3) (2022-10-22) ##### Bug Fixes - \hline after \cr ([#​3735](https://redirect.github.com/KaTeX/KaTeX/issues/3735)) ([ebf6bf5](https://redirect.github.com/KaTeX/KaTeX/commit/ebf6bf5b50a98ac6c5aca1896c0a6ba985c1c91c)), closes [#​3734](https://redirect.github.com/KaTeX/KaTeX/issues/3734) #### [0.16.2](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.1...v0.16.2) (2022-08-29) ##### Bug Fixes - **auto-render:** concatenate content of successive text nodes ([#​3422](https://redirect.github.com/KaTeX/KaTeX/issues/3422)) ([4d3fdd8](https://redirect.github.com/KaTeX/KaTeX/commit/4d3fdd8647a1c320dc7bcb9c9ea2af81379f700d)) - Implement \pmb via CSS text-shadow ([#​3505](https://redirect.github.com/KaTeX/KaTeX/issues/3505)) ([176552a](https://redirect.github.com/KaTeX/KaTeX/commit/176552a69183d71425b491d4cc2fa1d462a1246a)) #### [0.16.1](https://redirect.github.com/KaTeX/KaTeX/compare/v0.16.0...v0.16.1) (2022-08-28) ##### Bug Fixes - Use SVGs for some stacked delims ([#​3686](https://redirect.github.com/KaTeX/KaTeX/issues/3686)) ([8a65a2e](https://redirect.github.com/KaTeX/KaTeX/commit/8a65a2e1fd69ffeee2fac62229f9f05ebf6afd45))
material-extensions/vscode-material-icon-theme (material-icon-theme) ### [`v5.35.0`](https://redirect.github.com/material-extensions/vscode-material-icon-theme/blob/HEAD/CHANGELOG.md#v5350) [Compare Source](https://redirect.github.com/material-extensions/vscode-material-icon-theme/compare/v5.34.0...v5.35.0) [compare changes](https://redirect.github.com/material-extensions/vscode-material-icon-theme/compare/v5.34.0...v5.35.0) ##### 🚀 Enhancements - Add CAD file extensions to 3d icon mapping ([#​3436](https://redirect.github.com/material-extensions/vscode-material-icon-theme/pull/3436)) - Add tsdown icon ([#​3418](https://redirect.github.com/material-extensions/vscode-material-icon-theme/pull/3418)) - Add new icons for mrpack ([#​3439](https://redirect.github.com/material-extensions/vscode-material-icon-theme/pull/3439)) - Add support for vercel.ts icon (typed Vercel configuration) ([#​3441](https://redirect.github.com/material-extensions/vscode-material-icon-theme/pull/3441)) - Support jxl image file type ([#​3444](https://redirect.github.com/material-extensions/vscode-material-icon-theme/pull/3444)) - Add uiua file icon ([#​3408](https://redirect.github.com/material-extensions/vscode-material-icon-theme/pull/3408)) - Add folder associations for rust/cargo projects ([#​3447](https://redirect.github.com/material-extensions/vscode-material-icon-theme/pull/3447)) - **icon:** Add zed folder icon ([#​3442](https://redirect.github.com/material-extensions/vscode-material-icon-theme/pull/3442)) - **icon:** Add redis icon ([#​3450](https://redirect.github.com/material-extensions/vscode-material-icon-theme/pull/3450)) - Add more unit tests for writefile helper function ([9e4c98aa](https://redirect.github.com/material-extensions/vscode-material-icon-theme/commit/9e4c98aa)) - Include language IDs into the file icons ([c9a9d2ed](https://redirect.github.com/material-extensions/vscode-material-icon-theme/commit/c9a9d2ed)) - Update dependencies ([d7274c71](https://redirect.github.com/material-extensions/vscode-material-icon-theme/commit/d7274c71)) ##### 🩹 Fixes - Add rootDir to tsconfig.declarations.json for TypeScript 6 ([4f7f49e9](https://redirect.github.com/material-extensions/vscode-material-icon-theme/commit/4f7f49e9)) - Correct typos in CONTRIBUTING.md ([4de4acf7](https://redirect.github.com/material-extensions/vscode-material-icon-theme/commit/4de4acf7)) ##### 💅 Refactors - **core:** Rewrite toTitleCase for clarity and add tests ([33c0e614](https://redirect.github.com/material-extensions/vscode-material-icon-theme/commit/33c0e614)) - Remove duplicate toTitleCase, consolidate imports ([e247951d](https://redirect.github.com/material-extensions/vscode-material-icon-theme/commit/e247951d)) ##### 🏡 Chore - Improve release process ([b959b483](https://redirect.github.com/material-extensions/vscode-material-icon-theme/commit/b959b483)) ##### ✅ Tests - **core:** Add comprehensive tests for object helpers ([57f476c5](https://redirect.github.com/material-extensions/vscode-material-icon-theme/commit/57f476c5)) ##### ❤️ Contributors - Philipp Kief ([@​PKief](https://redirect.github.com/PKief)) - Sayan Shankhari ([@​SayanShankhari](https://redirect.github.com/SayanShankhari)) - Tymon Marek ([@​TymonMarek](https://redirect.github.com/TymonMarek)) - Unteksi-ozar ([@​Unteksi-ozar](https://redirect.github.com/Unteksi-ozar)) - 锐冰 SharpIce ([@​SharpIceX](https://redirect.github.com/SharpIceX)) - El Mahdi Bennajah ([@​bennajah](https://redirect.github.com/bennajah)) - Glitch714 ([@​glitchplaysgames714](https://redirect.github.com/glitchplaysgames714)) - Andrin Haldner ([@​AHaldner](https://redirect.github.com/AHaldner)) - Kaden Gruizenga ([@​kgruiz](https://redirect.github.com/kgruiz))
pnpm/pnpm (pnpm) ### [`v11.4.0`](https://redirect.github.com/pnpm/pnpm/blob/HEAD/pnpm/CHANGELOG.md#1140) [Compare Source](https://redirect.github.com/pnpm/pnpm/compare/v11.3.0...v11.4.0) ##### Minor Changes - Treat tarball-integrity mismatches against the lockfile as a hard failure by default. Previously, `pnpm install` (non-frozen) would log `ERR_PNPM_TARBALL_INTEGRITY`, silently re-resolve from the registry, and overwrite the locked integrity — which meant a compromised registry, proxy, or republished version could substitute attacker-controlled content on a clean machine even though the project shipped a committed lockfile. `pnpm install` now exits with `ERR_PNPM_TARBALL_INTEGRITY` and a hint pointing at the new opt-in flag. The only opt-in is **`pnpm install --update-checksums`** — narrowly scoped to refreshing the locked integrity values from what the registry currently serves. Mirrors yarn's flag of the same name. A warning still prints when the bypass takes effect so the operation is auditable. `--force` and `pnpm update` deliberately do **not** bypass the integrity check. They are routine refresh operations; silently overwriting a locked integrity in those flows would erase the protection a committed lockfile is supposed to provide. `--frozen-lockfile` behavior is unchanged. `--fix-lockfile` keeps its documented purpose (filling in missing lockfile entries) and is also not a bypass. - `pnpm runtime set ` now saves the runtime to `devEngines.runtime` by default instead of `engines.runtime`. Pass `--save-prod` (or `-P`) to save it to `engines.runtime` instead [#​11948](https://redirect.github.com/pnpm/pnpm/issues/11948). ##### Patch Changes - Fix a credential disclosure issue where an unscoped `_authToken` (or `_auth`, or `username` + `_password`, or `tokenHelper`) defined in one source — `~/.npmrc`, `~/.config/pnpm/auth.ini`, a workspace `.npmrc`, CLI flags, etc. — would be sent as an `Authorization` header to whichever registry a different (potentially untrusted) source named. The same fix extends to client TLS credentials (`cert`, `key`) so they aren't presented to a registry their author didn't choose. pnpm now rewrites each unscoped per-registry setting (`_authToken`, `_auth`, `username`, `_password`, `tokenHelper`, `cert`, `key`) to its URL-scoped form at load time, using the `registry=` value declared in the same source (or the npmjs default registry if the source declares none). A later layer overriding `registry=` therefore cannot pull an unscoped credential along, because it is already pinned to the URL its author intended. `ca`/`cafile` are intentionally not rescoped — they're trust anchors, not credentials, and corporate MITM-proxy setups rely on them applying globally. Every rescope emits a deprecation warning telling the user where the setting was pinned and how to write it directly. npm has rejected unscoped credentials outright since `npm@9`, and pnpm intends to remove support in a future major release. To target a specific registry, write the setting URL-scoped (e.g. `//registry.example.com/:_authToken=...` or `//registry.example.com/:cert=...`). `@pnpm/network.auth-header`: removed the `defaultRegistry` parameter from `createGetAuthHeaderByURI` and `getAuthHeadersFromCreds`. Now that credentials are URL-scoped at load time, the merged `configByUri` never contains the empty-string "default registry" placeholder slot, so re-keying it onto the merged default registry is no longer needed. - Fix `pnpm deploy` crashing with `ENOENT: ... lstat '/node_modules'` when `configDependencies` declares pacquet (`pacquet` or `@pnpm/pacquet`). The deploy directory never installs config dependencies, so the install engine they designate isn't on disk to invoke; the nested install now skips them. - Reject git resolutions whose `commit` field is not a 40-character hexadecimal SHA before invoking `git`. A malicious lockfile could otherwise smuggle a value such as `--upload-pack=` through `git fetch` / `git checkout`, which on SSH or local-file transports executes the supplied command. - Limit concurrent project manifest reads while listing large workspaces to avoid `EMFILE` errors. - Reject patch files whose `diff --git` headers reference paths outside the patched package directory. Previously a malicious `.patch` file added via a pull request could write, delete, or rename arbitrary files reachable by the user running `pnpm install`. - Improve the log message that pnpm prints after auto-adding entries to `minimumReleaseAgeExclude` when `minimumReleaseAge` is set without `minimumReleaseAgeStrict`. The message previously referred to the internal "loose mode" terminology, which wasn't searchable in the docs; it now tells the user to set `minimumReleaseAgeStrict` to `true` if they want these updates gated behind a prompt instead [#​11747](https://redirect.github.com/pnpm/pnpm/issues/11747). - Reject dependency aliases that contain path-traversal segments (such as `@x/../../../../../.git/hooks`) when reading them from a package manifest or symlinking them into `node_modules`. A malicious registry package could otherwise use a transitive dependency key to make `pnpm install` create symlinks at attacker-chosen paths outside the intended `node_modules` directory. - Reject `pnpm-lock.yaml` entries whose remote tarball `resolution:` block is missing the `integrity` field. Previously the worker that extracts a downloaded tarball skipped hash verification when no integrity was supplied and minted a fresh one from the unverified bytes, so an attacker who could both alter the lockfile (e.g. via a pull request that strips `integrity:`) and serve modified content at the referenced tarball URL could install a tampered package without any error — including under `--frozen-lockfile`. pnpm now fails closed at lockfile-read time with `ERR_PNPM_MISSING_TARBALL_INTEGRITY`. Git-hosted tarballs (`gitHosted: true` or a URL on codeload.github.com / bitbucket.org / gitlab.com) and `file:` tarballs are exempt — the commit SHA in a git-host URL and the user-controlled local path already anchor the bytes. - Validate `devEngines.runtime` and `engines.runtime` version ranges for `node`, `deno`, and `bun` when `onFail` is set to `error` or `warn`. Previously these settings only had an effect with `onFail: 'download'` — the `error` and `warn` modes silently did nothing [#​11818](https://redirect.github.com/pnpm/pnpm/issues/11818). Violations now throw `ERR_PNPM_BAD_RUNTIME_VERSION`. - Require provenance before treating trusted publisher metadata as the strongest trust evidence. ### [`v11.3.0`](https://redirect.github.com/pnpm/pnpm/blob/HEAD/pnpm/CHANGELOG.md#1130) [Compare Source](https://redirect.github.com/pnpm/pnpm/compare/v11.2.2...v11.3.0) ##### Minor Changes - Added `pnpm stage` with `publish`, `list`, `view`, `approve`, `reject`, and `download` subcommands for npm staged publishing. - Added a new setting `trustLockfile`. When `true`, `pnpm install` skips the supply-chain verification pass that re-applies `minimumReleaseAge` / `trustPolicy='no-downgrade'` to every entry in the loaded lockfile. The install treats the lockfile as already-trusted — useful for closed-source projects where every commit comes from a trusted author. Defaults to `false`; verification stays on by default. Set in `pnpm-workspace.yaml`. Also cut the memory footprint of the verification pass itself: the per-(registry, name) trust-meta cache previously retained the full packument — dependency graphs, scripts, README, and per-version manifests — for the entire install. On large workspaces (`~4k` lockfile entries with `minimumReleaseAge` + `trustPolicy: no-downgrade` enabled) this could OOM CI runners with a 2GB heap cap. The cache now stores only the fields the trust check actually reads (`time`, per-version `_npmUser.trustedPublisher`, `dist.attestations.provenance`). The abbreviated-metadata cache is similarly projected to just the package-level `modified` field and the set of currently-listed version names. Fixes [#​11860](https://redirect.github.com/pnpm/pnpm/issues/11860). - Implemented `pnpm pkg` command natively, following `npm pkg` standards. - Implemented `pnpm repo` command natively, following `npm repo` standards. - Implemented `pnpm set-script` (alias `ss`) natively. Adds or updates an entry in the `scripts` field of the project manifest, supporting `package.json`, `package.json5`, and `package.yaml` formats. - Add a `skip-manifest-obfuscation` option for `pnpm pack` and `pnpm publish`. When enabled, the original `packageManager` field and publish lifecycle scripts are kept in the packed/published manifest instead of being stripped. The pnpm-specific `pnpm` field continues to be omitted. ##### Patch Changes - Fixed `pnpm dlx` failing with `ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND` when the installed package's CAS slot is missing its `package.json`. Observed in the wild for `pnpm dlx node@runtime:` when the GVS slot was populated without the synthesized manifest runtime archives need (they don't ship a `package.json` of their own, so the synthesized one is the only way it gets there; an existing slot from an earlier code path that skipped the synthesis stays incomplete). The bin link itself is wired up from the resolution and remains valid, so `dlx` now falls back to the scopeless package name when the slot's manifest is unreadable — for single-bin packages (the dlx common case, including every `runtime:` spec) this matches what `manifest.bin` would have named. Multi-bin packages already require `--package= ` to disambiguate and don't enter this code path. - Fixed non-determinism in `pnpm dedupe` and `pnpm install` when a dependency graph contains packages with transitive peer dependencies on each other (e.g. `@aws-sdk/client-sts` and `@aws-sdk/client-sso-oidc`) and `auto-install-peers` is enabled. The lockfile no longer flips between two equally-valid forms across consecutive runs. The root cause was that `resolveDependencies` pushed onto its `pkgAddresses` / `postponedResolutionsQueue` arrays from inside `Promise.all`-spawned callbacks, so completion-order timing leaked into the array order and downstream cyclic-peer suffix assignment. Fixes [#​8155](https://redirect.github.com/pnpm/pnpm/issues/8155). - Fixed a regression introduced by [#​11711](https://redirect.github.com/pnpm/pnpm/pull/11711) where `pnpm add ` (and any other wanted-dependency whose alias can't be parsed from the user-supplied spec, e.g. tarball URLs or `pnpm/test-git-fetch#sha`) was silently dropped from the manifest update and from `pendingBuilds`. The alias-keyed lookup added in that PR couldn't find a `wantedDependency` whose `alias` was `undefined` at parse time but resolved to a package name only after fetching, so the entry never made it into `specsToUpsert`. Restored the original index-based pairing between `directDependencies` and `wantedDependencies`; the catalog-protocol preservation that PR was originally fixing is unaffected because it's driven by `rdd.catalogLookup.userSpecifiedBareSpecifier`, not by the lookup. Fixes the three `rebuilds dependencies` / `rebuilds specific dependencies` / `rebuild with pending option` failures in `building/commands/test/build/index.ts`. - Fixed `pnpm add --config` leaving orphan entries in `pnpm-lock.env.yaml` (the optional subdependencies of the previously resolved version of the updated config dependency). ### [`v11.2.2`](https://redirect.github.com/pnpm/pnpm/blob/HEAD/pnpm/CHANGELOG.md#1122) [Compare Source](https://redirect.github.com/pnpm/pnpm/compare/v11.2.1...v11.2.2) ##### Patch Changes - When the install engine is delegated to pacquet via `configDependencies`, the user's CLI flags passed to `pnpm install` (e.g. `--no-runtime`, `--prod`, `--dev`, `--no-optional`, `--node-linker`, `--cpu`/`--os`/`--libc`, `--offline`, `--prefer-offline`) are now forwarded to pacquet's `install` subcommand verbatim. Previously pacquet was invoked with a fixed argument list, so flags like `--no-runtime` were silently dropped. Flag forwarding is gated on the command being `install`/`i`; `add`, `update`, and `dedupe` still don't forward (their flag surface doesn't line up with pacquet's `install`). - Fixed `pnpm up` (and `pnpm add` / `pnpm remove`) failing with `pacquet_package_manager::outdated_lockfile` when pacquet is declared in `configDependencies`. pnpm now passes `--ignore-manifest-check` to pacquet so its `--frozen-lockfile` check doesn't fire against the (pre-mutation) `package.json` pnpm hasn't written yet [#​11797](https://redirect.github.com/pnpm/pnpm/issues/11797). Requires a pacquet release that supports the flag — bump `PACQUET_VERSION` in the e2e tests once it ships.
silverwind/rolldown-license-plugin (rolldown-license-plugin) ### [`v3.0.8`](https://redirect.github.com/silverwind/rolldown-license-plugin/releases/tag/3.0.8) [Compare Source](https://redirect.github.com/silverwind/rolldown-license-plugin/compare/3.0.7...3.0.8) - update deps (silverwind) - swap path.join for template concat in I/O hot paths (silverwind) - simplify license sort and allow-branch control flow (silverwind)
typescript-eslint/typescript-eslint (typescript-eslint) ### [`v8.60.0`](https://redirect.github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/typescript-eslint/CHANGELOG.md#8600-2026-05-25) [Compare Source](https://redirect.github.com/typescript-eslint/typescript-eslint/compare/v8.59.4...v8.60.0) This was a version bump only for typescript-eslint to align it with other projects, there were no code changes. See [GitHub Releases](https://redirect.github.com/typescript-eslint/typescript-eslint/releases/tag/v8.60.0) for more information. You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website.
silverwind/updates (updates) ### [`v17.17.2`](https://redirect.github.com/silverwind/updates/releases/tag/17.17.2) [Compare Source](https://redirect.github.com/silverwind/updates/compare/17.17.1...17.17.2) - Read github env tokens lazily instead of at import (silverwind) ### [`v17.17.1`](https://redirect.github.com/silverwind/updates/releases/tag/17.17.1) [Compare Source](https://redirect.github.com/silverwind/updates/compare/17.17.0...17.17.1) - Scope GitHub token fallback to GitHub hosts only (silverwind) ### [`v17.17.0`](https://redirect.github.com/silverwind/updates/releases/tag/17.17.0) [Compare Source](https://redirect.github.com/silverwind/updates/compare/17.16.13...17.17.0) - update deps (silverwind) - Add per-package `overrides` config option ([#​140](https://redirect.github.com/silverwind/updates/issues/140)) (silverwind) - fix three bugs in range/tag handling (silverwind)
vitejs/vite (vite) ### [`v8.0.14`](https://redirect.github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#small-8014-2026-05-21-small) [Compare Source](https://redirect.github.com/vitejs/vite/compare/v8.0.13...v8.0.14) ##### Features - update rolldown to 1.0.2 ([#​22484](https://redirect.github.com/vitejs/vite/issues/22484)) ([96efc88](https://redirect.github.com/vitejs/vite/commit/96efc88570b6a6ddf1a910f106920cbac07b3cf0)) ##### Bug Fixes - **deps:** update all non-major dependencies ([#​22471](https://redirect.github.com/vitejs/vite/issues/22471)) ([98b8163](https://redirect.github.com/vitejs/vite/commit/98b81632139d51820f82036e58d6fbbf122b77b3)) - **dev:** handle errors when sending messages to vite server ([#​22450](https://redirect.github.com/vitejs/vite/issues/22450)) ([e8e9a34](https://redirect.github.com/vitejs/vite/commit/e8e9a34dcf2540139de558a10187630884d10217)) - **html:** handle trailing slash paths in transformIndexHtml ([#​22480](https://redirect.github.com/vitejs/vite/issues/22480)) ([5d94d1b](https://redirect.github.com/vitejs/vite/commit/5d94d1bffdb2a15de9341194d89baec86ce1f693)) - **optimizer:** pass oxc jsx options to transformSync in dependency scan ([#​22342](https://redirect.github.com/vitejs/vite/issues/22342)) ([b3132da](https://redirect.github.com/vitejs/vite/commit/b3132dacea9c6e0cf526cd9f0f09d850f577c262)) ##### Miscellaneous Chores - **deps:** update rolldown-related dependencies ([#​22470](https://redirect.github.com/vitejs/vite/issues/22470)) ([7cb728e](https://redirect.github.com/vitejs/vite/commit/7cb728eb629cc677661f1bc52a044ffc0b87fc7f)) - remove irrelevant commits from changelog ([2c69495](https://redirect.github.com/vitejs/vite/commit/2c69495f250edf01132d4a20128de19dbe836086)) ##### Code Refactoring - **glob:** do not rewrite import path for absolute base ([#​22310](https://redirect.github.com/vitejs/vite/issues/22310)) ([0ae2844](https://redirect.github.com/vitejs/vite/commit/0ae2844ab6d6d1ccf78a2975b8132769fc35b302)) ##### Tests - **css:** sass does not use main field ([#​22449](https://redirect.github.com/vitejs/vite/issues/22449)) ([ebf39a0](https://redirect.github.com/vitejs/vite/commit/ebf39a04329ddc6ba765e006a5d463680a952270))
vuejs/core (vue) ### [`v3.5.35`](https://redirect.github.com/vuejs/core/blob/HEAD/CHANGELOG.md#3535-2026-05-27) [Compare Source](https://redirect.github.com/vuejs/core/compare/v3.5.34...v3.5.35) ##### Bug Fixes - **compiler-core:** avoid double processing v-for keys with v-memo ([#​14861](https://redirect.github.com/vuejs/core/issues/14861)) ([34a0ded](https://redirect.github.com/vuejs/core/commit/34a0ded4d27289a8f227462bd35b6341a4b51831)), closes [#​14859](https://redirect.github.com/vuejs/core/issues/14859) - **compiler-sfc:** resolve top-level exports from files registered as global types ([#​14805](https://redirect.github.com/vuejs/core/issues/14805)) ([3d077f2](https://redirect.github.com/vuejs/core/commit/3d077f26e33510f2ba001d14142ba76a1414dfff)), closes [nuxt/nuxt#33694](https://redirect.github.com/nuxt/nuxt/issues/33694) - **runtime-core:** avoid repeated hydration mismatch checks ([#​14857](https://redirect.github.com/vuejs/core/issues/14857)) ([170fc95](https://redirect.github.com/vuejs/core/commit/170fc95eb64b97024dcb3df770557065e2919aa8)), closes [#​14855](https://redirect.github.com/vuejs/core/issues/14855) - **runtime-core:** skip idle persisted transition hooks in keep-alive moves ([#​14865](https://redirect.github.com/vuejs/core/issues/14865)) ([80fc139](https://redirect.github.com/vuejs/core/commit/80fc139f90513943f1d0da20d353feec8a9ec894)), closes [#​14031](https://redirect.github.com/vuejs/core/issues/14031) - **server-renderer:** propagate sync errors from `ssrRenderSuspense` ([#​14804](https://redirect.github.com/vuejs/core/issues/14804)) ([4760997](https://redirect.github.com/vuejs/core/commit/47609975e294fbcc8017b6d68c9be38fa5508f36)), closes [nuxt/nuxt#28162](https://redirect.github.com/nuxt/nuxt/issues/28162) - **teleport:** skip child unmount when pending mount discarded ([#​14876](https://redirect.github.com/vuejs/core/issues/14876)) ([#​14877](https://redirect.github.com/vuejs/core/issues/14877)) ([584beb1](https://redirect.github.com/vuejs/core/commit/584beb1262d1247d41ed3b463c485c57022fa922)) ##### Performance Improvements - **reactivity:** skip type checks for cached proxies ([#​14860](https://redirect.github.com/vuejs/core/issues/14860)) ([5734fe9](https://redirect.github.com/vuejs/core/commit/5734fe97f6e42d7abb1893c8bc38a17f7deb00b1)) - **runtime-dom:** optimize array event handler dispatch ([#​14828](https://redirect.github.com/vuejs/core/issues/14828)) ([bb18dc8](https://redirect.github.com/vuejs/core/commit/bb18dc8e567ce22f1e5dfbc6b16c1003b48c2785)) - **server-renderer:** avoid materializing iterables in ssrRenderList ([#​14821](https://redirect.github.com/vuejs/core/issues/14821)) ([1b7a2cc](https://redirect.github.com/vuejs/core/commit/1b7a2cc15c501a4b1e4be61874879381af59b74f))
vuejs/language-tools (vue-tsc) ### [`v3.3.2`](https://redirect.github.com/vuejs/language-tools/blob/HEAD/CHANGELOG.md#332-2026-05-25) [Compare Source](https://redirect.github.com/vuejs/language-tools/compare/v3.3.1...v3.3.2) ##### language-core - **feat:** preserve literal types for inline `v-for` sources ([#​6067](https://redirect.github.com/vuejs/language-tools/issues/6067)) - Thanks to [@​kkesidis](https://redirect.github.com/kkesidis)! - **fix:** align `v-bind` shorthand identifier skipping with interpolation - Thanks to [@​KazariEX](https://redirect.github.com/KazariEX)! ##### vscode - **feat:** transform tsserver content ([#​6062](https://redirect.github.com/vuejs/language-tools/issues/6062)) - Thanks to [@​KazariEX](https://redirect.github.com/KazariEX)! - **fix:** do not mark trailing slash in capitalized self-closing tags as invalid ([#​6065](https://redirect.github.com/vuejs/language-tools/issues/6065)) - Thanks to [@​suisanka](https://redirect.github.com/suisanka)!
--- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Only on Monday (`* * * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://redirect.github.com/renovatebot/renovate). --------- Co-authored-by: Lunny Xiao Co-authored-by: silverwind --- .github/workflows/cron-renovate.yml | 2 +- Makefile | 8 - package.json | 29 +- pnpm-lock.yaml | 3532 ++++++++++++++++++------ pnpm-workspace.yaml | 23 - public/assets/img/svg/octicon-flag.svg | 1 + renovate.json5 | 2 +- tools/migrate-nolyfills.ts | 33 - 8 files changed, 2628 insertions(+), 1002 deletions(-) create mode 100644 public/assets/img/svg/octicon-flag.svg delete mode 100644 tools/migrate-nolyfills.ts diff --git a/.github/workflows/cron-renovate.yml b/.github/workflows/cron-renovate.yml index 2c6cc8aedcd..a50af530f20 100644 --- a/.github/workflows/cron-renovate.yml +++ b/.github/workflows/cron-renovate.yml @@ -28,5 +28,5 @@ jobs: token: ${{ secrets.RENOVATE_TOKEN }} env: RENOVATE_BINARY_SOURCE: install # auto-install go/node toolchains needed by post-upgrade tasks. - RENOVATE_ALLOWED_POST_UPGRADE_COMMANDS: '["^make (tidy|svg nolyfill)$"]' + RENOVATE_ALLOWED_POST_UPGRADE_COMMANDS: '["^make (tidy|svg)$"]' RENOVATE_REPOSITORIES: '["go-gitea/gitea"]' diff --git a/Makefile b/Makefile index a74752b8d71..2b19075a484 100644 --- a/Makefile +++ b/Makefile @@ -608,14 +608,6 @@ update-js: node_modules ## update js dependencies rm -rf node_modules pnpm-lock.yaml pnpm install @touch node_modules - $(MAKE) --no-print-directory nolyfill - -.PHONY: nolyfill -nolyfill: node_modules ## apply nolyfill overrides to package.json and relock - pnpm exec nolyfill install - node tools/migrate-nolyfills.ts - pnpm install - @touch node_modules .PHONY: update-py update-py: node_modules ## update py dependencies diff --git a/package.json b/package.json index c56dd03b083..8a915ea795c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "type": "module", - "packageManager": "pnpm@11.2.1", + "packageManager": "pnpm@11.4.0", "engines": { "node": ">= 22.18.0", "pnpm": ">= 11.0.0" @@ -28,7 +28,7 @@ "@lezer/highlight": "1.2.3", "@mcaptcha/vanilla-glue": "0.1.0-rc2", "@mermaid-js/layout-elk": "0.2.1", - "@primer/octicons": "19.26.0", + "@primer/octicons": "19.27.0", "@replit/codemirror-indentation-markers": "6.5.3", "@replit/codemirror-lang-nix": "6.0.1", "@replit/codemirror-lang-svelte": "6.0.0", @@ -45,19 +45,19 @@ "colord": "2.9.3", "compare-versions": "6.1.1", "cropperjs": "1.6.2", - "dayjs": "1.11.20", + "dayjs": "1.11.21", "easymde": "2.21.0", "esbuild": "0.28.0", "idiomorph": "0.7.4", "jquery": "4.0.0", "js-yaml": "4.1.1", - "katex": "0.16.47", + "katex": "0.17.0", "mermaid": "11.15.0", "online-3d-viewer": "0.18.0", "pdfobject": "2.3.1", "perfect-debounce": "2.1.0", "postcss": "8.5.15", - "rolldown-license-plugin": "3.0.7", + "rolldown-license-plugin": "3.0.8", "sortablejs": "1.15.7", "swagger-ui-dist": "5.32.6", "tailwindcss": "3.4.19", @@ -67,14 +67,14 @@ "tributejs": "5.1.3", "uint8-to-base64": "0.2.1", "vanilla-colorful": "0.7.2", - "vite": "8.0.13", + "vite": "8.0.14", "vite-string-plugin": "2.0.4", - "vue": "3.5.34", + "vue": "3.5.35", "vue-bar-graph": "2.2.0", "vue-chartjs": "5.3.3" }, "devDependencies": { - "@eslint-community/eslint-plugin-eslint-comments": "4.7.1", + "@eslint-community/eslint-plugin-eslint-comments": "4.7.2", "@eslint/json": "1.2.0", "@playwright/test": "1.60.0", "@stylistic/eslint-plugin": "5.10.0", @@ -89,9 +89,9 @@ "@types/swagger-ui-dist": "3.30.6", "@types/throttle-debounce": "5.0.2", "@types/toastify-js": "1.12.4", - "@typescript-eslint/parser": "8.59.4", + "@typescript-eslint/parser": "8.60.0", "@vitejs/plugin-vue": "6.0.7", - "@vitest/eslint-plugin": "1.6.17", + "@vitest/eslint-plugin": "1.6.18", "eslint": "10.4.0", "eslint-import-resolver-typescript": "4.4.4", "eslint-plugin-array-func": "5.1.1", @@ -109,8 +109,7 @@ "happy-dom": "20.9.0", "jiti": "2.7.0", "markdownlint-cli": "0.48.0", - "material-icon-theme": "5.34.0", - "nolyfill": "1.0.44", + "material-icon-theme": "5.35.0", "postcss-html": "1.8.1", "spectral-cli-bundle": "1.0.8", "stylelint": "17.12.0", @@ -120,9 +119,9 @@ "stylelint-value-no-unknown-custom-properties": "6.1.1", "svgo": "4.0.1", "typescript": "6.0.3", - "typescript-eslint": "8.59.4", - "updates": "17.16.13", + "typescript-eslint": "8.60.0", + "updates": "17.17.2", "vitest": "4.1.7", - "vue-tsc": "3.3.1" + "vue-tsc": "3.3.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e96e6a6174b..11c6ebf5add 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,28 +4,6 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -overrides: - array-includes: npm:@nolyfill/array-includes@^1 - array.prototype.findlastindex: npm:@nolyfill/array.prototype.findlastindex@^1 - array.prototype.flat: npm:@nolyfill/array.prototype.flat@^1 - array.prototype.flatmap: npm:@nolyfill/array.prototype.flatmap@^1 - es-aggregate-error: npm:@nolyfill/es-aggregate-error@^1 - hasown: npm:@nolyfill/hasown@^1 - is-core-module: npm:@nolyfill/is-core-module@^1 - object.assign: npm:@nolyfill/object.assign@^1 - object.fromentries: npm:@nolyfill/object.fromentries@^1 - object.groupby: npm:@nolyfill/object.groupby@^1 - object.values: npm:@nolyfill/object.values@^1 - safe-buffer: npm:@nolyfill/safe-buffer@^1 - safe-regex-test: npm:@nolyfill/safe-regex-test@^1 - safer-buffer: npm:@nolyfill/safer-buffer@^1 - string.prototype.includes: npm:@nolyfill/string.prototype.includes@^1 - string.prototype.trimend: npm:@nolyfill/string.prototype.trimend@^1 - object-keys: npm:@nolyfill/object-keys@^1 - object.entries: npm:@nolyfill/object.entries@^1 - abab: npm:@nolyfill/abab@^1 - es-set-tostringtag: npm:@nolyfill/es-set-tostringtag@^1 - importers: .: @@ -97,8 +75,8 @@ importers: specifier: 0.2.1 version: 0.2.1(mermaid@11.15.0) '@primer/octicons': - specifier: 19.26.0 - version: 19.26.0 + specifier: 19.27.0 + version: 19.27.0 '@replit/codemirror-indentation-markers': specifier: 6.5.3 version: 6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0) @@ -116,7 +94,7 @@ importers: version: 2.6.2 '@vitejs/plugin-vue': specifier: 6.0.7 - version: 6.0.7(vite@8.0.13(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))(vue@3.5.34(typescript@6.0.3)) + version: 6.0.7(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))(vue@3.5.35(typescript@6.0.3)) ansi_up: specifier: 6.0.6 version: 6.0.6 @@ -128,7 +106,7 @@ importers: version: 4.5.1 chartjs-adapter-dayjs-4: specifier: 1.0.4 - version: 1.0.4(chart.js@4.5.1)(dayjs@1.11.20) + version: 1.0.4(chart.js@4.5.1)(dayjs@1.11.21) chartjs-plugin-zoom: specifier: 2.2.0 version: 2.2.0(chart.js@4.5.1) @@ -148,8 +126,8 @@ importers: specifier: 1.6.2 version: 1.6.2 dayjs: - specifier: 1.11.20 - version: 1.11.20 + specifier: 1.11.21 + version: 1.11.21 easymde: specifier: 2.21.0 version: 2.21.0 @@ -166,8 +144,8 @@ importers: specifier: 4.1.1 version: 4.1.1 katex: - specifier: 0.16.47 - version: 0.16.47 + specifier: 0.17.0 + version: 0.17.0 mermaid: specifier: 11.15.0 version: 11.15.0 @@ -184,8 +162,8 @@ importers: specifier: 8.5.15 version: 8.5.15 rolldown-license-plugin: - specifier: 3.0.7 - version: 3.0.7(rolldown@1.0.1) + specifier: 3.0.8 + version: 3.0.8(rolldown@1.0.2) sortablejs: specifier: 1.15.7 version: 1.15.7 @@ -214,24 +192,24 @@ importers: specifier: 0.7.2 version: 0.7.2 vite: - specifier: 8.0.13 - version: 8.0.13(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) + specifier: 8.0.14 + version: 8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) vite-string-plugin: specifier: 2.0.4 - version: 2.0.4(vite@8.0.13(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)) + version: 2.0.4(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)) vue: - specifier: 3.5.34 - version: 3.5.34(typescript@6.0.3) + specifier: 3.5.35 + version: 3.5.35(typescript@6.0.3) vue-bar-graph: specifier: 2.2.0 version: 2.2.0(typescript@6.0.3) vue-chartjs: specifier: 5.3.3 - version: 5.3.3(chart.js@4.5.1)(vue@3.5.34(typescript@6.0.3)) + version: 5.3.3(chart.js@4.5.1)(vue@3.5.35(typescript@6.0.3)) devDependencies: '@eslint-community/eslint-plugin-eslint-comments': - specifier: 4.7.1 - version: 4.7.1(eslint@10.4.0(jiti@2.7.0)) + specifier: 4.7.2 + version: 4.7.2(eslint@10.4.0(jiti@2.7.0)) '@eslint/json': specifier: 1.2.0 version: 1.2.0 @@ -275,17 +253,17 @@ importers: specifier: 1.12.4 version: 1.12.4 '@typescript-eslint/parser': - specifier: 8.59.4 - version: 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + specifier: 8.60.0 + version: 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) '@vitest/eslint-plugin': - specifier: 1.6.17 - version: 1.6.17(@typescript-eslint/eslint-plugin@8.59.4(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.7(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.13(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))) + specifier: 1.6.18 + version: 1.6.18(@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.7(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))) eslint: specifier: 10.4.0 version: 10.4.0(jiti@2.7.0) eslint-import-resolver-typescript: specifier: 4.4.4 - version: 4.4.4(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.0(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.0(jiti@2.7.0)) + version: 4.4.4(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.0(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-array-func: specifier: 5.1.1 version: 5.1.1(eslint@10.4.0(jiti@2.7.0)) @@ -297,7 +275,7 @@ importers: version: 6.0.0(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-import-x: specifier: 4.16.2 - version: 4.16.2(@typescript-eslint/utils@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.0(jiti@2.7.0)) + version: 4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-playwright: specifier: 2.10.4 version: 2.10.4(eslint@10.4.0(jiti@2.7.0)) @@ -312,7 +290,7 @@ importers: version: 64.0.0(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-vue: specifier: 10.9.1 - version: 10.9.1(@stylistic/eslint-plugin@5.10.0(eslint@10.4.0(jiti@2.7.0)))(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.0(jiti@2.7.0))) + version: 10.9.1(@stylistic/eslint-plugin@5.10.0(eslint@10.4.0(jiti@2.7.0)))(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.0(jiti@2.7.0))) eslint-plugin-vue-scoped-css: specifier: 3.1.0 version: 3.1.0(eslint@10.4.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.0(jiti@2.7.0))) @@ -332,11 +310,8 @@ importers: specifier: 0.48.0 version: 0.48.0 material-icon-theme: - specifier: 5.34.0 - version: 5.34.0 - nolyfill: - specifier: 1.0.44 - version: 1.0.44 + specifier: 5.35.0 + version: 5.35.0 postcss-html: specifier: 1.8.1 version: 1.8.1 @@ -365,17 +340,17 @@ importers: specifier: 6.0.3 version: 6.0.3 typescript-eslint: - specifier: 8.59.4 - version: 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + specifier: 8.60.0 + version: 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) updates: - specifier: 17.16.13 - version: 17.16.13 + specifier: 17.17.2 + version: 17.17.2 vitest: specifier: 4.1.7 - version: 4.1.7(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.13(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)) + version: 4.1.7(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)) vue-tsc: - specifier: 3.3.1 - version: 3.3.1(typescript@6.0.3) + specifier: 3.3.2 + version: 3.3.2(typescript@6.0.3) packages: @@ -386,36 +361,36 @@ packages: '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.3': - resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/runtime@7.29.2': - resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} '@braintree/sanitize-url@7.1.2': resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} - '@cacheable/memory@2.0.8': - resolution: {integrity: sha512-FvEb29x5wVwu/Kf93IWwsOOEuhHh6dYCJF3vcKLzXc0KXIW181AOzv6ceT4ZpBHDvAfG60eqb+ekmrnLHIy+jw==} + '@cacheable/memory@2.0.9': + resolution: {integrity: sha512-HdMx6DoGywB30vacDbBsITbIX4pgFqj1zsrV58jZBUw3klzkNoXhj7qOqAgledhxG7YZI5rBSJg7Zp8/VG0DuA==} '@cacheable/utils@2.4.1': resolution: {integrity: sha512-eiFgzCbIneyMlLOmNG4g9xzF7Hv3Mga4LjxjcSC/ues6VYq2+gUbQI8JqNuw/ZM8tJIeIaBGpswAsqV2V7ApgA==} @@ -561,8 +536,8 @@ packages: '@codemirror/view@6.43.0': resolution: {integrity: sha512-V7ZCLQO3Jus9hzh2jVCCPW3mO4IBMr43O37PqSUYautJSnnJF41YlgLw21x0fLJTYvJ+Vkm6Gp+qKGH9pltgXA==} - '@csstools/css-calc@3.2.0': - resolution: {integrity: sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==} + '@csstools/css-calc@3.2.1': + resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 @@ -574,8 +549,8 @@ packages: peerDependencies: '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.1.3': - resolution: {integrity: sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==} + '@csstools/css-syntax-patches-for-csstree@1.1.4': + resolution: {integrity: sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==} peerDependencies: css-tree: ^3.2.1 peerDependenciesMeta: @@ -773,8 +748,8 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-plugin-eslint-comments@4.7.1': - resolution: {integrity: sha512-Ql2nJFwA8wUGpILYGOQaT1glPsmvEwE0d+a+l7AALLzQvInqdbXJdx7aSu0DpUX9dB1wMVBMhm99/++S3MdEtQ==} + '@eslint-community/eslint-plugin-eslint-comments@4.7.2': + resolution: {integrity: sha512-LF03qURSwEWm2dz5wtdDCzNk+7Opl0X7q6I3undsaIuNsEiNvRV3BCtqu14Q/6Pzg1tBj44LcxpW2EpSLZStZw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 @@ -834,8 +809,8 @@ packages: resolution: {integrity: sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/plugin-kit@0.7.1': - resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} + '@eslint/plugin-kit@0.7.2': + resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@github/browserslist-config@1.0.0': @@ -880,8 +855,8 @@ packages: '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - '@iconify/utils@3.1.1': - resolution: {integrity: sha512-MwzoDtw9rO1x+qfgLTV/IVXsHDBqeYZoMIQC8SfxfYSlaSUG+oWiAcoiB1yajAda6mqblm4/1/w2E8tRu7a7Tw==} + '@iconify/utils@3.1.3': + resolution: {integrity: sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==} '@jest/environment@29.7.0': resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} @@ -927,8 +902,8 @@ packages: '@lezer/common@1.5.2': resolution: {integrity: sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==} - '@lezer/cpp@1.1.5': - resolution: {integrity: sha512-DIhSXmYtJKLehrjzDFN+2cPt547ySQ41nA8yqcDf/GxMc+YM736xqltFkvADL2M0VebU5I+3+4ks2Vv+Kyq3Aw==} + '@lezer/cpp@1.1.6': + resolution: {integrity: sha512-vh9gWWJOXFVY8HBHK3Twzq8MgwG2iN4GSyzBP9sCGTe37P15x2R14VaBQk0VA0ezTRN1KHYBBsHhvpGZ2Xy/pA==} '@lezer/css@1.3.3': resolution: {integrity: sha512-RzBo8r+/6QJeow7aPHIpGVIH59xTcJXp399820gZoMo9noQDRVpJLheIBUicYwKcsbOYoBRoLZlf2720dG/4Tg==} @@ -954,14 +929,14 @@ packages: '@lezer/lr@1.4.10': resolution: {integrity: sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==} - '@lezer/markdown@1.6.3': - resolution: {integrity: sha512-jpGm5Ps+XErS+xA4urw7ogEGkeZOahVQF21Z6oECF0sj+2liwZopd2+I8uH5I/vZsRuuze3OxBREIANLf6KKUw==} + '@lezer/markdown@1.6.4': + resolution: {integrity: sha512-N0SxazMj4k65DBfaf1azqtMZd6u7MqluP84/NZnB/io8Td9aleFmAhz9hcbvSfsxT5tdYlJ5qgv5aMJGY4zEtA==} '@lezer/php@1.0.5': resolution: {integrity: sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==} - '@lezer/python@1.1.18': - resolution: {integrity: sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==} + '@lezer/python@1.1.19': + resolution: {integrity: sha512-MhQIURHRytsNzP/YXnqpYKW6la6voAH3kyplTOOiCdjyFY6cWWGFVmYVdHIPrElqSDf4iCDktQCockB9FxuhzQ==} '@lezer/rust@1.0.2': resolution: {integrity: sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==} @@ -992,9 +967,6 @@ packages: '@mermaid-js/parser@1.1.1': resolution: {integrity: sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==} - '@napi-rs/wasm-runtime@0.2.12': - resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@napi-rs/wasm-runtime@1.1.4': resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} peerDependencies: @@ -1013,90 +985,15 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@nolyfill/abab@1.0.44': - resolution: {integrity: sha512-ZQFi9RSKHKnXM4lLtazYx7otLcqEpQ6sSzs7yzb6zlcimyFIv3CV4s2G+CaA3GnPDTmUFugCn2wWap4jynGQow==} - engines: {node: '>=12.4.0'} - - '@nolyfill/array-includes@1.0.44': - resolution: {integrity: sha512-IVEqpEgFbLaU0hUoMwJYXNSdi6lq+FxHdxd8xTKDLxh8k6u5YNGz4Bo6bT46l7p0x8PbJmHViBtngqhvE528fA==} - engines: {node: '>=12.4.0'} - - '@nolyfill/array.prototype.findlastindex@1.0.44': - resolution: {integrity: sha512-BLeHS3SulsR3iFxxETL9q21lArV2KS7lh2wcUnhue1ppx19xah1W7MdFxepyeGbM3Umk9S90snfboXAds5HkTg==} - engines: {node: '>=12.4.0'} - - '@nolyfill/array.prototype.flat@1.0.44': - resolution: {integrity: sha512-HnOqOT4te0l+XU9UKhy3ry+pc+ZRNsUJFR7omMEtjXf4+dq6oXmIBk7vR35+hSTk4ldjwm/27jwV3ZIGp3l4IQ==} - engines: {node: '>=12.4.0'} - - '@nolyfill/array.prototype.flatmap@1.0.44': - resolution: {integrity: sha512-P6OsaEUrpBJ9NdNekFDQVM9LOFHPDKSJzwOWRBaC6LqREX+4lkZT2Q+to78R6aG6atuOQsxBVqPjMGCKjWdvyQ==} - engines: {node: '>=12.4.0'} - - '@nolyfill/es-set-tostringtag@1.0.44': - resolution: {integrity: sha512-Qfiv/3wI+mKSPCgU8Fg/7Auu4os00St4GwyLqCCZ/oBD4W00itWUkl9Rq1MCGS+VXYDnTobvrc6AvPagwnT2pg==} - engines: {node: '>=12.4.0'} - - '@nolyfill/hasown@1.0.44': - resolution: {integrity: sha512-GA/21lkTr2PAQuT6jGnhLuBD5IFd/AEhBXJ/tf33+/bVxPxg+5ejKx9jGQGnyV/P0eSmdup5E+s8b2HL6lOrwQ==} - engines: {node: '>=12.4.0'} - - '@nolyfill/is-core-module@1.0.39': - resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} - engines: {node: '>=12.4.0'} - - '@nolyfill/object-keys@1.0.44': - resolution: {integrity: sha512-k59MxUv8AFPd+GzZlGBhBaW6zu6t7f2euhctz7vdT3WsE3LVPANgE7uAN71IIpxaIn+bBEX1Xesf/rTwWqVO0Q==} - engines: {node: '>=12.4.0'} - - '@nolyfill/object.assign@1.0.44': - resolution: {integrity: sha512-cZoXq09YZXDgkxRMAP/TTb3kAsWm7p5OyBugWDe4fOfxf0XRI55mgDSkuyq41sV1qW1zVC5aSsKEh1hQo1KOvA==} - engines: {node: '>=12.4.0'} - - '@nolyfill/object.entries@1.0.44': - resolution: {integrity: sha512-RCxO6EH9YbvxQWGYLKOd7MjNi7vKzPkXv1VDWNsy1C8BksQxXNPQrddlu3INi1O2fexk82WXpCCeaCtpU/y21w==} - engines: {node: '>=12.4.0'} - - '@nolyfill/object.fromentries@1.0.44': - resolution: {integrity: sha512-/LrsCtpLmByZ6GwP/NeXULSgMyNsVr5d6FlgQy1HZatAiBc8c+WZ1VmFkK19ZLXCNNXBedXDultrp0x4Nz+QQw==} - engines: {node: '>=12.4.0'} - - '@nolyfill/object.groupby@1.0.44': - resolution: {integrity: sha512-jCt/8pN+10mlbeg0ZESpVVaqn5qqpv6kpjM+GDfEP7cXGDSPlIjtvfYWRZK4k4Gftkhhgqkzvcrr8z1wuNO1TQ==} - engines: {node: '>=12.4.0'} - - '@nolyfill/object.values@1.0.44': - resolution: {integrity: sha512-bwIpVzFMudUC0ofnvdSDB/OyGUizcU+r32ZZ0QTMbN03gUttMtdCFDekuSYT0XGFgufTQyZ4ONBnAeb3DFCPGQ==} - engines: {node: '>=12.4.0'} - - '@nolyfill/safe-regex-test@1.0.44': - resolution: {integrity: sha512-Q6veatd1NebtD8Sre6zjvO35QzG21IskMVOOEbePFcNO9noanNJgsqHeOCr0c5yZz6Z0DAizLg2gIZWokJSkXw==} - engines: {node: '>=12.4.0'} - - '@nolyfill/safer-buffer@1.0.44': - resolution: {integrity: sha512-Ouw1fMwjAy1V4MpnDASfu1DCPgkP0nNFteiiWbFoEGSqa7Vnmkb6if2c522N2WcMk+RuaaabQbC1F1D4/kTXcg==} - engines: {node: '>=12.4.0'} - - '@nolyfill/shared@1.0.44': - resolution: {integrity: sha512-NI1zxDh4LYL7PYlKKCwojjuc5CEZslywrOTKBNyodjmWjRiZ4AlCMs3Gp+zDoPQPNkYCSQp/luNojHmJWWfCbw==} - - '@nolyfill/string.prototype.includes@1.0.44': - resolution: {integrity: sha512-d1t7rnoAYyoap0X3a/gCnusCvxzK6v7uMFzW8k0mI2WtAK8HiKuzaQUwAriyVPh63GsvQCqvXx8Y5gtdh4LjSA==} - engines: {node: '>=12.4.0'} - - '@nolyfill/string.prototype.trimend@1.0.44': - resolution: {integrity: sha512-3dsKlf4Ma7o+uxLIg5OI1Tgwfet2pE8WTbPjEGWvOe6CSjMtK0skJnnSVHaEVX4N4mYU81To0qDeZOPqjaUotg==} - engines: {node: '>=12.4.0'} - - '@oxc-project/types@0.130.0': - resolution: {integrity: sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==} + '@oxc-project/types@0.132.0': + resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==} '@package-json/types@0.0.12': resolution: {integrity: sha512-uu43FGU34B5VM9mCNjXCwLaGHYjXdNincqKLaraaCW+7S2+SmiBg1Nv8bPnmschrIfZmfKNY9f3fC376MRrObw==} - '@pkgr/core@0.2.9': - resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@pkgr/core@0.3.6': + resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} + engines: {node: ^14.18.0 || >=16.0.0} '@playwright/test@1.60.0': resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==} @@ -1106,8 +1003,8 @@ packages: '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - '@primer/octicons@19.26.0': - resolution: {integrity: sha512-K+ewEvXf9w+65i4OeLDXOhd1yT5qdMIpU/miqmjTuaGufZCxFn4131SIh0CfrinmLFnL9Ow60kk3hxx0Y4oIAQ==} + '@primer/octicons@19.27.0': + resolution: {integrity: sha512-7xC6D89f9IcoDezeKTGETbgRAoXJnbZlakavqYzD4Wo+uTC6212k0fTE/dLV8WCDOwfp//WyONftdaFRdI1VdQ==} '@replit/codemirror-indentation-markers@6.5.3': resolution: {integrity: sha512-hL5Sfvw3C1vgg7GolLe/uxX5T3tmgOA3ZzqlMv47zjU1ON51pzNWiVbS22oh6crYhtVhv8b3gdXwoYp++2ilHw==} @@ -1157,97 +1054,97 @@ packages: resolution: {integrity: sha512-FqALmHI8D4o6lk/LRWDnhw95z5eO+eAa6ORjVg09YRR7BkcM6oPHU9uyC0gtQG5vpFLvgpeU4+zEAz2H8APHNw==} engines: {node: '>= 10'} - '@rolldown/binding-android-arm64@1.0.1': - resolution: {integrity: sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==} + '@rolldown/binding-android-arm64@1.0.2': + resolution: {integrity: sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.1': - resolution: {integrity: sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==} + '@rolldown/binding-darwin-arm64@1.0.2': + resolution: {integrity: sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.1': - resolution: {integrity: sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==} + '@rolldown/binding-darwin-x64@1.0.2': + resolution: {integrity: sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.1': - resolution: {integrity: sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==} + '@rolldown/binding-freebsd-x64@1.0.2': + resolution: {integrity: sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.1': - resolution: {integrity: sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.2': + resolution: {integrity: sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.1': - resolution: {integrity: sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==} + '@rolldown/binding-linux-arm64-gnu@1.0.2': + resolution: {integrity: sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.1': - resolution: {integrity: sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==} + '@rolldown/binding-linux-arm64-musl@1.0.2': + resolution: {integrity: sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.1': - resolution: {integrity: sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==} + '@rolldown/binding-linux-ppc64-gnu@1.0.2': + resolution: {integrity: sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.1': - resolution: {integrity: sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==} + '@rolldown/binding-linux-s390x-gnu@1.0.2': + resolution: {integrity: sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.1': - resolution: {integrity: sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==} + '@rolldown/binding-linux-x64-gnu@1.0.2': + resolution: {integrity: sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.1': - resolution: {integrity: sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==} + '@rolldown/binding-linux-x64-musl@1.0.2': + resolution: {integrity: sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.1': - resolution: {integrity: sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==} + '@rolldown/binding-openharmony-arm64@1.0.2': + resolution: {integrity: sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.1': - resolution: {integrity: sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==} + '@rolldown/binding-wasm32-wasi@1.0.2': + resolution: {integrity: sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.1': - resolution: {integrity: sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==} + '@rolldown/binding-win32-arm64-msvc@1.0.2': + resolution: {integrity: sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.1': - resolution: {integrity: sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==} + '@rolldown/binding-win32-x64-msvc@1.0.2': + resolution: {integrity: sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1306,8 +1203,8 @@ packages: peerDependencies: stylelint: ^17.6.0 - '@swc/helpers@0.5.21': - resolution: {integrity: sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==} + '@swc/helpers@0.5.23': + resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} '@tootallnate/once@2.0.1': resolution: {integrity: sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==} @@ -1424,8 +1321,8 @@ packages: '@types/esrecurse@4.3.1': resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} @@ -1511,165 +1408,234 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/eslint-plugin@8.59.4': - resolution: {integrity: sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==} + '@typescript-eslint/eslint-plugin@8.60.0': + resolution: {integrity: sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.59.4 + '@typescript-eslint/parser': ^8.60.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.59.4': - resolution: {integrity: sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ==} + '@typescript-eslint/eslint-plugin@8.60.1': + resolution: {integrity: sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.60.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.60.0': + resolution: {integrity: sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.59.4': - resolution: {integrity: sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==} + '@typescript-eslint/project-service@8.60.0': + resolution: {integrity: sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.59.4': - resolution: {integrity: sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/tsconfig-utils@8.59.4': - resolution: {integrity: sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==} + '@typescript-eslint/project-service@8.60.1': + resolution: {integrity: sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.59.4': - resolution: {integrity: sha512-uonTuPAAKr9XaBGqJ3LjYTh72zy5DyGesljO9gtmk/eFW0W1fRHjnwVYKB35Lm8d5Q5CluEW3gPHjTvZTmgrfA==} + '@typescript-eslint/scope-manager@8.60.0': + resolution: {integrity: sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/scope-manager@8.60.1': + resolution: {integrity: sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.60.0': + resolution: {integrity: sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/tsconfig-utils@8.60.1': + resolution: {integrity: sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.60.0': + resolution: {integrity: sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.59.4': - resolution: {integrity: sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.59.4': - resolution: {integrity: sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/utils@8.59.4': - resolution: {integrity: sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw==} + '@typescript-eslint/type-utils@8.60.1': + resolution: {integrity: sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.59.4': - resolution: {integrity: sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==} + '@typescript-eslint/types@8.60.0': + resolution: {integrity: sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@unrs/resolver-binding-android-arm-eabi@1.11.1': - resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} + '@typescript-eslint/types@8.60.1': + resolution: {integrity: sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.60.0': + resolution: {integrity: sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/typescript-estree@8.60.1': + resolution: {integrity: sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.60.0': + resolution: {integrity: sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.60.1': + resolution: {integrity: sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.60.0': + resolution: {integrity: sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/visitor-keys@8.60.1': + resolution: {integrity: sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@unrs/resolver-binding-android-arm-eabi@1.12.2': + resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==} cpu: [arm] os: [android] - '@unrs/resolver-binding-android-arm64@1.11.1': - resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} + '@unrs/resolver-binding-android-arm64@1.12.2': + resolution: {integrity: sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==} cpu: [arm64] os: [android] - '@unrs/resolver-binding-darwin-arm64@1.11.1': - resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} + '@unrs/resolver-binding-darwin-arm64@1.12.2': + resolution: {integrity: sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==} cpu: [arm64] os: [darwin] - '@unrs/resolver-binding-darwin-x64@1.11.1': - resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} + '@unrs/resolver-binding-darwin-x64@1.12.2': + resolution: {integrity: sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==} cpu: [x64] os: [darwin] - '@unrs/resolver-binding-freebsd-x64@1.11.1': - resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} + '@unrs/resolver-binding-freebsd-x64@1.12.2': + resolution: {integrity: sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==} cpu: [x64] os: [freebsd] - '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': - resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': + resolution: {integrity: sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==} cpu: [arm] os: [linux] - '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': - resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': + resolution: {integrity: sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==} cpu: [arm] os: [linux] - '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': - resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': + resolution: {integrity: sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==} cpu: [arm64] os: [linux] libc: [glibc] - '@unrs/resolver-binding-linux-arm64-musl@1.11.1': - resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': + resolution: {integrity: sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==} cpu: [arm64] os: [linux] libc: [musl] - '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': - resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': + resolution: {integrity: sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': + resolution: {integrity: sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': + resolution: {integrity: sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==} cpu: [ppc64] os: [linux] libc: [glibc] - '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': - resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': + resolution: {integrity: sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==} cpu: [riscv64] os: [linux] libc: [glibc] - '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': - resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': + resolution: {integrity: sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==} cpu: [riscv64] os: [linux] libc: [musl] - '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': - resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': + resolution: {integrity: sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==} cpu: [s390x] os: [linux] libc: [glibc] - '@unrs/resolver-binding-linux-x64-gnu@1.11.1': - resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + resolution: {integrity: sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==} cpu: [x64] os: [linux] libc: [glibc] - '@unrs/resolver-binding-linux-x64-musl@1.11.1': - resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + resolution: {integrity: sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==} cpu: [x64] os: [linux] libc: [musl] - '@unrs/resolver-binding-wasm32-wasi@1.11.1': - resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + resolution: {integrity: sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==} + cpu: [arm64] + os: [openharmony] + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': + resolution: {integrity: sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': - resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': + resolution: {integrity: sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==} cpu: [arm64] os: [win32] - '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': - resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': + resolution: {integrity: sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==} cpu: [ia32] os: [win32] - '@unrs/resolver-binding-win32-x64-msvc@1.11.1': - resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': + resolution: {integrity: sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==} cpu: [x64] os: [win32] @@ -1683,8 +1649,8 @@ packages: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 vue: ^3.2.25 - '@vitest/eslint-plugin@1.6.17': - resolution: {integrity: sha512-sIVY9ZeVcXyPxFCNRkIt8Yw4keKIcUyp9/8qnmuomPwE+ST1htw5sZsbqdUMTiah9SmCg1JYoK9RqdDtPeNYYg==} + '@vitest/eslint-plugin@1.6.18': + resolution: {integrity: sha512-J6U4X0jH3NwTuYouvrJn6I8ypTOU+GhKEjyVwpoPnDuc23usa/xi/R0caWLBbNp3xLy3/rL1YkuJuneTMVV4Mg==} engines: {node: '>=18'} peerDependencies: '@typescript-eslint/eslint-plugin': '*' @@ -1737,37 +1703,41 @@ packages: '@volar/typescript@2.4.28': resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} - '@vue/compiler-core@3.5.34': - resolution: {integrity: sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==} + '@vue/compiler-core@3.5.35': + resolution: {integrity: sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==} - '@vue/compiler-dom@3.5.34': - resolution: {integrity: sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==} + '@vue/compiler-dom@3.5.35': + resolution: {integrity: sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==} - '@vue/compiler-sfc@3.5.34': - resolution: {integrity: sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==} + '@vue/compiler-sfc@3.5.35': + resolution: {integrity: sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==} - '@vue/compiler-ssr@3.5.34': - resolution: {integrity: sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ==} + '@vue/compiler-ssr@3.5.35': + resolution: {integrity: sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==} - '@vue/language-core@3.3.1': - resolution: {integrity: sha512-NP8g6V7x81NVOXbLupUvYY6i6LqUkjkVowe2epRedmpgaFCOdjgWHE/rQBvEJ4r7koAYODIjGeBWEdt6n7jYXQ==} + '@vue/language-core@3.3.2': + resolution: {integrity: sha512-CLwjSfHlPLhjd2qhuS3tTFtnOIWHXAM5u4X1DxmzlQ8j5bmOYlKCsSusOP7jCRJnlVg0mCTQtHU3vwFvopZGoQ==} - '@vue/reactivity@3.5.34': - resolution: {integrity: sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==} + '@vue/reactivity@3.5.35': + resolution: {integrity: sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==} - '@vue/runtime-core@3.5.34': - resolution: {integrity: sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==} + '@vue/runtime-core@3.5.35': + resolution: {integrity: sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==} - '@vue/runtime-dom@3.5.34': - resolution: {integrity: sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==} + '@vue/runtime-dom@3.5.35': + resolution: {integrity: sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==} - '@vue/server-renderer@3.5.34': - resolution: {integrity: sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==} + '@vue/server-renderer@3.5.35': + resolution: {integrity: sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA==} peerDependencies: - vue: 3.5.34 + vue: 3.5.35 - '@vue/shared@3.5.34': - resolution: {integrity: sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==} + '@vue/shared@3.5.35': + resolution: {integrity: sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==} + + abab@2.0.6: + resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + deprecated: Use your platform's native atob() and btoa() methods instead acorn-globals@7.0.1: resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} @@ -1799,6 +1769,14 @@ packages: alien-signals@3.2.1: resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==} + ansi-escapes@1.4.0: + resolution: {integrity: sha512-wiXutNjDUlNEDWHcYH3jtZUhd3c4/VojassD8zHdHCY13xbZy2XbW+NKQwA0tWGBVzDA9qEzYwfoSsWmviidhw==} + engines: {node: '>=0.10.0'} + + ansi-regex@2.1.1: + resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} + engines: {node: '>=0.10.0'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1807,6 +1785,10 @@ packages: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} + ansi-styles@2.2.1: + resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} + engines: {node: '>=0.10.0'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1835,9 +1817,40 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + asciinema-player@3.15.1: resolution: {integrity: sha512-agVYeNlPxthLyAb92l9AS7ypW0uhesqOuQzyR58Q4Sj+MvesQztZBgx86lHqNJkB8rQ6EP0LeA9czGytQUBpYw==} + asn1@0.2.6: + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + + assert-plus@1.0.0: + resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} + engines: {node: '>=0.8'} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -1849,11 +1862,25 @@ packages: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - axe-core@4.11.4: - resolution: {integrity: sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==} + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + aws-sign2@0.7.0: + resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} + + aws4@1.13.2: + resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} + + axe-core@4.12.0: + resolution: {integrity: sha512-FTavr/7Ba0IptwGOPxnQvdyW2tAsdLBMTBXz7rKH6xJ2skpyxpBxyHkDdBs4lf69yRqYpkqCdfhnwS8YULGOmg==} engines: {node: '>=4'} axobject-query@4.1.0: @@ -1870,23 +1897,33 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.10.27: - resolution: {integrity: sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==} + baseline-browser-mapping@2.10.33: + resolution: {integrity: sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==} engines: {node: '>=6.0.0'} hasBin: true + bcrypt-pbkdf@1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + biome@0.3.3: + resolution: {integrity: sha512-4LXjrQYbn9iTXu9Y4SKT7ABzTV0WnLDHCVSd2fPUOKsy1gQ+E4xPFmlY1zcWexoi0j7fGHItlL6OWA2CZ/yYAQ==} + hasBin: true + + bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - brace-expansion@1.1.14: - resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + brace-expansion@1.1.15: + resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} - brace-expansion@5.0.5: - resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} engines: {node: 18 || 20 || >=22} braces@3.0.3: @@ -1905,16 +1942,28 @@ packages: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} - builtin-modules@5.1.0: - resolution: {integrity: sha512-c5JxaDrzwRjq3WyJkI1AGR5xy6Gr6udlt7sQPbl09+3ckB+Zo2qqQ2KhCTBr7Q8dHB43bENGYEk4xddrFH/b7A==} + builtin-modules@5.2.0: + resolution: {integrity: sha512-02yxLeyxF4dNl6SlY6/5HfRSrSdZ/sCPoxy2kZNP5dZZX8LSAD9aE2gtJIUgWrsQTiMPl3mxESyrobSwvRGisQ==} engines: {node: '>=18.20'} bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - cacheable@2.3.4: - resolution: {integrity: sha512-djgxybDbw9fL/ZWMI3+CE8ZilNxcwFkVtDc1gJ+IlOSSWkSMPQabhV/XCHTQ6pwwN6aivXPZ43omTooZiX06Ew==} + cacheable@2.3.5: + resolution: {integrity: sha512-EQfaKe09tl615iNvq/TBRWTFf1AKJNXYQSsMx0Z3EI0nA+pVsVPS8wJhnRlkbdacKPh1d0qVIhwTc2zsQNFEEg==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} @@ -1924,13 +1973,20 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001791: - resolution: {integrity: sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==} + caniuse-lite@1.0.30001793: + resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + + caseless@0.12.0: + resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chalk@1.1.3: + resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} + engines: {node: '>=0.10.0'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1985,9 +2041,20 @@ packages: resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} engines: {node: '>=4'} + cli-cursor@1.0.2: + resolution: {integrity: sha512-25tABq090YNKkF6JH7lcwO0zFJTRke4Jcq9iX2nr/Sz0Cjjv4gckmwlW6Ty/aoyFd6z3ysR2hMGC2GFugmBo6A==} + engines: {node: '>=0.10.0'} + + cli-width@1.1.1: + resolution: {integrity: sha512-eMU2akIeEIkCxGXUNmDnJq1KzOIiPnJ+rKqRe6hcxE3vIOPvpMrBYOn/Bl7zNlYJj/zQxXquAnozHUCf9Whnsg==} + clippie@4.2.0: resolution: {integrity: sha512-NcWaVzqChJ69+foFhwJXta3KXWNDJlpicxcfZG5udobyszOSBDhmFubKv1b/1nIZiVAsPoKqME2iV1SITZqFoQ==} + code-point-at@1.1.0: + resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==} + engines: {node: '>=0.10.0'} + codemirror-lang-elixir@4.0.1: resolution: {integrity: sha512-z6W/XB4b7TZrp9EZYBGVq93vQfvKbff+1iM8YZaVErL0dguBAeLmVRlEv1NuDZHOP1qjJ3NwyibkUkNWn7q9VQ==} @@ -2019,6 +2086,9 @@ packages: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -2031,8 +2101,8 @@ packages: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} - comment-parser@1.4.6: - resolution: {integrity: sha512-ObxuY6vnbWTN6Od72xfwN9DbzC7Y2vv8u1Soi9ahRKL37gb6y1qk6/dgjs+3JWuXJHWvsg3BXIwzd/rkmAwavg==} + comment-parser@1.4.7: + resolution: {integrity: sha512-0h+uSNtQGW3D98eQt3jJ8L06Fves8hncB4V/PKdw/Qb8Hnk19VaKuTr55UNRYiSoVa7WwrFls+rh3ux9agmkeQ==} engines: {node: '>= 12.0.0'} compare-versions@6.1.1: @@ -2041,18 +2111,22 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} core-js-compat@3.49.0: resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} + core-js@2.6.12: + resolution: {integrity: sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==} + deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js. + core-js@3.32.2: resolution: {integrity: sha512-pxXSw1mYZPDGvTQqEc5vgIb83jGQKFGYWY76z4a7weZXUolw3G+OvpZqSRcfYOoOVUQJYEPsWeQK8pKEnUtWxQ==} + core-util-is@1.0.2: + resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} + cose-base@1.0.3: resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} @@ -2129,8 +2203,8 @@ packages: peerDependencies: cytoscape: ^3.2.0 - cytoscape@3.33.3: - resolution: {integrity: sha512-Gej7U+OKR+LZ8kvX7rb2HhCYJ0IhvEFsnkud4SB1PR+BUY/TsSO0dmOW59WEVLu51b1Rm+gQRKoz4bLYxGSZ2g==} + cytoscape@3.33.4: + resolution: {integrity: sha512-HIN5Pmd9MrX9BkV7tDwnOcEJCSFvCpc8X97h3f508J6I5FsqAY65wKOCvgH2CuP42CaahWaz4tuh32SOOIH7ww==} engines: {node: '>=0.10'} d3-array@2.12.1: @@ -2278,12 +2352,28 @@ packages: damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + dashdash@1.14.1: + resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} + engines: {node: '>=0.10'} + data-urls@3.0.2: resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} engines: {node: '>=12'} - dayjs@1.11.20: - resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} @@ -2319,6 +2409,14 @@ packages: resolution: {integrity: sha512-RHd9ABw4Fvk+gYDWqwOftG849x0bYOySl/RgX0tLI9i27ZIeSO91mLZJEp7oPHOMFqHvpgu21YptmDt0FYD/0A==} engines: {node: '>=0.10.0'} + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + delaunator@5.1.0: resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} @@ -2365,17 +2463,30 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - dompurify@3.4.2: - resolution: {integrity: sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==} + dompurify@3.4.7: + resolution: {integrity: sha512-2jBxDJY4RR06tQNy4w5FlFH7kfxsQZlufd0sbv+chfHCxeJwrFw2baUDsSwvBISD4K4RDbd0PTfy3uNXsR6siA==} domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + earlgrey-runtime@0.1.2: + resolution: {integrity: sha512-T4qoScXi5TwALDv8nlGTvOuCT8jXcKcxtO8qVdqv46IA2GHJfQzwoBPbkOmORnyhu3A98cVVuhWLsM2CzPljJg==} + easymde@2.21.0: resolution: {integrity: sha512-5uE7I/DEN8gvGRwxaqAv7h1PMEK2ykNXVX5zL0dK3nCYROGja3AMbdQz8eCEELnfvCfy7tRkTmLuvyJG8uSWjQ==} - electron-to-chromium@1.5.349: - resolution: {integrity: sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==} + ecc-jsbn@0.1.2: + resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} + + editor@1.0.0: + resolution: {integrity: sha512-SoRmbGStwNYHgKfjOrX2L0mUvp9bUVv0uPppZSOMAntEbcFtoC3MKF5b3T6HQPXKIV+QGY3xPO3JK5it5lVkuw==} + + electron-to-chromium@1.5.364: + resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==} elkjs@0.9.3: resolution: {integrity: sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==} @@ -2405,6 +2516,14 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} @@ -2412,8 +2531,24 @@ packages: es-module-lexer@2.1.0: resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} - es-toolkit@1.46.1: - resolution: {integrity: sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==} + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + es-toolkit@1.47.0: + resolution: {integrity: sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==} esbuild@0.28.0: resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} @@ -2472,8 +2607,8 @@ packages: eslint-plugin-import-x: optional: true - eslint-module-utils@2.12.1: - resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + eslint-module-utils@2.13.0: + resolution: {integrity: sha512-bLohSkT6469rRs8czj0tLTD8vaeIS/whvPRJVjDr7IuoTT1k5DYDERlNycjDj/HkOlvQdYurmfZ/g3fG5bgeLQ==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' @@ -2571,8 +2706,8 @@ packages: peerDependencies: eslint: '>=8.40.0' - eslint-plugin-prettier@5.5.5: - resolution: {integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==} + eslint-plugin-prettier@5.5.6: + resolution: {integrity: sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: '@types/eslint': '>=8.0.0' @@ -2707,10 +2842,21 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + exit-hook@1.1.1: + resolution: {integrity: sha512-MsG3prOVw1WtLXAZbM3KiYtooKR1LvxHh3VHsVtIy0uiUu8usxgB/94DP2HxtD/661lLdB6yzQ09lGJSQr6nkg==} + engines: {node: '>=0.10.0'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + extsprintf@1.3.0: + resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} + engines: {'0': node >=0.6.0} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2727,8 +2873,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} @@ -2752,6 +2898,10 @@ packages: fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + figures@1.7.0: + resolution: {integrity: sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ==} + engines: {node: '>=0.10.0'} + file-entry-cache@11.1.3: resolution: {integrity: sha512-oMbq0PD6VIiIwMF6LIa7MEwd/l9huKwmqRKXqmrkqIZv8CvRbfowL+L0ryAl8h//HfAS0zS+4SbYoRyAoA6BJA==} @@ -2781,10 +2931,31 @@ packages: flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + forever-agent@0.6.1: + resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} + + form-data@2.3.3: + resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} + engines: {node: '>= 0.12'} + form-data@4.0.5: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} + fs-extra@0.26.7: + resolution: {integrity: sha512-waKu+1KumRhYv8D8gMRCKJGAMI9pRnPuEb1mvgYD0f7wBscg+h6bW4FDTmEZhB9VKxvoTtxW+Y7bnIlB7zja6Q==} + + fs-promise@0.5.0: + resolution: {integrity: sha512-Y+4F4ujhEcayCJt6JmzcOun9MYGQwz+bVUiuBmTkJImhBHKpBvmVPZR9wtfiF7k3ffwAOAuurygQe+cPLSFQhw==} + deprecated: Use mz or fs-extra^3.0 with Promise Support + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2795,16 +2966,45 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + functional-red-black-tree@1.0.1: resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} - get-east-asian-width@1.5.0: - resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} engines: {node: '>=18'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + getpass@0.1.7: + resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2813,6 +3013,10 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + global-modules@2.0.0: resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} engines: {node: '>=6'} @@ -2833,6 +3037,10 @@ packages: resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==} engines: {node: '>=18'} + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + globby@16.2.0: resolution: {integrity: sha512-QrJia2qDf5BB/V6HYlDTs0I0lBahyjLzpGQg3KT7FnCdTonAyPy2RtY802m2k4ALx6Dp752f82WsOczEVr3l6Q==} engines: {node: '>=20'} @@ -2840,6 +3048,10 @@ packages: globjoin@0.1.4: resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -2854,6 +3066,23 @@ packages: resolution: {integrity: sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ==} engines: {node: '>=20.0.0'} + har-schema@2.0.0: + resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} + engines: {node: '>=4'} + + har-validator@5.1.5: + resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} + engines: {node: '>=6'} + deprecated: this library is no longer supported + + has-ansi@2.0.0: + resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} + engines: {node: '>=0.10.0'} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -2862,10 +3091,29 @@ packages: resolution: {integrity: sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==} engines: {node: '>=12'} + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + hashery@1.5.1: resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==} engines: {node: '>=20'} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + hookified@1.15.1: resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} @@ -2887,6 +3135,10 @@ packages: resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} engines: {node: '>= 6'} + http-signature@1.2.0: + resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} + engines: {node: '>=0.8', npm: '>=1.3.7'} + https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -2924,6 +3176,13 @@ packages: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} engines: {node: '>=12'} + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} @@ -2931,6 +3190,16 @@ packages: resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + inquirer-promise@0.0.3: + resolution: {integrity: sha512-82CQX586JAV9GAgU9yXZsMDs+NorjA0nLhkfFx9+PReyOnuoHRbHrC1Z90sS95bFJI1Tm1gzMObuE0HabzkJpg==} + + inquirer@0.11.4: + resolution: {integrity: sha512-QR+2TW90jnKk9LUUtbcA3yQXKt2rDEKMh6+BAZQIeumtzHexnwVLdPakSslGijXYLJCzFv7GMXbFCn0pA00EUw==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + internmap@1.0.1: resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} @@ -2944,13 +3213,29 @@ packages: is-alphanumerical@2.0.1: resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + is-buffer@1.1.6: resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} @@ -2961,6 +3246,22 @@ packages: is-bun-module@2.0.0: resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} @@ -2968,10 +3269,22 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@1.0.0: + resolution: {integrity: sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==} + engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -2979,6 +3292,18 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -2990,12 +3315,57 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-typedarray@1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + is-valid-element-name@1.0.0: resolution: {integrity: sha512-GZITEJY2LkSjQfaIPBha7eyZv+ge0PhBR7KITeCCWvy7VBQrCUdFkvpI+HrAPQjVtVjy1LvlEkqQTHckoszruw==} + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isstream@0.1.2: + resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + jest-environment-jsdom@29.7.0: resolution: {integrity: sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3041,6 +3411,9 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsbn@0.1.1: + resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} + jsdoc-type-pratt-parser@7.2.0: resolution: {integrity: sha512-dh140MMgjyg3JhJZY/+iEzW+NO5xR2gpbDFKHqotCmexElVntw7GjWjt511+C/Ef02RU5TKYrJo/Xlzk+OLaTw==} engines: {node: '>=20.0.0'} @@ -3071,9 +3444,15 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true @@ -3081,10 +3460,17 @@ packages: jsonc-parser@3.3.1: resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonfile@2.4.0: + resolution: {integrity: sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==} + jsonpointer@5.0.1: resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} engines: {node: '>=0.10.0'} + jsprim@1.4.2: + resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} + engines: {node: '>=0.6.0'} + jsx-ast-utils-x@0.1.0: resolution: {integrity: sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3093,10 +3479,17 @@ packages: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + kaiser@0.0.4: + resolution: {integrity: sha512-m8ju+rmBqvclZmyrOXgGGhOYSjKJK6RN1NhqEltemY87UqZOxEkizg9TOy1vQSyJ01Wx6SAPuuN0iO2Mgislvw==} + katex@0.16.47: resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} hasBin: true + katex@0.17.0: + resolution: {integrity: sha512-Vdw0ATsQ9V+LuegM/BTwQqV/6cTl5lbGcIrU+BCgLxyf6bo38ybOr372tuSIxir3CN720flu1meYR6XzNMwQnw==} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -3114,6 +3507,9 @@ packages: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} + klaw@1.3.1: + resolution: {integrity: sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==} + language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -3215,8 +3611,8 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - linkify-it@5.0.0: - resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + linkify-it@5.0.1: + resolution: {integrity: sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==} locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} @@ -3243,6 +3639,12 @@ packages: lodash.upperfirst@4.3.1: resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} + lodash@3.10.1: + resolution: {integrity: sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -3269,10 +3671,14 @@ packages: engines: {node: '>= 12'} hasBin: true - material-icon-theme@5.34.0: - resolution: {integrity: sha512-m+ZgtdtJMkfOwxpfUH8FqyJbfnAJuxMBNv4XRvz3m808Is42RbeYc/5W0bk5qGJJLxzF5Cupj6Or52aQISwz3A==} + material-icon-theme@5.35.0: + resolution: {integrity: sha512-ptU3rjmZjPCeLRog0HcJ1HtnoVgOslc350WHk1oIvX7fVFE4NBAF7GL3QvCCEjtd1TSSDoxmS4dWsv6bTB1x9g==} engines: {vscode: ^1.55.0} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mathml-tag-names@4.0.0: resolution: {integrity: sha512-aa6AU2Pcx0VP/XWnh8IGL0SYSgQHDT6Ucror2j2mXeFAlN3ahaNs8EZtG1YiticMkSLj3Gt6VPFfZogt7G5iFQ==} @@ -3393,9 +3799,6 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - mlly@1.8.2: - resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} - moo@0.5.3: resolution: {integrity: sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==} @@ -3405,6 +3808,9 @@ packages: muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + mute-stream@0.0.5: + resolution: {integrity: sha512-EbrziT4s8cWPmzr47eYVW3wimS4HsvlnV5ri1xw1aR6JQo/OrJX5rkl32K/QQHdxeabJETtfeaROGhd8W7uBgg==} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -3446,13 +3852,9 @@ packages: encoding: optional: true - node-releases@2.0.38: - resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} - - nolyfill@1.0.44: - resolution: {integrity: sha512-PoggwVLiJUn0MnodpftsiC7EuknW5+6v62ntTOQ6T6l7g2r6aoaOwgk0tQW2BxGLYw9bF298LL8jDFTmEFuzlA==} - engines: {node: '>=12.4.0'} - hasBin: true + node-releases@2.0.46: + resolution: {integrity: sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==} + engines: {node: '>=18'} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} @@ -3461,9 +3863,16 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + number-is-nan@1.0.1: + resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==} + engines: {node: '>=0.10.0'} + nwsapi@2.2.23: resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} + oauth-sign@0.9.0: + resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -3472,9 +3881,44 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@1.1.0: + resolution: {integrity: sha512-GZ+g4jayMqzCRMgB2sol7GiCLjKfS1PINkjmx8spcKce1LiVqcbQreXwqs2YAFXC6R03VIG28ZS31t8M866v6A==} + engines: {node: '>=0.10.0'} + online-3d-viewer@0.18.0: resolution: {integrity: sha512-y7ZlV/zkakNUyjqcXz6XecA7vXgLEUnaAey9tyx8o6/wcdV64RfjXAQOjGXGY2JOZoDi4Cg1ic9icSWMWAvRQA==} @@ -3482,6 +3926,14 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + os-homedir@1.0.2: + resolution: {integrity: sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==} + engines: {node: '>=0.10.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -3517,6 +3969,10 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -3533,6 +3989,9 @@ packages: perfect-debounce@2.1.0: resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} + performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -3552,9 +4011,6 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - playwright-core@1.60.0: resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} engines: {node: '>=18'} @@ -3575,6 +4031,10 @@ packages: points-on-path@0.2.1: resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + postcss-html@1.8.1: resolution: {integrity: sha512-OLF6P7qctfAWayOhLpcVnTGqVeJzu2W3WpIYelfz2+JV5oGxfkcEvweN9U4XpeqE0P98dcD9ssusGwlF0TK0uQ==} engines: {node: ^12 || >=14} @@ -3670,10 +4130,14 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qified@0.9.1: - resolution: {integrity: sha512-n7mar4T0xQ+39dE2vGTAlbxUEpndwPANH0kDef1/MYsB8Bba9wshkybIRx74qgcvKQPEWErf9AqAdYjhzY2Ilg==} + qified@0.10.1: + resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==} engines: {node: '>=20'} + qs@6.5.5: + resolution: {integrity: sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==} + engines: {node: '>=0.6'} + querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} @@ -3690,10 +4154,20 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readline2@1.0.1: + resolution: {integrity: sha512-8/td4MmwUB6PkZUbV25uKz7dfrmjYWxsW8DVfibWdlHRk/l/DfHKn4pU+dfcoGLFgWOdyGCzINRQD7jn+Bv+/g==} + refa@0.12.1: resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regenerator-runtime@0.9.6: + resolution: {integrity: sha512-D0Y/JJ4VhusyMOd/o25a3jdUqN/bC85EFsaoL9Oqmy/O4efCh+xhp7yj2EEOsj974qvMkcW8AwUzJ1jB/MbxCw==} + regexp-ast-analysis@0.7.1: resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -3702,6 +4176,10 @@ packages: resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} hasBin: true + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + regjsparser@0.13.1: resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==} hasBin: true @@ -3710,6 +4188,16 @@ packages: resolution: {integrity: sha512-U7XpAktpbSgHTRSNRrjKSrjYkZKuhUukfoBlXWXUExCAqhzh1TU3BDRAfJmarcl5voKS+pbKU9MvyLWKZ4UEEg==} engines: {node: '>= 0.8.0'} + request-promise@3.0.0: + resolution: {integrity: sha512-wVGUX+BoKxYsavTA72i6qHcyLbjzM4LR4y/AmDCqlbuMAursZdDWO7PmgbGAUvD2SeEJ5iB99VSq/U51i/DNbw==} + engines: {node: '>=0.10.0'} + deprecated: request-promise has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142 + + request@2.88.2: + resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} + engines: {node: '>= 6'} + deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -3729,31 +4217,43 @@ packages: engines: {node: '>= 0.4'} hasBin: true - resolve@2.0.0-next.6: - resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==} + resolve@2.0.0-next.7: + resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==} engines: {node: '>= 0.4'} hasBin: true + restore-cursor@1.0.1: + resolution: {integrity: sha512-reSjH4HuiFlxlaBaFCiS6O76ZGG2ygKoSlCsipKdaZuKSPx/+bt9mULkn4l0asVzbEfQQmXRg6Wp6gv6m0wElw==} + engines: {node: '>=0.10.0'} + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} - rolldown-license-plugin@3.0.7: - resolution: {integrity: sha512-jBgDoLE/UWPB1VWzERbpWGuHA0+YBDflFHX889Mayg4Z8Re27kFh7GKbuaEt4f3VK1xpJ4PGVcUoT3V2+fAybw==} + rolldown-license-plugin@3.0.8: + resolution: {integrity: sha512-q4nvGtimBIxValBXzkri+0jP2Sdf6PLztYykn7vCu0nVkJEzGsVRfcH/1X6qm0S8//4/gFt/XxwJqpaM6uSdJA==} peerDependencies: rolldown: '*' - rolldown@1.0.1: - resolution: {integrity: sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==} + rolldown@1.0.2: + resolution: {integrity: sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true roughjs@4.6.6: resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + run-async@0.1.0: + resolution: {integrity: sha512-qOX+w+IxFgpUpJfkv2oGN0+ExPs68F4sZHfaRRx4dDexAQkG83atugKVEylyT5ARees3HBbfmuvnjbrd8j9Wjw==} + run-con@1.3.2: resolution: {integrity: sha512-CcfE+mYiTcKEzg0IqS08+efdnH0oJ3zV0wSUFBNrMHMuxCtXvBCLzCJHatwuXDcu/RlhjTziTo/a1ruQik6/Yg==} hasBin: true @@ -3764,6 +4264,27 @@ packages: rw@1.3.3: resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + rx-lite@3.1.2: + resolution: {integrity: sha512-1I1+G2gteLB8Tkt8YI1sJvSIfa0lWuRtC8GjvtyPBcLSF5jBCCJJqKrpER5JU5r6Bhe+i9/pK3VMuUcXu0kdwQ==} + + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} + engines: {node: '>=0.4'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sax@1.6.0: resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} engines: {node: '>=11.0.0'} @@ -3780,21 +4301,33 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.8.0: - resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} engines: {node: '>=10'} hasBin: true - seroval-plugins@1.5.3: - resolution: {integrity: sha512-LhVh4KjjkKmCxOUjoaUwtqbDjyMfnA535yEmmGDuwZcIYtw8ns6tZmeszNTECeUg/3sJpnEjsz/KhQrcPXPw1Q==} + seroval-plugins@1.5.4: + resolution: {integrity: sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==} engines: {node: '>=10'} peerDependencies: seroval: ^1.0 - seroval@1.5.3: - resolution: {integrity: sha512-BXe0x4buEeYiIKaRUnth1WqCILQ3k4O67KP/B4pC3pVz0Mv2c96ngA9QDREUYxWY1sb2RZVRqwI9RcpVMyHCVw==} + seroval@1.5.4: + resolution: {integrity: sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==} engines: {node: '>=10'} + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -3803,6 +4336,22 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -3826,8 +4375,8 @@ packages: resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==} engines: {node: '>= 18'} - solid-js@1.9.12: - resolution: {integrity: sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw==} + solid-js@1.9.13: + resolution: {integrity: sha512-6hJeJMOcEX8ktqjpDoJZEmld3ijvcvWBDtiXBm7f4332SiFN66QeAQI1REQshvyUoISsSeJ4PHDauKYbwao9JQ==} solid-transition-group@0.2.3: resolution: {integrity: sha512-iB72c9N5Kz9ykRqIXl0lQohOau4t0dhel9kjwFvx81UZJbVwaChMuBuyhiZmK24b8aKEK0w3uFM96ZxzcyZGdg==} @@ -3851,6 +4400,11 @@ packages: engines: {node: '>=20'} hasBin: true + sshpk@1.18.0: + resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} + engines: {node: '>=0.10.0'} + hasBin: true + stable-hash-x@0.2.0: resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} engines: {node: '>=12.0.0'} @@ -3865,6 +4419,14 @@ packages: std-env@4.1.0: resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + string-width@1.0.2: + resolution: {integrity: sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==} + engines: {node: '>=0.10.0'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -3877,6 +4439,26 @@ packages: resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} engines: {node: '>=20'} + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + strip-ansi@3.0.1: + resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} + engines: {node: '>=0.10.0'} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -3944,6 +4526,10 @@ packages: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} + supports-color@2.0.0: + resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} + engines: {node: '>=0.8.0'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -3980,8 +4566,8 @@ packages: resolution: {integrity: sha512-esiWJ7ixSKGpd9DJPBTC4ckChqdOjIwJfYhVHkcQ2Gnm41323p1TRmEI+esTQ9ppD+b5opps2OTEGTCGX5kF+g==} engines: {node: '>=14'} - synckit@0.11.12: - resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} + synckit@0.11.13: + resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==} engines: {node: ^14.18.0 || >=16.0.0} table@6.9.0: @@ -4007,15 +4593,18 @@ packages: resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} engines: {node: '>=12.22'} + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyexec@1.1.2: - resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} engines: {node: '>=18'} - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} tinyrainbow@3.1.0: @@ -4032,6 +4621,10 @@ packages: toastify-js@1.12.0: resolution: {integrity: sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==} + tough-cookie@2.5.0: + resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} + engines: {node: '>=0.8'} + tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} @@ -4065,6 +4658,12 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + tweetnacl@0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -4073,8 +4672,24 @@ packages: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} - typescript-eslint@8.59.4: - resolution: {integrity: sha512-Rw6+44QNFaXtgHSjPy+Kw8hrJniMYzR85E9yLmOLcfZ91/rz+JXQbDTCmc6ccxMPY6K6PgAq26f0JCBfR7LIPQ==} + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.8: + resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} + engines: {node: '>= 0.4'} + + typescript-eslint@8.60.0: + resolution: {integrity: sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -4090,18 +4705,19 @@ packages: engines: {node: '>=14.17'} hasBin: true - typo-js@1.3.1: - resolution: {integrity: sha512-elJkpCL6Z77Ghw0Lv0lGnhBAjSTOQ5FhiVOCfOuxhaoTT2xtLVbqikYItK5HHchzPbHEUFAcjOH669T2ZzeCbg==} + typo-js@1.3.2: + resolution: {integrity: sha512-Z1YkJ7IIYNrFeOxAlHUercY4Q2I+PhYD/3VkWpJGy/Oqudy3bFpNcQxnv6Oa9fTSXCHPGz1eDoX1bZYm2Z891A==} uc.micro@2.1.0: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} - ufo@1.6.4: - resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} - uint8-to-base64@0.2.1: resolution: {integrity: sha512-uO/84GaoDUfiAxpa8EksjVLE77A9Kc7ZTziN4zRpq4de9yLaLcZn3jx1/sVjyupsywcVX6RKWbqLe7gUNyzH+Q==} + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + undici-types@7.24.6: resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} @@ -4113,8 +4729,12 @@ packages: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'} - unrs-resolver@1.11.1: - resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + unrs-resolver@1.12.2: + resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==} + + untildify@3.0.3: + resolution: {integrity: sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==} + engines: {node: '>=4'} update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} @@ -4122,8 +4742,8 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - updates@17.16.13: - resolution: {integrity: sha512-Od8Ea50dJZWp3dKnibQSQ08Wp1Z3qhn3i0zMBOB47qPltSlGjZFVDR+XjfSEzQIE/Grp2z8OQ/G4YuNiH3X8xA==} + updates@17.17.2: + resolution: {integrity: sha512-gOwGrBYBvHVS+OiaUFRuilrmf/P8eYVmKUrkq7W3fvYUgpBsNKgfZo/CPhLKBa7xEFiEuMwMxTkiKsd9mn8pEw==} engines: {node: '>=22'} hasBin: true @@ -4133,23 +4753,36 @@ packages: url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + user-home@2.0.0: + resolution: {integrity: sha512-KMWqdlOcjCYdtIJpicDSFBQ8nFwS2i9sslAd6f4+CBGcU4gist2REnr2fxj2YocvJFxSF3ZOHLYLVZnUxv4BZQ==} + engines: {node: '>=0.10.0'} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuid@11.1.1: - resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} + uuid@14.0.0: + resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} + hasBin: true + + uuid@3.4.0: + resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true vanilla-colorful@0.7.2: resolution: {integrity: sha512-z2YZusTFC6KnLERx1cgoIRX2CjPRP0W75N+3CC6gbvdX5Ch47rZkEMGO2Xnf+IEmi3RiFLxS18gayMA27iU7Kg==} + verror@1.10.0: + resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} + engines: {'0': node >=0.6.0} + vite-string-plugin@2.0.4: resolution: {integrity: sha512-uahQl15I6hsHGzbjCmllVoKZBmZavXAp8GXBmq5omNQM52wgBv7e//98A6cONbq0Of6F/5uJ30OjF5ggNY6reQ==} peerDependencies: vite: '*' - vite@8.0.13: - resolution: {integrity: sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==} + vite@8.0.14: + resolution: {integrity: sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -4250,14 +4883,14 @@ packages: peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - vue-tsc@3.3.1: - resolution: {integrity: sha512-webBP3jhlxzhELZ2g+11KJ6pg5OVY1xWhWrj7N/yQMi1CrtxJnW+tUACyRVeDK0cQNLP2Va5HNYK8pe+7c+msw==} + vue-tsc@3.3.2: + resolution: {integrity: sha512-n7nQoA3YWW/eiDR8jMiv/uJvlg0uLGs+YgUrsTrf9EZaYSt3tuvMZb5V8+7Mvh/EH5pnY/hoVdgfjH+XcK+wwA==} hasBin: true peerDependencies: typescript: '>=5.0.0' - vue@3.5.34: - resolution: {integrity: sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==} + vue@3.5.35: + resolution: {integrity: sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==} peerDependencies: typescript: '*' peerDependenciesMeta: @@ -4294,6 +4927,22 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.21: + resolution: {integrity: sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==} + engines: {node: '>= 0.4'} + which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true @@ -4312,12 +4961,15 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + write-file-atomic@7.0.1: resolution: {integrity: sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==} engines: {node: ^20.17.0 || >=22.9.0} - ws@8.20.0: - resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -4352,32 +5004,32 @@ snapshots: '@antfu/install-pkg@1.1.0': dependencies: package-manager-detector: 1.6.0 - tinyexec: 1.1.2 + tinyexec: 1.2.4 - '@babel/code-frame@7.29.0': + '@babel/code-frame@7.29.7': dependencies: - '@babel/helper-validator-identifier': 7.28.5 + '@babel/helper-validator-identifier': 7.29.7 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@7.29.7': {} - '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} - '@babel/parser@7.29.3': + '@babel/parser@7.29.7': dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 - '@babel/runtime@7.29.2': {} + '@babel/runtime@7.29.7': {} - '@babel/types@7.29.0': + '@babel/types@7.29.7': dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 '@braintree/sanitize-url@7.1.2': {} - '@cacheable/memory@2.0.8': + '@cacheable/memory@2.0.9': dependencies: '@cacheable/utils': 2.4.1 '@keyv/bigmap': 1.3.1(keyv@5.6.0) @@ -4475,7 +5127,7 @@ snapshots: '@codemirror/lang-cpp@6.0.3': dependencies: '@codemirror/language': 6.12.3 - '@lezer/cpp': 1.1.5 + '@lezer/cpp': 1.1.6 '@codemirror/lang-css@6.3.1': dependencies: @@ -4563,7 +5215,7 @@ snapshots: '@codemirror/state': 6.6.0 '@codemirror/view': 6.43.0 '@lezer/common': 1.5.2 - '@lezer/markdown': 1.6.3 + '@lezer/markdown': 1.6.4 '@codemirror/lang-php@6.0.2': dependencies: @@ -4579,7 +5231,7 @@ snapshots: '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@lezer/common': 1.5.2 - '@lezer/python': 1.1.18 + '@lezer/python': 1.1.19 '@codemirror/lang-rust@6.0.2': dependencies: @@ -4700,7 +5352,7 @@ snapshots: style-mod: 4.1.3 w3c-keyname: 2.2.8 - '@csstools/css-calc@3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 @@ -4709,7 +5361,7 @@ snapshots: dependencies: '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.1.3(css-tree@3.2.1)': + '@csstools/css-syntax-patches-for-csstree@1.1.4(css-tree@3.2.1)': optionalDependencies: css-tree: 3.2.1 @@ -4730,7 +5382,7 @@ snapshots: '@deltablot/dropzone@7.4.3': dependencies: - '@swc/helpers': 0.5.21 + '@swc/helpers': 0.5.23 '@emnapi/core@1.10.0': dependencies: @@ -4826,7 +5478,7 @@ snapshots: '@esbuild/win32-x64@0.28.0': optional: true - '@eslint-community/eslint-plugin-eslint-comments@4.7.1(eslint@10.4.0(jiti@2.7.0))': + '@eslint-community/eslint-plugin-eslint-comments@4.7.2(eslint@10.4.0(jiti@2.7.0))': dependencies: escape-string-regexp: 4.0.0 eslint: 10.4.0(jiti@2.7.0) @@ -4895,7 +5547,7 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 - '@eslint/plugin-kit@0.7.1': + '@eslint/plugin-kit@0.7.2': dependencies: '@eslint/core': 1.2.1 levn: 0.4.1 @@ -4933,11 +5585,11 @@ snapshots: '@iconify/types@2.0.0': {} - '@iconify/utils@3.1.1': + '@iconify/utils@3.1.3': dependencies: '@antfu/install-pkg': 1.1.0 '@iconify/types': 2.0.0 - mlly: 1.8.2 + import-meta-resolve: 4.2.0 '@jest/environment@29.7.0': dependencies: @@ -4994,7 +5646,7 @@ snapshots: '@lezer/common@1.5.2': {} - '@lezer/cpp@1.1.5': + '@lezer/cpp@1.1.6': dependencies: '@lezer/common': 1.5.2 '@lezer/highlight': 1.2.3 @@ -5044,7 +5696,7 @@ snapshots: dependencies: '@lezer/common': 1.5.2 - '@lezer/markdown@1.6.3': + '@lezer/markdown@1.6.4': dependencies: '@lezer/common': 1.5.2 '@lezer/highlight': 1.2.3 @@ -5055,7 +5707,7 @@ snapshots: '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.10 - '@lezer/python@1.1.18': + '@lezer/python@1.1.19': dependencies: '@lezer/common': 1.5.2 '@lezer/highlight': 1.2.3 @@ -5109,13 +5761,6 @@ snapshots: dependencies: '@chevrotain/types': 11.1.2 - '@napi-rs/wasm-runtime@0.2.12': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.2 - optional: true - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 @@ -5135,73 +5780,11 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@nolyfill/abab@1.0.44': {} - - '@nolyfill/array-includes@1.0.44': - dependencies: - '@nolyfill/shared': 1.0.44 - - '@nolyfill/array.prototype.findlastindex@1.0.44': - dependencies: - '@nolyfill/shared': 1.0.44 - - '@nolyfill/array.prototype.flat@1.0.44': - dependencies: - '@nolyfill/shared': 1.0.44 - - '@nolyfill/array.prototype.flatmap@1.0.44': - dependencies: - '@nolyfill/shared': 1.0.44 - - '@nolyfill/es-set-tostringtag@1.0.44': {} - - '@nolyfill/hasown@1.0.44': {} - - '@nolyfill/is-core-module@1.0.39': {} - - '@nolyfill/object-keys@1.0.44': - dependencies: - '@nolyfill/shared': 1.0.44 - - '@nolyfill/object.assign@1.0.44': - dependencies: - '@nolyfill/shared': 1.0.44 - - '@nolyfill/object.entries@1.0.44': - dependencies: - '@nolyfill/shared': 1.0.44 - - '@nolyfill/object.fromentries@1.0.44': - dependencies: - '@nolyfill/shared': 1.0.44 - - '@nolyfill/object.groupby@1.0.44': - dependencies: - '@nolyfill/shared': 1.0.44 - - '@nolyfill/object.values@1.0.44': - dependencies: - '@nolyfill/shared': 1.0.44 - - '@nolyfill/safe-regex-test@1.0.44': {} - - '@nolyfill/safer-buffer@1.0.44': {} - - '@nolyfill/shared@1.0.44': {} - - '@nolyfill/string.prototype.includes@1.0.44': - dependencies: - '@nolyfill/shared': 1.0.44 - - '@nolyfill/string.prototype.trimend@1.0.44': - dependencies: - '@nolyfill/shared': 1.0.44 - - '@oxc-project/types@0.130.0': {} + '@oxc-project/types@0.132.0': {} '@package-json/types@0.0.12': {} - '@pkgr/core@0.2.9': {} + '@pkgr/core@0.3.6': {} '@playwright/test@1.60.0': dependencies: @@ -5209,7 +5792,7 @@ snapshots: '@popperjs/core@2.11.8': {} - '@primer/octicons@19.26.0': + '@primer/octicons@19.27.0': dependencies: object-assign: 4.1.1 @@ -5255,53 +5838,53 @@ snapshots: '@resvg/resvg-wasm@2.6.2': {} - '@rolldown/binding-android-arm64@1.0.1': + '@rolldown/binding-android-arm64@1.0.2': optional: true - '@rolldown/binding-darwin-arm64@1.0.1': + '@rolldown/binding-darwin-arm64@1.0.2': optional: true - '@rolldown/binding-darwin-x64@1.0.1': + '@rolldown/binding-darwin-x64@1.0.2': optional: true - '@rolldown/binding-freebsd-x64@1.0.1': + '@rolldown/binding-freebsd-x64@1.0.2': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.1': + '@rolldown/binding-linux-arm-gnueabihf@1.0.2': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.1': + '@rolldown/binding-linux-arm64-gnu@1.0.2': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.1': + '@rolldown/binding-linux-arm64-musl@1.0.2': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.1': + '@rolldown/binding-linux-ppc64-gnu@1.0.2': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.1': + '@rolldown/binding-linux-s390x-gnu@1.0.2': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.1': + '@rolldown/binding-linux-x64-gnu@1.0.2': optional: true - '@rolldown/binding-linux-x64-musl@1.0.1': + '@rolldown/binding-linux-x64-musl@1.0.2': optional: true - '@rolldown/binding-openharmony-arm64@1.0.1': + '@rolldown/binding-openharmony-arm64@1.0.2': optional: true - '@rolldown/binding-wasm32-wasi@1.0.1': + '@rolldown/binding-wasm32-wasi@1.0.2': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.1': + '@rolldown/binding-win32-arm64-msvc@1.0.2': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.1': + '@rolldown/binding-win32-x64-msvc@1.0.2': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -5327,25 +5910,25 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@solid-primitives/refs@1.1.3(solid-js@1.9.12)': + '@solid-primitives/refs@1.1.3(solid-js@1.9.13)': dependencies: - '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) - solid-js: 1.9.12 + '@solid-primitives/utils': 6.4.0(solid-js@1.9.13) + solid-js: 1.9.13 - '@solid-primitives/transition-group@1.1.2(solid-js@1.9.12)': + '@solid-primitives/transition-group@1.1.2(solid-js@1.9.13)': dependencies: - solid-js: 1.9.12 + solid-js: 1.9.13 - '@solid-primitives/utils@6.4.0(solid-js@1.9.12)': + '@solid-primitives/utils@6.4.0(solid-js@1.9.13)': dependencies: - solid-js: 1.9.12 + solid-js: 1.9.13 '@standard-schema/spec@1.1.0': {} '@stylistic/eslint-plugin@5.10.0(eslint@10.4.0(jiti@2.7.0))': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) - '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/types': 8.60.1 eslint: 10.4.0(jiti@2.7.0) eslint-visitor-keys: 4.2.1 espree: 10.4.0 @@ -5363,7 +5946,7 @@ snapshots: style-search: 0.1.0 stylelint: 17.12.0(typescript@6.0.3) - '@swc/helpers@0.5.21': + '@swc/helpers@0.5.23': dependencies: tslib: 2.8.1 @@ -5508,7 +6091,7 @@ snapshots: '@types/esrecurse@4.3.1': {} - '@types/estree@1.0.8': {} + '@types/estree@1.0.9': {} '@types/geojson@7946.0.16': {} @@ -5558,7 +6141,7 @@ snapshots: '@types/tern@0.23.9': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/throttle-debounce@5.0.2': {} @@ -5583,14 +6166,14 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.59.4(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.60.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.59.4 - '@typescript-eslint/type-utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.59.4 + '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.60.0 + '@typescript-eslint/type-utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.60.0 eslint: 10.4.0(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 @@ -5599,14 +6182,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.59.4(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.60.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.59.4 - '@typescript-eslint/type-utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.59.4 + '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.60.0 + '@typescript-eslint/type-utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.60.0 eslint: 10.4.0(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 @@ -5615,66 +6198,130 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.59.4 - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.59.4 + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/type-utils': 8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.60.1 + eslint: 10.4.0(jiti@2.7.0) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/type-utils': 8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.60.1 + eslint: 10.4.0(jiti@2.7.0) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + optional: true + + '@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.60.0 + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.60.0 debug: 4.4.3 eslint: 10.4.0(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.59.4 - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/typescript-estree': 8.59.4(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.59.4 + '@typescript-eslint/scope-manager': 8.60.0 + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/typescript-estree': 8.60.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.60.0 debug: 4.4.3 eslint: 10.4.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.59.4(typescript@5.9.3)': + '@typescript-eslint/project-service@8.60.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@5.9.3) - '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@5.9.3) + '@typescript-eslint/types': 8.60.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.59.4(typescript@6.0.3)': + '@typescript-eslint/project-service@8.60.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@6.0.3) - '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@6.0.3) + '@typescript-eslint/types': 8.60.0 debug: 4.4.3 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.59.4': + '@typescript-eslint/project-service@8.60.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/visitor-keys': 8.59.4 + '@typescript-eslint/tsconfig-utils': 8.60.1(typescript@5.9.3) + '@typescript-eslint/types': 8.60.1 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color - '@typescript-eslint/tsconfig-utils@8.59.4(typescript@5.9.3)': + '@typescript-eslint/project-service@8.60.1(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.60.1(typescript@6.0.3) + '@typescript-eslint/types': 8.60.1 + debug: 4.4.3 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.60.0': + dependencies: + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/visitor-keys': 8.60.0 + + '@typescript-eslint/scope-manager@8.60.1': + dependencies: + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/visitor-keys': 8.60.1 + + '@typescript-eslint/tsconfig-utils@8.60.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/tsconfig-utils@8.59.4(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.60.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.60.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.9.3) - '@typescript-eslint/utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + typescript: 5.9.3 + + '@typescript-eslint/tsconfig-utils@8.60.1(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + + '@typescript-eslint/type-utils@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) debug: 4.4.3 eslint: 10.4.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@5.9.3) @@ -5682,11 +6329,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/typescript-estree': 8.59.4(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/typescript-estree': 8.60.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) debug: 4.4.3 eslint: 10.4.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@6.0.3) @@ -5694,122 +6341,217 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.59.4': {} - - '@typescript-eslint/typescript-estree@8.59.4(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.59.4(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@5.9.3) - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/visitor-keys': 8.59.4 + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/typescript-estree': 8.60.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) debug: 4.4.3 - minimatch: 10.2.5 - semver: 7.8.0 - tinyglobby: 0.2.16 + eslint: 10.4.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.59.4(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.59.4(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@6.0.3) - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/visitor-keys': 8.59.4 + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/typescript-estree': 8.60.1(typescript@6.0.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + debug: 4.4.3 + eslint: 10.4.0(jiti@2.7.0) + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + optional: true + + '@typescript-eslint/types@8.60.0': {} + + '@typescript-eslint/types@8.60.1': {} + + '@typescript-eslint/typescript-estree@8.60.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.60.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@5.9.3) + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/visitor-keys': 8.60.0 debug: 4.4.3 minimatch: 10.2.5 - semver: 7.8.0 - tinyglobby: 0.2.16 + semver: 7.8.1 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@8.60.0(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.60.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@6.0.3) + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/visitor-keys': 8.60.0 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.1 + tinyglobby: 0.2.17 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.60.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.60.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.60.1(typescript@5.9.3) + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/visitor-keys': 8.60.1 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.1 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@8.60.1(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.60.1(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.60.1(typescript@6.0.3) + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/visitor-keys': 8.60.1 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.1 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) - '@typescript-eslint/scope-manager': 8.59.4 - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.60.0 + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) eslint: 10.4.0(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/utils@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) - '@typescript-eslint/scope-manager': 8.59.4 - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/typescript-estree': 8.59.4(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.60.0 + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/typescript-estree': 8.60.0(typescript@6.0.3) eslint: 10.4.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.59.4': + '@typescript-eslint/utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.59.4 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/typescript-estree': 8.60.1(typescript@5.9.3) + eslint: 10.4.0(jiti@2.7.0) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/typescript-estree': 8.60.1(typescript@6.0.3) + eslint: 10.4.0(jiti@2.7.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.60.0': + dependencies: + '@typescript-eslint/types': 8.60.0 eslint-visitor-keys: 5.0.1 - '@unrs/resolver-binding-android-arm-eabi@1.11.1': - optional: true - - '@unrs/resolver-binding-android-arm64@1.11.1': - optional: true - - '@unrs/resolver-binding-darwin-arm64@1.11.1': - optional: true - - '@unrs/resolver-binding-darwin-x64@1.11.1': - optional: true - - '@unrs/resolver-binding-freebsd-x64@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-arm64-musl@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-x64-gnu@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-x64-musl@1.11.1': - optional: true - - '@unrs/resolver-binding-wasm32-wasi@1.11.1': + '@typescript-eslint/visitor-keys@8.60.1': dependencies: - '@napi-rs/wasm-runtime': 0.2.12 + '@typescript-eslint/types': 8.60.1 + eslint-visitor-keys: 5.0.1 + + '@unrs/resolver-binding-android-arm-eabi@1.12.2': optional: true - '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + '@unrs/resolver-binding-android-arm64@1.12.2': optional: true - '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + '@unrs/resolver-binding-darwin-arm64@1.12.2': optional: true - '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + '@unrs/resolver-binding-darwin-x64@1.12.2': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true '@upsetjs/venn.js@2.0.0': @@ -5817,21 +6559,21 @@ snapshots: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) - '@vitejs/plugin-vue@6.0.7(vite@8.0.13(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))(vue@3.5.34(typescript@6.0.3))': + '@vitejs/plugin-vue@6.0.7(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))(vue@3.5.35(typescript@6.0.3))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.0.13(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) - vue: 3.5.34(typescript@6.0.3) + vite: 8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) + vue: 3.5.35(typescript@6.0.3) - '@vitest/eslint-plugin@1.6.17(@typescript-eslint/eslint-plugin@8.59.4(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.7(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.13(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)))': + '@vitest/eslint-plugin@1.6.18(@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.7(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)))': dependencies: - '@typescript-eslint/scope-manager': 8.59.4 - '@typescript-eslint/utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/utils': 8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) eslint: 10.4.0(jiti@2.7.0) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.59.4(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.60.1(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) typescript: 6.0.3 - vitest: 4.1.7(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.13(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)) + vitest: 4.1.7(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)) transitivePeerDependencies: - supports-color @@ -5844,13 +6586,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.7(vite@8.0.13(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))': + '@vitest/mocker@4.1.7(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))': dependencies: '@vitest/spy': 4.1.7 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.13(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) + vite: 8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) '@vitest/pretty-format@4.1.7': dependencies: @@ -5888,69 +6630,71 @@ snapshots: path-browserify: 1.0.1 vscode-uri: 3.1.0 - '@vue/compiler-core@3.5.34': + '@vue/compiler-core@3.5.35': dependencies: - '@babel/parser': 7.29.3 - '@vue/shared': 3.5.34 + '@babel/parser': 7.29.7 + '@vue/shared': 3.5.35 entities: 7.0.1 estree-walker: 2.0.2 source-map-js: 1.2.1 - '@vue/compiler-dom@3.5.34': + '@vue/compiler-dom@3.5.35': dependencies: - '@vue/compiler-core': 3.5.34 - '@vue/shared': 3.5.34 + '@vue/compiler-core': 3.5.35 + '@vue/shared': 3.5.35 - '@vue/compiler-sfc@3.5.34': + '@vue/compiler-sfc@3.5.35': dependencies: - '@babel/parser': 7.29.3 - '@vue/compiler-core': 3.5.34 - '@vue/compiler-dom': 3.5.34 - '@vue/compiler-ssr': 3.5.34 - '@vue/shared': 3.5.34 + '@babel/parser': 7.29.7 + '@vue/compiler-core': 3.5.35 + '@vue/compiler-dom': 3.5.35 + '@vue/compiler-ssr': 3.5.35 + '@vue/shared': 3.5.35 estree-walker: 2.0.2 magic-string: 0.30.21 postcss: 8.5.15 source-map-js: 1.2.1 - '@vue/compiler-ssr@3.5.34': + '@vue/compiler-ssr@3.5.35': dependencies: - '@vue/compiler-dom': 3.5.34 - '@vue/shared': 3.5.34 + '@vue/compiler-dom': 3.5.35 + '@vue/shared': 3.5.35 - '@vue/language-core@3.3.1': + '@vue/language-core@3.3.2': dependencies: '@volar/language-core': 2.4.28 - '@vue/compiler-dom': 3.5.34 - '@vue/shared': 3.5.34 + '@vue/compiler-dom': 3.5.35 + '@vue/shared': 3.5.35 alien-signals: 3.2.1 muggle-string: 0.4.1 path-browserify: 1.0.1 picomatch: 4.0.4 - '@vue/reactivity@3.5.34': + '@vue/reactivity@3.5.35': dependencies: - '@vue/shared': 3.5.34 + '@vue/shared': 3.5.35 - '@vue/runtime-core@3.5.34': + '@vue/runtime-core@3.5.35': dependencies: - '@vue/reactivity': 3.5.34 - '@vue/shared': 3.5.34 + '@vue/reactivity': 3.5.35 + '@vue/shared': 3.5.35 - '@vue/runtime-dom@3.5.34': + '@vue/runtime-dom@3.5.35': dependencies: - '@vue/reactivity': 3.5.34 - '@vue/runtime-core': 3.5.34 - '@vue/shared': 3.5.34 + '@vue/reactivity': 3.5.35 + '@vue/runtime-core': 3.5.35 + '@vue/shared': 3.5.35 csstype: 3.2.3 - '@vue/server-renderer@3.5.34(vue@3.5.34(typescript@6.0.3))': + '@vue/server-renderer@3.5.35(vue@3.5.35(typescript@6.0.3))': dependencies: - '@vue/compiler-ssr': 3.5.34 - '@vue/shared': 3.5.34 - vue: 3.5.34(typescript@6.0.3) + '@vue/compiler-ssr': 3.5.35 + '@vue/shared': 3.5.35 + vue: 3.5.35(typescript@6.0.3) - '@vue/shared@3.5.34': {} + '@vue/shared@3.5.35': {} + + abab@2.0.6: {} acorn-globals@7.0.1: dependencies: @@ -5983,16 +6727,22 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 + fast-uri: 3.1.2 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 alien-signals@3.2.1: {} + ansi-escapes@1.4.0: {} + + ansi-regex@2.1.1: {} + ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} + ansi-styles@2.2.1: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -6014,11 +6764,67 @@ snapshots: aria-query@5.3.2: {} + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.findlastindex@1.2.6: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + asciinema-player@3.15.1: dependencies: - '@babel/runtime': 7.29.2 - solid-js: 1.9.12 - solid-transition-group: 0.2.3(solid-js@1.9.12) + '@babel/runtime': 7.29.7 + solid-js: 1.9.13 + solid-transition-group: 0.2.3(solid-js@1.9.13) + + asn1@0.2.6: + dependencies: + safer-buffer: 2.1.2 + + assert-plus@1.0.0: {} assertion-error@2.0.1: {} @@ -6026,9 +6832,19 @@ snapshots: astral-regex@2.0.0: {} + async-function@1.0.0: {} + asynckit@0.4.0: {} - axe-core@4.11.4: {} + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + aws-sign2@0.7.0: {} + + aws4@1.13.2: {} + + axe-core@4.12.0: {} axobject-query@4.1.0: {} @@ -6038,18 +6854,36 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.10.27: {} + baseline-browser-mapping@2.10.33: {} + + bcrypt-pbkdf@1.0.2: + dependencies: + tweetnacl: 0.14.5 binary-extensions@2.3.0: {} + biome@0.3.3: + dependencies: + bluebird: 3.7.2 + chalk: 1.1.3 + commander: 2.20.3 + editor: 1.0.0 + fs-promise: 0.5.0 + inquirer-promise: 0.0.3 + request-promise: 3.0.0 + untildify: 3.0.3 + user-home: 2.0.0 + + bluebird@3.7.2: {} + boolbase@1.0.0: {} - brace-expansion@1.1.14: + brace-expansion@1.1.15: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@5.0.5: + brace-expansion@5.0.6: dependencies: balanced-match: 4.0.4 @@ -6059,10 +6893,10 @@ snapshots: browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.10.27 - caniuse-lite: 1.0.30001791 - electron-to-chromium: 1.5.349 - node-releases: 2.0.38 + baseline-browser-mapping: 2.10.33 + caniuse-lite: 1.0.30001793 + electron-to-chromium: 1.5.364 + node-releases: 2.0.46 update-browserslist-db: 1.2.3(browserslist@4.28.2) buffer@5.7.1: @@ -6072,26 +6906,53 @@ snapshots: builtin-modules@3.3.0: {} - builtin-modules@5.1.0: {} + builtin-modules@5.2.0: {} bytes@3.1.2: {} - cacheable@2.3.4: + cacheable@2.3.5: dependencies: - '@cacheable/memory': 2.0.8 + '@cacheable/memory': 2.0.9 '@cacheable/utils': 2.4.1 hookified: 1.15.1 keyv: 5.6.0 - qified: 0.9.1 + qified: 0.10.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 callsites@3.1.0: {} camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001791: {} + caniuse-lite@1.0.30001793: {} + + caseless@0.12.0: {} chai@6.2.2: {} + chalk@1.1.3: + dependencies: + ansi-styles: 2.2.1 + escape-string-regexp: 1.0.5 + has-ansi: 2.0.0 + strip-ansi: 3.0.1 + supports-color: 2.0.0 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -6109,10 +6970,10 @@ snapshots: dependencies: '@kurkle/color': 0.3.4 - chartjs-adapter-dayjs-4@1.0.4(chart.js@4.5.1)(dayjs@1.11.20): + chartjs-adapter-dayjs-4@1.0.4(chart.js@4.5.1)(dayjs@1.11.21): dependencies: chart.js: 4.5.1 - dayjs: 1.11.20 + dayjs: 1.11.21 chartjs-plugin-zoom@2.2.0(chart.js@4.5.1): dependencies: @@ -6144,8 +7005,16 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 + cli-cursor@1.0.2: + dependencies: + restore-cursor: 1.0.1 + + cli-width@1.1.1: {} + clippie@4.2.0: {} + code-point-at@1.1.0: {} + codemirror-lang-elixir@4.0.1: dependencies: '@codemirror/language': 6.12.3 @@ -6153,7 +7022,7 @@ snapshots: codemirror-spell-checker@1.1.2: dependencies: - typo-js: 1.3.1 + typo-js: 1.3.2 codemirror@5.65.21: {} @@ -6173,28 +7042,32 @@ snapshots: commander@14.0.3: {} + commander@2.20.3: {} + commander@4.1.1: {} commander@7.2.0: {} commander@8.3.0: {} - comment-parser@1.4.6: {} + comment-parser@1.4.7: {} compare-versions@6.1.1: {} concat-map@0.0.1: {} - confbox@0.1.8: {} - convert-source-map@2.0.0: {} core-js-compat@3.49.0: dependencies: browserslist: 4.28.2 + core-js@2.6.12: {} + core-js@3.32.2: {} + core-util-is@1.0.2: {} + cose-base@1.0.3: dependencies: layout-base: 1.0.2 @@ -6260,17 +7133,17 @@ snapshots: csstype@3.2.3: {} - cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.3): + cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.4): dependencies: cose-base: 1.0.3 - cytoscape: 3.33.3 + cytoscape: 3.33.4 - cytoscape-fcose@2.2.0(cytoscape@3.33.3): + cytoscape-fcose@2.2.0(cytoscape@3.33.4): dependencies: cose-base: 2.2.0 - cytoscape: 3.33.3 + cytoscape: 3.33.4 - cytoscape@3.33.3: {} + cytoscape@3.33.4: {} d3-array@2.12.1: dependencies: @@ -6446,13 +7319,35 @@ snapshots: damerau-levenshtein@1.0.8: {} + dashdash@1.14.1: + dependencies: + assert-plus: 1.0.0 + data-urls@3.0.2: dependencies: - abab: '@nolyfill/abab@1.0.44' + abab: 2.0.6 whatwg-mimetype: 3.0.0 whatwg-url: 11.0.0 - dayjs@1.11.20: {} + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + dayjs@1.11.21: {} debug@3.2.7: dependencies: @@ -6477,6 +7372,18 @@ snapshots: kind-of: 3.2.2 rename-keys: 1.2.0 + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + delaunator@5.1.0: dependencies: robust-predicates: 3.0.3 @@ -6517,7 +7424,7 @@ snapshots: dependencies: domelementtype: 2.3.0 - dompurify@3.4.2: + dompurify@3.4.7: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -6527,6 +7434,19 @@ snapshots: domelementtype: 2.3.0 domhandler: 5.0.3 + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + earlgrey-runtime@0.1.2: + dependencies: + core-js: 2.6.12 + kaiser: 0.0.4 + lodash: 4.18.1 + regenerator-runtime: 0.9.6 + easymde@2.21.0: dependencies: '@types/codemirror': 5.60.17 @@ -6535,7 +7455,14 @@ snapshots: codemirror-spell-checker: 1.1.2 marked: 4.3.0 - electron-to-chromium@1.5.349: {} + ecc-jsbn@0.1.2: + dependencies: + jsbn: 0.1.1 + safer-buffer: 2.1.2 + + editor@1.0.0: {} + + electron-to-chromium@1.5.364: {} elkjs@0.9.3: {} @@ -6555,11 +7482,91 @@ snapshots: dependencies: is-arrayish: 0.2.1 + es-abstract@1.24.2: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.4 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.8 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.21 + + es-define-property@1.0.1: {} + es-errors@1.3.0: {} es-module-lexer@2.1.0: {} - es-toolkit@1.46.1: {} + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.4 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + es-toolkit@1.47.0: {} esbuild@0.28.0: optionalDependencies: @@ -6610,45 +7617,45 @@ snapshots: dependencies: eslint: 10.4.0(jiti@2.7.0) - eslint-import-context@0.1.9(unrs-resolver@1.11.1): + eslint-import-context@0.1.9(unrs-resolver@1.12.2): dependencies: get-tsconfig: 4.14.0 stable-hash-x: 0.2.0 optionalDependencies: - unrs-resolver: 1.11.1 + unrs-resolver: 1.12.2 eslint-import-resolver-node@0.3.10: dependencies: debug: 3.2.7 - is-core-module: '@nolyfill/is-core-module@1.0.39' - resolve: 2.0.0-next.6 + is-core-module: 2.16.2 + resolve: 2.0.0-next.7 transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.0(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.0(jiti@2.7.0)): + eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.0(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.0(jiti@2.7.0)): dependencies: debug: 4.4.3 eslint: 10.4.0(jiti@2.7.0) - eslint-import-context: 0.1.9(unrs-resolver@1.11.1) + eslint-import-context: 0.1.9(unrs-resolver@1.12.2) get-tsconfig: 4.14.0 is-bun-module: 2.0.0 stable-hash-x: 0.2.0 - tinyglobby: 0.2.16 - unrs-resolver: 1.11.1 + tinyglobby: 0.2.17 + unrs-resolver: 1.12.2 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)) - eslint-plugin-import-x: 4.16.2(@typescript-eslint/utils@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.0(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)) + eslint-plugin-import-x: 4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.0(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)): + eslint-module-utils@2.13.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) eslint: 10.4.0(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.0(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.0(jiti@2.7.0)) + eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.0(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.0(jiti@2.7.0)) transitivePeerDependencies: - supports-color @@ -6685,8 +7692,8 @@ snapshots: '@eslint/eslintrc': 3.3.5 '@eslint/js': 9.39.4 '@github/browserslist-config': 1.0.0 - '@typescript-eslint/eslint-plugin': 8.59.4(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/parser': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.60.1(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) aria-query: 5.3.2 eslint: 10.4.0(jiti@2.7.0) eslint-config-prettier: 10.1.8(eslint@10.4.0(jiti@2.7.0)) @@ -6694,17 +7701,17 @@ snapshots: eslint-plugin-eslint-comments: 3.2.0(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-filenames: 1.3.2(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-i18n-text: 1.0.1(eslint@10.4.0(jiti@2.7.0)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-jsx-a11y: 6.10.2(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-no-only-tests: 3.4.0 - eslint-plugin-prettier: 5.5.5(eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)))(eslint@10.4.0(jiti@2.7.0))(prettier@3.8.3) + eslint-plugin-prettier: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)))(eslint@10.4.0(jiti@2.7.0))(prettier@3.8.3) eslint-rule-documentation: 1.0.23 globals: 16.5.0 jsx-ast-utils: 3.3.5 prettier: 3.8.3 svg-element-attributes: 1.3.1 typescript: 5.9.3 - typescript-eslint: 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + typescript-eslint: 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) transitivePeerDependencies: - '@types/eslint' - eslint-import-resolver-typescript @@ -6715,49 +7722,49 @@ snapshots: dependencies: eslint: 10.4.0(jiti@2.7.0) - eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.0(jiti@2.7.0)): + eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.0(jiti@2.7.0)): dependencies: '@package-json/types': 0.0.12 - '@typescript-eslint/types': 8.59.4 - comment-parser: 1.4.6 + '@typescript-eslint/types': 8.60.1 + comment-parser: 1.4.7 debug: 4.4.3 eslint: 10.4.0(jiti@2.7.0) - eslint-import-context: 0.1.9(unrs-resolver@1.11.1) + eslint-import-context: 0.1.9(unrs-resolver@1.12.2) is-glob: 4.0.3 minimatch: 10.2.5 - semver: 7.8.0 + semver: 7.8.1 stable-hash-x: 0.2.0 - unrs-resolver: 1.11.1 + unrs-resolver: 1.12.2 optionalDependencies: - '@typescript-eslint/utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) eslint-import-resolver-node: 0.3.10 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)): dependencies: '@rtsao/scc': 1.1.0 - array-includes: '@nolyfill/array-includes@1.0.44' - array.prototype.findlastindex: '@nolyfill/array.prototype.findlastindex@1.0.44' - array.prototype.flat: '@nolyfill/array.prototype.flat@1.0.44' - array.prototype.flatmap: '@nolyfill/array.prototype.flatmap@1.0.44' + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 eslint: 10.4.0(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)) - hasown: '@nolyfill/hasown@1.0.44' - is-core-module: '@nolyfill/is-core-module@1.0.39' + eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)) + hasown: 2.0.4 + is-core-module: 2.16.2 is-glob: 4.0.3 minimatch: 3.1.5 - object.fromentries: '@nolyfill/object.fromentries@1.0.44' - object.groupby: '@nolyfill/object.groupby@1.0.44' - object.values: '@nolyfill/object.values@1.0.44' + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 semver: 6.3.1 - string.prototype.trimend: '@nolyfill/string.prototype.trimend@1.0.44' + string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -6766,21 +7773,21 @@ snapshots: eslint-plugin-jsx-a11y@6.10.2(eslint@10.4.0(jiti@2.7.0)): dependencies: aria-query: 5.3.2 - array-includes: '@nolyfill/array-includes@1.0.44' - array.prototype.flatmap: '@nolyfill/array.prototype.flatmap@1.0.44' + array-includes: 3.1.9 + array.prototype.flatmap: 1.3.3 ast-types-flow: 0.0.8 - axe-core: 4.11.4 + axe-core: 4.12.0 axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 eslint: 10.4.0(jiti@2.7.0) - hasown: '@nolyfill/hasown@1.0.44' + hasown: 2.0.4 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 minimatch: 3.1.5 - object.fromentries: '@nolyfill/object.fromentries@1.0.44' - safe-regex-test: '@nolyfill/safe-regex-test@1.0.44' - string.prototype.includes: '@nolyfill/string.prototype.includes@1.0.44' + object.fromentries: 2.0.8 + safe-regex-test: 1.1.0 + string.prototype.includes: 2.0.1 eslint-plugin-no-only-tests@3.4.0: {} @@ -6789,12 +7796,12 @@ snapshots: eslint: 10.4.0(jiti@2.7.0) globals: 17.6.0 - eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)))(eslint@10.4.0(jiti@2.7.0))(prettier@3.8.3): + eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)))(eslint@10.4.0(jiti@2.7.0))(prettier@3.8.3): dependencies: eslint: 10.4.0(jiti@2.7.0) prettier: 3.8.3 prettier-linter-helpers: 1.0.1 - synckit: 0.11.12 + synckit: 0.11.13 optionalDependencies: eslint-config-prettier: 10.1.8(eslint@10.4.0(jiti@2.7.0)) @@ -6802,7 +7809,7 @@ snapshots: dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 - comment-parser: 1.4.6 + comment-parser: 1.4.7 eslint: 10.4.0(jiti@2.7.0) jsdoc-type-pratt-parser: 7.2.0 refa: 0.12.1 @@ -6821,13 +7828,13 @@ snapshots: lodash.merge: 4.6.2 minimatch: 10.2.5 scslre: 0.3.0 - semver: 7.8.0 + semver: 7.8.1 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 eslint-plugin-unicorn@64.0.0(eslint@10.4.0(jiti@2.7.0)): dependencies: - '@babel/helper-validator-identifier': 7.28.5 + '@babel/helper-validator-identifier': 7.29.7 '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) change-case: 5.4.4 ci-info: 4.4.0 @@ -6842,32 +7849,32 @@ snapshots: pluralize: 8.0.0 regexp-tree: 0.1.27 regjsparser: 0.13.1 - semver: 7.8.0 + semver: 7.8.1 strip-indent: 4.1.1 eslint-plugin-vue-scoped-css@3.1.0(eslint@10.4.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.0(jiti@2.7.0))): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) - es-toolkit: 1.46.1 + es-toolkit: 1.47.0 eslint: 10.4.0(jiti@2.7.0) postcss: 8.5.15 postcss-safe-parser: 7.0.1(postcss@8.5.15) postcss-selector-parser: 7.1.1 vue-eslint-parser: 10.4.0(eslint@10.4.0(jiti@2.7.0)) - eslint-plugin-vue@10.9.1(@stylistic/eslint-plugin@5.10.0(eslint@10.4.0(jiti@2.7.0)))(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.0(jiti@2.7.0))): + eslint-plugin-vue@10.9.1(@stylistic/eslint-plugin@5.10.0(eslint@10.4.0(jiti@2.7.0)))(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.0(jiti@2.7.0))): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) eslint: 10.4.0(jiti@2.7.0) natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 7.1.1 - semver: 7.8.0 + semver: 7.8.1 vue-eslint-parser: 10.4.0(eslint@10.4.0(jiti@2.7.0)) xml-name-validator: 4.0.0 optionalDependencies: '@stylistic/eslint-plugin': 5.10.0(eslint@10.4.0(jiti@2.7.0)) - '@typescript-eslint/parser': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) eslint-plugin-wc@3.1.0(eslint@10.4.0(jiti@2.7.0)): dependencies: @@ -6880,7 +7887,7 @@ snapshots: eslint-scope@9.1.2: dependencies: '@types/esrecurse': 4.3.1 - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 esrecurse: 4.3.0 estraverse: 5.3.0 @@ -6897,11 +7904,11 @@ snapshots: '@eslint/config-array': 0.23.5 '@eslint/config-helpers': 0.6.0 '@eslint/core': 1.2.1 - '@eslint/plugin-kit': 0.7.1 + '@eslint/plugin-kit': 0.7.2 '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 ajv: 6.15.0 cross-spawn: 7.0.6 debug: 4.4.3 @@ -6955,7 +7962,7 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 esutils@2.0.3: {} @@ -6963,8 +7970,14 @@ snapshots: events@3.3.0: {} + exit-hook@1.1.1: {} + expect-type@1.3.0: {} + extend@3.0.2: {} + + extsprintf@1.3.0: {} + fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -6981,7 +7994,7 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.1.0: {} + fast-uri@3.1.2: {} fastest-levenshtein@1.0.16: {} @@ -7001,6 +8014,11 @@ snapshots: fflate@0.8.2: {} + figures@1.7.0: + dependencies: + escape-string-regexp: 1.0.5 + object-assign: 4.1.1 + file-entry-cache@11.1.3: dependencies: flat-cache: 6.1.22 @@ -7027,34 +8045,106 @@ snapshots: flat-cache@6.1.22: dependencies: - cacheable: 2.3.4 + cacheable: 2.3.5 flatted: 3.4.2 hookified: 1.15.1 flatted@3.4.2: {} + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + forever-agent@0.6.1: {} + + form-data@2.3.3: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + form-data@4.0.5: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 - es-set-tostringtag: '@nolyfill/es-set-tostringtag@1.0.44' - hasown: '@nolyfill/hasown@1.0.44' + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 mime-types: 2.1.35 + fs-extra@0.26.7: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 2.4.0 + klaw: 1.3.1 + path-is-absolute: 1.0.1 + rimraf: 2.7.1 + + fs-promise@0.5.0: + dependencies: + any-promise: 1.3.0 + fs-extra: 0.26.7 + mz: 2.7.0 + thenify-all: 1.6.0 + + fs.realpath@1.0.0: {} + fsevents@2.3.2: optional: true fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.4 + is-callable: 1.2.7 + functional-red-black-tree@1.0.1: {} - get-east-asian-width@1.5.0: {} + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + + get-east-asian-width@1.6.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 get-tsconfig@4.14.0: dependencies: resolve-pkg-maps: 1.0.0 + getpass@0.1.7: + dependencies: + assert-plus: 1.0.0 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -7063,6 +8153,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + global-modules@2.0.0: dependencies: global-prefix: 3.0.0 @@ -7079,6 +8178,11 @@ snapshots: globals@17.6.0: {} + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + globby@16.2.0: dependencies: '@sindresorhus/merge-streams': 4.0.0 @@ -7090,6 +8194,8 @@ snapshots: globjoin@0.1.4: {} + gopd@1.2.0: {} + graceful-fs@4.2.11: {} hachure-fill@0.5.2: {} @@ -7103,19 +8209,50 @@ snapshots: '@types/ws': 8.18.1 entities: 7.0.1 whatwg-mimetype: 3.0.0 - ws: 8.20.0 + ws: 8.21.0 transitivePeerDependencies: - bufferutil - utf-8-validate + har-schema@2.0.0: {} + + har-validator@5.1.5: + dependencies: + ajv: 6.15.0 + har-schema: 2.0.0 + + has-ansi@2.0.0: + dependencies: + ansi-regex: 2.1.1 + + has-bigints@1.1.0: {} + has-flag@4.0.0: {} has-flag@5.0.1: {} + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + hashery@1.5.1: dependencies: hookified: 1.15.1 + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + hookified@1.15.1: {} hookified@2.2.0: {} @@ -7141,6 +8278,12 @@ snapshots: transitivePeerDependencies: - supports-color + http-signature@1.2.0: + dependencies: + assert-plus: 1.0.0 + jsprim: 1.4.2 + sshpk: 1.18.0 + https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 @@ -7150,7 +8293,7 @@ snapshots: iconv-lite@0.6.3: dependencies: - safer-buffer: '@nolyfill/safer-buffer@1.0.44' + safer-buffer: 2.1.2 idiomorph@0.7.4: {} @@ -7171,10 +8314,44 @@ snapshots: indent-string@5.0.0: {} + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + ini@1.3.8: {} ini@4.1.3: {} + inquirer-promise@0.0.3: + dependencies: + earlgrey-runtime: 0.1.2 + inquirer: 0.11.4 + + inquirer@0.11.4: + dependencies: + ansi-escapes: 1.4.0 + ansi-regex: 2.1.1 + chalk: 1.1.3 + cli-cursor: 1.0.2 + cli-width: 1.1.1 + figures: 1.7.0 + lodash: 3.10.1 + readline2: 1.0.1 + run-async: 0.1.0 + rx-lite: 3.1.2 + string-width: 1.0.2 + strip-ansi: 3.0.1 + through: 2.3.8 + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.4 + side-channel: 1.1.0 + internmap@1.0.1: {} internmap@2.0.3: {} @@ -7186,46 +8363,156 @@ snapshots: is-alphabetical: 2.0.1 is-decimal: 2.0.1 + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-arrayish@0.2.1: {} + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-buffer@1.1.6: {} is-builtin-module@5.0.0: dependencies: - builtin-modules: 5.1.0 + builtin-modules: 5.2.0 is-bun-module@2.0.0: dependencies: - semver: 7.8.0 + semver: 7.8.1 + + is-callable@1.2.7: {} + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 is-decimal@2.0.1: {} is-extglob@2.1.1: {} + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@1.0.0: + dependencies: + number-is-nan: 1.0.1 + is-fullwidth-code-point@3.0.0: {} + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 is-hexadecimal@2.0.1: {} + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-number@7.0.0: {} is-path-inside@4.0.0: {} is-potential-custom-element-name@1.0.1: {} + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.21 + + is-typedarray@1.0.0: {} + is-valid-element-name@1.0.0: dependencies: is-potential-custom-element-name: 1.0.1 + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + isexe@2.0.0: {} + isstream@0.1.2: {} + jest-environment-jsdom@29.7.0: dependencies: '@jest/environment': 29.7.0 @@ -7243,7 +8530,7 @@ snapshots: jest-message-util@29.7.0: dependencies: - '@babel/code-frame': 7.29.0 + '@babel/code-frame': 7.29.7 '@jest/types': 29.6.3 '@types/stack-utils': 2.0.3 chalk: 4.1.2 @@ -7284,11 +8571,13 @@ snapshots: dependencies: argparse: 2.0.1 + jsbn@0.1.1: {} + jsdoc-type-pratt-parser@7.2.0: {} jsdom@20.0.3: dependencies: - abab: '@nolyfill/abab@1.0.44' + abab: 2.0.6 acorn: 8.16.0 acorn-globals: 7.0.1 cssom: 0.5.0 @@ -7312,7 +8601,7 @@ snapshots: whatwg-encoding: 2.0.0 whatwg-mimetype: 3.0.0 whatwg-url: 11.0.0 - ws: 8.20.0 + ws: 8.21.0 xml-name-validator: 4.0.0 transitivePeerDependencies: - bufferutil @@ -7329,29 +8618,52 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema@0.4.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} + json-stringify-safe@5.0.1: {} + json5@1.0.2: dependencies: minimist: 1.2.8 jsonc-parser@3.3.1: {} + jsonfile@2.4.0: + optionalDependencies: + graceful-fs: 4.2.11 + jsonpointer@5.0.1: {} + jsprim@1.4.2: + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.4.0 + verror: 1.10.0 + jsx-ast-utils-x@0.1.0: {} jsx-ast-utils@3.3.5: dependencies: - array-includes: '@nolyfill/array-includes@1.0.44' - array.prototype.flat: '@nolyfill/array.prototype.flat@1.0.44' - object.assign: '@nolyfill/object.assign@1.0.44' - object.values: '@nolyfill/object.values@1.0.44' + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + kaiser@0.0.4: + dependencies: + earlgrey-runtime: 0.1.2 katex@0.16.47: dependencies: commander: 8.3.0 + katex@0.17.0: + dependencies: + commander: 8.3.0 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -7368,6 +8680,10 @@ snapshots: kind-of@6.0.3: {} + klaw@1.3.1: + optionalDependencies: + graceful-fs: 4.2.11 + language-subtag-registry@0.3.23: {} language-tags@1.0.9: @@ -7441,7 +8757,7 @@ snapshots: lines-and-columns@1.2.4: {} - linkify-it@5.0.0: + linkify-it@5.0.1: dependencies: uc.micro: 2.1.0 @@ -7463,6 +8779,10 @@ snapshots: lodash.upperfirst@4.3.1: {} + lodash@3.10.1: {} + + lodash@4.18.1: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -7471,7 +8791,7 @@ snapshots: dependencies: argparse: 2.0.1 entities: 4.5.0 - linkify-it: 5.0.0 + linkify-it: 5.0.1 mdurl: 2.0.0 punycode.js: 2.3.1 uc.micro: 2.1.0 @@ -7489,7 +8809,7 @@ snapshots: minimatch: 10.2.5 run-con: 1.3.2 smol-toml: 1.6.1 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 transitivePeerDependencies: - supports-color @@ -7511,13 +8831,16 @@ snapshots: marked@4.3.0: {} - material-icon-theme@5.34.0: + material-icon-theme@5.35.0: dependencies: + biome: 0.3.3 chroma-js: 3.2.0 events: 3.3.0 fast-deep-equal: 3.1.3 svgson: 5.3.1 + math-intrinsics@1.1.0: {} + mathml-tag-names@4.0.0: {} mdn-data@2.0.28: {} @@ -7533,26 +8856,26 @@ snapshots: mermaid@11.15.0: dependencies: '@braintree/sanitize-url': 7.1.2 - '@iconify/utils': 3.1.1 + '@iconify/utils': 3.1.3 '@mermaid-js/parser': 1.1.1 '@types/d3': 7.4.3 '@upsetjs/venn.js': 2.0.0 - cytoscape: 3.33.3 - cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.3) - cytoscape-fcose: 2.2.0(cytoscape@3.33.3) + cytoscape: 3.33.4 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.4) + cytoscape-fcose: 2.2.0(cytoscape@3.33.4) d3: 7.9.0 d3-sankey: 0.12.3 dagre-d3-es: 7.0.14 - dayjs: 1.11.20 - dompurify: 3.4.2 - es-toolkit: 1.46.1 + dayjs: 1.11.21 + dompurify: 3.4.7 + es-toolkit: 1.47.0 katex: 0.16.47 khroma: 2.1.0 marked: 16.4.2 roughjs: 4.6.6 stylis: 4.4.0 ts-dedent: 2.2.0 - uuid: 11.1.1 + uuid: 14.0.0 micromark-core-commonmark@2.0.3: dependencies: @@ -7739,27 +9062,22 @@ snapshots: minimatch@10.2.5: dependencies: - brace-expansion: 5.0.5 + brace-expansion: 5.0.6 minimatch@3.1.5: dependencies: - brace-expansion: 1.1.14 + brace-expansion: 1.1.15 minimist@1.2.8: {} - mlly@1.8.2: - dependencies: - acorn: 8.16.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.4 - moo@0.5.3: {} ms@2.1.3: {} muggle-string@0.4.1: {} + mute-stream@0.0.5: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -7776,9 +9094,9 @@ snapshots: node-exports-info@1.6.0: dependencies: - array.prototype.flatmap: '@nolyfill/array.prototype.flatmap@1.0.44' + array.prototype.flatmap: 1.3.3 es-errors: 1.3.0 - object.entries: '@nolyfill/object.entries@1.0.44' + object.entries: 1.1.9 semver: 6.3.1 node-fetch@2.6.13: @@ -7789,9 +9107,7 @@ snapshots: dependencies: whatwg-url: 5.0.0 - node-releases@2.0.38: {} - - nolyfill@1.0.44: {} + node-releases@2.0.46: {} normalize-path@3.0.0: {} @@ -7799,14 +9115,64 @@ snapshots: dependencies: boolbase: 1.0.0 + number-is-nan@1.0.1: {} + nwsapi@2.2.23: {} + oauth-sign@0.9.0: {} + object-assign@4.1.1: {} object-hash@3.0.0: {} + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + obug@2.1.1: {} + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@1.1.0: {} + online-3d-viewer@0.18.0: dependencies: '@simonwep/pickr': 1.9.0 @@ -7822,6 +9188,14 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + os-homedir@1.0.2: {} + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -7848,7 +9222,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.29.0 + '@babel/code-frame': 7.29.7 error-ex: 1.3.4 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -7863,6 +9237,8 @@ snapshots: path-exists@4.0.0: {} + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} path-parse@1.0.7: {} @@ -7873,6 +9249,8 @@ snapshots: perfect-debounce@2.1.0: {} + performance-now@2.1.0: {} + picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -7883,12 +9261,6 @@ snapshots: pirates@4.0.7: {} - pkg-types@1.3.1: - dependencies: - confbox: 0.1.8 - mlly: 1.8.2 - pathe: 2.0.3 - playwright-core@1.60.0: {} playwright@1.60.0: @@ -7906,6 +9278,8 @@ snapshots: path-data-parser: 0.1.0 points-on-curve: 0.2.0 + possible-typed-array-names@1.1.0: {} + postcss-html@1.8.1: dependencies: htmlparser2: 8.0.2 @@ -7985,10 +9359,12 @@ snapshots: punycode@2.3.1: {} - qified@0.9.1: + qified@0.10.1: dependencies: hookified: 2.2.0 + qs@6.5.5: {} + querystringify@2.2.0: {} queue-microtask@1.2.3: {} @@ -8003,10 +9379,29 @@ snapshots: dependencies: picomatch: 2.3.2 + readline2@1.0.1: + dependencies: + code-point-at: 1.1.0 + is-fullwidth-code-point: 1.0.0 + mute-stream: 0.0.5 + refa@0.12.1: dependencies: '@eslint-community/regexpp': 4.12.2 + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regenerator-runtime@0.9.6: {} + regexp-ast-analysis@0.7.1: dependencies: '@eslint-community/regexpp': 4.12.2 @@ -8014,12 +9409,50 @@ snapshots: regexp-tree@0.1.27: {} + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + regjsparser@0.13.1: dependencies: jsesc: 3.1.0 rename-keys@1.2.0: {} + request-promise@3.0.0: + dependencies: + bluebird: 3.7.2 + lodash: 4.18.1 + request: 2.88.2 + + request@2.88.2: + dependencies: + aws-sign2: 0.7.0 + aws4: 1.13.2 + caseless: 0.12.0 + combined-stream: 1.0.8 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 2.3.3 + har-validator: 5.1.5 + http-signature: 1.2.0 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.35 + oauth-sign: 0.9.0 + performance-now: 2.1.0 + qs: 6.5.5 + safe-buffer: 5.2.1 + tough-cookie: 2.5.0 + tunnel-agent: 0.6.0 + uuid: 3.4.0 + require-from-string@2.0.2: {} requires-port@1.0.0: {} @@ -8031,47 +9464,56 @@ snapshots: resolve@1.22.12: dependencies: es-errors: 1.3.0 - is-core-module: '@nolyfill/is-core-module@1.0.39' + is-core-module: 2.16.2 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - resolve@2.0.0-next.6: + resolve@2.0.0-next.7: dependencies: es-errors: 1.3.0 - is-core-module: '@nolyfill/is-core-module@1.0.39' + is-core-module: 2.16.2 node-exports-info: 1.6.0 - object-keys: '@nolyfill/object-keys@1.0.44' + object-keys: 1.1.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@1.0.1: + dependencies: + exit-hook: 1.1.1 + onetime: 1.1.0 + reusify@1.1.0: {} + rimraf@2.7.1: + dependencies: + glob: 7.2.3 + robust-predicates@3.0.3: {} - rolldown-license-plugin@3.0.7(rolldown@1.0.1): + rolldown-license-plugin@3.0.8(rolldown@1.0.2): dependencies: - rolldown: 1.0.1 + rolldown: 1.0.2 - rolldown@1.0.1: + rolldown@1.0.2: dependencies: - '@oxc-project/types': 0.130.0 + '@oxc-project/types': 0.132.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.1 - '@rolldown/binding-darwin-arm64': 1.0.1 - '@rolldown/binding-darwin-x64': 1.0.1 - '@rolldown/binding-freebsd-x64': 1.0.1 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.1 - '@rolldown/binding-linux-arm64-gnu': 1.0.1 - '@rolldown/binding-linux-arm64-musl': 1.0.1 - '@rolldown/binding-linux-ppc64-gnu': 1.0.1 - '@rolldown/binding-linux-s390x-gnu': 1.0.1 - '@rolldown/binding-linux-x64-gnu': 1.0.1 - '@rolldown/binding-linux-x64-musl': 1.0.1 - '@rolldown/binding-openharmony-arm64': 1.0.1 - '@rolldown/binding-wasm32-wasi': 1.0.1 - '@rolldown/binding-win32-arm64-msvc': 1.0.1 - '@rolldown/binding-win32-x64-msvc': 1.0.1 + '@rolldown/binding-android-arm64': 1.0.2 + '@rolldown/binding-darwin-arm64': 1.0.2 + '@rolldown/binding-darwin-x64': 1.0.2 + '@rolldown/binding-freebsd-x64': 1.0.2 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.2 + '@rolldown/binding-linux-arm64-gnu': 1.0.2 + '@rolldown/binding-linux-arm64-musl': 1.0.2 + '@rolldown/binding-linux-ppc64-gnu': 1.0.2 + '@rolldown/binding-linux-s390x-gnu': 1.0.2 + '@rolldown/binding-linux-x64-gnu': 1.0.2 + '@rolldown/binding-linux-x64-musl': 1.0.2 + '@rolldown/binding-openharmony-arm64': 1.0.2 + '@rolldown/binding-wasm32-wasi': 1.0.2 + '@rolldown/binding-win32-arm64-msvc': 1.0.2 + '@rolldown/binding-win32-x64-msvc': 1.0.2 roughjs@4.6.6: dependencies: @@ -8080,6 +9522,10 @@ snapshots: points-on-curve: 0.2.0 points-on-path: 0.2.1 + run-async@0.1.0: + dependencies: + once: 1.4.0 + run-con@1.3.2: dependencies: deep-extend: 0.6.0 @@ -8093,6 +9539,31 @@ snapshots: rw@1.3.3: {} + rx-lite@3.1.2: {} + + safe-array-concat@1.1.4: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-buffer@5.2.1: {} + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + sax@1.6.0: {} saxes@6.0.0: @@ -8107,13 +9578,35 @@ snapshots: semver@6.3.1: {} - semver@7.8.0: {} + semver@7.8.1: {} - seroval-plugins@1.5.3(seroval@1.5.3): + seroval-plugins@1.5.4(seroval@1.5.4): dependencies: - seroval: 1.5.3 + seroval: 1.5.4 - seroval@1.5.3: {} + seroval@1.5.4: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 shebang-command@2.0.0: dependencies: @@ -8121,6 +9614,34 @@ snapshots: shebang-regex@3.0.0: {} + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} signal-exit@4.1.0: {} @@ -8137,17 +9658,17 @@ snapshots: smol-toml@1.6.1: {} - solid-js@1.9.12: + solid-js@1.9.13: dependencies: csstype: 3.2.3 - seroval: 1.5.3 - seroval-plugins: 1.5.3(seroval@1.5.3) + seroval: 1.5.4 + seroval-plugins: 1.5.4(seroval@1.5.4) - solid-transition-group@0.2.3(solid-js@1.9.12): + solid-transition-group@0.2.3(solid-js@1.9.13): dependencies: - '@solid-primitives/refs': 1.1.3(solid-js@1.9.12) - '@solid-primitives/transition-group': 1.1.2(solid-js@1.9.12) - solid-js: 1.9.12 + '@solid-primitives/refs': 1.1.3(solid-js@1.9.13) + '@solid-primitives/transition-group': 1.1.2(solid-js@1.9.13) + solid-js: 1.9.13 sortablejs@1.15.7: {} @@ -8160,6 +9681,18 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + sshpk@1.18.0: + dependencies: + asn1: 0.2.6 + assert-plus: 1.0.0 + bcrypt-pbkdf: 1.0.2 + dashdash: 1.14.1 + ecc-jsbn: 0.1.2 + getpass: 0.1.7 + jsbn: 0.1.1 + safer-buffer: 2.1.2 + tweetnacl: 0.14.5 + stable-hash-x@0.2.0: {} stack-utils@2.0.6: @@ -8170,6 +9703,17 @@ snapshots: std-env@4.1.0: {} + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + string-width@1.0.2: + dependencies: + code-point-at: 1.1.0 + is-fullwidth-code-point: 1.0.0 + strip-ansi: 3.0.1 + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -8178,14 +9722,47 @@ snapshots: string-width@8.1.0: dependencies: - get-east-asian-width: 1.5.0 + get-east-asian-width: 1.6.0 strip-ansi: 7.2.0 string-width@8.2.1: dependencies: - get-east-asian-width: 1.5.0 + get-east-asian-width: 1.6.0 strip-ansi: 7.2.0 + string.prototype.includes@2.0.1: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + strip-ansi@3.0.1: + dependencies: + ansi-regex: 2.1.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -8224,9 +9801,9 @@ snapshots: stylelint@17.12.0(typescript@6.0.3): dependencies: - '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-syntax-patches-for-csstree': 1.1.3(css-tree@3.2.1) + '@csstools/css-syntax-patches-for-csstree': 1.1.4(css-tree@3.2.1) '@csstools/css-tokenizer': 4.0.0 '@csstools/media-query-list-parser': 5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/selector-resolve-nested': 4.0.0(postcss-selector-parser@7.1.1) @@ -8272,11 +9849,13 @@ snapshots: lines-and-columns: 1.2.4 mz: 2.7.0 pirates: 4.0.7 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 ts-interface-checker: 0.1.13 supports-color@10.2.2: {} + supports-color@2.0.0: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -8320,9 +9899,9 @@ snapshots: transitivePeerDependencies: - encoding - synckit@0.11.12: + synckit@0.11.13: dependencies: - '@pkgr/core': 0.2.9 + '@pkgr/core': 0.3.6 table@6.9.0: dependencies: @@ -8372,11 +9951,13 @@ snapshots: throttle-debounce@5.0.2: {} + through@2.3.8: {} + tinybench@2.9.0: {} - tinyexec@1.1.2: {} + tinyexec@1.2.4: {} - tinyglobby@0.2.16: + tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 @@ -8393,6 +9974,11 @@ snapshots: toastify-js@1.12.0: {} + tough-cookie@2.5.0: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + tough-cookie@4.1.4: dependencies: psl: 1.15.0 @@ -8429,29 +10015,68 @@ snapshots: tslib@2.8.1: {} + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + tweetnacl@0.14.5: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 type-detect@4.0.8: {} - typescript-eslint@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3): + typed-array-buffer@1.0.3: dependencies: - '@typescript-eslint/eslint-plugin': 8.59.4(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/parser': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.9.3) - '@typescript-eslint/utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.8: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript-eslint@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.60.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) eslint: 10.4.0(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - typescript-eslint@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3): + typescript-eslint@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.59.4(@typescript-eslint/parser@8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/parser': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.59.4(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.4(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.60.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.60.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) eslint: 10.4.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: @@ -8461,43 +10086,53 @@ snapshots: typescript@6.0.3: {} - typo-js@1.3.1: {} + typo-js@1.3.2: {} uc.micro@2.1.0: {} - ufo@1.6.4: {} - uint8-to-base64@0.2.1: {} + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + undici-types@7.24.6: {} unicorn-magic@0.4.0: {} universalify@0.2.0: {} - unrs-resolver@1.11.1: + unrs-resolver@1.12.2: dependencies: napi-postinstall: 0.3.4 optionalDependencies: - '@unrs/resolver-binding-android-arm-eabi': 1.11.1 - '@unrs/resolver-binding-android-arm64': 1.11.1 - '@unrs/resolver-binding-darwin-arm64': 1.11.1 - '@unrs/resolver-binding-darwin-x64': 1.11.1 - '@unrs/resolver-binding-freebsd-x64': 1.11.1 - '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 - '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 - '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 - '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 - '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 - '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-x64-musl': 1.11.1 - '@unrs/resolver-binding-wasm32-wasi': 1.11.1 - '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 - '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 - '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + '@unrs/resolver-binding-android-arm-eabi': 1.12.2 + '@unrs/resolver-binding-android-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-x64': 1.12.2 + '@unrs/resolver-binding-freebsd-x64': 1.12.2 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-arm64-musl': 1.12.2 + '@unrs/resolver-binding-linux-loong64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-loong64-musl': 1.12.2 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-musl': 1.12.2 + '@unrs/resolver-binding-linux-s390x-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-musl': 1.12.2 + '@unrs/resolver-binding-openharmony-arm64': 1.12.2 + '@unrs/resolver-binding-wasm32-wasi': 1.12.2 + '@unrs/resolver-binding-win32-arm64-msvc': 1.12.2 + '@unrs/resolver-binding-win32-ia32-msvc': 1.12.2 + '@unrs/resolver-binding-win32-x64-msvc': 1.12.2 + + untildify@3.0.3: {} update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: @@ -8505,7 +10140,7 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - updates@17.16.13: {} + updates@17.17.2: {} uri-js@4.4.1: dependencies: @@ -8516,33 +10151,45 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 + user-home@2.0.0: + dependencies: + os-homedir: 1.0.2 + util-deprecate@1.0.2: {} - uuid@11.1.1: {} + uuid@14.0.0: {} + + uuid@3.4.0: {} vanilla-colorful@0.7.2: {} - vite-string-plugin@2.0.4(vite@8.0.13(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)): + verror@1.10.0: dependencies: - vite: 8.0.13(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.3.0 - vite@8.0.13(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0): + vite-string-plugin@2.0.4(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)): + dependencies: + vite: 8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) + + vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 postcss: 8.5.15 - rolldown: 1.0.1 - tinyglobby: 0.2.16 + rolldown: 1.0.2 + tinyglobby: 0.2.17 optionalDependencies: '@types/node': 25.9.1 esbuild: 0.28.0 fsevents: 2.3.3 jiti: 2.7.0 - vitest@4.1.7(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.13(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)): + vitest@4.1.7(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)): dependencies: '@vitest/expect': 4.1.7 - '@vitest/mocker': 4.1.7(vite@8.0.13(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)) + '@vitest/mocker': 4.1.7(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)) '@vitest/pretty-format': 4.1.7 '@vitest/runner': 4.1.7 '@vitest/snapshot': 4.1.7 @@ -8556,10 +10203,10 @@ snapshots: picomatch: 4.0.4 std-env: 4.1.0 tinybench: 2.9.0 - tinyexec: 1.1.2 - tinyglobby: 0.2.16 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.0.13(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) + vite: 8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.9.1 @@ -8572,14 +10219,14 @@ snapshots: vue-bar-graph@2.2.0(typescript@6.0.3): dependencies: - vue: 3.5.34(typescript@6.0.3) + vue: 3.5.35(typescript@6.0.3) transitivePeerDependencies: - typescript - vue-chartjs@5.3.3(chart.js@4.5.1)(vue@3.5.34(typescript@6.0.3)): + vue-chartjs@5.3.3(chart.js@4.5.1)(vue@3.5.35(typescript@6.0.3)): dependencies: chart.js: 4.5.1 - vue: 3.5.34(typescript@6.0.3) + vue: 3.5.35(typescript@6.0.3) vue-eslint-parser@10.4.0(eslint@10.4.0(jiti@2.7.0)): dependencies: @@ -8589,23 +10236,23 @@ snapshots: eslint-visitor-keys: 5.0.1 espree: 11.2.0 esquery: 1.7.0 - semver: 7.8.0 + semver: 7.8.1 transitivePeerDependencies: - supports-color - vue-tsc@3.3.1(typescript@6.0.3): + vue-tsc@3.3.2(typescript@6.0.3): dependencies: '@volar/typescript': 2.4.28 - '@vue/language-core': 3.3.1 + '@vue/language-core': 3.3.2 typescript: 6.0.3 - vue@3.5.34(typescript@6.0.3): + vue@3.5.35(typescript@6.0.3): dependencies: - '@vue/compiler-dom': 3.5.34 - '@vue/compiler-sfc': 3.5.34 - '@vue/runtime-dom': 3.5.34 - '@vue/server-renderer': 3.5.34(vue@3.5.34(typescript@6.0.3)) - '@vue/shared': 3.5.34 + '@vue/compiler-dom': 3.5.35 + '@vue/compiler-sfc': 3.5.35 + '@vue/runtime-dom': 3.5.35 + '@vue/server-renderer': 3.5.35(vue@3.5.35(typescript@6.0.3)) + '@vue/shared': 3.5.35 optionalDependencies: typescript: 6.0.3 @@ -8635,6 +10282,47 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.21 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.21: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + which@1.3.1: dependencies: isexe: 2.0.0 @@ -8650,11 +10338,13 @@ snapshots: word-wrap@1.2.5: {} + wrappy@1.0.2: {} + write-file-atomic@7.0.1: dependencies: signal-exit: 4.1.0 - ws@8.20.0: {} + ws@8.21.0: {} xml-lexer@0.2.2: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0c8f7c63fef..c16cfb76cba 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,4 +1,3 @@ -packages: [ . ] # workaround for https://github.com/SukkaW/nolyfill/issues/119 savePrefix: '' dedupePeerDependents: false updateNotifier: false @@ -8,28 +7,6 @@ peerDependencyRules: allowedVersions: eslint-plugin-github>eslint: '>=9' -overrides: - array-includes: npm:@nolyfill/array-includes@^1 - array.prototype.findlastindex: npm:@nolyfill/array.prototype.findlastindex@^1 - array.prototype.flat: npm:@nolyfill/array.prototype.flat@^1 - array.prototype.flatmap: npm:@nolyfill/array.prototype.flatmap@^1 - es-aggregate-error: npm:@nolyfill/es-aggregate-error@^1 - hasown: npm:@nolyfill/hasown@^1 - is-core-module: npm:@nolyfill/is-core-module@^1 - object.assign: npm:@nolyfill/object.assign@^1 - object.fromentries: npm:@nolyfill/object.fromentries@^1 - object.groupby: npm:@nolyfill/object.groupby@^1 - object.values: npm:@nolyfill/object.values@^1 - safe-buffer: npm:@nolyfill/safe-buffer@^1 - safe-regex-test: npm:@nolyfill/safe-regex-test@^1 - safer-buffer: npm:@nolyfill/safer-buffer@^1 - string.prototype.includes: npm:@nolyfill/string.prototype.includes@^1 - string.prototype.trimend: npm:@nolyfill/string.prototype.trimend@^1 - object-keys: npm:@nolyfill/object-keys@^1 - object.entries: npm:@nolyfill/object.entries@^1 - abab: npm:@nolyfill/abab@^1 - es-set-tostringtag: npm:@nolyfill/es-set-tostringtag@^1 - allowBuilds: '@scarf/scarf': false core-js: false diff --git a/public/assets/img/svg/octicon-flag.svg b/public/assets/img/svg/octicon-flag.svg new file mode 100644 index 00000000000..1d757dca08d --- /dev/null +++ b/public/assets/img/svg/octicon-flag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/renovate.json5 b/renovate.json5 index 6f86c61e515..bbfbb8d78a0 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -95,7 +95,7 @@ "matchManagers": ["npm"], "postUpdateOptions": ["pnpmDedupe"], "postUpgradeTasks": { - "commands": ["make svg nolyfill"], + "commands": ["make svg"], "fileFilters": ["package.json", "pnpm-lock.yaml", "pnpm-workspace.yaml", "public/assets/img/svg/**"], "executionMode": "branch", }, diff --git a/tools/migrate-nolyfills.ts b/tools/migrate-nolyfills.ts deleted file mode 100644 index 2e5f635272a..00000000000 --- a/tools/migrate-nolyfills.ts +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env node -// nolyfill writes overrides to package.json#pnpm.overrides which pnpm v11 ignores. -// This moves them to pnpm-workspace.yaml until SukkaW/nolyfill#119 is fixed. -import {readFileSync, writeFileSync} from 'node:fs'; -import {exit} from 'node:process'; -import {fileURLToPath} from 'node:url'; -import {dump} from 'js-yaml'; - -const packagePath = fileURLToPath(new URL('../package.json', import.meta.url)); -const workspacePath = fileURLToPath(new URL('../pnpm-workspace.yaml', import.meta.url)); - -const packageJson: {pnpm?: {overrides?: Record}} = JSON.parse(readFileSync(packagePath, 'utf8')); -const overrides = packageJson.pnpm?.overrides; - -if (!overrides || !Object.keys(overrides).length) { - exit(0); -} - -const block = dump({overrides}, {lineWidth: -1, quotingType: "'"}); -const workspace = readFileSync(workspacePath, 'utf8'); -const overridesRegex = /^overrides:[^\n]*(?:\n(?:[ \t][^\n]*|[ \t]*(?=\n[ \t])))*\n?/m; - -if (!overridesRegex.test(workspace)) { - console.error(`No 'overrides:' block found in pnpm-workspace.yaml`); - exit(1); -} - -writeFileSync(workspacePath, workspace.replace(overridesRegex, block)); - -const pnpm = packageJson.pnpm!; -delete pnpm.overrides; -if (!Object.keys(pnpm).length) delete packageJson.pnpm; -writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`); From 9619d93e3b81e9f8fe77f01fe8ec21f1cc343bde Mon Sep 17 00:00:00 2001 From: Giteabot Date: Mon, 1 Jun 2026 22:53:44 -0700 Subject: [PATCH 04/40] chore(deps): update action dependencies (#37964) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | Pending | |---|---|---|---|---| | [aws-actions/configure-aws-credentials](https://redirect.github.com/aws-actions/configure-aws-credentials) | action | patch | `v6.1.1` → `v6.1.2` | `v6.1.3` | | [docker/build-push-action](https://redirect.github.com/docker/build-push-action) | action | minor | `v7.1.0` → `v7.2.0` | | | [docker/login-action](https://redirect.github.com/docker/login-action) | action | minor | `v4.1.0` → `v4.2.0` | | | [docker/metadata-action](https://redirect.github.com/docker/metadata-action) | action | minor | `v6.0.0` → `v6.1.0` | | | [docker/setup-buildx-action](https://redirect.github.com/docker/setup-buildx-action) | action | minor | `v4.0.0` → `v4.1.0` | | | [docker/setup-qemu-action](https://redirect.github.com/docker/setup-qemu-action) | action | minor | `v4.0.0` → `v4.1.0` | | | redis | service | digest | `48e78eb` → `e74c9b9` | | --- ### Release Notes
aws-actions/configure-aws-credentials (aws-actions/configure-aws-credentials) ### [`v6.1.2`](https://redirect.github.com/aws-actions/configure-aws-credentials/releases/tag/v6.1.2) [Compare Source](https://redirect.github.com/aws-actions/configure-aws-credentials/compare/v6.1.1...v6.1.2) ##### Bug Fixes - additional filesystem checks ([#​1799](https://redirect.github.com/aws-actions/configure-aws-credentials/issues/1799)) ([c39f282](https://redirect.github.com/aws-actions/configure-aws-credentials/commit/c39f282697aca8a78c522ecf1f7da9899a31432c))
docker/build-push-action (docker/build-push-action) ### [`v7.2.0`](https://redirect.github.com/docker/build-push-action/releases/tag/v7.2.0) [Compare Source](https://redirect.github.com/docker/build-push-action/compare/v7.1.0...v7.2.0) - Bump [@​actions/core](https://redirect.github.com/actions/core) from 3.0.0 to 3.0.1 in [#​1525](https://redirect.github.com/docker/build-push-action/pull/1525) - Bump [@​docker/actions-toolkit](https://redirect.github.com/docker/actions-toolkit) from 0.87.0 to 0.90.0 in [#​1517](https://redirect.github.com/docker/build-push-action/pull/1517) - Bump brace-expansion from 2.0.2 to 5.0.6 in [#​1534](https://redirect.github.com/docker/build-push-action/pull/1534) - Bump fast-xml-builder from 1.1.4 to 1.2.0 in [#​1529](https://redirect.github.com/docker/build-push-action/pull/1529) - Bump fast-xml-parser from 5.5.7 to 5.8.0 in [#​1521](https://redirect.github.com/docker/build-push-action/pull/1521) - Bump postcss from 8.5.6 to 8.5.10 in [#​1526](https://redirect.github.com/docker/build-push-action/pull/1526) - Bump tar from 6.2.1 to 7.5.15 in [#​1533](https://redirect.github.com/docker/build-push-action/pull/1533) **Full Changelog**:
docker/login-action (docker/login-action) ### [`v4.2.0`](https://redirect.github.com/docker/login-action/releases/tag/v4.2.0) [Compare Source](https://redirect.github.com/docker/login-action/compare/v4.1.0...v4.2.0) - Bump [@​actions/core](https://redirect.github.com/actions/core) from 3.0.0 to 3.0.1 in [#​976](https://redirect.github.com/docker/login-action/pull/976) - Bump [@​aws-sdk/client-ecr](https://redirect.github.com/aws-sdk/client-ecr) and [@​aws-sdk/client-ecr-public](https://redirect.github.com/aws-sdk/client-ecr-public) to 3.1050.0 in [#​960](https://redirect.github.com/docker/login-action/pull/960) - Bump [@​docker/actions-toolkit](https://redirect.github.com/docker/actions-toolkit) from 0.86.0 to 0.90.0 in [#​970](https://redirect.github.com/docker/login-action/pull/970) - Bump brace-expansion from 2.0.1 to 5.0.6 in [#​993](https://redirect.github.com/docker/login-action/pull/993) - Bump fast-xml-builder from 1.1.4 to 1.2.0 in [#​985](https://redirect.github.com/docker/login-action/pull/985) - Bump fast-xml-parser from 5.3.6 to 5.8.0 in [#​963](https://redirect.github.com/docker/login-action/pull/963) - Bump http-proxy-agent and https-proxy-agent to 9.0.0 in [#​961](https://redirect.github.com/docker/login-action/pull/961) - Bump postcss from 8.5.6 to 8.5.10 in [#​979](https://redirect.github.com/docker/login-action/pull/979) - Bump tar from 6.2.1 to 7.5.15 in [#​991](https://redirect.github.com/docker/login-action/pull/991) - Bump vite from 7.3.1 to 7.3.3 in [#​986](https://redirect.github.com/docker/login-action/pull/986) **Full Changelog**:
docker/metadata-action (docker/metadata-action) ### [`v6.1.0`](https://redirect.github.com/docker/metadata-action/releases/tag/v6.1.0) [Compare Source](https://redirect.github.com/docker/metadata-action/compare/v6...v6.1.0) - Bump [@​docker/actions-toolkit](https://redirect.github.com/docker/actions-toolkit) from 0.79.0 to 0.90.0 in [#​613](https://redirect.github.com/docker/metadata-action/pull/613) - Bump brace-expansion from 1.1.12 to 5.0.6 in [#​658](https://redirect.github.com/docker/metadata-action/pull/658) [#​630](https://redirect.github.com/docker/metadata-action/pull/630) - Bump csv-parse from 6.1.0 to 6.2.1 in [#​617](https://redirect.github.com/docker/metadata-action/pull/617) - Bump fast-xml-parser from 5.4.2 to 5.8.0 in [#​620](https://redirect.github.com/docker/metadata-action/pull/620) - Bump flatted from 3.3.3 to 3.4.2 in [#​623](https://redirect.github.com/docker/metadata-action/pull/623) - Bump glob from 10.3.15 to 10.5.0 in [#​621](https://redirect.github.com/docker/metadata-action/pull/621) - Bump handlebars from 4.7.8 to 4.7.9 in [#​629](https://redirect.github.com/docker/metadata-action/pull/629) - Bump lodash from 4.17.23 to 4.18.1 in [#​639](https://redirect.github.com/docker/metadata-action/pull/639) - Bump moment-timezone from 0.6.0 to 0.6.1 in [#​619](https://redirect.github.com/docker/metadata-action/pull/619) - Bump picomatch from 4.0.3 to 4.0.4 in [#​626](https://redirect.github.com/docker/metadata-action/pull/626) - Bump postcss from 8.5.6 to 8.5.10 in [#​649](https://redirect.github.com/docker/metadata-action/pull/649) - Bump tar from 6.2.1 to 7.5.15 in [#​657](https://redirect.github.com/docker/metadata-action/pull/657) - Bump undici from 6.23.0 to 6.25.0 in [#​614](https://redirect.github.com/docker/metadata-action/pull/614) - Bump vite from 7.3.1 to 7.3.2 in [#​637](https://redirect.github.com/docker/metadata-action/pull/637) **Full Changelog**:
docker/setup-buildx-action (docker/setup-buildx-action) ### [`v4.1.0`](https://redirect.github.com/docker/setup-buildx-action/releases/tag/v4.1.0) [Compare Source](https://redirect.github.com/docker/setup-buildx-action/compare/v4...v4.1.0) - Bump [@​docker/actions-toolkit](https://redirect.github.com/docker/actions-toolkit) from 0.79.0 to 0.90.0 in [#​489](https://redirect.github.com/docker/setup-buildx-action/pull/489) - Bump brace-expansion from 1.1.12 to 5.0.6 in [#​547](https://redirect.github.com/docker/setup-buildx-action/pull/547) [#​508](https://redirect.github.com/docker/setup-buildx-action/pull/508) - Bump fast-xml-builder from 1.0.0 to 1.2.0 in [#​540](https://redirect.github.com/docker/setup-buildx-action/pull/540) - Bump fast-xml-parser from 5.4.2 to 5.8.0 in [#​496](https://redirect.github.com/docker/setup-buildx-action/pull/496) - Bump flatted from 3.3.3 to 3.4.2 in [#​499](https://redirect.github.com/docker/setup-buildx-action/pull/499) - Bump glob from 10.3.12 to 13.0.6 in [#​495](https://redirect.github.com/docker/setup-buildx-action/pull/495) - Bump handlebars from 4.7.8 to 4.7.9 in [#​504](https://redirect.github.com/docker/setup-buildx-action/pull/504) - Bump lodash from 4.17.23 to 4.18.1 in [#​523](https://redirect.github.com/docker/setup-buildx-action/pull/523) - Bump picomatch from 4.0.3 to 4.0.4 in [#​503](https://redirect.github.com/docker/setup-buildx-action/pull/503) - Bump postcss from 8.5.6 to 8.5.10 in [#​537](https://redirect.github.com/docker/setup-buildx-action/pull/537) - Bump tar from 6.2.1 to 7.5.15 in [#​545](https://redirect.github.com/docker/setup-buildx-action/pull/545) - Bump undici from 6.23.0 to 6.25.0 in [#​492](https://redirect.github.com/docker/setup-buildx-action/pull/492) - Bump vite from 7.3.1 to 7.3.2 in [#​520](https://redirect.github.com/docker/setup-buildx-action/pull/520) **Full Changelog**:
docker/setup-qemu-action (docker/setup-qemu-action) ### [`v4.1.0`](https://redirect.github.com/docker/setup-qemu-action/releases/tag/v4.1.0) [Compare Source](https://redirect.github.com/docker/setup-qemu-action/compare/v4...v4.1.0) - Add `reset` input to uninstall current emulators by [@​crazy-max](https://redirect.github.com/crazy-max) in [#​21](https://redirect.github.com/docker/setup-qemu-action/pull/21) - Bump [@​docker/actions-toolkit](https://redirect.github.com/docker/actions-toolkit) from 0.77.0 to 0.91.0 in [#​250](https://redirect.github.com/docker/setup-qemu-action/pull/250) [#​247](https://redirect.github.com/docker/setup-qemu-action/pull/247) - Bump brace-expansion from 1.1.12 to 1.1.15 in [#​265](https://redirect.github.com/docker/setup-qemu-action/pull/265) - Bump fast-xml-builder from 1.0.0 to 1.2.0 in [#​286](https://redirect.github.com/docker/setup-qemu-action/pull/286) - Bump fast-xml-parser from 5.4.2 to 5.8.0 in [#​255](https://redirect.github.com/docker/setup-qemu-action/pull/255) - Bump flatted from 3.3.3 to 3.4.2 in [#​257](https://redirect.github.com/docker/setup-qemu-action/pull/257) - Bump glob from 10.3.15 to 10.5.0 in [#​254](https://redirect.github.com/docker/setup-qemu-action/pull/254) - Bump handlebars from 4.7.8 to 4.7.9 in [#​262](https://redirect.github.com/docker/setup-qemu-action/pull/262) - Bump lodash from 4.17.23 to 4.18.1 in [#​273](https://redirect.github.com/docker/setup-qemu-action/pull/273) - Bump postcss from 8.5.6 to 8.5.10 in [#​285](https://redirect.github.com/docker/setup-qemu-action/pull/285) - Bump tar from 6.2.1 to 7.5.15 in [#​287](https://redirect.github.com/docker/setup-qemu-action/pull/287) - Bump tmp from 0.2.5 to 0.2.6 in [#​291](https://redirect.github.com/docker/setup-qemu-action/pull/291) - Bump undici from 6.23.0 to 6.26.0 in [#​251](https://redirect.github.com/docker/setup-qemu-action/pull/251) - Bump vite from 7.3.1 to 7.3.2 in [#​271](https://redirect.github.com/docker/setup-qemu-action/pull/271) **Full Changelog**:
--- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Only on Monday (`* * * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://redirect.github.com/renovatebot/renovate). Co-authored-by: Lunny Xiao Co-authored-by: silverwind --- .github/actions/docker-dryrun/action.yml | 8 ++++---- .github/workflows/pull-db-tests.yml | 2 +- .github/workflows/release-nightly.yml | 18 +++++++++--------- .github/workflows/release-tag-rc.yml | 18 +++++++++--------- .github/workflows/release-tag-version.yml | 18 +++++++++--------- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/.github/actions/docker-dryrun/action.yml b/.github/actions/docker-dryrun/action.yml index d280ea26ce7..e9cd88d46f6 100644 --- a/.github/actions/docker-dryrun/action.yml +++ b/.github/actions/docker-dryrun/action.yml @@ -9,10 +9,10 @@ inputs: runs: using: composite steps: - - uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 - - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + - uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Build regular image - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . platforms: ${{ inputs.platform }} @@ -20,7 +20,7 @@ runs: file: Dockerfile cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful - name: Build rootless image - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . platforms: ${{ inputs.platform }} diff --git a/.github/workflows/pull-db-tests.yml b/.github/workflows/pull-db-tests.yml index 641a3cacb8b..cbf86247ce1 100644 --- a/.github/workflows/pull-db-tests.yml +++ b/.github/workflows/pull-db-tests.yml @@ -131,7 +131,7 @@ jobs: ports: - "7700:7700" redis: - image: redis:latest@sha256:48e78eb9d1e1adcfb10184b2cc3c7fc5ed21e5a3be08875f239257d194bab8c9 + image: redis:latest@sha256:e74c9b933d78e2829583d88f92793f4524752a15ac59c8baff2dd5ed000b7432 options: >- # wait until redis has started --health-cmd "redis-cli ping" --health-interval 5s diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index 82ebf79a61e..d1561329431 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -57,7 +57,7 @@ jobs: echo "Cleaned name is ${REF_NAME}" echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT" - name: configure aws - uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1 + uses: aws-actions/configure-aws-credentials@acca2b1b2070338fb9fd1ca27ecee81d687e58e5 # v6.1.2 with: aws-region: ${{ secrets.AWS_REGION }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} @@ -79,8 +79,8 @@ jobs: # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force - - uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 - - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + - uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Get cleaned branch name id: clean_name env: @@ -88,7 +88,7 @@ jobs: run: | REF_NAME=$(echo "$REF" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//') echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT" - - uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 id: meta with: images: |- @@ -98,7 +98,7 @@ jobs: type=raw,value=${{ steps.clean_name.outputs.branch }} annotations: | org.opencontainers.image.authors="maintainers@gitea.io" - - uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 id: meta_rootless with: images: |- @@ -112,18 +112,18 @@ jobs: annotations: | org.opencontainers.image.authors="maintainers@gitea.io" - name: Login to Docker Hub - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GHCR using PAT - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: build regular docker image - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . platforms: linux/amd64,linux/arm64,linux/riscv64 @@ -133,7 +133,7 @@ jobs: cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful,mode=max - name: build rootless docker image - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . platforms: linux/amd64,linux/arm64,linux/riscv64 diff --git a/.github/workflows/release-tag-rc.yml b/.github/workflows/release-tag-rc.yml index b246d87b9ad..3e7655027c4 100644 --- a/.github/workflows/release-tag-rc.yml +++ b/.github/workflows/release-tag-rc.yml @@ -58,7 +58,7 @@ jobs: echo "Cleaned name is ${REF_NAME}" echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT" - name: configure aws - uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1 + uses: aws-actions/configure-aws-credentials@acca2b1b2070338fb9fd1ca27ecee81d687e58e5 # v6.1.2 with: aws-region: ${{ secrets.AWS_REGION }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} @@ -90,9 +90,9 @@ jobs: # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force - - uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 - - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + - uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 id: meta with: images: |- @@ -105,7 +105,7 @@ jobs: type=semver,pattern={{version}} annotations: | org.opencontainers.image.authors="maintainers@gitea.io" - - uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 id: meta_rootless with: images: |- @@ -121,18 +121,18 @@ jobs: annotations: | org.opencontainers.image.authors="maintainers@gitea.io" - name: Login to Docker Hub - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GHCR using PAT - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: build regular container image - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . platforms: linux/amd64,linux/arm64,linux/riscv64 @@ -140,7 +140,7 @@ jobs: tags: ${{ steps.meta.outputs.tags }} annotations: ${{ steps.meta.outputs.annotations }} - name: build rootless container image - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . platforms: linux/amd64,linux/arm64,linux/riscv64 diff --git a/.github/workflows/release-tag-version.yml b/.github/workflows/release-tag-version.yml index 9eb910c8e36..66a2984def3 100644 --- a/.github/workflows/release-tag-version.yml +++ b/.github/workflows/release-tag-version.yml @@ -61,7 +61,7 @@ jobs: echo "Cleaned name is ${REF_NAME}" echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT" - name: configure aws - uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1 + uses: aws-actions/configure-aws-credentials@acca2b1b2070338fb9fd1ca27ecee81d687e58e5 # v6.1.2 with: aws-region: ${{ secrets.AWS_REGION }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} @@ -93,9 +93,9 @@ jobs: # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force - - uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 - - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + - uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 id: meta with: images: |- @@ -112,7 +112,7 @@ jobs: type=semver,pattern={{major}}.{{minor}} annotations: | org.opencontainers.image.authors="maintainers@gitea.io" - - uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 id: meta_rootless with: images: |- @@ -133,18 +133,18 @@ jobs: annotations: | org.opencontainers.image.authors="maintainers@gitea.io" - name: Login to Docker Hub - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GHCR using PAT - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: build regular container image - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . platforms: linux/amd64,linux/arm64,linux/riscv64 @@ -152,7 +152,7 @@ jobs: tags: ${{ steps.meta.outputs.tags }} annotations: ${{ steps.meta.outputs.annotations }} - name: build rootless container image - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . platforms: linux/amd64,linux/arm64,linux/riscv64 From 79810ba2e37a5b5b7840a7737a877fc7f1ea7c38 Mon Sep 17 00:00:00 2001 From: puni9869 <80308335+puni9869@users.noreply.github.com> Date: Tue, 2 Jun 2026 12:38:23 +0530 Subject: [PATCH 05/40] fix: use committer time where ever possible as default (#37969) Fix https://github.com/go-gitea/gitea/issues/37857 --------- Co-authored-by: wxiaoguang --- MAINTAINERS | 2 +- routers/web/repo/wiki.go | 6 +++--- templates/repo/commit_page.tmpl | 3 +-- templates/repo/wiki/revision.tmpl | 4 ++-- templates/repo/wiki/view.tmpl | 4 ++-- tests/integration/repo_commits_test.go | 22 ++++++++++++++++++++++ 6 files changed, 31 insertions(+), 10 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 26192eff447..03ff6999f33 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -64,4 +64,4 @@ metiftikci (@metiftikci) Christopher Homberger (@ChristopherHX) Tobias Balle-Petersen (@tobiasbp) TheFox (@TheFox0x7) -Nicolas (@bircni) \ No newline at end of file +Nicolas (@bircni) diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index 4d9cd21c132..dcb0c25829f 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -496,7 +496,7 @@ func Wiki(ctx *context.Context) { ctx.ServerError("GetCommitByPath", err) return } - ctx.Data["Author"] = lastCommit.Author + ctx.Data["Committer"] = lastCommit.Committer ctx.HTML(http.StatusOK, tplWikiView) } @@ -528,7 +528,7 @@ func WikiRevision(ctx *context.Context) { ctx.ServerError("GetCommitByPath", err) return } - ctx.Data["Author"] = lastCommit.Author + ctx.Data["Committer"] = lastCommit.Committer ctx.HTML(http.StatusOK, tplWikiRevision) } @@ -587,7 +587,7 @@ func WikiPages(ctx *context.Context) { Name: displayName, SubURL: wiki_service.WebPathToURLPath(wikiName), GitEntryName: entry.Entry.Name(), - UpdatedUnix: timeutil.TimeStamp(entry.Commit.Author.When.Unix()), + UpdatedUnix: timeutil.TimeStamp(entry.Commit.Committer.When.Unix()), }) } ctx.Data["Pages"] = pages diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl index 975cd303ec4..9618eda7612 100644 --- a/templates/repo/commit_page.tmpl +++ b/templates/repo/commit_page.tmpl @@ -139,8 +139,7 @@ {{.Commit.Author.Name}} {{end}} - - {{DateUtils.TimeSince .Commit.Author.When}} + {{DateUtils.TimeSince .Commit.Committer.When}}
{{if or (ne .Commit.Committer.Name .Commit.Author.Name) (ne .Commit.Committer.Email .Commit.Author.Email)}} diff --git a/templates/repo/wiki/revision.tmpl b/templates/repo/wiki/revision.tmpl index c59047a6404..a9df43ea468 100644 --- a/templates/repo/wiki/revision.tmpl +++ b/templates/repo/wiki/revision.tmpl @@ -9,8 +9,8 @@
{{$title}}
- {{$timeSince := DateUtils.TimeSince .Author.When}} - {{ctx.Locale.Tr "repo.wiki.last_commit_info" .Author.Name $timeSince}} + {{$timeSince := DateUtils.TimeSince .Committer.When}} + {{ctx.Locale.Tr "repo.wiki.last_commit_info" .Committer.Name $timeSince}}
diff --git a/templates/repo/wiki/view.tmpl b/templates/repo/wiki/view.tmpl index 4c7ef364d29..967a8814c96 100644 --- a/templates/repo/wiki/view.tmpl +++ b/templates/repo/wiki/view.tmpl @@ -37,8 +37,8 @@
{{$title}}
- {{$timeSince := DateUtils.TimeSince .Author.When}} - {{ctx.Locale.Tr "repo.wiki.last_commit_info" .Author.Name $timeSince}} + {{$timeSince := DateUtils.TimeSince .Committer.When}} + {{ctx.Locale.Tr "repo.wiki.last_commit_info" .Committer.Name $timeSince}}
diff --git a/tests/integration/repo_commits_test.go b/tests/integration/repo_commits_test.go index ab7119769f8..9335ef70656 100644 --- a/tests/integration/repo_commits_test.go +++ b/tests/integration/repo_commits_test.go @@ -69,6 +69,28 @@ func TestRepoCommits(t *testing.T) { assert.Equal(t, "6543", strings.TrimSpace(authorElem.Text())) }) + t.Run("CommitPageUsesCommitterDate", func(t *testing.T) { + const ( + commitID = "5099b81332712fe655e34e8dd63574f503f61811" + expectedCommitterTime = "2017-08-06T19:56:13+02:00" + authorTime = "2017-08-06T19:55:01+02:00" + ) + + req := NewRequest(t, "GET", "/user2/repo16/commits/branch/master") + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + + var commitListTime string + doc.doc.Find("#commits-table tbody tr").EachWithBreak(func(_ int, row *goquery.Selection) bool { + if path.Base(row.Find(".commit-id-short").AttrOr("href", "")) != commitID { + return true + } + commitListTime = row.Find("td").Eq(3).Find("relative-time").AttrOr("datetime", "") + return false + }) + require.Equal(t, expectedCommitterTime, commitListTime) + }) + t.Run("LastCommitNonExistingCommiter", func(t *testing.T) { req := NewRequest(t, "GET", "/user2/repo1/src/branch/branch2") resp := session.MakeRequest(t, req, http.StatusOK) From fbaaac9c147fc3d40b5ac44dd6aaac28637a2d28 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 4 Jun 2026 00:12:02 +0800 Subject: [PATCH 06/40] fix: remove "no-transfrom" from the cache-control header (#37985) Cloudflare has officially removed the "auto-minify" feature https://community.cloudflare.com/t/655677, so we don't need such option anymore. Fix #34521 --- modules/httpcache/httpcache.go | 17 +++++------------ modules/httpcache/httpcache_test.go | 2 +- modules/httplib/serve.go | 5 ++--- routers/common/errpage.go | 2 +- services/context/api.go | 2 +- services/context/context.go | 2 +- 6 files changed, 11 insertions(+), 19 deletions(-) diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go index f55e13ce594..785363c29ca 100644 --- a/modules/httpcache/httpcache.go +++ b/modules/httpcache/httpcache.go @@ -15,9 +15,8 @@ import ( ) type CacheControlOptions struct { - IsPublic bool - MaxAge time.Duration - NoTransform bool + IsPublic bool + MaxAge time.Duration } // SetCacheControlInHeader sets suitable cache-control headers in the response @@ -38,25 +37,19 @@ func SetCacheControlInHeader(h http.Header, opts *CacheControlOptions) { directives = append(directives, "max-age=0", publicPrivate, "must-revalidate") h.Set("X-Gitea-Debug", fmt.Sprintf("RUN_MODE=%v, MaxAge=%s", setting.RunMode, opts.MaxAge)) } - - if opts.NoTransform { - directives = append(directives, "no-transform") - } h.Set("Cache-Control", strings.Join(directives, ", ")) } func CacheControlForPublicStatic() *CacheControlOptions { return &CacheControlOptions{ - IsPublic: true, - MaxAge: setting.StaticCacheTime, - NoTransform: true, + IsPublic: true, + MaxAge: setting.StaticCacheTime, } } func CacheControlForPrivateStatic() *CacheControlOptions { return &CacheControlOptions{ - MaxAge: setting.StaticCacheTime, - NoTransform: true, + MaxAge: setting.StaticCacheTime, } } diff --git a/modules/httpcache/httpcache_test.go b/modules/httpcache/httpcache_test.go index f9625981aaf..d715cac4a24 100644 --- a/modules/httpcache/httpcache_test.go +++ b/modules/httpcache/httpcache_test.go @@ -18,7 +18,7 @@ func TestHandleGenericETagCache(t *testing.T) { matchedEtag := `"matched-etag"` lastModifiedTime := new(time.Date(2021, time.January, 2, 15, 4, 5, 0, time.FixedZone("test-zone", 8*3600))) lastModified := lastModifiedTime.UTC().Format(http.TimeFormat) - cacheControl := "max-age=0, private, must-revalidate, no-transform" + cacheControl := "max-age=0, private, must-revalidate" type testCase struct { name string reqHeaders map[string]string diff --git a/modules/httplib/serve.go b/modules/httplib/serve.go index cf2e049cb7a..e2da9e85fa7 100644 --- a/modules/httplib/serve.go +++ b/modules/httplib/serve.go @@ -94,9 +94,8 @@ func ServeSetHeaders(w http.ResponseWriter, opts ServeHeaderOptions) { } httpcache.SetCacheControlInHeader(header, &httpcache.CacheControlOptions{ - IsPublic: opts.CacheIsPublic, - MaxAge: opts.CacheDuration, - NoTransform: true, + IsPublic: opts.CacheIsPublic, + MaxAge: opts.CacheDuration, }) if !opts.LastModified.IsZero() { diff --git a/routers/common/errpage.go b/routers/common/errpage.go index bb89070aaab..1426b05ef93 100644 --- a/routers/common/errpage.go +++ b/routers/common/errpage.go @@ -32,7 +32,7 @@ func renderServerErrorPage(w http.ResponseWriter, req *http.Request, respCode in } } - httpcache.SetCacheControlInHeader(w.Header(), &httpcache.CacheControlOptions{NoTransform: true}) + httpcache.SetCacheControlInHeader(w.Header(), &httpcache.CacheControlOptions{}) tmplCtx := context.NewTemplateContextForWeb(reqctx.FromContext(req.Context()), req, middleware.Locale(w, req)) w.WriteHeader(respCode) diff --git a/services/context/api.go b/services/context/api.go index 97a97ecc550..05b6490826a 100644 --- a/services/context/api.go +++ b/services/context/api.go @@ -242,7 +242,7 @@ func APIContexter() func(http.Handler) http.Handler { } } - httpcache.SetCacheControlInHeader(ctx.Resp.Header(), &httpcache.CacheControlOptions{NoTransform: true}) + httpcache.SetCacheControlInHeader(ctx.Resp.Header(), &httpcache.CacheControlOptions{}) next.ServeHTTP(ctx.Resp, ctx.Req) }) } diff --git a/services/context/context.go b/services/context/context.go index 2875bb46161..c1e438975f6 100644 --- a/services/context/context.go +++ b/services/context/context.go @@ -196,7 +196,7 @@ func Contexter() func(next http.Handler) http.Handler { } } - httpcache.SetCacheControlInHeader(ctx.Resp.Header(), &httpcache.CacheControlOptions{NoTransform: true}) + httpcache.SetCacheControlInHeader(ctx.Resp.Header(), &httpcache.CacheControlOptions{}) ctx.Data["SystemConfig"] = setting.Config() From 623bb81bb95ee5738197955850df308cc1f45528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20G=C3=B3ra?= <108656216+dawidgora@users.noreply.github.com> Date: Wed, 3 Jun 2026 18:30:30 +0200 Subject: [PATCH 07/40] fix(releases): generate notes for initial tag (#37697) Fixes https://github.com/go-gitea/gitea/issues/37286 Automatic release notes for the first release in a repository were empty when there was no previous tag. Before this change, the release notes generator used the tag name to build the changelog link, but reused that state for pull request collection. When `PreviousTag` was empty, the PR collection logic did not scan a useful commit range, so merged pull requests were omitted from the generated notes. This pull request fixes that by decoupling the internal PR collection range from the rendered changelog link: - when a previous tag exists, behavior stays unchanged - when no previous tag exists, release notes collect merged pull requests from the full reachable history up to the target tag - the displayed full changelog link for the first release still uses the existing `/commits/tag/{tag}` format Tests were updated to cover: - generating notes for a repository with no previous tags - including merged pull requests before the first tag - preserving existing behavior when a previous tag exists --- services/release/notes.go | 49 ++++++++++++++++++++++------- services/release/notes_test.go | 56 ++++++++++++++++++++++++++++------ 2 files changed, 84 insertions(+), 21 deletions(-) diff --git a/services/release/notes.go b/services/release/notes.go index b8baeb9620a..e62335c98b5 100644 --- a/services/release/notes.go +++ b/services/release/notes.go @@ -10,6 +10,7 @@ import ( "slices" "strings" + "gitea.dev/models/db" issues_model "gitea.dev/models/issues" repo_model "gitea.dev/models/repo" user_model "gitea.dev/models/user" @@ -32,18 +33,23 @@ func GenerateReleaseNotes(ctx context.Context, repo *repo_model.Repository, gitR return "", err } - if opts.PreviousTag == "" { - // no previous tag, usually due to there is no tag in the repo, use the same content as GitHub - content := fmt.Sprintf("**Full Changelog**: %s/commits/tag/%s\n", repo.HTMLURL(ctx), util.PathEscapeSegments(opts.TagName)) - return content, nil + isFirstRelease, err := isFirstRelease(ctx, repo.ID) + if err != nil { + return "", fmt.Errorf("isFirstRelease: %w", err) } - baseCommit, err := gitRepo.GetCommit(opts.PreviousTag) - if err != nil { + baseCommitID := "" + if opts.PreviousTag != "" { + baseCommit, err := gitRepo.GetCommit(opts.PreviousTag) + if err != nil { + return "", util.ErrorWrapTranslatable(util.ErrNotExist, "repo.release.generate_notes_tag_not_found", opts.TagName) + } + baseCommitID = baseCommit.ID.String() + } else if !isFirstRelease { return "", util.ErrorWrapTranslatable(util.ErrNotExist, "repo.release.generate_notes_tag_not_found", opts.TagName) } - commits, err := gitRepo.CommitsBetweenIDs(headCommit.ID.String(), baseCommit.ID.String()) + commits, err := gitRepo.CommitsBetweenIDs(headCommit.ID.String(), baseCommitID) if err != nil { return "", fmt.Errorf("CommitsBetweenIDs: %w", err) } @@ -58,10 +64,27 @@ func GenerateReleaseNotes(ctx context.Context, repo *repo_model.Repository, gitR return "", err } - content := buildReleaseNotesContent(ctx, repo, opts.TagName, opts.PreviousTag, prs, contributors, newContributors) + fullChangelogURL := "" + if isFirstRelease { + // Keep the first-release changelog link aligned with GitHub, while collecting PRs from full history. + fullChangelogURL = fmt.Sprintf("%s/commits/tag/%s", repo.HTMLURL(ctx), util.PathEscapeSegments(opts.TagName)) + } + + content := buildReleaseNotesContent(ctx, repo, opts.TagName, opts.PreviousTag, prs, contributors, newContributors, fullChangelogURL) return content, nil } +func isFirstRelease(ctx context.Context, repoID int64) (bool, error) { + count, err := db.Count[repo_model.Release](ctx, repo_model.FindReleasesOptions{ + RepoID: repoID, + IncludeDrafts: false, + }) + if err != nil { + return false, err + } + return count == 0, nil +} + func resolveHeadCommit(gitRepo *git.Repository, tagName, tagTarget string) (*git.Commit, error) { ref := tagName if !gitRepo.IsTagExist(tagName) { @@ -107,7 +130,7 @@ func collectPullRequestsFromCommits(ctx context.Context, repoID int64, commits [ return prs, nil } -func buildReleaseNotesContent(ctx context.Context, repo *repo_model.Repository, tagName, baseRef string, prs []*issues_model.PullRequest, contributors []*user_model.User, newContributors []*issues_model.PullRequest) string { +func buildReleaseNotesContent(ctx context.Context, repo *repo_model.Repository, tagName, baseRef string, prs []*issues_model.PullRequest, contributors []*user_model.User, newContributors []*issues_model.PullRequest, fullChangelogURL string) string { var builder strings.Builder builder.WriteString("## What's Changed\n") @@ -136,8 +159,12 @@ func buildReleaseNotesContent(ctx context.Context, repo *repo_model.Repository, } builder.WriteString("**Full Changelog**: ") - compareURL := fmt.Sprintf("%s/compare/%s...%s", repo.HTMLURL(ctx), util.PathEscapeSegments(baseRef), util.PathEscapeSegments(tagName)) - fmt.Fprintf(&builder, "[%s...%s](%s)", baseRef, tagName, compareURL) + if fullChangelogURL != "" { + builder.WriteString(fullChangelogURL) + } else { + compareURL := fmt.Sprintf("%s/compare/%s...%s", repo.HTMLURL(ctx), util.PathEscapeSegments(baseRef), util.PathEscapeSegments(tagName)) + fmt.Fprintf(&builder, "[%s...%s](%s)", baseRef, tagName, compareURL) + } builder.WriteByte('\n') return builder.String() } diff --git a/services/release/notes_test.go b/services/release/notes_test.go index 2922da424b5..3fb3b7c553a 100644 --- a/services/release/notes_test.go +++ b/services/release/notes_test.go @@ -21,13 +21,14 @@ import ( func TestGenerateReleaseNotes(t *testing.T) { unittest.PrepareTestEnv(t) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - gitRepo, err := gitrepo.OpenRepository(t.Context(), repo) - require.NoError(t, err) - t.Run("ChangeLogsWithPRs", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + gitRepo, err := gitrepo.OpenRepository(t.Context(), repo) + require.NoError(t, err) + t.Cleanup(func() { gitRepo.Close() }) + mergedCommit := "90c1019714259b24fb81711d4416ac0f18667dfa" - createMergedPullRequest(t, repo, mergedCommit, 5) + createMergedPullRequest(t, repo, mergedCommit, 5, "Release notes test pull request") content, err := GenerateReleaseNotes(t.Context(), repo, gitRepo, GenerateReleaseNotesOptions{ TagName: "v1.2.0", @@ -50,16 +51,51 @@ func TestGenerateReleaseNotes(t *testing.T) { }) t.Run("NoPreviousTag", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) + gitRepo, err := gitrepo.OpenRepository(t.Context(), repo) + require.NoError(t, err) + t.Cleanup(func() { gitRepo.Close() }) + + createMergedPullRequest(t, repo, "69554a64c1e6030f051e5c3f94bfbd773cd6a324", 5, "Initial tag PR 1") + createMergedPullRequest(t, repo, "27566bd5738fc8b4e3fef3c5e72cce608537bd95", 4, "Initial tag PR 2") + createMergedPullRequest(t, repo, "5099b81332712fe655e34e8dd63574f503f61811", 8, "Initial tag PR 3") + content, err := GenerateReleaseNotes(t.Context(), repo, gitRepo, GenerateReleaseNotesOptions{ - TagName: "v1.2.0", - TagTarget: "DefaultBranch", + TagName: "v0.1.0", + TagTarget: repo.DefaultBranch, }) require.NoError(t, err) - assert.Equal(t, "**Full Changelog**: https://try.gitea.io/user2/repo1/commits/tag/v1.2.0\n", content) + + assert.Contains(t, content, "## What's Changed\n") + assert.Contains(t, content, "* Initial tag PR 1 in [#") + assert.Contains(t, content, "* Initial tag PR 2 in [#") + assert.Contains(t, content, "* Initial tag PR 3 in [#") + assert.Contains(t, content, "\n## Contributors\n") + assert.Contains(t, content, "* @user5\n") + assert.Contains(t, content, "* @user4\n") + assert.Contains(t, content, "* @user8\n") + assert.Contains(t, content, "\n## New Contributors\n") + assert.Contains(t, content, "* @user5 made their first contribution in [#") + assert.Contains(t, content, "* @user4 made their first contribution in [#") + assert.Contains(t, content, "* @user8 made their first contribution in [#") + assert.Contains(t, content, "**Full Changelog**: https://try.gitea.io/user2/repo16/commits/tag/v0.1.0\n") + }) + + t.Run("EmptyPreviousTagWithExistingTags", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + gitRepo, err := gitrepo.OpenRepository(t.Context(), repo) + require.NoError(t, err) + t.Cleanup(func() { gitRepo.Close() }) + + _, err = GenerateReleaseNotes(t.Context(), repo, gitRepo, GenerateReleaseNotesOptions{ + TagName: "v1.2.0", + TagTarget: repo.DefaultBranch, + }) + require.Error(t, err) }) } -func createMergedPullRequest(t *testing.T, repo *repo_model.Repository, mergeCommit string, posterID int64) *issues_model.PullRequest { +func createMergedPullRequest(t *testing.T, repo *repo_model.Repository, mergeCommit string, posterID int64, title string) *issues_model.PullRequest { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: posterID}) issue := &issues_model.Issue{ @@ -67,7 +103,7 @@ func createMergedPullRequest(t *testing.T, repo *repo_model.Repository, mergeCom Repo: repo, Poster: user, PosterID: user.ID, - Title: "Release notes test pull request", + Title: title, Content: "content", } From 735e940a6148b7cbe0ee58ed1508856e84a82327 Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Wed, 3 Jun 2026 18:50:47 +0200 Subject: [PATCH 08/40] fix(oauth2): not respecting claims before second login (#37874) fixes defect where claims where only applies on login but not during account linking making only the second login take them into account fixes: https://github.com/go-gitea/gitea/issues/32566 --- routers/web/auth/oauth.go | 7 - routers/web/auth/oauth_signin_sync.go | 9 ++ tests/integration/auth_oauth2_test.go | 203 ++++++++++++++++++++++++-- 3 files changed, 196 insertions(+), 23 deletions(-) diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 7bed3523ed8..f7d9c3c34ac 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -188,10 +188,6 @@ func SignInOAuthCallback(ctx *context.Context) { source := authSource.Cfg.(*oauth2.Source) - isAdmin, isRestricted := getUserAdminAndRestrictedFromGroupClaims(source, &gothUser) - u.IsAdmin = isAdmin.ValueOrDefault(user_service.UpdateOptionField[bool]{FieldValue: false}).FieldValue - u.IsRestricted = isRestricted.ValueOrDefault(setting.Service.DefaultUserIsRestricted) - linkAccountData := &LinkAccountData{authSource.ID, gothUser} if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingDisabled { linkAccountData = nil @@ -373,9 +369,6 @@ func handleOAuth2SignIn(ctx *context.Context, authSource *auth.Source, u *user_m opts.IsActive = optional.Some(true) } - // Update GroupClaims - opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser) - if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval { if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil { ctx.ServerError("SyncGroupsToTeams", err) diff --git a/routers/web/auth/oauth_signin_sync.go b/routers/web/auth/oauth_signin_sync.go index a939a0e71ec..f5ec66e006a 100644 --- a/routers/web/auth/oauth_signin_sync.go +++ b/routers/web/auth/oauth_signin_sync.go @@ -14,6 +14,7 @@ import ( asymkey_service "gitea.dev/services/asymkey" "gitea.dev/services/auth/source/oauth2" "gitea.dev/services/context" + user_service "gitea.dev/services/user" "github.com/markbates/goth" ) @@ -50,6 +51,14 @@ func oauth2SignInSync(ctx *context.Context, authSourceID int64, u *user_model.Us } } + // sync user flags (admin/restricted) + isAdmin, isRestricted := getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser) + if isAdmin.Has() || isRestricted.Has() { + if err = user_service.UpdateUser(ctx, u, &user_service.UpdateOptions{IsAdmin: isAdmin, IsRestricted: isRestricted}); err != nil { + log.Error("Unable to sync OAuth2 user admin or restricted status %s: %v", gothUser.Provider, err) + } + } + err = oauth2UpdateSSHPubIfNeed(ctx, authSource, &gothUser, u) if err != nil { log.Error("Unable to sync OAuth2 SSH public key %s: %v", gothUser.Provider, err) diff --git a/tests/integration/auth_oauth2_test.go b/tests/integration/auth_oauth2_test.go index bcd49811494..3bb3d56e7bd 100644 --- a/tests/integration/auth_oauth2_test.go +++ b/tests/integration/auth_oauth2_test.go @@ -54,7 +54,7 @@ func TestMigrateAzureADV2ToOIDC(t *testing.T) { ) // The fake OIDC server issues tokens containing both sub and oid claims, mirroring what Azure AD v2.0 returns. - srv := newFakeOIDCServer(t, subValue, oidValue) + srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: subValue, OID: oidValue}) // --- Step 1: Establish the legacy Azure AD V2 state --- // Create an azureadv2 auth source. In production this would have been the source used before the migration. @@ -138,7 +138,7 @@ func TestOIDCIgnoresStaleExternalLoginLinks(t *testing.T) { setup := func(t *testing.T, sourceName, sub, userName, email string) (*auth_model.Source, *user_model.User) { t.Helper() - srv := newFakeOIDCServerWithProfile(t, sub, sub+"-oid", email, "OIDC Test User") + srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: sub, OID: sub + "-oid", Email: email, Name: "OIDC Test User"}) addOAuth2Source(t, sourceName, oauth2.Source{ Provider: "openidConnect", ClientID: "test-client-id", @@ -191,14 +191,27 @@ func TestOIDCIgnoresStaleExternalLoginLinks(t *testing.T) { }) } -// newFakeOIDCServer starts an httptest.Server that implements the minimum OIDC endpoints needed to complete a sign-in flow: -func newFakeOIDCServer(t *testing.T, sub, oid string) *httptest.Server { - return newFakeOIDCServerWithProfile(t, sub, oid, sub+"@example.com", "OIDC Test User") +// FakeOIDCConfig holds configuration for the fake OIDC server used in tests. +type FakeOIDCConfig struct { + Sub string + OID string + Email string + Name string + Groups []string } -func newFakeOIDCServerWithProfile(t *testing.T, sub, oid, email, name string) *httptest.Server { +// newFakeOIDCServer starts a httptest.Server that implements the minimum OIDC endpoints needed to complete a sign-in flow +func newFakeOIDCServer(t *testing.T, cfg FakeOIDCConfig) *httptest.Server { t.Helper() + // Set defaults for backward compatibility with existing tests + if cfg.Email == "" { + cfg.Email = cfg.Sub + "@example.com" + } + if cfg.Name == "" { + cfg.Name = "OIDC Test User" + } + var srv *httptest.Server srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -212,11 +225,18 @@ func newFakeOIDCServerWithProfile(t *testing.T, sub, oid, email, name string) *h }) case "/token": // returns an ID token with both "sub" and "oid" claims so tests can verify which one ends up as ExternalID claims := map[string]any{ - "iss": srv.URL, - "aud": "test-client-id", - "exp": time.Now().Add(time.Hour).Unix(), - "sub": sub, - "oid": oid, + "iss": srv.URL, + "aud": "test-client-id", + "exp": time.Now().Add(time.Hour).Unix(), + "sub": cfg.Sub, + "email": cfg.Email, + "name": cfg.Name, + } + if cfg.OID != "" { + claims["oid"] = cfg.OID + } + if cfg.Groups != nil { + claims["groups"] = cfg.Groups } payload, _ := json.Marshal(claims) header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"none"}`)) @@ -232,11 +252,18 @@ func newFakeOIDCServerWithProfile(t *testing.T, sub, oid, email, name string) *h }) case "/userinfo": // sub MUST match the id_token sub; goth rejects mismatches. - _ = json.NewEncoder(w).Encode(map[string]any{ - "sub": sub, - "email": email, - "name": name, - }) + response := map[string]any{ + "sub": cfg.Sub, + "email": cfg.Email, + "name": cfg.Name, + } + if cfg.OID != "" { + response["oid"] = cfg.OID + } + if cfg.Groups != nil { + response["groups"] = cfg.Groups + } + _ = json.NewEncoder(w).Encode(response) default: http.NotFound(w, r) } @@ -264,3 +291,147 @@ func doOIDCSignIn(t *testing.T, sourceName string) { callbackURL := fmt.Sprintf("/user/oauth2/%s/callback?code=test-code&state=%s", sourceName, url.QueryEscape(state)) session.MakeRequest(t, NewRequest(t, "GET", callbackURL), http.StatusSeeOther) } + +// newOIDCSource is a helper function to create a configured OAuth2 source for testing +func newOIDCSource(srv *httptest.Server, withAdmin, withRestricted bool) oauth2.Source { + src := oauth2.Source{ + Provider: "openidConnect", + ClientID: "test-client-id", + ClientSecret: "test-client-secret", + OpenIDConnectAutoDiscoveryURL: srv.URL + "/.well-known/openid-configuration", + GroupClaimName: "groups", + } + if withAdmin { + src.AdminGroup = "admins" + } + if withRestricted { + src.RestrictedGroup = "restricted-users" + } + return src +} + +// TestOAuth2GroupClaimsAppliedOnFirstLogin verifies that group claims from OAuth2/OIDC +// are correctly applied to newly created users on the first login +func TestOAuth2GroupClaimsAppliedOnFirstLogin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + // Enable auto-registration to ensure first login creates user with group claims + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() + // Use sub claim as username for deterministic user naming + defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameUserid)() + + tt := []struct { + Name string + IsAdmin bool + IsRestricted bool + SourceName string + }{ + { + Name: "user in both admin and restricted groups", + IsAdmin: true, + IsRestricted: true, + SourceName: "test-group-claims", + }, + { + Name: "no groups", + IsAdmin: false, + IsRestricted: false, + SourceName: "test-no-groups", + }, + } + for _, tc := range tt { + t.Run(tc.Name, func(t *testing.T) { + // Set up OIDC server with group claims + srv := newFakeOIDCServer(t, FakeOIDCConfig{ + Sub: tc.SourceName, + Email: tc.SourceName + "@example.com", + Name: "Test User", + Groups: []string{"admins", "restricted-users"}, + }) + + // Ensure it's the first login so no user in database + unittest.AssertNotExistsBean(t, &user_model.User{Name: tc.SourceName}) + + addOAuth2Source(t, tc.SourceName, newOIDCSource(srv, tc.IsAdmin, tc.IsRestricted)) + + doOIDCSignIn(t, tc.SourceName) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: tc.SourceName}) + assert.Equal(t, tc.IsAdmin, user.IsAdmin) + assert.Equal(t, tc.IsRestricted, user.IsRestricted) + assert.Equal(t, auth_model.OAuth2, user.LoginType) + }) + } +} + +// TestOAuth2GroupClaimsManualLinking tests that group claims are applied correctly +// when a user goes through the manual linking flow (auto-registration disabled). +func TestOAuth2GroupClaimsManualLinking(t *testing.T) { + defer tests.PrepareTestEnv(t)() + // Disable auto-registration to force manual linking flow + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, false)() + defer test.MockVariableValue(&setting.Service.AllowOnlyInternalRegistration, false)() + + tt := []struct { + Name string + IsAdmin bool + IsRestricted bool + SourceName string + }{ + { + Name: "user in both admin and restricted groups", + IsAdmin: true, + IsRestricted: true, + SourceName: "test-group-claims-manual-linking", + }, + { + Name: "no groups", + IsAdmin: false, + IsRestricted: false, + SourceName: "test-no-groups-manual-linking", + }, + } + + for _, tc := range tt { + t.Run(tc.Name, func(t *testing.T) { + srv := newFakeOIDCServer(t, FakeOIDCConfig{ + Sub: tc.SourceName, + Email: tc.SourceName + "@example.com", + Name: "Manual User", + Groups: []string{"admins", "restricted-users"}, + }) + addOAuth2Source(t, tc.SourceName, newOIDCSource(srv, tc.IsAdmin, tc.IsRestricted)) + unittest.AssertNotExistsBean(t, &user_model.User{Name: tc.SourceName}) + session := emptyTestSession(t) + resp := session.MakeRequest(t, NewRequest(t, "GET", "/user/oauth2/"+tc.SourceName), http.StatusTemporaryRedirect) + + location := resp.Header().Get("Location") + u, err := url.Parse(location) + require.NoError(t, err) + state := u.Query().Get("state") + require.NotEmpty(t, state, "redirect to OIDC provider must include state") + + callbackURL := fmt.Sprintf("/user/oauth2/%s/callback?code=test-code&state=%s", tc.SourceName, url.QueryEscape(state)) + session.MakeRequest(t, NewRequest(t, "GET", callbackURL), http.StatusSeeOther) + + // Submit the form to create a new account + linkAccountResp := session.MakeRequest(t, NewRequest(t, "GET", "/user/link_account"), http.StatusOK) + // Verify we're on the link account page + assert.Contains(t, linkAccountResp.Body.String(), "link_account") + + // Use NewRequestWithValues to POST form data (no CSRF needed in tests) + // Field names are lowercase in HTML forms: user_name, email, password, retype + req := NewRequestWithValues(t, "POST", "/user/link_account_signup", map[string]string{ + "user_name": tc.SourceName, + "email": tc.SourceName + "@example.com", + "password": "", // AllowOnlyExternalRegistration means no password needed + "retype": "", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: tc.SourceName}) + assert.Equal(t, tc.IsAdmin, user.IsAdmin) + assert.Equal(t, tc.IsRestricted, user.IsRestricted) + assert.Equal(t, auth_model.OAuth2, user.LoginType) + }) + } +} From b2748d7654db6ef7c423585b1bb1581d19d42848 Mon Sep 17 00:00:00 2001 From: Thomas Sayen <69324626+Chi-Iroh@users.noreply.github.com> Date: Wed, 3 Jun 2026 19:40:38 +0200 Subject: [PATCH 09/40] feat(ui): add "follow rename" to file commit history list (#34994) Fix #28253 --------- Co-authored-by: wxiaoguang --- modules/git/repo_commit.go | 29 ++++++++++++------ modules/git/repo_commit_test.go | 47 +++++++++++++++++++++++++++-- modules/paginator/paginator.go | 17 +++++------ options/locale/locale_en-US.json | 1 + routers/api/v1/repo/commits.go | 2 +- routers/api/v1/repo/wiki.go | 2 +- routers/web/feed/file.go | 2 +- routers/web/repo/commit.go | 47 +++++++++++++++++++---------- routers/web/repo/wiki.go | 2 +- routers/web/user/home.go | 11 ++++--- routers/web/user/profile.go | 3 +- services/context/pagination.go | 4 +-- services/context/pagination_test.go | 20 ++++++++++++ templates/repo/commits_table.tmpl | 27 +++++++++++------ web_src/css/repo.css | 24 --------------- web_src/js/features/repo-commit.ts | 10 ++++++ web_src/js/index.ts | 3 +- 17 files changed, 169 insertions(+), 82 deletions(-) diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index cbe5053346b..acf2a13b0e1 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -222,17 +222,20 @@ type CommitsByFileAndRangeOptions struct { Page int Since string Until string + + // when using FollowRename, there is no quick way to know the total count, so use hasMore to indicate if there are more commits to load + FollowRename bool } // CommitsByFileAndRange return the commits according revision file and the page -func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) ([]*Commit, error) { - gitCmd := gitcmd.NewCommand("rev-list"). - AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize). +func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) (commits []*Commit, hasMore bool, _ error) { + limit := setting.Git.CommitsRangeSize + gitCmd := gitcmd.NewCommand("--no-pager", "log"). + AddArguments("--pretty=tformat:%H"). + AddOptionFormat("--max-count=%d", limit+1). AddOptionFormat("--skip=%d", (opts.Page-1)*setting.Git.CommitsRangeSize) - gitCmd.AddDynamicArguments(opts.Revision) - - if opts.Not != "" { - gitCmd.AddOptionValues("--not", opts.Not) + if opts.FollowRename { + gitCmd.AddArguments("--follow") } if opts.Since != "" { gitCmd.AddOptionFormat("--since=%s", opts.Since) @@ -240,9 +243,12 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) if opts.Until != "" { gitCmd.AddOptionFormat("--until=%s", opts.Until) } + gitCmd.AddDynamicArguments(opts.Revision) + if opts.Not != "" { + gitCmd.AddOptionValues("--not", opts.Not) + } gitCmd.AddDashesAndList(opts.File) - var commits []*Commit stdoutReader, stdoutReaderClose := gitCmd.MakeStdoutPipe() defer stdoutReaderClose() err := gitCmd.WithDir(repo.Path). @@ -274,7 +280,12 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) } }). RunWithStderr(repo.Ctx) - return commits, err + + hasMore = len(commits) > limit + if hasMore { + commits = commits[:limit] + } + return commits, hasMore, err } // FilesCountBetween return the number of files changed between two commits diff --git a/modules/git/repo_commit_test.go b/modules/git/repo_commit_test.go index 517decf0ca3..aecba042508 100644 --- a/modules/git/repo_commit_test.go +++ b/modules/git/repo_commit_test.go @@ -6,8 +6,10 @@ package git import ( "os" "path/filepath" + "strings" "testing" + "gitea.dev/modules/git/gitcmd" "gitea.dev/modules/setting" "gitea.dev/modules/test" @@ -140,11 +142,52 @@ func TestCommitsByFileAndRange(t *testing.T) { defer bareRepo1.Close() // "foo" has 3 commits in "master" branch - commits, err := bareRepo1.CommitsByFileAndRange(CommitsByFileAndRangeOptions{Revision: "master", File: "foo", Page: 1}) + commits, hasMore, err := bareRepo1.CommitsByFileAndRange(CommitsByFileAndRangeOptions{Revision: "master", File: "foo", Page: 1}) require.NoError(t, err) + assert.True(t, hasMore) assert.Len(t, commits, 2) - commits, err = bareRepo1.CommitsByFileAndRange(CommitsByFileAndRangeOptions{Revision: "master", File: "foo", Page: 2}) + commits, hasMore, err = bareRepo1.CommitsByFileAndRange(CommitsByFileAndRangeOptions{Revision: "master", File: "foo", Page: 2}) require.NoError(t, err) assert.Len(t, commits, 1) + assert.False(t, hasMore) + + repoFollowRenameDir := filepath.Join(t.TempDir(), "repo.git") + require.NoError(t, gitcmd.NewCommand("init").AddDynamicArguments(repoFollowRenameDir).Run(t.Context())) + _, _, runErr := gitcmd.NewCommand("fast-import").WithDir(repoFollowRenameDir).WithStdinBytes([]byte(strings.TrimSpace(` +blob +mark :1 +data 0 + +reset refs/heads/master +commit refs/heads/master +mark :2 +author Chi-Iroh 1778660718 +0200 +committer Chi-Iroh 1778660718 +0200 +data 10 +Add a.txt +M 100644 :1 a.txt + +commit refs/heads/master +mark :3 +author Chi-Iroh 1778660741 +0200 +committer Chi-Iroh 1778660741 +0200 +data 22 +Rename a.txt to b.txt +from :2 +D a.txt +M 100644 :1 b.txt + `))).RunStdString(t.Context()) + require.NoError(t, runErr) + + repoFollowRename, err := OpenRepository(t.Context(), repoFollowRenameDir) + require.NoError(t, err) + defer repoFollowRename.Close() + + commits, _, err = repoFollowRename.CommitsByFileAndRange(CommitsByFileAndRangeOptions{Revision: "master", File: "b.txt", Page: 1}) + require.NoError(t, err) + assert.Len(t, commits, 1) + commits, _, err = repoFollowRename.CommitsByFileAndRange(CommitsByFileAndRangeOptions{Revision: "master", File: "b.txt", Page: 1, FollowRename: true}) + require.NoError(t, err) + assert.Len(t, commits, 2) } diff --git a/modules/paginator/paginator.go b/modules/paginator/paginator.go index 942bf7117b6..ee06d70dca5 100644 --- a/modules/paginator/paginator.go +++ b/modules/paginator/paginator.go @@ -37,10 +37,11 @@ type Paginator struct { total int // total rows count, -1 means unknown totalPages int // total pages count, -1 means unknown current int // current page number - curRows int // current page rows count pagingNum int // how many rows in one page numPages int // how many pages to show on the UI + + hasNext *bool // used for total=-1 ("unlimited paging") } // New initialize a new pagination calculation and returns a Paginator as result. @@ -60,15 +61,13 @@ func New(total, pagingNum, current, numPages int) *Paginator { } } -func (p *Paginator) SetCurRows(rows int) { +func (p *Paginator) SetUnlimitedPaging(curRows int, hasNext bool) { // For "unlimited paging", we need to know the rows of current page to determine if there is a next page. - // There is still an edge case: when curRows==pagingNum, then the "next page" will be an empty page. - // Ideally we should query one more row to determine if there is really a next page, but it's impossible in current framework. - p.curRows = rows - if p.total == -1 && p.current == 1 && !p.HasNext() { + p.hasNext = &hasNext + if p.total == -1 && p.current == 1 && !hasNext { // if there is only one page for the "unlimited paging", set total rows/pages count // then the tmpl could decide to hide the nav bar. - p.total = rows + p.total = curRows p.totalPages = util.Iif(p.total == 0, 0, 1) } } @@ -92,8 +91,8 @@ func (p *Paginator) Previous() int { // HasNext returns true if there is a next page relative to current page. func (p *Paginator) HasNext() bool { - if p.total == -1 { - return p.curRows >= p.pagingNum + if p.hasNext != nil { + return *p.hasNext } return p.current*p.pagingNum < p.total } diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index fdef5813580..51a97977421 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -1321,6 +1321,7 @@ "repo.editor.fork_branch_exists": "Branch \"%s\" already exists in your fork. Please choose a new branch name.", "repo.commits.desc": "Browse source code change history.", "repo.commits.commits": "Commits", + "repo.commits.history_enable_follow_renames": "Include renames", "repo.commits.no_commits": "No commits in common. \"%s\" and \"%s\" have entirely different histories.", "repo.commits.nothing_to_compare": "There are no differences to show.", "repo.commits.search.tooltip": "You can prefix keywords with \"author:\", \"committer:\", \"after:\", or \"before:\", e.g. \"revert author:Alice before:2019-01-13\".", diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go index 0e66bae4304..620ee037006 100644 --- a/routers/api/v1/repo/commits.go +++ b/routers/api/v1/repo/commits.go @@ -262,7 +262,7 @@ func GetAllCommits(ctx *context.APIContext) { return } - commits, err = ctx.Repo.GitRepo.CommitsByFileAndRange( + commits, _, err = ctx.Repo.GitRepo.CommitsByFileAndRange( git.CommitsByFileAndRangeOptions{ Revision: sha, File: path, diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go index e6fdd6afc60..16471044820 100644 --- a/routers/api/v1/repo/wiki.go +++ b/routers/api/v1/repo/wiki.go @@ -435,7 +435,7 @@ func ListPageRevisions(ctx *context.APIContext) { page := max(ctx.FormInt("page"), 1) // get Commit Count - commitsHistory, err := wikiRepo.CommitsByFileAndRange( + commitsHistory, _, err := wikiRepo.CommitsByFileAndRange( git.CommitsByFileAndRangeOptions{ Revision: ctx.Repo.Repository.DefaultWikiBranch, File: pageFilename, diff --git a/routers/web/feed/file.go b/routers/web/feed/file.go index 122d219b6ca..f1155219286 100644 --- a/routers/web/feed/file.go +++ b/routers/web/feed/file.go @@ -20,7 +20,7 @@ func ShowFileFeed(ctx *context.Context, repo *repo.Repository, formatType string if len(fileName) == 0 { return } - commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange( + commits, _, err := ctx.Repo.GitRepo.CommitsByFileAndRange( git.CommitsByFileAndRangeOptions{ Revision: ctx.Repo.RefFullName.ShortName(), // FIXME: legacy code used ShortName File: fileName, diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 0760cb4eeda..7cb2f82a903 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -214,37 +214,52 @@ func FileHistory(ctx *context.Context) { return } - commitsCount, err := gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository, ctx.Repo.RefFullName.ShortName(), ctx.Repo.TreePath) - if err != nil { - ctx.ServerError("FileCommitsCount", err) - return - } else if commitsCount == 0 { - ctx.NotFound(nil) - return - } + followRename := ctx.FormBool("follow-rename") + ctx.Data["ShowFollowRename"] = true + ctx.Data["FollowRenameChecked"] = followRename page := max(ctx.FormInt("page"), 1) - - commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange( + commits, hasMore, err := ctx.Repo.GitRepo.CommitsByFileAndRange( git.CommitsByFileAndRangeOptions{ - Revision: ctx.Repo.RefFullName.ShortName(), // FIXME: legacy code used ShortName - File: ctx.Repo.TreePath, - Page: page, + Revision: ctx.Repo.RefFullName.ShortName(), // FIXME: legacy code used ShortName + File: ctx.Repo.TreePath, + Page: page, + FollowRename: followRename, }) if err != nil { ctx.ServerError("CommitsByFileAndRange", err) return } + + var commitsCount int64 + if followRename { + // there is no quick method to know the total count when "follow rename" + commitsCount = -1 + } else { + commitsCount, err = gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository, ctx.Repo.RefFullName.ShortName(), ctx.Repo.TreePath) + if err != nil { + ctx.ServerError("FileCommitsCount", err) + return + } + } + + if len(commits) == 0 { + ctx.NotFound(nil) + return + } + + ctx.Data["FileTreePath"] = ctx.Repo.TreePath + ctx.Data["CommitCount"] = commitsCount ctx.Data["Commits"], err = processGitCommits(ctx, commits) if err != nil { ctx.ServerError("processGitCommits", err) return } - ctx.Data["FileTreePath"] = ctx.Repo.TreePath - ctx.Data["CommitCount"] = commitsCount - pager := context.NewPagination(commitsCount, setting.Git.CommitsRangeSize, page, 5) + if commitsCount == -1 { + pager.WithUnlimitedPaging(len(commits), hasMore) + } pager.AddParamFromRequest(ctx.Req) ctx.Data["Page"] = pager ctx.HTML(http.StatusOK, tplCommits) diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index dcb0c25829f..e0881dc9f1d 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -351,7 +351,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) page := max(ctx.FormInt("page"), 1) // get Commit Count - commitsHistory, err := wikiGitRepo.CommitsByFileAndRange( + commitsHistory, _, err := wikiGitRepo.CommitsByFileAndRange( git.CommitsByFileAndRangeOptions{ Revision: ctx.Repo.Repository.DefaultWikiBranch, File: pageFilename, diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 3573a132414..6951be45ef3 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -111,6 +111,7 @@ func Dashboard(ctx *context.Context) { prepareHeatmapURL(ctx) + pageSize := setting.UI.User.RepoPagingNum feeds, count, err := feed_service.GetFeedsForDashboard(ctx, activities_model.GetFeedsOptions{ RequestedUser: ctxUser, RequestedTeam: ctx.Org.Team, @@ -119,17 +120,17 @@ func Dashboard(ctx *context.Context) { OnlyPerformedBy: false, IncludeDeleted: false, Date: ctx.FormString("date"), - ListOptions: db.ListOptions{ - Page: page, - PageSize: setting.UI.FeedPagingNum, - }, + ListOptions: db.ListOptions{Page: page, PageSize: pageSize}, }) if err != nil { ctx.ServerError("GetFeeds", err) return } - pager := context.NewPagination(count, setting.UI.FeedPagingNum, page, 5).WithCurRows(len(feeds)) + // FIXME: UNLIMITE-PAGING-ONE-MORE-ROW: here is still an edge case: when curRows==pagingNum, then the "next page" will be an empty page. + // Ideally we should query one more row to determine if there is really a next page, but it's impossible in current framework. + pager := context.NewPagination(count, pageSize, page, 5).WithUnlimitedPaging(len(feeds), len(feeds) == pageSize) + pager.AddParamFromRequest(ctx.Req) ctx.Data["Page"] = pager ctx.Data["Feeds"] = feeds diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index dfe9a12bc2f..654bd1a5f08 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -312,7 +312,8 @@ func prepareUserProfileTabData(ctx *context.Context, profileDbRepo *repo_model.R pager := context.NewPagination(total, pagingNum, page, 5) if tab == "activity" { - pager.WithCurRows(curRows) + // FIXME: UNLIMITE-PAGING-ONE-MORE-ROW: see another comment + pager.WithUnlimitedPaging(curRows, curRows == pagingNum) } pager.AddParamFromRequest(ctx.Req) ctx.Data["Page"] = pager diff --git a/services/context/pagination.go b/services/context/pagination.go index a6e83df6a15..ef0f44ab37c 100644 --- a/services/context/pagination.go +++ b/services/context/pagination.go @@ -33,8 +33,8 @@ func NewPagination(total int64, pagingNum, current, numPages int) *Pagination { return p } -func (p *Pagination) WithCurRows(n int) *Pagination { - p.Paginater.SetCurRows(n) +func (p *Pagination) WithUnlimitedPaging(curRows int, hasNext bool) *Pagination { + p.Paginater.SetUnlimitedPaging(curRows, hasNext) return p } diff --git a/services/context/pagination_test.go b/services/context/pagination_test.go index 0ddef26ca3d..1b15fa7b81d 100644 --- a/services/context/pagination_test.go +++ b/services/context/pagination_test.go @@ -32,4 +32,24 @@ func TestPagination(t *testing.T) { params.Del("foo") v, _ = url.ParseQuery(string(p.GetParams())) assert.Equal(t, params, v) + + p = NewPagination(-1, 1, 1, 1) + p.WithUnlimitedPaging(0, false) + assert.Zero(t, p.Paginater.TotalPages()) + assert.False(t, p.Paginater.HasNext()) + + p = NewPagination(-1, 1, 1, 1) + p.WithUnlimitedPaging(10, false) + assert.Equal(t, 1, p.Paginater.TotalPages()) // first page, no next, so it should know that the total page number is 1 + assert.False(t, p.Paginater.HasNext()) + + p = NewPagination(-1, 1, 2, 1) + p.WithUnlimitedPaging(10, false) + assert.Equal(t, -1, p.Paginater.TotalPages()) + assert.False(t, p.Paginater.HasNext()) + + p = NewPagination(-1, 1, 1, 1) + p.WithUnlimitedPaging(10, true) + assert.Equal(t, -1, p.Paginater.TotalPages()) + assert.True(t, p.Paginater.HasNext()) } diff --git a/templates/repo/commits_table.tmpl b/templates/repo/commits_table.tmpl index 56a4867ff4b..59e87d10c03 100644 --- a/templates/repo/commits_table.tmpl +++ b/templates/repo/commits_table.tmpl @@ -1,21 +1,30 @@ -

-
- {{if or .PageIsCommits (gt .CommitCount 0)}} - {{.CommitCount}} {{ctx.Locale.Tr "repo.commits.commits"}} +
+
+ {{if .Commits}} + {{if gt .CommitCount 0}}{{.CommitCount}}{{end}}{{/* CommitCount is -1 when "follow rename" */}} + {{ctx.Locale.Tr "repo.commits.commits"}} {{else if .IsNothingToCompare}} {{ctx.Locale.Tr "repo.commits.nothing_to_compare"}} {{else}} {{ctx.Locale.Tr "repo.commits.no_commits" $.BaseBranch $.HeadBranch}} {{end}}
- {{if .IsDiffCompare}} -
+
+ {{if .ShowFollowRename}} +
+ + +
+ {{end}} + {{if .IsDiffCompare}} + - {{end}} -

+ {{end}} + + {{if .PageIsCommits}}
@@ -29,7 +38,7 @@
{{end}} -{{if and .Commits (gt .CommitCount 0)}} +{{if .Commits}} {{template "repo/commits_list" .}} {{end}} diff --git a/web_src/css/repo.css b/web_src/css/repo.css index b47e03b8ce4..b7cb5e1dcd1 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -1816,14 +1816,6 @@ tbody.commit-list { box-shadow: 0 0.5rem 1rem var(--color-shadow) !important; } -.commits-table .commits-table-right form { - display: flex; - align-items: center; - gap: 0.75em; - justify-content: center; - flex-wrap: wrap; -} - @media (max-width: 767.98px) { .repository.view.issue .comment-list, .repository.view.issue .comment-list .timeline-item { @@ -1848,22 +1840,6 @@ tbody.commit-list { flex-basis: auto !important; margin-bottom: 0.5rem !important; } - .commits-table { - flex-direction: column; - } - .commits-table .commits-table-left { - align-items: initial !important; - margin-bottom: 6px; - } - .commits-table .commits-table-right form > div:nth-child(1) { - order: 1; /* the "commit search" input */ - } - .commits-table .commits-table-right form > div:nth-child(2) { - order: 3; /* the "search all" checkbox */ - } - .commits-table .commits-table-right form > button:nth-child(3) { - order: 2; /* the "search" button */ - } .commit-table { overflow-x: auto; } diff --git a/web_src/js/features/repo-commit.ts b/web_src/js/features/repo-commit.ts index 16f38993749..17130427a62 100644 --- a/web_src/js/features/repo-commit.ts +++ b/web_src/js/features/repo-commit.ts @@ -24,3 +24,13 @@ export function initCommitStatuses() { }); }); } + +export function initCommitFileHistoryFollowRename() { + registerGlobalInitFunc('initCommitHistoryFollowRename', (el: HTMLInputElement) => { + el.addEventListener('change', () => { + const url = new URL(window.location.toString()); + url.searchParams.set('follow-rename', `${el.checked}`); + window.location.assign(url.toString()); + }); + }); +} diff --git a/web_src/js/index.ts b/web_src/js/index.ts index 094caefb7e0..a2994d6912d 100644 --- a/web_src/js/index.ts +++ b/web_src/js/index.ts @@ -21,7 +21,7 @@ import {initMarkupContent} from './markup/content.ts'; import {initRepoFileView} from './features/file-view.ts'; import {initUserExternalLogins, initUserCheckAppUrl} from './features/user-auth.ts'; import {initRepoPullRequestReview, initRepoIssueFilterItemLabel} from './features/repo-issue.ts'; -import {initRepoEllipsisButton, initCommitStatuses} from './features/repo-commit.ts'; +import {initRepoEllipsisButton, initCommitStatuses, initCommitFileHistoryFollowRename} from './features/repo-commit.ts'; import {initRepoTopicBar} from './features/repo-home.ts'; import {initAdminCommon} from './features/admin/common.ts'; import {initRepoCodeView} from './features/repo-code.ts'; @@ -123,6 +123,7 @@ const initPerformanceTracer = callInitFunctions([ initRepoCodeView, initBranchSelectorTabs, initRepoEllipsisButton, + initCommitFileHistoryFollowRename, initRepoDiffCommitBranchesAndTags, initRepoEditor, initRepoGraphGit, From 792fa5eeba4de5f54b0c31f6b0a869ba2c911396 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Thu, 4 Jun 2026 04:51:48 +0530 Subject: [PATCH 10/40] feat(api): add q parameter to list branches API for server-side filtering (#37982) The GET /repos/{owner}/{repo}/branches endpoint currently has no way to filter branches by name server-side, forcing API consumers to paginate through all branches and filter client-side. The UI already supports branch search (added in [#27055](https://github.com/go-gitea/gitea/pull/27055)). The underlying DB layer has a Keyword field on FindBranchOptions in models/git/branch_list.go that does a LIKE %keyword% SQL filter, it just wasn't wired up to the API handler. This PR exposes a ?q= query parameter on the endpoint that maps to FindBranchOptions.Keyword. Example: ```GET /repos/owner/repo/branches?q=feature ``` Closes #37981 --------- Co-authored-by: wxiaoguang --- routers/api/v1/repo/branch.go | 6 ++++++ templates/swagger/v1_json.tmpl | 6 ++++++ templates/swagger/v1_openapi3_json.tmpl | 8 ++++++++ tests/integration/api_repo_branch_test.go | 17 +++++++++++++++++ tests/integration/integration_test.go | 4 ++++ 5 files changed, 41 insertions(+) diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index 629945398d8..3afdf9694d1 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -306,6 +306,10 @@ func ListBranches(ctx *context.APIContext) { // in: query // description: page size of results // type: integer + // - name: q + // in: query + // description: branch name substring to filter by + // type: string // responses: // "200": // "$ref": "#/responses/BranchList" @@ -314,6 +318,7 @@ func ListBranches(ctx *context.APIContext) { var apiBranches []*api.Branch listOptions := utils.GetListOptions(ctx) + keyword := ctx.FormString("q") if !ctx.Repo.Repository.IsEmpty { if ctx.Repo.GitRepo == nil { @@ -325,6 +330,7 @@ func ListBranches(ctx *context.APIContext) { ListOptions: listOptions, RepoID: ctx.Repo.Repository.ID, IsDeletedBranch: optional.Some(false), + Keyword: keyword, } var err error totalNumOfBranches, err = db.Count[git_model.Branch](ctx, branchOpts) diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 20c7abd3454..0b3226294f2 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -7158,6 +7158,12 @@ "description": "page size of results", "name": "limit", "in": "query" + }, + { + "type": "string", + "description": "branch name substring to filter by", + "name": "q", + "in": "query" } ], "responses": { diff --git a/templates/swagger/v1_openapi3_json.tmpl b/templates/swagger/v1_openapi3_json.tmpl index 0a15c8eba49..1402f76899a 100644 --- a/templates/swagger/v1_openapi3_json.tmpl +++ b/templates/swagger/v1_openapi3_json.tmpl @@ -18224,6 +18224,14 @@ "schema": { "type": "integer" } + }, + { + "description": "branch name substring to filter by", + "in": "query", + "name": "q", + "schema": { + "type": "string" + } } ], "responses": { diff --git a/tests/integration/api_repo_branch_test.go b/tests/integration/api_repo_branch_test.go index 188da619fb9..864e351c8d0 100644 --- a/tests/integration/api_repo_branch_test.go +++ b/tests/integration/api_repo_branch_test.go @@ -128,3 +128,20 @@ func TestAPIRepoBranchesMirror(t *testing.T) { assert.NoError(t, err) assert.JSONEq(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}", string(bs)) } + +func TestAPIRepoBranchesSearch(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteRepository) + + // "test" matches "test_branch" but not "master" + resp := MakeRequest(t, NewRequestf(t, "GET", "/api/v1/repos/org3/repo3/branches?q=test").AddTokenAuth(token), http.StatusOK) + branches := DecodeJSON(t, resp, []api.Branch{}) + assert.Len(t, branches, 1) + assert.Equal(t, "test_branch", branches[0].Name) + + // no match returns empty list + resp = MakeRequest(t, NewRequestf(t, "GET", "/api/v1/repos/org3/repo3/branches?q=doesnotexist").AddTokenAuth(token), http.StatusOK) + branches = DecodeJSON(t, resp, []api.Branch{}) + assert.Empty(t, branches) +} diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index 9c42366832f..6f0928a12e1 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -323,6 +323,10 @@ func logUnexpectedResponse(t testing.TB, recorder *httptest.ResponseRecorder) { } } +// DecodeJSON decodes then response as JSON into typed variable and return it +// HINT: don't use it on existing variable (reuse existing variable): +// if the existing var already contains some values but the new input doesn't, then it leads to wrong test result in edge cases. +// For slice decoding, use: v := DecodeJSON(t, resp, []T{}) func DecodeJSON[T any](t testing.TB, resp *httptest.ResponseRecorder, v T) (ret T) { t.Helper() From aaf4b149fa29a11ec6495d65610031dee7fcfb79 Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Thu, 4 Jun 2026 06:38:56 -0700 Subject: [PATCH 11/40] chore(deps): upgrade zstd seekable package (#37988) Upgrade `github.com/SaveTheRbtz/zstd-seekable-format-go/pkg` from `v0.8.3` to `v0.10.0`: https://github.com/SaveTheRbtz/zstd-seekable-format-go/releases/tag/pkg%2Fv0.10.0 This keeps Gitea's seekable zstd wrapper on the stable v0.10 API while preserving the existing public `modules/zstd` API. API migration: - update `SeekableWriter` and `SeekableReader` internals for the concrete `*seekable.Writer` and `*seekable.Reader` types introduced by SaveTheRbtz/zstd-seekable-format-go#264 - update generated dependency metadata after `go mod tidy` removed the now-unused `github.com/google/btree` transitive dependency - no Gitea call sites needed changes because `modules/zstd` still exposes the same constructors and interfaces Validation: - `go test ./modules/zstd` - `make --always-make checks-backend` --------- Co-authored-by: Giteabot --- assets/go-licenses.json | 5 ----- go.mod | 3 +-- go.sum | 6 ++---- modules/zstd/zstd.go | 4 ++-- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/assets/go-licenses.json b/assets/go-licenses.json index 22dc1696246..506ab3c9ee6 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -649,11 +649,6 @@ "path": "github.com/golang/snappy/LICENSE", "licenseText": "Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, - { - "name": "github.com/google/btree", - "path": "github.com/google/btree/LICENSE", - "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" - }, { "name": "github.com/google/flatbuffers", "path": "github.com/google/flatbuffers/LICENSE", diff --git a/go.mod b/go.mod index ead88bf5fb7..e1b54b8aac5 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347 github.com/ProtonMail/go-crypto v1.4.1 github.com/PuerkitoBio/goquery v1.12.0 - github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.3 + github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0 github.com/alecthomas/chroma/v2 v2.25.0 github.com/aws/aws-sdk-go-v2/credentials v1.19.16 github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.14 @@ -195,7 +195,6 @@ require ( github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/snappy v1.0.0 // indirect - github.com/google/btree v1.1.3 // indirect github.com/google/flatbuffers v25.12.19+incompatible // indirect github.com/google/go-querystring v1.2.0 // indirect github.com/google/go-tpm v0.9.8 // indirect diff --git a/go.sum b/go.sum index 521779a0278..6a97d37ced9 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,8 @@ github.com/RoaringBitmap/roaring/v2 v2.16.0 h1:Kys1UNf49d5W8Tq3bpuAhIr/Z8/yPB+59 github.com/RoaringBitmap/roaring/v2 v2.16.0/go.mod h1:eq4wdNXxtJIS/oikeCzdX1rBzek7ANzbth041hrU8Q4= github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4= github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk= -github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.3 h1:ikrUPushSxbpr9mmDhSgz1KyUTChjwkDrxY/jzv2OTQ= -github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.3/go.mod h1:bnXbvnI9Mfqdj4L3Y9aCsB1A/ztuYLNRzgVKsJFgR/U= +github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0 h1:LvK7+C6qgz8BPnmn7xGekf8vTkcqTvyBBHaLYIMxx0g= +github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.10.0/go.mod h1:I28hc9eaiqKCoOB+9Wh/P1IOScfm0xCgyWC6DAo0lmo= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= @@ -367,8 +367,6 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v1.9.3 h1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8= github.com/gomodule/redigo v1.9.3/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw= -github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= -github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs= github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= diff --git a/modules/zstd/zstd.go b/modules/zstd/zstd.go index d2249447d62..00cf2ea82e0 100644 --- a/modules/zstd/zstd.go +++ b/modules/zstd/zstd.go @@ -60,7 +60,7 @@ func (r *Reader) Close() error { type SeekableWriter struct { buf []byte n int - w seekable.Writer + w *seekable.Writer } var _ io.WriteCloser = (*SeekableWriter)(nil) @@ -114,7 +114,7 @@ func (w *SeekableWriter) Close() error { } type SeekableReader struct { - r seekable.Reader + r *seekable.Reader c func() error } From dac41a124fd34820a3c8caf3b3592ba62cd514ff Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 4 Jun 2026 21:56:16 +0800 Subject: [PATCH 12/40] fix!: raise git required version to 2.13 (#37996) format `lstrip=2` is only supported in git >= 2.13 https://git-scm.com/docs/git-for-each-ref/2.13.7 ref: #37994 Co-authored-by: Giteabot --- modules/git/config.go | 6 ++-- modules/git/git.go | 9 +----- modules/git/remote.go | 8 +----- modules/git/repo_commit.go | 51 +++++++-------------------------- modules/git/repo_commit_test.go | 3 +- 5 files changed, 16 insertions(+), 61 deletions(-) diff --git a/modules/git/config.go b/modules/git/config.go index b1ca18c9882..e6a2ac88173 100644 --- a/modules/git/config.go +++ b/modules/git/config.go @@ -46,10 +46,8 @@ func syncGitConfig(ctx context.Context) (err error) { return err } - if DefaultFeatures().CheckVersionAtLeast("2.10") { - if err := configSet(ctx, "receive.advertisePushOptions", "true"); err != nil { - return err - } + if err := configSet(ctx, "receive.advertisePushOptions", "true"); err != nil { + return err } if DefaultFeatures().CheckVersionAtLeast("2.18") { diff --git a/modules/git/git.go b/modules/git/git.go index 5359781968f..0c2deb0281e 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -22,7 +22,7 @@ import ( "github.com/hashicorp/go-version" ) -const RequiredVersion = "2.6.0" // the minimum Git version required +const RequiredVersion = "2.13.0" // the minimum Git version required type Features struct { gitVersion *version.Version @@ -173,13 +173,6 @@ func InitFull() (err error) { if err = InitSimple(); err != nil { return err } - - if setting.LFS.StartServer { - if !DefaultFeatures().CheckVersionAtLeast("2.1.2") { - return errors.New("LFS server support requires Git >= 2.1.2") - } - } - return syncGitConfig(context.Background()) } diff --git a/modules/git/remote.go b/modules/git/remote.go index f215cd36713..b4fe6cfd100 100644 --- a/modules/git/remote.go +++ b/modules/git/remote.go @@ -15,13 +15,7 @@ import ( // GetRemoteAddress returns remote url of git repository in the repoPath with special remote name func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) { - var cmd *gitcmd.Command - if DefaultFeatures().CheckVersionAtLeast("2.7") { - cmd = gitcmd.NewCommand("remote", "get-url").AddDynamicArguments(remoteName) - } else { - cmd = gitcmd.NewCommand("config", "--get").AddDynamicArguments("remote." + remoteName + ".url") - } - + cmd := gitcmd.NewCommand("remote", "get-url").AddDynamicArguments(remoteName) result, _, err := cmd.WithDir(repoPath).RunStdString(ctx) if err != nil { return "", err diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index acf2a13b0e1..b955f2709b3 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -7,7 +7,6 @@ package git import ( "bytes" "io" - "os" "strconv" "strings" @@ -409,7 +408,7 @@ func (repo *Repository) commitsBefore(id ObjectID, limit int) ([]*Commit, error) commits := make([]*Commit, 0, len(formattedLog)) for _, commit := range formattedLog { - branches, err := repo.getBranches(os.Environ(), commit.ID.String(), 2) + branches, err := repo.getBranches(nil, commit.ID.String(), 2) if err != nil { return nil, err } @@ -433,46 +432,17 @@ func (repo *Repository) getCommitsBeforeLimit(id ObjectID, num int) ([]*Commit, } func (repo *Repository) getBranches(env []string, commitID string, limit int) ([]string, error) { - if DefaultFeatures().CheckVersionAtLeast("2.7.0") { - stdout, _, err := gitcmd.NewCommand("for-each-ref", "--format=%(refname:strip=2)"). - AddOptionFormat("--count=%d", limit). - AddOptionValues("--contains", commitID, BranchPrefix). - WithDir(repo.Path). - WithEnv(env). - RunStdString(repo.Ctx) - if err != nil { - return nil, err - } - - branches := strings.Fields(stdout) - return branches, nil - } - - stdout, _, err := gitcmd.NewCommand("branch"). + stdout, _, err := gitcmd.NewCommand("for-each-ref", "--format=%(refname:strip=2)"). + AddOptionFormat("--count=%d", limit). AddOptionValues("--contains", commitID). - WithDir(repo.Path). + AddArguments(BranchPrefix). WithEnv(env). + WithDir(repo.Path). RunStdString(repo.Ctx) if err != nil { return nil, err } - - refs := strings.Split(stdout, "\n") - - var maxNum int - if len(refs) > limit { - maxNum = limit - } else { - maxNum = len(refs) - 1 - } - - branches := make([]string, maxNum) - for i, ref := range refs[:maxNum] { - parts := strings.Fields(ref) - - branches[i] = parts[len(parts)-1] - } - return branches, nil + return strings.Fields(stdout), nil } // GetCommitsFromIDs get commits from commit IDs @@ -515,16 +485,17 @@ func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID s parts := bytes.SplitSeq(bytes.TrimSpace(stdout), []byte{'\n'}) - // check the commits one by one until we find a commit contained by another branch + // check the commits one by one until we find a commit contained by another branch, // and we think this commit is the divergence point - for commitID := range parts { - branches, err := repo.getBranches(env, string(commitID), 2) + for part := range parts { + commitID := string(part) + branches, err := repo.getBranches(env, commitID, 2) if err != nil { return "", err } for _, b := range branches { if b != branch { - return string(commitID), nil + return commitID, nil } } } diff --git a/modules/git/repo_commit_test.go b/modules/git/repo_commit_test.go index aecba042508..fc905d24257 100644 --- a/modules/git/repo_commit_test.go +++ b/modules/git/repo_commit_test.go @@ -4,7 +4,6 @@ package git import ( - "os" "path/filepath" "strings" "testing" @@ -38,7 +37,7 @@ func TestRepository_GetCommitBranches(t *testing.T) { for _, testCase := range testCases { commit, err := bareRepo1.GetCommit(testCase.CommitID) assert.NoError(t, err) - branches, err := bareRepo1.getBranches(os.Environ(), commit.ID.String(), 2) + branches, err := bareRepo1.getBranches(nil, commit.ID.String(), 2) assert.NoError(t, err) assert.Equal(t, testCase.ExpectedBranches, branches) } From 7a26d5a2ae7f708d535623ccbe201ecd3e121d5d Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Fri, 5 Jun 2026 01:18:00 +0000 Subject: [PATCH 13/40] [skip ci] Updated translations via Crowdin --- options/locale/locale_ga-IE.json | 1 + 1 file changed, 1 insertion(+) diff --git a/options/locale/locale_ga-IE.json b/options/locale/locale_ga-IE.json index 3e3fd8746f3..9bff2c24be8 100644 --- a/options/locale/locale_ga-IE.json +++ b/options/locale/locale_ga-IE.json @@ -1321,6 +1321,7 @@ "repo.editor.fork_branch_exists": "Tá brainse \"%s\" ann cheana féin i do fhorc. Roghnaigh ainm brainse nua le do thoil.", "repo.commits.desc": "Brabhsáil stair athraithe cód foinse.", "repo.commits.commits": "Tiomáintí", + "repo.commits.history_enable_follow_renames": "Cuir athainmneacha san áireamh", "repo.commits.no_commits": "Níl aon ghealltanas i gcoiteann. Tá stair iomlán difriúil ag \"%s\" agus \"%s\".", "repo.commits.nothing_to_compare": "Níl aon difríochtaí le taispeáint.", "repo.commits.search.tooltip": "Is féidir eochairfhocail a réamhfhostú le “údar:”, “committer:”, “after:”, nó “before:”, e.g. \"fill an t-údar:Alice roimh: 2019-01-13\".", From aa63d1583dc6ca69887a2abc9a176ec16695df61 Mon Sep 17 00:00:00 2001 From: bircni Date: Fri, 5 Jun 2026 20:10:25 +0200 Subject: [PATCH 14/40] fix(actions): return 404 when job log blob is missing (#38003) - When the `action_task` row exists but the underlying dbfs/storage blob is gone, `OpenLogs` returns a wrapped `os.ErrNotExist` which surfaces as a 500 on the job logs endpoints. - Translate it to the same `util.NewNotExistErrorf` shape already used for unknown job ids / expired logs, so both the API (`/api/v1/repos/.../actions/jobs//logs`) and the web download handler return a clean 404 instead. Fixes #37990. --- routers/common/actions.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/routers/common/actions.go b/routers/common/actions.go index 6fc5da7b13c..4c3c2129282 100644 --- a/routers/common/actions.go +++ b/routers/common/actions.go @@ -4,7 +4,9 @@ package common import ( + "errors" "fmt" + "io/fs" "strings" actions_model "gitea.dev/models/actions" @@ -51,6 +53,9 @@ func DownloadActionsRunJobLogs(ctx *context.Base, ctxRepo *repo_model.Repository reader, err := actions.OpenLogs(ctx, task.LogInStorage, task.LogFilename) if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return util.NewNotExistErrorf("logs not found") + } return fmt.Errorf("OpenLogs: %w", err) } defer reader.Close() From 3659b5acc215785d7bc7529c78b8431f573aae8f Mon Sep 17 00:00:00 2001 From: bircni Date: Fri, 5 Jun 2026 23:33:40 +0200 Subject: [PATCH 15/40] ci(workflows): add AgentScan workflow to flag possible AI-assisted PRs (#37962) This PR adds an automated AgentScan workflow to help detect and handle pull requests that appear to be created or authored primarily by automated agents. - If a PR is classified as `automation` or community-flagged, the workflow: - Adds the `possible bot` label, - Posts a policy comment linking to the repository AI Contribution Policy (`CONTRIBUTING.md#ai-contribution-policy`) and listing required disclosures and checks, - Optionally closes the PR if classification indicates an automated/unwelcome submission. --- .github/workflows/agent-scan.yml | 113 +++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 .github/workflows/agent-scan.yml diff --git a/.github/workflows/agent-scan.yml b/.github/workflows/agent-scan.yml new file mode 100644 index 00000000000..2c84face622 --- /dev/null +++ b/.github/workflows/agent-scan.yml @@ -0,0 +1,113 @@ +name: AgentScan + +on: + # jobs only use pinned actions and never checkout code + pull_request_target: # zizmor: ignore[dangerous-triggers] + types: [opened, reopened, synchronize, edited] + +concurrency: + group: agent-scan-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + issues: write + pull-requests: write + +jobs: + agentscan: + runs-on: ubuntu-latest + steps: + - name: AgentScan + id: agentscan + uses: MatteoGabriele/agentscan-action@0a0c88109b5153dff2805f969f5060441efb7b65 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + skip-members: "dependabot[bot],renovate[bot], giteabot (backports)" + agent-scan-comment: false + + - name: Handle flagged PR + if: contains(fromJSON('["automation","mixed"]'), steps.agentscan.outputs.classification) || steps.agentscan.outputs.community-flagged == 'true' + env: + CLASSIFICATION: ${{ steps.agentscan.outputs.classification }} + COMMUNITY_FLAGGED: ${{ steps.agentscan.outputs.community-flagged }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 + with: + script: | + const core = require('@actions/core'); + const prNumber = context.payload.pull_request.number; + const classification = process.env.CLASSIFICATION; + const communityFlagged = process.env.COMMUNITY_FLAGGED === 'true'; + const shouldClose = classification === 'automation' || communityFlagged; + + const issue = context.payload.pull_request; + const labels = issue.labels?.map(l => l.name) || []; + + if (!labels.includes('possible bot')) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + labels: ['possible bot'], + }); + } + + const comments = await github.paginate(github.rest.issues.listComments, { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + per_page: 100, + }); + + const alreadyCommented = comments.some(c => c.user.type === 'Bot' && c.body.includes('AI Contribution Policy')); + + if (!alreadyCommented) { + const closingNote = shouldClose + ? "We're closing this for now as the account looks automated. If we got that wrong, please just reopen the PR and we'll take another look." + : 'If this was flagged in error, we apologise! 😳 Just let us know. 🙏'; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: [ + "We've flagged this pull request as potentially AI-assisted.", + '', + 'Gitea welcomes the thoughtful use of AI tools, but contributors must use them responsibly and clearly disclose any assistance. Please follow the AI Contribution Policy in `CONTRIBUTING.md` and update this PR accordingly:', + '', + 'Maintainers may close PRs that do not disclose AI assistance, appear to be low-quality AI-generated content, or where the contributor cannot explain the changes.', + '', + 'See: https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md#ai-contribution-policy', + '', + closingNote, + ].join('\n'), + }); + } else { + core.info('Possible-bot comment already exists - skipping comment.'); + } + + if (shouldClose && issue.state === 'open' && !alreadyCommented) { + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + state: 'closed', + title: '🚨 unwelcome pr from bot 🚨', + }); + } + + const actionTaken = [ + 'Added `possible bot` label', + alreadyCommented ? null : 'posted policy comment', + shouldClose && !alreadyCommented ? 'closed PR' : null, + ].filter(Boolean).join(', '); + + core.summary + .addHeading('AgentScan: Possible Bot Flag', 2) + .addTable([ + [{ data: 'Property', header: true }, { data: 'Value', header: true }], + ['Pull Request', `#${prNumber}`], + ['Classification', classification], + ['Community flagged', String(communityFlagged)], + ['Action', actionTaken || 'No action (already handled)'], + ]) + .write(); From 4088d7e241d21de8a720debecd2c95cb86a9dc4f Mon Sep 17 00:00:00 2001 From: bircni Date: Sat, 6 Jun 2026 11:00:14 +0200 Subject: [PATCH 16/40] fix(ui): keep actions run title intact when subject contains an issue ref (#38005) --- models/repo/repo.go | 8 +-- modules/markup/html.go | 42 +++++++------- modules/markup/html_internal_test.go | 6 +- modules/templates/util_render.go | 79 +++++++-------------------- modules/templates/util_render_test.go | 8 +-- routers/web/repo/commit.go | 6 +- templates/repo/actions/runs_list.tmpl | 11 ++-- web_src/css/repo/issue-list.css | 14 +++++ 8 files changed, 69 insertions(+), 105 deletions(-) diff --git a/models/repo/repo.go b/models/repo/repo.go index 7f8507722f0..e56603fc812 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -23,6 +23,7 @@ import ( "gitea.dev/modules/base" "gitea.dev/modules/git" giturl "gitea.dev/modules/git/url" + "gitea.dev/modules/htmlutil" "gitea.dev/modules/httplib" "gitea.dev/modules/log" "gitea.dev/modules/markup" @@ -641,12 +642,7 @@ func (repo *Repository) CanContentChange() bool { // DescriptionHTML does special handles to description and return HTML string. func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML { - desc, err := markup.PostProcessDescriptionHTML(markup.NewRenderContext(ctx), repo.Description) - if err != nil { - log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err) - return template.HTML(markup.SanitizeDescription(repo.Description)) - } - return template.HTML(markup.SanitizeDescription(desc)) + return markup.PostProcessDescriptionHTML(markup.NewRenderContext(ctx), htmlutil.EscapeString(repo.Description)) } // CloneLink represents different types of clone URLs of repository. diff --git a/modules/markup/html.go b/modules/markup/html.go index 4943bdf4a5f..a21c4707113 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -14,8 +14,10 @@ import ( "sync" "gitea.dev/modules/htmlutil" + "gitea.dev/modules/log" "gitea.dev/modules/markup/common" "gitea.dev/modules/translation" + "gitea.dev/modules/util" "golang.org/x/net/html" "golang.org/x/net/html/atom" @@ -151,8 +153,7 @@ func PostProcessDefault(ctx *RenderContext, input io.Reader, output io.Writer) e } // PostProcessCommitMessage will use the same logic as PostProcess, but will disable the shortLinkProcessor. -// FIXME: this function and its family have a very strange design: it takes HTML as input and output, processes the "escaped" content. -func PostProcessCommitMessage(ctx *RenderContext, content template.HTML) (template.HTML, error) { +func PostProcessCommitMessage(ctx *RenderContext, content template.HTML) template.HTML { procs := []processor{ fullIssuePatternProcessor, comparePatternProcessor, @@ -166,8 +167,7 @@ func PostProcessCommitMessage(ctx *RenderContext, content template.HTML) (templa emojiProcessor, emojiShortCodeProcessor, } - s, err := postProcessString(ctx, procs, string(content)) - return template.HTML(s), err + return postProcessHTML(ctx, procs, content) } var emojiProcessors = []processor{ @@ -189,7 +189,7 @@ func isBareURLSubject(content string) bool { // PostProcessCommitMessageSubject will use the same logic as PostProcess and // PostProcessCommitMessage, but will disable the shortLinkProcessor and // emailAddressProcessor, and wraps the whole subject in defaultLink. -func PostProcessCommitMessageSubject(ctx *RenderContext, defaultLink, content string) (string, error) { +func PostProcessCommitMessageSubject(ctx *RenderContext, defaultLink string, content template.HTML) template.HTML { procs := []processor{ fullIssuePatternProcessor, comparePatternProcessor, @@ -207,7 +207,7 @@ func PostProcessCommitMessageSubject(ctx *RenderContext, defaultLink, content st // plain text inside defaultLink. Partial URLs inside larger text still become // their own links (nested anchors aren't legal HTML, so the outer defaultLink // naturally breaks on that span, same as on GitHub). - if !isBareURLSubject(content) { + if !isBareURLSubject(string(content)) { procs = append(procs, linkProcessor) } procs = append(procs, func(ctx *RenderContext, node *html.Node) { @@ -215,27 +215,28 @@ func PostProcessCommitMessageSubject(ctx *RenderContext, defaultLink, content st node.Type = html.ElementNode node.Data = "a" node.DataAtom = atom.A - node.Attr = []html.Attribute{{Key: "href", Val: defaultLink}, {Key: "class", Val: "muted"}} + node.Attr = []html.Attribute{{Key: "href", Val: defaultLink}, {Key: "class", Val: "muted title-full-link"}} node.FirstChild, node.LastChild = ch, ch }) - return postProcessString(ctx, procs, content) + rendered := postProcessHTML(ctx, procs, content) + return htmlutil.HTMLFormat(`%s`, rendered) } // PostProcessIssueTitle to process title on individual issue/pull page -func PostProcessIssueTitle(ctx *RenderContext, title string) (string, error) { - return postProcessString(ctx, []processor{ +func PostProcessIssueTitle(ctx *RenderContext, titleHTML template.HTML) template.HTML { + return postProcessHTML(ctx, []processor{ issueIndexPatternProcessor, commitCrossReferencePatternProcessor, hashCurrentPatternProcessor, emojiShortCodeProcessor, emojiProcessor, - }, title) + }, titleHTML) } // PostProcessDescriptionHTML will use similar logic as PostProcess, but will // use a single special linkProcessor. -func PostProcessDescriptionHTML(ctx *RenderContext, content string) (string, error) { - return postProcessString(ctx, []processor{ +func PostProcessDescriptionHTML(ctx *RenderContext, content template.HTML) template.HTML { + return postProcessHTML(ctx, []processor{ descriptionLinkProcessor, emojiShortCodeProcessor, emojiProcessor, @@ -243,17 +244,18 @@ func PostProcessDescriptionHTML(ctx *RenderContext, content string) (string, err } // PostProcessEmoji for when we want to just process emoji and shortcodes -// in various places it isn't already run through the normal markdown processor -func PostProcessEmoji(ctx *RenderContext, content string) (string, error) { - return postProcessString(ctx, emojiProcessors, content) +// in various places it isn't already run through the normal Markdown processor +func PostProcessEmoji(ctx *RenderContext, content template.HTML) template.HTML { + return postProcessHTML(ctx, emojiProcessors, content) } -func postProcessString(ctx *RenderContext, procs []processor, content string) (string, error) { +func postProcessHTML(ctx *RenderContext, procs []processor, content template.HTML) template.HTML { var buf strings.Builder - if err := postProcess(ctx, procs, strings.NewReader(content), &buf); err != nil { - return "", err + if err := postProcess(ctx, procs, strings.NewReader(string(content)), &buf); err != nil { + log.Warn("postProcessHTML err: %v, input: %s", err, util.TruncateRunes(string(content), 200)) + return content } - return buf.String(), nil + return template.HTML(buf.String()) } func RenderTocHeadingItems(ctx *RenderContext, nodeDetailsAttrs map[string]string, out io.Writer) { diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index 186489ae722..6036f368901 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -5,6 +5,7 @@ package markup import ( "fmt" + "html/template" "strconv" "strings" "testing" @@ -260,9 +261,8 @@ func TestRender_PostProcessIssueTitle(t *testing.T) { "repo": "someRepo", "style": IssueNameStyleNumeric, } - actual, err := PostProcessIssueTitle(NewTestRenderContext(metas), "#1") - assert.NoError(t, err) - assert.Equal(t, "#1", actual) + actual := PostProcessIssueTitle(NewTestRenderContext(metas), "#1") + assert.Equal(t, template.HTML("#1"), actual) } func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) { diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go index 3c7f9b0a7a1..81941902aee 100644 --- a/modules/templates/util_render.go +++ b/modules/templates/util_render.go @@ -11,7 +11,6 @@ import ( "net/url" "regexp" "strings" - "unicode" issues_model "gitea.dev/models/issues" "gitea.dev/models/renderhelper" @@ -39,60 +38,35 @@ func NewRenderUtils(ctx reqctx.RequestContext) *RenderUtils { return &RenderUtils{ctx: ctx} } -// RenderCommitMessage renders commit message with XSS-safe and special links. +// RenderCommitMessage renders commit message title (only title) func (ut *RenderUtils) RenderCommitMessage(msg string, repo *repo.Repository) template.HTML { - cleanMsg := template.HTML(template.HTMLEscapeString(msg)) - // we can safely assume that it will not return any error, since there shouldn't be any special HTML. - // "repo" can be nil when rendering commit messages for deleted repositories in a user's dashboard feed. - fullMessage, err := markup.PostProcessCommitMessage(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), cleanMsg) - if err != nil { - log.Error("PostProcessCommitMessage: %v", err) - return "" - } - msgLines := strings.Split(strings.TrimSpace(string(fullMessage)), "\n") - if len(msgLines) == 0 { - return "" - } - return renderCodeBlock(template.HTML(msgLines[0])) + msgLine := strings.TrimSpace(msg) + msgLine, _, _ = strings.Cut(msgLine, "\n") + msgLine = strings.TrimSpace(msgLine) + rendered := markup.PostProcessCommitMessage(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), htmlutil.EscapeString(msgLine)) + return renderCodeBlock(rendered) } // RenderCommitMessageLinkSubject renders commit message as a XSS-safe link to // the provided default url, handling for special links without email to links. func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, repo *repo.Repository) template.HTML { - msgLine := strings.TrimLeftFunc(msg, unicode.IsSpace) - lineEnd := strings.IndexByte(msgLine, '\n') - if lineEnd > 0 { - msgLine = msgLine[:lineEnd] - } - msgLine = strings.TrimRightFunc(msgLine, unicode.IsSpace) - if len(msgLine) == 0 { - return "" - } - - // we can safely assume that it will not return any error, since there shouldn't be any special HTML. - renderedMessage, err := markup.PostProcessCommitMessageSubject(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), urlDefault, template.HTMLEscapeString(msgLine)) - if err != nil { - log.Error("PostProcessCommitMessageSubject: %v", err) - return "" - } - return renderCodeBlock(template.HTML(renderedMessage)) + msgLine := strings.TrimSpace(msg) + msgLine, _, _ = strings.Cut(msgLine, "\n") + msgLine = strings.TrimSpace(msgLine) + rctx := renderhelper.NewRenderContextRepoComment(ut.ctx, repo) + rendered := markup.PostProcessCommitMessageSubject(rctx, urlDefault, htmlutil.EscapeString(msgLine)) + return renderCodeBlock(rendered) } // RenderCommitBody extracts the body of a commit message without its title. func (ut *RenderUtils) RenderCommitBody(msg string, repo *repo.Repository) template.HTML { _, body, _ := strings.Cut(strings.TrimSpace(msg), "\n") - body = strings.TrimFunc(body, unicode.IsSpace) + body = strings.TrimSpace(body) if body == "" { return "" } - rctx := renderhelper.NewRenderContextRepoComment(ut.ctx, repo) - htmlContent := template.HTML(template.HTMLEscapeString(body)) - renderedMessage, err := markup.PostProcessCommitMessage(rctx, htmlContent) - if err != nil { - log.Error("PostProcessCommitMessage: %v", err) - return "" - } + renderedMessage := markup.PostProcessCommitMessage(rctx, htmlutil.EscapeString(body)) return renderedMessage } @@ -108,25 +82,15 @@ func renderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML { // RenderIssueTitle renders issue/pull title with defined post processors func (ut *RenderUtils) RenderIssueTitle(text string, repo *repo.Repository) template.HTML { // wrap "`…`" in before post-processing so code-span content stays literal, like comment bodies - htmlWithCode := renderCodeBlock(template.HTML(template.HTMLEscapeString(text))) - renderedText, err := markup.PostProcessIssueTitle(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), string(htmlWithCode)) - if err != nil { - log.Error("PostProcessIssueTitle: %v", err) - return "" - } - return template.HTML(renderedText) + htmlWithCode := renderCodeBlock(htmlutil.EscapeString(text)) + return markup.PostProcessIssueTitle(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), htmlWithCode) } // RenderIssueSimpleTitle only renders with emoji and inline code block func (ut *RenderUtils) RenderIssueSimpleTitle(text string) template.HTML { // see RenderIssueTitle: wrap code spans before processing emoji - htmlWithCode := renderCodeBlock(template.HTML(template.HTMLEscapeString(text))) - renderedText, err := markup.PostProcessEmoji(markup.NewRenderContext(ut.ctx), string(htmlWithCode)) - if err != nil { - log.Error("RenderIssueSimpleTitle: %v", err) - return "" - } - return template.HTML(renderedText) + htmlWithCode := renderCodeBlock(htmlutil.EscapeString(text)) + return markup.PostProcessEmoji(markup.NewRenderContext(ut.ctx), htmlWithCode) } func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML { @@ -202,12 +166,7 @@ func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML { // RenderEmoji renders html text with emoji post processors func (ut *RenderUtils) RenderEmoji(text string) template.HTML { - renderedText, err := markup.PostProcessEmoji(markup.NewRenderContext(ut.ctx), template.HTMLEscapeString(text)) - if err != nil { - log.Error("RenderEmoji: %v", err) - return "" - } - return template.HTML(renderedText) + return markup.PostProcessEmoji(markup.NewRenderContext(ut.ctx), htmlutil.EscapeString(text)) } // reactionToEmoji renders emoji for use in reactions diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go index 50a443c7468..5a28a1feba8 100644 --- a/modules/templates/util_render_test.go +++ b/modules/templates/util_render_test.go @@ -131,24 +131,24 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit }) t.Run("RenderCommitMessage", func(t *testing.T) { - expected := `space @mention-user ` + expected := `space @mention-user` assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessage(testInput(), mockRepo)) }) t.Run("RenderCommitMessageLinkSubject", func(t *testing.T) { - expected := `space @mention-user` + expected := `space @mention-user` assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessageLinkSubject(testInput(), "https://example.com/link", mockRepo)) }) t.Run("RenderCommitMessageLinkSubjectURLOnly", func(t *testing.T) { // a bare URL in the subject must not hijack the default link - expected := `https://example.com/file.bin` + expected := `https://example.com/file.bin` assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessageLinkSubject("https://example.com/file.bin", "https://example.com/link", mockRepo)) }) t.Run("RenderCommitMessageLinkSubjectPartialURL", func(t *testing.T) { // a URL embedded in larger subject text still becomes its own link - expected := `see https://example.com/x here` + expected := `see https://example.com/x here` assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessageLinkSubject("see https://example.com/x here", "https://example.com/link", mockRepo)) }) diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 7cb2f82a903..ff3496629fa 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -414,11 +414,7 @@ func Diff(ctx *context.Context) { ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit) rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{CurrentRefSubURL: "commit/" + util.PathEscapeSegments(commitID)}) htmlMessage := template.HTML(template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{})))) - ctx.Data["NoteRendered"], err = markup.PostProcessCommitMessage(rctx, htmlMessage) - if err != nil { - ctx.ServerError("PostProcessCommitMessage", err) - return - } + ctx.Data["NoteRendered"] = markup.PostProcessCommitMessage(rctx, htmlMessage) } else if !git.IsErrNotExist(err) { log.Error("GetNote: %v", err) } diff --git a/templates/repo/actions/runs_list.tmpl b/templates/repo/actions/runs_list.tmpl index edc167e706b..58dc222cf5b 100644 --- a/templates/repo/actions/runs_list.tmpl +++ b/templates/repo/actions/runs_list.tmpl @@ -13,13 +13,10 @@
- - {{if $run.Title}} - {{ctx.RenderUtils.RenderCommitMessageLinkSubject $run.Title $run.Link $.Repository}} - {{else}} - {{ctx.Locale.Tr "actions.runs.empty_commit_message"}} - {{end}} - +
+ {{$title := or $run.Title (ctx.Locale.Tr "actions.runs.empty_commit_message")}} + {{ctx.RenderUtils.RenderCommitMessageLinkSubject $title $run.Link $.Repository}} +
{{$workflowName := index $.WorkflowNames $run.WorkflowID}} {{if not $.CurWorkflow}}{{if $workflowName}}{{$workflowName}}{{else}}{{$run.WorkflowID}}{{end}} {{end}}#{{$run.Index}}: diff --git a/web_src/css/repo/issue-list.css b/web_src/css/repo/issue-list.css index 1a635f20199..cecf9920acd 100644 --- a/web_src/css/repo/issue-list.css +++ b/web_src/css/repo/issue-list.css @@ -92,3 +92,17 @@ margin-right: 8px; text-align: left; } + +/* for "title (#123)": + + title ( + #123 + ) + + * hover on "title": also highlight the right parentheses + * hover on "#123": don't highlight other parts + */ +.title-full-link-hover:not(:has(:not(.title-full-link):hover)):hover > a.title-full-link { + color: var(--color-primary); + text-decoration: underline; +} From e88650cfcf503f45b97d976f76026423ba85135e Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 6 Jun 2026 17:24:03 +0800 Subject: [PATCH 17/40] chore: fix various layout problems (#37983) Fix various misaligments, fix space between list item bar items, remove deadcode (milestone dashboard) --- templates/projects/list.tmpl | 22 ++++----- templates/repo/issue/milestones.tmpl | 34 ++++++------- templates/shared/issuelist.tmpl | 18 ++++--- templates/user/dashboard/milestones.tmpl | 43 ++++++---------- tests/e2e/issue-project.test.ts | 2 +- tests/e2e/utils.ts | 2 +- tests/integration/issue_test.go | 2 +- web_src/css/base.css | 5 -- web_src/css/index.css | 1 - web_src/css/modules/svg.css | 2 +- web_src/css/repo/issue-list.css | 38 ++++++++++++--- web_src/css/shared/flex-list.css | 10 ++-- web_src/css/shared/milestone.css | 62 ------------------------ 13 files changed, 93 insertions(+), 148 deletions(-) delete mode 100644 web_src/css/shared/milestone.css diff --git a/templates/projects/list.tmpl b/templates/projects/list.tmpl index 346f4002b40..9fedd05f949 100644 --- a/templates/projects/list.tmpl +++ b/templates/projects/list.tmpl @@ -41,26 +41,26 @@
-
+
{{/* the milestone-list class is kept because many tests depend on it */}} {{range .Projects}} -
  • -

    +
    + {{svg .IconName 16}} - {{.Title}} -

    -
    -
    -
    + {{.Title}} + +
    +
    +
    {{svg "octicon-issue-opened" 14}} {{ctx.Locale.PrettyNumber .NumOpenIssues}} {{ctx.Locale.Tr "repo.issues.open_title"}}
    -
    +
    {{svg "octicon-check" 14}} {{ctx.Locale.PrettyNumber .NumClosedIssues}} {{ctx.Locale.Tr "repo.issues.closed_title"}}
    {{if and $.CanWriteProjects (not $.Repository.IsArchived)}} -
    +
    {{svg "octicon-pencil" 14}}{{ctx.Locale.Tr "repo.issues.label_edit"}} {{if .IsClosed}} {{svg "octicon-check" 14}}{{ctx.Locale.Tr "repo.projects.open"}} @@ -74,7 +74,7 @@ {{if .Description}}
    {{.RenderedContent}}
    {{end}} -
  • +
    {{else}} {{if and (eq .OpenCount 0) (eq .ClosedCount 0)}}
    diff --git a/templates/repo/issue/milestones.tmpl b/templates/repo/issue/milestones.tmpl index 90e2a8ae3b9..4af4697b44f 100644 --- a/templates/repo/issue/milestones.tmpl +++ b/templates/repo/issue/milestones.tmpl @@ -15,42 +15,42 @@ {{template "repo/issue/filters" .}} -
    +
    {{range .Milestones}} -
  • -
    -

    +
    +
    + {{svg "octicon-milestone" 16}} {{.Name}} -

    -
    - {{.Completeness}}% - + +
    + {{.Completeness}}% +
    -
    -
    -
    +
    +
    +
    {{svg "octicon-issue-opened" 14}} {{ctx.Locale.PrettyNumber .NumOpenIssues}} {{ctx.Locale.Tr "repo.issues.open_title"}}
    -
    +
    {{svg "octicon-check" 14}} {{ctx.Locale.PrettyNumber .NumClosedIssues}} {{ctx.Locale.Tr "repo.issues.closed_title"}}
    {{if .TotalTrackedTime}} -
    +
    {{svg "octicon-clock"}} {{.TotalTrackedTime|Sec2Hour}}
    {{end}} {{if .UpdatedUnix}} -
    +
    {{svg "octicon-clock"}} {{ctx.Locale.Tr "repo.milestones.update_ago" (DateUtils.TimeSince .UpdatedUnix)}}
    {{end}} -
    +
    {{if .IsClosed}} {{$closedDate:= DateUtils.TimeSince .ClosedDateUnix}} {{svg "octicon-clock" 14}} @@ -69,7 +69,7 @@
    {{if and (or $.CanWriteIssues $.CanWritePulls) (not $.Repository.IsArchived)}} -
  • +
    {{end}} {{template "base/paginate" .}} diff --git a/templates/shared/issuelist.tmpl b/templates/shared/issuelist.tmpl index 11c4210b460..838caeb7b28 100644 --- a/templates/shared/issuelist.tmpl +++ b/templates/shared/issuelist.tmpl @@ -16,7 +16,7 @@
    - {{.Title | ctx.RenderUtils.RenderIssueSimpleTitle}} + {{.Title | ctx.RenderUtils.RenderIssueSimpleTitle}} {{if .IsPull}} {{if (index $.CommitStatuses .PullRequest.ID)}} {{/* make the "flex" children align with parent "inline" */}} @@ -37,14 +37,16 @@
    {{end}}
    -
    - +
    + {{if eq $.listType "dashboard"}} {{.Repo.FullName}}#{{.Index}} {{else}} #{{.Index}} {{end}} + +
    {{$timeStr := DateUtils.TimeSince .GetLastEventTimestamp}} {{if .OriginalAuthor}} {{ctx.Locale.Tr .GetLastEventLabelFake $timeStr .OriginalAuthor}} @@ -53,6 +55,8 @@ {{else}} {{ctx.Locale.Tr .GetLastEventLabelFake $timeStr .Poster.GetDisplayName}} {{end}} +
    + {{if .IsPull}}
    @@ -99,11 +103,9 @@ {{end}} {{if ne .DeadlineUnix 0}} - - - {{svg "octicon-calendar" 14}} - {{DateUtils.AbsoluteShort .DeadlineUnix}} - + + {{svg "octicon-calendar" 14}} + {{DateUtils.AbsoluteShort .DeadlineUnix}} {{end}} {{if .IsPull}} diff --git a/templates/user/dashboard/milestones.tmpl b/templates/user/dashboard/milestones.tmpl index d5dd64b1a34..62c2df7e8f0 100644 --- a/templates/user/dashboard/milestones.tmpl +++ b/templates/user/dashboard/milestones.tmpl @@ -71,45 +71,45 @@
    -
    +
    {{range .Milestones}} -
  • -
    -

    +
    +
    + {{.Repo.FullName}} {{svg "octicon-milestone" 16}} {{.Name}} -

    -
    - {{.Completeness}}% - + +
    + {{.Completeness}}% +
    -
    -
    -
    +
    +
    +
    {{svg "octicon-issue-opened" 14}} {{ctx.Locale.PrettyNumber .NumOpenIssues}} {{ctx.Locale.Tr "repo.issues.open_title"}}
    -
    +
    {{svg "octicon-check" 14}} {{ctx.Locale.PrettyNumber .NumClosedIssues}} {{ctx.Locale.Tr "repo.issues.closed_title"}}
    {{if .TotalTrackedTime}} -
    +
    {{svg "octicon-clock"}} {{.TotalTrackedTime|Sec2Hour}}
    {{end}} {{if .UpdatedUnix}} -
    +
    {{svg "octicon-clock"}} {{ctx.Locale.Tr "repo.milestones.update_ago" (DateUtils.TimeSince .UpdatedUnix)}}
    {{end}} -
    +
    {{if .IsClosed}} {{$closedDate:= DateUtils.TimeSince .ClosedDateUnix}} {{svg "octicon-clock" 14}} @@ -127,22 +127,11 @@ {{end}}
    - {{if and (or $.CanWriteIssues $.CanWritePulls) (not $.Repository.IsArchived)}} - - {{end}}
    {{if .Content}}
    {{.RenderedContent}}
    {{end}} -
  • +
    {{end}} {{template "base/paginate" .}} diff --git a/tests/e2e/issue-project.test.ts b/tests/e2e/issue-project.test.ts index 1595cfadc5a..10466cd822f 100644 --- a/tests/e2e/issue-project.test.ts +++ b/tests/e2e/issue-project.test.ts @@ -372,7 +372,7 @@ test('close project and view in closed projects list', async ({page}) => { await expect(page.locator('.milestone-list')).toContainText(closedProjectTitle); // Close the second project by clicking the close link - const projectCard = page.locator('.milestone-card').filter({hasText: closedProjectTitle}); + const projectCard = page.locator('.milestone-list > .item').filter({hasText: closedProjectTitle}); await projectCard.locator('a.link-action[data-url$="/close"]').click(); // Wait for redirect back to project view page diff --git a/tests/e2e/utils.ts b/tests/e2e/utils.ts index 8dcd6aedae2..5b19793f37b 100644 --- a/tests/e2e/utils.ts +++ b/tests/e2e/utils.ts @@ -159,7 +159,7 @@ export async function createProject( await page.waitForURL(new RegExp(`/${owner}/${repo}/projects$`)); // Extract the project ID from the project link in the list - const projectLink = page.locator('.milestone-list .milestone-card').filter({hasText: title}).locator('a').first(); + const projectLink = page.locator('.milestone-list > .item').filter({hasText: title}).locator('a').first(); const href = await projectLink.getAttribute('href'); const match = /\/projects\/(\d+)/.exec(href || ''); const id = match ? parseInt(match[1]) : 0; diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go index aaeba60d8e9..ffb491344b3 100644 --- a/tests/integration/issue_test.go +++ b/tests/integration/issue_test.go @@ -34,7 +34,7 @@ import ( func getIssuesSelection(t testing.TB, htmlDoc *HTMLDoc) *goquery.Selection { issueList := htmlDoc.doc.Find("#issue-list") assert.Equal(t, 1, issueList.Length()) - return issueList.Find(".item").Find(".issue-item-title") + return issueList.Find(".item").Find(".list-item-large-title") } func getIssue(t *testing.T, repoID int64, issueSelection *goquery.Selection) *issues_model.Issue { diff --git a/web_src/css/base.css b/web_src/css/base.css index 49ddca57a30..0716ad1913e 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -454,11 +454,6 @@ img.ui.avatar, margin-right: 4px; } -.ui.inline.delete-button { - padding: 8px 15px; - font-weight: var(--font-weight-normal); -} - .ui .migrate { color: var(--color-text-light-2) !important; } diff --git a/web_src/css/index.css b/web_src/css/index.css index 89ad63ba1a8..a56982efcf7 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -37,7 +37,6 @@ @import "./modules/charescape.css"; @import "./shared/flex-list.css"; -@import "./shared/milestone.css"; @import "./shared/settings.css"; @import "./features/dropzone.css"; diff --git a/web_src/css/modules/svg.css b/web_src/css/modules/svg.css index 7adfc77a818..b772f98ac6f 100644 --- a/web_src/css/modules/svg.css +++ b/web_src/css/modules/svg.css @@ -2,7 +2,7 @@ and material icons should have no "fill" set explicitly, otherwise some like ".editorconfig" won't render correctly */ .svg:not(.git-entry-icon) { display: inline-block; - vertical-align: text-top; + vertical-align: middle; /* middle is the best choice for different font sizes from 1px to 36px when no flex */ fill: currentcolor; } diff --git a/web_src/css/repo/issue-list.css b/web_src/css/repo/issue-list.css index cecf9920acd..dfffe201874 100644 --- a/web_src/css/repo/issue-list.css +++ b/web_src/css/repo/issue-list.css @@ -34,14 +34,6 @@ } } -#issue-list .issue-item-title { - font-size: 16px; - font-weight: var(--font-weight-semibold); - color: var(--color-text); - text-decoration: none; - overflow-wrap: anywhere; -} - #issue-list .branches { display: inline-flex; } @@ -106,3 +98,33 @@ color: var(--color-primary); text-decoration: underline; } + +.list-item-large-title { + font-size: 16px; + font-weight: var(--font-weight-semibold); + color: var(--color-text); + text-decoration: none; + overflow-wrap: anywhere; +} + +.list-item-title-progress { + width: 200px; + height: 16px; +} + +.list-item-secondary-bar { + display: flex; + flex-wrap: wrap; + gap: var(--gap-block); + justify-content: space-between; + color: var(--color-text-light-2); +} + +.list-item-secondary-bar a { + color: var(--color-text-light-2); + text-decoration: none; +} + +.list-item-secondary-bar a:hover { + color: var(--color-text); +} diff --git a/web_src/css/shared/flex-list.css b/web_src/css/shared/flex-list.css index 1acaa362e22..b224fe667c6 100644 --- a/web_src/css/shared/flex-list.css +++ b/web_src/css/shared/flex-list.css @@ -32,7 +32,7 @@ .items-with-main > .item { display: flex; - gap: 8px; + gap: var(--gap-block); align-items: flex-start; } @@ -44,7 +44,7 @@ .items-with-main > .item .item-main { display: flex; flex-direction: column; - gap: 0.25em; + gap: var(--gap-inline); flex-grow: 1; flex-basis: 60%; /* avoid wrapping the "item-trailing" too aggressively */ min-width: 0; /* make the "text truncate" work, otherwise the flex axis is not limited and the text just overflows */ @@ -52,7 +52,7 @@ .items-with-main > .item .item-header { display: flex; - gap: .25rem; + gap: var(--gap-inline); justify-content: space-between; flex-wrap: wrap; } @@ -63,7 +63,7 @@ .items-with-main > .item .item-trailing { display: flex; - gap: 0.5rem; + gap: var(--gap-block); align-items: center; flex-grow: 0; flex-wrap: wrap; @@ -92,7 +92,7 @@ display: flex; align-items: center; flex-wrap: wrap; - gap: .25rem; + gap: var(--gap-inline); color: var(--color-text-light-2); overflow-wrap: anywhere; } diff --git a/web_src/css/shared/milestone.css b/web_src/css/shared/milestone.css deleted file mode 100644 index 47e822f8d3a..00000000000 --- a/web_src/css/shared/milestone.css +++ /dev/null @@ -1,62 +0,0 @@ -.milestone-list { - list-style: none; -} - -.milestone-card { - width: 100%; - padding-top: 10px; - padding-bottom: 10px; -} - -.milestone-card + .milestone-card { - border-top: 1px solid var(--color-secondary); -} - -.milestone-card .render-content { - padding-top: 10px; -} - -.milestone-header progress { - width: 200px; - height: 16px; -} - -.milestone-header { - display: flex; - align-items: center; - margin: 0; - flex-wrap: wrap; - justify-content: space-between; -} - -.milestone-toolbar { - padding-top: 5px; - display: flex; - flex-wrap: wrap; - gap: 8px; - justify-content: space-between; -} - -.milestone-toolbar .group { - color: var(--color-text-light-2); - display: flex; - flex-wrap: wrap; - gap: 8px; -} - -.milestone-toolbar .group > a { - font-size: 15px; - color: var(--color-text-light-2); -} - -.milestone-toolbar .group > a:hover { - color: var(--color-text); -} - -@media (max-width: 767.98px) { - .milestone-card { - display: flex; - flex-direction: column; - gap: 8px; - } -} From 743bbaa9c2a77894302d819a48c33c36cee31946 Mon Sep 17 00:00:00 2001 From: Sandro Date: Sat, 6 Jun 2026 13:06:08 +0200 Subject: [PATCH 18/40] fix: refactor git error handling and make archive streaming handle non-existing commit id (#38007) Co-authored-by: wxiaoguang --- modules/git/gitcmd/error.go | 62 +++++++++++++++--------- modules/git/remote.go | 6 +-- modules/git/repo_commit.go | 6 +-- modules/git/repo_commit_nogogit.go | 14 +++--- modules/git/tree_nogogit.go | 3 +- routers/api/v1/repo/pull.go | 2 +- routers/web/repo/pull.go | 4 +- services/repository/archiver/archiver.go | 6 +-- services/wiki/wiki.go | 2 +- tests/integration/wiki_test.go | 2 +- 10 files changed, 57 insertions(+), 50 deletions(-) diff --git a/modules/git/gitcmd/error.go b/modules/git/gitcmd/error.go index 436f4e18ae4..8d5ec5f5e69 100644 --- a/modules/git/gitcmd/error.go +++ b/modules/git/gitcmd/error.go @@ -9,6 +9,8 @@ import ( "fmt" "os/exec" "strings" + + "gitea.dev/modules/util" ) type RunStdError interface { @@ -41,32 +43,14 @@ func (r *runStdError) Stderr() string { } func ErrorAsStderr(err error) (string, bool) { - var runErr RunStdError - if errors.As(err, &runErr) { + if runErr, ok := errors.AsType[RunStdError](err); ok { return runErr.Stderr(), true } return "", false } -func StderrHasPrefix(err error, prefix string) bool { - stderr, ok := ErrorAsStderr(err) - if !ok { - return false - } - return strings.HasPrefix(stderr, prefix) -} - -func StderrContains(err error, sub string) bool { - stderr, ok := ErrorAsStderr(err) - if !ok { - return false - } - return strings.Contains(stderr, sub) -} - func IsErrorExitCode(err error, code int) bool { - var exitError *exec.ExitError - if errors.As(err, &exitError) { + if exitError, ok := errors.AsType[*exec.ExitError](err); ok { return exitError.ExitCode() == code } return false @@ -85,11 +69,41 @@ func IsErrorCanceledOrKilled(err error) bool { return errors.Is(err, context.Canceled) || IsErrorSignalKilled(err) } -func IsStdErrorNotValidObjectName(err error) bool { +type StderrPrefix string + +type StderrSubStr string + +const ( + StderrNotValidObjectName StderrPrefix = "fatal: not a valid object name" + StderrNotTreeObject StderrPrefix = "fatal: not a tree object" + StderrPathSpec StderrPrefix = "fatal: pathspec" + StderrBadRevision StderrPrefix = "fatal: bad revision" + + StderrNoSuchRemote1 StderrPrefix = "fatal: no such remote" // git < 2.30, exit status 128 + StderrNoSuchRemote2 StderrPrefix = "error: no such remote" // git >= 2.30. exit status 2 + + // fatal: ambiguous argument 'origin': unknown revision or path not in the working tree. + StderrUnknownRevisionOrPath StderrSubStr = "unknown revision or path not in the working tree" +) + +func IsStderr[T StderrPrefix | StderrSubStr](err error, check T) bool { stderr, ok := ErrorAsStderr(err) - // Git is lowercasing the "fatal: Not a valid object name" error message - // ref: https://lore.kernel.org/git/pull.2052.git.1771836302101.gitgitgadget@gmail.com - return ok && strings.Contains(strings.ToLower(stderr), "fatal: not a valid object name") + if !ok { + return false + } + checkLen := len(check) + if len(stderr) < checkLen { + return false + } + switch any(check).(type) { + case StderrPrefix: + // Git is lowercasing the "fatal: Not a valid object name" error message + // ref: https://lore.kernel.org/git/pull.2052.git.1771836302101.gitgitgadget@gmail.com + return util.AsciiEqualFold(stderr[:checkLen], string(check)) + case StderrSubStr: + return strings.Contains(stderr, string(check)) + } + return false } type pipelineError struct { diff --git a/modules/git/remote.go b/modules/git/remote.go index b4fe6cfd100..e94ca374e4d 100644 --- a/modules/git/remote.go +++ b/modules/git/remote.go @@ -66,11 +66,7 @@ func (err *ErrInvalidCloneAddr) Unwrap() error { // IsRemoteNotExistError checks the prefix of the error message to see whether a remote does not exist. func IsRemoteNotExistError(err error) bool { - // see: https://github.com/go-gitea/gitea/issues/32889#issuecomment-2571848216 - // Should not add space in the end, sometimes git will add a `:` - prefix1 := "fatal: No such remote" // git < 2.30, exit status 128 - prefix2 := "error: No such remote" // git >= 2.30. exit status 2 - return gitcmd.StderrHasPrefix(err, prefix1) || gitcmd.StderrHasPrefix(err, prefix2) + return gitcmd.IsStderr(err, gitcmd.StderrNoSuchRemote1) || gitcmd.IsStderr(err, gitcmd.StderrNoSuchRemote2) } // ParseRemoteAddr checks if given remote address is valid, diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index b955f2709b3..20827062df3 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -24,9 +24,9 @@ func (repo *Repository) GetTagCommitID(name string) (string, error) { return repo.GetRefCommitID(TagPrefix + name) } -// GetCommit returns commit object of by ID string. -func (repo *Repository) GetCommit(commitID string) (*Commit, error) { - id, err := repo.ConvertToGitID(commitID) +// GetCommit returns a commit object of by the git ref. +func (repo *Repository) GetCommit(ref string) (*Commit, error) { + id, err := repo.ConvertToGitID(ref) if err != nil { return nil, err } diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go index c90650532b8..d8b842beeed 100644 --- a/modules/git/repo_commit_nogogit.go +++ b/modules/git/repo_commit_nogogit.go @@ -109,16 +109,16 @@ func (repo *Repository) getCommitWithBatch(batch CatFileBatch, id ObjectID) (*Co } } -// ConvertToGitID returns a GitHash object from a potential ID string -func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) { +// ConvertToGitID returns a git object ID from the git ref, it doesn't guarantee the returned ID really exists +func (repo *Repository) ConvertToGitID(ref string) (ObjectID, error) { objectFormat, err := repo.GetObjectFormat() if err != nil { return nil, err } - if len(commitID) == objectFormat.FullLength() && objectFormat.IsValid(commitID) { - ID, err := NewIDFromString(commitID) + if len(ref) == objectFormat.FullLength() && objectFormat.IsValid(ref) { + id, err := NewIDFromString(ref) if err == nil { - return ID, nil + return id, nil } } @@ -127,10 +127,10 @@ func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) { return nil, err } defer cancel() - info, err := batch.QueryInfo(commitID) + info, err := batch.QueryInfo(ref) if err != nil { if IsErrNotExist(err) { - return nil, ErrNotExist{commitID, ""} + return nil, ErrNotExist{ref, ""} } return nil, err } diff --git a/modules/git/tree_nogogit.go b/modules/git/tree_nogogit.go index 1d78ecdee24..ebb4c7bb122 100644 --- a/modules/git/tree_nogogit.go +++ b/modules/git/tree_nogogit.go @@ -7,7 +7,6 @@ package git import ( "io" - "strings" "gitea.dev/modules/git/gitcmd" ) @@ -65,7 +64,7 @@ func (t *Tree) ListEntries() (Entries, error) { stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-l").AddDynamicArguments(t.ID.String()).WithDir(t.repo.Path).RunStdBytes(t.repo.Ctx) if runErr != nil { - if gitcmd.IsStdErrorNotValidObjectName(runErr) || strings.Contains(runErr.Error(), "fatal: not a tree object") { + if gitcmd.IsStderr(runErr, gitcmd.StderrNotValidObjectName) || gitcmd.IsStderr(runErr, gitcmd.StderrNotTreeObject) { return nil, ErrNotExist{ ID: t.ID.String(), } diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 123c0c671a9..6f6ba77fae0 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -1435,7 +1435,7 @@ func GetPullRequestCommits(ctx *context.APIContext) { compareInfo, err = git_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, git.RefNameFromBranch(pr.BaseBranch), git.RefName(pr.GetGitHeadRefName()), false, false) } - if gitcmd.StderrHasPrefix(err, "fatal: bad revision") { + if gitcmd.IsStderr(err, gitcmd.StderrBadRevision) { ctx.APIError(http.StatusNotFound, "invalid base branch or revision") return } else if err != nil { diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 6862a3efc0f..eb6d5408a52 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -376,9 +376,7 @@ func (prInfo *pullRequestViewInfo) prepareViewFillCompareInfo(ctx *context.Conte pull := prInfo.issue.PullRequest prInfo.CompareInfo, err = git_service.GetCompareInfo(ctx, ctx.Repo.Repository, ctx.Repo.Repository, ctx.Repo.GitRepo, baseRef, git.RefName(pull.GetGitHeadRefName()), false, false) if err != nil { - isKnownErrorForBroken := gitcmd.IsStdErrorNotValidObjectName(err) || - // fatal: ambiguous argument 'origin': unknown revision or path not in the working tree. - gitcmd.StderrContains(err, "unknown revision or path not in the working tree") + isKnownErrorForBroken := gitcmd.IsStderr(err, gitcmd.StderrNotValidObjectName) || gitcmd.IsStderr(err, gitcmd.StderrUnknownRevisionOrPath) if !isKnownErrorForBroken { log.Error("GetCompareInfo: %v", err) } diff --git a/services/repository/archiver/archiver.go b/services/repository/archiver/archiver.go index 93e83b51de5..2b781185564 100644 --- a/services/repository/archiver/archiver.go +++ b/services/repository/archiver/archiver.go @@ -56,13 +56,13 @@ func NewRequest(repo *repo_model.Repository, gitRepo *git.Repository, archiveRef } // Get corresponding commit. - commitID, err := gitRepo.ConvertToGitID(archiveRefShortName) + commit, err := gitRepo.GetCommit(archiveRefShortName) if err != nil { return nil, util.NewNotExistErrorf("unrecognized repository reference: %s", archiveRefShortName) } r := &ArchiveRequest{Repo: repo, archiveRefShortName: archiveRefShortName, Type: archiveType, Paths: paths} - r.CommitID = commitID.String() + r.CommitID = commit.ID.String() return r, nil } @@ -330,7 +330,7 @@ func ServeRepoArchive(ctx *gitea_context.Base, archiveReq *ArchiveRequest) error // because errors may happen in git command and such cases aren't in our control. httplib.ServeSetHeaders(ctx.Resp, httplib.ServeHeaderOptions{Filename: downloadName}) if err := archiveReq.Stream(ctx, ctx.Resp); err != nil && !ctx.Written() { - if gitcmd.StderrHasPrefix(err, "fatal: pathspec") { + if gitcmd.IsStderr(err, gitcmd.StderrPathSpec) || gitcmd.IsStderr(err, gitcmd.StderrNotTreeObject) { return util.NewInvalidArgumentErrorf("path doesn't exist or is invalid") } return fmt.Errorf("archive repo %s: failed to stream: %w", archiveReq.Repo.FullName(), err) diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index 9fcc5750ec9..abe846ae063 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -59,7 +59,7 @@ func prepareGitPath(gitRepo *git.Repository, defaultWikiBranch string, wikiPath // Look for both files filesInIndex, err := gitRepo.LsTree(defaultWikiBranch, unescaped, gitPath) if err != nil { - if gitcmd.IsStdErrorNotValidObjectName(err) { + if gitcmd.IsStderr(err, gitcmd.StderrNotValidObjectName) { return false, gitPath, nil // branch doesn't exist } log.Error("Wiki LsTree failed, err: %v", err) diff --git a/tests/integration/wiki_test.go b/tests/integration/wiki_test.go index 0c1b00e5076..11eef67c685 100644 --- a/tests/integration/wiki_test.go +++ b/tests/integration/wiki_test.go @@ -72,7 +72,7 @@ EOF // reader can't push _, _, runErr = gitcmd.NewCommand("push", "origin", "refs/heads/master").WithDir(dstLocalPath).RunStdString(t.Context()) - assert.True(t, gitcmd.StderrContains(runErr, "remote: Repository not found\n")) + assert.Contains(t, runErr.Stderr(), "remote: Repository not found\n") req := NewRequest(t, "GET", "/user2/repo1/wiki/raw/Home.md") resp := MakeRequest(t, req, http.StatusOK) assert.Contains(t, resp.Body.String(), "This is the home page!") From 42513398c05ca6bdf71da76cb6f9baaebe8cb924 Mon Sep 17 00:00:00 2001 From: bircni Date: Sat, 6 Jun 2026 17:44:56 +0200 Subject: [PATCH 19/40] fix(lfs): reject unknown SSH LFS sub-verbs to prevent auth bypass (#38008) An authenticated SSH user could pass a malformed sub-verb (e.g. `git-lfs-authenticate badverb`) so getAccessMode falls through to AccessModeNone (0). The permission check in routers/private/serv.go then evaluates `userMode < 0` which is always false, granting a valid LFS JWT for any private repository. The HTTP LFS handler only validates the Op claim on writes, so the token works for downloads. Validate the sub-verb in runServ before calling getAccessMode and fail fast for anything other than upload/download. --- cmd/serv.go | 23 ++++++++++++-------- cmd/serv_test.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 cmd/serv_test.go diff --git a/cmd/serv.go b/cmd/serv.go index 93009cd32a3..b39076f6a78 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -113,23 +113,25 @@ func handleCliResponseExtra(extra private.ResponseExtra) error { return nil } -func getAccessMode(verb, lfsVerb string) perm.AccessMode { +// getAccessMode maps an SSH git/LFS verb to the access mode it requires, with +// ok=false for an unrecognised verb. Callers MUST reject the request when ok is +// false: AccessModeNone would otherwise pass the `userMode < mode` permission +// check in routers/private/serv.go and grant access. +func getAccessMode(verb, lfsVerb string) (mode perm.AccessMode, ok bool) { switch verb { case git.CmdVerbUploadPack, git.CmdVerbUploadArchive: - return perm.AccessModeRead + return perm.AccessModeRead, true case git.CmdVerbReceivePack: - return perm.AccessModeWrite + return perm.AccessModeWrite, true case git.CmdVerbLfsAuthenticate, git.CmdVerbLfsTransfer: switch lfsVerb { case git.CmdSubVerbLfsUpload: - return perm.AccessModeWrite + return perm.AccessModeWrite, true case git.CmdSubVerbLfsDownload: - return perm.AccessModeRead + return perm.AccessModeRead, true } } - // should be unreachable - setting.PanicInDevOrTesting("unknown verb: %s %s", verb, lfsVerb) - return perm.AccessModeNone + return perm.AccessModeNone, false } func runServ(ctx context.Context, c *cli.Command) error { @@ -247,7 +249,10 @@ func runServ(ctx context.Context, c *cli.Command) error { } } - requestedMode := getAccessMode(verb, lfsVerb) + requestedMode, ok := getAccessMode(verb, lfsVerb) + if !ok { + return fail(ctx, "Unknown git command", "Unknown git command %s %s", verb, lfsVerb) + } results, extra := private.ServCommand(ctx, keyID, username, reponame, requestedMode, verb, lfsVerb) if extra.HasError() { diff --git a/cmd/serv_test.go b/cmd/serv_test.go new file mode 100644 index 00000000000..3bdcba1df37 --- /dev/null +++ b/cmd/serv_test.go @@ -0,0 +1,56 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "testing" + + "gitea.dev/models/perm" + "gitea.dev/modules/git" + + "github.com/stretchr/testify/assert" +) + +func TestGetAccessMode(t *testing.T) { + cases := []struct { + verb, lfsVerb string + expected perm.AccessMode + }{ + {git.CmdVerbUploadPack, "", perm.AccessModeRead}, + {git.CmdVerbUploadArchive, "", perm.AccessModeRead}, + {git.CmdVerbReceivePack, "", perm.AccessModeWrite}, + {git.CmdVerbLfsAuthenticate, git.CmdSubVerbLfsUpload, perm.AccessModeWrite}, + {git.CmdVerbLfsAuthenticate, git.CmdSubVerbLfsDownload, perm.AccessModeRead}, + {git.CmdVerbLfsTransfer, git.CmdSubVerbLfsUpload, perm.AccessModeWrite}, + {git.CmdVerbLfsTransfer, git.CmdSubVerbLfsDownload, perm.AccessModeRead}, + } + for _, tc := range cases { + t.Run(tc.verb+"/"+tc.lfsVerb, func(t *testing.T) { + mode, ok := getAccessMode(tc.verb, tc.lfsVerb) + assert.True(t, ok) + assert.Equal(t, tc.expected, mode) + }) + } +} + +// TestGetAccessModeUnknownVerb locks in the invariant that getAccessMode reports +// ok=false for unrecognised verbs and LFS sub-verbs, so runServ rejects them. An +// unknown verb has no valid access mode; if it were treated as AccessModeNone (0) +// it would pass the `userMode < mode` permission check in routers/private/serv.go +// and hand out valid LFS JWTs for any private repository. +func TestGetAccessModeUnknownVerb(t *testing.T) { + cases := []struct{ verb, lfsVerb string }{ + {git.CmdVerbLfsAuthenticate, ""}, + {git.CmdVerbLfsAuthenticate, "badverb"}, + {git.CmdVerbLfsTransfer, "badverb"}, + {"git-unknown-verb", ""}, + } + for _, tc := range cases { + t.Run(tc.verb+"/"+tc.lfsVerb, func(t *testing.T) { + mode, ok := getAccessMode(tc.verb, tc.lfsVerb) + assert.False(t, ok) + assert.Equal(t, perm.AccessModeNone, mode) + }) + } +} From c43eb7c33a100ffc7b2367adf165f7085e0ccdc5 Mon Sep 17 00:00:00 2001 From: bircni Date: Sun, 7 Jun 2026 00:07:47 +0200 Subject: [PATCH 20/40] fix(auth): do not auto-reactivate disabled users on OAuth2 callback (#38009) The OAuth2 sign-in callback unconditionally set IsActive=true on the local user row whenever the IdP authenticated them, silently undoing an administrator's "Disable Account" action and granting the user a fresh session in the same response. Treat the local IsActive flag as an authoritative admin override: inactive users get a session and are routed through the existing activate / prohibit-login pages by verifyAuthWithOptions, matching the local-credentials sign-in path. Adds an integration regression test that disables a linked local user and asserts the row stays IsActive=false after a full OIDC callback. --------- Co-authored-by: wxiaoguang --- models/unittest/unit_tests.go | 2 +- routers/web/auth/oauth.go | 16 ++++++- services/auth/source/oauth2/source_sync.go | 4 +- tests/integration/auth_oauth2_test.go | 54 ++++++++++++++++++++++ 4 files changed, 71 insertions(+), 5 deletions(-) diff --git a/models/unittest/unit_tests.go b/models/unittest/unit_tests.go index 45ea0d175ea..c9895500214 100644 --- a/models/unittest/unit_tests.go +++ b/models/unittest/unit_tests.go @@ -47,7 +47,7 @@ func OrderBy(orderBy string) any { } func whereOrderConditions(e db.Engine, conditions []any) db.Engine { - orderBy := "id" // query must have the "ORDER BY", otherwise the result is not deterministic + orderBy := "id" // query must have the "ORDER BY", otherwise the result is not deterministic. FIXME: some tables do not have "id" column for _, condition := range conditions { switch cond := condition.(type) { case *testCond: diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index f7d9c3c34ac..06c1b20d578 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -364,9 +364,21 @@ func handleOAuth2SignIn(ctx *context.Context, authSource *auth.Source, u *user_m opts := &user_service.UpdateOptions{} - // Reactivate user if they are deactivated + // HINT: OAUTH-AUTO-SYNC-USER-ACTIVATION: see services/auth/source/oauth2/source_sync.go + // Reactivate user only if they were disabled by the OAuth2 auto sync cron (invalid_grant), + // which clears AccessToken/RefreshToken/ExpiresAt on the ExternalLoginUser row + // An admin-disabled user has no such signature, so we leave IsActive alone + // and let verifyAuthWithOptions route them through the prohibit-login / activate page. if !u.IsActive { - opts.IsActive = optional.Some(true) + extLogin, hasExt, err := user_model.GetExternalLogin(ctx, authSource.ID, gothUser.UserID) + if err != nil { + ctx.ServerError("GetExternalLogin", err) + return + } + isDisabledByAutoSync := hasExt && extLogin.RefreshToken == "" + if isDisabledByAutoSync { + opts.IsActive = optional.Some(true) + } } if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval { diff --git a/services/auth/source/oauth2/source_sync.go b/services/auth/source/oauth2/source_sync.go index 8fc58f9ae10..3123e54bbb2 100644 --- a/services/auth/source/oauth2/source_sync.go +++ b/services/auth/source/oauth2/source_sync.go @@ -88,8 +88,8 @@ func (source *Source) refresh(ctx context.Context, provider goth.Provider, u *us } } - // Delete stored tokens, since they are invalid. This - // also provents us from checking this in subsequent runs. + // HINT: OAUTH-AUTO-SYNC-USER-ACTIVATION + // Delete stored tokens, since they are invalid. This also prevents us from checking this in subsequent runs. u.AccessToken = "" u.RefreshToken = "" u.ExpiresAt = time.Time{} diff --git a/tests/integration/auth_oauth2_test.go b/tests/integration/auth_oauth2_test.go index 3bb3d56e7bd..7978c3bd048 100644 --- a/tests/integration/auth_oauth2_test.go +++ b/tests/integration/auth_oauth2_test.go @@ -13,6 +13,7 @@ import ( "time" auth_model "gitea.dev/models/auth" + "gitea.dev/models/db" "gitea.dev/models/unittest" user_model "gitea.dev/models/user" "gitea.dev/modules/json" @@ -23,6 +24,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "xorm.io/builder" ) // TestMigrateAzureADV2ToOIDC simulates a login source migration from the Azure AD V2 OAuth2 provider to the OpenID Connect provider, @@ -191,6 +193,58 @@ func TestOIDCIgnoresStaleExternalLoginLinks(t *testing.T) { }) } +// TestOAuth2CallbackReactivationGating exercises the gate in handleOAuth2SignIn: +// an inactive user can only be reactivated when who was disabled by auto-sync +func TestOAuth2CallbackReactivationGating(t *testing.T) { + defer tests.PrepareTestEnv(t)() + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() + defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameUserid)() + + srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: "test-sub", Email: "test@example.com", Name: "Test User"}) + addOAuth2Source(t, "test-oauth-source", oauth2.Source{ + Provider: "openidConnect", + ClientID: "test-client-id", + ClientSecret: "test-client-secret", + OpenIDConnectAutoDiscoveryURL: srv.URL + "/.well-known/openid-configuration", + }) + authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), "test-oauth-source") + require.NoError(t, err) + + u := &user_model.User{Name: "test-user", Email: "test@example.com"} + require.NoError(t, user_model.CreateUser(t.Context(), u, &user_model.Meta{})) + + extLink := &user_model.ExternalLoginUser{ + UserID: u.ID, + LoginSourceID: authSource.ID, + Provider: authSource.Name, + ExternalID: "test-sub", + } + require.NoError(t, user_model.LinkExternalToUser(t.Context(), u, extLink)) + + prepareUserExternalLink := func(t *testing.T, refreshToken string) { + err := user_model.UpdateUserCols(t.Context(), &user_model.User{ID: u.ID, IsActive: false}, "is_active") + require.NoError(t, err) + _, err = db.GetEngine(t.Context()).Where(builder.Eq{"user_id": u.ID}).Cols("refresh_token"). + Update(&user_model.ExternalLoginUser{RefreshToken: refreshToken}) + require.NoError(t, err) + require.False(t, unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}).IsActive) + } + + t.Run("admin-disabled user is not reactivated", func(t *testing.T) { + prepareUserExternalLink(t, "non-empty-refresh-token") + doOIDCSignIn(t, authSource.Name) + after := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}) + assert.False(t, after.IsActive, "OAuth callback must not re-enable an administrator-disabled account") + }) + + t.Run("auto-sync-disabled user is reactivated", func(t *testing.T) { + prepareUserExternalLink(t, "" /* empty refresh token */) + doOIDCSignIn(t, authSource.Name) + after := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}) + assert.True(t, after.IsActive, "OAuth callback must reactivate a sync-disabled account on successful login") + }) +} + // FakeOIDCConfig holds configuration for the fake OIDC server used in tests. type FakeOIDCConfig struct { Sub string From 5fe4f962e87341cb5221f5069366bc1cf85d59ed Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Jun 2026 06:19:39 +0000 Subject: [PATCH 21/40] refactor(api): clarify APIError message usage and fix legacy lint error (#38012) Avoid unclear & fragile "any" tricks, fix various abuses Co-authored-by: wxiaoguang --- modules/markup/markdown/ast.go | 17 +++- .../markup/markdown/transform_blockquote.go | 5 +- modules/markup/markdown/transform_codespan.go | 9 +- routers/api/v1/admin/org.go | 2 +- routers/api/v1/admin/user.go | 46 +++++----- routers/api/v1/api.go | 4 +- routers/api/v1/notify/notifications.go | 4 +- routers/api/v1/notify/repo.go | 2 +- routers/api/v1/notify/threads.go | 4 +- routers/api/v1/notify/user.go | 2 +- routers/api/v1/org/action.go | 22 ++--- routers/api/v1/org/avatar.go | 2 +- routers/api/v1/org/label.go | 4 +- routers/api/v1/org/member.go | 2 +- routers/api/v1/org/org.go | 8 +- routers/api/v1/org/team.go | 4 +- routers/api/v1/packages/package.go | 16 ++-- routers/api/v1/repo/action.go | 44 ++++----- routers/api/v1/repo/avatar.go | 2 +- routers/api/v1/repo/blob.go | 2 +- routers/api/v1/repo/branch.go | 50 +++++------ routers/api/v1/repo/collaborators.go | 12 +-- routers/api/v1/repo/commits.go | 4 +- routers/api/v1/repo/download.go | 6 +- routers/api/v1/repo/file.go | 12 +-- routers/api/v1/repo/fork.go | 4 +- routers/api/v1/repo/issue.go | 20 ++--- routers/api/v1/repo/issue_attachment.go | 10 +-- routers/api/v1/repo/issue_comment.go | 12 +-- .../api/v1/repo/issue_comment_attachment.go | 12 +-- routers/api/v1/repo/issue_label.go | 2 +- routers/api/v1/repo/issue_lock.go | 5 +- routers/api/v1/repo/issue_pin.go | 2 +- routers/api/v1/repo/issue_reaction.go | 12 +-- routers/api/v1/repo/issue_subscription.go | 2 +- routers/api/v1/repo/issue_tracked_time.go | 19 ++-- routers/api/v1/repo/key.go | 2 +- routers/api/v1/repo/label.go | 4 +- routers/api/v1/repo/migrate.go | 10 +-- routers/api/v1/repo/mirror.go | 17 ++-- routers/api/v1/repo/pull.go | 44 ++++----- routers/api/v1/repo/pull_review.go | 28 +++--- routers/api/v1/repo/release.go | 10 +-- routers/api/v1/repo/release_attachment.go | 6 +- routers/api/v1/repo/repo.go | 34 +++---- routers/api/v1/repo/status.go | 2 +- routers/api/v1/repo/tag.go | 18 ++-- routers/api/v1/repo/teams.go | 6 +- routers/api/v1/repo/transfer.go | 22 ++--- routers/api/v1/repo/wiki.go | 10 +-- routers/api/v1/shared/action.go | 7 +- routers/api/v1/shared/block.go | 4 +- routers/api/v1/user/action.go | 22 ++--- routers/api/v1/user/app.go | 7 +- routers/api/v1/user/avatar.go | 2 +- routers/api/v1/user/email.go | 2 +- routers/api/v1/user/follower.go | 2 +- routers/api/v1/user/gpg_key.go | 2 +- routers/api/v1/user/star.go | 2 +- routers/api/v1/user/watch.go | 2 +- routers/api/v1/utils/sort.go | 4 +- services/context/api.go | 89 ++++++++++--------- services/context/package.go | 21 ++--- services/context/user.go | 15 ++-- 64 files changed, 395 insertions(+), 384 deletions(-) diff --git a/modules/markup/markdown/ast.go b/modules/markup/markdown/ast.go index f29f8837349..fae9d06e2a9 100644 --- a/modules/markup/markdown/ast.go +++ b/modules/markup/markdown/ast.go @@ -95,13 +95,13 @@ type Icon struct { // ColorPreview is an inline for a color preview type ColorPreview struct { ast.BaseInline - Color []byte + Color string } // Dump implements Node.Dump. func (n *ColorPreview) Dump(source []byte, level int) { m := map[string]string{} - m["Color"] = string(n.Color) + m["Color"] = n.Color ast.DumpHelper(n, source, level, m, nil) } @@ -114,7 +114,7 @@ func (n *ColorPreview) Kind() ast.NodeKind { } // NewColorPreview returns a new Span node. -func NewColorPreview(color []byte) *ColorPreview { +func NewColorPreview(color string) *ColorPreview { return &ColorPreview{ BaseInline: ast.BaseInline{}, Color: color, @@ -170,3 +170,14 @@ func (n *RawHTML) Kind() ast.NodeKind { func NewRawHTML(rawHTML template.HTML) *RawHTML { return &RawHTML{rawHTML: rawHTML} } + +func childSingleText(node ast.Node, source []byte) (string, bool) { + if node.FirstChild() == nil || node.FirstChild() != node.LastChild() { + return "", false + } + c, ok := node.FirstChild().(*ast.Text) + if !ok { + return "", false + } + return string(c.Segment.Value(source)), true +} diff --git a/modules/markup/markdown/transform_blockquote.go b/modules/markup/markdown/transform_blockquote.go index b6cbef9a0a8..2ec7cec9d5e 100644 --- a/modules/markup/markdown/transform_blockquote.go +++ b/modules/markup/markdown/transform_blockquote.go @@ -46,7 +46,10 @@ func (g *ASTTransformer) extractBlockquoteAttentionEmphasis(firstParagraph ast.N if !ok { return "", nil } - val1 := string(node1.Text(reader.Source())) //nolint:staticcheck // Text is deprecated + val1, ok := childSingleText(node1, reader.Source()) + if !ok { + return "", nil + } attentionType := strings.ToLower(val1) if g.attentionTypes.Contains(attentionType) { return attentionType, []ast.Node{node1} diff --git a/modules/markup/markdown/transform_codespan.go b/modules/markup/markdown/transform_codespan.go index 900c38b129e..df4a56fd9e5 100644 --- a/modules/markup/markdown/transform_codespan.go +++ b/modules/markup/markdown/transform_codespan.go @@ -39,7 +39,7 @@ func (r *HTMLRenderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Nod r.Writer.RawWrite(w, value) } case *ColorPreview: - _ = r.renderInternal.FormatWithSafeAttrs(w, ``, string(v.Color)) + _ = r.renderInternal.FormatWithSafeAttrs(w, ``, v.Color) } } return ast.WalkSkipChildren, nil @@ -68,8 +68,11 @@ func cssColorHandler(value string) bool { } func (g *ASTTransformer) transformCodeSpan(_ *markup.RenderContext, v *ast.CodeSpan, reader text.Reader) { - colorContent := v.Text(reader.Source()) //nolint:staticcheck // Text is deprecated - if cssColorHandler(string(colorContent)) { + colorContent, ok := childSingleText(v, reader.Source()) + if !ok { + return + } + if cssColorHandler(colorContent) { v.AppendChild(v, NewColorPreview(colorContent)) } } diff --git a/routers/api/v1/admin/org.go b/routers/api/v1/admin/org.go index 6375748086c..4713c7a1c78 100644 --- a/routers/api/v1/admin/org.go +++ b/routers/api/v1/admin/org.go @@ -67,7 +67,7 @@ func CreateOrg(ctx *context.APIContext) { db.IsErrNameReserved(err) || db.IsErrNameCharsNotAllowed(err) || db.IsErrNamePatternNotAllowed(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else { ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index d7f10b75b0b..9441fe0c18e 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -40,7 +40,7 @@ func parseAuthSource(ctx *context.APIContext, u *user_model.User, sourceID int64 source, err := auth.GetSourceByID(ctx, sourceID) if err != nil { if auth.IsErrSourceNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -97,14 +97,12 @@ func CreateUser(ctx *context.APIContext) { if u.LoginType == auth.Plain { if len(form.Password) < setting.MinPasswordLength { - err := errors.New("PasswordIsRequired") - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, "PasswordIsRequired") return } if !password.IsComplexEnough(form.Password) { - err := errors.New("PasswordComplexity") - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, "PasswordComplexity") return } @@ -112,7 +110,7 @@ func CreateUser(ctx *context.APIContext) { if password.IsErrIsPwnedRequest(err) { log.Error(err.Error()) } - ctx.APIError(http.StatusBadRequest, errors.New("PasswordPwned")) + ctx.APIError(http.StatusBadRequest, "PasswordPwned") return } } @@ -143,7 +141,7 @@ func CreateUser(ctx *context.APIContext) { user_model.IsErrEmailCharIsNotSupported(err) || user_model.IsErrEmailInvalid(err) || db.IsErrNamePatternNotAllowed(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -204,11 +202,11 @@ func EditUser(ctx *context.APIContext) { if err := user_service.UpdateAuth(ctx, ctx.ContextUser, authOpts); err != nil { switch { case errors.Is(err, password.ErrMinLength): - ctx.APIError(http.StatusBadRequest, fmt.Errorf("password must be at least %d characters", setting.MinPasswordLength)) + ctx.APIError(http.StatusBadRequest, fmt.Sprintf("password must be at least %d characters", setting.MinPasswordLength)) case errors.Is(err, password.ErrComplexity): - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) case errors.Is(err, password.ErrIsPwned), password.IsErrIsPwnedRequest(err): - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) default: ctx.APIErrorInternal(err) } @@ -222,9 +220,9 @@ func EditUser(ctx *context.APIContext) { if !user_model.IsEmailDomainAllowed(*form.Email) { err = fmt.Errorf("the domain of user email %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", *form.Email) } - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) case user_model.IsErrEmailAlreadyUsed(err): - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) default: ctx.APIErrorInternal(err) } @@ -249,7 +247,7 @@ func EditUser(ctx *context.APIContext) { if err := user_service.UpdateUser(ctx, ctx.ContextUser, opts); err != nil { if user_model.IsErrDeleteLastAdminUser(err) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -289,13 +287,13 @@ func DeleteUser(ctx *context.APIContext) { // "$ref": "#/responses/validationError" if ctx.ContextUser.IsOrganization() { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("%s is an organization not a user", ctx.ContextUser.Name)) + ctx.APIError(http.StatusUnprocessableEntity, "target is an organization but not user") return } // admin should not delete themself if ctx.ContextUser.ID == ctx.Doer.ID { - ctx.APIError(http.StatusUnprocessableEntity, errors.New("you cannot delete yourself")) + ctx.APIError(http.StatusUnprocessableEntity, "you cannot delete yourself") return } @@ -304,7 +302,7 @@ func DeleteUser(ctx *context.APIContext) { org_model.IsErrUserHasOrgs(err) || packages_model.IsErrUserOwnPackages(err) || user_model.IsErrDeleteLastAdminUser(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -471,13 +469,11 @@ func SearchUsers(ctx *context.APIContext) { var visible []api.VisibleType visibilityParam := ctx.FormString("visibility") - if len(visibilityParam) > 0 { - if visibility, ok := api.VisibilityModes[visibilityParam]; ok { - visible = []api.VisibleType{visibility} - } else { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid visibility: \"%s\"", visibilityParam)) - return - } + if visibility, ok := api.VisibilityModes[visibilityParam]; ok { + visible = []api.VisibleType{visibility} + } else if visibilityParam != "" { + ctx.APIError(http.StatusUnprocessableEntity, "invalid visibility") + return } searchOpts := user_model.SearchUserOptions{ @@ -551,7 +547,7 @@ func RenameUser(ctx *context.APIContext) { // "$ref": "#/responses/validationError" if ctx.ContextUser.IsOrganization() { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("%s is an organization not a user", ctx.ContextUser.Name)) + ctx.APIError(http.StatusUnprocessableEntity, "target is an organization but not user") return } @@ -560,7 +556,7 @@ func RenameUser(ctx *context.APIContext) { // Check if username has been changed if err := user_service.RenameUser(ctx, ctx.ContextUser, newName, ctx.Doer); err != nil { if user_model.IsErrUserAlreadyExist(err) || db.IsErrNameReserved(err) || db.IsErrNamePatternNotAllowed(err) || db.IsErrNameCharsNotAllowed(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else { ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index eb5f52fd575..4248faea5d4 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -734,14 +734,14 @@ func mustEnableWiki(ctx *context.APIContext) { // FIXME: for consistency, maybe most mustNotBeArchived checks should be replaced with mustEnableEditor func mustNotBeArchived(ctx *context.APIContext) { if ctx.Repo.Repository.IsArchived { - ctx.APIError(http.StatusLocked, fmt.Errorf("%s is archived", ctx.Repo.Repository.FullName())) + ctx.APIError(http.StatusLocked, "repo is archived") return } } func mustEnableEditor(ctx *context.APIContext) { if !ctx.Repo.Repository.CanEnableEditor() { - ctx.APIError(http.StatusLocked, fmt.Errorf("%s is not allowed to edit", ctx.Repo.Repository.FullName())) + ctx.APIError(http.StatusLocked, "repo is not allowed to edit") return } } diff --git a/routers/api/v1/notify/notifications.go b/routers/api/v1/notify/notifications.go index 92aae46a784..8e032292c2e 100644 --- a/routers/api/v1/notify/notifications.go +++ b/routers/api/v1/notify/notifications.go @@ -28,7 +28,7 @@ func NewAvailable(ctx *context.APIContext) { Status: []activities_model.NotificationStatus{activities_model.NotificationStatusUnread}, }) if err != nil { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } @@ -38,7 +38,7 @@ func NewAvailable(ctx *context.APIContext) { func getFindNotificationOptions(ctx *context.APIContext) *activities_model.FindNotificationOptions { before, since, err := context.GetQueryBeforeSince(ctx.Base) if err != nil { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return nil } opts := &activities_model.FindNotificationOptions{ diff --git a/routers/api/v1/notify/repo.go b/routers/api/v1/notify/repo.go index 16996907508..7ba4469ff66 100644 --- a/routers/api/v1/notify/repo.go +++ b/routers/api/v1/notify/repo.go @@ -183,7 +183,7 @@ func ReadRepoNotifications(ctx *context.APIContext) { if len(qLastRead) > 0 { tmpLastRead, err := time.Parse(time.RFC3339, qLastRead) if err != nil { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) return } if !tmpLastRead.IsZero() { diff --git a/routers/api/v1/notify/threads.go b/routers/api/v1/notify/threads.go index 2b8c91169f8..c66db13f8b6 100644 --- a/routers/api/v1/notify/threads.go +++ b/routers/api/v1/notify/threads.go @@ -104,14 +104,14 @@ func getThread(ctx *context.APIContext) *activities_model.Notification { n, err := activities_model.GetNotificationByID(ctx, ctx.PathParamInt64("id")) if err != nil { if db.IsErrNotExist(err) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } return nil } if n.UserID != ctx.Doer.ID && !ctx.Doer.IsAdmin { - ctx.APIError(http.StatusForbidden, fmt.Errorf("only user itself and admin are allowed to read/change this thread %d", n.ID)) + ctx.APIError(http.StatusForbidden, fmt.Sprintf("only user itself and admin are allowed to read/change this thread %d", n.ID)) return nil } return n diff --git a/routers/api/v1/notify/user.go b/routers/api/v1/notify/user.go index 2e54eb178d4..9894f577c54 100644 --- a/routers/api/v1/notify/user.go +++ b/routers/api/v1/notify/user.go @@ -134,7 +134,7 @@ func ReadNotifications(ctx *context.APIContext) { if len(qLastRead) > 0 { tmpLastRead, err := time.Parse(time.RFC3339, qLastRead) if err != nil { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) return } if !tmpLastRead.IsZero() { diff --git a/routers/api/v1/org/action.go b/routers/api/v1/org/action.go index d63a134bd55..3f1135dcf3f 100644 --- a/routers/api/v1/org/action.go +++ b/routers/api/v1/org/action.go @@ -111,9 +111,9 @@ func (Action) CreateOrUpdateSecret(ctx *context.APIContext) { _, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.PathParam("secretname"), opt.Data, opt.Description) if err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -158,9 +158,9 @@ func (Action) DeleteSecret(ctx *context.APIContext) { err := secret_service.DeleteSecretByName(ctx, ctx.Org.Organization.ID, 0, ctx.PathParam("secretname")) if err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -277,7 +277,7 @@ func (Action) GetVariable(ctx *context.APIContext) { }) if err != nil { if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -327,9 +327,9 @@ func (Action) DeleteVariable(ctx *context.APIContext) { if err := actions_service.DeleteVariableByName(ctx, ctx.Org.Organization.ID, 0, ctx.PathParam("variablename")); err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -387,13 +387,13 @@ func (Action) CreateVariable(ctx *context.APIContext) { return } if v != nil && v.ID > 0 { - ctx.APIError(http.StatusConflict, util.NewAlreadyExistErrorf("variable name %s already exists", variableName)) + ctx.APIError(http.StatusConflict, "variable name already exists") return } if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value, opt.Description); err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -445,7 +445,7 @@ func (Action) UpdateVariable(ctx *context.APIContext) { }) if err != nil { if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -462,7 +462,7 @@ func (Action) UpdateVariable(ctx *context.APIContext) { if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else { ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/org/avatar.go b/routers/api/v1/org/avatar.go index 38b0655bea9..7e6f8a77c91 100644 --- a/routers/api/v1/org/avatar.go +++ b/routers/api/v1/org/avatar.go @@ -39,7 +39,7 @@ func UpdateAvatar(ctx *context.APIContext) { content, err := base64.StdEncoding.DecodeString(form.Image) if err != nil { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) return } diff --git a/routers/api/v1/org/label.go b/routers/api/v1/org/label.go index 4c3365e6a6d..443003d5de2 100644 --- a/routers/api/v1/org/label.go +++ b/routers/api/v1/org/label.go @@ -90,7 +90,7 @@ func CreateLabel(ctx *context.APIContext) { form.Color = strings.Trim(form.Color, " ") color, err := label.NormalizeColor(form.Color) if err != nil { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } form.Color = color @@ -209,7 +209,7 @@ func EditLabel(ctx *context.APIContext) { if form.Color != nil { color, err := label.NormalizeColor(*form.Color) if err != nil { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } l.Color = color diff --git a/routers/api/v1/org/member.go b/routers/api/v1/org/member.go index 957544d1047..11b46a05c1c 100644 --- a/routers/api/v1/org/member.go +++ b/routers/api/v1/org/member.go @@ -221,7 +221,7 @@ func checkCanChangeOrgUserStatus(ctx *context.APIContext, targetUser *user_model // allow org owners to change status of members isOwner, err := ctx.Org.Organization.IsOwnedBy(ctx, ctx.Doer.ID) if err != nil { - ctx.APIError(http.StatusInternalServerError, err) + ctx.APIErrorInternal(err) } else if !isOwner { ctx.APIError(http.StatusForbidden, "Cannot change member visibility") } diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go index 87e847d6421..16d3e230a01 100644 --- a/routers/api/v1/org/org.go +++ b/routers/api/v1/org/org.go @@ -256,7 +256,7 @@ func Create(ctx *context.APIContext) { // "$ref": "#/responses/validationError" form := web.GetForm(ctx).(*api.CreateOrgOption) if !ctx.Doer.CanCreateOrganization() { - ctx.APIError(http.StatusForbidden, nil) + ctx.APIError(http.StatusForbidden, "not allowed to create org") return } @@ -282,7 +282,7 @@ func Create(ctx *context.APIContext) { db.IsErrNameReserved(err) || db.IsErrNameCharsNotAllowed(err) || db.IsErrNamePatternNotAllowed(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -355,7 +355,7 @@ func Rename(ctx *context.APIContext) { orgUser := ctx.Org.Organization.AsUser() if err := user_service.RenameUser(ctx, orgUser, form.NewName, ctx.Doer); err != nil { if user_model.IsErrUserAlreadyExist(err) || db.IsErrNameReserved(err) || db.IsErrNamePatternNotAllowed(err) || db.IsErrNameCharsNotAllowed(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -394,7 +394,7 @@ func Edit(ctx *context.APIContext) { if err := org.UpdateOrgEmailAddress(ctx, ctx.Org.Organization, form.Email); err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index d8acc767c58..4373b90f2fa 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -236,7 +236,7 @@ func CreateTeam(ctx *context.APIContext) { if err := org_service.NewTeam(ctx, team); err != nil { if organization.IsErrTeamAlreadyExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -490,7 +490,7 @@ func AddTeamMember(ctx *context.APIContext) { } if err := org_service.AddTeamMember(ctx, ctx.Org.Team, u); err != nil { if errors.Is(err, user_model.ErrBlockedUser) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) } else { ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/packages/package.go b/routers/api/v1/packages/package.go index 6b01023ef91..d06d2a636b8 100644 --- a/routers/api/v1/packages/package.go +++ b/routers/api/v1/packages/package.go @@ -329,7 +329,7 @@ func GetLatestPackageVersion(ctx *context.APIContext) { return } if len(pvs) == 0 { - ctx.APIError(http.StatusNotFound, err) + ctx.APIErrorNotFound() return } @@ -383,7 +383,7 @@ func LinkPackage(ctx *context.APIContext) { pkg, err := packages.GetPackageByName(ctx, ctx.ContextUser.ID, packages.Type(ctx.PathParam("type")), ctx.PathParam("name")) if err != nil { if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -393,7 +393,7 @@ func LinkPackage(ctx *context.APIContext) { repo, err := repo_model.GetRepositoryByName(ctx, ctx.ContextUser.ID, ctx.PathParam("repo_name")) if err != nil { if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -404,9 +404,9 @@ func LinkPackage(ctx *context.APIContext) { if err != nil { switch { case errors.Is(err, util.ErrInvalidArgument): - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) case errors.Is(err, util.ErrPermissionDenied): - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) default: ctx.APIErrorInternal(err) } @@ -445,7 +445,7 @@ func UnlinkPackage(ctx *context.APIContext) { pkg, err := packages.GetPackageByName(ctx, ctx.ContextUser.ID, packages.Type(ctx.PathParam("type")), ctx.PathParam("name")) if err != nil { if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -456,9 +456,9 @@ func UnlinkPackage(ctx *context.APIContext) { if err != nil { switch { case errors.Is(err, util.ErrPermissionDenied): - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) case errors.Is(err, util.ErrInvalidArgument): - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) default: ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go index 169ed4b68ec..5fc2e97d7a3 100644 --- a/routers/api/v1/repo/action.go +++ b/routers/api/v1/repo/action.go @@ -141,9 +141,9 @@ func (Action) CreateOrUpdateSecret(ctx *context.APIContext) { _, created, err := secret_service.CreateOrUpdateSecret(ctx, 0, repo.ID, ctx.PathParam("secretname"), opt.Data, opt.Description) if err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -195,9 +195,9 @@ func (Action) DeleteSecret(ctx *context.APIContext) { err := secret_service.DeleteSecretByName(ctx, 0, repo.ID, ctx.PathParam("secretname")) if err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -243,7 +243,7 @@ func (Action) GetVariable(ctx *context.APIContext) { }) if err != nil { if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -298,9 +298,9 @@ func (Action) DeleteVariable(ctx *context.APIContext) { if err := actions_service.DeleteVariableByName(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParam("variablename")); err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -361,13 +361,13 @@ func (Action) CreateVariable(ctx *context.APIContext) { return } if v != nil && v.ID > 0 { - ctx.APIError(http.StatusConflict, util.NewAlreadyExistErrorf("variable name %s already exists", variableName)) + ctx.APIError(http.StatusConflict, "variable name already exists") return } if _, err := actions_service.CreateVariable(ctx, 0, repoID, variableName, opt.Value, opt.Description); err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -422,7 +422,7 @@ func (Action) UpdateVariable(ctx *context.APIContext) { }) if err != nil { if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -439,7 +439,7 @@ func (Action) UpdateVariable(ctx *context.APIContext) { if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -957,7 +957,7 @@ func ActionsGetWorkflow(ctx *context.APIContext) { workflow, err := convert.GetActionWorkflow(ctx, ctx.Repo.GitRepo, ctx.Repo.Repository, workflowID) if err != nil { if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -1005,7 +1005,7 @@ func ActionsDisableWorkflow(ctx *context.APIContext) { err := actions_service.EnableOrDisableWorkflow(ctx, workflowID, false) if err != nil { if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -1062,7 +1062,7 @@ func ActionsDispatchWorkflow(ctx *context.APIContext) { workflowID := ctx.PathParam("workflow_id") opt := web.GetForm(ctx).(*api.CreateActionWorkflowDispatch) if opt.Ref == "" { - ctx.APIError(http.StatusUnprocessableEntity, util.NewInvalidArgumentErrorf("ref is required parameter")) + ctx.APIError(http.StatusUnprocessableEntity, "ref is required parameter") return } @@ -1088,11 +1088,11 @@ func ActionsDispatchWorkflow(ctx *context.APIContext) { }) if err != nil { if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else if errors.Is(err, util.ErrPermissionDenied) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) } else if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -1151,7 +1151,7 @@ func ActionsEnableWorkflow(ctx *context.APIContext) { err := actions_service.EnableOrDisableWorkflow(ctx, workflowID, true) if err != nil { if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -1490,13 +1490,13 @@ func RerunWorkflowJob(ctx *context.APIContext) { func handleWorkflowRerunError(ctx *context.APIContext, err error) { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) return } else if errors.Is(err, util.ErrAlreadyExist) { - ctx.APIError(http.StatusConflict, err) + ctx.APIError(http.StatusConflict, err.Error()) return } else if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) return } ctx.APIErrorInternal(err) @@ -1560,7 +1560,7 @@ func ListWorkflowRunJobs(ctx *context.APIContext) { // Avoid the list all jobs functionality for this api route to be used with a runID == 0. if runID <= 0 { - ctx.APIError(http.StatusBadRequest, util.NewInvalidArgumentErrorf("runID must be a positive integer")) + ctx.APIError(http.StatusBadRequest, "runID must be a positive integer") return } diff --git a/routers/api/v1/repo/avatar.go b/routers/api/v1/repo/avatar.go index b3d52e16634..b00394ffcc5 100644 --- a/routers/api/v1/repo/avatar.go +++ b/routers/api/v1/repo/avatar.go @@ -44,7 +44,7 @@ func UpdateAvatar(ctx *context.APIContext) { content, err := base64.StdEncoding.DecodeString(form.Image) if err != nil { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) return } diff --git a/routers/api/v1/repo/blob.go b/routers/api/v1/repo/blob.go index cd731b0d787..79b2dee245c 100644 --- a/routers/api/v1/repo/blob.go +++ b/routers/api/v1/repo/blob.go @@ -48,7 +48,7 @@ func GetBlob(ctx *context.APIContext) { } if blob, err := files_service.GetBlobBySHA(ctx.Repo.Repository, ctx.Repo.GitRepo, sha); err != nil { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else { ctx.JSON(http.StatusOK, blob) } diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index 3afdf9694d1..3b6575d6763 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -155,9 +155,9 @@ func DeleteBranch(ctx *context.APIContext) { case git.IsErrBranchNotExist(err): ctx.APIErrorNotFound(err) case errors.Is(err, repo_service.ErrBranchIsDefault): - ctx.APIError(http.StatusForbidden, errors.New("can not delete default or pull request target branch")) + ctx.APIError(http.StatusForbidden, "can not delete default or pull request target branch") case errors.Is(err, git_model.ErrBranchIsProtected): - ctx.APIError(http.StatusForbidden, errors.New("branch protected")) + ctx.APIError(http.StatusForbidden, "branch protected") default: ctx.APIErrorInternal(err) } @@ -448,7 +448,7 @@ func UpdateBranch(ctx *context.APIContext) { case git_model.IsErrBranchNotExist(err): ctx.APIErrorNotFound(err) case errors.Is(err, util.ErrInvalidArgument): - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) case git.IsErrPushRejected(err): rej := err.(*git.ErrPushRejected) ctx.APIError(http.StatusForbidden, rej.Message) @@ -684,7 +684,7 @@ func CreateBranchProtection(ctx *context.APIContext) { whitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -693,7 +693,7 @@ func CreateBranchProtection(ctx *context.APIContext) { forcePushAllowlistUsers, err := user_model.GetUserIDsByNames(ctx, form.ForcePushAllowlistUsernames, false) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -702,7 +702,7 @@ func CreateBranchProtection(ctx *context.APIContext) { mergeWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -711,7 +711,7 @@ func CreateBranchProtection(ctx *context.APIContext) { approvalsWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.ApprovalsWhitelistUsernames, false) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -722,7 +722,7 @@ func CreateBranchProtection(ctx *context.APIContext) { bypassAllowlistUsers, err = user_model.GetUserIDsByNames(ctx, form.BypassAllowlistUsernames, false) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -734,7 +734,7 @@ func CreateBranchProtection(ctx *context.APIContext) { whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false) if err != nil { if organization.IsErrTeamNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -743,7 +743,7 @@ func CreateBranchProtection(ctx *context.APIContext) { forcePushAllowlistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ForcePushAllowlistTeams, false) if err != nil { if organization.IsErrTeamNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -752,7 +752,7 @@ func CreateBranchProtection(ctx *context.APIContext) { mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false) if err != nil { if organization.IsErrTeamNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -761,7 +761,7 @@ func CreateBranchProtection(ctx *context.APIContext) { approvalsWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ApprovalsWhitelistTeams, false) if err != nil { if organization.IsErrTeamNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -771,7 +771,7 @@ func CreateBranchProtection(ctx *context.APIContext) { bypassAllowlistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.BypassAllowlistTeams, false) if err != nil { if organization.IsErrTeamNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -999,7 +999,7 @@ func EditBranchProtection(ctx *context.APIContext) { whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -1012,7 +1012,7 @@ func EditBranchProtection(ctx *context.APIContext) { forcePushAllowlistUsers, err = user_model.GetUserIDsByNames(ctx, form.ForcePushAllowlistUsernames, false) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -1025,7 +1025,7 @@ func EditBranchProtection(ctx *context.APIContext) { mergeWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -1038,7 +1038,7 @@ func EditBranchProtection(ctx *context.APIContext) { approvalsWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.ApprovalsWhitelistUsernames, false) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -1051,7 +1051,7 @@ func EditBranchProtection(ctx *context.APIContext) { bypassAllowlistUsers, err = user_model.GetUserIDsByNames(ctx, form.BypassAllowlistUsernames, false) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -1067,7 +1067,7 @@ func EditBranchProtection(ctx *context.APIContext) { whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false) if err != nil { if organization.IsErrTeamNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -1080,7 +1080,7 @@ func EditBranchProtection(ctx *context.APIContext) { forcePushAllowlistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ForcePushAllowlistTeams, false) if err != nil { if organization.IsErrTeamNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -1093,7 +1093,7 @@ func EditBranchProtection(ctx *context.APIContext) { mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false) if err != nil { if organization.IsErrTeamNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -1106,7 +1106,7 @@ func EditBranchProtection(ctx *context.APIContext) { approvalsWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ApprovalsWhitelistTeams, false) if err != nil { if organization.IsErrTeamNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -1119,7 +1119,7 @@ func EditBranchProtection(ctx *context.APIContext) { bypassAllowlistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.BypassAllowlistTeams, false) if err != nil { if organization.IsErrTeamNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -1331,10 +1331,10 @@ func MergeUpstream(ctx *context.APIContext) { mergeStyle, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, form.Branch, form.FfOnly) if err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) return } else if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) return } ctx.APIErrorInternal(err) diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go index b7938cb516d..e254d5e1289 100644 --- a/routers/api/v1/repo/collaborators.go +++ b/routers/api/v1/repo/collaborators.go @@ -107,7 +107,7 @@ func IsCollaborator(ctx *context.APIContext) { user, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator")) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -167,7 +167,7 @@ func AddOrUpdateCollaborator(ctx *context.APIContext) { collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator")) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -186,7 +186,7 @@ func AddOrUpdateCollaborator(ctx *context.APIContext) { if err := repo_service.AddOrUpdateCollaborator(ctx, ctx.Repo.Repository, collaborator, p); err != nil { if errors.Is(err, user_model.ErrBlockedUser) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -230,7 +230,7 @@ func DeleteCollaborator(ctx *context.APIContext) { collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator")) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -284,7 +284,7 @@ func GetRepoPermissions(ctx *context.APIContext) { collaborator, err := user_model.GetUserByName(ctx, collaboratorUsername) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -326,7 +326,7 @@ func GetReviewers(ctx *context.APIContext) { canChooseReviewer := issue_service.CanDoerChangeReviewRequests(ctx, ctx.Doer, ctx.Repo.Repository, 0) if !canChooseReviewer { - ctx.APIError(http.StatusForbidden, errors.New("doer has no permission to get reviewers")) + ctx.APIError(http.StatusForbidden, "doer has no permission to get reviewers") return } diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go index 620ee037006..d83686083e5 100644 --- a/routers/api/v1/repo/commits.go +++ b/routers/api/v1/repo/commits.go @@ -217,7 +217,7 @@ func GetAllCommits(ctx *context.APIContext) { // get commit specified by sha baseCommit, err = ctx.Repo.GitRepo.GetCommit(sha) if err != nil { - ctx.NotFoundOrServerError(err) + ctx.APIErrorAuto(err) return } } @@ -383,7 +383,7 @@ func GetCommitPullRequest(ctx *context.APIContext) { pr, err := issues_model.GetPullRequestByMergedCommit(ctx, ctx.Repo.Repository.ID, ctx.PathParam("sha")) if err != nil { if issues_model.IsErrPullRequestNotExist(err) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/repo/download.go b/routers/api/v1/repo/download.go index d9470d143ea..0df406488c3 100644 --- a/routers/api/v1/repo/download.go +++ b/routers/api/v1/repo/download.go @@ -17,9 +17,9 @@ func serveRepoArchive(ctx *context.APIContext, reqFileName string, paths []strin aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, reqFileName, paths) if err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -28,7 +28,7 @@ func serveRepoArchive(ctx *context.APIContext, reqFileName string, paths []strin err = archiver_service.ServeRepoArchive(ctx.Base, aReq) if err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else { ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index 5f0659dc818..244d9393ce6 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -409,7 +409,7 @@ func ChangeFiles(ctx *context.APIContext) { for _, file := range apiOpts.Files { contentReader, err := base64Reader(file.ContentBase64) if err != nil { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } // FIXME: ChangeFileOperation.SHA is NOT required for update or delete if last commit is provided in the options @@ -483,7 +483,7 @@ func CreateFile(ctx *context.APIContext) { } contentReader, err := base64Reader(apiOpts.ContentBase64) if err != nil { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } @@ -554,7 +554,7 @@ func UpdateFile(ctx *context.APIContext) { } contentReader, err := base64Reader(apiOpts.ContentBase64) if err != nil { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } willCreate := apiOpts.SHA == "" @@ -584,17 +584,17 @@ func handleChangeRepoFilesError(ctx *context.APIContext, err error) { return } if files_service.IsErrUserCannotCommit(err) || pull_service.IsErrFilePathProtected(err) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) return } if git_model.IsErrBranchAlreadyExists(err) || files_service.IsErrFilenameInvalid(err) || pull_service.IsErrSHADoesNotMatch(err) || files_service.IsErrFilePathInvalid(err) || files_service.IsErrRepoFileAlreadyExists(err) || files_service.IsErrCommitIDDoesNotMatch(err) || files_service.IsErrSHAOrCommitIDNotProvided(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) return } ctx.APIErrorInternal(err) diff --git a/routers/api/v1/repo/fork.go b/routers/api/v1/repo/fork.go index 9d95f538743..8943ad39931 100644 --- a/routers/api/v1/repo/fork.go +++ b/routers/api/v1/repo/fork.go @@ -165,9 +165,9 @@ func CreateFork(ctx *context.APIContext) { }) if err != nil { if errors.Is(err, util.ErrAlreadyExist) || repo_model.IsErrReachLimitOfRepo(err) { - ctx.APIError(http.StatusConflict, err) + ctx.APIError(http.StatusConflict, err.Error()) } else if errors.Is(err, user_model.ErrBlockedUser) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) } else { ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 5d73cc3b312..a075c68f749 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -187,7 +187,7 @@ func SearchIssues(ctx *context.APIContext) { before, since, err := context.GetQueryBeforeSince(ctx.Base) if err != nil { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } @@ -196,7 +196,7 @@ func SearchIssues(ctx *context.APIContext) { repoIDs, allPublic, err := buildSearchIssuesRepoIDs(ctx) if err != nil { if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -384,7 +384,7 @@ func ListIssues(ctx *context.APIContext) { // "$ref": "#/responses/notFound" before, since, err := context.GetQueryBeforeSince(ctx.Base) if err != nil { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } @@ -682,7 +682,7 @@ func CreateIssue(ctx *context.APIContext) { return } if !valid { - ctx.APIError(http.StatusUnprocessableEntity, repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: ctx.Repo.Repository.Name}) + ctx.APIError(http.StatusUnprocessableEntity, repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: ctx.Repo.Repository.Name}.Error()) return } } @@ -693,9 +693,9 @@ func CreateIssue(ctx *context.APIContext) { if err := issue_service.NewIssue(ctx, ctx.Repo.Repository, issue, form.Labels, nil, assigneeIDs, form.Projects); err != nil { if errors.Is(err, user_model.ErrBlockedUser) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) } else if errors.Is(err, util.ErrPermissionDenied) || errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -794,7 +794,7 @@ func EditIssue(ctx *context.APIContext) { // handles concurrent requests. // TODO: wrap all mutations in a transaction to fully prevent partial writes. if form.ContentVersion != nil && *form.ContentVersion != issue.ContentVersion { - ctx.APIError(http.StatusConflict, issues_model.ErrIssueAlreadyChanged) + ctx.APIError(http.StatusConflict, issues_model.ErrIssueAlreadyChanged.Error()) return } @@ -813,7 +813,7 @@ func EditIssue(ctx *context.APIContext) { err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, contentVersion) if err != nil { if errors.Is(err, issues_model.ErrIssueAlreadyChanged) { - ctx.APIError(http.StatusConflict, err) + ctx.APIError(http.StatusConflict, err.Error()) return } @@ -869,7 +869,7 @@ func EditIssue(ctx *context.APIContext) { err = issue_service.UpdateAssignees(ctx, issue, oneAssignee, form.Assignees, ctx.Doer) if err != nil { if errors.Is(err, user_model.ErrBlockedUser) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -918,7 +918,7 @@ func EditIssue(ctx *context.APIContext) { if canWrite && form.Projects != nil { if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, ctx.Doer, *form.Projects); err != nil { if errors.Is(err, util.ErrPermissionDenied) || errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else { ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/repo/issue_attachment.go b/routers/api/v1/repo/issue_attachment.go index 00fa792771f..123f66399d4 100644 --- a/routers/api/v1/repo/issue_attachment.go +++ b/routers/api/v1/repo/issue_attachment.go @@ -194,9 +194,9 @@ func CreateIssueAttachment(ctx *context.APIContext) { }) if err != nil { if upload.IsErrFileTypeForbidden(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else if errors.Is(err, util.ErrContentTooLarge) { - ctx.APIError(http.StatusRequestEntityTooLarge, err) + ctx.APIError(http.StatusRequestEntityTooLarge, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -272,7 +272,7 @@ func EditIssueAttachment(ctx *context.APIContext) { if err := attachment_service.UpdateAttachment(ctx, setting.Attachment.AllowedTypes, attachment); err != nil { if upload.IsErrFileTypeForbidden(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -336,7 +336,7 @@ func DeleteIssueAttachment(ctx *context.APIContext) { func getIssueFromContext(ctx *context.APIContext) *issues_model.Issue { issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { - ctx.NotFoundOrServerError(err) + ctx.APIErrorAuto(err) return nil } @@ -361,7 +361,7 @@ func getIssueAttachmentSafeWrite(ctx *context.APIContext) *repo_model.Attachment func getIssueAttachmentSafeRead(ctx *context.APIContext, issue *issues_model.Issue) *repo_model.Attachment { attachment, err := repo_model.GetAttachmentByID(ctx, ctx.PathParamInt64("attachment_id")) if err != nil { - ctx.NotFoundOrServerError(err) + ctx.APIErrorAuto(err) return nil } if !attachmentBelongsToRepoOrIssue(ctx, attachment, issue) { diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go index 5b2d2084735..02a0f702ce9 100644 --- a/routers/api/v1/repo/issue_comment.go +++ b/routers/api/v1/repo/issue_comment.go @@ -65,7 +65,7 @@ func ListIssueComments(ctx *context.APIContext) { before, since, err := context.GetQueryBeforeSince(ctx.Base) if err != nil { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) @@ -169,7 +169,7 @@ func ListIssueCommentsAndTimeline(ctx *context.APIContext) { before, since, err := context.GetQueryBeforeSince(ctx.Base) if err != nil { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) @@ -274,7 +274,7 @@ func ListRepoIssueComments(ctx *context.APIContext) { before, since, err := context.GetQueryBeforeSince(ctx.Base) if err != nil { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } @@ -392,14 +392,14 @@ func CreateIssueComment(ctx *context.APIContext) { } if issue.IsLocked && !ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin { - ctx.APIError(http.StatusForbidden, errors.New(ctx.Locale.TrString("repo.issues.comment_on_locked"))) + ctx.APIError(http.StatusForbidden, ctx.Locale.TrString("repo.issues.comment_on_locked")) return } comment, err := issue_service.CreateIssueComment(ctx, ctx.Doer, ctx.Repo.Repository, issue, form.Body, nil) if err != nil { if errors.Is(err, user_model.ErrBlockedUser) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -595,7 +595,7 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) comment.Content = form.Body if err := issue_service.UpdateComment(ctx, comment, comment.ContentVersion, ctx.Doer, oldContent); err != nil { if errors.Is(err, user_model.ErrBlockedUser) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) } else { ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/repo/issue_comment_attachment.go b/routers/api/v1/repo/issue_comment_attachment.go index d2650edb634..56d494190e4 100644 --- a/routers/api/v1/repo/issue_comment_attachment.go +++ b/routers/api/v1/repo/issue_comment_attachment.go @@ -202,9 +202,9 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) { }) if err != nil { if upload.IsErrFileTypeForbidden(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else if errors.Is(err, util.ErrContentTooLarge) { - ctx.APIError(http.StatusRequestEntityTooLarge, err) + ctx.APIError(http.StatusRequestEntityTooLarge, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -218,7 +218,7 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) { if err = issue_service.UpdateComment(ctx, comment, comment.ContentVersion, ctx.Doer, comment.Content); err != nil { if errors.Is(err, user_model.ErrBlockedUser) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -285,7 +285,7 @@ func EditIssueCommentAttachment(ctx *context.APIContext) { if err := attachment_service.UpdateAttachment(ctx, setting.Attachment.AllowedTypes, attach); err != nil { if upload.IsErrFileTypeForbidden(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -346,7 +346,7 @@ func DeleteIssueCommentAttachment(ctx *context.APIContext) { func getIssueCommentSafe(ctx *context.APIContext) *issues_model.Comment { comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id")) if err != nil { - ctx.NotFoundOrServerError(err) + ctx.APIErrorAuto(err) return nil } if err := comment.LoadIssue(ctx); err != nil { @@ -391,7 +391,7 @@ func canUserWriteIssueCommentAttachment(ctx *context.APIContext, comment *issues func getIssueCommentAttachmentSafeRead(ctx *context.APIContext, comment *issues_model.Comment) *repo_model.Attachment { attachment, err := repo_model.GetAttachmentByID(ctx, ctx.PathParamInt64("attachment_id")) if err != nil { - ctx.NotFoundOrServerError(err) + ctx.APIErrorAuto(err) return nil } if !attachmentBelongsToRepoOrComment(ctx, attachment, comment) { diff --git a/routers/api/v1/repo/issue_label.go b/routers/api/v1/repo/issue_label.go index ecf42688615..c1bbbe29d00 100644 --- a/routers/api/v1/repo/issue_label.go +++ b/routers/api/v1/repo/issue_label.go @@ -181,7 +181,7 @@ func DeleteIssueLabel(ctx *context.APIContext) { label, err := issues_model.GetLabelByID(ctx, ctx.PathParamInt64("id")) if err != nil { if issues_model.IsErrLabelNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else { ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/repo/issue_lock.go b/routers/api/v1/repo/issue_lock.go index 283b441fd20..75247593fd3 100644 --- a/routers/api/v1/repo/issue_lock.go +++ b/routers/api/v1/repo/issue_lock.go @@ -4,7 +4,6 @@ package repo import ( - "errors" "net/http" issues_model "gitea.dev/models/issues" @@ -63,7 +62,7 @@ func LockIssue(ctx *context.APIContext) { } if !ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) { - ctx.APIError(http.StatusForbidden, errors.New("no permission to lock this issue")) + ctx.APIError(http.StatusForbidden, "no permission to lock this issue") return } @@ -130,7 +129,7 @@ func UnlockIssue(ctx *context.APIContext) { } if !ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) { - ctx.APIError(http.StatusForbidden, errors.New("no permission to unlock this issue")) + ctx.APIError(http.StatusForbidden, "no permission to unlock this issue") return } diff --git a/routers/api/v1/repo/issue_pin.go b/routers/api/v1/repo/issue_pin.go index b8cebfbb04e..c71649aeca4 100644 --- a/routers/api/v1/repo/issue_pin.go +++ b/routers/api/v1/repo/issue_pin.go @@ -46,7 +46,7 @@ func PinIssue(ctx *context.APIContext) { if issues_model.IsErrIssueNotExist(err) { ctx.APIErrorNotFound() } else if issues_model.IsErrIssueMaxPinReached(err) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else { ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/repo/issue_reaction.go b/routers/api/v1/repo/issue_reaction.go index 3662e63c71b..c9fa39e93d2 100644 --- a/routers/api/v1/repo/issue_reaction.go +++ b/routers/api/v1/repo/issue_reaction.go @@ -72,7 +72,7 @@ func GetIssueCommentReactions(ctx *context.APIContext) { } if !ctx.Repo.Permission.CanReadIssuesOrPulls(comment.Issue.IsPull) { - ctx.APIError(http.StatusForbidden, errors.New("no permission to get reactions")) + ctx.APIError(http.StatusForbidden, "no permission to get reactions") return } @@ -214,7 +214,7 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp } if comment.Issue.IsLocked && !ctx.Repo.Permission.CanWriteIssuesOrPulls(comment.Issue.IsPull) { - ctx.APIError(http.StatusForbidden, errors.New("no permission to change reaction")) + ctx.APIError(http.StatusForbidden, "no permission to change reaction") return } @@ -223,7 +223,7 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp reaction, err := issue_service.CreateCommentReaction(ctx, ctx.Doer, comment, form.Reaction) if err != nil { if issues_model.IsErrForbiddenIssueReaction(err) || errors.Is(err, user_model.ErrBlockedUser) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) } else if issues_model.IsErrReactionAlreadyExist(err) { ctx.JSON(http.StatusOK, api.Reaction{ User: convert.ToUser(ctx, ctx.Doer, ctx.Doer), @@ -305,7 +305,7 @@ func GetIssueReactions(ctx *context.APIContext) { } if !ctx.Repo.Permission.CanReadIssuesOrPulls(issue.IsPull) { - ctx.APIError(http.StatusForbidden, errors.New("no permission to get reactions")) + ctx.APIError(http.StatusForbidden, "no permission to get reactions") return } @@ -429,7 +429,7 @@ func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, i } if issue.IsLocked && !ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull) { - ctx.APIError(http.StatusForbidden, errors.New("no permission to change reaction")) + ctx.APIError(http.StatusForbidden, "no permission to change reaction") return } @@ -438,7 +438,7 @@ func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, i reaction, err := issue_service.CreateIssueReaction(ctx, ctx.Doer, issue, form.Reaction) if err != nil { if issues_model.IsErrForbiddenIssueReaction(err) || errors.Is(err, user_model.ErrBlockedUser) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) } else if issues_model.IsErrReactionAlreadyExist(err) { ctx.JSON(http.StatusOK, api.Reaction{ User: convert.ToUser(ctx, ctx.Doer, ctx.Doer), diff --git a/routers/api/v1/repo/issue_subscription.go b/routers/api/v1/repo/issue_subscription.go index b2480e88a5a..84af3194df2 100644 --- a/routers/api/v1/repo/issue_subscription.go +++ b/routers/api/v1/repo/issue_subscription.go @@ -128,7 +128,7 @@ func setIssueSubscription(ctx *context.APIContext, watch bool) { // only admin and user for itself can change subscription if user.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin { - ctx.APIError(http.StatusForbidden, fmt.Errorf("%s is not permitted to change subscriptions for %s", ctx.Doer.Name, user.Name)) + ctx.APIError(http.StatusForbidden, fmt.Sprintf("%s is not permitted to change subscriptions for %s", ctx.Doer.Name, user.Name)) return } diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go index 414b44f5246..1af649bfd79 100644 --- a/routers/api/v1/repo/issue_tracked_time.go +++ b/routers/api/v1/repo/issue_tracked_time.go @@ -4,7 +4,6 @@ package repo import ( - "errors" "net/http" "time" @@ -95,7 +94,7 @@ func ListTrackedTimes(ctx *context.APIContext) { if qUser != "" { user, err := user_model.GetUserByName(ctx, qUser) if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else if err != nil { ctx.APIErrorInternal(err) return @@ -104,7 +103,7 @@ func ListTrackedTimes(ctx *context.APIContext) { } if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Base); err != nil { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } @@ -116,7 +115,7 @@ func ListTrackedTimes(ctx *context.APIContext) { if opts.UserID == 0 { opts.UserID = ctx.Doer.ID } else { - ctx.APIError(http.StatusForbidden, errors.New("query by user not allowed; not enough rights")) + ctx.APIError(http.StatusForbidden, "query by user not allowed; not enough rights") return } } @@ -286,7 +285,7 @@ func ResetIssueTime(ctx *context.APIContext) { err = issues_model.DeleteIssueUserTimes(ctx, issue, ctx.Doer) if err != nil { if db.IsErrNotExist(err) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -437,7 +436,7 @@ func ListTrackedTimesByUser(ctx *context.APIContext) { } if !ctx.IsUserRepoAdmin() && !ctx.Doer.IsAdmin && ctx.Doer.ID != user.ID { - ctx.APIError(http.StatusForbidden, errors.New("query by user not allowed; not enough rights")) + ctx.APIError(http.StatusForbidden, "query by user not allowed; not enough rights") return } @@ -523,7 +522,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) { if qUser != "" { user, err := user_model.GetUserByName(ctx, qUser) if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else if err != nil { ctx.APIErrorInternal(err) return @@ -533,7 +532,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) { var err error if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Base); err != nil { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } @@ -545,7 +544,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) { if opts.UserID == 0 { opts.UserID = ctx.Doer.ID } else { - ctx.APIError(http.StatusForbidden, errors.New("query by user not allowed; not enough rights")) + ctx.APIError(http.StatusForbidden, "query by user not allowed; not enough rights") return } } @@ -607,7 +606,7 @@ func ListMyTrackedTimes(ctx *context.APIContext) { var err error if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Base); err != nil { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } diff --git a/routers/api/v1/repo/key.go b/routers/api/v1/repo/key.go index 44471a4f73f..b704bcee1d4 100644 --- a/routers/api/v1/repo/key.go +++ b/routers/api/v1/repo/key.go @@ -179,7 +179,7 @@ func HandleCheckKeyStringError(ctx *context.APIContext, err error) { } else if asymkey_model.IsErrKeyUnableVerify(err) { ctx.APIError(http.StatusUnprocessableEntity, "Unable to verify key content") } else { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid key content: %w", err)) + ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid key content: %v", err)) } } diff --git a/routers/api/v1/repo/label.go b/routers/api/v1/repo/label.go index aec82dce757..790a9212bc2 100644 --- a/routers/api/v1/repo/label.go +++ b/routers/api/v1/repo/label.go @@ -153,7 +153,7 @@ func CreateLabel(ctx *context.APIContext) { color, err := label.NormalizeColor(form.Color) if err != nil { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } form.Color = color @@ -231,7 +231,7 @@ func EditLabel(ctx *context.APIContext) { if form.Color != nil { color, err := label.NormalizeColor(*form.Color) if err != nil { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } l.Color = color diff --git a/routers/api/v1/repo/migrate.go b/routers/api/v1/repo/migrate.go index 65ef10777c4..0e3e68d1e8b 100644 --- a/routers/api/v1/repo/migrate.go +++ b/routers/api/v1/repo/migrate.go @@ -72,7 +72,7 @@ func Migrate(ctx *context.APIContext) { } if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -110,12 +110,12 @@ func Migrate(ctx *context.APIContext) { gitServiceType := convert.ToGitServiceType(form.Service) if form.Mirror && setting.Mirror.DisableNewPull { - ctx.APIError(http.StatusForbidden, errors.New("the site administrator has disabled the creation of new pull mirrors")) + ctx.APIError(http.StatusForbidden, "the site administrator has disabled the creation of new pull mirrors") return } if setting.Repository.DisableMigrations { - ctx.APIError(http.StatusForbidden, errors.New("the site administrator has disabled migrations")) + ctx.APIError(http.StatusForbidden, "the site administrator has disabled migrations") return } @@ -235,9 +235,9 @@ func handleMigrateError(ctx *context.APIContext, repoOwner *user_model.User, err case db.IsErrNamePatternNotAllowed(err): ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(db.ErrNamePatternNotAllowed).Pattern)) case git.IsErrInvalidCloneAddr(err): - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) case base.IsErrNotSupported(err): - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) default: err = util.SanitizeErrorCredentialURLs(err) if strings.Contains(err.Error(), "Authentication failed") || diff --git a/routers/api/v1/repo/mirror.go b/routers/api/v1/repo/mirror.go index 53d47ff135e..c76946493ab 100644 --- a/routers/api/v1/repo/mirror.go +++ b/routers/api/v1/repo/mirror.go @@ -5,6 +5,7 @@ package repo import ( "errors" + "fmt" "net/http" "strings" "time" @@ -112,7 +113,7 @@ func PushMirrorSync(ctx *context.APIContext) { // Get All push mirrors of a specific repo pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, ctx.Repo.Repository.ID, db.ListOptions{}) if err != nil { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) return } @@ -175,7 +176,7 @@ func ListPushMirrors(ctx *context.APIContext) { // Get all push mirrors for the specified repository. pushMirrors, count, err := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, utils.GetListOptions(ctx)) if err != nil { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) return } @@ -239,7 +240,7 @@ func GetPushMirrorByName(ctx *context.APIContext) { ctx.APIErrorInternal(err) return } else if !exist { - ctx.APIError(http.StatusNotFound, nil) + ctx.APIErrorNotFound() return } @@ -334,7 +335,7 @@ func DeletePushMirrorByRemoteName(ctx *context.APIContext) { // Delete push mirror on repo by name. err := repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{RepoID: ctx.Repo.Repository.ID, RemoteName: remoteName}) if err != nil { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) return } ctx.Status(http.StatusNoContent) @@ -344,8 +345,12 @@ func CreatePushMirror(ctx *context.APIContext, mirrorOption *api.CreatePushMirro repo := ctx.Repo.Repository interval, err := time.ParseDuration(mirrorOption.Interval) - if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) { - ctx.APIError(http.StatusBadRequest, err) + if err != nil { + ctx.APIError(http.StatusBadRequest, fmt.Sprintf("invalid interval: %v", err)) + return + } + if interval != 0 && interval < setting.Mirror.MinInterval { + ctx.APIError(http.StatusBadRequest, fmt.Sprintf("interval is shorter than minimum %v", setting.Mirror.MinInterval.String())) return } diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 6f6ba77fae0..54e7b78a1b4 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -126,7 +126,7 @@ func ListPullRequests(ctx *context.APIContext) { poster, err := user_model.GetUserByName(ctx, posterStr) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -448,7 +448,7 @@ func CreatePullRequest(ctx *context.APIContext) { HeadBranch: existingPr.HeadBranch, BaseBranch: existingPr.BaseBranch, } - ctx.APIError(http.StatusConflict, err) + ctx.APIError(http.StatusConflict, err.Error()) return } @@ -551,7 +551,7 @@ func CreatePullRequest(ctx *context.APIContext) { return } if !valid { - ctx.APIError(http.StatusUnprocessableEntity, repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: repo.Name}) + ctx.APIError(http.StatusUnprocessableEntity, repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: repo.Name}.Error()) return } } @@ -570,11 +570,11 @@ func CreatePullRequest(ctx *context.APIContext) { if err := pull_service.NewPullRequest(ctx, prOpts); err != nil { if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else if errors.Is(err, user_model.ErrBlockedUser) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) } else if errors.Is(err, issues_model.ErrMustCollaborator) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -663,7 +663,7 @@ func EditPullRequest(ctx *context.APIContext) { // handles concurrent requests. // TODO: wrap all mutations in a transaction to fully prevent partial writes. if form.ContentVersion != nil && *form.ContentVersion != issue.ContentVersion { - ctx.APIError(http.StatusConflict, issues_model.ErrIssueAlreadyChanged) + ctx.APIError(http.StatusConflict, issues_model.ErrIssueAlreadyChanged.Error()) return } @@ -682,7 +682,7 @@ func EditPullRequest(ctx *context.APIContext) { err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, contentVersion) if err != nil { if errors.Is(err, issues_model.ErrIssueAlreadyChanged) { - ctx.APIError(http.StatusConflict, err) + ctx.APIError(http.StatusConflict, err.Error()) return } @@ -721,7 +721,7 @@ func EditPullRequest(ctx *context.APIContext) { if user_model.IsErrUserNotExist(err) { ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("Assignee does not exist: [name: %s]", err)) } else if errors.Is(err, user_model.ErrBlockedUser) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -788,18 +788,18 @@ func EditPullRequest(ctx *context.APIContext) { return } if !branchExist { - ctx.APIError(http.StatusNotFound, fmt.Errorf("new base '%s' not exist", form.Base)) + ctx.APIError(http.StatusNotFound, fmt.Sprintf("new base '%s' not exist", form.Base)) return } if err := pull_service.ChangeTargetBranch(ctx, pr, ctx.Doer, form.Base); err != nil { if issues_model.IsErrPullRequestAlreadyExists(err) { - ctx.APIError(http.StatusConflict, err) + ctx.APIError(http.StatusConflict, err.Error()) return } else if issues_model.IsErrIssueIsClosed(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } else if pull_service.IsErrPullRequestHasMerged(err) { - ctx.APIError(http.StatusConflict, err) + ctx.APIError(http.StatusConflict, err.Error()) return } ctx.APIErrorInternal(err) @@ -977,11 +977,11 @@ func MergePullRequest(ctx *context.APIContext) { } else if errors.Is(err, pull_service.ErrNotMergeableState) { ctx.APIError(http.StatusMethodNotAllowed, "Please try again later") } else if errors.Is(err, pull_service.ErrNotReadyToMerge) { - ctx.APIError(http.StatusMethodNotAllowed, err) + ctx.APIError(http.StatusMethodNotAllowed, err.Error()) } else if asymkey_service.IsErrWontSign(err) { - ctx.APIError(http.StatusMethodNotAllowed, err) + ctx.APIError(http.StatusMethodNotAllowed, err.Error()) } else if errors.Is(err, pull_service.ErrHeadCommitsNotAllVerified) { - ctx.APIError(http.StatusMethodNotAllowed, err) + ctx.APIError(http.StatusMethodNotAllowed, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -992,11 +992,11 @@ func MergePullRequest(ctx *context.APIContext) { if manuallyMerged { if err := pull_service.MergedManually(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil { if pull_service.IsErrInvalidMergeStyle(err) { - ctx.APIError(http.StatusMethodNotAllowed, fmt.Errorf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do))) + ctx.APIError(http.StatusMethodNotAllowed, fmt.Sprintf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do))) return } if strings.Contains(err.Error(), "Wrong commit ID") { - ctx.APIError(http.StatusConflict, err) + ctx.APIError(http.StatusConflict, err.Error()) return } ctx.APIErrorInternal(err) @@ -1034,7 +1034,7 @@ func MergePullRequest(ctx *context.APIContext) { scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), message, deleteBranchAfterMerge) if err != nil { if pull_model.IsErrAlreadyScheduledToAutoMerge(err) { - ctx.APIError(http.StatusConflict, err) + ctx.APIError(http.StatusConflict, err.Error()) return } ctx.APIErrorInternal(err) @@ -1048,7 +1048,7 @@ func MergePullRequest(ctx *context.APIContext) { if err := pull_service.Merge(ctx, pr, ctx.Doer, repo_model.MergeStyle(form.Do), form.HeadCommitID, message, false); err != nil { if pull_service.IsErrInvalidMergeStyle(err) { - ctx.APIError(http.StatusMethodNotAllowed, fmt.Errorf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do))) + ctx.APIError(http.StatusMethodNotAllowed, fmt.Sprintf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do))) } else if pull_service.IsErrMergeConflicts(err) { conflictError := err.(pull_service.ErrMergeConflicts) ctx.JSON(http.StatusConflict, conflictError) @@ -1230,7 +1230,7 @@ func UpdatePullRequest(ctx *context.APIContext) { } if pr.HasMerged { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, "pull request is already merged") return } @@ -1240,7 +1240,7 @@ func UpdatePullRequest(ctx *context.APIContext) { } if pr.Issue.IsClosed { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, "pull request is already closed") return } diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go index 901e6b69c08..54919cef90f 100644 --- a/routers/api/v1/repo/pull_review.go +++ b/routers/api/v1/repo/pull_review.go @@ -458,7 +458,7 @@ func DeletePullReview(ctx *context.APIContext) { return } if !ctx.Doer.IsAdmin && ctx.Doer.ID != review.ReviewerID { - ctx.APIError(http.StatusForbidden, nil) + ctx.APIError(http.StatusForbidden, "no permission to delete comment") return } @@ -575,7 +575,7 @@ func CreatePullReview(ctx *context.APIContext) { review, _, err := pull_service.SubmitReview(ctx, ctx.Doer, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, opts.CommitID, nil) if err != nil { if errors.Is(err, pull_service.ErrSubmitReviewOnClosedPR) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -641,7 +641,7 @@ func SubmitPullReview(ctx *context.APIContext) { } if review.Type != issues_model.ReviewTypePending { - ctx.APIError(http.StatusUnprocessableEntity, errors.New("only a pending review can be submitted")) + ctx.APIError(http.StatusUnprocessableEntity, "only a pending review can be submitted") return } @@ -653,7 +653,7 @@ func SubmitPullReview(ctx *context.APIContext) { // if review stay pending return if reviewType == issues_model.ReviewTypePending { - ctx.APIError(http.StatusUnprocessableEntity, errors.New("review stay pending")) + ctx.APIError(http.StatusUnprocessableEntity, "review stay pending") return } @@ -667,7 +667,7 @@ func SubmitPullReview(ctx *context.APIContext) { review, _, err = pull_service.SubmitReview(ctx, ctx.Doer, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, headCommitID, nil) if err != nil { if errors.Is(err, pull_service.ErrSubmitReviewOnClosedPR) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -698,7 +698,7 @@ func preparePullReviewType(ctx *context.APIContext, pr *issues_model.PullRequest case api.ReviewStateApproved: // can not approve your own PR if pr.Issue.IsPoster(ctx.Doer.ID) { - ctx.APIError(http.StatusUnprocessableEntity, errors.New("approve your own pull is not allowed")) + ctx.APIError(http.StatusUnprocessableEntity, "approve your own pull is not allowed") return -1, true } reviewType = issues_model.ReviewTypeApprove @@ -707,7 +707,7 @@ func preparePullReviewType(ctx *context.APIContext, pr *issues_model.PullRequest case api.ReviewStateRequestChanges: // can not reject your own PR if pr.Issue.IsPoster(ctx.Doer.ID) { - ctx.APIError(http.StatusUnprocessableEntity, errors.New("reject your own pull is not allowed")) + ctx.APIError(http.StatusUnprocessableEntity, "reject your own pull is not allowed") return -1, true } reviewType = issues_model.ReviewTypeReject @@ -717,7 +717,7 @@ func preparePullReviewType(ctx *context.APIContext, pr *issues_model.PullRequest needsBody = false // if there is no body we need to ensure that there are comments if !hasBody && !hasComments { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("review event %s requires a body or a comment", event)) + ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("review event %s requires a body or a comment", event)) return -1, true } default: @@ -726,7 +726,7 @@ func preparePullReviewType(ctx *context.APIContext, pr *issues_model.PullRequest // reject reviews with empty body if a body is required for this call if needsBody && !hasBody { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("review event %s requires a body", event)) + ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("review event %s requires a body", event)) return -1, true } @@ -935,11 +935,11 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions comment, err := issue_service.ReviewRequest(ctx, pr.Issue, ctx.Doer, &permDoer, reviewer, isAdd) if err != nil { if issues_model.IsErrReviewRequestOnClosedPR(err) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) return } if issues_model.IsErrNotValidReviewRequest(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -960,11 +960,11 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions comment, err := issue_service.TeamReviewRequest(ctx, pr.Issue, ctx.Doer, teamReviewer, isAdd) if err != nil { if issues_model.IsErrReviewRequestOnClosedPR(err) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) return } if issues_model.IsErrNotValidReviewRequest(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -1102,7 +1102,7 @@ func dismissReview(ctx *context.APIContext, msg string, isDismiss, dismissPriors _, err := pull_service.DismissReview(ctx, review.ID, ctx.Repo.Repository.ID, msg, ctx.Doer, isDismiss, dismissPriors) if err != nil { if pull_service.IsErrDismissRequestOnClosedPR(err) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) return } ctx.APIErrorInternal(err) diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go index ee995f4402b..a4fc03ae321 100644 --- a/routers/api/v1/repo/release.go +++ b/routers/api/v1/repo/release.go @@ -4,8 +4,6 @@ package repo import ( - "errors" - "fmt" "net/http" auth_model "gitea.dev/models/auth" @@ -243,7 +241,7 @@ func CreateRelease(ctx *context.APIContext) { form := web.GetForm(ctx).(*api.CreateReleaseOption) if ctx.Repo.Repository.IsEmpty { - ctx.APIError(http.StatusUnprocessableEntity, errors.New("repo is empty")) + ctx.APIError(http.StatusUnprocessableEntity, "repo is empty") return } rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, form.TagName) @@ -273,11 +271,11 @@ func CreateRelease(ctx *context.APIContext) { // It doesn't need to be the same as the "release note" if err := release_service.CreateRelease(ctx.Repo.GitRepo, rel, nil, form.TagMessage); err != nil { if repo_model.IsErrReleaseAlreadyExist(err) { - ctx.APIError(http.StatusConflict, err) + ctx.APIError(http.StatusConflict, err.Error()) } else if release_service.IsErrProtectedTagName(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else if git.IsErrNotExist(err) { - ctx.APIError(http.StatusNotFound, fmt.Errorf("target \"%v\" not found: %w", rel.Target, err)) + ctx.APIError(http.StatusNotFound, "target not found") } else { ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/repo/release_attachment.go b/routers/api/v1/repo/release_attachment.go index 896c6d24ccb..715552d72c7 100644 --- a/routers/api/v1/repo/release_attachment.go +++ b/routers/api/v1/repo/release_attachment.go @@ -250,12 +250,12 @@ func CreateReleaseAttachment(ctx *context.APIContext) { }) if err != nil { if upload.IsErrFileTypeForbidden(err) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) return } if errors.Is(err, util.ErrContentTooLarge) { - ctx.APIError(http.StatusRequestEntityTooLarge, err) + ctx.APIError(http.StatusRequestEntityTooLarge, err.Error()) return } @@ -340,7 +340,7 @@ func EditReleaseAttachment(ctx *context.APIContext) { if err := attachment_service.UpdateAttachment(ctx, setting.Repository.Release.AllowedTypes, attach); err != nil { if upload.IsErrFileTypeForbidden(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index e0e40086a56..8adef610001 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -173,7 +173,7 @@ func Search(ctx *context.APIContext) { opts.Collaborate = optional.Some(true) case "": default: - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid search mode: \"%s\"", mode)) + ctx.APIError(http.StatusUnprocessableEntity, "invalid search mode") return } @@ -234,7 +234,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre // If the readme template does not exist, a 400 will be returned. if opt.AutoInit && len(opt.Readme) > 0 && !slices.Contains(repo_module.Readmes, opt.Readme) { - ctx.APIError(http.StatusBadRequest, fmt.Errorf("readme template does not exist, available templates: %v", repo_module.Readmes)) + ctx.APIError(http.StatusBadRequest, fmt.Sprintf("readme template does not exist, available templates: %v", repo_module.Readmes)) return } @@ -258,9 +258,9 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre } else if db.IsErrNameReserved(err) || db.IsErrNamePatternNotAllowed(err) || label.IsErrTemplateLoad(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else if errors.Is(err, util.ErrPermissionDenied) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -413,7 +413,7 @@ func Generate(ctx *context.APIContext) { ctx.APIError(http.StatusConflict, "The repository with the same name already exists.") } else if db.IsErrNameReserved(err) || db.IsErrNamePatternNotAllowed(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -652,13 +652,13 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err if err := repo_service.ChangeRepositoryName(ctx, ctx.Doer, repo, newRepoName); err != nil { switch { case repo_model.IsErrRepoAlreadyExist(err): - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) case db.IsErrNameReserved(err): - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) case db.IsErrNamePatternNotAllowed(err): - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) default: - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("ChangeRepositoryName: %w", err)) + ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("ChangeRepositoryName: %v", err)) } return err } @@ -692,7 +692,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public if visibilityChanged && setting.Repository.ForcePrivate && !*opts.Private && !ctx.Doer.IsAdmin { err := errors.New("cannot change private repository to public") - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return err } @@ -756,12 +756,12 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { // Check that values are valid if !validation.IsValidURL(opts.ExternalTracker.ExternalTrackerURL) { err := errors.New("External tracker URL not valid") - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return err } if len(opts.ExternalTracker.ExternalTrackerFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(opts.ExternalTracker.ExternalTrackerFormat) { err := errors.New("External tracker URL format not valid") - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return err } @@ -897,7 +897,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { // so unrelated PATCH calls don't reject historical configs. if opts.AllowMergeUpdate != nil || opts.AllowRebaseUpdate != nil || opts.DefaultUpdateStyle != nil { if err := config.ValidateUpdateSettings(); err != nil { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return err } } @@ -988,7 +988,7 @@ func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) e if opts.Archived != nil { if repo.IsMirror { err := errors.New("repo is a mirror, cannot archive/un-archive") - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return err } if *opts.Archived { @@ -1042,14 +1042,14 @@ func updateMirror(ctx *context.APIContext, opts api.EditRepoOption) error { interval, err := time.ParseDuration(*opts.MirrorInterval) if err != nil { log.Error("Wrong format for MirrorInternal Sent: %s", err) - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return err } // Ensure the provided duration is not too short if interval != 0 && interval < setting.Mirror.MinInterval { err := fmt.Errorf("invalid mirror interval: %s is below minimum interval: %s", interval, setting.Mirror.MinInterval) - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return err } @@ -1119,7 +1119,7 @@ func updateMirror(ctx *context.APIContext, opts api.EditRepoOption) error { // finally update the mirror in the DB if err := repo_model.UpdateMirror(ctx, mirror); err != nil { log.Error("Failed to Set Mirror Interval: %s", err) - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return err } diff --git a/routers/api/v1/repo/status.go b/routers/api/v1/repo/status.go index 9df2a32e72d..c7d7014e541 100644 --- a/routers/api/v1/repo/status.go +++ b/routers/api/v1/repo/status.go @@ -55,7 +55,7 @@ func NewCommitStatus(ctx *context.APIContext) { form := web.GetForm(ctx).(*api.CreateStatusOption) sha := ctx.PathParam("sha") if len(sha) == 0 { - ctx.APIError(http.StatusBadRequest, nil) + ctx.APIError(http.StatusBadRequest, "sha not provided") return } status := &git_model.CommitStatus{ diff --git a/routers/api/v1/repo/tag.go b/routers/api/v1/repo/tag.go index 796bdb10883..d43d5ea628b 100644 --- a/routers/api/v1/repo/tag.go +++ b/routers/api/v1/repo/tag.go @@ -109,13 +109,13 @@ func GetAnnotatedTag(ctx *context.APIContext) { tag, err := ctx.Repo.GitRepo.GetAnnotatedTag(sha) if err != nil { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) return } commit, err := ctx.Repo.GitRepo.GetTagCommit(tag.Name) if err != nil { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) return } ctx.JSON(http.StatusOK, convert.ToAnnotatedTag(ctx, ctx.Repo.Repository, tag, commit)) @@ -203,13 +203,13 @@ func CreateTag(ctx *context.APIContext) { commit, err := ctx.Repo.GitRepo.GetCommit(form.Target) if err != nil { - ctx.APIError(http.StatusNotFound, fmt.Errorf("target not found: %w", err)) + ctx.APIError(http.StatusNotFound, fmt.Sprintf("target not found: %v", err)) return } if err := release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, commit.ID.String(), form.TagName, form.Message); err != nil { if release_service.IsErrTagAlreadyExists(err) { - ctx.APIError(http.StatusConflict, err) + ctx.APIError(http.StatusConflict, err.Error()) return } if release_service.IsErrProtectedTagName(err) { @@ -278,7 +278,7 @@ func DeleteTag(ctx *context.APIContext) { } if !tag.IsTag { - ctx.APIError(http.StatusConflict, errors.New("a tag attached to a release cannot be deleted directly")) + ctx.APIError(http.StatusConflict, "a tag attached to a release cannot be deleted directly") return } @@ -438,7 +438,7 @@ func CreateTagProtection(ctx *context.APIContext) { whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.WhitelistUsernames, false) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -449,7 +449,7 @@ func CreateTagProtection(ctx *context.APIContext) { whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.WhitelistTeams, false) if err != nil { if organization.IsErrTeamNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -546,7 +546,7 @@ func EditTagProtection(ctx *context.APIContext) { whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.WhitelistTeams, false) if err != nil { if organization.IsErrTeamNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) @@ -560,7 +560,7 @@ func EditTagProtection(ctx *context.APIContext) { whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.WhitelistUsernames, false) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) return } ctx.APIErrorInternal(err) diff --git a/routers/api/v1/repo/teams.go b/routers/api/v1/repo/teams.go index 1913bb6eecd..fc39b1c6166 100644 --- a/routers/api/v1/repo/teams.go +++ b/routers/api/v1/repo/teams.go @@ -201,13 +201,13 @@ func changeRepoTeam(ctx *context.APIContext, add bool) { var err error if add { if repoHasTeam { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("team '%s' is already added to repo", team.Name)) + ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("team '%s' is already added to repo", team.Name)) return } err = repo_service.TeamAddRepository(ctx, team, ctx.Repo.Repository) } else { if !repoHasTeam { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("team '%s' was not added to repo", team.Name)) + ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("team '%s' was not added to repo", team.Name)) return } err = repo_service.RemoveRepositoryFromTeam(ctx, team, ctx.Repo.Repository.ID) @@ -224,7 +224,7 @@ func getTeamByParam(ctx *context.APIContext) *organization.Team { team, err := organization.GetTeam(ctx, ctx.Repo.Owner.ID, ctx.PathParam("team")) if err != nil { if organization.IsErrTeamNotExist(err) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) return nil } ctx.APIErrorInternal(err) diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go index e2c124a86a5..63fc3b0712c 100644 --- a/routers/api/v1/repo/transfer.go +++ b/routers/api/v1/repo/transfer.go @@ -87,12 +87,12 @@ func Transfer(ctx *context.APIContext) { for _, tID := range *opts.TeamIDs { team, err := organization.GetTeamByID(ctx, tID) if err != nil { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("team %d not found", tID)) + ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("team %d not found", tID)) return } if team.OrgID != org.ID { - ctx.APIError(http.StatusForbidden, fmt.Errorf("team %d belongs not to org %d", tID, org.ID)) + ctx.APIError(http.StatusForbidden, fmt.Sprintf("team %d belongs not to org %d", tID, org.ID)) return } @@ -110,13 +110,13 @@ func Transfer(ctx *context.APIContext) { if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, ctx.Repo.Repository, teams); err != nil { switch { case repo_model.IsErrRepoTransferInProgress(err): - ctx.APIError(http.StatusConflict, err) + ctx.APIError(http.StatusConflict, err.Error()) case repo_model.IsErrRepoAlreadyExist(err): - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) case repo_service.IsRepositoryLimitReached(err): - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) case errors.Is(err, user_model.ErrBlockedUser): - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) default: ctx.APIErrorInternal(err) } @@ -163,11 +163,11 @@ func AcceptTransfer(ctx *context.APIContext) { if err != nil { switch { case repo_model.IsErrNoPendingTransfer(err): - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) case errors.Is(err, util.ErrPermissionDenied): - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) case repo_service.IsRepositoryLimitReached(err): - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) default: ctx.APIErrorInternal(err) } @@ -207,9 +207,9 @@ func RejectTransfer(ctx *context.APIContext) { if err != nil { switch { case repo_model.IsErrNoPendingTransfer(err): - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) case errors.Is(err, util.ErrPermissionDenied): - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) default: ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go index 16471044820..dad0bfbbce0 100644 --- a/routers/api/v1/repo/wiki.go +++ b/routers/api/v1/repo/wiki.go @@ -59,7 +59,7 @@ func NewWikiPage(ctx *context.APIContext) { form := web.GetForm(ctx).(*api.CreateWikiPageOptions) if util.IsEmptyString(form.Title) { - ctx.APIError(http.StatusBadRequest, nil) + ctx.APIError(http.StatusBadRequest, "title is required") return } @@ -71,16 +71,16 @@ func NewWikiPage(ctx *context.APIContext) { content, err := base64.StdEncoding.DecodeString(form.ContentBase64) if err != nil { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) return } form.ContentBase64 = string(content) if err := wiki_service.AddWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName, form.ContentBase64, form.Message); err != nil { if repo_model.IsErrWikiReservedName(err) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else if repo_model.IsErrWikiAlreadyExist(err) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -149,7 +149,7 @@ func EditWikiPage(ctx *context.APIContext) { content, err := base64.StdEncoding.DecodeString(form.ContentBase64) if err != nil { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) return } form.ContentBase64 = string(content) diff --git a/routers/api/v1/shared/action.go b/routers/api/v1/shared/action.go index 3dbe7008bec..5c95fa192c0 100644 --- a/routers/api/v1/shared/action.go +++ b/routers/api/v1/shared/action.go @@ -16,6 +16,7 @@ import ( "gitea.dev/modules/optional" "gitea.dev/modules/setting" api "gitea.dev/modules/structs" + "gitea.dev/modules/util" "gitea.dev/modules/webhook" "gitea.dev/routers/api/v1/utils" "gitea.dev/services/context" @@ -53,7 +54,7 @@ func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64, runAttemptI for _, status := range ctx.FormStrings("status") { values, err := convertToInternal(status) if err != nil { - ctx.APIError(http.StatusBadRequest, fmt.Errorf("Invalid status %s", status)) + ctx.APIError(http.StatusBadRequest, err.Error()) return } opts.Statuses = append(opts.Statuses, values...) @@ -125,7 +126,7 @@ func convertToInternal(s string) ([]actions_model.Status, error) { case "cancelled", "timed_out": return []actions_model.Status{actions_model.StatusCancelled}, nil default: - return nil, fmt.Errorf("invalid status %s", s) + return nil, util.NewInvalidArgumentErrorf("invalid status %s", s) } } @@ -155,7 +156,7 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) { for _, status := range ctx.FormStrings("status") { values, err := convertToInternal(status) if err != nil { - ctx.APIError(http.StatusBadRequest, fmt.Errorf("Invalid status %s", status)) + ctx.APIError(http.StatusBadRequest, err.Error()) return } opts.Status = append(opts.Status, values...) diff --git a/routers/api/v1/shared/block.go b/routers/api/v1/shared/block.go index e8190041aa5..8b2a207ccb2 100644 --- a/routers/api/v1/shared/block.go +++ b/routers/api/v1/shared/block.go @@ -68,7 +68,7 @@ func BlockUser(ctx *context.APIContext, blocker *user_model.User) { if err := user_service.BlockUser(ctx, ctx.Doer, blocker, blockee, ctx.FormString("note")); err != nil { if errors.Is(err, user_model.ErrCanNotBlock) || errors.Is(err, user_model.ErrBlockOrganization) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -87,7 +87,7 @@ func UnblockUser(ctx *context.APIContext, doer, blocker *user_model.User) { if err := user_service.UnblockUser(ctx, doer, blocker, blockee); err != nil { if errors.Is(err, user_model.ErrCanNotUnblock) || errors.Is(err, user_model.ErrBlockOrganization) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else { ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/user/action.go b/routers/api/v1/user/action.go index f5654908f0d..1086b221924 100644 --- a/routers/api/v1/user/action.go +++ b/routers/api/v1/user/action.go @@ -53,9 +53,9 @@ func CreateOrUpdateSecret(ctx *context.APIContext) { _, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Doer.ID, 0, ctx.PathParam("secretname"), opt.Data, opt.Description) if err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -95,9 +95,9 @@ func DeleteSecret(ctx *context.APIContext) { err := secret_service.DeleteSecretByName(ctx, ctx.Doer.ID, 0, ctx.PathParam("secretname")) if err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -148,13 +148,13 @@ func CreateVariable(ctx *context.APIContext) { return } if v != nil && v.ID > 0 { - ctx.APIError(http.StatusConflict, util.NewAlreadyExistErrorf("variable name %s already exists", variableName)) + ctx.APIError(http.StatusConflict, "variable name already exists") return } if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value, opt.Description); err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -201,7 +201,7 @@ func UpdateVariable(ctx *context.APIContext) { }) if err != nil { if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -218,7 +218,7 @@ func UpdateVariable(ctx *context.APIContext) { if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -253,9 +253,9 @@ func DeleteVariable(ctx *context.APIContext) { if err := actions_service.DeleteVariableByName(ctx, ctx.Doer.ID, 0, ctx.PathParam("variablename")); err != nil { if errors.Is(err, util.ErrInvalidArgument) { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) } else if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } @@ -292,7 +292,7 @@ func GetVariable(ctx *context.APIContext) { }) if err != nil { if errors.Is(err, util.ErrNotExist) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go index 729c408a151..a410909e0e5 100644 --- a/routers/api/v1/user/app.go +++ b/routers/api/v1/user/app.go @@ -5,7 +5,6 @@ package user import ( - "errors" "fmt" "net/http" "strconv" @@ -112,13 +111,13 @@ func CreateAccessToken(ctx *context.APIContext) { return } if exist { - ctx.APIError(http.StatusBadRequest, errors.New("access token name has been used already")) + ctx.APIError(http.StatusBadRequest, "access token name has been used already") return } scope, err := auth_model.AccessTokenScope(strings.Join(form.Scopes, ",")).Normalize() if err != nil { - ctx.APIError(http.StatusBadRequest, fmt.Errorf("invalid access token scope provided: %w", err)) + ctx.APIError(http.StatusBadRequest, fmt.Sprintf("invalid access token scope provided: %v", err)) return } if scope == "" { @@ -188,7 +187,7 @@ func DeleteAccessToken(ctx *context.APIContext) { case 1: tokenID = tokens[0].ID default: - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("multiple matches for token name '%s'", token)) + ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("multiple matches for token name '%s'", token)) return } } diff --git a/routers/api/v1/user/avatar.go b/routers/api/v1/user/avatar.go index d02ca87fb27..428ac0f62b7 100644 --- a/routers/api/v1/user/avatar.go +++ b/routers/api/v1/user/avatar.go @@ -32,7 +32,7 @@ func UpdateAvatar(ctx *context.APIContext) { content, err := base64.StdEncoding.DecodeString(form.Image) if err != nil { - ctx.APIError(http.StatusBadRequest, err) + ctx.APIError(http.StatusBadRequest, err.Error()) return } diff --git a/routers/api/v1/user/email.go b/routers/api/v1/user/email.go index 71b5a691c15..d3329f7a743 100644 --- a/routers/api/v1/user/email.go +++ b/routers/api/v1/user/email.go @@ -122,7 +122,7 @@ func DeleteEmail(ctx *context.APIContext) { if err := user_service.DeleteEmailAddresses(ctx, ctx.Doer, form.Emails); err != nil { if user_model.IsErrEmailAddressNotExist(err) { - ctx.APIError(http.StatusNotFound, err) + ctx.APIError(http.StatusNotFound, err.Error()) } else { ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/user/follower.go b/routers/api/v1/user/follower.go index 11f659c85a4..47eabb5351d 100644 --- a/routers/api/v1/user/follower.go +++ b/routers/api/v1/user/follower.go @@ -233,7 +233,7 @@ func Follow(ctx *context.APIContext) { if err := user_model.FollowUser(ctx, ctx.Doer, ctx.ContextUser); err != nil { if errors.Is(err, user_model.ErrBlockedUser) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) } else { ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/user/gpg_key.go b/routers/api/v1/user/gpg_key.go index bae4d7420f1..562e70b5c0b 100644 --- a/routers/api/v1/user/gpg_key.go +++ b/routers/api/v1/user/gpg_key.go @@ -294,7 +294,7 @@ func HandleAddGPGKeyError(ctx *context.APIContext, err error, token string) { case asymkey_model.IsErrGPGKeyIDAlreadyUsed(err): ctx.APIError(http.StatusUnprocessableEntity, "A key with the same id already exists") case asymkey_model.IsErrGPGKeyParsing(err): - ctx.APIError(http.StatusUnprocessableEntity, err) + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) case asymkey_model.IsErrGPGNoEmailFound(err): ctx.APIError(http.StatusNotFound, "None of the emails attached to the GPG key could be found. It may still be added if you provide a valid signature for the token: "+token) case asymkey_model.IsErrGPGInvalidTokenSignature(err): diff --git a/routers/api/v1/user/star.go b/routers/api/v1/user/star.go index 7036a4f854b..c78b6872c5a 100644 --- a/routers/api/v1/user/star.go +++ b/routers/api/v1/user/star.go @@ -174,7 +174,7 @@ func Star(ctx *context.APIContext) { err := repo_model.StarRepo(ctx, ctx.Doer, ctx.Repo.Repository, true) if err != nil { if errors.Is(err, user_model.ErrBlockedUser) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) } else { ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/user/watch.go b/routers/api/v1/user/watch.go index 94d3de84b8a..2d1f55d8709 100644 --- a/routers/api/v1/user/watch.go +++ b/routers/api/v1/user/watch.go @@ -172,7 +172,7 @@ func Watch(ctx *context.APIContext) { err := repo_model.WatchRepo(ctx, ctx.Doer, ctx.Repo.Repository, true) if err != nil { if errors.Is(err, user_model.ErrBlockedUser) { - ctx.APIError(http.StatusForbidden, err) + ctx.APIError(http.StatusForbidden, err.Error()) } else { ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/utils/sort.go b/routers/api/v1/utils/sort.go index b5f819cdb6e..669a09988ad 100644 --- a/routers/api/v1/utils/sort.go +++ b/routers/api/v1/utils/sort.go @@ -26,12 +26,12 @@ func ResolveSortOrder(ctx *context.APIContext, orderByMap map[string]map[string] } orderMap, ok := orderByMap[sortOrder] if !ok { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid sort order: %q", sortOrder)) + ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid sort order: %q", sortOrder)) return "", false } orderBy, ok := orderMap[sortMode] if !ok { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid sort mode: %q", sortMode)) + ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid sort mode: %q", sortMode)) return "", false } return orderBy, true diff --git a/services/context/api.go b/services/context/api.go index 05b6490826a..7731b5692fc 100644 --- a/services/context/api.go +++ b/services/context/api.go @@ -137,16 +137,34 @@ func (ctx *APIContext) apiErrorInternal(skip int, err error) { }) } -// APIError responds with an error message to client with given obj as the message. -// If status is 500, also it prints error to log. -func (ctx *APIContext) APIError(status int, obj any) { +// APIErrorNotFound handles 404s for APIContext +// String will replace message, errors will be added to a slice +func (ctx *APIContext) APIErrorNotFound(objs ...any) { var message string - if err, ok := obj.(error); ok { - message = err.Error() - } else { - message = fmt.Sprintf("%s", obj) - } + var errs []string + for _, obj := range objs { + // Ignore nil + if obj == nil { + continue + } + if err, ok := obj.(error); ok { + errs = append(errs, err.Error()) + } else { + message = obj.(string) + } + } + ctx.JSON(http.StatusNotFound, map[string]any{ + "message": util.IfZero(message, "not found"), // do not use locale in API + "url": setting.API.SwaggerURL, + "errors": errs, + }) +} + +// APIError responds with an error message to client. +// If status is 500, also it prints error to log. +func (ctx *APIContext) APIError(status int, msg string) { + message := msg if status == http.StatusInternalServerError { log.ErrorWithSkip(1, "APIError: %s", message) @@ -161,6 +179,26 @@ func (ctx *APIContext) APIError(status int, obj any) { }) } +// APIErrorAuto use error check function to determine the response code +func (ctx *APIContext) APIErrorAuto(err error) { + switch { + case errors.Is(err, util.ErrInvalidArgument): + ctx.APIError(http.StatusBadRequest, err.Error()) + case errors.Is(err, util.ErrPermissionDenied): + ctx.APIError(http.StatusForbidden, err.Error()) + case errors.Is(err, util.ErrNotExist): + ctx.APIError(http.StatusNotFound, err.Error()) + case errors.Is(err, util.ErrAlreadyExist): + ctx.APIError(http.StatusConflict, err.Error()) + case errors.Is(err, util.ErrContentTooLarge): + ctx.APIError(http.StatusRequestEntityTooLarge, err.Error()) + case errors.Is(err, util.ErrUnprocessableContent): + ctx.APIError(http.StatusUnprocessableEntity, err.Error()) + default: + ctx.apiErrorInternal(1, err) + } +} + type apiContextKeyType struct{} var apiContextKey = apiContextKeyType{} @@ -248,30 +286,6 @@ func APIContexter() func(http.Handler) http.Handler { } } -// APIErrorNotFound handles 404s for APIContext -// String will replace message, errors will be added to a slice -func (ctx *APIContext) APIErrorNotFound(objs ...any) { - var message string - var errs []string - for _, obj := range objs { - // Ignore nil - if obj == nil { - continue - } - - if err, ok := obj.(error); ok { - errs = append(errs, err.Error()) - } else { - message = obj.(string) - } - } - ctx.JSON(http.StatusNotFound, map[string]any{ - "message": util.IfZero(message, "not found"), // do not use locale in API - "url": setting.API.SwaggerURL, - "errors": errs, - }) -} - // ReferencesGitRepo injects the GitRepo into the Context // you can optional skip the IsEmpty check func ReferencesGitRepo(allowEmpty ...bool) func(ctx *APIContext) { @@ -329,17 +343,6 @@ func RepoRefForAPI(next http.Handler) http.Handler { }) } -// NotFoundOrServerError use error check function to determine if the error -// is about not found. It responds with 404 status code for not found error, -// or error context description for logging purpose of 500 server error. -func (ctx *APIContext) NotFoundOrServerError(err error) { - if errors.Is(err, util.ErrNotExist) { - ctx.JSON(http.StatusNotFound, nil) - return - } - ctx.APIErrorInternal(err) -} - // IsUserSiteAdmin returns true if current user is a site admin func (ctx *APIContext) IsUserSiteAdmin() bool { return ctx.IsSigned && ctx.Doer.IsAdmin diff --git a/services/context/package.go b/services/context/package.go index 4918e124913..7c1e55c8a34 100644 --- a/services/context/package.go +++ b/services/context/package.go @@ -34,11 +34,8 @@ type packageAssignmentCtx struct { // PackageAssignment returns a middleware to handle Context.Package assignment func PackageAssignment() func(ctx *Context) { return func(ctx *Context) { - errorFn := func(status int, obj any) { - err, ok := obj.(error) - if !ok { - err = fmt.Errorf("%s", obj) - } + errorFn := func(status int, msg string) { + err := fmt.Errorf("%s", msg) if status == http.StatusNotFound { ctx.NotFound(err) } else { @@ -58,11 +55,11 @@ func PackageAssignmentAPI() func(ctx *APIContext) { } } -func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, any)) *Package { +func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, string)) *Package { pkgOwner := ctx.ContextUser accessMode, err := determineAccessMode(ctx.Base, pkgOwner, ctx.Doer) if err != nil { - errCb(http.StatusInternalServerError, fmt.Errorf("determineAccessMode: %w", err)) + errCb(http.StatusInternalServerError, fmt.Sprintf("determineAccessMode: %v", err)) return nil } @@ -81,25 +78,25 @@ func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, any)) *Package pv, err := packages_model.GetVersionByNameAndVersion(ctx, pkg.Owner.ID, packages_model.Type(packageType), name, version) if err != nil { if errors.Is(err, packages_model.ErrPackageNotExist) { - errCb(http.StatusNotFound, fmt.Errorf("GetVersionByNameAndVersion: %w", err)) + errCb(http.StatusNotFound, fmt.Sprintf("GetVersionByNameAndVersion: %v", err)) } else { - errCb(http.StatusInternalServerError, fmt.Errorf("GetVersionByNameAndVersion: %w", err)) + errCb(http.StatusInternalServerError, fmt.Sprintf("GetVersionByNameAndVersion: %v", err)) } return pkg } pkg.Descriptor, err = packages_model.GetPackageDescriptor(ctx, pv) if err != nil { - errCb(http.StatusInternalServerError, fmt.Errorf("GetPackageDescriptor: %w", err)) + errCb(http.StatusInternalServerError, fmt.Sprintf("GetPackageDescriptor: %v", err)) return pkg } } else { p, err := packages_model.GetPackageByName(ctx, pkg.Owner.ID, packages_model.Type(packageType), name) if err != nil { if errors.Is(err, packages_model.ErrPackageNotExist) { - errCb(http.StatusNotFound, fmt.Errorf("GetPackageByName: %w", err)) + errCb(http.StatusNotFound, fmt.Sprintf("GetPackageByName: %v", err)) } else { - errCb(http.StatusInternalServerError, fmt.Errorf("GetPackageByName: %w", err)) + errCb(http.StatusInternalServerError, fmt.Sprintf("GetPackageByName: %v", err)) } return pkg } diff --git a/services/context/user.go b/services/context/user.go index ad0876ebf00..d335b9738a5 100644 --- a/services/context/user.go +++ b/services/context/user.go @@ -14,11 +14,8 @@ import ( // UserAssignmentWeb returns a middleware to handle context-user assignment for web routes func UserAssignmentWeb() func(ctx *Context) { return func(ctx *Context) { - errorFn := func(status int, obj any) { - err, ok := obj.(error) - if !ok { - err = fmt.Errorf("%s", obj) - } + errorFn := func(status int, msg string) { + err := fmt.Errorf("%s", msg) if status == http.StatusNotFound { ctx.NotFound(err) } else { @@ -37,7 +34,7 @@ func UserAssignmentAPI() func(ctx *APIContext) { } } -func userAssignment(ctx *Base, doer *user_model.User, errCb func(int, any)) (contextUser *user_model.User) { +func userAssignment(ctx *Base, doer *user_model.User, errCb func(int, string)) (contextUser *user_model.User) { username := ctx.PathParam("username") if doer != nil && strings.EqualFold(doer.LowerName, username) { @@ -50,12 +47,12 @@ func userAssignment(ctx *Base, doer *user_model.User, errCb func(int, any)) (con if redirectUserID, err := user_model.LookupUserRedirect(ctx, username); err == nil { RedirectToUser(ctx, doer, username, redirectUserID) } else if user_model.IsErrUserRedirectNotExist(err) { - errCb(http.StatusNotFound, err) + errCb(http.StatusNotFound, err.Error()) } else { - errCb(http.StatusInternalServerError, fmt.Errorf("LookupUserRedirect: %w", err)) + errCb(http.StatusInternalServerError, fmt.Sprintf("LookupUserRedirect: %v", err)) } } else { - errCb(http.StatusInternalServerError, fmt.Errorf("GetUserByName: %w", err)) + errCb(http.StatusInternalServerError, fmt.Sprintf("GetUserByName: %v", err)) } } } From 9bbea90bfe1aedec1433b28a06649b33d48e88a7 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 7 Jun 2026 18:28:17 +0800 Subject: [PATCH 22/40] fix: pgsql lint (#38022) --- models/db/driver_postgresschema.go | 65 +++++++++--------------------- 1 file changed, 20 insertions(+), 45 deletions(-) diff --git a/models/db/driver_postgresschema.go b/models/db/driver_postgresschema.go index ad2b5abc04a..616712fb2a0 100644 --- a/models/db/driver_postgresschema.go +++ b/models/db/driver_postgresschema.go @@ -4,8 +4,10 @@ package db import ( + "context" "database/sql" "database/sql/driver" + "errors" "sync" "gitea.dev/modules/setting" @@ -14,61 +16,34 @@ import ( "xorm.io/xorm/dialects" ) -var registerOnce sync.Once +type postgresSchemaDriver struct{} -func registerPostgresSchemaDriver() { - registerOnce.Do(func() { - sql.Register(sqlDriverPostgresSchema, &postgresSchemaDriver{}) - dialects.RegisterDriver(sqlDriverPostgresSchema, dialects.QueryDriver("postgres")) - }) -} +var registerPostgresSchemaDriver = sync.OnceFunc(func() { + sql.Register(sqlDriverPostgresSchema, &postgresSchemaDriver{}) + dialects.RegisterDriver(sqlDriverPostgresSchema, dialects.QueryDriver("postgres")) +}) -type postgresSchemaDriver struct { - pq.Driver -} - -// Open opens a new connection to the database. name is a connection string. -// This function opens the postgres connection in the default manner but immediately -// runs set_config to set the search_path appropriately -func (d *postgresSchemaDriver) Open(name string) (driver.Conn, error) { - conn, err := d.Driver.Open(name) +// Open opens the postgres connection in the default manner with default schema support. +// It immediately runs "set_config" to set the search_path appropriately. +func (*postgresSchemaDriver) Open(connStr string) (driver.Conn, error) { + conn, err := pq.Driver{}.Open(connStr) if err != nil { - return conn, err + return nil, err } - schemaValue, _ := driver.String.ConvertValue(setting.Database.Schema) - // golangci lint is incorrect here - there is no benefit to using driver.ExecerContext here - // and in any case pq does not implement it - if execer, ok := conn.(driver.Execer); ok { //nolint:staticcheck // see above - _, err := execer.Exec(`SELECT set_config( + connExec, ok := conn.(driver.ExecerContext) + if !ok { + return nil, errors.New("postgres driver does not implement ExecerContext interface") + } + _, err = connExec.ExecContext(context.Background(), `SELECT set_config( 'search_path', $1 || ',' || current_setting('search_path'), - false)`, []driver.Value{schemaValue}) - if err != nil { - _ = conn.Close() - return nil, err - } - return conn, nil - } - - stmt, err := conn.Prepare(`SELECT set_config( - 'search_path', - $1 || ',' || current_setting('search_path'), - false)`) + false)`, + []driver.NamedValue{{Ordinal: 1, Value: setting.Database.Schema}}, + ) if err != nil { _ = conn.Close() return nil, err } - defer stmt.Close() - - // driver.String.ConvertValue will never return err for string - - // golangci lint is incorrect here - there is no benefit to using stmt.ExecWithContext here - _, err = stmt.Exec([]driver.Value{schemaValue}) //nolint:staticcheck // see above - if err != nil { - _ = conn.Close() - return nil, err - } - return conn, nil } From e2fbfc8730f589235932d8d694db5d6e07ae091b Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 7 Jun 2026 18:33:16 +0800 Subject: [PATCH 23/40] fix: various dropdown problems (#38020) 1. remove legacy onResponseKeepSelectedItem, refactor the code to dropdown.js 2. make dropdown correctly handle "single selection + remote query + filter" * fix #38018 3. fix incorrect "transition" class usage for the dropdown dividers --- web_src/css/modules/dropdown.css | 4 ++++ web_src/fomantic/build/components/dropdown.js | 20 ++++++++++-------- web_src/js/features/repo-new.ts | 1 - web_src/js/modules/fomantic/dropdown.test.ts | 16 +++++++------- web_src/js/modules/fomantic/dropdown.ts | 21 ++----------------- 5 files changed, 25 insertions(+), 37 deletions(-) diff --git a/web_src/css/modules/dropdown.css b/web_src/css/modules/dropdown.css index 9d3dc48adae..02957fe31d9 100644 --- a/web_src/css/modules/dropdown.css +++ b/web_src/css/modules/dropdown.css @@ -80,6 +80,10 @@ border-top-width: 0; } +.ui.dropdown .menu .hidden { /* for hidden items and dividers */ + display: none; +} + .ui.dropdown .menu > .header { margin: 1rem 0 0.75rem; padding: 0 1.14285714rem; diff --git a/web_src/fomantic/build/components/dropdown.js b/web_src/fomantic/build/components/dropdown.js index b8f066db748..0faed1858c4 100644 --- a/web_src/fomantic/build/components/dropdown.js +++ b/web_src/fomantic/build/components/dropdown.js @@ -308,12 +308,12 @@ $.fn.dropdown = function(parameters) { firstUnfiltered: function() { module.verbose('Selecting first non-filtered element'); module.remove.selectedItem(); - $item + const $selectable = $item .not(selector.unselectable) - .not(selector.addition + selector.hidden) - .eq(0) - .addClass(className.selected) - ; + .not(selector.addition + selector.hidden); + let $selectedItem = $selectable.filter(`[data-value="${CSS.escape($input.val())}"]`); // GITEA-PATCH: try to re-select the last selected item for single selection + if (!$selectedItem.length) $selectedItem = $item.eq(0); + $selectedItem.addClass(className.selected); }, nextAvailable: function($selected) { $selected = $selected.eq(0); @@ -772,11 +772,13 @@ $.fn.dropdown = function(parameters) { if(!Array.isArray(preSelected)) { preSelected = preSelected && preSelected!=="" ? preSelected.split(settings.delimiter) : []; } - $.each(preSelected,function(index,value){ - $item.filter('[data-value="'+CSS.escape(value)+'"]') // GITEA-PATCH: use "CSS.escape" for query selector + if (module.is.multiple()) { // GITEA-PATCH: only hide selected items when the dropdown is "multiple selection" + $.each(preSelected, function (index, value) { + $item.filter('[data-value="' + CSS.escape(value) + '"]') // GITEA-PATCH: use "CSS.escape" for query selector .addClass(className.filtered) - ; - }); + ; + }); + } afterFiltered(); }); } diff --git a/web_src/js/features/repo-new.ts b/web_src/js/features/repo-new.ts index b690ace0f7f..8b1188f19f1 100644 --- a/web_src/js/features/repo-new.ts +++ b/web_src/js/features/repo-new.ts @@ -47,7 +47,6 @@ function initRepoNewTemplateSearch(form: HTMLFormElement) { value: String(tmplRepo.repository.id), }); } - $repoTemplateDropdown.fomanticExt.onResponseKeepSelectedItem($repoTemplateDropdown, inputRepoTemplate.value); return {results}; }, cache: false, diff --git a/web_src/js/modules/fomantic/dropdown.test.ts b/web_src/js/modules/fomantic/dropdown.test.ts index dd3497c8fce..542cb854b84 100644 --- a/web_src/js/modules/fomantic/dropdown.test.ts +++ b/web_src/js/modules/fomantic/dropdown.test.ts @@ -13,13 +13,13 @@ test('hideScopedEmptyDividers-simple', () => {
    `); hideScopedEmptyDividers(container); expect(container.innerHTML).toEqual(` - +
    a
    - - + +
    b
    - + `); }); @@ -35,7 +35,7 @@ test('hideScopedEmptyDividers-items-all-filtered', () => { hideScopedEmptyDividers(container); expect(container.innerHTML).toEqual(`
    - +
    a
    b
    @@ -52,7 +52,7 @@ test('hideScopedEmptyDividers-hide-last', () => { hideScopedEmptyDividers(container); expect(container.innerHTML).toEqual(`
    a
    - +
    b
    `); }); @@ -68,9 +68,9 @@ test('hideScopedEmptyDividers-scoped-items', () => { hideScopedEmptyDividers(container); expect(container.innerHTML).toEqual(`
    a
    - +
    b
    - +
    c
    `); }); diff --git a/web_src/js/modules/fomantic/dropdown.ts b/web_src/js/modules/fomantic/dropdown.ts index e49c410682f..a12b56d23d9 100644 --- a/web_src/js/modules/fomantic/dropdown.ts +++ b/web_src/js/modules/fomantic/dropdown.ts @@ -8,7 +8,6 @@ const fomanticDropdownFn = $.fn.dropdown; export function initAriaDropdownPatch() { if ($.fn.dropdown === ariaDropdownFn) throw new Error('initAriaDropdownPatch could only be called once'); $.fn.dropdown = ariaDropdownFn; - $.fn.fomanticExt.onResponseKeepSelectedItem = onResponseKeepSelectedItem; $.fn.fomanticExt.onDropdownAfterFiltered = onDropdownAfterFiltered; (ariaDropdownFn as FomanticInitFunction).settings = fomanticDropdownFn.settings; } @@ -296,8 +295,8 @@ export function hideScopedEmptyDividers(container: Element) { let curScope: string = '', lastVisibleScope: string = ''; const isDivider = (item: Element) => item.classList.contains('divider'); const isScopedDivider = (item: Element) => isDivider(item) && item.hasAttribute('data-scope'); - const hideDivider = (item: Element) => item.classList.add('hidden', 'transition'); // dropdown has its own classes to hide items - const showDivider = (item: Element) => item.classList.remove('hidden', 'transition'); + const hideDivider = (item: Element) => item.classList.add('hidden'); // dropdown has its own classes to hide items + const showDivider = (item: Element) => item.classList.remove('hidden'); const isHidden = (item: Element) => item.classList.contains('hidden') || item.classList.contains('filtered') || item.classList.contains('tw-hidden'); const handleScopeSwitch = (itemScope: string) => { if (curScopeVisibleItems.length === 1 && isScopedDivider(curScopeVisibleItems[0])) { @@ -347,19 +346,3 @@ export function hideScopedEmptyDividers(container: Element) { if (visibleItems[i + 1].matches('.divider')) hideDivider(visibleItems[i]); } } - -function onResponseKeepSelectedItem(dropdown: typeof $ | HTMLElement, selectedValue: string) { - // There is a bug in fomantic dropdown when using "apiSettings" to fetch data - // * when there is a selected item, the dropdown insists on hiding the selected one from the list: - // * in the "filter" function: ('[data-value="'+value+'"]').addClass(className.filtered) - // - // When user selects one item, and click the dropdown again, - // then the dropdown only shows other items and will select another (wrong) one. - // It can't be easily fix by using setTimeout(patch, 0) in `onResponse` because the `onResponse` is called before another `setTimeout(..., timeLeft)` - // Fortunately, the "timeLeft" is controlled by "loadingDuration" which is always zero at the moment, so we can use `setTimeout(..., 10)` - const elDropdown = (dropdown instanceof HTMLElement) ? dropdown : (dropdown as any)[0]; - setTimeout(() => { - queryElems(elDropdown, `.menu .item[data-value="${CSS.escape(selectedValue)}"].filtered`, (el) => el.classList.remove('filtered')); - $(elDropdown).dropdown('set selected', selectedValue ?? ''); - }, 10); -} From ea35af1b68d57522c7686618bd61d3216d91589f Mon Sep 17 00:00:00 2001 From: bircni Date: Sun, 7 Jun 2026 17:30:18 +0200 Subject: [PATCH 24/40] fix: bound CODEOWNERS regex match time (#38011) User-supplied CODEOWNERS patterns were compiled without a match timeout, so a crafted pattern (e.g. (a+)+) against a crafted file path could backtrack for tens of seconds inside the PR creation transaction and exhaust the database connection pool. Set MatchTimeout on each compiled rule; the caller already treats match errors as non-matches. --------- Signed-off-by: wxiaoguang Co-authored-by: wxiaoguang --- models/issues/pull.go | 8 ++++++++ models/issues/pull_test.go | 19 +++++++++++++++++++ services/issue/pull.go | 14 ++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/models/issues/pull.go b/models/issues/pull.go index 7dbcef0d3fe..2b93b926b01 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -10,6 +10,7 @@ import ( "fmt" "io" "strings" + "time" "gitea.dev/models/db" git_model "gitea.dev/models/git" @@ -860,6 +861,11 @@ func GetCodeOwnersFromContent(ctx context.Context, data string) ([]*CodeOwnerRul return rules, warnings } +// codeOwnerMatchTimeout bounds a single pattern match so a crafted pattern +// cannot stall via catastrophic backtracking. See also the aggregate budget +// enforced by the caller across the whole rules×files match loop. +const codeOwnerMatchTimeout = 150 * time.Millisecond + type CodeOwnerRule struct { Rule *regexp2.Regexp // it supports negative lookahead, does better for end users Negative bool @@ -888,6 +894,8 @@ func ParseCodeOwnersLine(ctx context.Context, tokens []string) (*CodeOwnerRule, warnings = append(warnings, fmt.Sprintf("incorrect codeowner regexp: %s", err)) return nil, warnings } + // Bound matching time so user-supplied patterns cannot stall PR creation via catastrophic backtracking. + rule.Rule.MatchTimeout = codeOwnerMatchTimeout for _, user := range tokens[1:] { user = strings.TrimPrefix(user, "@") diff --git a/models/issues/pull_test.go b/models/issues/pull_test.go index 166fdd8482b..bd7499fa80b 100644 --- a/models/issues/pull_test.go +++ b/models/issues/pull_test.go @@ -4,7 +4,9 @@ package issues_test import ( + "strings" "testing" + "time" "gitea.dev/models/db" issues_model "gitea.dev/models/issues" @@ -39,6 +41,7 @@ func TestPullRequest(t *testing.T) { t.Run("DeleteOrphanedObjects", testDeleteOrphanedObjects) t.Run("ParseCodeOwnersLine", testParseCodeOwnersLine) t.Run("CodeOwnerAbsolutePathPatterns", testCodeOwnerAbsolutePathPatterns) + t.Run("CodeOwnerPatternMatchTimeout", testCodeOwnerPatternMatchTimeout) t.Run("GetApprovers", testGetApprovers) t.Run("GetPullRequestByMergedCommit", testGetPullRequestByMergedCommit) t.Run("Migrate_InsertPullRequests", testMigrateInsertPullRequests) @@ -376,6 +379,22 @@ func testCodeOwnerAbsolutePathPatterns(t *testing.T) { } } +// testCodeOwnerPatternMatchTimeout ensures user-supplied CODEOWNERS patterns +// cannot stall pull request processing through catastrophic regex backtracking: +// each compiled rule must enforce a bounded match time. +func testCodeOwnerPatternMatchTimeout(t *testing.T) { + rules, _ := issues_model.GetCodeOwnersFromContent(t.Context(), "(a+)+ @user5\n") + require.Len(t, rules, 1) + + maliciousInput := strings.Repeat("a", 30) + "X" + start := time.Now() + _, err := rules[0].Rule.MatchString(maliciousInput) + elapsed := time.Since(start) + + require.Error(t, err, "expected MatchTimeout error on pathological input") + assert.Less(t, elapsed, time.Second, "match timeout did not bound regex evaluation; took %s", elapsed) +} + func testGetApprovers(t *testing.T) { pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 5}) // Official reviews are already deduplicated. Allow unofficial reviews diff --git a/services/issue/pull.go b/services/issue/pull.go index 055c1023b4b..260ac5ceaec 100644 --- a/services/issue/pull.go +++ b/services/issue/pull.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "slices" + "time" issues_model "gitea.dev/models/issues" org_model "gitea.dev/models/organization" @@ -26,6 +27,10 @@ type ReviewRequestNotifier struct { var codeOwnerFiles = []string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"} +// codeOwnerMatchBudget caps the total wall-clock time spent evaluating all +// CODEOWNERS rules against all changed files for a single PR. +const codeOwnerMatchBudget = 2 * time.Second + func IsCodeOwnerFile(f string) bool { return slices.Contains(codeOwnerFiles, f) } @@ -93,8 +98,17 @@ func PullRequestCodeOwnersReview(ctx context.Context, pr *issues_model.PullReque uniqUsers := make(map[int64]*user_model.User) uniqTeams := make(map[string]*org_model.Team) + // Bound the total time spent matching rules×files. The per-rule MatchTimeout + // only caps a single match; without an aggregate budget a crafted CODEOWNERS + // plus a PR touching many files could still exhaust CPU inside this loop. + matchDeadline := time.Now().Add(codeOwnerMatchBudget) +ruleLoop: for _, rule := range rules { for _, f := range changedFiles { + if time.Now().After(matchDeadline) { + log.Warn("CODEOWNERS matching for PR %s#%d exceeded its time budget; some rules were not evaluated", pr.BaseRepo.FullName(), pr.ID) + break ruleLoop + } shouldMatch := !rule.Negative matched, _ := rule.Rule.MatchString(f) // err only happens when timeouts, any error can be considered as not matched if matched == shouldMatch { From 1c289df6eb13a5acbb7345c0e61fbf7b8e749ba3 Mon Sep 17 00:00:00 2001 From: bircni Date: Sun, 7 Jun 2026 18:45:20 +0200 Subject: [PATCH 25/40] enhance: Adjust Workflow Graph styling (#37497) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix workflow dependency graph overflow by making the graph container scrollable (no more clipped DAGs; addresses #37493). - Improve Actions job list readability by keeping durations fixed-width/right-aligned so long times don’t squeeze job names. - Make workflow graph layout more intuitive by vertically centering shorter columns to reduce misleading “looks like it depends on” alignments (addresses #37395). ### Screenshot Fixes #37493 Fixes #37395 --------- Co-authored-by: silverwind Co-authored-by: Claude (Opus 4.7) Co-authored-by: wxiaoguang --- routers/web/devtest/mock_actions.go | 71 +- templates/devtest/repo-action-view.tmpl | 10 +- web_src/js/components/ActionRunJobView.vue | 36 +- web_src/js/components/ActionRunView.ts | 6 - web_src/js/components/RepoActionView.vue | 48 +- .../js/components/WorkflowGraph.utils.test.ts | 197 ++++ web_src/js/components/WorkflowGraph.utils.ts | 559 +++++++++++ web_src/js/components/WorkflowGraph.vue | 916 ++++++++---------- 8 files changed, 1246 insertions(+), 597 deletions(-) create mode 100644 web_src/js/components/WorkflowGraph.utils.test.ts create mode 100644 web_src/js/components/WorkflowGraph.utils.ts diff --git a/routers/web/devtest/mock_actions.go b/routers/web/devtest/mock_actions.go index 69062ff6e78..61f20a3ef48 100644 --- a/routers/web/devtest/mock_actions.go +++ b/routers/web/devtest/mock_actions.go @@ -214,6 +214,75 @@ func MockActionsRunsJobs(ctx *context.Context) { return fmt.Sprintf("%s/jobs/%d", resp.State.Run.Link, jobID) } + // Keep devtest mock runs minimal: use run 10 as a "complex graph" repro. + // This combines long durations, parallel roots, and a multi-dependency downstream job + // to validate the workflow graph rendering. + if runID == 10 { + resp.State.Run.WorkflowID = "workflow-devtest-complex" + resp.State.Run.Duration = "7h 12m 34s" + + type mj struct { + jobID string + name string + status actions_model.Status + duration string + needs []string + } + mockJobs := []mj{ + {jobID: "job-100", name: "job-100", status: actions_model.StatusSuccess, duration: "3s", needs: nil}, + {jobID: "job-101", name: "job-101", status: actions_model.StatusSuccess, duration: "3s", needs: []string{"job-100"}}, + {jobID: "job-102", name: "job-102", status: actions_model.StatusSuccess, duration: "4s", needs: []string{"job-100", "job-101"}}, + {jobID: "job-103", name: "job-103", status: actions_model.StatusSuccess, duration: "2s", needs: []string{"job-100"}}, + + {jobID: "prep-jdk", name: "prep-jdk", status: actions_model.StatusSuccess, duration: "3s", needs: nil}, + {jobID: "code-analysis", name: "code-analysis", status: actions_model.StatusSuccess, duration: "3s", needs: nil}, + + // Matrix expansion (the " (...)" suffix is the heuristic the frontend uses to group rows) + {jobID: "matrix-e2e-1-chromium", name: "matrix-e2e (1, chromium)", status: actions_model.StatusSuccess, duration: "2s", needs: []string{"prep-jdk"}}, + {jobID: "matrix-e2e-1-firefox", name: "matrix-e2e (1, firefox)", status: actions_model.StatusSuccess, duration: "2s", needs: []string{"prep-jdk"}}, + {jobID: "matrix-e2e-2-chromium", name: "matrix-e2e (2, chromium)", status: actions_model.StatusSuccess, duration: "2s", needs: []string{"prep-jdk"}}, + {jobID: "matrix-e2e-3-chromium", name: "matrix-e2e (3, chromium)", status: actions_model.StatusSuccess, duration: "4s", needs: []string{"prep-jdk"}}, + {jobID: "matrix-e2e-3-firefox", name: "matrix-e2e (3, firefox)", status: actions_model.StatusSuccess, duration: "2s", needs: []string{"prep-jdk"}}, + {jobID: "matrix-e2e-99-webkit", name: "matrix-e2e (99, webkit)", status: actions_model.StatusSuccess, duration: "2s", needs: []string{"prep-jdk"}}, + + {jobID: "unit-test", name: "unit-test", status: actions_model.StatusSuccess, duration: "3s", needs: []string{"prep-jdk"}}, + {jobID: "arch-test", name: "arch-test", status: actions_model.StatusSuccess, duration: "3s", needs: []string{"prep-jdk"}}, + {jobID: "integration-test", name: "integration-test", status: actions_model.StatusSuccess, duration: "4s", needs: []string{"prep-jdk"}}, + + {jobID: "build-image", name: "build-image", status: actions_model.StatusSuccess, duration: "3s", needs: []string{ + "unit-test", + "arch-test", + "integration-test", + "code-analysis", + "matrix-e2e-1-chromium", + "matrix-e2e-1-firefox", + "matrix-e2e-2-chromium", + "matrix-e2e-3-chromium", + "matrix-e2e-3-firefox", + "matrix-e2e-99-webkit", + }}, + } + + resp.State.Run.Jobs = nil + for i, j := range mockJobs { + id := runID*1000 + int64(i) + resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{ + ID: id, + Link: jobLink(id), + JobID: j.jobID, + Name: j.name, + Status: j.status.String(), + CanRerun: j.jobID == "job-100", + Duration: j.duration, + Needs: j.needs, + }) + } + + fillViewRunResponseCurrentJob(ctx, resp) + ctx.JSON(http.StatusOK, resp) + return + } + resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{ ID: runID * 10, Link: jobLink(runID * 10), @@ -240,7 +309,7 @@ func MockActionsRunsJobs(ctx *context.Context) { Name: "ULTRA LOOOOOOOOOOOONG job name 102 that exceeds the limit", Status: actions_model.StatusFailure.String(), CanRerun: false, - Duration: "3h", + Duration: "3h35m10s", Needs: []string{"job-100", "job-101"}, }) resp.State.Run.Jobs = append(resp.State.Run.Jobs, &actions.ViewJob{ diff --git a/templates/devtest/repo-action-view.tmpl b/templates/devtest/repo-action-view.tmpl index 4e06f72cdd9..12da63a7e9e 100644 --- a/templates/devtest/repo-action-view.tmpl +++ b/templates/devtest/repo-action-view.tmpl @@ -1,11 +1,11 @@ {{template "base/head" .}}
    {{template "repo/actions/view_component" (dict "JobID" (or .JobID 0) diff --git a/web_src/js/components/ActionRunJobView.vue b/web_src/js/components/ActionRunJobView.vue index 835518ecb8e..d150e5899b6 100644 --- a/web_src/js/components/ActionRunJobView.vue +++ b/web_src/js/components/ActionRunJobView.vue @@ -2,7 +2,6 @@ import {computed, nextTick, onBeforeUnmount, onMounted, ref, toRefs, watch} from 'vue'; import {SvgIcon} from '../svg.ts'; import ActionStatusIcon from './ActionStatusIcon.vue'; -import WorkflowGraph from './WorkflowGraph.vue'; import {addDelegatedEventListener, createElementFromAttrs, toggleElem} from '../utils/dom.ts'; import {formatDatetime, formatDatetimeISO} from '../utils/time.ts'; import {POST} from '../modules/fetch.ts'; @@ -13,7 +12,6 @@ import {localUserSettings} from '../modules/user-settings.ts'; import type {ActionsArtifact, ActionsJob, ActionsRun, ActionsStatus} from '../modules/gitea-actions.ts'; import { type ActionRunViewStore, - collectCallerChildJobs, createLogLineMessage, type LogLine, type LogLineCommand, @@ -118,14 +116,11 @@ const currentJob = ref({ const stepsContainer = ref(null); const jobStepLogs = ref>([]); -// Reusable workflow caller view: when the selected job is a caller node, the right pane -// shows the children list rather than step logs (callers don't run on a runner). +// Reusable workflow caller view: the right pane shows just the header (name + uses path + +// status). Callers don't run on a runner, and the dependency graph for their children lives +// in the run summary's WorkflowGraph, not here — matching GitHub Actions. const selectedJob = computed(() => (run.value.jobs || []).find((it) => it.id === props.jobId)); const isCallerJob = computed(() => Boolean(selectedJob.value?.isReusableCaller)); -const callerChildJobs = computed(() => { - if (!isCallerJob.value) return []; - return collectCallerChildJobs(run.value.jobs || [], props.jobId); -}); watch(optionAlwaysAutoScroll, () => { saveLocaleStorageOptions(); @@ -477,20 +472,6 @@ async function hashChangeListener() {
    - -
    - -
    -
    @@ -578,8 +559,7 @@ async function hashChangeListener() { border-radius: 3px; } -.job-info-header:has(+ .job-step-container), -.job-info-header:has(+ .caller-children-container) { +.job-info-header:has(+ .job-step-container) { border-radius: var(--border-radius) var(--border-radius) 0 0; } @@ -613,14 +593,6 @@ async function hashChangeListener() { min-width: 0; } -.caller-children-container { - flex: 1; - display: flex; - flex-direction: column; - border-top: 1px solid var(--color-console-border); - color: var(--color-console-fg); -} - .job-step-container { max-height: 100%; border-radius: 0 0 var(--border-radius) var(--border-radius); diff --git a/web_src/js/components/ActionRunView.ts b/web_src/js/components/ActionRunView.ts index e9b929444fd..0d6ae9a61bd 100644 --- a/web_src/js/components/ActionRunView.ts +++ b/web_src/js/components/ActionRunView.ts @@ -104,12 +104,6 @@ export function buildJobsByParentJobID(jobs: ActionsJob[]): Map(() => { while (stack.length > 0) { const {job, depth} = stack.pop()!; const children = childrenByParent.get(job.id) || []; - const hasChildren = children.length > 0; - result.push({job, depth, hasChildren}); - if (hasChildren && isJobCollapsed(job.id)) continue; + result.push({job, depth}); + if (children.length > 0 && isJobCollapsed(job.id)) continue; for (let i = children.length - 1; i >= 0; i--) stack.push({job: children[i], depth: depth + 1}); } return result; @@ -216,24 +214,28 @@ async function deleteArtifact(name: string) { v-for="item in visibleJobListItems" :key="item.job.id" > - - - {{ item.job.name }} - - {{ item.job.duration }} - + + + + {{ item.job.name }} + + {{ item.job.duration }} +
    @@ -258,7 +260,7 @@ async function deleteArtifact(name: string) { - + {{ artifact.name }} {{ locale.artifactExpired }} @@ -406,23 +408,23 @@ async function deleteArtifact(name: string) { background-color: var(--color-active); } -.job-brief-toggle { +.caller-row-toggle { border: none; padding: 0; background: transparent; - cursor: pointer; color: inherit; - display: inline-flex; - align-items: center; - justify-content: center; + cursor: pointer; + text-align: inherit; +} + +.job-brief-toggle-icon { flex-shrink: 0; - /* the icon is always chevron-down; flip to chevron-up when expanded */ transition: transform 0.15s ease; - /* sit right after the job name; rerun/duration float to the right via auto-margin */ + /* sit between name and duration; duration uses order:2 with margin-left:auto to float right */ order: 1; } -.job-brief-toggle:not(.collapsed) { +.job-brief-toggle-icon:not(.collapsed) { transform: rotate(180deg); } diff --git a/web_src/js/components/WorkflowGraph.utils.test.ts b/web_src/js/components/WorkflowGraph.utils.test.ts new file mode 100644 index 00000000000..9d79766d83c --- /dev/null +++ b/web_src/js/components/WorkflowGraph.utils.test.ts @@ -0,0 +1,197 @@ +import {computeGraphHighlightState, computeJobLevels, createWorkflowGraphModel, matrixKeyFromJobName} from './WorkflowGraph.utils.ts'; +import type {ActionsJob} from '../modules/gitea-actions.ts'; + +const mockJobs: ActionsJob[] = [ + {id: 1, link: '', jobId: 'job-100', name: 'job-100', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '3s'}, + {id: 2, link: '', jobId: 'job-101', name: 'job-101', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '3s', needs: ['job-100']}, + {id: 3, link: '', jobId: 'job-102', name: 'job-102', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '4s', needs: ['job-101']}, + {id: 4, link: '', jobId: 'job-103', name: 'job-103', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '2s', needs: ['job-100']}, + {id: 5, link: '', jobId: 'prep-jdk', name: 'prep-jdk', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '3s'}, + {id: 6, link: '', jobId: 'code-analysis', name: 'code-analysis', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '3s'}, + {id: 7, link: '', jobId: 'matrix-e2e-1-chromium', name: 'matrix-e2e (1, chromium)', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '2s', needs: ['job-100', 'prep-jdk', 'code-analysis']}, + {id: 8, link: '', jobId: 'matrix-e2e-1-firefox', name: 'matrix-e2e (1, firefox)', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '2s', needs: ['job-100', 'prep-jdk', 'code-analysis']}, + {id: 9, link: '', jobId: 'matrix-e2e-2-chromium', name: 'matrix-e2e (2, chromium)', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '2s', needs: ['job-100', 'prep-jdk', 'code-analysis']}, + {id: 10, link: '', jobId: 'matrix-e2e-3-chromium', name: 'matrix-e2e (3, chromium)', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '4s', needs: ['job-100', 'prep-jdk', 'code-analysis']}, + {id: 11, link: '', jobId: 'matrix-e2e-3-firefox', name: 'matrix-e2e (3, firefox)', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '2s', needs: ['job-100', 'prep-jdk', 'code-analysis']}, + {id: 12, link: '', jobId: 'matrix-e2e-99-webkit', name: 'matrix-e2e (99, webkit)', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '2s', needs: ['job-100', 'prep-jdk', 'code-analysis']}, + {id: 13, link: '', jobId: 'unit-test', name: 'unit-test', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '3s', needs: ['prep-jdk', 'code-analysis']}, + {id: 14, link: '', jobId: 'arch-test', name: 'arch-test', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '3s', needs: ['prep-jdk', 'code-analysis']}, + {id: 15, link: '', jobId: 'integration-test', name: 'integration-test', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '4s', needs: ['prep-jdk', 'code-analysis']}, + {id: 16, link: '', jobId: 'build-image', name: 'build-image', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '3s', needs: [ + 'unit-test', + 'arch-test', + 'integration-test', + 'matrix-e2e-1-chromium', + 'matrix-e2e-1-firefox', + 'matrix-e2e-2-chromium', + 'matrix-e2e-3-chromium', + 'matrix-e2e-3-firefox', + 'matrix-e2e-99-webkit', + ]}, +]; + +const verifyDeployJobs: ActionsJob[] = [ + {id: 101, link: '', jobId: 'seed-dev', name: 'seed-dev', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '2s'}, + {id: 102, link: '', jobId: 'seed-qa', name: 'seed-qa', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '3s'}, + {id: 103, link: '', jobId: 'verify-dev', name: 'Verify Dev', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '3s', needs: ['seed-dev']}, + {id: 104, link: '', jobId: 'verify-qa', name: 'Verify QA', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '4s', needs: ['seed-qa']}, + {id: 105, link: '', jobId: 'deploy', name: 'Deploy', status: 'blocked', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '', needs: ['verify-dev', 'verify-qa']}, +]; + +// Multi-level pipeline with two matrices and a leaf with two parents. +const wfTest1Jobs: ActionsJob[] = [ + {id: 1, link: '', jobId: 'init', name: 'Initialize Pipeline', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '1s'}, + {id: 2, link: '', jobId: 'lint-frontend', name: 'Lint Frontend', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '3s', needs: ['init']}, + {id: 3, link: '', jobId: 'lint-backend', name: 'Lint Backend', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '3s', needs: ['init']}, + {id: 4, link: '', jobId: 'build-frontend', name: 'Build Frontend', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '4s', needs: ['lint-frontend']}, + {id: 5, link: '', jobId: 'build-backend', name: 'Build Backend', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '5s', needs: ['lint-backend']}, + {id: 6, link: '', jobId: 'tu-api-t', name: 'Unit Tests (api, true)', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '3s', needs: ['build-frontend', 'build-backend']}, + {id: 7, link: '', jobId: 'tu-api-f', name: 'Unit Tests (api, false)', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '3s', needs: ['build-frontend', 'build-backend']}, + {id: 8, link: '', jobId: 'tu-svc-t', name: 'Unit Tests (service, true)', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '3s', needs: ['build-frontend', 'build-backend']}, + {id: 9, link: '', jobId: 'test-integration', name: 'Integration Tests', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '6s', needs: ['build-backend']}, + {id: 10, link: '', jobId: 'te-c-d', name: 'E2E Tests (chrome, desktop)', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '4s', needs: ['build-frontend', 'tu-api-t', 'tu-api-f', 'tu-svc-t']}, + {id: 11, link: '', jobId: 'te-c-m', name: 'E2E Tests (chrome, mobile)', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '4s', needs: ['build-frontend', 'tu-api-t', 'tu-api-f', 'tu-svc-t']}, + {id: 12, link: '', jobId: 'te-f-d', name: 'E2E Tests (firefox, desktop)', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '4s', needs: ['build-frontend', 'tu-api-t', 'tu-api-f', 'tu-svc-t']}, + {id: 13, link: '', jobId: 'bundle-app', name: 'Bundle Application', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '3s', needs: ['tu-api-t', 'tu-api-f', 'tu-svc-t', 'test-integration', 'te-c-d', 'te-c-m', 'te-f-d']}, + {id: 14, link: '', jobId: 'deploy-dev', name: 'Deploy to Dev', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '3s', needs: ['bundle-app']}, + {id: 15, link: '', jobId: 'deploy-qa', name: 'Deploy to QA', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '3s', needs: ['bundle-app']}, + {id: 16, link: '', jobId: 'verify-dev', name: 'Verify Dev', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '2s', needs: ['deploy-dev']}, + {id: 17, link: '', jobId: 'verify-qa', name: 'Verify QA', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '2s', needs: ['deploy-qa']}, + {id: 18, link: '', jobId: 'deploy-prod', name: 'Deploy to Production', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '5s', needs: ['verify-dev', 'verify-qa']}, + {id: 19, link: '', jobId: 'post-deploy-checks', name: 'Post-Deploy Checks', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '2s', needs: ['deploy-prod']}, +]; + +test('matrix key heuristic strips trailing parameter list', () => { + expect(matrixKeyFromJobName('matrix-e2e (1, chromium)')).toBe('matrix-e2e'); + expect(matrixKeyFromJobName('plain-job')).toBeNull(); +}); + +test('computeJobLevels keeps stable topological levels', () => { + const levels = computeJobLevels(mockJobs); + expect(levels.get('job-100')).toBe(0); + expect(levels.get('job-101')).toBe(1); + expect(levels.get('job-102')).toBe(2); + expect(levels.get('build-image')).toBe(2); +}); + +test('graph model collapses matrix and groups jobs that share parents and children', () => { + const graph = createWorkflowGraphModel(mockJobs); + + expect(graph.nodes.find((n) => n.type === 'matrix')?.jobs).toHaveLength(6); + const groupJobIds = graph.nodes.filter((n) => n.type === 'group').map((g) => g.jobs.map((j) => j.jobId)); + expect(groupJobIds).toEqual(expect.arrayContaining([ + ['prep-jdk', 'code-analysis'], + ['unit-test', 'arch-test', 'integration-test'], + ])); +}); + +test('expanded matrix height includes summary and toggle rows', () => { + const collapsed = createWorkflowGraphModel(mockJobs); + const expanded = createWorkflowGraphModel(mockJobs, new Set(['matrix-e2e'])); + const collapsedMatrix = collapsed.nodes.find((n) => n.id === 'matrix:matrix-e2e'); + const expandedMatrix = expanded.nodes.find((n) => n.id === 'matrix:matrix-e2e'); + + expect(collapsedMatrix?.displayHeight).toBeLessThan(expandedMatrix?.displayHeight ?? 0); + // 6 jobs * 26 row height + 24 header + 6 pad * 2 = 192 + expect(expandedMatrix?.displayHeight).toBe(192); +}); + +test('every dependency is rendered as one routed edge', () => { + const graph = createWorkflowGraphModel(mockJobs); + const rootGroup = graph.nodes.find((n) => n.type === 'group' && n.jobs.some((j) => j.jobId === 'prep-jdk'))!; + const testGroup = graph.nodes.find((n) => n.type === 'group' && n.jobs.some((j) => j.jobId === 'unit-test'))!; + const expectedKeys = [ + `${rootGroup.id}->matrix:matrix-e2e`, + `${rootGroup.id}->${testGroup.id}`, + ]; + const keys = new Set(graph.routedEdges.map((e) => e.key)); + for (const k of expectedKeys) expect(keys.has(k)).toBe(true); +}); + +test('same-row edge collapses to a single horizontal line', () => { + const graph = createWorkflowGraphModel(verifyDeployJobs); + const verifyDevEdge = graph.routedEdges.find((e) => e.fromId === 'job:101' && e.toId === 'job:103'); + const verifyQaEdge = graph.routedEdges.find((e) => e.fromId === 'job:102' && e.toId === 'job:104'); + expect(verifyDevEdge?.path).toMatch(/^M [\d.]+ [\d.]+ H [\d.]+$/); + expect(verifyQaEdge?.path).toMatch(/^M [\d.]+ [\d.]+ H [\d.]+$/); +}); + +test('different-row edge uses cubic bezier curve', () => { + const graph = createWorkflowGraphModel(verifyDeployJobs); + const deployLowerEdge = graph.routedEdges.find((e) => e.fromId === 'job:104' && e.toId === 'job:105'); + expect(deployLowerEdge?.path).toContain(' C '); +}); + +test('multi-level pipeline with two matrices and a converging leaf renders without errors', () => { + const graph = createWorkflowGraphModel(wfTest1Jobs); + const matrices = graph.nodes.filter((n) => n.type === 'matrix'); + expect(matrices.map((n) => n.matrixKey).sort()).toEqual(['E2E Tests', 'Unit Tests']); + + const deployProd = graph.nodes.find((n) => n.id === 'job:18'); + const verifyDev = graph.nodes.find((n) => n.id === 'job:16'); + const verifyQa = graph.nodes.find((n) => n.id === 'job:17'); + expect(verifyDev?.level).toBe(verifyQa?.level); + expect(deployProd?.level).toBe((verifyDev?.level ?? 0) + 1); + + for (const node of graph.nodes) { + expect(Number.isFinite(node.x)).toBe(true); + expect(Number.isFinite(node.y)).toBe(true); + expect(node.x).toBeGreaterThanOrEqual(0); + expect(node.y).toBeGreaterThanOrEqual(0); + } + for (const edge of graph.routedEdges) { + expect(edge.path).not.toMatch(/NaN|undefined|Infinity/); + } +}); + +test('reusable callers with identical dependency signature are kept as separate nodes', () => { + const jobs: ActionsJob[] = [ + {id: 1, link: '', jobId: 'prepare', name: 'prepare', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '30s'}, + {id: 2, link: '', jobId: 'local_caller', name: 'local caller', status: 'running', canRerun: false, isReusableCaller: true, parentJobID: 0, duration: '5m', needs: ['prepare'], callUses: './.gitea/workflows/lib.yml'}, + {id: 3, link: '', jobId: 'cross_caller', name: 'cross-repo caller', status: 'waiting', canRerun: false, isReusableCaller: true, parentJobID: 0, duration: '0s', needs: ['prepare'], callUses: 'user2/lib/.gitea/workflows/ext.yml@main'}, + {id: 4, link: '', jobId: 'final', name: 'final', status: 'blocked', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '0s', needs: ['local_caller', 'cross_caller']}, + ]; + const graph = createWorkflowGraphModel(jobs); + expect(graph.nodes.find((n) => n.type === 'group')).toBeUndefined(); + expect(graph.nodes.find((n) => n.id === 'job:2')?.name).toBe('local caller'); + expect(graph.nodes.find((n) => n.id === 'job:3')?.name).toBe('cross-repo caller'); +}); + +test('reusable caller with matrix-pattern name does not get absorbed into a sibling matrix node', () => { + const jobs: ActionsJob[] = [ + {id: 1, link: '', jobId: 'deploy_dev', name: 'deploy (dev)', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '1s'}, + {id: 2, link: '', jobId: 'deploy_qa', name: 'deploy (qa)', status: 'success', canRerun: false, isReusableCaller: false, parentJobID: 0, duration: '1s'}, + {id: 3, link: '', jobId: 'deploy_staging', name: 'deploy (staging)', status: 'running', canRerun: false, isReusableCaller: true, parentJobID: 0, duration: '2s', callUses: './.gitea/workflows/deploy.yml'}, + ]; + const graph = createWorkflowGraphModel(jobs); + expect(graph.nodes.find((n) => n.id === 'job:3')?.name).toBe('deploy (staging)'); + const matrixNode = graph.nodes.find((n) => n.type === 'matrix'); + expect(matrixNode?.jobs.map((j) => j.id).sort()).toEqual([1, 2]); +}); + +test('directed highlight state covers ancestors and descendants of the hovered node', () => { + const graph = createWorkflowGraphModel(mockJobs); + const rootGroup = graph.nodes.find((n) => n.type === 'group' && n.jobs.some((j) => j.jobId === 'prep-jdk'))!; + + const highlight = computeGraphHighlightState(rootGroup.id, graph.adjacency); + expect(highlight.nodeIds.has('matrix:matrix-e2e')).toBe(true); + expect(highlight.nodeIds.has('job:16')).toBe(true); + expect(highlight.edgeKeys.has(`${rootGroup.id}->matrix:matrix-e2e`)).toBe(true); +}); + +test('directed highlight state for converging graph excludes sibling branch when hovering parent', () => { + const graph = createWorkflowGraphModel(verifyDeployJobs); + + const parentHighlight = computeGraphHighlightState('job:103', graph.adjacency); + expect(parentHighlight.nodeIds.has('job:101')).toBe(true); + expect(parentHighlight.nodeIds.has('job:105')).toBe(true); + expect(parentHighlight.nodeIds.has('job:104')).toBe(false); + expect(parentHighlight.edgeKeys.has('job:103->job:105')).toBe(true); + expect(parentHighlight.edgeKeys.has('job:104->job:105')).toBe(false); + + const sinkHighlight = computeGraphHighlightState('job:105', graph.adjacency); + expect(sinkHighlight.nodeIds.has('job:103')).toBe(true); + expect(sinkHighlight.nodeIds.has('job:104')).toBe(true); + expect(sinkHighlight.edgeKeys.has('job:103->job:105')).toBe(true); + expect(sinkHighlight.edgeKeys.has('job:104->job:105')).toBe(true); +}); diff --git a/web_src/js/components/WorkflowGraph.utils.ts b/web_src/js/components/WorkflowGraph.utils.ts new file mode 100644 index 00000000000..71ef7e6fc9e --- /dev/null +++ b/web_src/js/components/WorkflowGraph.utils.ts @@ -0,0 +1,559 @@ +import type {ActionsJob, ActionsStatus} from '../modules/gitea-actions.ts'; + +export type GraphNodeType = 'job' | 'matrix' | 'group'; + +export type GraphNode = { + id: string; + type: GraphNodeType; + name: string; + status: ActionsStatus; + duration: string; + x: number; + y: number; + level: number; + displayHeight: number; + jobs: ActionsJob[]; + matrixKey?: string; +}; + +export type Edge = { + fromId: string; + toId: string; + key: string; +}; + +export type RoutedEdge = Edge & { + path: string; + fromNode: GraphNode; + toNode: GraphNode; +}; + +export type SharedSegment = { + key: string; + edgeKeys: string[]; + path: string; +}; + +export type GraphHighlightState = { + nodeIds: Set; + edgeKeys: Set; +}; + +export type WorkflowGraphLayoutOptions = { + margin: number; + nodeWidth: number; + nodeHeight: number; + columnGap: number; + laneGap: number; + groupRowHeight: number; + groupPadY: number; + matrixCollapsedHeight: number; + matrixHeaderHeight: number; + matrixRowHeight: number; + matrixPadY: number; +}; + +export type WorkflowGraphModel = { + nodes: GraphNode[]; + edges: Edge[]; + routedEdges: RoutedEdge[]; + sharedSegments: SharedSegment[]; + adjacency: NodeAdjacency; +}; + +export type NodeAdjacency = { + incomingByNodeId: Map; + outgoingByNodeId: Map; +}; + +const defaultLayoutOptions: WorkflowGraphLayoutOptions = { + margin: 24, + nodeWidth: 220, + nodeHeight: 40, + columnGap: 96, + laneGap: 32, + groupRowHeight: 28, + groupPadY: 8, + matrixCollapsedHeight: 78, + matrixHeaderHeight: 24, + matrixRowHeight: 26, + matrixPadY: 6, +}; + +function canonicalKey(ids: Iterable): string { + return Array.from(ids).sort().join(''); +} + +function graphIdForJob(job: ActionsJob): string { + return `job:${job.id}`; +} + +export function matrixKeyFromJobName(name: string): string | null { + const idx = name.indexOf(' ('); + if (idx === -1) return null; + return name.slice(0, idx).trim() || null; +} + +export function boxBottom(node: GraphNode): number { + return node.y + node.displayHeight; +} + +export function boxCenterY(node: GraphNode): number { + return node.y + node.displayHeight / 2; +} + +function matrixPanelHeight(rowCount: number, expanded: boolean, options: WorkflowGraphLayoutOptions): number { + if (rowCount <= 0) return options.nodeHeight; + if (!expanded) return options.matrixCollapsedHeight; + return options.matrixHeaderHeight + rowCount * options.matrixRowHeight + options.matrixPadY * 2; +} + +function groupPanelHeight(rowCount: number, options: WorkflowGraphLayoutOptions): number { + return rowCount * options.groupRowHeight + options.groupPadY * 2; +} + +function compareStatusWorstFirst(a: ActionsStatus, b: ActionsStatus): number { + const rank = (s: ActionsStatus) => { + if (s === 'failure') return 0; + if (s === 'cancelled') return 1; + if (s === 'running') return 2; + if (s === 'waiting') return 3; + if (s === 'blocked') return 4; + if (s === 'success') return 5; + if (s === 'skipped') return 6; + return 7; + }; + return rank(a) - rank(b); +} + +function aggregateStatus(children: ActionsJob[]): ActionsStatus { + return children.map((c) => c.status).slice().sort(compareStatusWorstFirst)[0] ?? 'unknown'; +} + +function buildDirectNeedsMap(jobs: ActionsJob[]): Map { + const directNeedsByJobId = new Map(); + const dependentsByJobId = new Map>(); + + for (const job of jobs) { + const needs = job.needs || []; + directNeedsByJobId.set(job.jobId, needs); + for (const need of needs) { + if (!dependentsByJobId.has(need)) dependentsByJobId.set(need, new Set()); + dependentsByJobId.get(need)!.add(job.jobId); + } + } + + const reachabilityCache = new Map(); + function canReach(fromJobId: string, toJobId: string): boolean { + const cacheKey = `${fromJobId}->${toJobId}`; + if (reachabilityCache.has(cacheKey)) return reachabilityCache.get(cacheKey)!; + const visited = new Set(); + const stack = Array.from(dependentsByJobId.get(fromJobId) || []); + while (stack.length > 0) { + const current = stack.pop()!; + if (current === toJobId) { + reachabilityCache.set(cacheKey, true); + return true; + } + if (visited.has(current)) continue; + visited.add(current); + stack.push(...(dependentsByJobId.get(current) || [])); + } + reachabilityCache.set(cacheKey, false); + return false; + } + + const reducedNeedsByJobId = new Map(); + for (const [jobId, needs] of directNeedsByJobId) { + reducedNeedsByJobId.set(jobId, needs.filter((need) => { + return !needs.some((other) => other !== need && canReach(need, other)); + })); + } + return reducedNeedsByJobId; +} + +export function computeJobLevels(jobs: ActionsJob[]): Map { + const jobMap = new Map(); + for (const job of jobs) { + jobMap.set(job.name, job); + if (job.jobId) jobMap.set(job.jobId, job); + } + + const levels = new Map(); + const visited = new Set(); + const recursionStack = new Set(); + + function dfs(jobNameOrId: string): number { + if (recursionStack.has(jobNameOrId)) return 0; + if (visited.has(jobNameOrId)) return levels.get(jobNameOrId) ?? 0; + recursionStack.add(jobNameOrId); + visited.add(jobNameOrId); + + const job = jobMap.get(jobNameOrId); + if (!job) { + recursionStack.delete(jobNameOrId); + return 0; + } + if (!job.needs?.length) { + levels.set(job.jobId, 0); + if (job.jobId !== job.name) levels.set(job.name, 0); + recursionStack.delete(jobNameOrId); + return 0; + } + + let maxLevel = -1; + for (const need of job.needs) { + if (!jobMap.has(need)) continue; + maxLevel = Math.max(maxLevel, dfs(need)); + } + const level = maxLevel + 1; + levels.set(job.name, level); + levels.set(job.jobId, level); + recursionStack.delete(jobNameOrId); + return level; + } + + for (const job of jobs) { + if (!visited.has(job.jobId)) dfs(job.jobId); + } + return levels; +} + +export function computeGraphHighlightState(hoveredId: string | null, adjacency: NodeAdjacency): GraphHighlightState { + if (!hoveredId) return {nodeIds: new Set(), edgeKeys: new Set()}; + const {incomingByNodeId, outgoingByNodeId} = adjacency; + + const edgeKeys = new Set(); + const collect = (startId: string, adj: Map, edgeKeyForward: boolean): Set => { + const seen = new Set(); + const queue = [startId]; + while (queue.length > 0) { + const current = queue.shift()!; + if (seen.has(current)) continue; + seen.add(current); + for (const next of adj.get(current) || []) { + edgeKeys.add(edgeKeyForward ? `${current}->${next}` : `${next}->${current}`); + if (!seen.has(next)) queue.push(next); + } + } + return seen; + }; + + const ancestors = collect(hoveredId, incomingByNodeId, false); + const descendants = collect(hoveredId, outgoingByNodeId, true); + return {nodeIds: new Set([...ancestors, ...descendants]), edgeKeys}; +} + +type VisualGraphBuild = { + nodes: GraphNode[]; + edges: Edge[]; +}; + +function buildVisualGraph( + jobs: ActionsJob[], + expandedMatrixKeys: ReadonlySet, + options: WorkflowGraphLayoutOptions, +): VisualGraphBuild { + const jobsByJobId = new Map(); + const jobIndexById = new Map(); + for (const [index, job] of jobs.entries()) { + jobIndexById.set(job.id, index); + if (!jobsByJobId.has(job.jobId)) jobsByJobId.set(job.jobId, []); + jobsByJobId.get(job.jobId)!.push(job); + } + + const matrixJobsByKey = new Map(); + for (const job of jobs) { + // Reusable callers are distinct workflow files — never fold them into a matrix bucket + // even if their display name happens to look like "name (variant)". + if (job.isReusableCaller) continue; + const matrixKey = matrixKeyFromJobName(job.name); + if (!matrixKey) continue; + if (!matrixJobsByKey.has(matrixKey)) matrixJobsByKey.set(matrixKey, []); + matrixJobsByKey.get(matrixKey)!.push(job); + } + for (const list of matrixJobsByKey.values()) { + list.sort((a, b) => (jobIndexById.get(a.id) ?? 0) - (jobIndexById.get(b.id) ?? 0)); + } + + const directNeedsByJobId = buildDirectNeedsMap(jobs); + const rawLevels = computeJobLevels(jobs); + const dependentsByJobId = new Map(); + const rawEdges: Array<{from: ActionsJob; to: ActionsJob}> = []; + + for (const job of jobs) { + for (const need of directNeedsByJobId.get(job.jobId) || []) { + for (const upstream of jobsByJobId.get(need) || []) { + rawEdges.push({from: upstream, to: job}); + if (!dependentsByJobId.has(upstream.jobId)) dependentsByJobId.set(upstream.jobId, []); + dependentsByJobId.get(upstream.jobId)!.push(job.jobId); + } + } + } + for (const list of dependentsByJobId.values()) list.sort(); + + // Group sibling jobs that share an identical (parents, children) signature into a single + // collapsed "group" node. This is a visual aggregation only - the underlying jobs are + // preserved on the node so the panel can list them. + const groupedJobIds = new Map(); + const groupsById = new Map(); + const groupCandidateBuckets = new Map(); + for (const job of jobs) { + if (matrixKeyFromJobName(job.name)) continue; + // Reusable callers represent distinct workflow files — keep each as its own node so the + // graph mirrors GitHub Actions, where every caller shows up as its own box even when + // siblings share an identical (parents, children) dependency signature. + if (job.isReusableCaller) continue; + const needsKey = canonicalKey(directNeedsByJobId.get(job.jobId) || []); + const childrenKey = (dependentsByJobId.get(job.jobId) || []).join(''); + if (!needsKey && !childrenKey) continue; + const level = rawLevels.get(job.jobId) ?? 0; + const key = `group:${level}:${needsKey}:${childrenKey}`; + if (!groupCandidateBuckets.has(key)) groupCandidateBuckets.set(key, []); + groupCandidateBuckets.get(key)!.push(job); + } + for (const [groupId, groupJobs] of groupCandidateBuckets) { + if (groupJobs.length < 2) continue; + groupJobs.sort((a, b) => (jobIndexById.get(a.id) ?? 0) - (jobIndexById.get(b.id) ?? 0)); + groupsById.set(groupId, groupJobs); + for (const job of groupJobs) groupedJobIds.set(job.id, groupId); + } + + const visualIdByJobId = new Map(); + for (const job of jobs) { + const matrixKey = matrixKeyFromJobName(job.name); + // Symmetric with the matrix-bucket loop above: a reusable caller whose display name + // happens to look like "name (variant)" must never be folded into the matrix node, or it + // would silently vanish (its visualId would point at a matrix node it isn't part of). + if (matrixKey && !job.isReusableCaller && (matrixJobsByKey.get(matrixKey)?.length ?? 0) > 1) { + visualIdByJobId.set(job.id, `matrix:${matrixKey}`); + continue; + } + visualIdByJobId.set(job.id, groupedJobIds.get(job.id) || graphIdForJob(job)); + } + + const emittedNodeIds = new Set(); + const nodes: GraphNode[] = []; + for (const job of jobs) { + const visualId = visualIdByJobId.get(job.id); + if (!visualId || emittedNodeIds.has(visualId)) continue; + emittedNodeIds.add(visualId); + + const matrixKey = matrixKeyFromJobName(job.name); + if (matrixKey && visualId.startsWith('matrix:')) { + const matrixJobs = matrixJobsByKey.get(matrixKey) || []; + nodes.push({ + id: visualId, + type: 'matrix', + name: matrixKey, + status: aggregateStatus(matrixJobs), + duration: '', + x: 0, y: 0, level: 0, + displayHeight: matrixPanelHeight(matrixJobs.length, expandedMatrixKeys.has(matrixKey), options), + jobs: matrixJobs, + matrixKey, + }); + continue; + } + + const groupJobs = groupsById.get(visualId); + if (groupJobs) { + nodes.push({ + id: visualId, + type: 'group', + name: groupJobs.map((g) => g.name).join(', '), + status: aggregateStatus(groupJobs), + duration: '', + x: 0, y: 0, level: 0, + displayHeight: groupPanelHeight(groupJobs.length, options), + jobs: groupJobs, + }); + continue; + } + + nodes.push({ + id: visualId, + type: 'job', + name: job.name, + status: job.status, + duration: job.duration, + x: 0, y: 0, level: 0, + displayHeight: options.nodeHeight, + jobs: [job], + }); + } + + const seenEdges = new Set(); + const edges: Edge[] = []; + for (const {from, to} of rawEdges) { + const fromId = visualIdByJobId.get(from.id); + const toId = visualIdByJobId.get(to.id); + if (!fromId || !toId || fromId === toId) continue; + const key = `${fromId}->${toId}`; + if (seenEdges.has(key)) continue; + seenEdges.add(key); + edges.push({fromId, toId, key}); + } + + return {nodes, edges}; +} + +function buildNodeAdjacency(edges: Edge[]): NodeAdjacency { + const incomingByNodeId = new Map(); + const outgoingByNodeId = new Map(); + for (const edge of edges) { + if (!incomingByNodeId.has(edge.toId)) incomingByNodeId.set(edge.toId, []); + incomingByNodeId.get(edge.toId)!.push(edge.fromId); + if (!outgoingByNodeId.has(edge.fromId)) outgoingByNodeId.set(edge.fromId, []); + outgoingByNodeId.get(edge.fromId)!.push(edge.toId); + } + return {incomingByNodeId, outgoingByNodeId}; +} + +function assignNodeLevels(nodes: GraphNode[], {incomingByNodeId}: NodeAdjacency): void { + const cache = new Map(); + function levelFor(id: string, visiting = new Set()): number { + if (cache.has(id)) return cache.get(id)!; + if (visiting.has(id)) return 0; + visiting.add(id); + const incoming = incomingByNodeId.get(id) || []; + const level = incoming.length > 0 ? + Math.max(...incoming.map((fromId) => levelFor(fromId, visiting))) + 1 : + 0; + visiting.delete(id); + cache.set(id, level); + return level; + } + for (const node of nodes) node.level = levelFor(node.id); +} + +// Roots stay in input order; later levels are sorted by the mean parent Y so that simple +// chains stay on a straight horizontal line. +function assignNodeCoordinates(nodesById: Map, nodes: GraphNode[], adjacency: NodeAdjacency, options: WorkflowGraphLayoutOptions): void { + const {incomingByNodeId} = adjacency; + const inputRank = (node: GraphNode): number => Math.min(...node.jobs.map((j) => j.id)); + + const nodesByLevel = new Map(); + for (const node of nodes) { + if (!nodesByLevel.has(node.level)) nodesByLevel.set(node.level, []); + nodesByLevel.get(node.level)!.push(node); + } + const orderedLevels = Array.from(nodesByLevel.keys()).sort((a, b) => a - b); + + // Initial X assignment and a default Y so barycenters can use a finite value. + for (const level of orderedLevels) { + const list = nodesByLevel.get(level)!; + list.sort((a, b) => inputRank(a) - inputRank(b)); + let yCursor = options.margin; + for (const node of list) { + node.x = options.margin + level * (options.nodeWidth + options.columnGap); + node.y = yCursor; + yCursor += node.displayHeight + options.laneGap; + } + } + + function packLevel(level: number, anchorOf: (n: GraphNode) => number): void { + const list = nodesByLevel.get(level)!; + const sorted = Array.from(list).sort((a, b) => anchorOf(a) - anchorOf(b) || inputRank(a) - inputRank(b)); + // Pack tight to top after sorting. Using barycenter only for order (not Y) keeps terminal + // nodes like build-image close to the top of their column instead of being pulled down to + // the mean Y of their parents — matching GitHub Actions' compact layout. + let prevBottom = options.margin - options.laneGap; + for (const node of sorted) { + node.y = prevBottom + options.laneGap; + prevBottom = boxBottom(node); + } + nodesByLevel.set(level, sorted); + } + + function meanCenterOf(ids: string[]): number | null { + if (ids.length === 0) return null; + let sum = 0; + for (const id of ids) sum += boxCenterY(nodesById.get(id)!); + return sum / ids.length; + } + + // Down-only barycenter pass: each child is anchored to the mean Y of its parents. Roots + // keep their initial yaml-declaration order (via inputRank), matching how GitHub Actions + // arranges root jobs. This produces a "main chain on top" layout where job-100 → job-101 → + // job-102 stays on a straight horizontal line. + for (const level of orderedLevels) { + if (level === 0) continue; + packLevel(level, (node) => meanCenterOf(incomingByNodeId.get(node.id) || []) ?? boxCenterY(node)); + } +} + +// Per-edge connector: source stub → cubic-bezier corner down/up to column midpoint → +// vertical run → cubic-bezier corner back to horizontal → target stub. The corner radius is +// fixed (not clamped to the row delta) so any two edges sharing the same source produce the +// same source-side path and overlap into a single visual line until they diverge at the V. +const cornerRadius = 12; + +function connectorPath(sx: number, sy: number, ex: number, ey: number, options: WorkflowGraphLayoutOptions): string { + if (Math.abs(sy - ey) < 0.5) return `M ${sx} ${sy} H ${ex}`; + // Anchor the V segment in the column gap immediately before the target instead of the + // horizontal midpoint. The long H stays at the source's Y, matching GitHub Actions' style + // — a multi-column edge runs along the source row across intermediate columns, then turns + // up/down only when it reaches the target column. + const midX = Math.max(ex - options.columnGap / 2, (sx + ex) / 2); + const dy = ey > sy ? 1 : -1; + // Keep the same H prefix to `midX - cornerRadius` for every edge so that edges sharing a + // source overlap visually until they fork. When there isn't 2*cornerRadius of vertical + // room for the V segment, emit a single S-curve between (midX - r, sy) and (midX + r, ey) + // instead of a backward V kink. + if (Math.abs(ey - sy) < cornerRadius * 2) { + return [ + `M ${sx} ${sy}`, + `H ${midX - cornerRadius}`, + `C ${midX} ${sy} ${midX} ${ey} ${midX + cornerRadius} ${ey}`, + `H ${ex}`, + ].join(' '); + } + const half = cornerRadius / 2; + return [ + `M ${sx} ${sy}`, + `H ${midX - cornerRadius}`, + `C ${midX - half} ${sy} ${midX} ${sy + half * dy} ${midX} ${sy + cornerRadius * dy}`, + `V ${ey - cornerRadius * dy}`, + `C ${midX} ${ey - half * dy} ${midX + half} ${ey} ${midX + cornerRadius} ${ey}`, + `H ${ex}`, + ].join(' '); +} + +function buildRoutedEdges( + nodesById: Map, + edges: Edge[], + options: WorkflowGraphLayoutOptions, +): Pick { + const routedEdges: RoutedEdge[] = []; + for (const edge of edges) { + const fromNode = nodesById.get(edge.fromId); + const toNode = nodesById.get(edge.toId); + if (!fromNode || !toNode) continue; + const startX = fromNode.x + options.nodeWidth; + const endX = toNode.x; + const startY = boxCenterY(fromNode); + const endY = boxCenterY(toNode); + routedEdges.push({...edge, fromNode, toNode, path: connectorPath(startX, startY, endX, endY, options)}); + } + + return {routedEdges, sharedSegments: []}; +} + +export function createWorkflowGraphModel( + jobs: ActionsJob[], + expandedMatrixKeys: ReadonlySet = new Set(), + partialOptions: Partial = {}, +): WorkflowGraphModel { + const options = {...defaultLayoutOptions, ...partialOptions}; + const {nodes, edges} = buildVisualGraph(jobs, expandedMatrixKeys, options); + const nodesById = new Map(nodes.map((n) => [n.id, n])); + const adjacency = buildNodeAdjacency(edges); + assignNodeLevels(nodes, adjacency); + assignNodeCoordinates(nodesById, nodes, adjacency, options); + return {nodes, edges, ...buildRoutedEdges(nodesById, edges, options), adjacency}; +} + +export function getWorkflowGraphLayoutOptions(partialOptions: Partial = {}): WorkflowGraphLayoutOptions { + return {...defaultLayoutOptions, ...partialOptions}; +} diff --git a/web_src/js/components/WorkflowGraph.vue b/web_src/js/components/WorkflowGraph.vue index 27ba960feb6..4c8762dc392 100644 --- a/web_src/js/components/WorkflowGraph.vue +++ b/web_src/js/components/WorkflowGraph.vue @@ -6,31 +6,17 @@ import {localUserSettings} from '../modules/user-settings.ts'; import {isPlainClick} from '../utils/dom.ts'; import {trN} from '../modules/i18n.ts'; import {debounce} from 'throttle-debounce'; -import type {ActionsJob, ActionsStatus} from '../modules/gitea-actions.ts'; +import type {ActionsJob} from '../modules/gitea-actions.ts'; import type {ActionRunViewStore} from './ActionRunView.ts'; - -interface JobNode { - id: number; - name: string; - status: ActionsStatus; - duration: string; - - x: number; - y: number; - level: number; -} - -interface Edge { - fromId: number; - toId: number; - key: string; -} - -interface RoutedEdge extends Edge { - path: string; - fromNode: JobNode; - toNode: JobNode; -} +import { + boxBottom, + boxCenterY, + computeGraphHighlightState, + createWorkflowGraphModel, + getWorkflowGraphLayoutOptions, + type GraphNode, + type RoutedEdge, +} from './WorkflowGraph.utils.ts'; interface StoredState { scale: number; @@ -45,10 +31,11 @@ const props = defineProps<{ runLink: string; workflowId: string; locale: Record; -}>() +}>(); const settingKeyStates = 'actions-graph-states'; const maxStoredStates = 10; +const layout = getWorkflowGraphLayoutOptions(); const scale = ref(1); const translateX = ref(0); @@ -56,9 +43,21 @@ const translateY = ref(0); const isDragging = ref(false); const lastMousePos = ref({x: 0, y: 0}); const graphContainer = ref(null); -const hoveredJobId = ref(null); +const hoveredGraphId = ref(null); const stateKey = () => `${props.store.viewData.currentRun.repoId}-${props.workflowId}`; +const expandedMatrixKeys = ref>(new Set()); + +function isMatrixExpanded(key: string): boolean { + return expandedMatrixKeys.value.has(key); +} + +function toggleMatrixExpanded(key: string) { + const next = new Set(expandedMatrixKeys.value); + if (next.has(key)) next.delete(key); + else next.add(key); + expandedMatrixKeys.value = next; +} const loadSavedState = () => { const allStates = localUserSettings.getJsonObject>(settingKeyStates, {}); @@ -85,289 +84,35 @@ const saveState = () => { localUserSettings.setJsonObject(settingKeyStates, Object.fromEntries(sortedStates)); }; -const minNodeWidth = 168; -const maxNodeWidth = 232; -const nodeWidth = computed(() => { - const maxNameLength = Math.max(...props.jobs.map(j => j.name.length), 0); - return Math.min(Math.max(minNodeWidth, maxNameLength * 8), maxNodeWidth); -}); +const graphModel = computed(() => createWorkflowGraphModel(props.jobs, expandedMatrixKeys.value)); +const jobsWithLayout = computed(() => graphModel.value.nodes); +const edges = computed(() => graphModel.value.edges); +const routedEdges = computed(() => graphModel.value.routedEdges); -const horizontalSpacing = computed(() => nodeWidth.value + 84); +const nodeWidth = layout.nodeWidth; const graphWidth = computed(() => { if (jobsWithLayout.value.length === 0) return 800; - const maxX = Math.max(...jobsWithLayout.value.map(j => j.x + nodeWidth.value)); - return maxX + margin * 2; + const maxX = Math.max(...jobsWithLayout.value.map((job) => job.x + nodeWidth)); + return maxX + layout.margin * 2; }); const graphHeight = computed(() => { if (jobsWithLayout.value.length === 0) return 400; - const maxY = Math.max(...jobsWithLayout.value.map(j => j.y + nodeHeight)); - return maxY + margin * 2; + const maxY = Math.max(...jobsWithLayout.value.map((job) => boxBottom(job))); + return maxY + layout.margin * 2; }); - -const jobsWithLayout = computed(() => { - try { - const levels = computeJobLevels(props.jobs); - const currentHorizontalSpacing = horizontalSpacing.value; - - const jobsByLevel: ActionsJob[][] = []; - let maxJobsPerLevel = 0; - - props.jobs.forEach(job => { - // `?? 0`, not `|| 0`: a root job's level is 0, which `||` would wrongly discard. - const level = levels.get(scopedKey(job)) ?? 0; - - if (!jobsByLevel[level]) { - jobsByLevel[level] = []; - } - jobsByLevel[level].push(job); - - if (jobsByLevel[level].length > maxJobsPerLevel) { - maxJobsPerLevel = jobsByLevel[level].length; - } - }); - - const result: JobNode[] = []; - jobsByLevel.forEach((levelJobs, levelIndex) => { - if (!levelJobs || levelJobs.length === 0) { - return; - } - - const startY = margin; - - levelJobs.forEach((job, jobIndex) => { - result.push({ - id: job.id, - name: job.name, - status: job.status, - duration: job.duration, - - x: margin + levelIndex * currentHorizontalSpacing, - y: startY + jobIndex * verticalSpacing, - level: levelIndex, - }); - }); - }); - - return result; - } catch (error) { - return props.jobs.map((job, index) => ({ - id: job.id, - name: job.name, - status: job.status, - duration: job.duration, - - x: margin + index * horizontalSpacing.value, - y: margin, - level: 0, - })); - } +const successRateLabel = computed(() => { + if (props.jobs.length === 0) return '0%'; + const successCount = props.jobs.filter((job) => job.status === 'success').length; + return `${((successCount / props.jobs.length) * 100).toFixed(0)}%`; }); -// scopedKey identifies a job within its reusable-workflow call scope so that the same -// JobID in different reusable calls does not collide. -function scopedKey(job: {parentJobID: number; jobId: string}): string { - return `${job.parentJobID || 0}:${job.jobId}`; -} - -function buildDirectNeedsMap(jobs: ActionsJob[]): Map { - // The map keys/values are scoped keys, not bare jobIds, so we keep edge construction - // accurate when reusable workflows reuse common job names like "build" / "test". - const directNeedsByScopedKey = new Map(); - const dependentsByScopedKey = new Map>(); - - for (const job of jobs) { - const fromKey = scopedKey(job); - const needKeys = (job.needs || []).map((n) => `${job.parentJobID || 0}:${n}`); - directNeedsByScopedKey.set(fromKey, needKeys); - - for (const needKey of needKeys) { - if (!dependentsByScopedKey.has(needKey)) { - dependentsByScopedKey.set(needKey, new Set()); - } - dependentsByScopedKey.get(needKey)!.add(fromKey); - } - } - - const reachabilityCache = new Map(); - - function canReach(fromKey: string, toKey: string): boolean { - const cacheKey = `${fromKey}->${toKey}`; - if (reachabilityCache.has(cacheKey)) { - return reachabilityCache.get(cacheKey)!; - } - - const visited = new Set(); - const stack = [...(dependentsByScopedKey.get(fromKey) || [])]; - - while (stack.length > 0) { - const current = stack.pop()!; - if (current === toKey) { - reachabilityCache.set(cacheKey, true); - return true; - } - if (visited.has(current)) continue; - visited.add(current); - stack.push(...(dependentsByScopedKey.get(current) || [])); - } - - reachabilityCache.set(cacheKey, false); - return false; - } - - const reducedNeedsByScopedKey = new Map(); - for (const [fromKey, needs] of directNeedsByScopedKey.entries()) { - reducedNeedsByScopedKey.set(fromKey, needs.filter((need) => { - return !needs.some((otherNeed) => otherNeed !== need && canReach(need, otherNeed)); - })); - } - - return reducedNeedsByScopedKey; -} - -const directNeedsByScopedKey = computed(() => buildDirectNeedsMap(props.jobs)); - -const edges = computed(() => { - const edgesList: Edge[] = []; - // Store every job per scoped key, not just one: matrix-expanded jobs share same jobId - const jobsByScopedKey = new Map(); - - for (const job of props.jobs) { - const key = scopedKey(job); - const existing = jobsByScopedKey.get(key); - if (existing) { - existing.push(job); - } else { - jobsByScopedKey.set(key, [job]); - } - } - - for (const job of props.jobs) { - for (const needKey of directNeedsByScopedKey.value.get(scopedKey(job)) || []) { - for (const upstreamJob of jobsByScopedKey.get(needKey) || []) { - edgesList.push({ - fromId: upstreamJob.id, - toId: job.id, - key: `${upstreamJob.id}-${job.id}`, - }); - } - } - } - - return edgesList; -}); - -function buildRoundedConnectorPath(startX: number, startY: number, endX: number, endY: number, turnX: number): string { - const deltaY = endY - startY; - if (Math.abs(deltaY) < 1) { - return `M ${startX} ${startY} H ${endX}`; - } - - const direction = deltaY > 0 ? 1 : -1; - const elbowSize = Math.max(8, Math.min(24, Math.abs(deltaY) / 2, Math.abs(endX - startX) / 2)); - const controlOffset = elbowSize / 2; - const clampedTurnX = Math.min(Math.max(turnX, startX + elbowSize), endX - elbowSize); - - return [ - `M ${startX} ${startY}`, - `H ${clampedTurnX - elbowSize}`, - `C ${clampedTurnX - controlOffset} ${startY} ${clampedTurnX} ${startY + direction * controlOffset} ${clampedTurnX} ${startY + direction * elbowSize}`, - `V ${endY - direction * elbowSize}`, - `C ${clampedTurnX} ${endY - direction * controlOffset} ${clampedTurnX + controlOffset} ${endY} ${clampedTurnX + elbowSize} ${endY}`, - `H ${endX}`, - ].join(' '); -} - -const routedEdges = computed(() => { - const nodesById = new Map(jobsWithLayout.value.map((job) => [job.id, job])); - const outgoingEdges = new Map(); - const incomingEdges = new Map(); - - for (const edge of edges.value) { - if (!outgoingEdges.has(edge.fromId)) { - outgoingEdges.set(edge.fromId, []); - } - outgoingEdges.get(edge.fromId)!.push(edge); - - if (!incomingEdges.has(edge.toId)) { - incomingEdges.set(edge.toId, []); - } - incomingEdges.get(edge.toId)!.push(edge); - } - - for (const sourceEdges of outgoingEdges.values()) { - sourceEdges.sort((a, b) => { - const targetA = nodesById.get(a.toId); - const targetB = nodesById.get(b.toId); - if (!targetA || !targetB) return 0; - return targetA.y - targetB.y || a.toId - b.toId; - }); - } - - const edgePaths: RoutedEdge[] = []; - - for (const edge of edges.value) { - const fromNode = nodesById.get(edge.fromId); - const toNode = nodesById.get(edge.toId); - if (!fromNode || !toNode) continue; - - const startX = fromNode.x + nodeWidth.value; - const startY = fromNode.y + nodeHeight / 2; - const endX = toNode.x; - const endY = toNode.y + nodeHeight / 2; - const sourceEdges = outgoingEdges.get(edge.fromId) || []; - const targetEdges = incomingEdges.get(edge.toId) || []; - const horizontalGap = endX - startX; - const turnOffset = Math.min(28, Math.max(16, horizontalGap * 0.14)); - const sourceTurnX = startX + turnOffset; - const targetTurnX = endX - turnOffset; - - let turnX = startX + horizontalGap / 2; - if (sourceEdges.length > 1) { - turnX = sourceTurnX; - } else if (targetEdges.length > 1) { - turnX = targetTurnX; - } - - const path = buildRoundedConnectorPath(startX, startY, endX, endY, turnX); - - edgePaths.push({ - ...edge, - path, - fromNode, - toNode, - }); - } - - return edgePaths; -}); - -const graphMetrics = computed(() => { - const successCount = jobsWithLayout.value.filter(job => job.status === 'success').length; - - const levels = new Map(); - jobsWithLayout.value.forEach(job => { - const count = levels.get(job.level) || 0; - levels.set(job.level, count + 1); - }) - const parallelism = Math.max(...Array.from(levels.values()), 0); - - return { - successRate: `${((successCount / jobsWithLayout.value.length) * 100).toFixed(0)}%`, - parallelism, - }; -}) - const graphStats = computed(() => [ trN(props.jobs.length, props.locale.graphJobsCount1, props.locale.graphJobsCountN), trN(edges.value.length, props.locale.graphDependenciesCount1, props.locale.graphDependenciesCountN), - props.locale.graphSuccessRate.replace('%s', graphMetrics.value.successRate), -].join(' • ')) - -const nodeHeight = 52; -const verticalSpacing = 90; -const margin = 40; + props.locale.graphSuccessRate.replace('%s', successRateLabel.value), +].join(' • ')); const minScale = 0.3; const maxScale = 1; @@ -398,42 +143,32 @@ function resetView() { function handleMouseDown(e: MouseEvent) { if (!isPlainClick(e)) return; - - // don't start drag on interactive/text elements inside the SVG const target = e.target as Element; const interactive = target.closest('div, p, a, span, button, input, text, .job-node-group'); if (interactive?.closest('svg')) return; e.preventDefault(); - isDragging.value = true; lastMousePos.value = {x: e.clientX, y: e.clientY}; - graphContainer.value!.style.cursor = 'grabbing'; + if (graphContainer.value) graphContainer.value.style.cursor = 'grabbing'; } function handleMouseMoveOnDocument(event: MouseEvent) { if (!isDragging.value) return; - const dx = event.clientX - lastMousePos.value.x; - const dy = event.clientY - lastMousePos.value.y; - - translateX.value += dx; - translateY.value += dy; - + translateX.value += event.clientX - lastMousePos.value.x; + translateY.value += event.clientY - lastMousePos.value.y; lastMousePos.value = {x: event.clientX, y: event.clientY}; } function handleMouseUpOnDocument() { if (!isDragging.value) return; isDragging.value = false; - graphContainer.value!.style.cursor = 'grab'; + if (graphContainer.value) graphContainer.value.style.cursor = 'grab'; } function handleWheel(event: WheelEvent) { - // Without a modifier, let the wheel scroll the page - if (!event.ctrlKey && !event.metaKey) { - return; - } + if (!event.ctrlKey && !event.metaKey) return; event.preventDefault(); const zoomFactor = Math.exp(-event.deltaY * 0.0015); zoomTo(scale.value * zoomFactor); @@ -442,8 +177,6 @@ function handleWheel(event: WheelEvent) { onMounted(() => { loadSavedState(); watch([translateX, translateY, scale], debounce(500, saveState)); - watch([scale], debounce(100, saveState)); - document.addEventListener('mousemove', handleMouseMoveOnDocument); document.addEventListener('mouseup', handleMouseUpOnDocument); }); @@ -453,106 +186,40 @@ onUnmounted(() => { document.removeEventListener('mouseup', handleMouseUpOnDocument); }); -function handleNodeMouseEnter(job: JobNode) { - hoveredJobId.value = job.id; +function handleNodeMouseEnter(id: string) { + hoveredGraphId.value = id; } function handleNodeMouseLeave() { - hoveredJobId.value = null; + hoveredGraphId.value = null; +} + +const highlightState = computed(() => computeGraphHighlightState(hoveredGraphId.value, graphModel.value.adjacency)); + +function isNodeHighlighted(nodeId: string): boolean { + return highlightState.value.nodeIds.has(nodeId); } function isEdgeHighlighted(edge: RoutedEdge): boolean { - if (!hoveredJobId.value) { - return false; - } - return edge.fromId === hoveredJobId.value || edge.toId === hoveredJobId.value; + return highlightState.value.edgeKeys.has(edge.key); } -const nodesWithIncomingEdge = computed(() => { - const set = new Set(); - for (const edge of routedEdges.value) set.add(edge.toId); - return set; +const splitRoutedEdges = computed(() => { + const highlighted: RoutedEdge[] = []; + const dimmed: RoutedEdge[] = []; + for (const edge of routedEdges.value) (isEdgeHighlighted(edge) ? highlighted : dimmed).push(edge); + return {highlighted, dimmed}; }); -const nodesWithOutgoingEdge = computed(() => { - const set = new Set(); - for (const edge of routedEdges.value) set.add(edge.fromId); - return set; -}); +const nodesWithIncomingEdge = computed(() => new Set(graphModel.value.adjacency.incomingByNodeId.keys())); +const nodesWithOutgoingEdge = computed(() => new Set(graphModel.value.adjacency.outgoingByNodeId.keys())); - -function computeJobLevels(jobs: ActionsJob[]): Map { - // Scope-aware: each job is keyed by `${parentJobID}:${jobId}` so the same JobID - // in different reusable workflow calls does not cross-link in the level graph. - const jobMap = new Map(); - jobs.forEach(job => { - jobMap.set(scopedKey(job), job); - }); - - const levels = new Map(); - const visited = new Set(); - const recursionStack = new Set(); - const MAX_DEPTH = 100; - - function dfs(scoped: string, depth: number = 0): number { - if (depth > MAX_DEPTH) { - console.error(`Max recursion depth (${MAX_DEPTH}) reached for: ${scoped}`); - return 0; - } - - if (recursionStack.has(scoped)) { - console.error(`Cycle detected involving: ${scoped}`); - return 0; - } - - if (visited.has(scoped)) { - return levels.get(scoped) || 0; - } - - recursionStack.add(scoped); - visited.add(scoped); - - const job = jobMap.get(scoped); - if (!job) { - recursionStack.delete(scoped); - return 0; - } - - if (!job.needs?.length) { - levels.set(scoped, 0); - recursionStack.delete(scoped); - return 0; - } - - let maxLevel = -1; - for (const need of job.needs) { - const needScoped = `${job.parentJobID || 0}:${need}`; - const needJob = jobMap.get(needScoped); - if (!needJob) continue; - - const needLevel = dfs(needScoped, depth + 1); - maxLevel = Math.max(maxLevel, needLevel); - } - - const level = maxLevel + 1; - levels.set(scoped, level); - - recursionStack.delete(scoped); - return level; - } - - jobs.forEach(job => { - const sk = scopedKey(job); - if (!visited.has(sk)) { - dfs(sk); - } - }); - - return levels; -} - -function onNodeClick(job: JobNode, event: MouseEvent) { - const link = `${props.runLink}/jobs/${job.id}`; +function onNodeClick(job: GraphNode | ActionsJob, event: MouseEvent) { + const target = 'jobs' in job ? job.jobs[0]! : job; + // Reusable callers have no per-job detail page; clicking them is a no-op so the graph + // doesn't lead users to a dead destination. + if (target.isReusableCaller) return; + const link = `${props.runLink}/jobs/${target.id}`; if (event.ctrlKey || event.metaKey) { window.open(link, '_blank'); return; @@ -562,7 +229,7 @@ function onNodeClick(job: JobNode, event: MouseEvent) { + + + + +
    @@ -688,6 +419,7 @@ function onNodeClick(job: JobNode, event: MouseEvent) { display: flex; flex-direction: column; } + .graph-header { display: flex; justify-content: space-between; @@ -719,7 +451,7 @@ function onNodeClick(job: JobNode, event: MouseEvent) { .graph-container { flex: 1; - overflow: hidden; + overflow: auto; padding: 10px 14px 18px; border-radius: 0 0 var(--border-radius) var(--border-radius); cursor: grab; @@ -737,86 +469,210 @@ function onNodeClick(job: JobNode, event: MouseEvent) { } .graph-svg path { - transition: all 0.2s ease; + transition: stroke-width 0.2s ease, opacity 0.2s ease; stroke-linecap: round; stroke-linejoin: round; } +.node-edge { + stroke: var(--color-secondary-dark-2); + stroke-width: 1.5; + opacity: 0.9; +} + .highlighted-edge { - stroke-width: 2 !important; - stroke: var(--color-workflow-edge-hover) !important; + stroke: var(--color-primary); + stroke-width: 2; } .job-node-group { cursor: pointer; - transition: all 0.2s ease; + transition: opacity 0.15s ease; } -.job-node-group:hover .job-rect { - /* due to SVG rendering limitation, only one of fill and drop-shadow can work */ - fill: var(--color-hover); - /* filter: drop-shadow(0 1px 3px var(--color-shadow-opaque)); */ +.job-node-group.caller-node { + cursor: default; } -.job-text-wrap { +.job-node-group:hover .job-rect, +.job-node-group.related-node .job-rect { + stroke: var(--color-primary); + stroke-width: 1.5; + fill: var(--color-primary-alpha-10); +} + +.graph-svg.has-hover .job-node-group:not(.related-node) { + opacity: 0.2; +} + +.graph-svg.has-hover .node-edge:not(.highlighted-edge) { + opacity: 0.15; +} + +.highlighted-edge-layer { + pointer-events: none; +} + +.highlighted-port { + fill: var(--color-primary); + stroke: var(--color-primary); +} + +.job-rect { + fill: var(--color-box-body); + stroke: var(--color-secondary); + stroke-width: 1; +} + +.matrix-foreign-object { + pointer-events: auto; + overflow: visible; +} + +.matrix-panel, +.grouped-panel { + width: 100%; + height: 100%; + box-sizing: border-box; + border-radius: 6px; + background: transparent; + pointer-events: auto; + user-select: none; +} + +.matrix-panel { + display: flex; + flex-direction: column; + padding: 6px 10px 8px; +} + +.matrix-panel-label { + font-size: 10px; + font-weight: var(--font-weight-medium); + color: var(--color-text-light-2); + line-height: 1.3; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + cursor: pointer; +} + +.matrix-panel-collapsed { + display: flex; + flex-direction: column; + gap: 2px; + padding: 2px 0 0 2px; + cursor: pointer; +} + +.matrix-panel-summary-row { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; +} + +.matrix-panel-summary { + font-size: 12px; + font-weight: var(--font-weight-semibold); + line-height: 1.3; + color: var(--color-text); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.matrix-panel-toggle { + font-size: 11px; + color: var(--color-text-light-2); + padding-left: 24px; + cursor: pointer; +} + +.matrix-panel-toggle:hover { + color: var(--color-primary); + text-decoration: underline; +} + +.matrix-panel-jobs { + display: flex; + flex-direction: column; + gap: 2px; + padding: 4px 0 0 2px; + overflow-y: auto; +} + +.grouped-panel { + display: flex; + flex-direction: column; + justify-content: center; + padding: 6px; + gap: 2px; +} + +.graph-list-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + min-height: 24px; + padding: 1px 6px; + border-radius: 5px; +} + +.graph-list-row:hover { + background: var(--color-hover); +} + +.graph-list-row-main, +.job-row-main { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; +} + +.graph-list-row-name, +.job-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 11px; + font-weight: var(--font-weight-semibold); + color: var(--color-text); +} + +.graph-list-row-duration, +.job-duration { + flex: 0 0 auto; + font-size: 10px; + color: var(--color-text-light-2); + white-space: nowrap; +} + +.job-row { width: 100%; height: 100%; display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: center; - gap: 1px; - padding: 4px 8px 4px 0; - overflow: hidden; -} - -.job-name { - width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 12px; - font-weight: var(--font-weight-semibold); - color: var(--color-text); - user-select: none; - pointer-events: none; -} - -.job-duration { - font-size: 10px; - line-height: 1.2; - color: var(--color-text-light-2); - white-space: nowrap; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - user-select: none; - pointer-events: none; -} - -.job-status-fg-obj, -.job-status-icon-wrap { - pointer-events: none; -} - -.job-status-icon-wrap { - width: 20px; - height: 20px; - display: flex; align-items: center; - justify-content: center; + justify-content: space-between; + gap: 8px; +} + +.job-card { + border-radius: 6px; + padding: 0 2px; } .node-port { - fill: var(--color-box-body); - stroke: var(--color-light-border); + fill: var(--color-secondary-dark-2); + stroke: var(--color-box-body); stroke-width: 1.25; - opacity: 0.85; + opacity: 0.9; pointer-events: none; } -.node-edge { - transition: stroke-width 0.2s ease, opacity 0.2s ease; - opacity: 0.75; +.job-node-group.related-node .node-port { + fill: var(--color-primary); } From 6dcae57b54c4e8197185c4675a09cfc8acf58bac Mon Sep 17 00:00:00 2001 From: Giteabot Date: Sun, 7 Jun 2026 22:40:35 -0700 Subject: [PATCH 26/40] chore(deps): update action dependencies (#38027) --- .github/workflows/cache-seeder.yml | 4 ++-- .github/workflows/cron-licenses.yml | 2 +- .github/workflows/cron-renovate.yml | 2 +- .github/workflows/cron-translations.yml | 2 +- .github/workflows/files-changed.yml | 2 +- .github/workflows/pull-compliance.yml | 10 +++++----- .github/workflows/pull-db-tests.yml | 12 ++++++------ .github/workflows/pull-docker-dryrun.yml | 6 +++--- .github/workflows/pull-e2e-tests.yml | 2 +- .github/workflows/pull-labeler.yml | 2 +- .github/workflows/release-nightly-snapcraft.yml | 2 +- .github/workflows/release-nightly.yml | 6 +++--- .github/workflows/release-tag-rc.yml | 6 +++--- .github/workflows/release-tag-version.yml | 6 +++--- 14 files changed, 32 insertions(+), 32 deletions(-) diff --git a/.github/workflows/cache-seeder.yml b/.github/workflows/cache-seeder.yml index 8ec7adee07d..4e2988adb4e 100644 --- a/.github/workflows/cache-seeder.yml +++ b/.github/workflows/cache-seeder.yml @@ -29,7 +29,7 @@ jobs: gobuild: runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: ./.github/actions/go-setup - run: make deps-backend deps-tools - run: TAGS="bindata" make backend @@ -59,7 +59,7 @@ jobs: include: - { tags: "bindata", target: "lint-backend" } steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: ./.github/actions/go-setup with: lint-cache: "true" diff --git a/.github/workflows/cron-licenses.yml b/.github/workflows/cron-licenses.yml index edb6f2e1576..2d4e9262883 100644 --- a/.github/workflows/cron-licenses.yml +++ b/.github/workflows/cron-licenses.yml @@ -12,7 +12,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod diff --git a/.github/workflows/cron-renovate.yml b/.github/workflows/cron-renovate.yml index a50af530f20..4db83a336dd 100644 --- a/.github/workflows/cron-renovate.yml +++ b/.github/workflows/cron-renovate.yml @@ -20,7 +20,7 @@ jobs: if: github.repository == 'go-gitea/gitea' # prevent running on forks timeout-minutes: 30 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: renovatebot/github-action@693b9ef15eec82123529a37c782242f091365961 # v46.1.14 with: renovate-version: ${{ env.RENOVATE_VERSION }} diff --git a/.github/workflows/cron-translations.yml b/.github/workflows/cron-translations.yml index 17f29d4e0c5..7c215b2c176 100644 --- a/.github/workflows/cron-translations.yml +++ b/.github/workflows/cron-translations.yml @@ -12,7 +12,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2.16.2 with: upload_sources: true diff --git a/.github/workflows/files-changed.yml b/.github/workflows/files-changed.yml index c17afbca97f..3c0603974e4 100644 --- a/.github/workflows/files-changed.yml +++ b/.github/workflows/files-changed.yml @@ -49,7 +49,7 @@ jobs: e2e: ${{ steps.changes.outputs.e2e }} shell: ${{ steps.changes.outputs.shell }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 id: changes with: diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml index 801966e1444..6c41b6b4c11 100644 --- a/.github/workflows/pull-compliance.yml +++ b/.github/workflows/pull-compliance.yml @@ -19,7 +19,7 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: ./.github/actions/go-setup with: lint-cache: "true" @@ -31,7 +31,7 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: ./.github/actions/go-setup with: cache: "false" @@ -62,7 +62,7 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: ./.github/actions/go-setup - run: make deps-backend deps-tools - run: make --always-make checks-backend # ensure the "go-licenses" make target runs @@ -72,7 +72,7 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: ./.github/actions/node-setup - run: make deps-frontend - run: make lint-frontend @@ -85,7 +85,7 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: ./.github/actions/go-setup - run: make deps-backend generate-go # no frontend build here as backend should be able to build, even without any frontend files diff --git a/.github/workflows/pull-db-tests.yml b/.github/workflows/pull-db-tests.yml index cbf86247ce1..4cc8d25bbb9 100644 --- a/.github/workflows/pull-db-tests.yml +++ b/.github/workflows/pull-db-tests.yml @@ -42,7 +42,7 @@ jobs: ports: - "9000:9000" steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: ./.github/actions/go-setup - uses: ./.github/actions/pgsql-shard with: @@ -78,7 +78,7 @@ jobs: ports: - "9000:9000" steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: ./.github/actions/go-setup - uses: ./.github/actions/pgsql-shard with: @@ -90,7 +90,7 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: ./.github/actions/go-setup - run: make deps-backend - run: make backend @@ -151,7 +151,7 @@ jobs: ports: - 10000:10000 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: ./.github/actions/go-setup - name: Add hosts to /etc/hosts run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 minio devstoreaccount1.azurite.local mysql elasticsearch meilisearch smtpimap" | sudo tee -a /etc/hosts' @@ -208,7 +208,7 @@ jobs: - "587:587" - "993:993" steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: ./.github/actions/go-setup - name: Add hosts to /etc/hosts run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mysql elasticsearch smtpimap" | sudo tee -a /etc/hosts' @@ -241,7 +241,7 @@ jobs: ports: - 10000:10000 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: ./.github/actions/go-setup - name: Add hosts to /etc/hosts run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mssql devstoreaccount1.azurite.local" | sudo tee -a /etc/hosts' diff --git a/.github/workflows/pull-docker-dryrun.yml b/.github/workflows/pull-docker-dryrun.yml index 43a4f48669d..f7483132b5b 100644 --- a/.github/workflows/pull-docker-dryrun.yml +++ b/.github/workflows/pull-docker-dryrun.yml @@ -21,7 +21,7 @@ jobs: needs: [files-changed] runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: ./.github/actions/docker-dryrun with: platform: linux/amd64 @@ -31,7 +31,7 @@ jobs: needs: [files-changed] runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: ./.github/actions/docker-dryrun with: platform: linux/arm64 @@ -41,7 +41,7 @@ jobs: needs: [files-changed] runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: ./.github/actions/docker-dryrun with: platform: linux/riscv64 diff --git a/.github/workflows/pull-e2e-tests.yml b/.github/workflows/pull-e2e-tests.yml index f0283f40227..bcd5eba381e 100644 --- a/.github/workflows/pull-e2e-tests.yml +++ b/.github/workflows/pull-e2e-tests.yml @@ -19,7 +19,7 @@ jobs: needs: files-changed runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: ./.github/actions/go-setup - uses: ./.github/actions/node-setup - run: make deps-frontend diff --git a/.github/workflows/pull-labeler.yml b/.github/workflows/pull-labeler.yml index 34395c8d9e3..dd190551624 100644 --- a/.github/workflows/pull-labeler.yml +++ b/.github/workflows/pull-labeler.yml @@ -30,7 +30,7 @@ jobs: pull-requests: write steps: # Base-branch checkout only: pull_request_target runs with elevated token; never run PR-head code here. - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: ref: ${{ github.event.pull_request.base.sha }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 diff --git a/.github/workflows/release-nightly-snapcraft.yml b/.github/workflows/release-nightly-snapcraft.yml index 0f9ac1d423b..46ea663f838 100644 --- a/.github/workflows/release-nightly-snapcraft.yml +++ b/.github/workflows/release-nightly-snapcraft.yml @@ -17,7 +17,7 @@ jobs: SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Install snapcraft run: sudo snap install snapcraft --classic diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index d1561329431..70251bb0910 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -14,7 +14,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force @@ -57,7 +57,7 @@ jobs: echo "Cleaned name is ${REF_NAME}" echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT" - name: configure aws - uses: aws-actions/configure-aws-credentials@acca2b1b2070338fb9fd1ca27ecee81d687e58e5 # v6.1.2 + uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0 with: aws-region: ${{ secrets.AWS_REGION }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} @@ -75,7 +75,7 @@ jobs: contents: read packages: write # to publish to ghcr.io steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force diff --git a/.github/workflows/release-tag-rc.yml b/.github/workflows/release-tag-rc.yml index 3e7655027c4..34ed45b281f 100644 --- a/.github/workflows/release-tag-rc.yml +++ b/.github/workflows/release-tag-rc.yml @@ -15,7 +15,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force @@ -58,7 +58,7 @@ jobs: echo "Cleaned name is ${REF_NAME}" echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT" - name: configure aws - uses: aws-actions/configure-aws-credentials@acca2b1b2070338fb9fd1ca27ecee81d687e58e5 # v6.1.2 + uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0 with: aws-region: ${{ secrets.AWS_REGION }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} @@ -86,7 +86,7 @@ jobs: contents: read packages: write # to publish to ghcr.io steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force diff --git a/.github/workflows/release-tag-version.yml b/.github/workflows/release-tag-version.yml index 66a2984def3..394c524b755 100644 --- a/.github/workflows/release-tag-version.yml +++ b/.github/workflows/release-tag-version.yml @@ -18,7 +18,7 @@ jobs: contents: read packages: write # to publish to ghcr.io steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force @@ -61,7 +61,7 @@ jobs: echo "Cleaned name is ${REF_NAME}" echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT" - name: configure aws - uses: aws-actions/configure-aws-credentials@acca2b1b2070338fb9fd1ca27ecee81d687e58e5 # v6.1.2 + uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0 with: aws-region: ${{ secrets.AWS_REGION }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} @@ -89,7 +89,7 @@ jobs: contents: read packages: write # to publish to ghcr.io steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force From 1e9ea9c8f54c25bcaf34f8a14a0e00fac5c99a2d Mon Sep 17 00:00:00 2001 From: Giteabot Date: Sun, 7 Jun 2026 23:03:55 -0700 Subject: [PATCH 27/40] fix(deps): update npm dependencies (#38029) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [@primer/octicons](https://primer.style/octicons) ([source](https://redirect.github.com/primer/octicons)) | [`19.27.0` → `19.28.0`](https://renovatebot.com/diffs/npm/@primer%2focticons/19.27.0/19.28.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@primer%2focticons/19.28.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@primer%2focticons/19.27.0/19.28.0?slim=true) | | [@typescript-eslint/parser](https://typescript-eslint.io/packages/parser) ([source](https://redirect.github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser)) | [`8.60.0` → `8.60.1`](https://renovatebot.com/diffs/npm/@typescript-eslint%2fparser/8.60.0/8.60.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@typescript-eslint%2fparser/8.60.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@typescript-eslint%2fparser/8.60.0/8.60.1?slim=true) | | [@vitest/eslint-plugin](https://redirect.github.com/vitest-dev/eslint-plugin-vitest) | [`1.6.18` → `1.6.19`](https://renovatebot.com/diffs/npm/@vitest%2feslint-plugin/1.6.18/1.6.19) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@vitest%2feslint-plugin/1.6.19?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitest%2feslint-plugin/1.6.18/1.6.19?slim=true) | | [eslint](https://eslint.org) ([source](https://redirect.github.com/eslint/eslint)) | [`10.4.0` → `10.4.1`](https://renovatebot.com/diffs/npm/eslint/10.4.0/10.4.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/eslint/10.4.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint/10.4.0/10.4.1?slim=true) | | [eslint-import-resolver-typescript](https://redirect.github.com/import-js/eslint-import-resolver-typescript) | [`4.4.4` → `4.4.5`](https://renovatebot.com/diffs/npm/eslint-import-resolver-typescript/4.4.4/4.4.5) | ![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-import-resolver-typescript/4.4.5?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-import-resolver-typescript/4.4.4/4.4.5?slim=true) | | [eslint-plugin-vue-scoped-css](https://future-architect.github.io/eslint-plugin-vue-scoped-css/) ([source](https://redirect.github.com/future-architect/eslint-plugin-vue-scoped-css)) | [`3.1.0` → `3.1.1`](https://renovatebot.com/diffs/npm/eslint-plugin-vue-scoped-css/3.1.0/3.1.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-plugin-vue-scoped-css/3.1.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-plugin-vue-scoped-css/3.1.0/3.1.1?slim=true) | | [js-yaml](https://redirect.github.com/nodeca/js-yaml) | [`4.1.1` → `4.2.0`](https://renovatebot.com/diffs/npm/js-yaml/4.1.1/4.2.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/js-yaml/4.2.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/js-yaml/4.1.1/4.2.0?slim=true) | | [pnpm](https://pnpm.io) ([source](https://redirect.github.com/pnpm/pnpm/tree/HEAD/pnpm)) | [`11.4.0` → `11.5.1`](https://renovatebot.com/diffs/npm/pnpm/11.4.0/11.5.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/pnpm/11.5.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/pnpm/11.4.0/11.5.1?slim=true) | | [rolldown-license-plugin](https://redirect.github.com/silverwind/rolldown-license-plugin) | [`3.0.8` → `3.0.9`](https://renovatebot.com/diffs/npm/rolldown-license-plugin/3.0.8/3.0.9) | ![age](https://developer.mend.io/api/mc/badges/age/npm/rolldown-license-plugin/3.0.9?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/rolldown-license-plugin/3.0.8/3.0.9?slim=true) | | [typescript-eslint](https://typescript-eslint.io/packages/typescript-eslint) ([source](https://redirect.github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint)) | [`8.60.0` → `8.60.1`](https://renovatebot.com/diffs/npm/typescript-eslint/8.60.0/8.60.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/typescript-eslint/8.60.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/typescript-eslint/8.60.0/8.60.1?slim=true) | | [updates](https://redirect.github.com/silverwind/updates) | [`17.17.2` → `17.17.3`](https://renovatebot.com/diffs/npm/updates/17.17.2/17.17.3) | ![age](https://developer.mend.io/api/mc/badges/age/npm/updates/17.17.3?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/updates/17.17.2/17.17.3?slim=true) | | [vite](https://vite.dev) ([source](https://redirect.github.com/vitejs/vite/tree/HEAD/packages/vite)) | [`8.0.14` → `8.0.16`](https://renovatebot.com/diffs/npm/vite/8.0.14/8.0.16) | ![age](https://developer.mend.io/api/mc/badges/age/npm/vite/8.0.16?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vite/8.0.14/8.0.16?slim=true) | | [vitest](https://vitest.dev) ([source](https://redirect.github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)) | [`4.1.7` → `4.1.8`](https://renovatebot.com/diffs/npm/vitest/4.1.7/4.1.8) | ![age](https://developer.mend.io/api/mc/badges/age/npm/vitest/4.1.8?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vitest/4.1.7/4.1.8?slim=true) | | [vue-tsc](https://redirect.github.com/vuejs/language-tools) ([source](https://redirect.github.com/vuejs/language-tools/tree/HEAD/packages/tsc)) | [`3.3.2` → `3.3.3`](https://renovatebot.com/diffs/npm/vue-tsc/3.3.2/3.3.3) | ![age](https://developer.mend.io/api/mc/badges/age/npm/vue-tsc/3.3.3?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vue-tsc/3.3.2/3.3.3?slim=true) | --- ### Release Notes
    primer/octicons (@​primer/octicons) ### [`v19.28.0`](https://redirect.github.com/primer/octicons/blob/HEAD/CHANGELOG.md#19280) [Compare Source](https://redirect.github.com/primer/octicons/compare/v19.27.0...v19.28.0) ##### Minor Changes - [#​1208](https://redirect.github.com/primer/octicons/pull/1208) [`eddab3ff`](https://redirect.github.com/primer/octicons/commit/eddab3ff19f1450eb1d60c78b1d20c2c4bc3fd15) Thanks [@​dylanatsmith](https://redirect.github.com/dylanatsmith)! - Fix vscode icon: update 16px, add 24px, remove 32px and 48px
    typescript-eslint/typescript-eslint (@​typescript-eslint/parser) ### [`v8.60.1`](https://redirect.github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/parser/CHANGELOG.md#8601-2026-06-01) [Compare Source](https://redirect.github.com/typescript-eslint/typescript-eslint/compare/v8.60.0...v8.60.1) This was a version bump only for parser to align it with other projects, there were no code changes. See [GitHub Releases](https://redirect.github.com/typescript-eslint/typescript-eslint/releases/tag/v8.60.1) for more information. You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website.
    vitest-dev/eslint-plugin-vitest (@​vitest/eslint-plugin) ### [`v1.6.19`](https://redirect.github.com/vitest-dev/eslint-plugin-vitest/releases/tag/v1.6.19) [Compare Source](https://redirect.github.com/vitest-dev/eslint-plugin-vitest/compare/v1.6.18...v1.6.19) *No significant changes* #####     [View changes on GitHub](https://redirect.github.com/vitest-dev/eslint-plugin-vitest/compare/v1.6.18...v1.6.19)
    eslint/eslint (eslint) ### [`v10.4.1`](https://redirect.github.com/eslint/eslint/releases/tag/v10.4.1) [Compare Source](https://redirect.github.com/eslint/eslint/compare/v10.4.0...v10.4.1) #### Bug Fixes - [`e557467`](https://redirect.github.com/eslint/eslint/commit/e557467db7496220eebcbe2ac5ea6d38c12bb1ec) fix: update `@eslint/plugin-kit` version to 0.7.2 ([#​20930](https://redirect.github.com/eslint/eslint/issues/20930)) (Francesco Trotta) - [`d4ce898`](https://redirect.github.com/eslint/eslint/commit/d4ce898796ca22c3b96aa70d3014cb85f4bac1cd) fix: propagate failures from delegated commands ([#​20917](https://redirect.github.com/eslint/eslint/issues/20917)) (Minh Vu) - [`f4f3507`](https://redirect.github.com/eslint/eslint/commit/f4f3507460bc016b5be979c05d2969793f570cbf) fix: prefer-arrow-callback invalid autofix with newline after `async` ([#​20916](https://redirect.github.com/eslint/eslint/issues/20916)) (kuldeep kumar) - [`c5bc78b`](https://redirect.github.com/eslint/eslint/commit/c5bc78b37e08b9054a11f0cc2d81808bb24acb85) fix: false positive for reference in `finally` block ([#​20655](https://redirect.github.com/eslint/eslint/issues/20655)) (Tanuj Kanti) - [`27538c0`](https://redirect.github.com/eslint/eslint/commit/27538c01f5df4e9306f6f4ba867b2dd6307fae59) fix: add missing CodePath and CodePathSegment types ([#​20853](https://redirect.github.com/eslint/eslint/issues/20853)) (Pixel998) #### Documentation - [`61b0add`](https://redirect.github.com/eslint/eslint/commit/61b0add61ffc52665562be7bb96f526690a78b30) docs: remove deprecated rule from related rules of `max-params` ([#​20921](https://redirect.github.com/eslint/eslint/issues/20921)) (Tanuj Kanti) - [`305d5b9`](https://redirect.github.com/eslint/eslint/commit/305d5b91aeac24d36fde42f75625a8f183d4ce43) docs: remove deprecated rules from related rules section ([#​20911](https://redirect.github.com/eslint/eslint/issues/20911)) (Tanuj Kanti) - [`49b0202`](https://redirect.github.com/eslint/eslint/commit/49b0202d01918b8061720d586dffd7c68047090c) docs: fix `display: none` of ad ([#​20901](https://redirect.github.com/eslint/eslint/issues/20901)) (Tanuj Kanti) - [`9067f94`](https://redirect.github.com/eslint/eslint/commit/9067f9492ec998afc5b4f057a477ecf6ebd45e44) docs: switch build to Node.js 24 ([#​20893](https://redirect.github.com/eslint/eslint/issues/20893)) (Milos Djermanovic) - [`c91b041`](https://redirect.github.com/eslint/eslint/commit/c91b0417e3420c76807ce1fa2aea76e2de87ab86) docs: Update README (GitHub Actions Bot) - [`e349265`](https://redirect.github.com/eslint/eslint/commit/e349265cb37f3ebc837e178e48a725bb782bd870) docs: clarify semver strings in rule deprecation objects ([#​20885](https://redirect.github.com/eslint/eslint/issues/20885)) (Milos Djermanovic) #### Chores - [`b0e466b`](https://redirect.github.com/eslint/eslint/commit/b0e466b6ab47bfc7de43d8de0c315d8ee83aa584) test: add `data` property to invalid tests cases for rules ([#​20924](https://redirect.github.com/eslint/eslint/issues/20924)) (Tanuj Kanti) - [`f78838b`](https://redirect.github.com/eslint/eslint/commit/f78838bc4c86d487e1bcc7cede260c4467721c46) test: add CodePath type coverage ([#​20904](https://redirect.github.com/eslint/eslint/issues/20904)) (Pixel998) - [`1daa4bd`](https://redirect.github.com/eslint/eslint/commit/1daa4bd734b79a62e317d0394394a6b38cff49f9) chore: update `eslint-plugin-eslint-comments` test data to latest commit ([#​20922](https://redirect.github.com/eslint/eslint/issues/20922)) (Francesco Trotta) - [`002942c`](https://redirect.github.com/eslint/eslint/commit/002942ce988ea28b78e0a2f3b074081e638b552c) ci: declare contents:read on update-readme workflow ([#​20919](https://redirect.github.com/eslint/eslint/issues/20919)) (Arpit Jain) - [`64bca24`](https://redirect.github.com/eslint/eslint/commit/64bca24e7bed35bc3c864fc625cb2d89eca87d5b) chore: update ecosystem plugins ([#​20912](https://redirect.github.com/eslint/eslint/issues/20912)) (ESLint Bot) - [`6d7c832`](https://redirect.github.com/eslint/eslint/commit/6d7c832950d5e92499d88e504080661f888f8f56) chore: ignore fflate updates in renovate ([#​20908](https://redirect.github.com/eslint/eslint/issues/20908)) (Pixel998) - [`b2c8638`](https://redirect.github.com/eslint/eslint/commit/b2c86382164d87c6203b78d52068cd6a2a6ffe30) ci: bump pnpm/action-setup from 6.0.7 to 6.0.8 ([#​20889](https://redirect.github.com/eslint/eslint/issues/20889)) (dependabot\[bot]) - [`a9b8d7f`](https://redirect.github.com/eslint/eslint/commit/a9b8d7f74c50211701cfc49710fa541fd91b2aa5) chore: increase maxBuffer for ecosystem tests ([#​20881](https://redirect.github.com/eslint/eslint/issues/20881)) (sethamus) - [`b702ead`](https://redirect.github.com/eslint/eslint/commit/b702ead5e1ed7cb9f28238a454797662efb37396) chore: update ecosystem update PR settings ([#​20884](https://redirect.github.com/eslint/eslint/issues/20884)) (Pixel998) - [`507f60e`](https://redirect.github.com/eslint/eslint/commit/507f60e9a78c9a902bc8759f066ae17a1ea6cd81) chore: update ecosystem plugins ([#​20882](https://redirect.github.com/eslint/eslint/issues/20882)) (ESLint Bot) - [`92f5c5b`](https://redirect.github.com/eslint/eslint/commit/92f5c5bb6bf3a5d167c8ee53a430833410295c6d) test: add unit test for message-count ([#​20878](https://redirect.github.com/eslint/eslint/issues/20878)) (kuldeep kumar) - [`df32108`](https://redirect.github.com/eslint/eslint/commit/df321080af5758b1fa25e4b9a40e26135642dd6e) chore: add [@​eslint/markdown](https://redirect.github.com/eslint/markdown) and typescript-eslint ecosystem tests ([#​20837](https://redirect.github.com/eslint/eslint/issues/20837)) (sethamus) - [`327f91d`](https://redirect.github.com/eslint/eslint/commit/327f91d36aa49f2a50ded931d841a16374fd875f) chore: use includeIgnoreFile internally ([#​20876](https://redirect.github.com/eslint/eslint/issues/20876)) (Kirk Waiblinger) - [`f0dc4bd`](https://redirect.github.com/eslint/eslint/commit/f0dc4bd893fb3a9f44e4ddc3ad7063ffb0beacd3) chore: pin fflate\@​0.8.2 ([#​20877](https://redirect.github.com/eslint/eslint/issues/20877)) (Milos Djermanovic) - [`0f4bd25`](https://redirect.github.com/eslint/eslint/commit/0f4bd257a67a082b756de746d9e0c4842ab764ca) ci: run Discord alert for ecosystem test failures ([#​20873](https://redirect.github.com/eslint/eslint/issues/20873)) (Copilot)
    import-js/eslint-import-resolver-typescript (eslint-import-resolver-typescript) ### [`v4.4.5`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/blob/HEAD/CHANGELOG.md#445) [Compare Source](https://redirect.github.com/import-js/eslint-import-resolver-typescript/compare/v4.4.4...v4.4.5) ##### Patch Changes - [#​473](https://redirect.github.com/import-js/eslint-import-resolver-typescript/pull/473) [`32c61ab`](https://redirect.github.com/import-js/eslint-import-resolver-typescript/commit/32c61abccf26bd2a2267f2e0e67d82e6f88d149a) Thanks [@​leey0818](https://redirect.github.com/leey0818)! - fix: check tsconfig matching before using resolver
    future-architect/eslint-plugin-vue-scoped-css (eslint-plugin-vue-scoped-css) ### [`v3.1.1`](https://redirect.github.com/future-architect/eslint-plugin-vue-scoped-css/blob/HEAD/CHANGELOG.md#311) [Compare Source](https://redirect.github.com/future-architect/eslint-plugin-vue-scoped-css/compare/v3.1.0...v3.1.1) ##### Patch Changes - Fix false positives in `vue-scoped-css/require-selector-used-inside` for selectors that start with ignored pseudo-classes such as `:has(...)`. ([#​496](https://redirect.github.com/future-architect/eslint-plugin-vue-scoped-css/pull/496))
    nodeca/js-yaml (js-yaml) ### [`v4.2.0`](https://redirect.github.com/nodeca/js-yaml/blob/HEAD/CHANGELOG.md#420---2026-06-01) [Compare Source](https://redirect.github.com/nodeca/js-yaml/compare/4.1.1...590dbabadd172b099c07654fab2eabec8c7a07b9) ##### Added - Added `docs/safety.md` with notes about processing untrusted YAML. - Added `maxDepth` (100) loader option. Not a problem, but gives a better exception instead of RangeError on stack overflow. - Added `maxMergeSeqLength` (20) loader option. Not a problem after `merge` fix, but an additional restriction for safety. - Added sourcemaps to `dist/` builds. ##### Changed - Stop resolving numbers with underscores as numeric scalars, [#​627](https://redirect.github.com/nodeca/js-yaml/issues/627). - Switched dev toolchains to Vite / neostandard. - Updated demo. - Reorganized tests. - `dist/` files are no longer kept in the repository. ##### Fixed - Fix parsing of properties on the first implicit block mapping key, [#​62](https://redirect.github.com/nodeca/js-yaml/issues/62). - Fix trailing whitespace handling when folding flow scalar lines, [#​307](https://redirect.github.com/nodeca/js-yaml/issues/307). - Reject top-level block scalars without content indentation, [#​280](https://redirect.github.com/nodeca/js-yaml/issues/280). - Ensure numbers survive round-trip, [#​737](https://redirect.github.com/nodeca/js-yaml/issues/737). - Fix test coverage for issue [#​221](https://redirect.github.com/nodeca/js-yaml/issues/221). - Fix flow scalar trailing whitespace folding, [#​307](https://redirect.github.com/nodeca/js-yaml/issues/307). - Fix digits in YAML named tag handles. ##### Security - Fix potential DoS via quadratic complexity in merge - deduplicate repeated elements (makes sense for malformed files > 10K).
    pnpm/pnpm (pnpm) ### [`v11.5.1`](https://redirect.github.com/pnpm/pnpm/blob/HEAD/pnpm/CHANGELOG.md#1151) [Compare Source](https://redirect.github.com/pnpm/pnpm/compare/v11.5.0...v11.5.1) ##### Patch Changes - Improve `pnpm audit` performance by pruning non-vulnerable lockfile subtrees and stopping path enumeration once vulnerable findings reach the path cap. - Avoid crashing when the workspace state cache is partially written or malformed. - Set `npm_config_user_agent` for root lifecycle scripts during headless installs. - Preserve the `integrity` field of a remote (non-registry) tarball dependency when its lockfile entry is rebuilt. Re-resolving such a dependency without re-fetching it (for example via `pnpm update`, or when another dependency changes) produced a resolution with no integrity — URL/tarball resolvers only learn the integrity after the tarball is downloaded — so the previously recorded integrity was dropped, making later installs fail with `ERR_PNPM_MISSING_TARBALL_INTEGRITY` [#​12067](https://redirect.github.com/pnpm/pnpm/issues/12067). - Normalize a string `repository` field into the `{ type, url }` object form when creating the publish manifest, matching npm's behavior. Some registries (e.g. Gitea/Codeberg) reject a string `repository` with a 500 Internal Server Error during `pnpm publish` [#​12099](https://redirect.github.com/pnpm/pnpm/issues/12099). - Preserve compatible optional peer versions already present in the lockfile when resolving dependencies. - Fixed inconsistent resolution of a peer dependency that is shared through a diamond. When a package peer-depends on both another package and one of that package's own peer dependencies (for example `@typescript-eslint/eslint-plugin` peer-depends on both `@typescript-eslint/parser` and `typescript`, and `@typescript-eslint/parser` peer-depends on `typescript`), pnpm no longer reuses a hoisted instance of the shared peer that was resolved against a different version [#​12079](https://redirect.github.com/pnpm/pnpm/issues/12079). ### [`v11.5.0`](https://redirect.github.com/pnpm/pnpm/blob/HEAD/pnpm/CHANGELOG.md#1150) [Compare Source](https://redirect.github.com/pnpm/pnpm/compare/v11.4.0...v11.5.0) ##### Minor Changes - Added a new `hoistingLimits` setting for `nodeLinker: hoisted` installs, mirroring yarn's `nmHoistingLimits`. It accepts `none` (the default — hoist as far as possible), `workspaces` (hoist only as far as each workspace package), or `dependencies` (hoist only up to each workspace package's direct dependencies). Originally proposed in [#​6468](https://redirect.github.com/pnpm/pnpm/pull/6468), closing [#​6457](https://redirect.github.com/pnpm/pnpm/issues/6457). - Replaced `enquirer` with `@inquirer/prompts` for all interactive prompts. Fixes the `update -i` scrolling overflow bug where long choice lists were clipped in the terminal [#​6643](https://redirect.github.com/pnpm/pnpm/issues/6643). **User-facing changes:** - `pnpm update -i` / `pnpm update -i --latest`: Scrolling now works correctly when many packages are available; the new library uses visual-line-aware pagination via `usePagination` - `pnpm audit --fix -i`: Same scrolling fix for vulnerability selection - `pnpm approve-builds`: Interactive build approval prompts updated - `pnpm patch`: Version selection and "apply to all" prompts updated - `pnpm patch-remove`: Patch removal selection updated - `pnpm publish`: Branch confirmation prompt updated - `pnpm login`: Credential prompts updated - `pnpm run` / `pnpm exec` (with `verifyDepsBeforeRun=prompt`): Confirmation prompt updated Vim-style `j`/`k` keys still work for up/down navigation in all interactive prompts. **Internal:** The `OtpEnquirer` and `LoginEnquirer` DI interfaces changed from `{ prompt }` to `{ input }` / `{ input, password }` respectively. Plugins or custom builds that inject their own enquirer mock will need to update. - Staged publishes are now recognized in the trust scale. When a package version's registry metadata carries an `approver` field, it is treated as the strongest trust evidence (ranked above trusted publishers and provenance attestations), since staged publishes require 2FA publish approvals. This prevents false-positive trust downgrade errors when moving from a staged publish to a lower trust level [#​11887](https://redirect.github.com/pnpm/pnpm/issues/11887). ##### Patch Changes - Fix pnpm hanging during peer resolution when an aliased install pulls in transitive packages with mutual peer cycles at different depths in the dependency tree (for example, `pnpm i nuxt@npm:nuxt-nightly@5x`). Cycles whose members hit the `findHit` cache instead of running their own `calculateDepPath` are now short-circuited by sibling resolutions at the level where the cycle is detected, so the cached path promises no longer deadlock. [#​11999](https://redirect.github.com/pnpm/pnpm/issues/11999). - Fix `pnpm dist-tag add` and `pnpm dist-tag rm` against npmjs.org failing without `--otp` with `[ERR_PNPM_UNAUTHORIZED] You must be logged in to set dist-tag … "You must provide a one-time pass. Upgrade your client to npm@latest in order to use 2FA."`. pnpm now sends `npm-auth-type: web` on dist-tag writes and surfaces the resulting OTP challenge through the existing browser-based 2FA flow (the same `withOtpHandling` helper used by `pnpm publish`), so the browser opens, the user authenticates, and the dist-tag is set on retry. `--otp=` continues to work via the classic flow. - Fix `minimumReleaseAgeExclude` handling in npm resolution fast paths so excluded packages do not get pinned to stale versions. Excludes are honored consistently during `publishedBy` metadata selection and cache-mtime shortcuts. - Fix the `integrity` field being dropped from the lockfile entry of a remote (non-registry) https-tarball dependency when an unrelated package is installed afterwards. URL/tarball resolvers do not return an integrity (it is only known after the tarball is downloaded), so when such a dependency was reused from the lockfile without being re-fetched, its integrity was lost. It is now carried over from the existing resolution. With pnpm's lockfile-integrity hardening, the missing integrity made subsequent `--frozen-lockfile` installs fail with `ERR_PNPM_MISSING_TARBALL_INTEGRITY`. [#​12001](https://redirect.github.com/pnpm/pnpm/issues/12001). - Skip dependency re-resolution when `pnpm-lock.yaml` is missing but `node_modules/.pnpm/lock.yaml` exists and still satisfies the manifest. `pnpm install` now reuses the materialized snapshot to regenerate `pnpm-lock.yaml` instead of walking the registry to rebuild it from scratch, turning the cache+node\_modules variation into a near-no-op for users who deleted the lockfile but kept the install [#​11993](https://redirect.github.com/pnpm/pnpm/issues/11993). `--frozen-lockfile` still refuses to proceed when `pnpm-lock.yaml` is absent — the regenerated lockfile must be committed, so failing loudly is the correct behavior for CI.
    silverwind/rolldown-license-plugin (rolldown-license-plugin) ### [`v3.0.9`](https://redirect.github.com/silverwind/rolldown-license-plugin/releases/tag/3.0.9) [Compare Source](https://redirect.github.com/silverwind/rolldown-license-plugin/compare/3.0.8...3.0.9) - update deps (silverwind) - make: collapse patch/minor/major into one rule (silverwind) - simplify generateBundle: pair dir+raw, rename shadow, inline single-use const (silverwind) - make update a combination target, split out update-js (silverwind) - add update-actions make target (silverwind) - remove authorship attribution rule from AGENTS.md (silverwind) - docs: use defineConfig in README usage example (silverwind)
    typescript-eslint/typescript-eslint (typescript-eslint) ### [`v8.60.1`](https://redirect.github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/typescript-eslint/CHANGELOG.md#8601-2026-06-01) [Compare Source](https://redirect.github.com/typescript-eslint/typescript-eslint/compare/v8.60.0...v8.60.1) This was a version bump only for typescript-eslint to align it with other projects, there were no code changes. See [GitHub Releases](https://redirect.github.com/typescript-eslint/typescript-eslint/releases/tag/v8.60.1) for more information. You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website.
    silverwind/updates (updates) ### [`v17.17.3`](https://redirect.github.com/silverwind/updates/releases/tag/17.17.3) [Compare Source](https://redirect.github.com/silverwind/updates/compare/17.17.2...17.17.3) - fix prerelease drop in updateVersionRange and scope regex (silverwind) - fix 1.2.x ranges, docker tag corruption, and per-file cooldown (silverwind) - fix go +incompatible, cargo inline-table, and prerelease selection (silverwind) - fix --pin range parsing, url tag deps, and -s flag docs (silverwind) - make update a combination target, split out update-js (silverwind) - add update-actions make target (silverwind) - remove authorship attribution rule from AGENTS.md (silverwind)
    vitejs/vite (vite) ### [`v8.0.16`](https://redirect.github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#small-8016-2026-06-01-small) [Compare Source](https://redirect.github.com/vitejs/vite/compare/v8.0.15...v8.0.16) ##### Bug Fixes - **deps:** reject UNC paths for launch-editor-middleware ([#​22571](https://redirect.github.com/vitejs/vite/issues/22571)) ([50b9512](https://redirect.github.com/vitejs/vite/commit/50b951225bbf6151eb84a3ad5a454908ab4a76c9)) - reject windows alternate paths ([#​22572](https://redirect.github.com/vitejs/vite/issues/22572)) ([dc245c7](https://redirect.github.com/vitejs/vite/commit/dc245c71e5007ea4d891a025e2d69ac96c736546)) ### [`v8.0.15`](https://redirect.github.com/vitejs/vite/blob/HEAD/packages/vite/CHANGELOG.md#small-8015-2026-06-01-small) [Compare Source](https://redirect.github.com/vitejs/vite/compare/v8.0.14...v8.0.15) ##### Features - send 408 on request timeout ([#​22476](https://redirect.github.com/vitejs/vite/issues/22476)) ([c85c9ee](https://redirect.github.com/vitejs/vite/commit/c85c9eeb9aaf41f477b48b057146887bd5620797)) - update rolldown to 1.0.3 ([#​22538](https://redirect.github.com/vitejs/vite/issues/22538)) ([646dbed](https://redirect.github.com/vitejs/vite/commit/646dbedd2870f8ec48df0321177d8aa64bbd1575)) ##### Bug Fixes - capitalize error messages and remove spurious space in parse error ([#​22488](https://redirect.github.com/vitejs/vite/issues/22488)) ([85a0eff](https://redirect.github.com/vitejs/vite/commit/85a0eff1c82bbb7c99a0fe8e63704316578a40d3)) - **deps:** update all non-major dependencies ([#​22511](https://redirect.github.com/vitejs/vite/issues/22511)) ([2686d7d](https://redirect.github.com/vitejs/vite/commit/2686d7d0b722402204d3bcc687a87adea1bcf9fa)) - **dev:** fix html-proxy cache key mismatch for /@​fs/ HTML paths ([#​21762](https://redirect.github.com/vitejs/vite/issues/21762)) ([47c4213](https://redirect.github.com/vitejs/vite/commit/47c4213f134f562c41ed7c031e4788510cf7e31e)) - **glob:** error on relative glob in virtual module when no files match ([#​22497](https://redirect.github.com/vitejs/vite/issues/22497)) ([5c8e98f](https://redirect.github.com/vitejs/vite/commit/5c8e98f8b584ac5d42f0f9b8580c49792213b13c)) - **optimizer:** close the rolldown bundle when write() rejects ([#​22528](https://redirect.github.com/vitejs/vite/issues/22528)) ([e3cfb9d](https://redirect.github.com/vitejs/vite/commit/e3cfb9deecff563550fa1b8abd27656b8b292815)) - **resolve:** provide onWarn for viteResolvePlugin in JS plugin containers ([#​22509](https://redirect.github.com/vitejs/vite/issues/22509)) ([40985f1](https://redirect.github.com/vitejs/vite/commit/40985f1c09b7696e594e6c5695fbc315d2da2c83)) ##### Miscellaneous Chores - **deps:** update rolldown-related dependencies ([#​22566](https://redirect.github.com/vitejs/vite/issues/22566)) ([3052a67](https://redirect.github.com/vitejs/vite/commit/3052a67d9350f4c5076ab1c222c4a21a589cbcdd)) ##### Code Refactoring - correct logic in `collectAllModules` function ([#​22562](https://redirect.github.com/vitejs/vite/issues/22562)) ([6978a9c](https://redirect.github.com/vitejs/vite/commit/6978a9ceb942c4f5e211d52b8a1e569f8a65c80c))
    vitest-dev/vitest (vitest) ### [`v4.1.8`](https://redirect.github.com/vitest-dev/vitest/releases/tag/v4.1.8) [Compare Source](https://redirect.github.com/vitest-dev/vitest/compare/v4.1.7...v4.1.8) #####    🐞 Bug Fixes - **browser**: - Disable client `cdp` API when `allowWrite/allowExec: false` \[backport to v4]  -  by [@​hi-ogawa](https://redirect.github.com/hi-ogawa) and **Codex** in [#​10450](https://redirect.github.com/vitest-dev/vitest/issues/10450) [(e4067)](https://redirect.github.com/vitest-dev/vitest/commit/e4067b3b1) - Remove orphaned Playwright route when same module is mocked via multiple ids \[backport to v4]  -  by [@​toxik](https://redirect.github.com/toxik) and [@​Zelys-DFKH](https://redirect.github.com/Zelys-DFKH) in [#​10474](https://redirect.github.com/vitest-dev/vitest/issues/10474) [(675b4)](https://redirect.github.com/vitest-dev/vitest/commit/675b4343f) #####     [View changes on GitHub](https://redirect.github.com/vitest-dev/vitest/compare/v4.1.7...v4.1.8)
    vuejs/language-tools (vue-tsc) ### [`v3.3.3`](https://redirect.github.com/vuejs/language-tools/blob/HEAD/CHANGELOG.md#333-2026-05-30) [Compare Source](https://redirect.github.com/vuejs/language-tools/compare/v3.3.2...v3.3.3) ##### vscode - **fix:** prevent grammar scopes leakage in capitalized tags ([#​6073](https://redirect.github.com/vuejs/language-tools/issues/6073)) - Thanks to [@​KazariEX](https://redirect.github.com/KazariEX)! - **fix:** preserve TS auto imports behavior in Vue files ([#​6072](https://redirect.github.com/vuejs/language-tools/issues/6072)) - Thanks to [@​KazariEX](https://redirect.github.com/KazariEX)! ##### workspace - **fix:** read PR title from env in `auto-version` workflow to prevent injection ([#​6074](https://redirect.github.com/vuejs/language-tools/issues/6074)) - Thanks to [@​arpitjain099](https://redirect.github.com/arpitjain099)!
    --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Only on Monday (`* * * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://redirect.github.com/renovatebot/renovate). Co-authored-by: bircni --- package.json | 28 +- pnpm-lock.yaml | 906 ++++++++++------------- public/assets/img/svg/octicon-vscode.svg | 2 +- 3 files changed, 393 insertions(+), 543 deletions(-) diff --git a/package.json b/package.json index 8a915ea795c..cc6c270a842 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "type": "module", - "packageManager": "pnpm@11.4.0", + "packageManager": "pnpm@11.5.1", "engines": { "node": ">= 22.18.0", "pnpm": ">= 11.0.0" @@ -28,7 +28,7 @@ "@lezer/highlight": "1.2.3", "@mcaptcha/vanilla-glue": "0.1.0-rc2", "@mermaid-js/layout-elk": "0.2.1", - "@primer/octicons": "19.27.0", + "@primer/octicons": "19.28.0", "@replit/codemirror-indentation-markers": "6.5.3", "@replit/codemirror-lang-nix": "6.0.1", "@replit/codemirror-lang-svelte": "6.0.0", @@ -50,14 +50,14 @@ "esbuild": "0.28.0", "idiomorph": "0.7.4", "jquery": "4.0.0", - "js-yaml": "4.1.1", + "js-yaml": "4.2.0", "katex": "0.17.0", "mermaid": "11.15.0", "online-3d-viewer": "0.18.0", "pdfobject": "2.3.1", "perfect-debounce": "2.1.0", "postcss": "8.5.15", - "rolldown-license-plugin": "3.0.8", + "rolldown-license-plugin": "3.0.9", "sortablejs": "1.15.7", "swagger-ui-dist": "5.32.6", "tailwindcss": "3.4.19", @@ -67,7 +67,7 @@ "tributejs": "5.1.3", "uint8-to-base64": "0.2.1", "vanilla-colorful": "0.7.2", - "vite": "8.0.14", + "vite": "8.0.16", "vite-string-plugin": "2.0.4", "vue": "3.5.35", "vue-bar-graph": "2.2.0", @@ -89,11 +89,11 @@ "@types/swagger-ui-dist": "3.30.6", "@types/throttle-debounce": "5.0.2", "@types/toastify-js": "1.12.4", - "@typescript-eslint/parser": "8.60.0", + "@typescript-eslint/parser": "8.60.1", "@vitejs/plugin-vue": "6.0.7", - "@vitest/eslint-plugin": "1.6.18", - "eslint": "10.4.0", - "eslint-import-resolver-typescript": "4.4.4", + "@vitest/eslint-plugin": "1.6.19", + "eslint": "10.4.1", + "eslint-import-resolver-typescript": "4.4.5", "eslint-plugin-array-func": "5.1.1", "eslint-plugin-de-morgan": "2.1.2", "eslint-plugin-github": "6.0.0", @@ -103,7 +103,7 @@ "eslint-plugin-sonarjs": "4.0.3", "eslint-plugin-unicorn": "64.0.0", "eslint-plugin-vue": "10.9.1", - "eslint-plugin-vue-scoped-css": "3.1.0", + "eslint-plugin-vue-scoped-css": "3.1.1", "eslint-plugin-wc": "3.1.0", "globals": "17.6.0", "happy-dom": "20.9.0", @@ -119,9 +119,9 @@ "stylelint-value-no-unknown-custom-properties": "6.1.1", "svgo": "4.0.1", "typescript": "6.0.3", - "typescript-eslint": "8.60.0", - "updates": "17.17.2", - "vitest": "4.1.7", - "vue-tsc": "3.3.2" + "typescript-eslint": "8.60.1", + "updates": "17.17.3", + "vitest": "4.1.8", + "vue-tsc": "3.3.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 11c6ebf5add..ce5d84c9823 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,8 +75,8 @@ importers: specifier: 0.2.1 version: 0.2.1(mermaid@11.15.0) '@primer/octicons': - specifier: 19.27.0 - version: 19.27.0 + specifier: 19.28.0 + version: 19.28.0 '@replit/codemirror-indentation-markers': specifier: 6.5.3 version: 6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0) @@ -94,7 +94,7 @@ importers: version: 2.6.2 '@vitejs/plugin-vue': specifier: 6.0.7 - version: 6.0.7(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))(vue@3.5.35(typescript@6.0.3)) + version: 6.0.7(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))(vue@3.5.35(typescript@6.0.3)) ansi_up: specifier: 6.0.6 version: 6.0.6 @@ -141,8 +141,8 @@ importers: specifier: 4.0.0 version: 4.0.0 js-yaml: - specifier: 4.1.1 - version: 4.1.1 + specifier: 4.2.0 + version: 4.2.0 katex: specifier: 0.17.0 version: 0.17.0 @@ -162,8 +162,8 @@ importers: specifier: 8.5.15 version: 8.5.15 rolldown-license-plugin: - specifier: 3.0.8 - version: 3.0.8(rolldown@1.0.2) + specifier: 3.0.9 + version: 3.0.9(rolldown@1.0.3) sortablejs: specifier: 1.15.7 version: 1.15.7 @@ -192,11 +192,11 @@ importers: specifier: 0.7.2 version: 0.7.2 vite: - specifier: 8.0.14 - version: 8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) + specifier: 8.0.16 + version: 8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) vite-string-plugin: specifier: 2.0.4 - version: 2.0.4(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)) + version: 2.0.4(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)) vue: specifier: 3.5.35 version: 3.5.35(typescript@6.0.3) @@ -209,7 +209,7 @@ importers: devDependencies: '@eslint-community/eslint-plugin-eslint-comments': specifier: 4.7.2 - version: 4.7.2(eslint@10.4.0(jiti@2.7.0)) + version: 4.7.2(eslint@10.4.1(jiti@2.7.0)) '@eslint/json': specifier: 1.2.0 version: 1.2.0 @@ -218,7 +218,7 @@ importers: version: 1.60.0 '@stylistic/eslint-plugin': specifier: 5.10.0 - version: 5.10.0(eslint@10.4.0(jiti@2.7.0)) + version: 5.10.0(eslint@10.4.1(jiti@2.7.0)) '@stylistic/stylelint-plugin': specifier: 5.2.0 version: 5.2.0(stylelint@17.12.0(typescript@6.0.3)) @@ -253,50 +253,50 @@ importers: specifier: 1.12.4 version: 1.12.4 '@typescript-eslint/parser': - specifier: 8.60.0 - version: 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + specifier: 8.60.1 + version: 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) '@vitest/eslint-plugin': - specifier: 1.6.18 - version: 1.6.18(@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.7(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))) + specifier: 1.6.19 + version: 1.6.19(@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.8(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))) eslint: - specifier: 10.4.0 - version: 10.4.0(jiti@2.7.0) + specifier: 10.4.1 + version: 10.4.1(jiti@2.7.0) eslint-import-resolver-typescript: - specifier: 4.4.4 - version: 4.4.4(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.0(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.0(jiti@2.7.0)) + specifier: 4.4.5 + version: 4.4.5(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.1(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.1(jiti@2.7.0)) eslint-plugin-array-func: specifier: 5.1.1 - version: 5.1.1(eslint@10.4.0(jiti@2.7.0)) + version: 5.1.1(eslint@10.4.1(jiti@2.7.0)) eslint-plugin-de-morgan: specifier: 2.1.2 - version: 2.1.2(eslint@10.4.0(jiti@2.7.0)) + version: 2.1.2(eslint@10.4.1(jiti@2.7.0)) eslint-plugin-github: specifier: 6.0.0 - version: 6.0.0(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)) + version: 6.0.0(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)) eslint-plugin-import-x: specifier: 4.16.2 - version: 4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.0(jiti@2.7.0)) + version: 4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.1(jiti@2.7.0)) eslint-plugin-playwright: specifier: 2.10.4 - version: 2.10.4(eslint@10.4.0(jiti@2.7.0)) + version: 2.10.4(eslint@10.4.1(jiti@2.7.0)) eslint-plugin-regexp: specifier: 3.1.0 - version: 3.1.0(eslint@10.4.0(jiti@2.7.0)) + version: 3.1.0(eslint@10.4.1(jiti@2.7.0)) eslint-plugin-sonarjs: specifier: 4.0.3 - version: 4.0.3(eslint@10.4.0(jiti@2.7.0)) + version: 4.0.3(eslint@10.4.1(jiti@2.7.0)) eslint-plugin-unicorn: specifier: 64.0.0 - version: 64.0.0(eslint@10.4.0(jiti@2.7.0)) + version: 64.0.0(eslint@10.4.1(jiti@2.7.0)) eslint-plugin-vue: specifier: 10.9.1 - version: 10.9.1(@stylistic/eslint-plugin@5.10.0(eslint@10.4.0(jiti@2.7.0)))(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.0(jiti@2.7.0))) + version: 10.9.1(@stylistic/eslint-plugin@5.10.0(eslint@10.4.1(jiti@2.7.0)))(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.1(jiti@2.7.0))) eslint-plugin-vue-scoped-css: - specifier: 3.1.0 - version: 3.1.0(eslint@10.4.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.0(jiti@2.7.0))) + specifier: 3.1.1 + version: 3.1.1(eslint@10.4.1(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.1(jiti@2.7.0))) eslint-plugin-wc: specifier: 3.1.0 - version: 3.1.0(eslint@10.4.0(jiti@2.7.0)) + version: 3.1.0(eslint@10.4.1(jiti@2.7.0)) globals: specifier: 17.6.0 version: 17.6.0 @@ -340,17 +340,17 @@ importers: specifier: 6.0.3 version: 6.0.3 typescript-eslint: - specifier: 8.60.0 - version: 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + specifier: 8.60.1 + version: 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) updates: - specifier: 17.17.2 - version: 17.17.2 + specifier: 17.17.3 + version: 17.17.3 vitest: - specifier: 4.1.7 - version: 4.1.7(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)) + specifier: 4.1.8 + version: 4.1.8(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)) vue-tsc: - specifier: 3.3.2 - version: 3.3.2(typescript@6.0.3) + specifier: 3.3.3 + version: 3.3.3(typescript@6.0.3) packages: @@ -985,8 +985,8 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@oxc-project/types@0.132.0': - resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==} + '@oxc-project/types@0.133.0': + resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} '@package-json/types@0.0.12': resolution: {integrity: sha512-uu43FGU34B5VM9mCNjXCwLaGHYjXdNincqKLaraaCW+7S2+SmiBg1Nv8bPnmschrIfZmfKNY9f3fC376MRrObw==} @@ -1003,8 +1003,8 @@ packages: '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - '@primer/octicons@19.27.0': - resolution: {integrity: sha512-7xC6D89f9IcoDezeKTGETbgRAoXJnbZlakavqYzD4Wo+uTC6212k0fTE/dLV8WCDOwfp//WyONftdaFRdI1VdQ==} + '@primer/octicons@19.28.0': + resolution: {integrity: sha512-FCpW9ZXI9U9h7wjYSXFQK4Zyp1Roc/kF8nymak4bYccWaWoUixbnIr4u8UYiRoPRSglm+23TZEyUZHrgNql9Jw==} '@replit/codemirror-indentation-markers@6.5.3': resolution: {integrity: sha512-hL5Sfvw3C1vgg7GolLe/uxX5T3tmgOA3ZzqlMv47zjU1ON51pzNWiVbS22oh6crYhtVhv8b3gdXwoYp++2ilHw==} @@ -1054,97 +1054,97 @@ packages: resolution: {integrity: sha512-FqALmHI8D4o6lk/LRWDnhw95z5eO+eAa6ORjVg09YRR7BkcM6oPHU9uyC0gtQG5vpFLvgpeU4+zEAz2H8APHNw==} engines: {node: '>= 10'} - '@rolldown/binding-android-arm64@1.0.2': - resolution: {integrity: sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==} + '@rolldown/binding-android-arm64@1.0.3': + resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.2': - resolution: {integrity: sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==} + '@rolldown/binding-darwin-arm64@1.0.3': + resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.2': - resolution: {integrity: sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==} + '@rolldown/binding-darwin-x64@1.0.3': + resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.2': - resolution: {integrity: sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==} + '@rolldown/binding-freebsd-x64@1.0.3': + resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.2': - resolution: {integrity: sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.2': - resolution: {integrity: sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==} + '@rolldown/binding-linux-arm64-gnu@1.0.3': + resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.2': - resolution: {integrity: sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==} + '@rolldown/binding-linux-arm64-musl@1.0.3': + resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.2': - resolution: {integrity: sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==} + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.2': - resolution: {integrity: sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==} + '@rolldown/binding-linux-s390x-gnu@1.0.3': + resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.2': - resolution: {integrity: sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==} + '@rolldown/binding-linux-x64-gnu@1.0.3': + resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.2': - resolution: {integrity: sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==} + '@rolldown/binding-linux-x64-musl@1.0.3': + resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.2': - resolution: {integrity: sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==} + '@rolldown/binding-openharmony-arm64@1.0.3': + resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.2': - resolution: {integrity: sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==} + '@rolldown/binding-wasm32-wasi@1.0.3': + resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.2': - resolution: {integrity: sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==} + '@rolldown/binding-win32-arm64-msvc@1.0.3': + resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.2': - resolution: {integrity: sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==} + '@rolldown/binding-win32-x64-msvc@1.0.3': + resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1408,14 +1408,6 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/eslint-plugin@8.60.0': - resolution: {integrity: sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.60.0 - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/eslint-plugin@8.60.1': resolution: {integrity: sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1424,52 +1416,29 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.60.0': - resolution: {integrity: sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==} + '@typescript-eslint/parser@8.60.1': + resolution: {integrity: sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.60.0': - resolution: {integrity: sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.60.1': resolution: {integrity: sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.60.0': - resolution: {integrity: sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.60.1': resolution: {integrity: sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.60.0': - resolution: {integrity: sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/tsconfig-utils@8.60.1': resolution: {integrity: sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.60.0': - resolution: {integrity: sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.60.1': resolution: {integrity: sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1477,33 +1446,16 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.60.0': - resolution: {integrity: sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.60.1': resolution: {integrity: sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.60.0': - resolution: {integrity: sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/typescript-estree@8.60.1': resolution: {integrity: sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.60.0': - resolution: {integrity: sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.60.1': resolution: {integrity: sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1511,10 +1463,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.60.0': - resolution: {integrity: sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.60.1': resolution: {integrity: sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1649,8 +1597,8 @@ packages: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 vue: ^3.2.25 - '@vitest/eslint-plugin@1.6.18': - resolution: {integrity: sha512-J6U4X0jH3NwTuYouvrJn6I8ypTOU+GhKEjyVwpoPnDuc23usa/xi/R0caWLBbNp3xLy3/rL1YkuJuneTMVV4Mg==} + '@vitest/eslint-plugin@1.6.19': + resolution: {integrity: sha512-zodmXRsVKFsuHxHJILuTFaaKsrsxm0YsiOX65clk+LpCW9JrVXaf6ERXr0caDs+NEk0S62Jyk0K7XYQ7gWXheA==} engines: {node: '>=18'} peerDependencies: '@typescript-eslint/eslint-plugin': '*' @@ -1665,11 +1613,11 @@ packages: vitest: optional: true - '@vitest/expect@4.1.7': - resolution: {integrity: sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==} + '@vitest/expect@4.1.8': + resolution: {integrity: sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==} - '@vitest/mocker@4.1.7': - resolution: {integrity: sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==} + '@vitest/mocker@4.1.8': + resolution: {integrity: sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1679,20 +1627,20 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.7': - resolution: {integrity: sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==} + '@vitest/pretty-format@4.1.8': + resolution: {integrity: sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==} - '@vitest/runner@4.1.7': - resolution: {integrity: sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==} + '@vitest/runner@4.1.8': + resolution: {integrity: sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==} - '@vitest/snapshot@4.1.7': - resolution: {integrity: sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==} + '@vitest/snapshot@4.1.8': + resolution: {integrity: sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==} - '@vitest/spy@4.1.7': - resolution: {integrity: sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==} + '@vitest/spy@4.1.8': + resolution: {integrity: sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==} - '@vitest/utils@4.1.7': - resolution: {integrity: sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==} + '@vitest/utils@4.1.8': + resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==} '@volar/language-core@2.4.28': resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} @@ -1715,8 +1663,8 @@ packages: '@vue/compiler-ssr@3.5.35': resolution: {integrity: sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==} - '@vue/language-core@3.3.2': - resolution: {integrity: sha512-CLwjSfHlPLhjd2qhuS3tTFtnOIWHXAM5u4X1DxmzlQ8j5bmOYlKCsSusOP7jCRJnlVg0mCTQtHU3vwFvopZGoQ==} + '@vue/language-core@3.3.3': + resolution: {integrity: sha512-X6p+7nfY7vVT6dQwUJ+v0Jfq/lwIfhL2jMi91dQ3ln4hnlGXlxsDu/FNkeyHYgvYtyQy18ZX76IZy7X4diDbiQ==} '@vue/reactivity@3.5.35': resolution: {integrity: sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==} @@ -2594,8 +2542,8 @@ packages: eslint-import-resolver-node@0.3.10: resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==} - eslint-import-resolver-typescript@4.4.4: - resolution: {integrity: sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==} + eslint-import-resolver-typescript@4.4.5: + resolution: {integrity: sha512-nbE5XLph6TLtGYcu/U6e6ZVXyKBhbDWK5cLGk76eJ7NdZpwf1P9EFkpt1Z01mNZNrrilsAYWKH6zUkL4reoXbw==} engines: {node: ^16.17.0 || >=18.6.0} peerDependencies: eslint: '*' @@ -2737,8 +2685,8 @@ packages: peerDependencies: eslint: '>=9.38.0' - eslint-plugin-vue-scoped-css@3.1.0: - resolution: {integrity: sha512-R9XLrIZaP6QGz9b4kO2K4+lP4NcO2TKcw71zBtIYCoqqTk5ja1ySruYAllBT2LPIJVQ4NZaB2IFSvLjLEpYqQA==} + eslint-plugin-vue-scoped-css@3.1.1: + resolution: {integrity: sha512-GIskMvLPnDtiu88rWXQHy2b2QZ4j959N5UgghML64jH0sg3Km+HRa9m7nkpcEBGLD4iA4vtMDbBIoLdFcbT8lQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} peerDependencies: eslint: '>=9.38.0' @@ -2790,8 +2738,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.4.0: - resolution: {integrity: sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==} + eslint@10.4.1: + resolution: {integrity: sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -3411,6 +3359,10 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + js-yaml@4.2.0: + resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} + hasBin: true + jsbn@0.1.1: resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} @@ -4238,13 +4190,13 @@ packages: robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} - rolldown-license-plugin@3.0.8: - resolution: {integrity: sha512-q4nvGtimBIxValBXzkri+0jP2Sdf6PLztYykn7vCu0nVkJEzGsVRfcH/1X6qm0S8//4/gFt/XxwJqpaM6uSdJA==} + rolldown-license-plugin@3.0.9: + resolution: {integrity: sha512-40u0paM+f049toEj+/q8PlIQXbgkwPSqORtRJihoXr/v1V4amhVSi2uWOZSWyVp1+V/vkTyAV+Ib/fA+DeO3Ag==} peerDependencies: rolldown: '*' - rolldown@1.0.2: - resolution: {integrity: sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==} + rolldown@1.0.3: + resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -4688,8 +4640,8 @@ packages: resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} engines: {node: '>= 0.4'} - typescript-eslint@8.60.0: - resolution: {integrity: sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw==} + typescript-eslint@8.60.1: + resolution: {integrity: sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -4742,8 +4694,8 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - updates@17.17.2: - resolution: {integrity: sha512-gOwGrBYBvHVS+OiaUFRuilrmf/P8eYVmKUrkq7W3fvYUgpBsNKgfZo/CPhLKBa7xEFiEuMwMxTkiKsd9mn8pEw==} + updates@17.17.3: + resolution: {integrity: sha512-ZIhWarBUBmKG65d0AeOOMlZFonGWn6Ntol4/epga/xbQymEOh/2s07U+1UGM94y9JEPbk4CjowYgEo3F76ZxYA==} engines: {node: '>=22'} hasBin: true @@ -4781,8 +4733,8 @@ packages: peerDependencies: vite: '*' - vite@8.0.14: - resolution: {integrity: sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==} + vite@8.0.16: + resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -4824,20 +4776,20 @@ packages: yaml: optional: true - vitest@4.1.7: - resolution: {integrity: sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==} + vitest@4.1.8: + resolution: {integrity: sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.7 - '@vitest/browser-preview': 4.1.7 - '@vitest/browser-webdriverio': 4.1.7 - '@vitest/coverage-istanbul': 4.1.7 - '@vitest/coverage-v8': 4.1.7 - '@vitest/ui': 4.1.7 + '@vitest/browser-playwright': 4.1.8 + '@vitest/browser-preview': 4.1.8 + '@vitest/browser-webdriverio': 4.1.8 + '@vitest/coverage-istanbul': 4.1.8 + '@vitest/coverage-v8': 4.1.8 + '@vitest/ui': 4.1.8 happy-dom: '*' jsdom: '*' vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -4883,8 +4835,8 @@ packages: peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - vue-tsc@3.3.2: - resolution: {integrity: sha512-n7nQoA3YWW/eiDR8jMiv/uJvlg0uLGs+YgUrsTrf9EZaYSt3tuvMZb5V8+7Mvh/EH5pnY/hoVdgfjH+XcK+wwA==} + vue-tsc@3.3.3: + resolution: {integrity: sha512-SWUEG7YRUeDJHT7Xsuhf02elYX2gxPzzAII7OxDAh4KNOr4QHQ0Lls0YfnaO5GNd560CwVa2HTfdqmA5MqvRqQ==} hasBin: true peerDependencies: typescript: '>=5.0.0' @@ -5094,7 +5046,7 @@ snapshots: '@citation-js/plugin-yaml@0.6.2': dependencies: - js-yaml: 4.1.1 + js-yaml: 4.2.0 '@citation-js/plugin-zenodo@0.6.2': dependencies: @@ -5478,24 +5430,24 @@ snapshots: '@esbuild/win32-x64@0.28.0': optional: true - '@eslint-community/eslint-plugin-eslint-comments@4.7.2(eslint@10.4.0(jiti@2.7.0))': + '@eslint-community/eslint-plugin-eslint-comments@4.7.2(eslint@10.4.1(jiti@2.7.0))': dependencies: escape-string-regexp: 4.0.0 - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) ignore: 7.0.5 - '@eslint-community/eslint-utils@4.9.1(eslint@10.4.0(jiti@2.7.0))': + '@eslint-community/eslint-utils@4.9.1(eslint@10.4.1(jiti@2.7.0))': dependencies: - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/compat@1.4.1(eslint@10.4.0(jiti@2.7.0))': + '@eslint/compat@1.4.1(eslint@10.4.1(jiti@2.7.0))': dependencies: '@eslint/core': 0.17.0 optionalDependencies: - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) '@eslint/config-array@0.23.5': dependencies: @@ -5525,7 +5477,7 @@ snapshots: globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.1 + js-yaml: 4.2.0 minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -5780,7 +5732,7 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@oxc-project/types@0.132.0': {} + '@oxc-project/types@0.133.0': {} '@package-json/types@0.0.12': {} @@ -5792,7 +5744,7 @@ snapshots: '@popperjs/core@2.11.8': {} - '@primer/octicons@19.27.0': + '@primer/octicons@19.28.0': dependencies: object-assign: 4.1.1 @@ -5838,53 +5790,53 @@ snapshots: '@resvg/resvg-wasm@2.6.2': {} - '@rolldown/binding-android-arm64@1.0.2': + '@rolldown/binding-android-arm64@1.0.3': optional: true - '@rolldown/binding-darwin-arm64@1.0.2': + '@rolldown/binding-darwin-arm64@1.0.3': optional: true - '@rolldown/binding-darwin-x64@1.0.2': + '@rolldown/binding-darwin-x64@1.0.3': optional: true - '@rolldown/binding-freebsd-x64@1.0.2': + '@rolldown/binding-freebsd-x64@1.0.3': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.2': + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.2': + '@rolldown/binding-linux-arm64-gnu@1.0.3': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.2': + '@rolldown/binding-linux-arm64-musl@1.0.3': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.2': + '@rolldown/binding-linux-ppc64-gnu@1.0.3': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.2': + '@rolldown/binding-linux-s390x-gnu@1.0.3': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.2': + '@rolldown/binding-linux-x64-gnu@1.0.3': optional: true - '@rolldown/binding-linux-x64-musl@1.0.2': + '@rolldown/binding-linux-x64-musl@1.0.3': optional: true - '@rolldown/binding-openharmony-arm64@1.0.2': + '@rolldown/binding-openharmony-arm64@1.0.3': optional: true - '@rolldown/binding-wasm32-wasi@1.0.2': + '@rolldown/binding-wasm32-wasi@1.0.3': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.2': + '@rolldown/binding-win32-arm64-msvc@1.0.3': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.2': + '@rolldown/binding-win32-x64-msvc@1.0.3': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -5925,11 +5877,11 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@stylistic/eslint-plugin@5.10.0(eslint@10.4.0(jiti@2.7.0))': + '@stylistic/eslint-plugin@5.10.0(eslint@10.4.1(jiti@2.7.0))': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) '@typescript-eslint/types': 8.60.1 - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 @@ -6166,15 +6118,15 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.60.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.60.0 - '@typescript-eslint/type-utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.60.0 - eslint: 10.4.0(jiti@2.7.0) + '@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/type-utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.60.1 + eslint: 10.4.1(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@5.9.3) @@ -6182,15 +6134,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.60.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.60.0 - '@typescript-eslint/type-utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.60.0 - eslint: 10.4.0(jiti@2.7.0) + '@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/type-utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.60.1 + eslint: 10.4.1(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -6198,77 +6150,26 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.60.1 - '@typescript-eslint/type-utils': 8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/utils': 8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/typescript-estree': 8.60.1(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.60.1 - eslint: 10.4.0(jiti@2.7.0) - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@5.9.3) + debug: 4.4.3 + eslint: 10.4.1(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.60.1 - '@typescript-eslint/type-utils': 8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/utils': 8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/typescript-estree': 8.60.1(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.60.1 - eslint: 10.4.0(jiti@2.7.0) - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@6.0.3) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - optional: true - - '@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.60.0 - '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.60.0 - debug: 4.4.3 - eslint: 10.4.0(jiti@2.7.0) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.60.0 - '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/typescript-estree': 8.60.0(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.60.0 - debug: 4.4.3 - eslint: 10.4.0(jiti@2.7.0) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/project-service@8.60.0(typescript@5.9.3)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@5.9.3) - '@typescript-eslint/types': 8.60.0 - debug: 4.4.3 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/project-service@8.60.0(typescript@6.0.3)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@6.0.3) - '@typescript-eslint/types': 8.60.0 debug: 4.4.3 + eslint: 10.4.1(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -6291,24 +6192,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.60.0': - dependencies: - '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/visitor-keys': 8.60.0 - '@typescript-eslint/scope-manager@8.60.1': dependencies: '@typescript-eslint/types': 8.60.1 '@typescript-eslint/visitor-keys': 8.60.1 - '@typescript-eslint/tsconfig-utils@8.60.0(typescript@5.9.3)': - dependencies: - typescript: 5.9.3 - - '@typescript-eslint/tsconfig-utils@8.60.0(typescript@6.0.3)': - dependencies: - typescript: 6.0.3 - '@typescript-eslint/tsconfig-utils@8.60.1(typescript@5.9.3)': dependencies: typescript: 5.9.3 @@ -6317,89 +6205,32 @@ snapshots: dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': - dependencies: - '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) - debug: 4.4.3 - eslint: 10.4.0(jiti@2.7.0) - ts-api-utils: 2.5.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/type-utils@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': - dependencies: - '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/typescript-estree': 8.60.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) - debug: 4.4.3 - eslint: 10.4.0(jiti@2.7.0) - ts-api-utils: 2.5.0(typescript@6.0.3) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/type-utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.60.1 '@typescript-eslint/typescript-estree': 8.60.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) debug: 4.4.3 - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.60.1 '@typescript-eslint/typescript-estree': 8.60.1(typescript@6.0.3) - '@typescript-eslint/utils': 8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) debug: 4.4.3 - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - optional: true - - '@typescript-eslint/types@8.60.0': {} '@typescript-eslint/types@8.60.1': {} - '@typescript-eslint/typescript-estree@8.60.0(typescript@5.9.3)': - dependencies: - '@typescript-eslint/project-service': 8.60.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@5.9.3) - '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/visitor-keys': 8.60.0 - debug: 4.4.3 - minimatch: 10.2.5 - semver: 7.8.1 - tinyglobby: 0.2.17 - ts-api-utils: 2.5.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/typescript-estree@8.60.0(typescript@6.0.3)': - dependencies: - '@typescript-eslint/project-service': 8.60.0(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@6.0.3) - '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/visitor-keys': 8.60.0 - debug: 4.4.3 - minimatch: 10.2.5 - semver: 7.8.1 - tinyglobby: 0.2.17 - ts-api-utils: 2.5.0(typescript@6.0.3) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/typescript-estree@8.60.1(typescript@5.9.3)': dependencies: '@typescript-eslint/project-service': 8.60.1(typescript@5.9.3) @@ -6430,55 +6261,28 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) - '@typescript-eslint/scope-manager': 8.60.0 - '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) - eslint: 10.4.0(jiti@2.7.0) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) - '@typescript-eslint/scope-manager': 8.60.0 - '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/typescript-estree': 8.60.0(typescript@6.0.3) - eslint: 10.4.0(jiti@2.7.0) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.60.1 '@typescript-eslint/types': 8.60.1 '@typescript-eslint/typescript-estree': 8.60.1(typescript@5.9.3) - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.60.1 '@typescript-eslint/types': 8.60.1 '@typescript-eslint/typescript-estree': 8.60.1(typescript@6.0.3) - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.60.0': - dependencies: - '@typescript-eslint/types': 8.60.0 - eslint-visitor-keys: 5.0.1 - '@typescript-eslint/visitor-keys@8.60.1': dependencies: '@typescript-eslint/types': 8.60.1 @@ -6559,62 +6363,62 @@ snapshots: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) - '@vitejs/plugin-vue@6.0.7(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))(vue@3.5.35(typescript@6.0.3))': + '@vitejs/plugin-vue@6.0.7(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))(vue@3.5.35(typescript@6.0.3))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) + vite: 8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) vue: 3.5.35(typescript@6.0.3) - '@vitest/eslint-plugin@1.6.18(@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.7(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)))': + '@vitest/eslint-plugin@1.6.19(@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.8(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)))': dependencies: '@typescript-eslint/scope-manager': 8.60.1 - '@typescript-eslint/utils': 8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) - eslint: 10.4.0(jiti@2.7.0) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.4.1(jiti@2.7.0) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.60.1(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) typescript: 6.0.3 - vitest: 4.1.7(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)) + vitest: 4.1.8(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)) transitivePeerDependencies: - supports-color - '@vitest/expect@4.1.7': + '@vitest/expect@4.1.8': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.7 - '@vitest/utils': 4.1.7 + '@vitest/spy': 4.1.8 + '@vitest/utils': 4.1.8 chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.7(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))': + '@vitest/mocker@4.1.8(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0))': dependencies: - '@vitest/spy': 4.1.7 + '@vitest/spy': 4.1.8 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) + vite: 8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) - '@vitest/pretty-format@4.1.7': + '@vitest/pretty-format@4.1.8': dependencies: tinyrainbow: 3.1.0 - '@vitest/runner@4.1.7': + '@vitest/runner@4.1.8': dependencies: - '@vitest/utils': 4.1.7 + '@vitest/utils': 4.1.8 pathe: 2.0.3 - '@vitest/snapshot@4.1.7': + '@vitest/snapshot@4.1.8': dependencies: - '@vitest/pretty-format': 4.1.7 - '@vitest/utils': 4.1.7 + '@vitest/pretty-format': 4.1.8 + '@vitest/utils': 4.1.8 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.7': {} + '@vitest/spy@4.1.8': {} - '@vitest/utils@4.1.7': + '@vitest/utils@4.1.8': dependencies: - '@vitest/pretty-format': 4.1.7 + '@vitest/pretty-format': 4.1.8 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 @@ -6660,7 +6464,7 @@ snapshots: '@vue/compiler-dom': 3.5.35 '@vue/shared': 3.5.35 - '@vue/language-core@3.3.2': + '@vue/language-core@3.3.3': dependencies: '@volar/language-core': 2.4.28 '@vue/compiler-dom': 3.5.35 @@ -7080,7 +6884,7 @@ snapshots: dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.1.1 + js-yaml: 4.2.0 parse-json: 5.2.0 optionalDependencies: typescript: 6.0.3 @@ -7613,9 +7417,9 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)): + eslint-config-prettier@10.1.8(eslint@10.4.1(jiti@2.7.0)): dependencies: - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) eslint-import-context@0.1.9(unrs-resolver@1.12.2): dependencies: @@ -7632,10 +7436,10 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.0(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.0(jiti@2.7.0)): + eslint-import-resolver-typescript@4.4.5(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.1(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.1(jiti@2.7.0)): dependencies: debug: 4.4.3 - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) eslint-import-context: 0.1.9(unrs-resolver@1.12.2) get-tsconfig: 4.14.0 is-bun-module: 2.0.0 @@ -7643,92 +7447,104 @@ snapshots: tinyglobby: 0.2.17 unrs-resolver: 1.12.2 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)) - eslint-plugin-import-x: 4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.0(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)) + eslint-plugin-import-x: 4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.1(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.13.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)): + eslint-module-utils@2.13.0(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) - eslint: 10.4.0(jiti@2.7.0) + '@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) + eslint: 10.4.1(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.0(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.0(jiti@2.7.0)) + eslint-import-resolver-typescript: 4.4.5(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.1(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.1(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-plugin-array-func@5.1.1(eslint@10.4.0(jiti@2.7.0)): + eslint-module-utils@2.13.0(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)): dependencies: - eslint: 10.4.0(jiti@2.7.0) + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.4.1(jiti@2.7.0) + eslint-import-resolver-node: 0.3.10 + eslint-import-resolver-typescript: 4.4.5(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.1(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.1(jiti@2.7.0)) + transitivePeerDependencies: + - supports-color + optional: true - eslint-plugin-de-morgan@2.1.2(eslint@10.4.0(jiti@2.7.0)): + eslint-plugin-array-func@5.1.1(eslint@10.4.1(jiti@2.7.0)): dependencies: - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) - eslint-plugin-escompat@3.11.4(eslint@10.4.0(jiti@2.7.0)): + eslint-plugin-de-morgan@2.1.2(eslint@10.4.1(jiti@2.7.0)): + dependencies: + eslint: 10.4.1(jiti@2.7.0) + + eslint-plugin-escompat@3.11.4(eslint@10.4.1(jiti@2.7.0)): dependencies: browserslist: 4.28.2 - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) - eslint-plugin-eslint-comments@3.2.0(eslint@10.4.0(jiti@2.7.0)): + eslint-plugin-eslint-comments@3.2.0(eslint@10.4.1(jiti@2.7.0)): dependencies: escape-string-regexp: 1.0.5 - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) ignore: 5.3.2 - eslint-plugin-filenames@1.3.2(eslint@10.4.0(jiti@2.7.0)): + eslint-plugin-filenames@1.3.2(eslint@10.4.1(jiti@2.7.0)): dependencies: - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) lodash.camelcase: 4.3.0 lodash.kebabcase: 4.1.1 lodash.snakecase: 4.1.1 lodash.upperfirst: 4.3.1 - eslint-plugin-github@6.0.0(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)): + eslint-plugin-github@6.0.0(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)): dependencies: - '@eslint/compat': 1.4.1(eslint@10.4.0(jiti@2.7.0)) + '@eslint/compat': 1.4.1(eslint@10.4.1(jiti@2.7.0)) '@eslint/eslintrc': 3.3.5 '@eslint/js': 9.39.4 '@github/browserslist-config': 1.0.0 - '@typescript-eslint/eslint-plugin': 8.60.1(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) aria-query: 5.3.2 - eslint: 10.4.0(jiti@2.7.0) - eslint-config-prettier: 10.1.8(eslint@10.4.0(jiti@2.7.0)) - eslint-plugin-escompat: 3.11.4(eslint@10.4.0(jiti@2.7.0)) - eslint-plugin-eslint-comments: 3.2.0(eslint@10.4.0(jiti@2.7.0)) - eslint-plugin-filenames: 1.3.2(eslint@10.4.0(jiti@2.7.0)) - eslint-plugin-i18n-text: 1.0.1(eslint@10.4.0(jiti@2.7.0)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)) - eslint-plugin-jsx-a11y: 6.10.2(eslint@10.4.0(jiti@2.7.0)) + eslint: 10.4.1(jiti@2.7.0) + eslint-config-prettier: 10.1.8(eslint@10.4.1(jiti@2.7.0)) + eslint-plugin-escompat: 3.11.4(eslint@10.4.1(jiti@2.7.0)) + eslint-plugin-eslint-comments: 3.2.0(eslint@10.4.1(jiti@2.7.0)) + eslint-plugin-filenames: 1.3.2(eslint@10.4.1(jiti@2.7.0)) + eslint-plugin-i18n-text: 1.0.1(eslint@10.4.1(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@10.4.1(jiti@2.7.0)) eslint-plugin-no-only-tests: 3.4.0 - eslint-plugin-prettier: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)))(eslint@10.4.0(jiti@2.7.0))(prettier@3.8.3) + eslint-plugin-prettier: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.4.1(jiti@2.7.0)))(eslint@10.4.1(jiti@2.7.0))(prettier@3.8.3) eslint-rule-documentation: 1.0.23 globals: 16.5.0 jsx-ast-utils: 3.3.5 prettier: 3.8.3 svg-element-attributes: 1.3.1 typescript: 5.9.3 - typescript-eslint: 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + typescript-eslint: 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) transitivePeerDependencies: - '@types/eslint' - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-i18n-text@1.0.1(eslint@10.4.0(jiti@2.7.0)): + eslint-plugin-i18n-text@1.0.1(eslint@10.4.1(jiti@2.7.0)): dependencies: - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) - eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.0(jiti@2.7.0)): + eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.1(jiti@2.7.0)): dependencies: '@package-json/types': 0.0.12 '@typescript-eslint/types': 8.60.1 comment-parser: 1.4.7 debug: 4.4.3 - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) eslint-import-context: 0.1.9(unrs-resolver@1.12.2) is-glob: 4.0.3 minimatch: 10.2.5 @@ -7736,12 +7552,12 @@ snapshots: stable-hash-x: 0.2.0 unrs-resolver: 1.12.2 optionalDependencies: - '@typescript-eslint/utils': 8.60.1(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) eslint-import-resolver-node: 0.3.10 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -7750,9 +7566,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)) + eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)) hasown: 2.0.4 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -7764,13 +7580,43 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@10.4.0(jiti@2.7.0)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 10.4.1(jiti@2.7.0) + eslint-import-resolver-node: 0.3.10 + eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)) + hasown: 2.0.4 + is-core-module: 2.16.2 + is-glob: 4.0.3 + minimatch: 3.1.5 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + optional: true + + eslint-plugin-jsx-a11y@6.10.2(eslint@10.4.1(jiti@2.7.0)): dependencies: aria-query: 5.3.2 array-includes: 3.1.9 @@ -7780,7 +7626,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) hasown: 2.0.4 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -7791,37 +7637,37 @@ snapshots: eslint-plugin-no-only-tests@3.4.0: {} - eslint-plugin-playwright@2.10.4(eslint@10.4.0(jiti@2.7.0)): + eslint-plugin-playwright@2.10.4(eslint@10.4.1(jiti@2.7.0)): dependencies: - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) globals: 17.6.0 - eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)))(eslint@10.4.0(jiti@2.7.0))(prettier@3.8.3): + eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@10.4.1(jiti@2.7.0)))(eslint@10.4.1(jiti@2.7.0))(prettier@3.8.3): dependencies: - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) prettier: 3.8.3 prettier-linter-helpers: 1.0.1 synckit: 0.11.13 optionalDependencies: - eslint-config-prettier: 10.1.8(eslint@10.4.0(jiti@2.7.0)) + eslint-config-prettier: 10.1.8(eslint@10.4.1(jiti@2.7.0)) - eslint-plugin-regexp@3.1.0(eslint@10.4.0(jiti@2.7.0)): + eslint-plugin-regexp@3.1.0(eslint@10.4.1(jiti@2.7.0)): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 comment-parser: 1.4.7 - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) jsdoc-type-pratt-parser: 7.2.0 refa: 0.12.1 regexp-ast-analysis: 0.7.1 scslre: 0.3.0 - eslint-plugin-sonarjs@4.0.3(eslint@10.4.0(jiti@2.7.0)): + eslint-plugin-sonarjs@4.0.3(eslint@10.4.1(jiti@2.7.0)): dependencies: '@eslint-community/regexpp': 4.12.2 builtin-modules: 3.3.0 bytes: 3.1.2 - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) functional-red-black-tree: 1.0.1 globals: 17.6.0 jsx-ast-utils-x: 0.1.0 @@ -7832,15 +7678,15 @@ snapshots: ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 - eslint-plugin-unicorn@64.0.0(eslint@10.4.0(jiti@2.7.0)): + eslint-plugin-unicorn@64.0.0(eslint@10.4.1(jiti@2.7.0)): dependencies: '@babel/helper-validator-identifier': 7.29.7 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) change-case: 5.4.4 ci-info: 4.4.0 clean-regexp: 1.0.0 core-js-compat: 3.49.0 - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) find-up-simple: 1.0.1 globals: 17.6.0 indent-string: 5.0.0 @@ -7852,33 +7698,33 @@ snapshots: semver: 7.8.1 strip-indent: 4.1.1 - eslint-plugin-vue-scoped-css@3.1.0(eslint@10.4.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.0(jiti@2.7.0))): + eslint-plugin-vue-scoped-css@3.1.1(eslint@10.4.1(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.1(jiti@2.7.0))): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) es-toolkit: 1.47.0 - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) postcss: 8.5.15 postcss-safe-parser: 7.0.1(postcss@8.5.15) postcss-selector-parser: 7.1.1 - vue-eslint-parser: 10.4.0(eslint@10.4.0(jiti@2.7.0)) + vue-eslint-parser: 10.4.0(eslint@10.4.1(jiti@2.7.0)) - eslint-plugin-vue@10.9.1(@stylistic/eslint-plugin@5.10.0(eslint@10.4.0(jiti@2.7.0)))(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.0(jiti@2.7.0))): + eslint-plugin-vue@10.9.1(@stylistic/eslint-plugin@5.10.0(eslint@10.4.1(jiti@2.7.0)))(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.1(jiti@2.7.0))): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) - eslint: 10.4.0(jiti@2.7.0) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) + eslint: 10.4.1(jiti@2.7.0) natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 7.1.1 semver: 7.8.1 - vue-eslint-parser: 10.4.0(eslint@10.4.0(jiti@2.7.0)) + vue-eslint-parser: 10.4.0(eslint@10.4.1(jiti@2.7.0)) xml-name-validator: 4.0.0 optionalDependencies: - '@stylistic/eslint-plugin': 5.10.0(eslint@10.4.0(jiti@2.7.0)) - '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) + '@stylistic/eslint-plugin': 5.10.0(eslint@10.4.1(jiti@2.7.0)) + '@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) - eslint-plugin-wc@3.1.0(eslint@10.4.0(jiti@2.7.0)): + eslint-plugin-wc@3.1.0(eslint@10.4.1(jiti@2.7.0)): dependencies: - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) is-valid-element-name: 1.0.0 js-levenshtein-esm: 2.0.0 @@ -7897,9 +7743,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.4.0(jiti@2.7.0): + eslint@10.4.1(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 '@eslint/config-helpers': 0.6.0 @@ -8571,6 +8417,10 @@ snapshots: dependencies: argparse: 2.0.1 + js-yaml@4.2.0: + dependencies: + argparse: 2.0.1 + jsbn@0.1.1: {} jsdoc-type-pratt-parser@7.2.0: {} @@ -9490,30 +9340,30 @@ snapshots: robust-predicates@3.0.3: {} - rolldown-license-plugin@3.0.8(rolldown@1.0.2): + rolldown-license-plugin@3.0.9(rolldown@1.0.3): dependencies: - rolldown: 1.0.2 + rolldown: 1.0.3 - rolldown@1.0.2: + rolldown@1.0.3: dependencies: - '@oxc-project/types': 0.132.0 + '@oxc-project/types': 0.133.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.2 - '@rolldown/binding-darwin-arm64': 1.0.2 - '@rolldown/binding-darwin-x64': 1.0.2 - '@rolldown/binding-freebsd-x64': 1.0.2 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.2 - '@rolldown/binding-linux-arm64-gnu': 1.0.2 - '@rolldown/binding-linux-arm64-musl': 1.0.2 - '@rolldown/binding-linux-ppc64-gnu': 1.0.2 - '@rolldown/binding-linux-s390x-gnu': 1.0.2 - '@rolldown/binding-linux-x64-gnu': 1.0.2 - '@rolldown/binding-linux-x64-musl': 1.0.2 - '@rolldown/binding-openharmony-arm64': 1.0.2 - '@rolldown/binding-wasm32-wasi': 1.0.2 - '@rolldown/binding-win32-arm64-msvc': 1.0.2 - '@rolldown/binding-win32-x64-msvc': 1.0.2 + '@rolldown/binding-android-arm64': 1.0.3 + '@rolldown/binding-darwin-arm64': 1.0.3 + '@rolldown/binding-darwin-x64': 1.0.3 + '@rolldown/binding-freebsd-x64': 1.0.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.3 + '@rolldown/binding-linux-arm64-musl': 1.0.3 + '@rolldown/binding-linux-ppc64-gnu': 1.0.3 + '@rolldown/binding-linux-s390x-gnu': 1.0.3 + '@rolldown/binding-linux-x64-gnu': 1.0.3 + '@rolldown/binding-linux-x64-musl': 1.0.3 + '@rolldown/binding-openharmony-arm64': 1.0.3 + '@rolldown/binding-wasm32-wasi': 1.0.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.3 + '@rolldown/binding-win32-x64-msvc': 1.0.3 roughjs@4.6.6: dependencies: @@ -10060,24 +9910,24 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3): + typescript-eslint@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.60.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) - eslint: 10.4.0(jiti@2.7.0) + '@typescript-eslint/eslint-plugin': 8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.60.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) + eslint: 10.4.1(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - typescript-eslint@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3): + typescript-eslint@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.60.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.60.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.3) - eslint: 10.4.0(jiti@2.7.0) + '@typescript-eslint/eslint-plugin': 8.60.1(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.60.1(typescript@6.0.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.4.1(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -10140,7 +9990,7 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - updates@17.17.2: {} + updates@17.17.3: {} uri-js@4.4.1: dependencies: @@ -10169,16 +10019,16 @@ snapshots: core-util-is: 1.0.2 extsprintf: 1.3.0 - vite-string-plugin@2.0.4(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)): + vite-string-plugin@2.0.4(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)): dependencies: - vite: 8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) + vite: 8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) - vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0): + vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 postcss: 8.5.15 - rolldown: 1.0.2 + rolldown: 1.0.3 tinyglobby: 0.2.17 optionalDependencies: '@types/node': 25.9.1 @@ -10186,15 +10036,15 @@ snapshots: fsevents: 2.3.3 jiti: 2.7.0 - vitest@4.1.7(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)): + vitest@4.1.8(@types/node@25.9.1)(happy-dom@20.9.0)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)): dependencies: - '@vitest/expect': 4.1.7 - '@vitest/mocker': 4.1.7(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)) - '@vitest/pretty-format': 4.1.7 - '@vitest/runner': 4.1.7 - '@vitest/snapshot': 4.1.7 - '@vitest/spy': 4.1.7 - '@vitest/utils': 4.1.7 + '@vitest/expect': 4.1.8 + '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)) + '@vitest/pretty-format': 4.1.8 + '@vitest/runner': 4.1.8 + '@vitest/snapshot': 4.1.8 + '@vitest/spy': 4.1.8 + '@vitest/utils': 4.1.8 es-module-lexer: 2.1.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -10206,7 +10056,7 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) + vite: 8.0.16(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.9.1 @@ -10228,10 +10078,10 @@ snapshots: chart.js: 4.5.1 vue: 3.5.35(typescript@6.0.3) - vue-eslint-parser@10.4.0(eslint@10.4.0(jiti@2.7.0)): + vue-eslint-parser@10.4.0(eslint@10.4.1(jiti@2.7.0)): dependencies: debug: 4.4.3 - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.1(jiti@2.7.0) eslint-scope: 9.1.2 eslint-visitor-keys: 5.0.1 espree: 11.2.0 @@ -10240,10 +10090,10 @@ snapshots: transitivePeerDependencies: - supports-color - vue-tsc@3.3.2(typescript@6.0.3): + vue-tsc@3.3.3(typescript@6.0.3): dependencies: '@volar/typescript': 2.4.28 - '@vue/language-core': 3.3.2 + '@vue/language-core': 3.3.3 typescript: 6.0.3 vue@3.5.35(typescript@6.0.3): diff --git a/public/assets/img/svg/octicon-vscode.svg b/public/assets/img/svg/octicon-vscode.svg index 04ac8cacd51..81e0f7cbb07 100644 --- a/public/assets/img/svg/octicon-vscode.svg +++ b/public/assets/img/svg/octicon-vscode.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 60f66a9bfdb60414ca8470b9b9a77fe56e1f2b83 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Mon, 8 Jun 2026 00:39:06 -0600 Subject: [PATCH 28/40] enhance(actions): improve reusable workflow `uses` handling and cancellation (#37991) Follow up #37478 ## Changes 1. #37478 doesn't support absolute URL in `uses`. This PR provides partial support for URL-style reusable workflow references. A reusable workflow can now be referenced by an absolute URL, as long as it points to the local Gitea instance: ```yaml jobs: call: uses: https://your-gitea.example.com/OWNER/REPO/.gitea/workflows/ci.yaml@v1 ``` 2. Show an error message in the UI for invalid `uses`. image 3. Fix reusable caller cancellation issue. A reusable caller's status is aggregated from its children, so cancellation should processes a caller's descendants deepest-first. --------- Signed-off-by: Zettat123 Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: bircni Co-authored-by: Giteabot --- models/actions/run_job.go | 21 ++++--- models/actions/run_job_test.go | 66 ++++++++++++++++++++++ options/locale/locale_en-US.json | 1 + routers/web/repo/actions/actions.go | 45 ++++++++++----- services/actions/reusable_workflow.go | 26 ++++++++- services/actions/reusable_workflow_test.go | 44 +++++++++++++++ 6 files changed, 179 insertions(+), 24 deletions(-) diff --git a/models/actions/run_job.go b/models/actions/run_job.go index caf66ca451c..df01546fd84 100644 --- a/models/actions/run_job.go +++ b/models/actions/run_job.go @@ -4,6 +4,7 @@ package actions import ( + "cmp" "context" "fmt" "slices" @@ -671,18 +672,18 @@ func cancelOneJob(ctx context.Context, job *ActionRunJob) (*ActionRunJob, error) func cancelReusableCaller(ctx context.Context, caller *ActionRunJob) ([]*ActionRunJob, error) { cancelledJobs := make([]*ActionRunJob, 0) - if c, err := cancelOneJob(ctx, caller); err != nil { - return cancelledJobs, err - } else if c != nil { - cancelledJobs = append(cancelledJobs, c) - } - attemptJobs, err := GetRunJobsByRunAndAttemptID(ctx, caller.RunID, caller.RunAttemptID) if err != nil { return cancelledJobs, err } - for _, c := range CollectAllDescendantJobs(caller, attemptJobs) { + // Cancel descendants deepest-first, then the caller: a caller's status is aggregated from its children, + // so each child must reach its final state before its parent caller is re-aggregated. + // A child's ID always exceeds its parent's, so descending ID is a valid deepest-first order. + descendants := CollectAllDescendantJobs(caller, attemptJobs) + slices.SortFunc(descendants, func(a, b *ActionRunJob) int { return cmp.Compare(b.ID, a.ID) }) + + for _, c := range descendants { cancelled, err := cancelOneJob(ctx, c) if err != nil { return cancelledJobs, err @@ -691,5 +692,11 @@ func cancelReusableCaller(ctx context.Context, caller *ActionRunJob) ([]*ActionR cancelledJobs = append(cancelledJobs, cancelled) } } + + if c, err := cancelOneJob(ctx, caller); err != nil { + return cancelledJobs, err + } else if c != nil { + cancelledJobs = append(cancelledJobs, c) + } return cancelledJobs, nil } diff --git a/models/actions/run_job_test.go b/models/actions/run_job_test.go index a9e07ce0cf0..4437b5906df 100644 --- a/models/actions/run_job_test.go +++ b/models/actions/run_job_test.go @@ -131,3 +131,69 @@ func TestGetPriorAttemptChildrenByParent(t *testing.T) { assertAttempt1Children(t, out) }) } + +// A reusable caller subtree with a Blocked descendant (e.g. a nested caller stuck on an invalid `uses:`) must aggregate to Cancelled, when the run is cancelled. +func TestCancelJobs_NestedBlockedReusableCaller(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + ctx := t.Context() + + run := &ActionRun{ + Title: "cancel-nested-caller", + RepoID: 4, + Index: 9701, + OwnerID: 1, + WorkflowID: "caller.yaml", + TriggerUserID: 1, + Ref: "refs/heads/master", + CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0", + Event: "push", + TriggerEvent: "push", + EventPayload: "{}", + Status: StatusBlocked, + } + require.NoError(t, db.Insert(ctx, run)) + + attempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1, Status: StatusBlocked} + require.NoError(t, db.Insert(ctx, attempt)) + run.LatestAttemptID = attempt.ID + require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id")) + + newJob := func(name string, attemptJobID, parentID int64, callUses string) *ActionRunJob { + job := &ActionRunJob{ + RunID: run.ID, + RunAttemptID: attempt.ID, + RepoID: run.RepoID, + OwnerID: run.OwnerID, + CommitSHA: run.CommitSHA, + Name: name, + JobID: name, + Attempt: 1, + Status: StatusBlocked, + AttemptJobID: attemptJobID, + IsReusableCaller: true, + CallUses: callUses, + ParentJobID: parentID, + } + require.NoError(t, db.Insert(ctx, job)) + return job + } + + // outer: a valid top-level caller that expanded; inner: a nested caller stuck Blocked (invalid uses, never expands). + outer := newJob("outer", 1, 0, "./.gitea/workflows/lib.yml") + inner := newJob("inner", 2, outer.ID, "https://other.example.com/o/r/.gitea/workflows/ci.yml@v1") + + // Cancel all jobs of the attempt, ordered by id (parent before child). + jobs, err := GetRunJobsByRunAndAttemptID(ctx, run.ID, attempt.ID) + require.NoError(t, err) + _, err = CancelJobs(ctx, jobs) + require.NoError(t, err) + + for _, j := range []*ActionRunJob{outer, inner} { + got := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: j.ID}) + assert.Equal(t, StatusCancelled, got.Status, "job %q should be cancelled", j.JobID) + } + gotAttempt := unittest.AssertExistsAndLoadBean(t, &ActionRunAttempt{ID: attempt.ID}) + assert.Equal(t, StatusCancelled, gotAttempt.Status, "attempt must aggregate to Cancelled") + gotRun := unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID}) + assert.Equal(t, StatusCancelled, gotRun.Status, "run must aggregate to Cancelled, not stay Blocked") +} diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 51a97977421..9595baebed9 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -3774,6 +3774,7 @@ "actions.runs.no_matching_online_runner_helper": "No matching online runner with label: %s", "actions.runs.no_job_without_needs": "The workflow must contain at least one job without dependencies.", "actions.runs.no_job": "The workflow must contain at least one job", + "actions.runs.invalid_reusable_workflow_uses": "Invalid reusable workflow \"uses\": %s", "actions.runs.actor": "Actor", "actions.runs.status": "Status", "actions.runs.actors_no_select": "All actors", diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index 5ae0d14a6fc..9c5e1664de0 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -27,6 +27,7 @@ import ( "gitea.dev/modules/templates" "gitea.dev/modules/util" shared_user "gitea.dev/routers/web/shared/user" + actions_service "gitea.dev/services/actions" "gitea.dev/services/context" "gitea.dev/services/convert" @@ -208,12 +209,20 @@ func prepareWorkflowTemplate(ctx *context.Context, commit *git.Commit) (workflow if !hasJobWithoutNeeds && len(j.Needs()) == 0 { hasJobWithoutNeeds = true } + if j.Uses != "" { + if _, err := actions_service.ResolveUses(ctx, j.Uses); err != nil { + workflow.ErrMsg = ctx.Locale.TrString("actions.runs.invalid_reusable_workflow_uses", err.Error()) + break + } + } } - if !hasJobWithoutNeeds { - workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job_without_needs") - } - if emptyJobsNumber == len(wf.Jobs) { - workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job") + if workflow.ErrMsg == "" { + if !hasJobWithoutNeeds { + workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job_without_needs") + } + if emptyJobsNumber == len(wf.Jobs) { + workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job") + } } workflows = append(workflows, workflow) } @@ -352,7 +361,7 @@ func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo, otherWo return } for _, run := range runs { - if !run.Status.In(actions_model.StatusWaiting, actions_model.StatusRunning) { + if !run.Status.In(actions_model.StatusWaiting, actions_model.StatusRunning, actions_model.StatusBlocked) { continue } jobs, err := actions_model.GetLatestAttemptJobsByRepoAndRunID(ctx, run.RepoID, run.ID) @@ -361,23 +370,31 @@ func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo, otherWo return } for _, job := range jobs { - if !job.Status.IsWaiting() { + if !job.Status.In(actions_model.StatusWaiting, actions_model.StatusBlocked) { continue } if err := actions.ValidateWorkflowContent(job.WorkflowPayload); err != nil { runErrors[run.ID] = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error()) break } - hasOnlineRunner := false - for _, runner := range runners { - if !runner.IsDisabled && runner.CanMatchLabels(job.RunsOn) { - hasOnlineRunner = true + if job.CallUses != "" { + if _, err := actions_service.ResolveUses(ctx, job.CallUses); err != nil { + runErrors[run.ID] = ctx.Locale.TrString("actions.runs.invalid_reusable_workflow_uses", err.Error()) break } } - if !hasOnlineRunner { - runErrors[run.ID] = ctx.Locale.TrString("actions.runs.no_matching_online_runner_helper", strings.Join(job.RunsOn, ",")) - break + if job.Status.IsWaiting() { + hasOnlineRunner := false + for _, runner := range runners { + if !runner.IsDisabled && runner.CanMatchLabels(job.RunsOn) { + hasOnlineRunner = true + break + } + } + if !hasOnlineRunner { + runErrors[run.ID] = ctx.Locale.TrString("actions.runs.no_matching_online_runner_helper", strings.Join(job.RunsOn, ",")) + break + } } } } diff --git a/services/actions/reusable_workflow.go b/services/actions/reusable_workflow.go index 9b5ecdef6f4..65a6acfbd03 100644 --- a/services/actions/reusable_workflow.go +++ b/services/actions/reusable_workflow.go @@ -6,6 +6,7 @@ package actions import ( "context" "fmt" + "strings" actions_model "gitea.dev/models/actions" "gitea.dev/models/db" @@ -15,7 +16,9 @@ import ( "gitea.dev/modules/actions/jobparser" "gitea.dev/modules/container" "gitea.dev/modules/gitrepo" + "gitea.dev/modules/httplib" "gitea.dev/modules/json" + "gitea.dev/modules/setting" api "gitea.dev/modules/structs" "gitea.dev/modules/util" "gitea.dev/services/convert" @@ -149,10 +152,10 @@ func expandReusableWorkflowCaller(ctx context.Context, run *actions_model.Action return fmt.Errorf("parse caller job %d: %w", caller.ID, err) } - // 3. Load called-workflow source. - ref, err := jobparser.ParseUses(parsedJob.Uses) + // 3. Resolve `uses` and load called-workflow source. + ref, err := ResolveUses(ctx, parsedJob.Uses) if err != nil { - return fmt.Errorf("parse uses %q: %w", parsedJob.Uses, err) + return fmt.Errorf("resolve uses %q: %w", parsedJob.Uses, err) } content, contentSourceRepoID, contentSourceCommitSHA, err := loadReusableWorkflowSource(ctx, run, caller, ref) if err != nil { @@ -340,3 +343,20 @@ func insertCallerChildren(ctx context.Context, run *actions_model.ActionRun, att } return nil } + +// ResolveUses normalizes and parses a reusable workflow `uses:` value. +// It first rewrites an absolute URL pointing to this instance into the cross-repo form (rejecting external URLs), +// then validates the syntax via jobparser.ParseUses. +func ResolveUses(ctx context.Context, uses string) (*jobparser.UsesRef, error) { + // Rewrite a local-instance URL to the equivalent cross-repo form "owner/repo/.gitea/workflows/file.yml@ref". + if strings.HasPrefix(uses, "http://") || strings.HasPrefix(uses, "https://") { + // ParseGiteaSiteURL returns nil for URLs that do not belong to this instance. + gsu := httplib.ParseGiteaSiteURL(ctx, uses) + if gsu == nil { + return nil, fmt.Errorf("unsupported reusable workflow URL %q: an absolute URL must point to this Gitea instance (%s)", uses, setting.AppURL) + } + // RoutePath is the instance-relative path (AppSubURL already stripped), e.g. "/owner/repo/.gitea/workflows/file.yml@ref". + uses = strings.TrimPrefix(gsu.RoutePath, "/") + } + return jobparser.ParseUses(uses) +} diff --git a/services/actions/reusable_workflow_test.go b/services/actions/reusable_workflow_test.go index cadb26a851b..a7bb41ba8ae 100644 --- a/services/actions/reusable_workflow_test.go +++ b/services/actions/reusable_workflow_test.go @@ -10,6 +10,9 @@ import ( actions_model "gitea.dev/models/actions" "gitea.dev/models/db" "gitea.dev/models/unittest" + "gitea.dev/modules/actions/jobparser" + "gitea.dev/modules/setting" + "gitea.dev/modules/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -132,3 +135,44 @@ func buildCallerChain(t *testing.T, callerUses ...string) []*actions_model.Actio } return jobs } + +func TestResolveUses(t *testing.T) { + defer test.MockVariableValue(&setting.AppURL, "https://gitea.example.com/sub/")() + defer test.MockVariableValue(&setting.AppSubURL, "/sub")() + ctx := t.Context() + + t.Run("LocalForms", func(t *testing.T) { + // Same-repo and cross-repo forms are not URLs and are parsed as-is. + ref, err := ResolveUses(ctx, "./.gitea/workflows/build.yml") + require.NoError(t, err) + assert.Equal(t, jobparser.UsesRef{Kind: jobparser.UsesKindLocalSameRepo, Path: ".gitea/workflows/build.yml"}, *ref) + + ref, err = ResolveUses(ctx, "owner/repo/.gitea/workflows/build.yml@v1") + require.NoError(t, err) + assert.Equal(t, jobparser.UsesRef{Kind: jobparser.UsesKindLocalCrossRepo, Owner: "owner", Repo: "repo", Path: ".gitea/workflows/build.yml", Ref: "v1"}, *ref) + }) + + t.Run("LocalInstanceURL", func(t *testing.T) { + // An absolute URL on this instance (incl. AppSubURL) resolves to the equivalent cross-repo ref. + ref, err := ResolveUses(ctx, "https://gitea.example.com/sub/owner/repo/.gitea/workflows/ci.yml@refs/heads/main") + require.NoError(t, err) + assert.Equal(t, jobparser.UsesRef{Kind: jobparser.UsesKindLocalCrossRepo, Owner: "owner", Repo: "repo", Path: ".gitea/workflows/ci.yml", Ref: "refs/heads/main"}, *ref) + }) + + t.Run("InvalidSyntax", func(t *testing.T) { + for _, in := range []string{ + "owner/.gitea/workflows/foo.yml", // missing repo segment + "owner/repo/.gitea/workflows/foo.yml", // missing @ref + "https://gitea.example.com/sub/repo/.gitea/workflows/ci.yml@refs/heads/main", // local absolute URL but missing owner + "not a valid uses at all", + } { + _, err := ResolveUses(ctx, in) + require.Error(t, err, "in = %s", in) + } + }) + + t.Run("ForeignURL", func(t *testing.T) { + _, err := ResolveUses(ctx, "https://other.gitea-example.com/owner/repo/.gitea/workflows/ci.yaml@v1") + assert.ErrorContains(t, err, "must point to this Gitea instance") + }) +} From 136f7d18aa0ac9fd16aa05bd6fd95589076f7d58 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 8 Jun 2026 16:58:42 +0800 Subject: [PATCH 29/40] fix: api error message (#38031) Fix various abuses and mistakes --- routers/api/v1/api.go | 8 ++-- routers/api/v1/org/org.go | 4 +- routers/api/v1/repo/action.go | 24 ++++------- routers/api/v1/repo/actions_run.go | 15 +------ routers/api/v1/repo/branch.go | 6 +-- routers/api/v1/repo/commits.go | 2 +- routers/api/v1/repo/file.go | 25 ++++-------- routers/api/v1/repo/issue.go | 14 +------ routers/api/v1/repo/issue_comment.go | 18 ++------- routers/api/v1/repo/issue_dependency.go | 24 ++--------- routers/api/v1/repo/issue_lock.go | 12 +----- routers/api/v1/repo/issue_reaction.go | 12 +----- routers/api/v1/repo/issue_tracked_time.go | 38 ++++-------------- routers/api/v1/repo/notes.go | 6 +-- routers/api/v1/repo/pull.go | 6 +-- routers/api/v1/repo/pull_review.go | 44 +++++---------------- routers/api/v1/repo/release_attachment.go | 2 +- routers/api/v1/repo/wiki.go | 18 ++------- routers/api/v1/shared/block.go | 6 +-- routers/api/v1/shared/runners.go | 6 +-- routers/api/v1/user/gpg_key.go | 5 +-- routers/api/v1/user/helper.go | 2 +- routers/api/v1/user/key.go | 5 +-- routers/api/v1/user/user.go | 2 +- services/context/api.go | 24 ++--------- services/wiki/wiki.go | 4 +- tests/integration/api_user_org_perm_test.go | 4 +- 27 files changed, 80 insertions(+), 256 deletions(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 4248faea5d4..618181eb1c1 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -151,7 +151,7 @@ func repoAssignment() func(ctx *context.APIContext) { if redirectUserID, err := user_model.LookupUserRedirect(ctx, userName); err == nil { context.RedirectToUser(ctx.Base, ctx.Doer, userName, redirectUserID) } else if user_model.IsErrUserRedirectNotExist(err) { - ctx.APIErrorNotFound("GetUserByName", err) + ctx.APIErrorNotFound() } else { ctx.APIErrorInternal(err) } @@ -626,7 +626,7 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) { if err == nil { context.RedirectToUser(ctx.Base, ctx.Doer, ctx.PathParam("org"), redirectUserID) } else if user_model.IsErrUserRedirectNotExist(err) { - ctx.APIErrorNotFound("GetOrgByName", err) + ctx.APIErrorNotFound() } else { ctx.APIErrorInternal(err) } @@ -862,12 +862,12 @@ func individualPermsChecker(ctx *context.APIContext) { switch ctx.ContextUser.Visibility { case api.VisibleTypePrivate: if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) { - ctx.APIErrorNotFound("Visit Project", nil) + ctx.APIErrorNotFound() return } case api.VisibleTypeLimited: if ctx.Doer == nil { - ctx.APIErrorNotFound("Visit Project", nil) + ctx.APIErrorNotFound() return } } diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go index 16d3e230a01..8f4d19719fd 100644 --- a/routers/api/v1/org/org.go +++ b/routers/api/v1/org/org.go @@ -146,7 +146,7 @@ func GetUserOrgsPermissions(ctx *context.APIContext) { op := api.OrganizationPermissions{} if !organization.HasOrgOrUserVisible(ctx, o, ctx.Doer) { - ctx.APIErrorNotFound("HasOrgOrUserVisible", nil) + ctx.APIErrorNotFound() return } @@ -312,7 +312,7 @@ func Get(ctx *context.APIContext) { // "$ref": "#/responses/notFound" if !organization.HasOrgOrUserVisible(ctx, ctx.Org.Organization.AsUser(), ctx.Doer) { - ctx.APIErrorNotFound("HasOrgOrUserVisible", nil) + ctx.APIErrorNotFound() return } diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go index 5fc2e97d7a3..60077474120 100644 --- a/routers/api/v1/repo/action.go +++ b/routers/api/v1/repo/action.go @@ -1164,11 +1164,8 @@ func ActionsEnableWorkflow(ctx *context.APIContext) { func getCurrentRepoActionRunByID(ctx *context.APIContext) *actions_model.ActionRun { runID := ctx.PathParamInt64("run") run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID) - if errors.Is(err, util.ErrNotExist) { - ctx.APIErrorNotFound(err) - return nil - } else if err != nil { - ctx.APIErrorInternal(err) + if err != nil { + ctx.APIErrorAuto(err) return nil } run.Repo = ctx.Repo.Repository @@ -1198,11 +1195,8 @@ func getCurrentRepoActionRunAttemptByNumber(ctx *context.APIContext) (*actions_m attemptNum := ctx.PathParamInt64("attempt") attempt, err := actions_model.GetRunAttemptByRunIDAndAttemptNum(ctx, run.ID, attemptNum) - if errors.Is(err, util.ErrNotExist) { - ctx.APIErrorNotFound(err) - return nil, nil - } else if err != nil { - ctx.APIErrorInternal(err) + if err != nil { + ctx.APIErrorAuto(err) return nil, nil } return run, attempt @@ -1454,7 +1448,7 @@ func RerunWorkflowJob(ctx *context.APIContext) { jobID := ctx.PathParamInt64("job_id") jobIdx := slices.IndexFunc(jobs, func(job *actions_model.ActionRunJob) bool { return job.ID == jobID }) if jobIdx == -1 { - ctx.APIErrorNotFound(util.NewNotExistErrorf("workflow job with id %d", jobID)) + ctx.APIErrorNotFound("workflow job not found") return } @@ -1566,11 +1560,7 @@ func ListWorkflowRunJobs(ctx *context.APIContext) { run, err := actions_model.GetRunByRepoAndID(ctx, repoID, runID) if err != nil { - if errors.Is(err, util.ErrNotExist) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } // runID is used as an additional filter next to repoID to ensure that we only list jobs for the specified repoID and runID. @@ -1674,7 +1664,7 @@ func GetWorkflowJob(ctx *context.APIContext) { } if !has || job.RepoID != ctx.Repo.Repository.ID { - ctx.APIErrorNotFound(util.ErrNotExist) + ctx.APIErrorNotFound() return } diff --git a/routers/api/v1/repo/actions_run.go b/routers/api/v1/repo/actions_run.go index d1d98ff21ab..1765ed564d6 100644 --- a/routers/api/v1/repo/actions_run.go +++ b/routers/api/v1/repo/actions_run.go @@ -4,10 +4,7 @@ package repo import ( - "errors" - actions_model "gitea.dev/models/actions" - "gitea.dev/modules/util" "gitea.dev/routers/common" "gitea.dev/services/context" ) @@ -45,11 +42,7 @@ func DownloadActionsRunJobLogs(ctx *context.APIContext) { jobID := ctx.PathParamInt64("job_id") curJob, err := actions_model.GetRunJobByRepoAndID(ctx, ctx.Repo.Repository.ID, jobID) if err != nil { - if errors.Is(err, util.ErrNotExist) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } if err = curJob.LoadRepo(ctx); err != nil { @@ -59,10 +52,6 @@ func DownloadActionsRunJobLogs(ctx *context.APIContext) { err = common.DownloadActionsRunJobLogs(ctx.Base, ctx.Repo.Repository, curJob) if err != nil { - if errors.Is(err, util.ErrNotExist) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) } } diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index 3b6575d6763..0806858b4da 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -64,7 +64,7 @@ func GetBranch(ctx *context.APIContext) { ctx.APIErrorInternal(err) return } else if !exist { - ctx.APIErrorNotFound(err) + ctx.APIErrorNotFound() return } @@ -153,7 +153,7 @@ func DeleteBranch(ctx *context.APIContext) { if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil { switch { case git.IsErrBranchNotExist(err): - ctx.APIErrorNotFound(err) + ctx.APIErrorNotFound() case errors.Is(err, repo_service.ErrBranchIsDefault): ctx.APIError(http.StatusForbidden, "can not delete default or pull request target branch") case errors.Is(err, git_model.ErrBranchIsProtected): @@ -446,7 +446,7 @@ func UpdateBranch(ctx *context.APIContext) { if err := repo_service.UpdateBranch(ctx, repo, ctx.Repo.GitRepo, ctx.Doer, branchName, opt.NewCommitID, opt.OldCommitID, opt.Force); err != nil { switch { case git_model.IsErrBranchNotExist(err): - ctx.APIErrorNotFound(err) + ctx.APIErrorNotFound() case errors.Is(err, util.ErrInvalidArgument): ctx.APIError(http.StatusUnprocessableEntity, err.Error()) case git.IsErrPushRejected(err): diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go index d83686083e5..6f8aaefb6d9 100644 --- a/routers/api/v1/repo/commits.go +++ b/routers/api/v1/repo/commits.go @@ -258,7 +258,7 @@ func GetAllCommits(ctx *context.APIContext) { ctx.APIErrorInternal(err) return } else if commitsCountTotal == 0 { - ctx.APIErrorNotFound("FileCommitsCount", nil) + ctx.APIErrorNotFound() return } diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index 244d9393ce6..5f10c4fbd71 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -213,7 +213,7 @@ func getBlobForEntry(ctx *context.APIContext) (blob *git.Blob, entry *git.TreeEn } if entry.IsDir() || entry.IsSubModule() { - ctx.APIErrorNotFound("getBlobForEntry", nil) + ctx.APIErrorNotFound() return nil, nil, nil } @@ -301,18 +301,14 @@ func GetEditorconfig(ctx *context.APIContext) { ec, _, err := ctx.Repo.GetEditorconfig(ctx.Repo.Commit) if err != nil { - if git.IsErrNotExist(err) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } fileName := ctx.PathParam("filename") def, err := ec.GetDefinitionForFilename(fileName) - if def == nil { - ctx.APIErrorNotFound(err) + if err != nil { + ctx.APIErrorNotFound(err.Error()) return } ctx.JSON(http.StatusOK, def) @@ -699,10 +695,8 @@ func DeleteFile(ctx *context.APIContext) { func resolveRefCommit(ctx *context.APIContext, ref string, minCommitIDLen ...int) *utils.RefCommit { ref = util.IfZero(ref, ctx.Repo.Repository.DefaultBranch) refCommit, err := utils.ResolveRefCommit(ctx, ctx.Repo.Repository, ref, minCommitIDLen...) - if errors.Is(err, util.ErrNotExist) { - ctx.APIErrorNotFound(err) - } else if err != nil { - ctx.APIErrorInternal(err) + if err != nil { + ctx.APIErrorAuto(err) } return refCommit } @@ -828,11 +822,8 @@ func getRepoContents(ctx *context.APIContext, opts files_service.GetContentsOrLi } ret, err := files_service.GetContentsOrList(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, refCommit, opts) if err != nil { - if git.IsErrNotExist(err) { - ctx.APIErrorNotFound("GetContentsOrList", err) - return nil - } - ctx.APIErrorInternal(err) + ctx.APIErrorAuto(err) + return nil } return &ret } diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index a075c68f749..9946afc8b7f 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -540,16 +540,10 @@ func getUserIDForFilter(ctx *context.APIContext, queryName string) int64 { } user, err := user_model.GetUserByName(ctx, userName) - if user_model.IsErrUserNotExist(err) { - ctx.APIErrorNotFound(err) - return 0 - } - if err != nil { - ctx.APIErrorInternal(err) + ctx.APIErrorAuto(err) return 0 } - return user.ID } @@ -969,11 +963,7 @@ func DeleteIssue(ctx *context.APIContext) { // "$ref": "#/responses/notFound" issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { - if issues_model.IsErrIssueNotExist(err) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go index 02a0f702ce9..6cece7e5cee 100644 --- a/routers/api/v1/repo/issue_comment.go +++ b/routers/api/v1/repo/issue_comment.go @@ -447,11 +447,7 @@ func GetIssueComment(ctx *context.APIContext) { comment, err := issues_model.GetCommentWithRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id")) if err != nil { - if issues_model.IsErrCommentNotExist(err) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } @@ -572,11 +568,7 @@ func EditIssueCommentDeprecated(ctx *context.APIContext) { func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) { comment, err := issues_model.GetCommentWithRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id")) if err != nil { - if issues_model.IsErrCommentNotExist(err) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } @@ -681,11 +673,7 @@ func DeleteIssueCommentDeprecated(ctx *context.APIContext) { func deleteIssueComment(ctx *context.APIContext) { comment, err := issues_model.GetCommentWithRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id")) if err != nil { - if issues_model.IsErrCommentNotExist(err) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } diff --git a/routers/api/v1/repo/issue_dependency.go b/routers/api/v1/repo/issue_dependency.go index 4912737c911..ff4e7cd5b46 100644 --- a/routers/api/v1/repo/issue_dependency.go +++ b/routers/api/v1/repo/issue_dependency.go @@ -63,11 +63,7 @@ func GetIssueDependencies(ctx *context.APIContext) { issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { - if issues_model.IsErrIssueNotExist(err) { - ctx.APIErrorNotFound("IsErrIssueNotExist", err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } @@ -487,11 +483,7 @@ func RemoveIssueBlocking(ctx *context.APIContext) { func getParamsIssue(ctx *context.APIContext) *issues_model.Issue { issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { - if issues_model.IsErrIssueNotExist(err) { - ctx.APIErrorNotFound("IsErrIssueNotExist", err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return nil } issue.Repo = ctx.Repo.Repository @@ -508,11 +500,7 @@ func getFormIssue(ctx *context.APIContext, form *api.IssueMeta) *issues_model.Is var err error repo, err = repo_model.GetRepositoryByOwnerAndName(ctx, form.Owner, form.Name) if err != nil { - if repo_model.IsErrRepoNotExist(err) { - ctx.APIErrorNotFound("IsErrRepoNotExist", err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return nil } } else { @@ -521,11 +509,7 @@ func getFormIssue(ctx *context.APIContext, form *api.IssueMeta) *issues_model.Is issue, err := issues_model.GetIssueByIndex(ctx, repo.ID, form.Index) if err != nil { - if issues_model.IsErrIssueNotExist(err) { - ctx.APIErrorNotFound("IsErrIssueNotExist", err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return nil } issue.Repo = repo diff --git a/routers/api/v1/repo/issue_lock.go b/routers/api/v1/repo/issue_lock.go index 75247593fd3..2a4f75a9373 100644 --- a/routers/api/v1/repo/issue_lock.go +++ b/routers/api/v1/repo/issue_lock.go @@ -53,11 +53,7 @@ func LockIssue(ctx *context.APIContext) { reason := web.GetForm(ctx).(*api.LockIssueOption).Reason issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { - if issues_model.IsErrIssueNotExist(err) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } @@ -120,11 +116,7 @@ func UnlockIssue(ctx *context.APIContext) { issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { - if issues_model.IsErrIssueNotExist(err) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } diff --git a/routers/api/v1/repo/issue_reaction.go b/routers/api/v1/repo/issue_reaction.go index c9fa39e93d2..6ad44ead619 100644 --- a/routers/api/v1/repo/issue_reaction.go +++ b/routers/api/v1/repo/issue_reaction.go @@ -53,11 +53,7 @@ func GetIssueCommentReactions(ctx *context.APIContext) { comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id")) if err != nil { - if issues_model.IsErrCommentNotExist(err) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } @@ -190,11 +186,7 @@ func DeleteIssueCommentReaction(ctx *context.APIContext) { func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) { comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id")) if err != nil { - if issues_model.IsErrCommentNotExist(err) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go index 1af649bfd79..33af841fbd1 100644 --- a/routers/api/v1/repo/issue_tracked_time.go +++ b/routers/api/v1/repo/issue_tracked_time.go @@ -71,16 +71,12 @@ func ListTrackedTimes(ctx *context.APIContext) { // "$ref": "#/responses/notFound" if !ctx.Repo.Repository.IsTimetrackerEnabled(ctx) { - ctx.APIErrorNotFound("Timetracker is disabled") + ctx.APIErrorNotFound("timetracker is disabled") return } issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { - if issues_model.IsErrIssueNotExist(err) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } @@ -182,11 +178,7 @@ func AddTime(ctx *context.APIContext) { form := web.GetForm(ctx).(*api.AddTimeOption) issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { - if issues_model.IsErrIssueNotExist(err) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } @@ -265,11 +257,7 @@ func ResetIssueTime(ctx *context.APIContext) { issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { - if issues_model.IsErrIssueNotExist(err) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } @@ -338,11 +326,7 @@ func DeleteTime(ctx *context.APIContext) { issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { - if issues_model.IsErrIssueNotExist(err) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } @@ -357,11 +341,7 @@ func DeleteTime(ctx *context.APIContext) { time, err := issues_model.GetTrackedTimeByID(ctx, issue.ID, ctx.PathParamInt64("id")) if err != nil { - if db.IsErrNotExist(err) { - ctx.APIErrorNotFound(err) - return - } - ctx.APIErrorInternal(err) + ctx.APIErrorAuto(err) return } if time.Deleted { @@ -423,11 +403,7 @@ func ListTrackedTimesByUser(ctx *context.APIContext) { } user, err := user_model.GetUserByName(ctx, ctx.PathParam("timetrackingusername")) if err != nil { - if user_model.IsErrUserNotExist(err) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } if user == nil { diff --git a/routers/api/v1/repo/notes.go b/routers/api/v1/repo/notes.go index d8fea56c4cd..d2bd708aa48 100644 --- a/routers/api/v1/repo/notes.go +++ b/routers/api/v1/repo/notes.go @@ -68,11 +68,7 @@ func getNote(ctx *context.APIContext, identifier string) { commitID, err := ctx.Repo.GitRepo.ConvertToGitID(identifier) if err != nil { - if git.IsErrNotExist(err) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 54e7b78a1b4..dcfaa3d3ac8 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -927,11 +927,7 @@ func MergePullRequest(ctx *context.APIContext) { pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { - if issues_model.IsErrPullRequestNotExist(err) { - ctx.APIErrorNotFound("GetPullRequestByIndex", err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go index 54919cef90f..9778dc416ac 100644 --- a/routers/api/v1/repo/pull_review.go +++ b/routers/api/v1/repo/pull_review.go @@ -63,11 +63,7 @@ func ListPullReviews(ctx *context.APIContext) { pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { - if issues_model.IsErrPullRequestNotExist(err) { - ctx.APIErrorNotFound("GetPullRequestByIndex", err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } @@ -389,11 +385,7 @@ func updatePullReviewCommentResolve(ctx *context.APIContext, isResolve bool) { func getPullReviewCommentToResolve(ctx *context.APIContext) *issues_model.Comment { comment, err := issues_model.GetCommentWithRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id")) if err != nil { - if issues_model.IsErrCommentNotExist(err) { - ctx.APIErrorNotFound("GetCommentByID", err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return nil } @@ -510,11 +502,7 @@ func CreatePullReview(ctx *context.APIContext) { opts := web.GetForm(ctx).(*api.CreatePullReviewOptions) pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { - if issues_model.IsErrPullRequestNotExist(err) { - ctx.APIErrorNotFound("GetPullRequestByIndex", err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } @@ -737,33 +725,25 @@ func preparePullReviewType(ctx *context.APIContext, pr *issues_model.PullRequest func prepareSingleReview(ctx *context.APIContext) (*issues_model.Review, *issues_model.PullRequest, bool) { pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { - if issues_model.IsErrPullRequestNotExist(err) { - ctx.APIErrorNotFound("GetPullRequestByIndex", err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return nil, nil, true } review, err := issues_model.GetReviewByID(ctx, ctx.PathParamInt64("id")) if err != nil { - if issues_model.IsErrReviewNotExist(err) { - ctx.APIErrorNotFound("GetReviewByID", err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return nil, nil, true } // validate the review is for the given PR if review.IssueID != pr.IssueID { - ctx.APIErrorNotFound("ReviewNotInPR") + ctx.APIErrorNotFound() return nil, nil, true } // make sure that the user has access to this review if it is pending if review.Type == issues_model.ReviewTypePending && review.ReviewerID != ctx.Doer.ID && !ctx.Doer.IsAdmin { - ctx.APIErrorNotFound("GetReviewByID") + ctx.APIErrorNotFound() return nil, nil, true } @@ -870,7 +850,7 @@ func parseReviewersByNames(ctx *context.APIContext, reviewerNames, teamReviewerN if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.APIErrorNotFound("UserNotExist", fmt.Sprintf("User '%s' not exist", r)) + ctx.APIErrorNotFound("user doesn't exist: " + r) return nil, nil } ctx.APIErrorInternal(err) @@ -886,7 +866,7 @@ func parseReviewersByNames(ctx *context.APIContext, reviewerNames, teamReviewerN teamReviewer, err = organization.GetTeam(ctx, ctx.Repo.Owner.ID, t) if err != nil { if organization.IsErrTeamNotExist(err) { - ctx.APIErrorNotFound("TeamNotExist", fmt.Sprintf("Team '%s' not exist", t)) + ctx.APIErrorNotFound("team doesn't exist: " + t) return nil, nil } ctx.APIErrorInternal(err) @@ -902,11 +882,7 @@ func parseReviewersByNames(ctx *context.APIContext, reviewerNames, teamReviewerN func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions, isAdd bool) { pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index")) if err != nil { - if issues_model.IsErrPullRequestNotExist(err) { - ctx.APIErrorNotFound("GetPullRequestByIndex", err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return } diff --git a/routers/api/v1/repo/release_attachment.go b/routers/api/v1/repo/release_attachment.go index 715552d72c7..915ac725b06 100644 --- a/routers/api/v1/repo/release_attachment.go +++ b/routers/api/v1/repo/release_attachment.go @@ -205,7 +205,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) { // Check if attachments are enabled if !setting.Attachment.Enabled { - ctx.APIErrorNotFound("Attachment is not enabled") + ctx.APIErrorNotFound("attachment is not enabled") return } diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go index dad0bfbbce0..67209fa1271 100644 --- a/routers/api/v1/repo/wiki.go +++ b/routers/api/v1/repo/wiki.go @@ -245,11 +245,7 @@ func DeleteWikiPage(ctx *context.APIContext) { wikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("pageName")) if err := wiki_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName); err != nil { - if err.Error() == "file does not exist" { - ctx.APIErrorNotFound(err) - return - } - ctx.APIErrorInternal(err) + ctx.APIErrorAuto(err) return } @@ -474,21 +470,13 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit) { wikiRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository.WikiStorageRepo()) if err != nil { - if git.IsErrNotExist(err) || err.Error() == "no such file or directory" { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return nil, nil } commit, err := wikiRepo.GetBranchCommit(ctx.Repo.Repository.DefaultWikiBranch) if err != nil { - if git.IsErrNotExist(err) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return wikiRepo, nil } return wikiRepo, commit diff --git a/routers/api/v1/shared/block.go b/routers/api/v1/shared/block.go index 8b2a207ccb2..c2a5fe8a4ef 100644 --- a/routers/api/v1/shared/block.go +++ b/routers/api/v1/shared/block.go @@ -45,7 +45,7 @@ func ListBlocks(ctx *context.APIContext, blocker *user_model.User) { func CheckUserBlock(ctx *context.APIContext, blocker *user_model.User) { blockee, err := user_model.GetUserByName(ctx, ctx.PathParam("username")) if err != nil { - ctx.APIErrorNotFound("GetUserByName", err) + ctx.APIErrorAuto(err) return } @@ -62,7 +62,7 @@ func CheckUserBlock(ctx *context.APIContext, blocker *user_model.User) { func BlockUser(ctx *context.APIContext, blocker *user_model.User) { blockee, err := user_model.GetUserByName(ctx, ctx.PathParam("username")) if err != nil { - ctx.APIErrorNotFound("GetUserByName", err) + ctx.APIErrorAuto(err) return } @@ -81,7 +81,7 @@ func BlockUser(ctx *context.APIContext, blocker *user_model.User) { func UnblockUser(ctx *context.APIContext, doer, blocker *user_model.User) { blockee, err := user_model.GetUserByName(ctx, ctx.PathParam("username")) if err != nil { - ctx.APIErrorNotFound("GetUserByName", err) + ctx.APIErrorAuto(err) return } diff --git a/routers/api/v1/shared/runners.go b/routers/api/v1/shared/runners.go index 329f17736eb..fbb02627689 100644 --- a/routers/api/v1/shared/runners.go +++ b/routers/api/v1/shared/runners.go @@ -77,11 +77,7 @@ func getRunnerByID(ctx *context.APIContext, ownerID, repoID, runnerID int64) (*a runner, err := actions_model.GetRunnerByID(ctx, runnerID) if err != nil { - if errors.Is(err, util.ErrNotExist) { - ctx.APIErrorNotFound("Runner not found") - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorAuto(err) return nil, false } diff --git a/routers/api/v1/user/gpg_key.go b/routers/api/v1/user/gpg_key.go index 562e70b5c0b..0148c7f5dac 100644 --- a/routers/api/v1/user/gpg_key.go +++ b/routers/api/v1/user/gpg_key.go @@ -4,7 +4,6 @@ package user import ( - "errors" "net/http" "strings" @@ -135,7 +134,7 @@ func GetGPGKey(ctx *context.APIContext) { // CreateUserGPGKey creates new GPG key to given user by ID. func CreateUserGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption, uid int64) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { - ctx.APIErrorNotFound("Not Found", errors.New("gpg keys setting is not allowed to be visited")) + ctx.APIErrorNotFound("gpg keys setting is not allowed to be changed") return } @@ -276,7 +275,7 @@ func DeleteGPGKey(ctx *context.APIContext) { // "$ref": "#/responses/notFound" if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { - ctx.APIErrorNotFound("Not Found", errors.New("gpg keys setting is not allowed to be visited")) + ctx.APIErrorNotFound("gpg keys setting is not allowed to be changed") return } diff --git a/routers/api/v1/user/helper.go b/routers/api/v1/user/helper.go index ce051e2d168..ee7b8b17275 100644 --- a/routers/api/v1/user/helper.go +++ b/routers/api/v1/user/helper.go @@ -18,7 +18,7 @@ func GetUserByPathParam(ctx *context.APIContext, name string) *user_model.User { if redirectUserID, err2 := user_model.LookupUserRedirect(ctx, username); err2 == nil { context.RedirectToUser(ctx.Base, ctx.Doer, username, redirectUserID) } else { - ctx.APIErrorNotFound("GetUserByName", err) + ctx.APIErrorNotFound() } } else { ctx.APIErrorInternal(err) diff --git a/routers/api/v1/user/key.go b/routers/api/v1/user/key.go index 1a932c94af4..c12e98ca4c8 100644 --- a/routers/api/v1/user/key.go +++ b/routers/api/v1/user/key.go @@ -6,7 +6,6 @@ package user import ( std_ctx "context" - "errors" "net/http" asymkey_model "gitea.dev/models/asymkey" @@ -201,7 +200,7 @@ func GetPublicKey(ctx *context.APIContext) { // CreateUserPublicKey creates new public key to given user by ID. func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid int64) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { - ctx.APIErrorNotFound("Not Found", errors.New("ssh keys setting is not allowed to be visited")) + ctx.APIErrorNotFound("ssh keys setting is not allowed to be changed") return } @@ -271,7 +270,7 @@ func DeletePublicKey(ctx *context.APIContext) { // "$ref": "#/responses/notFound" if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { - ctx.APIErrorNotFound("Not Found", errors.New("ssh keys setting is not allowed to be visited")) + ctx.APIErrorNotFound("ssh keys setting is not allowed to be changed") return } diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go index d45ca11348d..8343e380773 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -117,7 +117,7 @@ func GetInfo(ctx *context.APIContext) { if !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) { // fake ErrUserNotExist error message to not leak information about existence - ctx.APIErrorNotFound("GetUserByName", user_model.ErrUserNotExist{Name: ctx.PathParam("username")}) + ctx.APIErrorNotFound() return } ctx.JSON(http.StatusOK, convert.ToUser(ctx, ctx.ContextUser, ctx.Doer)) diff --git a/services/context/api.go b/services/context/api.go index 7731b5692fc..02ec2b71384 100644 --- a/services/context/api.go +++ b/services/context/api.go @@ -138,26 +138,10 @@ func (ctx *APIContext) apiErrorInternal(skip int, err error) { } // APIErrorNotFound handles 404s for APIContext -// String will replace message, errors will be added to a slice -func (ctx *APIContext) APIErrorNotFound(objs ...any) { - var message string - var errs []string - for _, obj := range objs { - // Ignore nil - if obj == nil { - continue - } - - if err, ok := obj.(error); ok { - errs = append(errs, err.Error()) - } else { - message = obj.(string) - } - } - ctx.JSON(http.StatusNotFound, map[string]any{ - "message": util.IfZero(message, "not found"), // do not use locale in API - "url": setting.API.SwaggerURL, - "errors": errs, +func (ctx *APIContext) APIErrorNotFound(msg ...string) { + ctx.JSON(http.StatusNotFound, APIError{ + Message: util.OptionalArg(msg, "not found"), + URL: setting.API.SwaggerURL, }) } diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index abe846ae063..d1006abf7e8 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -7,7 +7,6 @@ package wiki import ( "context" "fmt" - "os" "gitea.dev/models/db" repo_model "gitea.dev/models/repo" @@ -21,6 +20,7 @@ import ( "gitea.dev/modules/graceful" "gitea.dev/modules/log" repo_module "gitea.dev/modules/repository" + "gitea.dev/modules/util" asymkey_service "gitea.dev/services/asymkey" repo_service "gitea.dev/services/repository" ) @@ -304,7 +304,7 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model return err } } else { - return os.ErrNotExist + return util.ErrNotExist } // FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here diff --git a/tests/integration/api_user_org_perm_test.go b/tests/integration/api_user_org_perm_test.go index d31abdfeb37..63d69fab717 100644 --- a/tests/integration/api_user_org_perm_test.go +++ b/tests/integration/api_user_org_perm_test.go @@ -151,9 +151,7 @@ func testUnknownOrganization(t *testing.T) { req := NewRequest(t, "GET", "/api/v1/users/user1/orgs/unknown/permissions"). AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusNotFound) - apiError := DecodeJSON(t, resp, &api.APIError{}) - assert.Equal(t, "GetUserByName", apiError.Message) + MakeRequest(t, req, http.StatusNotFound) } func testHiddenMemberPermissionsForbidden(t *testing.T) { From 2a84831400aedaaa7075225928a73a822940ec8e Mon Sep 17 00:00:00 2001 From: Giteabot Date: Mon, 8 Jun 2026 09:53:12 -0700 Subject: [PATCH 30/40] chore(deps): update astral-sh/setup-uv action to v8.2.0 (#38036) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [astral-sh/setup-uv](https://redirect.github.com/astral-sh/setup-uv) | action | minor | `v8.1.0` → `v8.2.0` | --- ### Release Notes
    astral-sh/setup-uv (astral-sh/setup-uv) ### [`v8.2.0`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v8.2.0): 🌈 New inputs `quiet` and `download-from-astral-mirror` [Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v8.1.0...v8.2.0) #### Changes This release brings two new inputs and a few bug fixes. ##### New inputs Lets talk about the new inputs first. ##### quiet Pretty simple. It turns of all `info` loggings. Useful if you use this in a composite action and are not interested in all the details. In the upcoming releases we will add log groups to fully implement support for "less noise" > \[!NOTE]\ > Warnings and errors are always logged. ##### download-from-astral-mirror In some cases you may want to directly use the fallback of checking for available versions and downloading releases from GitHub instead of using the astral.sh mirror. Setting `download-from-astral-mirror: false` allows you to do that. ##### Bugfixes When using the astral.sh mirror to query available versions and download releases (done by default) we now stop sending the GitHub token in the header. The mirror never looked at it but we shouldn't be handing out that data even if it is just a short lived token. All other bugfixes try to limit the impact of failed GitHub queries due to retries and other faults. We couldn't pinpoint all rootcauses yet but added more logging for error cases to track them down. #### 🐛 Bug fixes - fix: report unexpected cache save failures [@​eifinger](https://redirect.github.com/eifinger) ([#​896](https://redirect.github.com/astral-sh/setup-uv/issues/896)) - fix: report unexpected setup failures [@​eifinger](https://redirect.github.com/eifinger) ([#​895](https://redirect.github.com/astral-sh/setup-uv/issues/895)) - fix: add timeout to fetch to prevent silent hangs [@​eifinger-bot](https://redirect.github.com/eifinger-bot) ([#​883](https://redirect.github.com/astral-sh/setup-uv/issues/883)) - Limit GitHub tokens to github.com download URLs [@​zsol](https://redirect.github.com/zsol) ([#​878](https://redirect.github.com/astral-sh/setup-uv/issues/878)) - increase libuv-workaround timeout to 100ms [@​eifinger](https://redirect.github.com/eifinger) ([#​880](https://redirect.github.com/astral-sh/setup-uv/issues/880)) #### 🚀 Enhancements - Add quiet input to suppress info-level log output [@​eifinger](https://redirect.github.com/eifinger) ([#​898](https://redirect.github.com/astral-sh/setup-uv/issues/898)) - feat: add `download-from-astral-mirror` input [@​eifinger](https://redirect.github.com/eifinger) ([#​897](https://redirect.github.com/astral-sh/setup-uv/issues/897)) #### 🧰 Maintenance - docs: update dependabot rollup biome guidance [@​eifinger](https://redirect.github.com/eifinger) ([#​902](https://redirect.github.com/astral-sh/setup-uv/issues/902)) - chore: update known checksums for 0.11.18 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​899](https://redirect.github.com/astral-sh/setup-uv/issues/899)) - chore: update known checksums for 0.11.17 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​892](https://redirect.github.com/astral-sh/setup-uv/issues/892)) - chore: update known checksums for 0.11.16 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​889](https://redirect.github.com/astral-sh/setup-uv/issues/889)) - chore: update known checksums for 0.11.15 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​885](https://redirect.github.com/astral-sh/setup-uv/issues/885)) - chore: update known checksums for 0.11.14 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​879](https://redirect.github.com/astral-sh/setup-uv/issues/879)) - chore: update known checksums for 0.11.13 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​877](https://redirect.github.com/astral-sh/setup-uv/issues/877)) - chore: update known checksums for 0.11.12 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​876](https://redirect.github.com/astral-sh/setup-uv/issues/876)) - chore: update known checksums for 0.11.11 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​873](https://redirect.github.com/astral-sh/setup-uv/issues/873)) - chore: update known checksums for 0.11.9/0.11.10 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​871](https://redirect.github.com/astral-sh/setup-uv/issues/871)) - chore: update known checksums for 0.11.8 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​867](https://redirect.github.com/astral-sh/setup-uv/issues/867)) - Bump setup-uv references to v8.1.0 SHA in docs [@​eifinger](https://redirect.github.com/eifinger) ([#​862](https://redirect.github.com/astral-sh/setup-uv/issues/862)) - Add update-docs.yml workflow [@​eifinger](https://redirect.github.com/eifinger) ([#​861](https://redirect.github.com/astral-sh/setup-uv/issues/861)) #### ⬆️ Dependency updates - chore(deps): roll up dependabot updates [@​eifinger](https://redirect.github.com/eifinger) ([#​903](https://redirect.github.com/astral-sh/setup-uv/issues/903)) - chore(deps): roll up dependabot updates [@​eifinger](https://redirect.github.com/eifinger) ([#​901](https://redirect.github.com/astral-sh/setup-uv/issues/901)) - chore(deps): bump release-drafter/release-drafter from 7.3.0 to 7.3.1 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​900](https://redirect.github.com/astral-sh/setup-uv/issues/900)) - chore(deps): bump eifinger/actionlint-action from 1.10.1 to 1.10.2 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​842](https://redirect.github.com/astral-sh/setup-uv/issues/842)) - chore(deps): bump github/codeql-action from 4.35.4 to 4.36.0 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​893](https://redirect.github.com/astral-sh/setup-uv/issues/893)) - chore(deps): bump zizmorcore/zizmor-action from 0.5.5 to 0.5.6 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​891](https://redirect.github.com/astral-sh/setup-uv/issues/891)) - chore(deps): bump release-drafter/release-drafter from 7.2.0 to 7.3.0 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​884](https://redirect.github.com/astral-sh/setup-uv/issues/884)) - chore(deps): bump zizmorcore/zizmor-action from 0.5.3 to 0.5.5 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​888](https://redirect.github.com/astral-sh/setup-uv/issues/888)) - chore(deps): bump github/codeql-action from 4.35.3 to 4.35.4 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​881](https://redirect.github.com/astral-sh/setup-uv/issues/881)) - chore(deps): bump github/codeql-action from 4.32.2 to 4.35.3 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​875](https://redirect.github.com/astral-sh/setup-uv/issues/875)) - chore(deps): bump actions/setup-node from 6.3.0 to 6.4.0 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​866](https://redirect.github.com/astral-sh/setup-uv/issues/866)) - chore(deps): bump zizmorcore/zizmor-action from 0.5.2 to 0.5.3 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​864](https://redirect.github.com/astral-sh/setup-uv/issues/864)) - chore(deps): bump peter-evans/create-pull-request from 8.1.0 to 8.1.1 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​863](https://redirect.github.com/astral-sh/setup-uv/issues/863))
    --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Only on Monday (`* * * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://redirect.github.com/renovatebot/renovate). --- .github/workflows/pull-compliance.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml index 6c41b6b4c11..d8129fd5b7a 100644 --- a/.github/workflows/pull-compliance.yml +++ b/.github/workflows/pull-compliance.yml @@ -42,7 +42,7 @@ jobs: - run: make lint-spell - if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true' || needs.files-changed.outputs.actions == 'true' - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 with: python-version: 3.14 - if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true' From 54916f708e77e9cfe9d2e85164e436383f5faf9a Mon Sep 17 00:00:00 2001 From: bircni Date: Mon, 8 Jun 2026 19:16:22 +0200 Subject: [PATCH 31/40] feat: Add avatar stacks (#37594) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parse `Co-authored-by:` trailers from commit messages and surface contributors as an avatar stack across the commit page, commits list, PR commits tab, latest-commit row, blame, graph, and dashboard feed. - Up to 10 visible 20px avatars, GitHub-style overlap (6px first stride, 4px between subsequent), `+N` chip for the rest. - Label: 1 → name; 2 → ` and `; 3+ → ` people` opens a Tippy popup with all participants. - Names and avatars link to the repo's commits-by-author search; fall back to profile or `mailto:`. - Trailer parsing uses `net/mail.ParseAddress`, scans only the trailing paragraph, filters out the commit's own author/committer. - Drops the non-standard `Co-committed-by:` emission on squash merge and web edits. Devtest: `/devtest/coauthor-avatars`. Fixes #25521 ---- image --------- Co-authored-by: Claude Opus 4.7 Co-authored-by: silverwind Co-authored-by: wxiaoguang Co-authored-by: Giteabot --- models/asymkey/gpg_key_commit_verification.go | 5 +- models/gituser/avatar_stack.go | 44 ++++++ models/gituser/gituser.go | 64 ++++++++ models/user/user.go | 38 +---- modules/git/commit.go | 32 ---- modules/git/commit_message.go | 131 +++++++++++++++++ modules/git/commit_message_test.go | 80 ++++++++++ modules/git/commit_test.go | 9 -- modules/repository/commits.go | 27 +--- modules/repository/commits_test.go | 34 ----- modules/setting/config/value.go | 5 + modules/templates/util_render.go | 139 +++++++++++++++++- modules/templates/util_render_test.go | 53 +++++++ options/locale/locale_en-US.json | 7 +- routers/web/devtest/devtest.go | 84 +++++++++-- routers/web/repo/blame.go | 35 ++--- routers/web/repo/commit.go | 10 +- routers/web/repo/compare.go | 4 +- routers/web/repo/compare_test.go | 6 +- routers/web/repo/view.go | 6 +- routers/web/repo/wiki.go | 2 +- services/git/commit.go | 19 +-- services/issue/comments.go | 2 +- services/pull/merge_squash.go | 4 +- services/pull/pull.go | 2 +- services/repository/files/temp_repo.go | 7 +- services/repository/gitgraph/graph_models.go | 55 ++++--- templates/devtest/avatar-stack.tmpl | 18 +++ templates/devtest/badge-commit-sign.tmpl | 2 +- templates/repo/blame.tmpl | 2 +- templates/repo/commit_page.tmpl | 23 +++ templates/repo/commit_sign_badge.tmpl | 4 +- templates/repo/commits_list.tmpl | 50 +++---- templates/repo/commits_list_small.tmpl | 25 ++-- templates/repo/graph/commits.tmpl | 9 +- templates/repo/latest_commit.tmpl | 10 +- templates/user/dashboard/feeds.tmpl | 6 +- tests/integration/repo_commits_test.go | 16 +- web_src/css/avatar.css | 125 ++++++++++++++++ web_src/css/base.css | 8 - web_src/css/index.css | 1 + web_src/css/repo.css | 12 +- web_src/js/features/repo-commit.ts | 16 ++ web_src/js/index.ts | 3 +- 44 files changed, 912 insertions(+), 322 deletions(-) create mode 100644 models/gituser/avatar_stack.go create mode 100644 models/gituser/gituser.go create mode 100644 modules/git/commit_message.go create mode 100644 modules/git/commit_message_test.go create mode 100644 templates/devtest/avatar-stack.tmpl create mode 100644 web_src/css/avatar.css diff --git a/models/asymkey/gpg_key_commit_verification.go b/models/asymkey/gpg_key_commit_verification.go index 17244076dd2..251d8eff11c 100644 --- a/models/asymkey/gpg_key_commit_verification.go +++ b/models/asymkey/gpg_key_commit_verification.go @@ -8,6 +8,7 @@ import ( "fmt" "hash" + "gitea.dev/models/gituser" repo_model "gitea.dev/models/repo" user_model "gitea.dev/models/user" "gitea.dev/modules/log" @@ -32,8 +33,8 @@ type CommitVerification struct { // SignCommit represents a commit with validation of signature. type SignCommit struct { - Verification *CommitVerification - *user_model.UserCommit + Verification *CommitVerification + *gituser.UserCommit // TODO: need to use a explicit field name, avoid anonymous field } const ( diff --git a/models/gituser/avatar_stack.go b/models/gituser/avatar_stack.go new file mode 100644 index 00000000000..d38c94bc7bf --- /dev/null +++ b/models/gituser/avatar_stack.go @@ -0,0 +1,44 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gituser + +import ( + "context" + + "gitea.dev/models/user" + "gitea.dev/modules/git" + "gitea.dev/modules/log" +) + +// AvatarStackData is the view-model for the AvatarStack render helpers. Participants[0] is +// the primary participant (commit author), painted on top; the rest follow. +type AvatarStackData struct { + Participants []*CommitParticipant + SearchByEmailLink string +} + +func BuildAvatarStackData(ctx context.Context, allParticipants []*git.CommitIdentity, emailUserMap *user.EmailUserMap) *AvatarStackData { + if emailUserMap == nil { + emails := make([]string, len(allParticipants)) + for i, sig := range allParticipants { + emails[i] = sig.Email + } + var err error + emailUserMap, err = user.GetUsersByEmails(ctx, emails) + if err != nil { + log.Error("GetUsersByEmails failed: %v", err) + } + } + ret := &AvatarStackData{ + Participants: make([]*CommitParticipant, 0, len(allParticipants)), + } + for _, p := range allParticipants { + var giteaUser *user.User + if emailUserMap != nil { + giteaUser = emailUserMap.GetByEmail(p.Email) + } + ret.Participants = append(ret.Participants, &CommitParticipant{GiteaUser: giteaUser, GitIdentity: p}) + } + return ret +} diff --git a/models/gituser/gituser.go b/models/gituser/gituser.go new file mode 100644 index 00000000000..81a94a3a85b --- /dev/null +++ b/models/gituser/gituser.go @@ -0,0 +1,64 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gituser + +import ( + "context" + "net/url" + + "gitea.dev/models/user" + "gitea.dev/modules/container" + "gitea.dev/modules/git" +) + +// CommitParticipant is one participant of a commit (its author or a co-author): +// a git identity, optionally matched to a Gitea user. +type CommitParticipant struct { + GitIdentity *git.CommitIdentity // git identity (name/email), never nil + GiteaUser *user.User // matched Gitea user, nil if unmatched +} + +// UserCommit represents a commit with matched of database "author" user. +type UserCommit struct { + GitCommit *git.Commit + AuthorUser *user.User + AvatarStackData *AvatarStackData +} + +func RepoCommitSearchByEmailLink(repoLink string, ref git.RefName) string { + if curRefWebLinkPath := ref.RefWebLinkPath(); curRefWebLinkPath != "" { + return repoLink + "/commits/" + curRefWebLinkPath + "/search?q=" + url.QueryEscape("author:") + "{email}" + } + return "" +} + +// GetUserCommitsByGitCommits checks if authors' e-mails of commits are corresponding to users. +func GetUserCommitsByGitCommits(ctx context.Context, gitCommits []*git.Commit, repoLink string, currentRef git.RefName) ([]*UserCommit, error) { + userCommits := make([]*UserCommit, 0, len(gitCommits)) + emailSet := make(container.Set[string]) + for _, c := range gitCommits { + emailSet.Add(c.Author.Email) + emailSet.Add(c.Committer.Email) + for _, p := range c.AllParticipantIdentities() { + emailSet.Add(p.Email) + } + } + + emailUserMap, err := user.GetUsersByEmails(ctx, emailSet.Values()) + if err != nil { + return nil, err + } + + searchByEmailLink := RepoCommitSearchByEmailLink(repoLink, currentRef) + for _, c := range gitCommits { + uc := &UserCommit{ + AuthorUser: emailUserMap.GetByEmail(c.Author.Email), // FIXME: why GetUserCommitsByGitCommits uses "Author", but ParseCommitsWithSignature uses "Committer"? + GitCommit: c, + AvatarStackData: BuildAvatarStackData(ctx, c.AllParticipantIdentities(), emailUserMap), + } + uc.AvatarStackData.SearchByEmailLink = searchByEmailLink + userCommits = append(userCommits, uc) + } + return userCommits, nil +} diff --git a/models/user/user.go b/models/user/user.go index 66e8d49b420..4e6227de9e7 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -1148,14 +1148,7 @@ func GetUsersBySource(ctx context.Context, s *auth.Source) ([]*User, error) { return users, err } -// UserCommit represents a commit with validation of user. -type UserCommit struct { //revive:disable-line:exported - User *User - *git.Commit -} - -// ValidateCommitWithEmail check if author's e-mail of commit is corresponding to a user. -func ValidateCommitWithEmail(ctx context.Context, c *git.Commit) *User { +func GetUserByGitAuthor(ctx context.Context, c *git.Commit) *User { if c.Author == nil { return nil } @@ -1166,33 +1159,6 @@ func ValidateCommitWithEmail(ctx context.Context, c *git.Commit) *User { return u } -// ValidateCommitsWithEmails checks if authors' e-mails of commits are corresponding to users. -func ValidateCommitsWithEmails(ctx context.Context, oldCommits []*git.Commit) ([]*UserCommit, error) { - var ( - newCommits = make([]*UserCommit, 0, len(oldCommits)) - emailSet = make(container.Set[string]) - ) - for _, c := range oldCommits { - if c.Author != nil { - emailSet.Add(c.Author.Email) - } - } - - emailUserMap, err := GetUsersByEmails(ctx, emailSet.Values()) - if err != nil { - return nil, err - } - - for _, c := range oldCommits { - user := emailUserMap.GetByEmail(c.Author.Email) // FIXME: why ValidateCommitsWithEmails uses "Author", but ParseCommitsWithSignature uses "Committer"? - newCommits = append(newCommits, &UserCommit{ - User: user, - Commit: c, - }) - } - return newCommits, nil -} - type EmailUserMap struct { m map[string]*User } @@ -1203,7 +1169,7 @@ func (eum *EmailUserMap) GetByEmail(email string) *User { func GetUsersByEmails(ctx context.Context, emails []string) (*EmailUserMap, error) { if len(emails) == 0 { - return nil, nil //nolint:nilnil // return nil when there are no emails to look up + return &EmailUserMap{}, nil } needCheckEmails := make(container.Set[string]) diff --git a/modules/git/commit.go b/modules/git/commit.go index 0231770a293..21288ad8459 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -11,18 +11,10 @@ import ( "os/exec" "strings" - "gitea.dev/modules/charset" "gitea.dev/modules/git/gitcmd" "gitea.dev/modules/util" ) -type CommitMessage struct { - MessageRaw string - messageUTF8 *string - messageTitle *string - messageBody *string -} - // Commit represents a git commit. type Commit struct { Tree // FIXME: bad design, this field can be nil if the commit is from "last commit cache" @@ -44,30 +36,6 @@ type CommitSignature struct { Payload string } -func (c *CommitMessage) MessageUTF8() string { - if c.messageUTF8 == nil { - bs := charset.ToUTF8(util.UnsafeStringToBytes(c.MessageRaw), charset.ConvertOpts{ErrorReplacement: []byte{'?'}}) - c.messageUTF8 = new(util.UnsafeBytesToString(bs)) - } - return *c.messageUTF8 -} - -func (c *CommitMessage) MessageTitle() string { - if c.messageTitle == nil { - s, _, _ := strings.Cut(strings.TrimSpace(c.MessageUTF8()), "\n") - c.messageTitle = new(strings.TrimSpace(s)) - } - return *c.messageTitle -} - -func (c *CommitMessage) MessageBody() string { - if c.messageBody == nil { - _, s, _ := strings.Cut(strings.TrimSpace(c.MessageUTF8()), "\n") - c.messageBody = new(strings.TrimSpace(s)) - } - return *c.messageBody -} - // ParentID returns oid of n-th parent (0-based index). // It returns nil if no such parent exists. func (c *Commit) ParentID(n int) (ObjectID, error) { diff --git a/modules/git/commit_message.go b/modules/git/commit_message.go new file mode 100644 index 00000000000..8fd3601f0d0 --- /dev/null +++ b/modules/git/commit_message.go @@ -0,0 +1,131 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "net/mail" + "regexp" + "strings" + "sync" + + "gitea.dev/modules/charset" + "gitea.dev/modules/container" + "gitea.dev/modules/util" +) + +// CoAuthoredByTrailer is the canonical token for the `Co-authored-by:` git trailer. +const CoAuthoredByTrailer = "Co-authored-by" + +type CommitIdentity struct { + Name string + Email string +} + +// CommitMessageTrailerValues keys are all in lower-case +type CommitMessageTrailerValues map[string][]string + +type CommitMessage struct { + MessageRaw string + messageUTF8 *string + messageTitle *string + messageBody *string + + trailerValues CommitMessageTrailerValues + + allParticipants []*CommitIdentity +} + +func (c *CommitMessage) MessageUTF8() string { + if c.messageUTF8 == nil { + bs := charset.ToUTF8(util.UnsafeStringToBytes(c.MessageRaw), charset.ConvertOpts{ErrorReplacement: []byte{'?'}}) + c.messageUTF8 = new(util.UnsafeBytesToString(bs)) + } + return *c.messageUTF8 +} + +func (c *CommitMessage) MessageTitle() string { + if c.messageTitle == nil { + s, _, _ := strings.Cut(strings.TrimSpace(c.MessageUTF8()), "\n") + c.messageTitle = new(strings.TrimSpace(s)) + } + return *c.messageTitle +} + +func (c *CommitMessage) MessageBody() string { + if c.messageBody == nil { + _, s, _ := strings.Cut(strings.TrimSpace(c.MessageUTF8()), "\n") + c.messageBody = new(strings.TrimSpace(s)) + } + return *c.messageBody +} + +func (c *CommitMessage) MessageTrailer() CommitMessageTrailerValues { + if c.trailerValues == nil { + _, _, trailer := CommitMessageSplitTrailer(c.MessageUTF8()) + c.trailerValues = CommitMessageParseTrailer(trailer) + } + return c.trailerValues +} + +var commitMessageTrailerSplit = sync.OnceValue(func() *regexp.Regexp { + // the sep is either something like "\n---\n" or "\n\n" in the body, or at the start of the body like "---\n" + return regexp.MustCompile(`(?s)^(?P.*?)(?P^|^\n|^-{3,}\n|\n-{3,}\n|\n\n)(?P(?:[A-Za-z0-9][-A-Za-z0-9]*:[^\n]*\n?)*)$`) +}) + +func CommitMessageSplitTrailer(s string) (content, sep, trailer string) { + s = util.NormalizeStringEOL(s) + re := commitMessageTrailerSplit() + v := re.FindStringSubmatch(s) + if v == nil { + return s, "", "" + } + return v[re.SubexpIndex("content")], v[re.SubexpIndex("sep")], v[re.SubexpIndex("trailer")] +} + +func CommitMessageParseTrailer(s string) CommitMessageTrailerValues { + ret := CommitMessageTrailerValues{} + for line := range strings.SplitSeq(util.NormalizeStringEOL(s), "\n") { + k, v, ok := strings.Cut(line, ":") + if !ok { + continue + } + k, v = strings.TrimSpace(k), strings.TrimSpace(v) + kLower := strings.ToLower(k) + ret[kLower] = append(ret[kLower], v) + } + return ret +} + +// AllParticipantIdentities returns all the participants in the commit, the first one is the commit's author +func (c *Commit) AllParticipantIdentities() []*CommitIdentity { + if c.allParticipants != nil { + return c.allParticipants + } + + exclude := container.Set[string]{} + c.allParticipants = append(c.allParticipants, &CommitIdentity{Name: c.Author.Name, Email: c.Author.Email}) + exclude.Add(strings.ToLower(c.Author.Email)) + + addParticipant := func(name, email string) { + if name == "" && email == "" { + return + } + emailLower := strings.ToLower(email) + if emailLower != "" && exclude.Contains(emailLower) { + return + } + c.allParticipants = append(c.allParticipants, &CommitIdentity{Name: name, Email: email}) + exclude.Add(emailLower) + } + addParticipant(c.Committer.Name, c.Committer.Email) + for _, coAuthorValue := range c.MessageTrailer()["co-authored-by"] { + addr, err := mail.ParseAddress(coAuthorValue) + if err == nil { + addParticipant(addr.Name, addr.Address) + } else { + addParticipant(coAuthorValue, "") + } + } + return c.allParticipants +} diff --git a/modules/git/commit_message_test.go b/modules/git/commit_message_test.go new file mode 100644 index 00000000000..049f1c03f78 --- /dev/null +++ b/modules/git/commit_message_test.go @@ -0,0 +1,80 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCommitMessageSanitizesInvalidUTF8(t *testing.T) { + commit := &Commit{ + CommitMessage: CommitMessage{MessageRaw: "title \xff\n\n\n\nbody \xff\n\n\n"}, + } + assert.Equal(t, "title ÿ", commit.MessageTitle()) + assert.Equal(t, "body ÿ", commit.MessageBody()) + assert.Equal(t, "title ÿ\n\n\n\nbody ÿ\n\n\n", commit.MessageUTF8()) +} + +func TestCommitMessageTrailer(t *testing.T) { + cases := []struct { + msg, body, sep, trailer string + }{ + {"", "", "", ""}, + {"a", "a", "", ""}, + {"a\n\nk", "a\n\nk", "", ""}, + {"a\n\nk:v", "a", "\n\n", "k:v"}, + {"a\n--\nk:v", "a\n--\nk:v", "", ""}, + {"a\n---\nk:v", "a", "\n---\n", "k:v"}, + + {"k: v", "", "", "k: v"}, + {"\nk:v", "", "\n", "k:v"}, + {"\n\nk:v", "", "\n\n", "k:v"}, + + {"---\nk:v", "", "---\n", "k:v"}, + {"\n---\nk:v", "", "\n---\n", "k:v"}, + {"a:b\n---\nk:v", "a:b", "\n---\n", "k:v"}, + } + for _, c := range cases { + body, sep, trailer := CommitMessageSplitTrailer(c.msg) + assert.Equal(t, c.body, body, "input=%q", c.msg) + assert.Equal(t, c.sep, sep, "input=%q", c.msg) + assert.Equal(t, c.trailer, trailer, "input=%q", c.msg) + } +} + +func TestCommitMessageAllParticipantIdentities(t *testing.T) { + sig := func(n, e string) *Signature { return &Signature{Name: n, Email: e} } + idt := func(n, e string) *CommitIdentity { return &CommitIdentity{Name: n, Email: e} } + cases := []struct { + commit *Commit + participant []*CommitIdentity + }{ + { + &Commit{ + Author: sig("a", "a@m.com"), Committer: sig("c", "c@m.com"), + CommitMessage: CommitMessage{MessageRaw: "CO-Authored-BY: x@m.com"}, + }, + []*CommitIdentity{idt("a", "a@m.com"), idt("c", "c@m.com"), idt("", "x@m.com")}, + }, + { + &Commit{ + Author: sig("a", "a@m.com"), Committer: sig("a", "A@M.com"), + CommitMessage: CommitMessage{MessageRaw: "CO-Authored-BY: a@m.com"}, + }, + []*CommitIdentity{idt("a", "a@m.com")}, + }, + { + &Commit{ + Author: sig("a", "a@m.com"), Committer: sig("", ""), + CommitMessage: CommitMessage{MessageRaw: "Co-authored-by: Full Name "}, + }, + []*CommitIdentity{idt("a", "a@m.com"), idt("Full Name", "X@M.com")}, + }, + } + for _, c := range cases { + assert.Equal(t, c.participant, c.commit.AllParticipantIdentities()) + } +} diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go index a7668e4debe..5e3d2fba71b 100644 --- a/modules/git/commit_test.go +++ b/modules/git/commit_test.go @@ -159,15 +159,6 @@ ISO-8859-1`, commitFromReader.Signature.Payload) assert.Equal(t, commitFromReader, commitFromReader2) } -func TestCommitMessageSanitizesInvalidUTF8(t *testing.T) { - commit := &Commit{ - CommitMessage: CommitMessage{MessageRaw: "title \xff\n\n\n\nbody \xff\n\n\n"}, - } - assert.Equal(t, "title ÿ", commit.MessageTitle()) - assert.Equal(t, "body ÿ", commit.MessageBody()) - assert.Equal(t, "title ÿ\n\n\n\nbody ÿ\n\n\n", commit.MessageUTF8()) -} - func TestHasPreviousCommit(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") diff --git a/modules/repository/commits.go b/modules/repository/commits.go index d40af3d82c4..318b052ef18 100644 --- a/modules/repository/commits.go +++ b/modules/repository/commits.go @@ -9,19 +9,15 @@ import ( "net/url" "time" - "gitea.dev/models/avatars" repo_model "gitea.dev/models/repo" user_model "gitea.dev/models/user" - "gitea.dev/modules/cache" - "gitea.dev/modules/cachegroup" "gitea.dev/modules/git" "gitea.dev/modules/gitrepo" - "gitea.dev/modules/log" - "gitea.dev/modules/setting" api "gitea.dev/modules/structs" ) // PushCommit represents a commit in a push operation. +// This struct is marshaled as JSON (see ActionContent2Commits) type PushCommit struct { Sha1 string Message string @@ -33,6 +29,7 @@ type PushCommit struct { } // PushCommits represents list of commits in a push operation. +// This struct is marshaled as JSON (see ActionContent2Commits) type PushCommits struct { Commits []*PushCommit HeadCommit *PushCommit @@ -128,26 +125,6 @@ func (pc *PushCommits) ToAPIPayloadCommits(ctx context.Context, repo *repo_model return commits, headCommit, nil } -// AvatarLink tries to match user in database with e-mail -// in order to show custom avatar, and falls back to general avatar link. -func (pc *PushCommits) AvatarLink(ctx context.Context, email string) string { - size := avatars.DefaultAvatarPixelSize * setting.Avatar.RenderedSizeFactor - - v, _ := cache.GetWithContextCache(ctx, cachegroup.EmailAvatarLink, email, func(ctx context.Context, email string) (string, error) { - u, err := user_model.GetUserByEmail(ctx, email) - if err != nil { - if !user_model.IsErrUserNotExist(err) { - log.Error("GetUserByEmail: %v", err) - return "", err - } - return avatars.GenerateEmailAvatarFastLink(ctx, email, size), nil - } - return u.AvatarLinkWithSize(ctx, size), nil - }) - - return v -} - // CommitToPushCommit transforms a git.Commit to PushCommit type. func CommitToPushCommit(commit *git.Commit) *PushCommit { return &PushCommit{ diff --git a/modules/repository/commits_test.go b/modules/repository/commits_test.go index c0f00337b92..5e6266d9f21 100644 --- a/modules/repository/commits_test.go +++ b/modules/repository/commits_test.go @@ -4,14 +4,12 @@ package repository import ( - "strconv" "testing" "time" repo_model "gitea.dev/models/repo" "gitea.dev/models/unittest" "gitea.dev/modules/git" - "gitea.dev/modules/setting" "github.com/stretchr/testify/assert" ) @@ -99,38 +97,6 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) { assert.Equal(t, []string{"readme.md"}, headCommit.Modified) } -func TestPushCommits_AvatarLink(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - pushCommits := NewPushCommits() - pushCommits.Commits = []*PushCommit{ - { - Sha1: "abcdef1", - CommitterEmail: "user2@example.com", - CommitterName: "User Two", - AuthorEmail: "user4@example.com", - AuthorName: "User Four", - Message: "message1", - }, - { - Sha1: "abcdef2", - CommitterEmail: "user2@example.com", - CommitterName: "User Two", - AuthorEmail: "user2@example.com", - AuthorName: "User Two", - Message: "message2", - }, - } - - assert.Equal(t, - "/avatars/ab53a2911ddf9b4817ac01ddcd3d975f?size="+strconv.Itoa(28*setting.Avatar.RenderedSizeFactor), - pushCommits.AvatarLink(t.Context(), "user2@example.com")) - - assert.Equal(t, - "/assets/img/avatar_default.png", - pushCommits.AvatarLink(t.Context(), "nonexistent@example.com")) -} - func TestCommitToPushCommit(t *testing.T) { now := time.Now() sig := &git.Signature{ diff --git a/modules/setting/config/value.go b/modules/setting/config/value.go index 655120c1804..240f48243cc 100644 --- a/modules/setting/config/value.go +++ b/modules/setting/config/value.go @@ -78,11 +78,16 @@ func isZeroOrEmpty(v any) bool { return false } +var SkipDatabaseConfig bool + func (opt *Option[T]) ValueRevision(ctx context.Context) (v T, rev int, has bool) { dg := GetDynGetter() if dg == nil { // this is an edge case: the database is not initialized but the system setting is going to be used // it should panic to avoid inconsistent config values (from config / system setting) and fix the code + if SkipDatabaseConfig { + return opt.DefaultValue(), 0, false + } panic("no config dyn value getter") } diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go index 81941902aee..46f90be78ad 100644 --- a/modules/templates/util_render.go +++ b/modules/templates/util_render.go @@ -10,8 +10,10 @@ import ( "math" "net/url" "regexp" + "slices" "strings" + user_model "gitea.dev/models/gituser" issues_model "gitea.dev/models/issues" "gitea.dev/models/renderhelper" "gitea.dev/models/repo" @@ -22,6 +24,7 @@ import ( "gitea.dev/modules/log" "gitea.dev/modules/markup" "gitea.dev/modules/markup/markdown" + "gitea.dev/modules/repository" "gitea.dev/modules/reqctx" "gitea.dev/modules/setting" "gitea.dev/modules/svg" @@ -31,11 +34,12 @@ import ( ) type RenderUtils struct { - ctx reqctx.RequestContext + ctx reqctx.RequestContext + avatarUtils *AvatarUtils } func NewRenderUtils(ctx reqctx.RequestContext) *RenderUtils { - return &RenderUtils{ctx: ctx} + return &RenderUtils{ctx: ctx, avatarUtils: NewAvatarUtils(ctx)} } // RenderCommitMessage renders commit message title (only title) @@ -291,3 +295,134 @@ func (ut *RenderUtils) RenderUnicodeEscapeToggleTd(combined, escapeStatus *chars } return `` + ut.RenderUnicodeEscapeToggleButton(escapeStatus) + `` } + +func renderAvatarStackViewEmailLink(data *user_model.AvatarStackData, email string) template.URL { + if data.SearchByEmailLink != "" && email != "" { + return template.URL(strings.ReplaceAll(data.SearchByEmailLink, "{email}", url.QueryEscape(email))) + } + return "" +} + +func (ut *RenderUtils) participantHref(data *user_model.AvatarStackData, participant *user_model.CommitParticipant) template.URL { + if href := renderAvatarStackViewEmailLink(data, participant.GitIdentity.Email); href != "" { + return href + } + if participant.GiteaUser != nil { + return template.URL(participant.GiteaUser.HomeLink()) + } else if participant.GitIdentity.Email != "" { + return template.URL("mailto:" + participant.GitIdentity.Email) + } + return "" +} + +func (ut *RenderUtils) participantAvatar(participant *user_model.CommitParticipant) template.HTML { + if participant.GiteaUser != nil { + return ut.avatarUtils.Avatar(participant.GiteaUser, 20) + } + return ut.avatarUtils.AvatarByEmail(participant.GitIdentity.Email, participant.GitIdentity.Name, 20) +} + +func participantName(participant *user_model.CommitParticipant) string { + if participant.GiteaUser != nil { + return participant.GiteaUser.GetDisplayName() + } + return participant.GitIdentity.Name +} + +const renderAvatarStackMaxVisible = 10 + +// AvatarStack renders overlapping avatars for the stack participants. It emits children in reverse +// so CSS `flex-direction: row-reverse` places the primary (Participants[0]) leftmost and last-painted (on top). +func (ut *RenderUtils) AvatarStack(data *user_model.AvatarStackData) template.HTML { + visible := data.Participants + overflow := len(visible) - renderAvatarStackMaxVisible + if overflow > 0 { + visible = visible[:renderAvatarStackMaxVisible] + } + + var b htmlutil.HTMLBuilder + b.WriteHTML(``) + if overflow > 0 { + b.WriteFormat(`+%d`, overflow, overflow) + } + + // FIXME: such "backward" breaks a11y like screen readers + for _, participant := range slices.Backward(visible) { + ut.writeAvatarStackItem(&b, data, participant) + } + b.WriteHTML(``) + return b.HTMLString() +} + +func (ut *RenderUtils) writeAvatarStackItem(b *htmlutil.HTMLBuilder, data *user_model.AvatarStackData, participant *user_model.CommitParticipant) { + avatar := ut.participantAvatar(participant) + if href := ut.participantHref(data, participant); href != "" { + b.WriteFormat(`%s`, href, avatar) + } else { + b.WriteFormat(`%s`, avatar) + } +} + +func (ut *RenderUtils) AvatarStackPushCommit(pushCommit *repository.PushCommit) template.HTML { + fakeGitCommit := git.Commit{ + CommitMessage: git.CommitMessage{MessageRaw: pushCommit.Message}, + Author: &git.Signature{Name: pushCommit.AuthorName, Email: pushCommit.AuthorEmail}, + // there is no way to know the real committer, but the field can't be nil + Committer: &git.Signature{Name: pushCommit.AuthorName, Email: pushCommit.AuthorEmail}, + } + data := user_model.BuildAvatarStackData(ut.ctx, fakeGitCommit.AllParticipantIdentities(), nil) + return ut.AvatarStack(data) +} + +// AvatarStackWithNames renders the avatar stack plus a label: `name` / `a and b` / `N people` (opens popup). +func (ut *RenderUtils) AvatarStackWithNames(data *user_model.AvatarStackData) template.HTML { + locale := ut.ctx.Value(translation.ContextKey).(translation.Locale) + participants := data.Participants + + var b htmlutil.HTMLBuilder + b.WriteHTML(``) + b.WriteHTML(ut.AvatarStack(data)) + + switch len(participants) { + case 1: + b.WriteHTML(ut.participantNameLink(data, participants[0])) + case 2: + b.WriteHTML(ut.participantNameLink(data, participants[0])) + b.WriteFormat(`%s`, locale.Tr("repo.commits.avatar_stack_and")) + b.WriteHTML(ut.participantNameLink(data, participants[1])) + default: + b.WriteFormat(``, + locale.Tr("repo.commits.avatar_stack_people", len(participants))) + b.WriteHTML(`
    `) + for _, participant := range participants { + b.WriteHTML(ut.participantPopupRow(data, participant)) + } + b.WriteHTML(`
    `) + } + + b.WriteHTML(`
    `) + return b.HTMLString() +} + +// participantNameLink prefers (in order): commits-by-author search, `GetShortDisplayNameLinkHTML` (keeps alt-name tooltip), `mailto:`, bare name. +func (ut *RenderUtils) participantNameLink(data *user_model.AvatarStackData, participant *user_model.CommitParticipant) template.HTML { + if href := renderAvatarStackViewEmailLink(data, participant.GitIdentity.Email); href != "" { + return htmlutil.HTMLFormat(`%s`, href, participantName(participant)) + } + if participant.GiteaUser != nil { + return participant.GiteaUser.GetShortDisplayNameLinkHTML() + } + if participant.GitIdentity.Email != "" { + return htmlutil.HTMLFormat(`%s`, participant.GitIdentity.Email, participant.GitIdentity.Name) + } + return template.HTML(template.HTMLEscapeString(participant.GitIdentity.Name)) +} + +func (ut *RenderUtils) participantPopupRow(data *user_model.AvatarStackData, participant *user_model.CommitParticipant) template.HTML { + avatar := ut.participantAvatar(participant) + name := participantName(participant) + if href := ut.participantHref(data, participant); href != "" { + return htmlutil.HTMLFormat(`%s%s`, href, avatar, name) + } + return htmlutil.HTMLFormat(`%s%s`, avatar, name) +} diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go index 5a28a1feba8..1db87feb798 100644 --- a/modules/templates/util_render_test.go +++ b/modules/templates/util_render_test.go @@ -7,15 +7,19 @@ import ( "context" "html/template" "os" + "strconv" "strings" "testing" + "gitea.dev/models/gituser" "gitea.dev/models/issues" "gitea.dev/models/repo" user_model "gitea.dev/models/user" + "gitea.dev/modules/git" "gitea.dev/modules/markup" "gitea.dev/modules/reqctx" "gitea.dev/modules/setting" + "gitea.dev/modules/setting/config" "gitea.dev/modules/test" "gitea.dev/modules/translation" @@ -298,3 +302,52 @@ func TestUserMention(t *testing.T) { rendered := newTestRenderUtils(t).MarkdownToHtml("@no-such-user @mention-user @mention-user") assert.Equal(t, `

    @no-such-user @mention-user @mention-user

    `, strings.TrimSpace(string(rendered))) } + +func TestAvatarStack(t *testing.T) { + defer test.MockVariableValue(&config.SkipDatabaseConfig, true)() + + ut := newTestRenderUtils(t) + mkCo := func(name, email string) *git.CommitIdentity { + return &git.CommitIdentity{Name: name, Email: email} + } + authorSig := mkCo("Alice", "alice@example.com") + mkData := func(co ...*git.CommitIdentity) *gituser.AvatarStackData { + all := append([]*git.CommitIdentity{authorSig}, co...) + return gituser.BuildAvatarStackData(t.Context(), all, &user_model.EmailUserMap{}) + } + + t.Run("lone author renders bare name, no label", func(t *testing.T) { + got := string(ut.AvatarStackWithNames(mkData())) + assert.Contains(t, got, ``) + assert.Contains(t, got, "Alice") + assert.NotContains(t, got, "avatar_stack_and") + assert.NotContains(t, got, "avatar_stack_people") + }) + + t.Run("two participants use and label", func(t *testing.T) { + got := string(ut.AvatarStackWithNames(mkData(mkCo("Bob", "bob@example.com")))) + assert.Contains(t, got, "repo.commits.avatar_stack_and") + assert.Contains(t, got, "Bob") + assert.NotContains(t, got, "avatar_stack_people") + assert.Contains(t, got, ``) + }) + + t.Run("three participants switch to N people label with tippy popup", func(t *testing.T) { + got := string(ut.AvatarStackWithNames(mkData(mkCo("Bob", "bob@example.com"), mkCo("Carol", "carol@example.com")))) + assert.Contains(t, got, "repo.commits.avatar_stack_people:3") + assert.NotContains(t, got, "repo.commits.avatar_stack_and") + assert.Contains(t, got, `data-global-init="initAvatarStackPopup"`) + assert.Contains(t, got, `
    `) + assert.Contains(t, got, `class="avatar-stack-popup"`) + }) + + t.Run("overflow chip renders beyond 10 participants", func(t *testing.T) { + cos := make([]*git.CommitIdentity, 0, renderAvatarStackMaxVisible+1) + for i := range renderAvatarStackMaxVisible + 1 { + cos = append(cos, mkCo("X", strconv.Itoa(i)+"@example.com")) + } + got := ut.AvatarStack(gituser.BuildAvatarStackData(t.Context(), cos, &user_model.EmailUserMap{})) + assert.Contains(t, got, `class="avatar-stack-overflow-chip`) + assert.Contains(t, got, "+1") + }) +} diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 9595baebed9..d9139f17e31 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -2205,10 +2205,10 @@ "repo.settings.trust_model.collaborator.desc": "Valid signatures by collaborators of this repository will be marked \"trusted\", whether they match the committer or not. Otherwise, valid signatures will be marked \"untrusted\" if the signature matches the committer and \"unmatched\" if not.", "repo.settings.trust_model.committer": "Committer", "repo.settings.trust_model.committer.long": "Committer: Trust signatures that match committers. This matches GitHub's behavior and will force commits signed by Gitea to have Gitea as the committer.", - "repo.settings.trust_model.committer.desc": "Valid signatures will only be marked \"trusted\" if they match the committer, otherwise they will be marked \"unmatched\". This forces Gitea to be the committer on signed commits, with the actual committer marked as Co-authored-by: and Co-committed-by: trailer in the commit. The default Gitea key must match a user in the database.", + "repo.settings.trust_model.committer.desc": "Valid signatures will only be marked \"trusted\" if they match the committer, otherwise they will be marked \"unmatched\". This forces Gitea to be the committer on signed commits, with the actual committer marked as a Co-authored-by: trailer in the commit. The default Gitea key must match a user in the database.", "repo.settings.trust_model.collaboratorcommitter": "Collaborator+Committer", "repo.settings.trust_model.collaboratorcommitter.long": "Collaborator+Committer: Trust signatures by collaborators which match the committer", - "repo.settings.trust_model.collaboratorcommitter.desc": "Valid signatures by collaborators of this repository will be marked \"trusted\" if they match the committer. Otherwise, valid signatures will be marked \"untrusted\" if the signature matches the committer and \"unmatched\" otherwise. This will force Gitea to be marked as the committer on signed commits, with the actual committer marked as Co-Authored-By: and Co-Committed-By: trailer in the commit. The default Gitea key must match a user in the database.", + "repo.settings.trust_model.collaboratorcommitter.desc": "Valid signatures by collaborators of this repository will be marked \"trusted\" if they match the committer. Otherwise, valid signatures will be marked \"untrusted\" if the signature matches the committer and \"unmatched\" otherwise. This will force Gitea to be marked as the committer on signed commits, with the actual committer marked as a Co-Authored-By: trailer in the commit. The default Gitea key must match a user in the database.", "repo.settings.wiki_delete": "Delete Wiki Data", "repo.settings.wiki_delete_desc": "Deleting repository wiki data is permanent and cannot be undone.", "repo.settings.wiki_delete_notices_1": "- This will permanently delete and disable the repository wiki for %s.", @@ -2599,6 +2599,9 @@ "repo.diff.review.reject": "Request changes", "repo.diff.review.self_approve": "Pull request authors can't approve their own pull request", "repo.diff.committed_by": "committed by", + "repo.diff.coauthored_by": "co-authored by", + "repo.commits.avatar_stack_and": "and", + "repo.commits.avatar_stack_people": "%d people", "repo.diff.protected": "Protected", "repo.diff.image.side_by_side": "Side by Side", "repo.diff.image.swipe": "Swipe", diff --git a/routers/web/devtest/devtest.go b/routers/web/devtest/devtest.go index 87c1ffdb2af..294bd7e1cb6 100644 --- a/routers/web/devtest/devtest.go +++ b/routers/web/devtest/devtest.go @@ -15,6 +15,7 @@ import ( "gitea.dev/models/asymkey" "gitea.dev/models/db" + "gitea.dev/models/gituser" user_model "gitea.dev/models/user" "gitea.dev/modules/badge" "gitea.dev/modules/charset" @@ -61,8 +62,8 @@ func prepareMockDataBadgeCommitSign(ctx *context.Context) { mockUser := mockUsers[0] commits = append(commits, &asymkey.SignCommit{ Verification: &asymkey.CommitVerification{}, - UserCommit: &user_model.UserCommit{ - Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()}, + UserCommit: &gituser.UserCommit{ + GitCommit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()}, }, }) commits = append(commits, &asymkey.SignCommit{ @@ -73,9 +74,9 @@ func prepareMockDataBadgeCommitSign(ctx *context.Context) { SigningKey: &asymkey.GPGKey{KeyID: "12345678"}, TrustStatus: "trusted", }, - UserCommit: &user_model.UserCommit{ - User: mockUser, - Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()}, + UserCommit: &gituser.UserCommit{ + AuthorUser: mockUser, + GitCommit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()}, }, }) commits = append(commits, &asymkey.SignCommit{ @@ -86,9 +87,9 @@ func prepareMockDataBadgeCommitSign(ctx *context.Context) { SigningSSHKey: &asymkey.PublicKey{Fingerprint: "aa:bb:cc:dd:ee"}, TrustStatus: "untrusted", }, - UserCommit: &user_model.UserCommit{ - User: mockUser, - Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()}, + UserCommit: &gituser.UserCommit{ + AuthorUser: mockUser, + GitCommit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()}, }, }) commits = append(commits, &asymkey.SignCommit{ @@ -99,9 +100,9 @@ func prepareMockDataBadgeCommitSign(ctx *context.Context) { SigningSSHKey: &asymkey.PublicKey{Fingerprint: "aa:bb:cc:dd:ee"}, TrustStatus: "other(unmatch)", }, - UserCommit: &user_model.UserCommit{ - User: mockUser, - Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()}, + UserCommit: &gituser.UserCommit{ + AuthorUser: mockUser, + GitCommit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()}, }, }) commits = append(commits, &asymkey.SignCommit{ @@ -110,9 +111,9 @@ func prepareMockDataBadgeCommitSign(ctx *context.Context) { Reason: "gpg.error", SigningEmail: "test@example.com", }, - UserCommit: &user_model.UserCommit{ - User: mockUser, - Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()}, + UserCommit: &gituser.UserCommit{ + AuthorUser: mockUser, + GitCommit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()}, }, }) @@ -159,6 +160,59 @@ func prepareMockDataBadgeActionsSvg(ctx *context.Context) { ctx.Data["SelectedStyle"] = selectedStyle } +func prepareMockDataAvatarStack(ctx *context.Context) { + /* + mockUsers, _ := db.Find[user_model.User](ctx, user_model.SearchUserOptions{ListOptions: db.ListOptions{PageSize: 3}}) + if len(mockUsers) == 0 { + return + } + u0 := mockUsers[0] + u1, u2 := u0, u0 + if len(mockUsers) >= 2 { + u1 = mockUsers[1] + } + if len(mockUsers) >= 3 { + u2 = mockUsers[2] + } + + authorSig := func(u *user_model.User) *git.Signature { + return &git.Signature{Name: u.Name, Email: u.Email} + } + coLinked := func(u *user_model.User) *gituser.CommitParticipant { + return &gituser.CommitParticipant{GiteaUser: u, GitIdentity: authorSig(u)} + } + coUnlinked := func(name, email string) *gituser.CommitParticipant { + return &gituser.CommitParticipant{GitIdentity: &git.Signature{Name: name, Email: email}} + } + nUnlinked := func(n int) []*gituser.CommitParticipant { + out := make([]*gituser.CommitParticipant, n) + for i := range out { + out[i] = coUnlinked(fmt.Sprintf("Contributor %d", i+1), fmt.Sprintf("contrib%d@example.com", i+1)) + } + return out + } + + type scenario struct { + Label string + Data *gituser.AvatarStackData + } + mk := gituser.BuildAvatarStackData() + extSig := &git.Signature{Name: "External Contributor", Email: "external@example.com"} + ctx.Data["AvatarStackScenarios"] = []scenario{ + {Label: "linked author, no co-authors", Data: mk(u0, authorSig(u0), nil)}, + {Label: "unlinked author, no co-authors", Data: mk(nil, extSig, nil)}, + {Label: "1 linked co-author", Data: mk(u0, authorSig(u0), []*gituser.CommitParticipant{coLinked(u1)})}, + {Label: "1 unlinked co-author", Data: mk(u0, authorSig(u0), []*gituser.CommitParticipant{coUnlinked("Bob Smith", "bob@example.com")})}, + {Label: "2 co-authors (3 people), u1 author", Data: mk(u1, authorSig(u1), []*gituser.CommitParticipant{coLinked(u0), coUnlinked("Bob Smith", "bob@example.com")})}, + {Label: "3 co-authors mixed (4 people)", Data: mk(u0, authorSig(u0), []*gituser.CommitParticipant{coLinked(u1), coLinked(u2), coUnlinked("Bob Smith", "bob@example.com")})}, + {Label: "9 co-authors (max visible, no overflow), u2 author", Data: mk(u2, authorSig(u2), nUnlinked(9))}, + {Label: "10 co-authors (overflow +1)", Data: mk(u0, authorSig(u0), nUnlinked(10))}, + {Label: "15 co-authors (overflow +6), unlinked author", Data: mk(nil, extSig, nUnlinked(15))}, + {Label: "30 co-authors (overflow +21)", Data: mk(u0, authorSig(u0), nUnlinked(30))}, + } + */ +} + func prepareMockDataRelativeTime(ctx *context.Context) { now := time.Now() ctx.Data["TimeNow"] = now @@ -196,6 +250,8 @@ func prepareMockData(ctx *context.Context) { prepareMockDataToastAndMessage(ctx) case "/devtest/unicode-escape": prepareMockDataUnicodeEscape(ctx) + case "/devtest/avatar-stack": + prepareMockDataAvatarStack(ctx) } } diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go index 6b97a58c17e..457f9795a23 100644 --- a/routers/web/repo/blame.go +++ b/routers/web/repo/blame.go @@ -12,8 +12,8 @@ import ( "path" "strconv" + "gitea.dev/models/gituser" repo_model "gitea.dev/models/repo" - user_model "gitea.dev/models/user" "gitea.dev/modules/charset" "gitea.dev/modules/git" "gitea.dev/modules/git/languagestats" @@ -29,13 +29,14 @@ import ( type blameRow struct { RowNumber int - Avatar template.HTML PreviousSha string PreviousShaURL string CommitURL string CommitMessage string CommitSince template.HTML + AvatarStackData *gituser.AvatarStackData + Code template.HTML EscapeStatus *charset.EscapeStatus } @@ -174,9 +175,9 @@ func fillBlameResult(br *gitrepo.BlameReader, r *blameResult) error { return nil } -func processBlameParts(ctx *context.Context, blameParts []*gitrepo.BlamePart) map[string]*user_model.UserCommit { +func processBlameParts(ctx *context.Context, blameParts []*gitrepo.BlamePart) map[string]*gituser.UserCommit { // store commit data by SHA to look up avatar info etc - commitNames := make(map[string]*user_model.UserCommit) + commitNames := make(map[string]*gituser.UserCommit) // and as blameParts can reference the same commits multiple // times, we cache the lookup work locally commits := make([]*git.Commit, 0, len(blameParts)) @@ -209,33 +210,28 @@ func processBlameParts(ctx *context.Context, blameParts []*gitrepo.BlamePart) ma } // populate commit email addresses to later look up avatars. - validatedCommits, err := user_model.ValidateCommitsWithEmails(ctx, commits) + userCommits, err := gituser.GetUserCommitsByGitCommits(ctx, commits, ctx.Repo.RepoLink, ctx.Repo.RefFullName) if err != nil { - ctx.ServerError("ValidateCommitsWithEmails", err) + ctx.ServerError("GetUserCommitsByGitCommits", err) return nil } - for _, c := range validatedCommits { - commitNames[c.ID.String()] = c + for _, c := range userCommits { + commitNames[c.GitCommit.ID.String()] = c } return commitNames } -func renderBlameFillFirstBlameRow(repoLink string, avatarUtils *templates.AvatarUtils, part *gitrepo.BlamePart, commit *user_model.UserCommit, br *blameRow) { - if commit.User != nil { - br.Avatar = avatarUtils.Avatar(commit.User, 18) - } else { - br.Avatar = avatarUtils.AvatarByEmail(commit.Author.Email, commit.Author.Name, 18) - } - +func renderBlameFillFirstBlameRow(ctx *context.Context, repoLink string, part *gitrepo.BlamePart, commit *gituser.UserCommit, br *blameRow) { + br.AvatarStackData = gituser.BuildAvatarStackData(ctx, commit.GitCommit.AllParticipantIdentities(), nil) br.PreviousSha = part.PreviousSha br.PreviousShaURL = fmt.Sprintf("%s/blame/commit/%s/%s", repoLink, url.PathEscape(part.PreviousSha), util.PathEscapeSegments(part.PreviousPath)) br.CommitURL = fmt.Sprintf("%s/commit/%s", repoLink, url.PathEscape(part.Sha)) - br.CommitMessage = commit.MessageUTF8() - br.CommitSince = templates.TimeSince(commit.Author.When) + br.CommitMessage = commit.GitCommit.MessageUTF8() + br.CommitSince = templates.TimeSince(commit.GitCommit.Author.When) } -func renderBlame(ctx *context.Context, blameParts []*gitrepo.BlamePart, commitNames map[string]*user_model.UserCommit) { +func renderBlame(ctx *context.Context, blameParts []*gitrepo.BlamePart, commitNames map[string]*gituser.UserCommit) { language, err := languagestats.GetFileLanguage(ctx, ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath) if err != nil { log.Error("Unable to get file language for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err) @@ -243,7 +239,6 @@ func renderBlame(ctx *context.Context, blameParts []*gitrepo.BlamePart, commitNa buf := &bytes.Buffer{} rows := make([]*blameRow, 0) - avatarUtils := templates.NewAvatarUtils(ctx) rowNumber := 0 // will be 1-based for _, part := range blameParts { for partLineIdx, line := range part.Lines { @@ -258,7 +253,7 @@ func renderBlame(ctx *context.Context, blameParts []*gitrepo.BlamePart, commitNa } if partLineIdx == 0 { - renderBlameFillFirstBlameRow(ctx.Repo.RepoLink, avatarUtils, part, commitNames[part.Sha], br) + renderBlameFillFirstBlameRow(ctx, ctx.Repo.RepoLink, part, commitNames[part.Sha], br) } } } diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index ff3496629fa..d8953878184 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -14,6 +14,7 @@ import ( asymkey_model "gitea.dev/models/asymkey" "gitea.dev/models/db" git_model "gitea.dev/models/git" + "gitea.dev/models/gituser" issues_model "gitea.dev/models/issues" "gitea.dev/models/renderhelper" repo_model "gitea.dev/models/repo" @@ -49,7 +50,7 @@ func RefCommits(ctx *context.Context) { switch { case len(ctx.Repo.TreePath) == 0: Commits(ctx) - case ctx.Repo.TreePath == "search": + case ctx.Repo.TreePath == "search": // FIXME: legacy dirty design, it conflicts with the FileHistory SearchCommits(ctx) default: FileHistory(ctx) @@ -396,7 +397,8 @@ func Diff(ctx *context.Context) { verification := asymkey_service.ParseCommitWithSignature(ctx, commit) ctx.Data["Verification"] = verification - ctx.Data["Author"] = user_model.ValidateCommitWithEmail(ctx, commit) + ctx.Data["Author"] = user_model.GetUserByGitAuthor(ctx, commit) + ctx.Data["CommitOtherParticipants"] = gituser.BuildAvatarStackData(ctx, commit.AllParticipantIdentities(), nil).Participants[1:] ctx.Data["Parents"] = parents ctx.Data["DiffNotAvailable"] = diffShortStat.NumFiles == 0 @@ -411,7 +413,7 @@ func Diff(ctx *context.Context) { err = git.GetNote(ctx, ctx.Repo.GitRepo, commitID, note) if err == nil { ctx.Data["NoteCommit"] = note.Commit - ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit) + ctx.Data["NoteAuthor"] = user_model.GetUserByGitAuthor(ctx, note.Commit) rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{CurrentRefSubURL: "commit/" + util.PathEscapeSegments(commitID)}) htmlMessage := template.HTML(template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{})))) ctx.Data["NoteRendered"] = markup.PostProcessCommitMessage(rctx, htmlMessage) @@ -461,7 +463,7 @@ func RawDiff(ctx *context.Context) { } func processGitCommits(ctx *context.Context, gitCommits []*git.Commit) ([]*git_model.SignCommitWithStatuses, error) { - commits, err := git_service.ConvertFromGitCommit(ctx, gitCommits, ctx.Repo.Repository) + commits, err := git_service.ConvertFromGitCommit(ctx, gitCommits, ctx.Repo.Repository, ctx.Repo.RefFullName) if err != nil { return nil, err } diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 2b2848afd15..45735fc8fe3 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -391,14 +391,14 @@ func prepareNewPullRequestTitleContent(ci *git_service.CompareInfo, commits []*g if useFirstCommitAsTitle { // the "commits" are from "ShowPrettyFormatLogToList", which is ordered from newest to oldest, here take the oldest one c := commits[len(commits)-1] - title = c.UserCommit.MessageTitle() + title = c.UserCommit.GitCommit.MessageTitle() } else { title = autoTitleFromBranchName(ci.HeadRef.ShortName()) } if len(commits) == 1 { c := commits[0] - content = c.MessageBody() + content = c.GitCommit.MessageBody() } var titleTrailer string diff --git a/routers/web/repo/compare_test.go b/routers/web/repo/compare_test.go index d5b67ebe56e..af0a735227a 100644 --- a/routers/web/repo/compare_test.go +++ b/routers/web/repo/compare_test.go @@ -9,8 +9,8 @@ import ( asymkey_model "gitea.dev/models/asymkey" git_model "gitea.dev/models/git" + "gitea.dev/models/gituser" issues_model "gitea.dev/models/issues" - user_model "gitea.dev/models/user" "gitea.dev/modules/git" "gitea.dev/modules/setting" git_service "gitea.dev/services/git" @@ -52,8 +52,8 @@ func TestNewPullRequestTitleContent(t *testing.T) { mockCommit := func(msg string) *git_model.SignCommitWithStatuses { return &git_model.SignCommitWithStatuses{ SignCommit: &asymkey_model.SignCommit{ - UserCommit: &user_model.UserCommit{ - Commit: &git.Commit{ + UserCommit: &gituser.UserCommit{ + GitCommit: &git.Commit{ CommitMessage: git.CommitMessage{MessageRaw: msg}, }, }, diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 437eedc57f1..6df4e738c99 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -21,6 +21,7 @@ import ( asymkey_model "gitea.dev/models/asymkey" "gitea.dev/models/db" git_model "gitea.dev/models/git" + "gitea.dev/models/gituser" repo_model "gitea.dev/models/repo" unit_model "gitea.dev/models/unit" user_model "gitea.dev/models/user" @@ -132,8 +133,11 @@ func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool { ctx.ServerError("CalculateTrustStatus", err) return false } + + avatarStackData := gituser.BuildAvatarStackData(ctx, latestCommit.AllParticipantIdentities(), nil) + avatarStackData.SearchByEmailLink = gituser.RepoCommitSearchByEmailLink(ctx.Repo.RepoLink, ctx.Repo.RefFullName) + ctx.Data["LatestCommitAvatarStackData"] = avatarStackData ctx.Data["LatestCommitVerification"] = verification - ctx.Data["LatestCommitUser"] = user_model.ValidateCommitWithEmail(ctx, latestCommit) statuses, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, latestCommit.ID.String(), db.ListOptionsAll) if err != nil { diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index e0881dc9f1d..6e2133a9452 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -361,7 +361,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) ctx.ServerError("CommitsByFileAndRange", err) return nil, nil } - ctx.Data["Commits"], err = git_service.ConvertFromGitCommit(ctx, commitsHistory, ctx.Repo.Repository) + ctx.Data["Commits"], err = git_service.ConvertFromGitCommit(ctx, commitsHistory, ctx.Repo.Repository, "") // no current ref sub path for wiki commit list if err != nil { ctx.ServerError("ConvertFromGitCommit", err) return nil, nil diff --git a/services/git/commit.go b/services/git/commit.go index 5708e162b99..bc0516b5d6f 100644 --- a/services/git/commit.go +++ b/services/git/commit.go @@ -9,6 +9,7 @@ import ( asymkey_model "gitea.dev/models/asymkey" "gitea.dev/models/db" git_model "gitea.dev/models/git" + "gitea.dev/models/gituser" repo_model "gitea.dev/models/repo" user_model "gitea.dev/models/user" "gitea.dev/modules/container" @@ -17,14 +18,14 @@ import ( ) // ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys. -func ParseCommitsWithSignature(ctx context.Context, repo *repo_model.Repository, oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType) ([]*asymkey_model.SignCommit, error) { +func ParseCommitsWithSignature(ctx context.Context, repo *repo_model.Repository, oldCommits []*gituser.UserCommit, repoTrustModel repo_model.TrustModelType) ([]*asymkey_model.SignCommit, error) { newCommits := make([]*asymkey_model.SignCommit, 0, len(oldCommits)) keyMap := map[string]bool{} emails := make(container.Set[string]) for _, c := range oldCommits { - if c.Committer != nil { - emails.Add(c.Committer.Email) + if c.GitCommit.Committer != nil { + emails.Add(c.GitCommit.Committer.Email) } } @@ -34,10 +35,10 @@ func ParseCommitsWithSignature(ctx context.Context, repo *repo_model.Repository, } for _, c := range oldCommits { - committerUser := emailUsers.GetByEmail(c.Committer.Email) // FIXME: why ValidateCommitsWithEmails uses "Author", but ParseCommitsWithSignature uses "Committer"? + committerUser := emailUsers.GetByEmail(c.GitCommit.Committer.Email) // FIXME: why GetUserCommitsByGitCommits uses "Author", but ParseCommitsWithSignature uses "Committer"? signCommit := &asymkey_model.SignCommit{ UserCommit: c, - Verification: asymkey_service.ParseCommitWithSignatureCommitter(ctx, c.Commit, committerUser), + Verification: asymkey_service.ParseCommitWithSignatureCommitter(ctx, c.GitCommit, committerUser), } isOwnerMemberCollaborator := func(user *user_model.User) (bool, error) { @@ -52,15 +53,15 @@ func ParseCommitsWithSignature(ctx context.Context, repo *repo_model.Repository, } // ConvertFromGitCommit converts git commits into SignCommitWithStatuses -func ConvertFromGitCommit(ctx context.Context, commits []*git.Commit, repo *repo_model.Repository) ([]*git_model.SignCommitWithStatuses, error) { - validatedCommits, err := user_model.ValidateCommitsWithEmails(ctx, commits) +func ConvertFromGitCommit(ctx context.Context, commits []*git.Commit, repo *repo_model.Repository, currentRef git.RefName) ([]*git_model.SignCommitWithStatuses, error) { + userCommits, err := gituser.GetUserCommitsByGitCommits(ctx, commits, repo.Link(), currentRef) if err != nil { return nil, err } signedCommits, err := ParseCommitsWithSignature( ctx, repo, - validatedCommits, + userCommits, repo.GetTrustModel(), ) if err != nil { @@ -77,7 +78,7 @@ func ParseCommitsWithStatus(ctx context.Context, oldCommits []*asymkey_model.Sig commit := &git_model.SignCommitWithStatuses{ SignCommit: c, } - statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, commit.ID.String(), db.ListOptionsAll) + statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, commit.GitCommit.ID.String(), db.ListOptionsAll) if err != nil { return nil, err } diff --git a/services/issue/comments.go b/services/issue/comments.go index 46964f5f1a3..ff6c9fccf56 100644 --- a/services/issue/comments.go +++ b/services/issue/comments.go @@ -184,7 +184,7 @@ func LoadCommentPushCommits(ctx context.Context, c *issues_model.Comment) error } defer closer.Close() - c.Commits, err = git_service.ConvertFromGitCommit(ctx, gitRepo.GetCommitsFromIDs(data.CommitIDs), c.Issue.Repo) + c.Commits, err = git_service.ConvertFromGitCommit(ctx, gitRepo.GetCommitsFromIDs(data.CommitIDs), c.Issue.Repo, "") // no current ref sub path for PR commit list if err != nil { log.Debug("ConvertFromGitCommit: %v", err) // no need to show 500 error to end user when the commit does not exist } else { diff --git a/services/pull/merge_squash.go b/services/pull/merge_squash.go index 67ec5bb81cf..229730257f2 100644 --- a/services/pull/merge_squash.go +++ b/services/pull/merge_squash.go @@ -65,9 +65,7 @@ func doMergeStyleSquash(ctx *mergeContext, message string) error { } if setting.Repository.PullRequest.AddCoCommitterTrailers && ctx.committer.String() != sig.String() { - // add trailer - message = AddCommitMessageTailer(message, "Co-authored-by", sig.String()) - message = AddCommitMessageTailer(message, "Co-committed-by", sig.String()) // FIXME: this one should be removed, it is not really used or widely used + message = AddCommitMessageTailer(message, git.CoAuthoredByTrailer, sig.String()) } cmdCommit := gitcmd.NewCommand("commit"). AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email). diff --git a/services/pull/pull.go b/services/pull/pull.go index 0d4a10ae9ef..a601450ff25 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -917,7 +917,7 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ } for _, author := range authors { - stringBuilder.WriteString("Co-authored-by: ") + stringBuilder.WriteString(git.CoAuthoredByTrailer + ": ") stringBuilder.WriteString(author) stringBuilder.WriteRune('\n') } diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go index b36f5f192a8..553f4232e2a 100644 --- a/services/repository/files/temp_repo.go +++ b/services/repository/files/temp_repo.go @@ -300,12 +300,7 @@ func (t *TemporaryUploadRepository) CommitTree(ctx context.Context, opts *Commit cmdCommitTree.AddOptionFormat("-S%s", key.KeyID) if t.repo.GetTrustModel() == repo_model.CommitterTrustModel || t.repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel { if committerSig.Name != authorSig.Name || committerSig.Email != authorSig.Email { - // Add trailers - _, _ = messageBytes.WriteString("\n") - _, _ = messageBytes.WriteString("Co-authored-by: ") - _, _ = messageBytes.WriteString(committerSig.String()) - _, _ = messageBytes.WriteString("\n") - _, _ = messageBytes.WriteString("Co-committed-by: ") + _, _ = messageBytes.WriteString("\n" + git.CoAuthoredByTrailer + ": ") _, _ = messageBytes.WriteString(committerSig.String()) _, _ = messageBytes.WriteString("\n") } diff --git a/services/repository/gitgraph/graph_models.go b/services/repository/gitgraph/graph_models.go index 82092f71f36..99f8222ca7c 100644 --- a/services/repository/gitgraph/graph_models.go +++ b/services/repository/gitgraph/graph_models.go @@ -13,8 +13,10 @@ import ( asymkey_model "gitea.dev/models/asymkey" "gitea.dev/models/db" git_model "gitea.dev/models/git" + "gitea.dev/models/gituser" repo_model "gitea.dev/models/repo" user_model "gitea.dev/models/user" + "gitea.dev/modules/container" "gitea.dev/modules/git" "gitea.dev/modules/log" asymkey_service "gitea.dev/services/asymkey" @@ -93,9 +95,7 @@ func (graph *Graph) AddCommit(row, column int, flowID int64, data []byte) error // before finally retrieving the latest status func (graph *Graph) LoadAndProcessCommits(ctx context.Context, repository *repo_model.Repository, gitRepo *git.Repository) error { var err error - var ok bool - - emails := map[string]*user_model.User{} + emailSet := make(container.Set[string]) keyMap := map[string]bool{} for _, c := range graph.Commits { @@ -106,14 +106,26 @@ func (graph *Graph) LoadAndProcessCommits(ctx context.Context, repository *repo_ if err != nil { return fmt.Errorf("GetCommit: %s Error: %w", c.Rev, err) } - if c.Commit.Author != nil { - email := c.Commit.Author.Email - if c.User, ok = emails[email]; !ok { - c.User, _ = user_model.GetUserByEmail(ctx, email) - emails[email] = c.User - } + emailSet.Add(c.Commit.Author.Email) } + for _, sig := range c.Commit.AllParticipantIdentities() { + emailSet.Add(sig.Email) + } + } + + emailUserMap, err := user_model.GetUsersByEmails(ctx, emailSet.Values()) + if err != nil { + log.Error("GetUsersByEmails: %v", err) + } + + for _, c := range graph.Commits { + if c.Commit == nil { + continue + } + + c.User = emailUserMap.GetByEmail(c.Commit.Author.Email) + c.AvatarStackData = gituser.BuildAvatarStackData(ctx, c.Commit.AllParticipantIdentities(), emailUserMap) c.Verification = asymkey_service.ParseCommitWithSignature(ctx, c.Commit) @@ -246,18 +258,19 @@ func newRefsFromRefNames(refNames []byte) []git.Reference { // Commit represents a commit at coordinate X, Y with the data type Commit struct { - Commit *git.Commit - User *user_model.User - Verification *asymkey_model.CommitVerification - Status *git_model.CommitStatus - Flow int64 - Row int - Column int - Refs []git.Reference - Rev string - Date time.Time - ShortRev string - Subject string + Commit *git.Commit + User *user_model.User // author + AvatarStackData *gituser.AvatarStackData + Verification *asymkey_model.CommitVerification + Status *git_model.CommitStatus + Flow int64 + Row int + Column int + Refs []git.Reference + Rev string + Date time.Time // author date from "%ad" + ShortRev string + Subject string } // OnlyRelation returns whether this a relation only commit diff --git a/templates/devtest/avatar-stack.tmpl b/templates/devtest/avatar-stack.tmpl new file mode 100644 index 00000000000..12d1ff953bf --- /dev/null +++ b/templates/devtest/avatar-stack.tmpl @@ -0,0 +1,18 @@ +{{template "devtest/devtest-header"}} +
    +
    +

    Avatar Stack

    + + + + {{range $s := .AvatarStackScenarios}} + + + + + {{end}} + +
    ScenarioRendered
    {{$s.Label}}{{ctx.RenderUtils.AvatarStackWithNames $s.Data}}
    +
    +
    +{{template "devtest/devtest-footer"}} diff --git a/templates/devtest/badge-commit-sign.tmpl b/templates/devtest/badge-commit-sign.tmpl index a6677c44958..8cfb63a0832 100644 --- a/templates/devtest/badge-commit-sign.tmpl +++ b/templates/devtest/badge-commit-sign.tmpl @@ -4,7 +4,7 @@

    Commit Sign Badges

    {{range $commit := .MockCommits}}
    - {{template "repo/commit_sign_badge" dict "Commit" $commit "CommitBaseLink" "/devtest/commit" "CommitSignVerification" $commit.Verification}} + {{template "repo/commit_sign_badge" dict "Commit" $commit.GitCommit "CommitBaseLink" "/devtest/commit" "CommitSignVerification" $commit.Verification}} {{template "repo/commit_sign_badge" dict "CommitSignVerification" $commit.Verification}}
    {{end}} diff --git a/templates/repo/blame.tmpl b/templates/repo/blame.tmpl index 8bdefa5d43e..d108ea33797 100644 --- a/templates/repo/blame.tmpl +++ b/templates/repo/blame.tmpl @@ -43,7 +43,7 @@
    - {{$row.Avatar}} + {{if $row.AvatarStackData}}{{ctx.RenderUtils.AvatarStack $row.AvatarStackData}}{{end}}
    + {{if .CommitOtherParticipants}} +
    + {{ctx.Locale.Tr "repo.diff.coauthored_by"}} + {{range $participant := .CommitOtherParticipants}} + {{$user := $participant.GiteaUser}} + {{$gitIdentity := $participant.GitIdentity}} + {{if $user}} + {{ctx.AvatarUtils.Avatar $user 20}} + {{$user.GetDisplayName}} + {{else}} + {{$gitName := $gitIdentity.Name}} + {{$gitEmail := $gitIdentity.Email}} + {{ctx.AvatarUtils.AvatarByEmail $gitEmail $gitName 20}} + {{if $gitEmail}} + {{$gitName}} + {{else}} + {{$gitName}} + {{end}} + {{end}} + {{end}} +
    + {{end}} + {{if .Verification}} {{template "repo/commit_sign_badge" dict "CommitSignVerification" .Verification}} {{end}} diff --git a/templates/repo/commit_sign_badge.tmpl b/templates/repo/commit_sign_badge.tmpl index f63e4ec899d..bf8185fd0b5 100644 --- a/templates/repo/commit_sign_badge.tmpl +++ b/templates/repo/commit_sign_badge.tmpl @@ -64,10 +64,10 @@ so this template should be kept as small as possible, DO NOT put large component {{- if $verified -}} {{- if and $signingUser $signingUser.ID -}} {{svg "gitea-lock"}} - {{ctx.AvatarUtils.Avatar $signingUser 16}} + {{ctx.AvatarUtils.Avatar $signingUser 20}} {{- else -}} {{svg "gitea-lock-cog"}} - {{ctx.AvatarUtils.AvatarByEmail $signingEmail "" 16}} + {{ctx.AvatarUtils.AvatarByEmail $signingEmail "" 20}} {{- end -}} {{- else -}} {{svg "gitea-unlock"}} diff --git a/templates/repo/commits_list.tmpl b/templates/repo/commits_list.tmpl index e79d189b8d3..7a99bc7ac2e 100644 --- a/templates/repo/commits_list.tmpl +++ b/templates/repo/commits_list.tmpl @@ -2,27 +2,21 @@ - + - + {{$commitRepoLink := $.RepoLink}}{{if $.CommitRepoLink}}{{$commitRepoLink = $.CommitRepoLink}}{{end}} - {{range $commit := .Commits}} + {{range $commit := $.Commits}} + {{$gitCommit := $commit.GitCommit}} + {{$commitID := $gitCommit.ID.String}} - {{if .Committer}} - - {{else}} - - {{end}} +
    {{ctx.Locale.Tr "repo.commits.author"}}{{ctx.Locale.Tr "repo.commits.author"}} {{StringUtils.ToUpper $.Repository.ObjectFormatName}}{{ctx.Locale.Tr "repo.commits.message"}}{{ctx.Locale.Tr "repo.commits.message"}} {{ctx.Locale.Tr "repo.commits.date"}}
    - - {{- if .User -}} - {{- ctx.AvatarUtils.Avatar .User 20 "tw-mr-2" -}} - {{- .User.GetShortDisplayNameLinkHTML -}} - {{- else -}} - {{- ctx.AvatarUtils.AvatarByEmail .Author.Email .Author.Name 20 "tw-mr-2" -}} - {{- .Author.Name -}} - {{- end -}} - + {{ctx.RenderUtils.AvatarStackWithNames $commit.AvatarStackData}} {{$commitBaseLink := ""}} @@ -33,52 +27,48 @@ {{else}} {{$commitBaseLink = printf "%s/commit" $commitRepoLink}} {{end}} - {{template "repo/commit_sign_badge" dict "Commit" . "CommitBaseLink" $commitBaseLink "CommitSignVerification" .Verification}} + {{template "repo/commit_sign_badge" dict "Commit" $gitCommit "CommitBaseLink" $commitBaseLink "CommitSignVerification" .Verification}} {{if $.PageIsWiki}} - - {{$commit.MessageTitle | ctx.RenderUtils.RenderEmoji}} + + {{$gitCommit.MessageTitle | ctx.RenderUtils.RenderEmoji}} {{else}} - {{$commitLink:= printf "%s/commit/%s" $commitRepoLink (PathEscape $commit.ID.String)}} - - {{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.MessageUTF8 $commitLink $.Repository}} + {{$commitLink:= printf "%s/commit/%s" $commitRepoLink (PathEscape $commitID)}} + + {{ctx.RenderUtils.RenderCommitMessageLinkSubject $gitCommit.MessageUTF8 $commitLink $.Repository}} {{end}} - {{if $commit.MessageBody}} + {{if $gitCommit.MessageBody}} {{end}} {{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}} - {{if $commit.MessageBody}} -
    {{ctx.RenderUtils.RenderCommitBody $commit.MessageUTF8 $.Repository}}
    + {{if $gitCommit.MessageBody}} +
    {{ctx.RenderUtils.RenderCommitBody $gitCommit.MessageUTF8 $.Repository}}
    {{end}} {{if $.CommitsTagsMap}} - {{range (index $.CommitsTagsMap .ID.String)}} + {{range (index $.CommitsTagsMap $commitID)}} {{- template "repo/tag/name" dict "AdditionalClasses" "tw-py-0" "RepoLink" $.Repository.Link "TagName" .TagName "IsRelease" (not .IsTag) -}} {{end}} {{end}}
    {{DateUtils.TimeSince .Committer.When}}{{DateUtils.TimeSince .Author.When}}{{DateUtils.TimeSince $gitCommit.Committer.When}} - + {{/* at the moment, wiki doesn't support these "view" links like "view at history point" */}} {{if not $.PageIsWiki}} {{/* view single file diff */}} {{if $.FileTreePath}} {{svg "octicon-file-diff"}} {{end}} {{/* view at history point */}} - {{$viewCommitLink := printf "%s/src/commit/%s" $commitRepoLink (PathEscape .ID.String)}} + {{$viewCommitLink := printf "%s/src/commit/%s" $commitRepoLink (PathEscape $commitID)}} {{if $.FileTreePath}}{{$viewCommitLink = printf "%s/%s" $viewCommitLink (PathEscapeSegments $.FileTreePath)}}{{end}} {{svg "octicon-file-code"}} {{end}} diff --git a/templates/repo/commits_list_small.tmpl b/templates/repo/commits_list_small.tmpl index c5f0d5b5900..2dd6727e93c 100644 --- a/templates/repo/commits_list_small.tmpl +++ b/templates/repo/commits_list_small.tmpl @@ -1,35 +1,32 @@ {{$index := 0}}
    -{{range $commit := .comment.Commits}} +{{range $commit := $.comment.Commits}} + {{$gitCommit := $commit.GitCommit}} {{$tag := printf "%s-%d" $.comment.HashTag $index}} {{$index = Eval $index "+" 1}}
    {{/*singular-commit*/}} {{svg "octicon-git-commit"}} - {{if .User}} - {{ctx.AvatarUtils.Avatar .User 20}} - {{else}} - {{ctx.AvatarUtils.AvatarByEmail .Author.Email .Author.Name 20}} - {{end}} + {{ctx.RenderUtils.AvatarStack $commit.AvatarStackData}} {{$commitBaseLink := printf "%s/commit" $.comment.Issue.PullRequest.BaseRepo.Link}} - {{$commitLink:= printf "%s/%s" $commitBaseLink (PathEscape .ID.String)}} + {{$commitLink:= printf "%s/%s" $commitBaseLink (PathEscape $gitCommit.ID.String)}} - - {{- ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.MessageUTF8 $commitLink $.comment.Issue.PullRequest.BaseRepo -}} + + {{- ctx.RenderUtils.RenderCommitMessageLinkSubject $gitCommit.MessageUTF8 $commitLink $.comment.Issue.PullRequest.BaseRepo -}} - {{if $commit.MessageBody}} + {{if $gitCommit.MessageBody}} {{end}} - {{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}} - {{template "repo/commit_sign_badge" dict "Commit" . "CommitBaseLink" $commitBaseLink "CommitSignVerification" .Verification}} + {{template "repo/commit_statuses" dict "Status" $commit.Status "Statuses" $commit.Statuses}} + {{template "repo/commit_sign_badge" dict "Commit" $gitCommit "CommitBaseLink" $commitBaseLink "CommitSignVerification" $commit.Verification}}
    - {{if $commit.MessageBody}} + {{if $gitCommit.MessageBody}}
    -		{{- ctx.RenderUtils.RenderCommitBody $commit.MessageUTF8 $.comment.Issue.PullRequest.BaseRepo -}}
    +		{{- ctx.RenderUtils.RenderCommitBody $gitCommit.MessageUTF8 $.comment.Issue.PullRequest.BaseRepo -}}
     	
    {{end}} {{end}} diff --git a/templates/repo/graph/commits.tmpl b/templates/repo/graph/commits.tmpl index d86f73fe654..b9288da911c 100644 --- a/templates/repo/graph/commits.tmpl +++ b/templates/repo/graph/commits.tmpl @@ -41,14 +41,7 @@ - {{if $commit.User}} - {{ctx.AvatarUtils.Avatar $commit.User 18}} - {{$commit.User.GetShortDisplayNameLinkHTML}} - {{else}} - {{$gitUserName := $commit.Commit.Author.Name}} - {{ctx.AvatarUtils.AvatarByEmail $commit.Commit.Author.Email $gitUserName 18}} - {{$gitUserName}} - {{end}} + {{ctx.RenderUtils.AvatarStackWithNames $commit.AvatarStackData}} {{DateUtils.FullTime $commit.Date}} diff --git a/templates/repo/latest_commit.tmpl b/templates/repo/latest_commit.tmpl index c0518189b85..b2aebf0d42c 100644 --- a/templates/repo/latest_commit.tmpl +++ b/templates/repo/latest_commit.tmpl @@ -2,15 +2,7 @@ {{if not .LatestCommit}} … {{else}} - - {{- if .LatestCommitUser -}} - {{- ctx.AvatarUtils.Avatar .LatestCommitUser 20 "tw-mr-2" -}} - {{.LatestCommitUser.GetShortDisplayNameLinkHTML}} - {{- else if .LatestCommit.Author -}} - {{- ctx.AvatarUtils.AvatarByEmail .LatestCommit.Author.Email .LatestCommit.Author.Name 20 "tw-mr-2" -}} - {{.LatestCommit.Author.Name}} - {{- end -}} - + {{ctx.RenderUtils.AvatarStackWithNames .LatestCommitAvatarStackData}} {{template "repo/commit_sign_badge" dict "Commit" .LatestCommit "CommitBaseLink" (print .RepoLink "/commit") "CommitSignVerification" .LatestCommitVerification}} diff --git a/templates/user/dashboard/feeds.tmpl b/templates/user/dashboard/feeds.tmpl index 9afb61887cf..ab02522c20f 100644 --- a/templates/user/dashboard/feeds.tmpl +++ b/templates/user/dashboard/feeds.tmpl @@ -89,10 +89,10 @@ {{$repo := .Repo}}
    {{range $pushCommit := $push.Commits}} - {{$commitLink := printf "%s/commit/%s" $repoLink .Sha1}} + {{$commitLink := printf "%s/commit/%s" $repoLink $pushCommit.Sha1}}
    - - {{ShortSha .Sha1}} + {{ctx.RenderUtils.AvatarStackPushCommit $pushCommit}} + {{ShortSha $pushCommit.Sha1}} {{ctx.RenderUtils.RenderCommitMessage $pushCommit.Message $repo}} diff --git a/tests/integration/repo_commits_test.go b/tests/integration/repo_commits_test.go index 9335ef70656..b55e1372c95 100644 --- a/tests/integration/repo_commits_test.go +++ b/tests/integration/repo_commits_test.go @@ -38,11 +38,15 @@ func TestRepoCommits(t *testing.T) { doc.doc.Find("#commits-table .commit-id-short").Each(func(i int, s *goquery.Selection) { commits = append(commits, path.Base(s.AttrOr("href", ""))) }) - doc.doc.Find("#commits-table .author-wrapper a").Each(func(i int, s *goquery.Selection) { + doc.doc.Find("#commits-table .avatar-stack-names a.muted").Each(func(i int, s *goquery.Selection) { userHrefs = append(userHrefs, s.AttrOr("href", "")) }) assert.Equal(t, []string{"69554a64c1e6030f051e5c3f94bfbd773cd6a324", "27566bd5738fc8b4e3fef3c5e72cce608537bd95", "5099b81332712fe655e34e8dd63574f503f61811"}, commits) - assert.Equal(t, []string{"/user2", "/user21", "/user2"}, userHrefs) + assert.Equal(t, []string{ + "/user2/repo16/commits/branch/master/search?q=author%3Auser2%40example.com", + "/user2/repo16/commits/branch/master/search?q=author%3Auser21%40example.com", + "/user2/repo16/commits/branch/master/search?q=author%3Auser2%40example.com", + }, userHrefs) }) t.Run("LastCommit", func(t *testing.T) { @@ -50,9 +54,9 @@ func TestRepoCommits(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusOK) doc := NewHTMLParser(t, resp.Body) commitHref := doc.doc.Find(".latest-commit .commit-id-short").AttrOr("href", "") - authorHref := doc.doc.Find(".latest-commit .author-wrapper a").AttrOr("href", "") + authorHref := doc.doc.Find(".latest-commit .avatar-stack-names a").AttrOr("href", "") assert.Equal(t, "/user2/repo16/commit/69554a64c1e6030f051e5c3f94bfbd773cd6a324", commitHref) - assert.Equal(t, "/user2", authorHref) + assert.Equal(t, "/user2/repo16/commits/branch/master/search?q=author%3Auser2%40example.com", authorHref) }) t.Run("CommitListNonExistingCommiter", func(t *testing.T) { @@ -65,7 +69,7 @@ func TestRepoCommits(t *testing.T) { doc := NewHTMLParser(t, resp.Body) commitHref := doc.doc.Find("#commits-table tr:first-child .commit-id-short").AttrOr("href", "") assert.Equal(t, "/user2/repo1/commit/985f0301dba5e7b34be866819cd15ad3d8f508ee", commitHref) - authorElem := doc.doc.Find("#commits-table tr:first-child .author-wrapper") + authorElem := doc.doc.Find("#commits-table tr:first-child .avatar-stack-names") assert.Equal(t, "6543", strings.TrimSpace(authorElem.Text())) }) @@ -97,7 +101,7 @@ func TestRepoCommits(t *testing.T) { doc := NewHTMLParser(t, resp.Body) commitHref := doc.doc.Find(".latest-commit .commit-id-short").AttrOr("href", "") assert.Equal(t, "/user2/repo1/commit/985f0301dba5e7b34be866819cd15ad3d8f508ee", commitHref) - authorElem := doc.doc.Find(".latest-commit .author-wrapper") + authorElem := doc.doc.Find(".latest-commit .avatar-stack-names") assert.Equal(t, "6543", strings.TrimSpace(authorElem.Text())) }) } diff --git a/web_src/css/avatar.css b/web_src/css/avatar.css new file mode 100644 index 00000000000..3b34b8207f4 --- /dev/null +++ b/web_src/css/avatar.css @@ -0,0 +1,125 @@ +img.ui.avatar, +.ui.avatar img, +.ui.avatar svg { + border-radius: var(--border-radius); + object-fit: contain; + aspect-ratio: 1; +} + +.avatar-stack-names { + display: inline-flex; + align-items: center; + align-self: center; + gap: 4px; + white-space: nowrap; + vertical-align: middle; +} + +.avatar-stack-names > a.muted, +.avatar-stack-names > .avatar-stack-popup-trigger { + overflow: hidden; + text-overflow: ellipsis; + max-width: 240px; +} + +/* use semibold for latest commit author */ +.latest-commit .avatar-stack-names > a, +.latest-commit .avatar-stack-names > .avatar-stack-popup-trigger { + font-weight: var(--font-weight-semibold); +} + +/* template emits children reversed; row-reverse re-orders visually and keeps the author last-painted (on top) */ +.avatar-stack { + display: inline-flex; + align-items: center; + flex-direction: row-reverse; +} + +.avatar-stack > * { + margin-left: -16px; + transition: transform 0.15s ease, opacity 0.15s ease; + position: relative; + display: inline-flex; +} + +.avatar-stack > *:last-child { margin-left: 0; } +.avatar-stack > *:nth-last-child(2) { margin-left: -14px; } + +/* hover spreads via transform (no layout shift); positions count from visual-left = last DOM child = :nth-last-child */ +.avatar-stack:hover > *:nth-last-child(2) { transform: translateX(14px); } +.avatar-stack:hover > *:nth-last-child(3) { transform: translateX(30px); } +.avatar-stack:hover > *:nth-last-child(4) { transform: translateX(46px); } +.avatar-stack:hover > *:nth-last-child(5) { transform: translateX(62px); } +.avatar-stack:hover > *:nth-last-child(6) { transform: translateX(78px); } +.avatar-stack:hover > *:nth-last-child(7) { transform: translateX(94px); } +.avatar-stack:hover > *:nth-last-child(8) { transform: translateX(110px); } +.avatar-stack:hover > *:nth-last-child(9) { transform: translateX(126px); } +.avatar-stack:hover > *:nth-last-child(10) { transform: translateX(142px); } +.avatar-stack:hover > *:nth-last-child(11) { transform: translateX(158px); } + +.avatar-stack .avatar { + border: 1px solid var(--color-body); + background: var(--color-body); + transition: border-color 0.15s ease, background-color 0.15s ease; +} + +.avatar-stack:hover .avatar { + background-color: var(--color-body); +} + +.avatar-stack-overflow-chip { + align-items: center; + justify-content: center; + width: 0; + height: 20px; + margin-left: 0; + border: 0 solid var(--color-body); + border-radius: var(--border-radius); + color: var(--color-text); + font-weight: var(--font-weight-semibold); + overflow: hidden; + opacity: 0; + transition: all 0.15s ease; +} + +.avatar-stack:hover .avatar-stack-overflow-chip { + width: 20px; + margin-left: -16px; + border-width: 1px; + opacity: 1; +} + +.avatar-stack-popup-trigger { + cursor: pointer; + background: none; + border: none; + padding: 0; + font: inherit; + color: inherit; +} + +.avatar-stack-popup-trigger:hover { + color: var(--color-primary); +} + +.avatar-stack-popup { + min-width: 200px; + display: flex; + flex-direction: column; + padding: 4px 0; +} + +.avatar-stack-popup > a { + padding: 6px 12px; + gap: 8px; +} + +.avatar-stack-popup > a:hover { + background: var(--color-hover); +} + +@media (max-width: 767.98px) { + .avatar-stack-names { + max-width: 80px; + } +} diff --git a/web_src/css/base.css b/web_src/css/base.css index 0716ad1913e..8b8cfac2d1f 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -386,14 +386,6 @@ a.label, color: var(--color-text-light-2); } -img.ui.avatar, -.ui.avatar img, -.ui.avatar svg { - border-radius: var(--border-radius); - object-fit: contain; - aspect-ratio: 1; -} - .full.height { flex-grow: 1; padding-bottom: var(--page-space-bottom); diff --git a/web_src/css/index.css b/web_src/css/index.css index a56982efcf7..6d9280c67f8 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -56,6 +56,7 @@ @import "./font_i18n.css"; @import "./base.css"; +@import "./avatar.css"; @import "./home.css"; @import "./install.css"; diff --git a/web_src/css/repo.css b/web_src/css/repo.css index b7cb5e1dcd1..0682290e1a6 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -1386,8 +1386,7 @@ tbody.commit-list { vertical-align: baseline; } -.message-wrapper, -.author-wrapper { +.message-wrapper { overflow: hidden; text-overflow: ellipsis; max-width: 100%; @@ -1395,12 +1394,6 @@ tbody.commit-list { vertical-align: middle; } -.author-wrapper { - max-width: 180px; - align-self: center; - white-space: nowrap; -} - .latest-commit .message-wrapper { max-width: calc(100% - 2.5rem); } @@ -1415,9 +1408,6 @@ tbody.commit-list { tr.commit-list { width: 100%; } - .author-wrapper { - max-width: 80px; - } } @media (min-width: 768px) and (max-width: 991.98px) { diff --git a/web_src/js/features/repo-commit.ts b/web_src/js/features/repo-commit.ts index 17130427a62..6103766202a 100644 --- a/web_src/js/features/repo-commit.ts +++ b/web_src/js/features/repo-commit.ts @@ -25,6 +25,22 @@ export function initCommitStatuses() { }); } +export function initAvatarStackPopup() { + registerGlobalInitFunc('initAvatarStackPopup', (el: HTMLElement) => { + const nextEl = el.nextElementSibling!; + if (!nextEl.matches('.tippy-target')) throw new Error('Expected next element to be a tippy target'); + createTippy(el, { + content: nextEl, + placement: 'bottom-start', + interactive: true, + role: 'dialog', + theme: 'menu', + trigger: 'click', + hideOnClick: true, + }); + }); +} + export function initCommitFileHistoryFollowRename() { registerGlobalInitFunc('initCommitHistoryFollowRename', (el: HTMLInputElement) => { el.addEventListener('change', () => { diff --git a/web_src/js/index.ts b/web_src/js/index.ts index a2994d6912d..6f081f30202 100644 --- a/web_src/js/index.ts +++ b/web_src/js/index.ts @@ -21,7 +21,7 @@ import {initMarkupContent} from './markup/content.ts'; import {initRepoFileView} from './features/file-view.ts'; import {initUserExternalLogins, initUserCheckAppUrl} from './features/user-auth.ts'; import {initRepoPullRequestReview, initRepoIssueFilterItemLabel} from './features/repo-issue.ts'; -import {initRepoEllipsisButton, initCommitStatuses, initCommitFileHistoryFollowRename} from './features/repo-commit.ts'; +import {initRepoEllipsisButton, initCommitStatuses, initAvatarStackPopup, initCommitFileHistoryFollowRename} from './features/repo-commit.ts'; import {initRepoTopicBar} from './features/repo-home.ts'; import {initAdminCommon} from './features/admin/common.ts'; import {initRepoCodeView} from './features/repo-code.ts'; @@ -146,6 +146,7 @@ const initPerformanceTracer = callInitFunctions([ initRepoRecentCommits, initCommitStatuses, + initAvatarStackPopup, initCaptcha, initUserCheckAppUrl, From ade76fe83868e9004be632148f7c625a91f3c1cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Mon, 8 Jun 2026 19:58:41 +0200 Subject: [PATCH 32/40] enhance: allow MathML core elements (#38034) Fixes #36352. --------- Co-authored-by: wxiaoguang --- modules/markup/sanitizer_default.go | 32 ++++++++++++++++++++++++ modules/markup/sanitizer_default_test.go | 5 +++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/modules/markup/sanitizer_default.go b/modules/markup/sanitizer_default.go index e38852a3d5a..9c7259dc8cc 100644 --- a/modules/markup/sanitizer_default.go +++ b/modules/markup/sanitizer_default.go @@ -63,6 +63,38 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy { policy.AllowAttrs("loading").OnElements("img") + // MathML Core (https://www.w3.org/TR/mathml-core/) + mathMLElements := []string{ + "math", + // token elements + "mi", "mn", "mo", "mtext", "mspace", "ms", + // layout elements + "mrow", "mfrac", "msqrt", "mroot", "mstyle", "merror", "mpadded", "mphantom", + // scripting elements + "msub", "msup", "msubsup", "munder", "mover", "munderover", "mmultiscripts", "mprescripts", "none", + // tabular elements + "mtable", "mtr", "mtd", + // semantic annotations + "semantics", "annotation", "annotation-xml", + } + policy.AllowAttrs("display", "alttext").OnElements("math") + policy.AllowAttrs( + // global presentation attributes + "dir", "displaystyle", "mathbackground", "mathcolor", "mathsize", "mathvariant", "scriptlevel", + // operator attributes + "accent", "accentunder", "fence", "form", "largeop", "lspace", "maxsize", "minsize", "movablelimits", "rspace", "separator", "stretchy", "symmetric", + // space and padding attributes + "depth", "height", "voffset", "width", + // fraction attribute + "linethickness", + // table attributes + "columnalign", "columnlines", "columnspacing", "frame", "framespacing", "rowalign", "rowlines", "rowspacing", + // cell attributes + "columnspan", + // annotation attribute + "encoding", + ).OnElements(mathMLElements...) + // Allow generally safe attributes (reference: https://github.com/jch/html-pipeline) generalSafeAttrs := []string{ "abbr", "accept", "accept-charset", diff --git a/modules/markup/sanitizer_default_test.go b/modules/markup/sanitizer_default_test.go index e66f00c02f6..e344a96722f 100644 --- a/modules/markup/sanitizer_default_test.go +++ b/modules/markup/sanitizer_default_test.go @@ -61,6 +61,9 @@ func TestSanitizer(t *testing.T) { // picture `c`, `c`, + // MathML + ``, ``, + // Disallow dangerous url schemes `bad`, `bad`, `bad`, `bad`, @@ -72,6 +75,6 @@ func TestSanitizer(t *testing.T) { } for i := 0; i < len(testCases); i += 2 { - assert.Equal(t, testCases[i+1], string(Sanitize(testCases[i]))) + assert.Equal(t, testCases[i+1], string(Sanitize(testCases[i])), "input: %s", testCases[i]) } } From d76a974b241cea7d9320bf0fdd4eb1c1e1271da7 Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Mon, 8 Jun 2026 20:18:58 +0200 Subject: [PATCH 33/40] feat(ssh): auto generate additional ssh keys (#33974) adds capabilities for gitea to generate ecdsa and ed25519 keys by default adds cli for built-in ssh key generation helpers closes: https://github.com/go-gitea/gitea/issues/33783 --------- Co-authored-by: Nicolas Co-authored-by: wxiaoguang Co-authored-by: Giteabot --- cmd/generate.go | 76 +++++++++++ cmd/helper.go | 17 +-- cmd/mailer.go | 8 +- modules/consts/asymkey.go | 12 ++ modules/generate/generate.go | 80 ++++++++++++ modules/setting/ssh.go | 5 +- modules/ssh/ssh.go | 101 +++++++------- modules/ssh/ssh_test.go | 123 ++++++++++++++++++ .../git_helper_for_declarative_test.go | 3 +- 9 files changed, 351 insertions(+), 74 deletions(-) create mode 100644 modules/consts/asymkey.go create mode 100644 modules/ssh/ssh_test.go diff --git a/cmd/generate.go b/cmd/generate.go index 01be73c2d11..239b75ba9c2 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -6,10 +6,12 @@ package cmd import ( "context" + "errors" "fmt" "os" "gitea.dev/modules/generate" + "gitea.dev/modules/ssh" "github.com/mattn/go-isatty" "github.com/urfave/cli/v3" @@ -21,6 +23,7 @@ func newGenerateCommand() *cli.Command { Usage: "Generate Gitea's secrets/keys/tokens", Commands: []*cli.Command{ newGenerateSecretCommand(), + newGenerateSSHCommand(), }, } } @@ -37,6 +40,17 @@ func newGenerateSecretCommand() *cli.Command { } } +func newGenerateSSHCommand() *cli.Command { + return &cli.Command{ + Name: "ssh", + Usage: "Generate ssh keys", + Commands: []*cli.Command{ + newGenerateSSHKeyCommand(), + newGenerateSSHHostKeysCommand(), + }, + } +} + func newGenerateInternalTokenCommand() *cli.Command { return &cli.Command{ Name: "INTERNAL_TOKEN", @@ -62,6 +76,30 @@ func newGenerateSecretKeyCommand() *cli.Command { } } +func newGenerateSSHKeyCommand() *cli.Command { + return &cli.Command{ + Name: "key", + Usage: "Generate a new ssh key", + Flags: []cli.Flag{ + &cli.IntFlag{Name: "bits", Aliases: []string{"b"}, Usage: "Number of bits in the key, ignored when key is ed25519"}, + &cli.StringFlag{Name: "type", Aliases: []string{"t"}, Value: "ed25519", Usage: "Specifies the type of key to create."}, + &cli.StringFlag{Name: "file", Aliases: []string{"f"}, Usage: "Specifies the path or base directory for the key file", Required: true}, + }, + Action: runGenerateKeyPair, + } +} + +func newGenerateSSHHostKeysCommand() *cli.Command { + return &cli.Command{ + Name: "host-keys", + Usage: "Generate host keys of all default key types (rsa, ecdsa, and ed25519) if they do not already exist.", + Flags: []cli.Flag{ + &cli.StringFlag{Name: "dir", Aliases: []string{"d"}, Usage: "Specifies the base directory for the key files", Required: true}, + }, + Action: runGenerateHostKey, + } +} + func runGenerateInternalToken(_ context.Context, c *cli.Command) error { internalToken, err := generate.NewInternalToken() if err != nil { @@ -103,3 +141,41 @@ func runGenerateSecretKey(_ context.Context, c *cli.Command) error { return nil } + +func runGenerateHostKey(_ context.Context, c *cli.Command) error { + file := c.String("dir") + info, err := os.Stat(file) + if errors.Is(err, os.ErrNotExist) { + if err = os.MkdirAll(file, 0o644); err != nil { + return err + } + } else if err != nil { + return err + } else if !info.IsDir() { + return errors.New("file already exists and is not a directory") + } + fmt.Fprintf(c.Writer, "Generating host keys in %s\n", file) + _, err = ssh.InitDefaultHostKeys(file) + return err +} + +func runGenerateKeyPair(_ context.Context, c *cli.Command) error { + file := c.String("file") + keyType := c.String("type") + + fmt.Fprintf(c.Writer, "Generating public/private %s key pair.\n", keyType) + + // Check if file exists to prevent overwriting + if _, err := os.Stat(file); err == nil { + if !confirm(c.Reader, c.Writer, "%s already exists.\nOverwrite (y/n)? ", file) { + fmt.Println("Aborting") + return nil + } + } + bits := c.Int("bits") + err := ssh.GenKeyPair(file, generate.SSHKeyType(keyType), bits) + if err == nil { + fmt.Printf("Your SSH key has been saved in %s\n", file) + } + return err +} diff --git a/cmd/helper.go b/cmd/helper.go index 9150e1c2338..37b20104379 100644 --- a/cmd/helper.go +++ b/cmd/helper.go @@ -38,22 +38,15 @@ func argsSet(c *cli.Command, args ...string) error { } // confirm waits for user input which confirms an action -func confirm() (bool, error) { +func confirm(stdin io.Reader, stdout io.Writer, msg string, args ...any) bool { var response string - - _, err := fmt.Scanln(&response) - if err != nil { - return false, err - } - + _, _ = fmt.Fprintf(stdout, msg, args...) + _, _ = fmt.Fscanln(stdin, &response) switch strings.ToLower(response) { case "y", "yes": - return true, nil - case "n", "no": - return false, nil - default: - return false, errors.New(response + " isn't a correct confirmation string") + return true } + return false } func initDB(ctx context.Context) error { diff --git a/cmd/mailer.go b/cmd/mailer.go index 61bd66c963a..d7b2f6b7bb1 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -22,14 +22,10 @@ func runSendMail(ctx context.Context, c *cli.Command) error { if !confirmSkipped { if len(body) == 0 { - fmt.Print("warning: Content is empty") + fmt.Println("warning: Content is empty") } - fmt.Print("Proceed with sending email? [Y/n] ") - isConfirmed, err := confirm() - if err != nil { - return err - } else if !isConfirmed { + if !confirm(c.Reader, c.Writer, "Proceed with sending email? [Y/n] ") { fmt.Println("The mail was not sent") return nil } diff --git a/modules/consts/asymkey.go b/modules/consts/asymkey.go new file mode 100644 index 00000000000..d6f19b2c53c --- /dev/null +++ b/modules/consts/asymkey.go @@ -0,0 +1,12 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package consts + +const ( + AsymKeyMinBitsRsa = 3071 // 3072-1 to tolerate the leading zero + AsymKeyMinBitsEC = 256 + + AsymKeyDefaultBitsRsa = 4096 // ssh-keygen command defaults to 3072 + AsymKeyDefaultBitsEcdsa = 256 +) diff --git a/modules/generate/generate.go b/modules/generate/generate.go index f2a5b366d8f..ca132e0756c 100644 --- a/modules/generate/generate.go +++ b/modules/generate/generate.go @@ -5,15 +5,23 @@ package generate import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" "crypto/rand" + "crypto/rsa" "encoding/base64" + "encoding/pem" "fmt" "io" "time" + "gitea.dev/modules/consts" "gitea.dev/modules/util" "github.com/golang-jwt/jwt/v5" + "golang.org/x/crypto/ssh" ) // NewInternalToken generate a new value intended to be used by INTERNAL_TOKEN. @@ -67,3 +75,75 @@ func NewJwtSecretWithBase64() ([]byte, string) { func NewSecretKey() (string, error) { return util.CryptoRandomString(64), nil } + +type SSHKeyType string + +const ( + SSHKeyRSA SSHKeyType = "rsa" + SSHKeyECDSA SSHKeyType = "ecdsa" + SSHKeyED25519 SSHKeyType = "ed25519" +) + +func NewSSHKey(keyType SSHKeyType, bits int) (ssh.PublicKey, *pem.Block, error) { + pub, priv, err := commonKeyGen(keyType, bits) + if err != nil { + return nil, nil, err + } + pemPriv, err := ssh.MarshalPrivateKey(priv, "") + if err != nil { + return nil, nil, err + } + sshPub, err := ssh.NewPublicKey(pub) + if err != nil { + return nil, nil, err + } + + return sshPub, pemPriv, nil +} + +// commonKeyGen is an abstraction over rsa, ecdsa, and ed25519 generating functions +func commonKeyGen(keyType SSHKeyType, bits int) (crypto.PublicKey, crypto.PrivateKey, error) { + switch keyType { + case SSHKeyRSA: + bits = util.IfZero(bits, consts.AsymKeyDefaultBitsRsa) + if bits < consts.AsymKeyMinBitsRsa { + return nil, nil, util.NewInvalidArgumentErrorf("invalid rsa bits: %d", bits) + } + privateKey, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return nil, nil, err + } + return &privateKey.PublicKey, privateKey, nil + case SSHKeyED25519: + return ed25519.GenerateKey(rand.Reader) + case SSHKeyECDSA: + bits = util.IfZero(bits, consts.AsymKeyDefaultBitsEcdsa) + if bits < consts.AsymKeyMinBitsEC { + return nil, nil, util.NewInvalidArgumentErrorf("invalid elliptic-curve bits: %d", bits) + } + curve, err := getEllipticCurve(bits) + if err != nil { + return nil, nil, err + } + privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + return nil, nil, err + } + return &privateKey.PublicKey, privateKey, nil + default: + return nil, nil, util.NewInvalidArgumentErrorf("unknown key type: %s", keyType) + } +} + +func getEllipticCurve(bits int) (elliptic.Curve, error) { + switch bits { + case 256: + return elliptic.P256(), nil + case 384: + return elliptic.P384(), nil + case 521: + return elliptic.P521(), nil + default: + return nil, util.NewInvalidArgumentErrorf("unsupported elliptic-curve bits: %d", bits) + } +} diff --git a/modules/setting/ssh.go b/modules/setting/ssh.go index 948ce773c52..683c90f2243 100644 --- a/modules/setting/ssh.go +++ b/modules/setting/ssh.go @@ -9,6 +9,7 @@ import ( "text/template" "time" + "gitea.dev/modules/consts" "gitea.dev/modules/log" "gitea.dev/modules/util" @@ -52,8 +53,8 @@ var SSH = struct { Domain: "", Port: 22, MinimumKeySizeCheck: true, - MinimumKeySizes: map[string]int{"ed25519": 256, "ed25519-sk": 256, "ecdsa": 256, "ecdsa-sk": 256, "rsa": 3071}, - ServerHostKeys: []string{"ssh/gitea.rsa", "ssh/gogs.rsa"}, + MinimumKeySizes: map[string]int{"ed25519": consts.AsymKeyMinBitsEC, "ed25519-sk": consts.AsymKeyMinBitsEC, "ecdsa": consts.AsymKeyMinBitsEC, "ecdsa-sk": consts.AsymKeyMinBitsEC, "rsa": consts.AsymKeyMinBitsRsa}, + ServerHostKeys: []string{"ssh/gitea.rsa", "ssh/gitea.ed25519", "ssh/gitea.ecdsa", "ssh/gogs.rsa"}, AuthorizedKeysCommandTemplate: "{{.AppPath}} --config={{.CustomConf}} serv key-{{.Key.ID}}", PerWriteTimeout: PerWriteTimeout, PerWritePerKbTimeout: PerWritePerKbTimeout, diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go index 94e29969b02..78e4b0805b3 100644 --- a/modules/ssh/ssh.go +++ b/modules/ssh/ssh.go @@ -6,9 +6,6 @@ package ssh import ( "bytes" "context" - "crypto/rand" - "crypto/rsa" - "crypto/x509" "encoding/pem" "errors" "io" @@ -23,11 +20,11 @@ import ( "syscall" asymkey_model "gitea.dev/models/asymkey" + "gitea.dev/modules/generate" "gitea.dev/modules/graceful" "gitea.dev/modules/log" "gitea.dev/modules/process" "gitea.dev/modules/setting" - "gitea.dev/modules/util" "github.com/gliderlabs/ssh" gossh "golang.org/x/crypto/ssh" @@ -59,7 +56,7 @@ func getExitStatusFromError(err error) int { return 0 } - exitErr, ok := err.(*exec.ExitError) + exitErr, ok := errors.AsType[*exec.ExitError](err) if !ok { return 1 } @@ -322,7 +319,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { } // sshConnectionFailed logs a failed connection -// - this mainly exists to give a nice function name in logging +// - this mainly exists to give a nice function name in logging func sshConnectionFailed(conn net.Conn, err error) { // Log the underlying error with a specific message log.Warn("Failed connection from %s with error: %v", conn.RemoteAddr(), err) @@ -351,40 +348,37 @@ func Listen(host string, port int, ciphers, keyExchanges, macs []string) { }, } - keys := make([]string, 0, len(setting.SSH.ServerHostKeys)) + hostKeyFiles := make([]string, 0, len(setting.SSH.ServerHostKeys)) for _, key := range setting.SSH.ServerHostKeys { - isExist, err := util.IsExist(key) + _, err := os.Stat(key) if err != nil { - log.Fatal("Unable to check if %s exists. Error: %v", setting.SSH.ServerHostKeys, err) - } - if isExist { - keys = append(keys, key) + if !errors.Is(err, os.ErrNotExist) { + log.Fatal("Unable to check if %s exists. Error: %v", setting.SSH.ServerHostKeys, err) + } + continue } + hostKeyFiles = append(hostKeyFiles, key) } - if len(keys) == 0 { - filePath := filepath.Dir(setting.SSH.ServerHostKeys[0]) - - if err := os.MkdirAll(filePath, os.ModePerm); err != nil { - log.Error("Failed to create dir %s: %v", filePath, err) + if len(hostKeyFiles) == 0 { + hostKeyDir := filepath.Dir(setting.SSH.ServerHostKeys[0]) + err := os.MkdirAll(hostKeyDir, os.ModePerm) + if err != nil { + log.Error("Failed to create dir %s: %v", hostKeyDir, err) } - - err := GenKeyPair(setting.SSH.ServerHostKeys[0]) + hostKeyFiles, err = InitDefaultHostKeys(hostKeyDir) if err != nil { log.Fatal("Failed to generate private key: %v", err) } - log.Trace("New private key is generated: %s", setting.SSH.ServerHostKeys[0]) - keys = append(keys, setting.SSH.ServerHostKeys[0]) } - for _, key := range keys { - log.Info("Adding SSH host key: %s", key) - err := srv.SetOption(ssh.HostKeyFile(key)) + for _, keyFile := range hostKeyFiles { + log.Info("Adding SSH host key: %s", keyFile) + err := srv.SetOption(ssh.HostKeyFile(keyFile)) if err != nil { log.Error("Failed to set Host Key. %s", err) } } - go func() { _, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Service: Built-in SSH server", process.SystemProcessType, true) defer finished() @@ -395,43 +389,44 @@ func Listen(host string, port int, ciphers, keyExchanges, macs []string) { // GenKeyPair make a pair of public and private keys for SSH access. // Public key is encoded in the format for inclusion in an OpenSSH authorized_keys file. // Private Key generated is PEM encoded -func GenKeyPair(keyPath string) error { - privateKey, err := rsa.GenerateKey(rand.Reader, 4096) +func GenKeyPair(keyPath string, keyType generate.SSHKeyType, bits int) error { + publicKey, privateKeyPEM, err := generate.NewSSHKey(keyType, bits) if err != nil { return err } - privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)} - f, err := os.OpenFile(keyPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) - if err != nil { - return err - } - defer func() { - if err = f.Close(); err != nil { - log.Error("Close: %v", err) - } - }() - - if err := pem.Encode(f, privateKeyPEM); err != nil { - return err - } - - // generate public key - pub, err := gossh.NewPublicKey(&privateKey.PublicKey) + public := gossh.MarshalAuthorizedKey(publicKey) + privateKeyBuf := &bytes.Buffer{} + err = pem.Encode(privateKeyBuf, privateKeyPEM) if err != nil { return err } - public := gossh.MarshalAuthorizedKey(pub) - p, err := os.OpenFile(keyPath+".pub", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) + err = os.WriteFile(keyPath, privateKeyBuf.Bytes(), 0o600) if err != nil { return err } - defer func() { - if err = p.Close(); err != nil { - log.Error("Close: %v", err) - } - }() - _, err = p.Write(public) - return err + + return os.WriteFile(keyPath+".pub", public, 0o644) +} + +// InitDefaultHostKeys mirrors how ssh-keygen -A operates +// it runs checks if public and private keys are already defined and creates new ones if not present +// key naming does not follow the OpenSSH convention due to existing settings being gitea.{KeyType} so generation follows gitea convention +func InitDefaultHostKeys(path string) (keyFiles []string, _ error) { + var errs []error + keyTypes := []generate.SSHKeyType{generate.SSHKeyRSA, generate.SSHKeyECDSA, generate.SSHKeyED25519} + for _, keyType := range keyTypes { + keyPath := filepath.Join(path, "gitea."+string(keyType)) + _, errStatPriv := os.Stat(keyPath) + if errStatPriv != nil { + err := GenKeyPair(keyPath, keyType, 0) + if err != nil { + errs = append(errs, err) + continue + } + } + keyFiles = append(keyFiles, keyPath) + } + return keyFiles, errors.Join(errs...) } diff --git a/modules/ssh/ssh_test.go b/modules/ssh/ssh_test.go new file mode 100644 index 00000000000..ad9ac813d4c --- /dev/null +++ b/modules/ssh/ssh_test.go @@ -0,0 +1,123 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package ssh + +import ( + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "os" + "path/filepath" + "testing" + + "gitea.dev/modules/generate" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + gossh "golang.org/x/crypto/ssh" +) + +func TestGenKeyPair(t *testing.T) { + testCases := []struct { + keyType generate.SSHKeyType + expectedType any + }{ + { + keyType: generate.SSHKeyRSA, + expectedType: &rsa.PrivateKey{}, + }, + { + keyType: generate.SSHKeyED25519, + expectedType: &ed25519.PrivateKey{}, + }, + { + keyType: generate.SSHKeyECDSA, + expectedType: &ecdsa.PrivateKey{}, + }, + } + tmpDir := t.TempDir() + for _, tc := range testCases { + name := "gitea." + string(tc.keyType) + fn := filepath.Join(tmpDir, name) + t.Run("Generate "+name, func(t *testing.T) { + require.NoError(t, GenKeyPair(fn, tc.keyType, 0)) + + bytes, err := os.ReadFile(fn) + require.NoError(t, err) + + privateKey, err := gossh.ParseRawPrivateKey(bytes) + require.NoError(t, err) + assert.IsType(t, tc.expectedType, privateKey) + }) + } + t.Run("Generate unknown key type", func(t *testing.T) { + err := GenKeyPair(t.TempDir()+"gitea.badkey", "badkey", 0) + require.Error(t, err) + }) +} + +func TestInitKeys(t *testing.T) { + tempDir := t.TempDir() + + keyTypes := []string{"rsa", "ecdsa", "ed25519"} + for _, keyType := range keyTypes { + privKeyPath := filepath.Join(tempDir, "gitea."+keyType) + pubKeyPath := filepath.Join(tempDir, "gitea."+keyType+".pub") + assert.NoFileExists(t, privKeyPath) + assert.NoFileExists(t, pubKeyPath) + } + + // Test basic creation + keyFiles, err := InitDefaultHostKeys(tempDir) + require.NoError(t, err) + assert.Len(t, keyFiles, len(keyTypes)) + + metadata := map[string]os.FileInfo{} + for _, keyType := range keyTypes { + privKeyPath := filepath.Join(tempDir, "gitea."+keyType) + pubKeyPath := filepath.Join(tempDir, "gitea."+keyType+".pub") + info, err := os.Stat(privKeyPath) + require.NoError(t, err) + metadata[privKeyPath] = info + + info, err = os.Stat(pubKeyPath) + require.NoError(t, err) + metadata[pubKeyPath] = info + } + + // Test recreation on missing private key and noop for missing pub key + require.NoError(t, os.Remove(filepath.Join(tempDir, "gitea.ecdsa.pub"))) + require.NoError(t, os.Remove(filepath.Join(tempDir, "gitea.ed25519"))) + + keyFiles, err = InitDefaultHostKeys(tempDir) + require.NoError(t, err) + assert.Len(t, keyFiles, len(keyTypes)) + + for _, keyType := range keyTypes { + privKeyPath := filepath.Join(tempDir, "gitea."+keyType) + pubKeyPath := filepath.Join(tempDir, "gitea."+keyType+".pub") + + infoPriv, err := os.Stat(privKeyPath) + require.NoError(t, err) + + switch keyType { + case "rsa": + // No modification to RSA key + infoPub, err := os.Stat(pubKeyPath) + require.NoError(t, err) + assert.Equal(t, metadata[privKeyPath], infoPriv) + assert.Equal(t, metadata[pubKeyPath], infoPub) + case "ecdsa": + // ECDSA public key should be missing, private unchanged + assert.Equal(t, metadata[privKeyPath], infoPriv) + assert.NoFileExists(t, pubKeyPath) + case "ed25519": + // ed25519 private key was removed, so both keys regenerated + infoPub, err := os.Stat(pubKeyPath) + require.NoError(t, err) + assert.NotEqual(t, metadata[privKeyPath], infoPriv) + assert.NotEqual(t, metadata[pubKeyPath], infoPub) + } + } +} diff --git a/tests/integration/git_helper_for_declarative_test.go b/tests/integration/git_helper_for_declarative_test.go index 3b374a094ee..bd0aedf6c91 100644 --- a/tests/integration/git_helper_for_declarative_test.go +++ b/tests/integration/git_helper_for_declarative_test.go @@ -15,6 +15,7 @@ import ( "testing" "time" + "gitea.dev/modules/generate" "gitea.dev/modules/git" "gitea.dev/modules/git/gitcmd" "gitea.dev/modules/setting" @@ -33,7 +34,7 @@ func withKeyFile(t *testing.T, keyname string, callback func(string)) { assert.NoError(t, err) keyFile := filepath.Join(tmpDir, keyname) - err = ssh.GenKeyPair(keyFile) + err = ssh.GenKeyPair(keyFile, generate.SSHKeyECDSA, 0) assert.NoError(t, err) err = os.WriteFile(filepath.Join(tmpDir, "ssh"), []byte("#!/bin/bash\n"+ From e01af366e2f7df7713f0a1448be7f2a01f90bfe8 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Mon, 8 Jun 2026 11:30:55 -0700 Subject: [PATCH 34/40] fix(deps): update npm dependencies (#38035) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | @​codemirror/autocomplete | [`6.20.2` → `6.20.3`](https://renovatebot.com/diffs/npm/@codemirror%2fautocomplete/6.20.2/6.20.3) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@codemirror%2fautocomplete/6.20.3?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@codemirror%2fautocomplete/6.20.2/6.20.3?slim=true) | | [eslint-plugin-vue](https://eslint.vuejs.org) ([source](https://redirect.github.com/vuejs/eslint-plugin-vue)) | [`10.9.1` → `10.9.2`](https://renovatebot.com/diffs/npm/eslint-plugin-vue/10.9.1/10.9.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-plugin-vue/10.9.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-plugin-vue/10.9.1/10.9.2?slim=true) | --- ### Release Notes
    vuejs/eslint-plugin-vue (eslint-plugin-vue) ### [`v10.9.2`](https://redirect.github.com/vuejs/eslint-plugin-vue/blob/HEAD/CHANGELOG.md#1092) [Compare Source](https://redirect.github.com/vuejs/eslint-plugin-vue/compare/v10.9.1...v10.9.2) ##### Patch Changes - Fixed [`vue/custom-event-name-casing`](https://eslint.vuejs.org/rules/custom-event-name-casing.html) to check segments of colon-separated event names like `update:foo-bar` ([#​3079](https://redirect.github.com/vuejs/eslint-plugin-vue/pull/3079)) - Fixed [`vue/one-component-per-file`](https://eslint.vuejs.org/rules/one-component-per-file.html) to not report functions not imported from Vue ([#​3063](https://redirect.github.com/vuejs/eslint-plugin-vue/pull/3063)) - Fixed [`vue/prefer-import-from-vue`](https://eslint.vuejs.org/rules/prefer-import-from-vue.html) to not report imports/exports of names that are not re-exported by `vue` ([#​3081](https://redirect.github.com/vuejs/eslint-plugin-vue/pull/3081)) - Fixed [`vue/return-in-computed-property`](https://eslint.vuejs.org/rules/return-in-computed-property.html) and [`vue/require-render-return`](https://eslint.vuejs.org/rules/require-render-return.html) to not report exhaustive switch statements when TypeScript type information is available ([#​3067](https://redirect.github.com/vuejs/eslint-plugin-vue/pull/3067))
    --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Only on Monday (`* * * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://redirect.github.com/renovatebot/renovate). --- package.json | 4 ++-- pnpm-lock.yaml | 60 +++++++++++++++++++++++++------------------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index cc6c270a842..139de62f5a4 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "@citation-js/plugin-bibtex": "0.7.21", "@citation-js/plugin-csl": "0.7.22", "@citation-js/plugin-software-formats": "0.6.2", - "@codemirror/autocomplete": "6.20.2", + "@codemirror/autocomplete": "6.20.3", "@codemirror/commands": "6.10.3", "@codemirror/lang-json": "6.0.2", "@codemirror/lang-markdown": "6.5.0", @@ -102,7 +102,7 @@ "eslint-plugin-regexp": "3.1.0", "eslint-plugin-sonarjs": "4.0.3", "eslint-plugin-unicorn": "64.0.0", - "eslint-plugin-vue": "10.9.1", + "eslint-plugin-vue": "10.9.2", "eslint-plugin-vue-scoped-css": "3.1.1", "eslint-plugin-wc": "3.1.0", "globals": "17.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ce5d84c9823..d7489e4f595 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,8 +21,8 @@ importers: specifier: 0.6.2 version: 0.6.2 '@codemirror/autocomplete': - specifier: 6.20.2 - version: 6.20.2 + specifier: 6.20.3 + version: 6.20.3 '@codemirror/commands': specifier: 6.10.3 version: 6.10.3 @@ -82,13 +82,13 @@ importers: version: 6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0) '@replit/codemirror-lang-nix': specifier: 6.0.1 - version: 6.0.1(@codemirror/autocomplete@6.20.2)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0)(@lezer/common@1.5.2)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.10) + version: 6.0.1(@codemirror/autocomplete@6.20.3)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0)(@lezer/common@1.5.2)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.10) '@replit/codemirror-lang-svelte': specifier: 6.0.0 - version: 6.0.0(@codemirror/autocomplete@6.20.2)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.11)(@codemirror/lang-javascript@6.2.5)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0)(@lezer/common@1.5.2)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.10) + version: 6.0.0(@codemirror/autocomplete@6.20.3)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.11)(@codemirror/lang-javascript@6.2.5)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0)(@lezer/common@1.5.2)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.10) '@replit/codemirror-vscode-keymap': specifier: 6.0.2 - version: 6.0.2(@codemirror/autocomplete@6.20.2)(@codemirror/commands@6.10.3)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.6)(@codemirror/search@6.7.0)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0) + version: 6.0.2(@codemirror/autocomplete@6.20.3)(@codemirror/commands@6.10.3)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.6)(@codemirror/search@6.7.0)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0) '@resvg/resvg-wasm': specifier: 2.6.2 version: 2.6.2 @@ -289,8 +289,8 @@ importers: specifier: 64.0.0 version: 64.0.0(eslint@10.4.1(jiti@2.7.0)) eslint-plugin-vue: - specifier: 10.9.1 - version: 10.9.1(@stylistic/eslint-plugin@5.10.0(eslint@10.4.1(jiti@2.7.0)))(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.1(jiti@2.7.0))) + specifier: 10.9.2 + version: 10.9.2(@stylistic/eslint-plugin@5.10.0(eslint@10.4.1(jiti@2.7.0)))(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.1(jiti@2.7.0))) eslint-plugin-vue-scoped-css: specifier: 3.1.1 version: 3.1.1(eslint@10.4.1(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.1(jiti@2.7.0))) @@ -446,8 +446,8 @@ packages: resolution: {integrity: sha512-3XQOO3u4WXY/7AWZyQ+9SuBzS8bYTlJ+NF1uCgrZO64g36nK5iIc5YV9cBl2TL2QhHF6S36nvAsXsj5fX9FeHw==} engines: {node: '>=14.0.0'} - '@codemirror/autocomplete@6.20.2': - resolution: {integrity: sha512-G5FPkgIiLjOgZMjqVjvuKQ1rGPtHogLldJr33eFJdVLtmwY+giGrlv/ewljLz6b9BSQLkjxuwBc6g6omDM+YxQ==} + '@codemirror/autocomplete@6.20.3': + resolution: {integrity: sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==} '@codemirror/commands@6.10.3': resolution: {integrity: sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==} @@ -2699,8 +2699,8 @@ packages: postcss-styl: optional: true - eslint-plugin-vue@10.9.1: - resolution: {integrity: sha512-cHB0Tf4Duvzwecwd/AqWzZvF/QszE13BhjVUpVXWCy9AeMR5GjkAjP3i85vqgLgOuTmkHR1OJ5oMeqLHtuw8zg==} + eslint-plugin-vue@10.9.2: + resolution: {integrity: sha512-4g7ZP3pYcuqd7Zp0pzUKcos0W+RkjBz4EGdhJ92FcYk6v03Ti/GK5NwjgsjxHK+98eXDbHeK7VtX1az7/8doZA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@stylistic/eslint-plugin': ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 @@ -5053,7 +5053,7 @@ snapshots: '@citation-js/date': 0.5.1 '@citation-js/name': 0.4.2 - '@codemirror/autocomplete@6.20.2': + '@codemirror/autocomplete@6.20.3': dependencies: '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 @@ -5083,7 +5083,7 @@ snapshots: '@codemirror/lang-css@6.3.1': dependencies: - '@codemirror/autocomplete': 6.20.2 + '@codemirror/autocomplete': 6.20.3 '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@lezer/common': 1.5.2 @@ -5091,7 +5091,7 @@ snapshots: '@codemirror/lang-go@6.0.1': dependencies: - '@codemirror/autocomplete': 6.20.2 + '@codemirror/autocomplete': 6.20.3 '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@lezer/common': 1.5.2 @@ -5099,7 +5099,7 @@ snapshots: '@codemirror/lang-html@6.4.11': dependencies: - '@codemirror/autocomplete': 6.20.2 + '@codemirror/autocomplete': 6.20.3 '@codemirror/lang-css': 6.3.1 '@codemirror/lang-javascript': 6.2.5 '@codemirror/language': 6.12.3 @@ -5116,7 +5116,7 @@ snapshots: '@codemirror/lang-javascript@6.2.5': dependencies: - '@codemirror/autocomplete': 6.20.2 + '@codemirror/autocomplete': 6.20.3 '@codemirror/language': 6.12.3 '@codemirror/lint': 6.9.6 '@codemirror/state': 6.6.0 @@ -5126,7 +5126,7 @@ snapshots: '@codemirror/lang-jinja@6.0.1': dependencies: - '@codemirror/autocomplete': 6.20.2 + '@codemirror/autocomplete': 6.20.3 '@codemirror/lang-html': 6.4.11 '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 @@ -5150,7 +5150,7 @@ snapshots: '@codemirror/lang-liquid@6.3.2': dependencies: - '@codemirror/autocomplete': 6.20.2 + '@codemirror/autocomplete': 6.20.3 '@codemirror/lang-html': 6.4.11 '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 @@ -5161,7 +5161,7 @@ snapshots: '@codemirror/lang-markdown@6.5.0': dependencies: - '@codemirror/autocomplete': 6.20.2 + '@codemirror/autocomplete': 6.20.3 '@codemirror/lang-html': 6.4.11 '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 @@ -5179,7 +5179,7 @@ snapshots: '@codemirror/lang-python@6.2.1': dependencies: - '@codemirror/autocomplete': 6.20.2 + '@codemirror/autocomplete': 6.20.3 '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@lezer/common': 1.5.2 @@ -5200,7 +5200,7 @@ snapshots: '@codemirror/lang-sql@6.10.0': dependencies: - '@codemirror/autocomplete': 6.20.2 + '@codemirror/autocomplete': 6.20.3 '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@lezer/common': 1.5.2 @@ -5225,7 +5225,7 @@ snapshots: '@codemirror/lang-xml@6.1.0': dependencies: - '@codemirror/autocomplete': 6.20.2 + '@codemirror/autocomplete': 6.20.3 '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.43.0 @@ -5234,7 +5234,7 @@ snapshots: '@codemirror/lang-yaml@6.1.3': dependencies: - '@codemirror/autocomplete': 6.20.2 + '@codemirror/autocomplete': 6.20.3 '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@lezer/common': 1.5.2 @@ -5754,9 +5754,9 @@ snapshots: '@codemirror/state': 6.6.0 '@codemirror/view': 6.43.0 - '@replit/codemirror-lang-nix@6.0.1(@codemirror/autocomplete@6.20.2)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0)(@lezer/common@1.5.2)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.10)': + '@replit/codemirror-lang-nix@6.0.1(@codemirror/autocomplete@6.20.3)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0)(@lezer/common@1.5.2)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.10)': dependencies: - '@codemirror/autocomplete': 6.20.2 + '@codemirror/autocomplete': 6.20.3 '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.43.0 @@ -5764,9 +5764,9 @@ snapshots: '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.10 - '@replit/codemirror-lang-svelte@6.0.0(@codemirror/autocomplete@6.20.2)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.11)(@codemirror/lang-javascript@6.2.5)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0)(@lezer/common@1.5.2)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.10)': + '@replit/codemirror-lang-svelte@6.0.0(@codemirror/autocomplete@6.20.3)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.11)(@codemirror/lang-javascript@6.2.5)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0)(@lezer/common@1.5.2)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.10)': dependencies: - '@codemirror/autocomplete': 6.20.2 + '@codemirror/autocomplete': 6.20.3 '@codemirror/lang-css': 6.3.1 '@codemirror/lang-html': 6.4.11 '@codemirror/lang-javascript': 6.2.5 @@ -5778,9 +5778,9 @@ snapshots: '@lezer/javascript': 1.5.4 '@lezer/lr': 1.4.10 - '@replit/codemirror-vscode-keymap@6.0.2(@codemirror/autocomplete@6.20.2)(@codemirror/commands@6.10.3)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.6)(@codemirror/search@6.7.0)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0)': + '@replit/codemirror-vscode-keymap@6.0.2(@codemirror/autocomplete@6.20.3)(@codemirror/commands@6.10.3)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.6)(@codemirror/search@6.7.0)(@codemirror/state@6.6.0)(@codemirror/view@6.43.0)': dependencies: - '@codemirror/autocomplete': 6.20.2 + '@codemirror/autocomplete': 6.20.3 '@codemirror/commands': 6.10.3 '@codemirror/language': 6.12.3 '@codemirror/lint': 6.9.6 @@ -7708,7 +7708,7 @@ snapshots: postcss-selector-parser: 7.1.1 vue-eslint-parser: 10.4.0(eslint@10.4.1(jiti@2.7.0)) - eslint-plugin-vue@10.9.1(@stylistic/eslint-plugin@5.10.0(eslint@10.4.1(jiti@2.7.0)))(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.1(jiti@2.7.0))): + eslint-plugin-vue@10.9.2(@stylistic/eslint-plugin@5.10.0(eslint@10.4.1(jiti@2.7.0)))(@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.1(jiti@2.7.0))): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) eslint: 10.4.1(jiti@2.7.0) From b1c088e9cf3fe587633c3d3a2554fe41c7bf7f51 Mon Sep 17 00:00:00 2001 From: bircni Date: Mon, 8 Jun 2026 20:49:06 +0200 Subject: [PATCH 35/40] enhance(actions): Make Summary UI more beautiful with more infos (#37824) ## Summary - Redesign the Actions run summary header to follow GitHub Actions layout: trigger info on the left, Status / Total duration / Artifacts columns inline on the right - Expose trigger user avatar, pull request link, and PR head branch info from the run view API - Update the workflow graph header to show the workflow filename (linked to the run workflow file) and `on: `, while keeping the jobs/dependencies/success stats line - Remove the redundant commit/workflow metadata row below the run title; that information now lives in the summary bar New: Old: Replaces https://github.com/go-gitea/gitea/pull/36721 --------- Co-authored-by: Giteabot --- options/locale/locale_en-US.json | 5 +- routers/web/devtest/mock_actions.go | 43 ++-- routers/web/repo/actions/view.go | 193 +++++++++++++--- routers/web/repo/actions/view_test.go | 62 ++++++ templates/repo/actions/view_component.tmpl | 4 + .../js/components/ActionRunSummaryView.vue | 206 ++++++++++++++++-- web_src/js/components/ActionRunView.ts | 2 + web_src/js/components/RepoActionView.vue | 73 ++++--- web_src/js/components/WorkflowGraph.vue | 39 +++- web_src/js/features/repo-actions.ts | 4 + web_src/js/modules/gitea-actions.ts | 6 + web_src/js/svg.ts | 2 + 12 files changed, 524 insertions(+), 115 deletions(-) diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index d9139f17e31..a629f11adf1 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -3804,7 +3804,10 @@ "actions.runs.latest": "Latest", "actions.runs.latest_attempt": "Latest attempt", "actions.runs.triggered_via": "Triggered via %s", - "actions.runs.total_duration": "Total duration:", + "actions.runs.rerun_triggered": "Re-run triggered", + "actions.runs.back_to_pull_request": "Back to pull request", + "actions.runs.back_to_workflow": "Back to workflow", + "actions.runs.total_duration": "Total duration", "actions.runs.workflow_dependencies": "Workflow Dependencies", "actions.runs.graph_jobs_count_1": "%d job", "actions.runs.graph_jobs_count_n": "%d jobs", diff --git a/routers/web/devtest/mock_actions.go b/routers/web/devtest/mock_actions.go index 61f20a3ef48..185cdc8acbb 100644 --- a/routers/web/devtest/mock_actions.go +++ b/routers/web/devtest/mock_actions.go @@ -87,29 +87,39 @@ func MockActionsRunsJobs(ctx *context.Context) { resp.State.Run.TitleHTML = `mock run title link` resp.State.Run.Link = setting.AppSubURL + "/devtest/repo-action-view/runs/" + strconv.FormatInt(runID, 10) resp.State.Run.CanDeleteArtifact = true - resp.State.Run.WorkflowID = "workflow-id" - resp.State.Run.WorkflowLink = "./workflow-link" + resp.State.Run.WorkflowID = "workflow-id.yml" resp.State.Run.TriggerEvent = "push" + user2, _ := user_model.GetUserByID(ctx, 2) + if user2 == nil { + user2 = &user_model.User{Name: "user2"} + } + user3, _ := user_model.GetUserByID(ctx, 3) + if user3 == nil { + user3 = &user_model.User{Name: "user3"} + } resp.State.Run.Commit = actions.ViewCommit{ ShortSha: "ccccdddd", Link: "./commit-link", Pusher: actions.ViewUser{ - DisplayName: "pusher user", - Link: "./pusher-link", + DisplayName: user2.GetDisplayName(), + Link: user2.HomeLink(), + AvatarLink: user2.AvatarLinkWithSize(ctx, 16), }, Branch: actions.ViewBranch{ - Name: "commit-branch", + Name: "user2:commit-branch", Link: "./branch-link", IsDeleted: false, }, } + resp.State.Run.PullRequest = &actions.ViewPullRequest{ + Index: "#37658", + Link: "./pull/37658", + } now := time.Now() currentAttemptNum := int64(1) if attemptID > 0 { currentAttemptNum = attemptID } - user2 := &user_model.User{Name: "user2"} - user3 := &user_model.User{Name: "user3"} attempts := []*actions_model.ActionRunAttempt{{ Attempt: 1, Status: actions_model.StatusSuccess, @@ -168,15 +178,16 @@ func MockActionsRunsJobs(ctx *context.Context) { } } resp.State.Run.Attempts = append(resp.State.Run.Attempts, &actions.ViewRunAttempt{ - Attempt: attempt.Attempt, - Status: attempt.Status.String(), - Done: attempt.Status.IsDone(), - Link: link, - Current: current, - Latest: attempt.Attempt == latestAttempt.Attempt, - TriggeredAt: attempt.Created.AsTime().Unix(), - TriggerUserName: attempt.TriggerUser.GetDisplayName(), - TriggerUserLink: attempt.TriggerUser.HomeLink(), + Attempt: attempt.Attempt, + Status: attempt.Status.String(), + Done: attempt.Status.IsDone(), + Link: link, + Current: current, + Latest: attempt.Attempt == latestAttempt.Attempt, + TriggeredAt: attempt.Created.AsTime().Unix(), + TriggerUserName: attempt.TriggerUser.GetDisplayName(), + TriggerUserLink: attempt.TriggerUser.HomeLink(), + TriggerUserAvatar: attempt.TriggerUser.AvatarLinkWithSize(ctx, 16), }) } isLatestAttempt := currentAttemptNum == latestAttempt.Attempt diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index da997444490..9f8477d4c03 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -20,14 +20,18 @@ import ( actions_model "gitea.dev/models/actions" "gitea.dev/models/db" git_model "gitea.dev/models/git" + issues_model "gitea.dev/models/issues" repo_model "gitea.dev/models/repo" "gitea.dev/models/unit" "gitea.dev/modules/actions" "gitea.dev/modules/base" + "gitea.dev/modules/cache" "gitea.dev/modules/git" "gitea.dev/modules/httplib" + "gitea.dev/modules/json" "gitea.dev/modules/log" "gitea.dev/modules/storage" + api "gitea.dev/modules/structs" "gitea.dev/modules/templates" "gitea.dev/modules/translation" "gitea.dev/modules/util" @@ -306,6 +310,7 @@ type ViewResponse struct { Attempts []*ViewRunAttempt `json:"attempts"` Jobs []*ViewJob `json:"jobs"` Commit ViewCommit `json:"commit"` + PullRequest *ViewPullRequest `json:"pullRequest,omitempty"` // Summary view: run duration and trigger time/event Duration string `json:"duration"` TriggeredAt int64 `json:"triggeredAt"` // unix seconds for relative time @@ -340,15 +345,21 @@ type ViewJob struct { } type ViewRunAttempt struct { - Attempt int64 `json:"attempt"` - Status string `json:"status"` - Done bool `json:"done"` - Link string `json:"link"` - Current bool `json:"current"` - Latest bool `json:"latest"` - TriggeredAt int64 `json:"triggeredAt"` - TriggerUserName string `json:"triggerUserName"` - TriggerUserLink string `json:"triggerUserLink"` + Attempt int64 `json:"attempt"` + Status string `json:"status"` + Done bool `json:"done"` + Link string `json:"link"` + Current bool `json:"current"` + Latest bool `json:"latest"` + TriggeredAt int64 `json:"triggeredAt"` + TriggerUserName string `json:"triggerUserName"` + TriggerUserLink string `json:"triggerUserLink"` + TriggerUserAvatar string `json:"triggerUserAvatar"` +} + +type ViewPullRequest struct { + Index string `json:"index"` + Link string `json:"link"` } type ViewCommit struct { @@ -361,6 +372,7 @@ type ViewCommit struct { type ViewUser struct { DisplayName string `json:"displayName"` Link string `json:"link"` + AvatarLink string `json:"avatarLink,omitempty"` } type ViewBranch struct { @@ -388,6 +400,132 @@ type ViewStepLogLine struct { Timestamp float64 `json:"timestamp"` } +func viewPullRequestFromRun(ctx context.Context, run *actions_model.ActionRun, prPayload *api.PullRequestPayload) *ViewPullRequest { + if run.Repo == nil { + return nil + } + refName := git.RefName(run.Ref) + if refName.IsPull() { + return &ViewPullRequest{ + Index: "#" + refName.ShortName(), + Link: run.RefLink(), + } + } + if prPayload != nil && prPayload.Index > 0 { + return &ViewPullRequest{ + Index: fmt.Sprintf("#%d", prPayload.Index), + Link: fmt.Sprintf("%s/pulls/%d", run.Repo.Link(), prPayload.Index), + } + } + // Push-triggered run: surface an open PR whose head matches this branch so + // users coming from a PR's check details can navigate back to it. + if refName.IsBranch() { + prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, run.RepoID, refName.ShortName()) + if err != nil { + log.Error("GetUnmergedPullRequestsByHeadInfo: %v", err) + } else if len(prs) == 1 { + pr := prs[0] + if err := pr.LoadBaseRepo(ctx); err != nil { + log.Error("LoadBaseRepo: %v", err) + return nil + } + return &ViewPullRequest{ + Index: fmt.Sprintf("#%d", pr.Index), + Link: fmt.Sprintf("%s/pulls/%d", pr.BaseRepo.Link(), pr.Index), + } + } + } + return nil +} + +func viewSummaryBranchFromRun(ctx context.Context, run *actions_model.ActionRun, prPayload *api.PullRequestPayload) ViewBranch { + refName := git.RefName(run.Ref) + if prPayload != nil && prPayload.PullRequest != nil && prPayload.PullRequest.Head != nil { + head := prPayload.PullRequest.Head + name := head.Name + if name == "" { + name = git.RefName(head.Ref).ShortName() + } + if head.Repository != nil && run.Repo != nil && head.RepoID > 0 && head.RepoID != run.Repo.ID { + ownerName := "" + if head.Repository.Owner != nil { + ownerName = head.Repository.Owner.UserName + } else if head.Repository.FullName != "" { + ownerName, _, _ = strings.Cut(head.Repository.FullName, "/") + } + if ownerName != "" && !strings.Contains(name, ":") { + name = ownerName + ":" + name + } + } + link := "" + if head.Repository != nil && head.Ref != "" { + repoLink := head.Repository.Link + if repoLink == "" { + repoLink = head.Repository.HTMLURL + } + if repoLink != "" { + link = repoLink + "/src/" + git.RefName(head.Ref).RefWebLinkPath() + } + } + return ViewBranch{Name: name, Link: link} + } + + branch := ViewBranch{ + Name: run.PrettyRef(), + Link: run.RefLink(), + } + if refName.IsBranch() { + b, err := git_model.GetBranch(ctx, run.RepoID, refName.ShortName()) + if err != nil && !git_model.IsErrBranchNotExist(err) { + log.Error("GetBranch: %v", err) + } else if git_model.IsErrBranchNotExist(err) || (b != nil && b.IsDeleted) { + branch.IsDeleted = true + } + } + return branch +} + +// actionsSummaryRefCacheTTL bounds how long the resolved PR/branch summary is +// cached. ViewPost is polled every second, but this metadata is stable for a +// run, so a short TTL collapses the repeated DB lookups while staying fresh +// enough for the navigation links. +const actionsSummaryRefCacheTTL = 10 // seconds + +type viewSummaryRefInfo struct { + PullRequest *ViewPullRequest `json:"pullRequest"` + Branch ViewBranch `json:"branch"` +} + +// getViewSummaryRefInfo resolves the run's pull request and head branch summary, +// caching the result briefly so the per-second poll does not hit the database on +// every request (GetUnmergedPullRequestsByHeadInfo / GetBranch). +func getViewSummaryRefInfo(ctx context.Context, run *actions_model.ActionRun) viewSummaryRefInfo { + compute := func() viewSummaryRefInfo { + // parse the event payload once and share it between both resolvers + prPayload, _ := run.GetPullRequestEventPayload() // nil unless this is a pull request event + return viewSummaryRefInfo{ + PullRequest: viewPullRequestFromRun(ctx, run, prPayload), + Branch: viewSummaryBranchFromRun(ctx, run, prPayload), + } + } + c := cache.GetCache() + if c == nil { + return compute() + } + cacheKey := fmt.Sprintf("actions_run_summary_ref:%d", run.ID) + if cached, ok := c.Get(cacheKey); ok && cached != "" { + var info viewSummaryRefInfo + if err := json.Unmarshal([]byte(cached), &info); err == nil { + return info + } + } + info := compute() + if data, err := json.Marshal(info); err == nil { + _ = c.Put(cacheKey, string(data), actionsSummaryRefCacheTTL) + } + return info +} + func ViewPost(ctx *context_module.Context) { run, attempt, jobs := getCurrentRunJobsByPathParam(ctx) if ctx.Written() { @@ -482,42 +620,33 @@ func fillViewRunResponseSummary(ctx *context_module.Context, resp *ViewResponse, } for _, runAttempt := range attempts { resp.State.Run.Attempts = append(resp.State.Run.Attempts, &ViewRunAttempt{ - Attempt: runAttempt.Attempt, - Status: runAttempt.Status.String(), - Done: runAttempt.Status.IsDone(), - Link: getRunViewLink(run, runAttempt), - Current: runAttempt.ID == attempt.ID, - Latest: runAttempt.ID == run.LatestAttemptID, - TriggeredAt: runAttempt.Created.AsTime().Unix(), - TriggerUserName: runAttempt.TriggerUser.GetDisplayName(), - TriggerUserLink: runAttempt.TriggerUser.HomeLink(), + Attempt: runAttempt.Attempt, + Status: runAttempt.Status.String(), + Done: runAttempt.Status.IsDone(), + Link: getRunViewLink(run, runAttempt), + Current: runAttempt.ID == attempt.ID, + Latest: runAttempt.ID == run.LatestAttemptID, + TriggeredAt: runAttempt.Created.AsTime().Unix(), + TriggerUserName: runAttempt.TriggerUser.GetDisplayName(), + TriggerUserLink: runAttempt.TriggerUser.HomeLink(), + TriggerUserAvatar: runAttempt.TriggerUser.AvatarLinkWithSize(ctx, 16), }) } pusher := ViewUser{ DisplayName: run.TriggerUser.GetDisplayName(), Link: run.TriggerUser.HomeLink(), - } - branch := ViewBranch{ - Name: run.PrettyRef(), - Link: run.RefLink(), - } - refName := git.RefName(run.Ref) - if refName.IsBranch() { - b, err := git_model.GetBranch(ctx, ctx.Repo.Repository.ID, refName.ShortName()) - if err != nil && !git_model.IsErrBranchNotExist(err) { - log.Error("GetBranch: %v", err) - } else if git_model.IsErrBranchNotExist(err) || (b != nil && b.IsDeleted) { - branch.IsDeleted = true - } + AvatarLink: run.TriggerUser.AvatarLinkWithSize(ctx, 16), } + refInfo := getViewSummaryRefInfo(ctx, run) resp.State.Run.Commit = ViewCommit{ ShortSha: base.ShortSha(run.CommitSHA), Link: fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA), Pusher: pusher, - Branch: branch, + Branch: refInfo.Branch, } + resp.State.Run.PullRequest = refInfo.PullRequest resp.State.Run.TriggerEvent = run.TriggerEvent // Legacy runs (LatestAttemptID == 0) have no attempt; their artifacts all share run_attempt_id=0, diff --git a/routers/web/repo/actions/view_test.go b/routers/web/repo/actions/view_test.go index 737ac5e1b3a..020930eb38c 100644 --- a/routers/web/repo/actions/view_test.go +++ b/routers/web/repo/actions/view_test.go @@ -7,6 +7,8 @@ import ( "testing" actions_model "gitea.dev/models/actions" + repo_model "gitea.dev/models/repo" + api "gitea.dev/modules/structs" "gitea.dev/modules/timeutil" "gitea.dev/modules/translation" @@ -14,6 +16,66 @@ import ( "github.com/stretchr/testify/require" ) +func TestViewPullRequestFromRun(t *testing.T) { + repo := &repo_model.Repository{ID: 1, OwnerName: "owner", Name: "repo"} + + t.Run("pull ref", func(t *testing.T) { + run := &actions_model.ActionRun{Repo: repo, Ref: "refs/pull/123/head"} + assert.Equal(t, &ViewPullRequest{Index: "#123", Link: "/owner/repo/pulls/123"}, viewPullRequestFromRun(t.Context(), run, nil)) + }) + + t.Run("pull request event payload", func(t *testing.T) { + // a non-pull ref forces the payload branch instead of the ref branch + run := &actions_model.ActionRun{Repo: repo, Ref: "refs/heads/feature"} + payload := &api.PullRequestPayload{Index: 42} + assert.Equal(t, &ViewPullRequest{Index: "#42", Link: "/owner/repo/pulls/42"}, viewPullRequestFromRun(t.Context(), run, payload)) + }) + + t.Run("nil repo", func(t *testing.T) { + run := &actions_model.ActionRun{Ref: "refs/pull/1/head"} + assert.Nil(t, viewPullRequestFromRun(t.Context(), run, nil)) + }) +} + +func TestViewSummaryBranchFromRun(t *testing.T) { + repo := &repo_model.Repository{ID: 1, OwnerName: "owner", Name: "repo"} + + t.Run("pull request event same repo", func(t *testing.T) { + run := &actions_model.ActionRun{Repo: repo, Ref: "refs/pull/7/head"} + payload := &api.PullRequestPayload{ + PullRequest: &api.PullRequest{Head: &api.PRBranchInfo{ + Name: "feature", + Ref: "refs/heads/feature", + RepoID: 1, + Repository: &api.Repository{Link: "/owner/repo"}, + }}, + } + assert.Equal(t, ViewBranch{Name: "feature", Link: "/owner/repo/src/branch/feature"}, viewSummaryBranchFromRun(t.Context(), run, payload)) + }) + + t.Run("pull request event from fork prefixes owner", func(t *testing.T) { + run := &actions_model.ActionRun{Repo: repo, Ref: "refs/pull/7/head"} + payload := &api.PullRequestPayload{ + PullRequest: &api.PullRequest{Head: &api.PRBranchInfo{ + Name: "feature", + Ref: "refs/heads/feature", + RepoID: 2, + Repository: &api.Repository{ + Link: "/forkowner/repo", + Owner: &api.User{UserName: "forkowner"}, + }, + }}, + } + assert.Equal(t, ViewBranch{Name: "forkowner:feature", Link: "/forkowner/repo/src/branch/feature"}, viewSummaryBranchFromRun(t.Context(), run, payload)) + }) + + t.Run("push to tag does not query branch", func(t *testing.T) { + // a tag ref is not a branch, so no GetBranch DB lookup happens + run := &actions_model.ActionRun{Repo: repo, Ref: "refs/tags/v1.0.0"} + assert.Equal(t, ViewBranch{Name: "v1.0.0", Link: "/owner/repo/src/tag/v1.0.0"}, viewSummaryBranchFromRun(t.Context(), run, nil)) + }) +} + func TestConvertToViewModel(t *testing.T) { task := &actions_model.ActionTask{ Status: actions_model.StatusSuccess, diff --git a/templates/repo/actions/view_component.tmpl b/templates/repo/actions/view_component.tmpl index 2ed47ad9df8..8b8a6dfeff0 100644 --- a/templates/repo/actions/view_component.tmpl +++ b/templates/repo/actions/view_component.tmpl @@ -18,6 +18,10 @@ data-locale-expand-caller-jobs="{{ctx.Locale.Tr "actions.runs.expand_caller_jobs"}}" data-locale-collapse-caller-jobs="{{ctx.Locale.Tr "actions.runs.collapse_caller_jobs"}}" data-locale-triggered-via="{{ctx.Locale.Tr "actions.runs.triggered_via"}}" + data-locale-rerun-triggered="{{ctx.Locale.Tr "actions.runs.rerun_triggered"}}" + data-locale-back-to-pull-request="{{ctx.Locale.Tr "actions.runs.back_to_pull_request"}}" + data-locale-back-to-workflow="{{ctx.Locale.Tr "actions.runs.back_to_workflow"}}" + data-locale-status-label="{{ctx.Locale.Tr "actions.runs.status"}}" data-locale-total-duration="{{ctx.Locale.Tr "actions.runs.total_duration"}}" data-locale-run-details="{{ctx.Locale.Tr "actions.runs.run_details"}}" data-locale-workflow-file="{{ctx.Locale.Tr "actions.runs.workflow_file"}}" diff --git a/web_src/js/components/ActionRunSummaryView.vue b/web_src/js/components/ActionRunSummaryView.vue index 17b7e2802e1..e3813e9e174 100644 --- a/web_src/js/components/ActionRunSummaryView.vue +++ b/web_src/js/components/ActionRunSummaryView.vue @@ -1,5 +1,4 @@