diff --git a/.editorconfig b/.editorconfig index 13aa8d50f0..bf1cf757cc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -25,6 +25,10 @@ insert_final_newline = false [templates/user/auth/oidc_wellknown.tmpl] indent_style = space +[templates/shared/actions/runner_badge_*.tmpl] +# editconfig lint requires these XML-like files to have charset defined, but the files don't have. +charset = unset + [Makefile] indent_style = tab diff --git a/.gitattributes b/.gitattributes index e218bbe25d..afd02555f5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,3 +8,4 @@ /vendor/** -text -eol linguist-vendored /web_src/js/vendor/** -text -eol linguist-vendored Dockerfile.* linguist-language=Dockerfile +Makefile.* linguist-language=Makefile diff --git a/.github/labeler.yml b/.github/labeler.yml index 49679d28cf..750f2b2cfb 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -51,7 +51,6 @@ modifies/internal: - ".github/**" - ".gitea/**" - ".devcontainer/**" - - "build.go" - "build/**" - "contrib/**" diff --git a/.github/workflows/pull-e2e-tests.yml b/.github/workflows/pull-e2e-tests.yml deleted file mode 100644 index 4f806e93bd..0000000000 --- a/.github/workflows/pull-e2e-tests.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: e2e-tests - -on: - pull_request: - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - files-changed: - uses: ./.github/workflows/files-changed.yml - - test-e2e: - # the "test-e2e" won't pass, and it seems that there is no useful test, so skip - # if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true' - if: false - needs: files-changed - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - uses: actions/setup-go@v6 - with: - go-version-file: go.mod - check-latest: true - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v5 - with: - node-version: 24 - - run: make deps-frontend frontend deps-backend - - run: pnpm exec playwright install --with-deps - - run: make test-e2e-sqlite - timeout-minutes: 40 - env: - USE_REPO_TEST_DIR: 1 diff --git a/.gitignore b/.gitignore index 7e8e5f84a7..11af4543bd 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,6 @@ prime/ # Ignore worktrees when working on multiple branches .worktrees/ + +# A Makefile for custom make targets +Makefile.local diff --git a/.golangci.yml b/.golangci.yml index 483843bc55..60482c415f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -14,6 +14,7 @@ linters: - govet - ineffassign - mirror + - modernize - nakedret - nolintlint - perfsprint @@ -55,6 +56,7 @@ linters: disabled-checks: - ifElseChain - singleCaseSwitch # Every time this occurred in the code, there was no other way. + - deprecatedComment # conflicts with go-swagger comments revive: severity: error rules: @@ -107,6 +109,11 @@ linters: - require-error usetesting: os-temp-dir: true + modernize: + disable: + - stringsbuilder + perfsprint: + concat-loop: false exclusions: generated: lax presets: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 96e05c578f..9d696bf6b1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -166,19 +166,19 @@ Here's how to run the test suite: - code lint -| | | -| :-------------------- | :---------------------------------------------------------------- | +| | | +| :-------------------- | :--------------------------------------------------------------------------- | |``make lint`` | lint everything (not needed if you only change the front- **or** backend) | -|``make lint-frontend`` | lint frontend files | -|``make lint-backend`` | lint backend files | +|``make lint-frontend`` | lint frontend files | +|``make lint-backend`` | lint backend files | - run tests (we suggest running them on Linux) -| Command | Action | | -| :------------------------------------- | :----------------------------------------------- | ------------ | -|``make test[\#SpecificTestName]`` | run unit test(s) | | -|``make test-sqlite[\#SpecificTestName]``| run [integration](tests/integration) test(s) for SQLite |[More details](tests/integration/README.md) | -|``make test-e2e-sqlite[\#SpecificTestName]``| run [end-to-end](tests/e2e) test(s) for SQLite |[More details](tests/e2e/README.md) | +| Command | Action | | +| :------------------------------------------ | :------------------------------------------------------- | ------------------------------------------- | +|``make test[\#SpecificTestName]`` | run unit test(s) | | +|``make test-sqlite[\#SpecificTestName]`` | run [integration](tests/integration) test(s) for SQLite | [More details](tests/integration/README.md) | +|``make test-e2e-sqlite[\#SpecificTestName]`` | run [end-to-end](tests/e2e) test(s) for SQLite | [More details](tests/e2e/README.md) | ## Translation diff --git a/Makefile b/Makefile index 7531e56d83..4a7e73e582 100644 --- a/Makefile +++ b/Makefile @@ -32,16 +32,15 @@ XGO_VERSION := go-1.25.x AIR_PACKAGE ?= github.com/air-verse/air@v1 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.2 -GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.5.0 +GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.6.0 GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.7.0 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1 XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1 GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 -ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1 +ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.9 GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.20.0 -GOPLS_MODERNIZE_PACKAGE ?= golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@v0.20.0 DOCKER_IMAGE ?= gitea/gitea DOCKER_TAG ?= latest @@ -199,6 +198,10 @@ TEST_MSSQL_DBNAME ?= gitea TEST_MSSQL_USERNAME ?= sa TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1 +# Include local Makefile +# Makefile.local is listed in .gitignore +sinclude Makefile.local + .PHONY: all all: build @@ -276,19 +279,6 @@ fmt-check: fmt exit 1; \ fi -.PHONY: fix -fix: ## apply automated fixes to Go code - $(GO) run $(GOPLS_MODERNIZE_PACKAGE) -fix ./... - -.PHONY: fix-check -fix-check: fix - @diff=$$(git diff --color=always $(GO_SOURCES)); \ - if [ -n "$$diff" ]; then \ - echo "Please run 'make fix' and commit the result:"; \ - printf "%s" "$${diff}"; \ - exit 1; \ - fi - .PHONY: $(TAGS_EVIDENCE) $(TAGS_EVIDENCE): @mkdir -p $(MAKE_EVIDENCE_DIR) @@ -328,7 +318,7 @@ checks: checks-frontend checks-backend ## run various consistency checks checks-frontend: lockfile-check svg-check ## check frontend files .PHONY: checks-backend -checks-backend: tidy-check swagger-check fmt-check fix-check swagger-validate security-check ## check backend files +checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check ## check backend files .PHONY: lint lint: lint-frontend lint-backend lint-spell ## lint everything @@ -374,6 +364,10 @@ lint-swagger: node_modules ## lint swagger files lint-md: node_modules ## lint markdown files $(NODE_VARS) pnpm exec markdownlint *.md +.PHONY: lint-md-fix +lint-md-fix: node_modules ## lint markdown files and fix issues + $(NODE_VARS) pnpm exec markdownlint --fix *.md + .PHONY: lint-spell lint-spell: ## lint spelling @go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -error $(SPELLCHECK_FILES) @@ -400,8 +394,7 @@ lint-go-windows: .PHONY: lint-go-gitea-vet lint-go-gitea-vet: ## lint go files with gitea-vet @echo "Running gitea-vet..." - @GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet - @$(GO) vet -vettool=gitea-vet ./... + @$(GO) vet -vettool="$(shell GOOS= GOARCH= go tool -n gitea-vet)" ./... .PHONY: lint-go-gopls lint-go-gopls: ## lint go files with gopls @@ -852,7 +845,6 @@ deps-tools: ## install tool dependencies $(GO) install $(GOVULNCHECK_PACKAGE) & \ $(GO) install $(ACTIONLINT_PACKAGE) & \ $(GO) install $(GOPLS_PACKAGE) & \ - $(GO) install $(GOPLS_MODERNIZE_PACKAGE) & \ wait node_modules: pnpm-lock.yaml diff --git a/build.go b/build.go deleted file mode 100644 index e81ba54690..0000000000 --- a/build.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build vendor - -package main - -// Libraries that are included to vendor utilities used during Makefile build. -// These libraries will not be included in a normal compilation. - -import ( - // for vet - _ "code.gitea.io/gitea-vet" -) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 5fee78af54..2ade845590 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -567,6 +567,11 @@ ENABLED = true ;; Alternative location to specify OAuth2 authentication secret. You cannot specify both this and JWT_SECRET, and must pick one ;JWT_SECRET_URI = file:/etc/gitea/oauth2_jwt_secret ;; +;; The "issuer" claim identifies the principal that issued the JWT. +;; Gitea 1.25 makes it default to "ROOT_URL without the last slash" to follow the standard. +;; If you have old logins from before 1.25, you may want to set it to the old (non-standard) value "ROOT_URL with the last slash". +;JWT_CLAIM_ISSUER = +;; ;; Lifetime of an OAuth2 access token in seconds ;ACCESS_TOKEN_EXPIRATION_TIME = 3600 ;; @@ -2329,7 +2334,7 @@ LEVEL = Info ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Resynchronize pre-receive, update and post-receive hooks of all repositories. +;; Resynchronize git hooks of all repositories (pre-receive, update, post-receive, proc-receive, ...) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;[cron.resync_all_hooks] ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/eslint.config.ts b/eslint.config.ts index ed2bc65245..548e650bc4 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -935,7 +935,6 @@ export default defineConfig([ }, { files: ['**/*.test.ts', 'web_src/js/test/setup.ts'], - // @ts-expect-error - https://github.com/vitest-dev/eslint-plugin-vitest/issues/737 plugins: {vitest}, languageOptions: {globals: globals.vitest}, rules: { diff --git a/flake.lock b/flake.lock index 5cb95c1aed..4cbc85b87a 100644 --- a/flake.lock +++ b/flake.lock @@ -1,23 +1,5 @@ { "nodes": { - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "nixpkgs": { "locked": { "lastModified": 1760038930, @@ -36,24 +18,8 @@ }, "root": { "inputs": { - "flake-utils": "flake-utils", "nixpkgs": "nixpkgs" } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 588f608ffc..6fb3891963 100644 --- a/flake.nix +++ b/flake.nix @@ -1,73 +1,94 @@ { inputs = { nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; - flake-utils.url = "github:numtide/flake-utils"; }; outputs = - { nixpkgs, flake-utils, ... }: - flake-utils.lib.eachDefaultSystem ( - system: - let - pkgs = nixpkgs.legacyPackages.${system}; - in - { - devShells.default = - with pkgs; - let - # only bump toolchain versions here - go = go_1_25; - nodejs = nodejs_24; - python3 = python312; - pnpm = pnpm_10; - - # Platform-specific dependencies - linuxOnlyInputs = lib.optionals pkgs.stdenv.isLinux [ - glibc.static - ]; + { nixpkgs, ... }: + let + supportedSystems = [ + "aarch64-darwin" + "aarch64-linux" + "x86_64-darwin" + "x86_64-linux" + ]; - linuxOnlyEnv = lib.optionalAttrs pkgs.stdenv.isLinux { - CFLAGS = "-I${glibc.static.dev}/include"; - LDFLAGS = "-L ${glibc.static}/lib"; + forEachSupportedSystem = + f: + nixpkgs.lib.genAttrs supportedSystems ( + system: + let + pkgs = import nixpkgs { + inherit system; }; in - pkgs.mkShell ( - { - buildInputs = [ - # generic - git - git-lfs - gnumake - gnused - gnutar - gzip - zip + f { inherit pkgs; } + ); + in + { + devShells = forEachSupportedSystem ( + { pkgs, ... }: + { + default = + let + inherit (pkgs) lib; + + # only bump toolchain versions here + go = pkgs.go_1_25; + nodejs = pkgs.nodejs_24; + python3 = pkgs.python312; + pnpm = pkgs.pnpm_10; - # frontend - nodejs - pnpm - cairo - pixman - pkg-config + # Platform-specific dependencies + linuxOnlyInputs = lib.optionals pkgs.stdenv.isLinux [ + pkgs.glibc.static + ]; - # linting - python3 - uv + linuxOnlyEnv = lib.optionalAttrs pkgs.stdenv.isLinux { + CFLAGS = "-I${pkgs.glibc.static.dev}/include"; + LDFLAGS = "-L ${pkgs.glibc.static}/lib"; + }; + in + pkgs.mkShell { + packages = + with pkgs; + [ + # generic + git + git-lfs + gnumake + gnused + gnutar + gzip + zip - # backend - go - gofumpt - sqlite - ] - ++ linuxOnlyInputs; + # frontend + nodejs + pnpm + cairo + pixman + pkg-config - GO = "${go}/bin/go"; - GOROOT = "${go}/share/go"; + # linting + python3 + uv - TAGS = "sqlite sqlite_unlock_notify"; - STATIC = "true"; - } - // linuxOnlyEnv - ); - } - ); + # backend + go + gofumpt + sqlite + ] + ++ linuxOnlyInputs; + + env = { + GO = "${go}/bin/go"; + GOROOT = "${go}/share/go"; + + TAGS = "sqlite sqlite_unlock_notify"; + STATIC = "true"; + } + // linuxOnlyEnv; + }; + } + ); + }; } diff --git a/go.mod b/go.mod index 81187804a3..51cf47b2d3 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module code.gitea.io/gitea -go 1.25.3 +go 1.25.0 + +toolchain go1.25.4 // rfc5280 said: "The serial number is an integer assigned by the CA to each certificate." // But some CAs use negative serial number, just relax the check. related: @@ -9,14 +11,13 @@ godebug x509negativeserial=1 require ( code.gitea.io/actions-proto-go v0.4.1 - code.gitea.io/gitea-vet v0.2.3 code.gitea.io/sdk/gitea v0.22.0 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 connectrpc.com/connect v1.18.1 gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed gitea.com/go-chi/cache v0.2.1 gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 - gitea.com/go-chi/session v0.0.0-20250926004215-636cadd82e15 + gitea.com/go-chi/session v0.0.0-20251124165456-68e0254e989e gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 github.com/42wim/httpsig v1.2.3 @@ -116,13 +117,13 @@ require ( github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-meta v1.1.0 gitlab.com/gitlab-org/api/client-go v0.142.4 - golang.org/x/crypto v0.42.0 + golang.org/x/crypto v0.45.0 golang.org/x/image v0.30.0 - golang.org/x/net v0.44.0 + golang.org/x/net v0.47.0 golang.org/x/oauth2 v0.30.0 - golang.org/x/sync v0.17.0 - golang.org/x/sys v0.37.0 - golang.org/x/text v0.30.0 + golang.org/x/sync v0.18.0 + golang.org/x/sys v0.38.0 + golang.org/x/text v0.31.0 google.golang.org/grpc v1.75.0 google.golang.org/protobuf v1.36.8 gopkg.in/ini.v1 v1.67.0 @@ -135,6 +136,7 @@ require ( require ( cloud.google.com/go/compute/metadata v0.8.0 // indirect + code.gitea.io/gitea-vet v0.2.3 // indirect dario.cat/mergo v1.0.2 // indirect filippo.io/edwards25519 v1.1.0 // indirect git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect @@ -279,9 +281,9 @@ require ( go.uber.org/zap/exp v0.3.0 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect - golang.org/x/mod v0.28.0 // indirect + golang.org/x/mod v0.29.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.37.0 // indirect + golang.org/x/tools v0.38.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect @@ -307,3 +309,5 @@ exclude github.com/gofrs/uuid v4.0.0+incompatible exclude github.com/goccy/go-json v0.4.11 exclude github.com/satori/go.uuid v1.2.0 + +tool code.gitea.io/gitea-vet diff --git a/go.sum b/go.sum index 02a710e7f0..86fe782ae7 100644 --- a/go.sum +++ b/go.sum @@ -41,8 +41,8 @@ gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g= gitea.com/go-chi/cache v0.2.1/go.mod h1:Qic0HZ8hOHW62ETGbonpwz8WYypj9NieU9659wFUJ8Q= gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR98IP2KZQKRzJJiV7aTeMAFwaWo= gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098/go.mod h1:LjzIOHlRemuUyO7WR12fmm18VZIlCAaOt9L3yKw40pk= -gitea.com/go-chi/session v0.0.0-20250926004215-636cadd82e15 h1:qFYmz05u/s9664o7+XEgrlHXSPQ4uHO8/ccZGUb1uxA= -gitea.com/go-chi/session v0.0.0-20250926004215-636cadd82e15/go.mod h1:0iEpFKnwO5dG0aF98O4eq6FMsAiXkNBaDIlUOlq4BtM= +gitea.com/go-chi/session v0.0.0-20251124165456-68e0254e989e h1:4bugwPyGMLvblEm3pZ8fZProSPVxE4l0UXF2Kv6IJoY= +gitea.com/go-chi/session v0.0.0-20251124165456-68e0254e989e/go.mod h1:KDvcfMUoXfATPHs2mbMoXFTXT45/FAFAS39waz9tPk0= gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:+wWBi6Qfruqu7xJgjOIrKVQGiLUZdpKYCZewJ4clqhw= gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:VyMQP6ue6MKHM8UsOXfNfuMKD0oSAWZdXVcpHIN2yaY= gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHqdhS7keYWioqfmxdnfblFDTGoOwcZ+o= @@ -840,8 +840,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -878,8 +878,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= -golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -908,8 +908,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= -golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -932,8 +932,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -975,8 +975,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -987,8 +987,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= -golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1002,8 +1002,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= @@ -1039,8 +1039,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= -golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/models/actions/main_test.go b/models/actions/main_test.go index 5d5089e3bb..4af483813a 100644 --- a/models/actions/main_test.go +++ b/models/actions/main_test.go @@ -13,6 +13,8 @@ func TestMain(m *testing.M) { unittest.MainTest(m, &unittest.TestOptions{ FixtureFiles: []string{ "action_runner_token.yml", + "action_run.yml", + "repository.yml", }, }) } diff --git a/models/actions/run.go b/models/actions/run.go index 4da6958e2d..be332d6857 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -193,9 +193,11 @@ func (run *ActionRun) IsSchedule() bool { return run.ScheduleID > 0 } +// UpdateRepoRunsNumbers updates the number of runs and closed runs of a repository. func UpdateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error { _, err := db.GetEngine(ctx).ID(repo.ID). NoAutoTime(). + Cols("num_action_runs", "num_closed_action_runs"). SetExpr("num_action_runs", builder.Select("count(*)").From("action_run"). Where(builder.Eq{"repo_id": repo.ID}), diff --git a/models/actions/run_test.go b/models/actions/run_test.go new file mode 100644 index 0000000000..bd2b92f4f6 --- /dev/null +++ b/models/actions/run_test.go @@ -0,0 +1,35 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + + "github.com/stretchr/testify/assert" +) + +func TestUpdateRepoRunsNumbers(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + // update the number to a wrong one, the original is 3 + _, err := db.GetEngine(t.Context()).ID(4).Cols("num_closed_action_runs").Update(&repo_model.Repository{ + NumClosedActionRuns: 2, + }) + assert.NoError(t, err) + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + assert.Equal(t, 4, repo.NumActionRuns) + assert.Equal(t, 2, repo.NumClosedActionRuns) + + // now update will correct them, only num_actionr_runs and num_closed_action_runs should be updated + err = UpdateRepoRunsNumbers(t.Context(), repo) + assert.NoError(t, err) + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + assert.Equal(t, 5, repo.NumActionRuns) + assert.Equal(t, 3, repo.NumClosedActionRuns) +} diff --git a/models/activities/notification.go b/models/activities/notification.go index b482e6020a..8a830c5aa2 100644 --- a/models/activities/notification.go +++ b/models/activities/notification.go @@ -386,7 +386,7 @@ func SetNotificationStatus(ctx context.Context, notificationID int64, user *user notification.Status = status - _, err = db.GetEngine(ctx).ID(notificationID).Update(notification) + _, err = db.GetEngine(ctx).ID(notificationID).Cols("status").Update(notification) return notification, err } diff --git a/models/asymkey/gpg_key_verify.go b/models/asymkey/gpg_key_verify.go index 55c64973b4..5df0265c88 100644 --- a/models/asymkey/gpg_key_verify.go +++ b/models/asymkey/gpg_key_verify.go @@ -78,7 +78,7 @@ func VerifyGPGKey(ctx context.Context, ownerID int64, keyID, token, signature st } key.Verified = true - if _, err := db.GetEngine(ctx).ID(key.ID).SetExpr("verified", true).Update(new(GPGKey)); err != nil { + if _, err := db.GetEngine(ctx).ID(key.ID).Cols("verified").Update(key); err != nil { return "", err } diff --git a/models/fixtures/action_run.yml b/models/fixtures/action_run.yml index b9688dd5f5..44b131c961 100644 --- a/models/fixtures/action_run.yml +++ b/models/fixtures/action_run.yml @@ -159,3 +159,23 @@ updated: 1683636626 need_approval: 0 approved_by: 0 +- + id: 805 + title: "update actions" + repo_id: 4 + owner_id: 1 + workflow_id: "artifact.yaml" + index: 191 + trigger_user_id: 1 + ref: "refs/heads/master" + commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0" + event: "push" + trigger_event: "push" + is_fork_pull_request: 0 + status: 5 + started: 1683636528 + stopped: 1683636626 + created: 1683636108 + updated: 1683636626 + need_approval: 0 + approved_by: 0 diff --git a/models/fixtures/action_run_job.yml b/models/fixtures/action_run_job.yml index 337e83605a..c5aeb4931c 100644 --- a/models/fixtures/action_run_job.yml +++ b/models/fixtures/action_run_job.yml @@ -143,3 +143,17 @@ status: 1 started: 1683636528 stopped: 1683636626 +- + id: 206 + run_id: 805 + repo_id: 4 + owner_id: 1 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 + is_fork_pull_request: 0 + name: job_2 + attempt: 1 + job_id: job_2 + task_id: 56 + status: 3 + started: 1683636528 + stopped: 1683636626 diff --git a/models/fixtures/action_task.yml b/models/fixtures/action_task.yml index e09fd6f2ec..a28ddd0add 100644 --- a/models/fixtures/action_task.yml +++ b/models/fixtures/action_task.yml @@ -197,3 +197,22 @@ log_length: 707 log_size: 90179 log_expired: 0 +- + id: 56 + attempt: 1 + runner_id: 1 + status: 3 # 3 is the status code for "cancelled" + started: 1683636528 + stopped: 1683636626 + repo_id: 4 + owner_id: 1 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 + is_fork_pull_request: 0 + token_hash: 6d8ef48297195edcc8e22c70b3020eaa06c52976db67d39b4240c64a69a2cc1508825121b7b8394e48e00b1bf3718b2aaaab + token_salt: eeeeeeee + token_last_eight: eeeeeeee + log_filename: artifact-test2/2f/47.log + log_in_storage: 1 + log_length: 707 + log_size: 90179 + log_expired: 0 diff --git a/models/fixtures/repo_unit.yml b/models/fixtures/repo_unit.yml index f8bb8ef0d3..4c3e37500f 100644 --- a/models/fixtures/repo_unit.yml +++ b/models/fixtures/repo_unit.yml @@ -740,3 +740,10 @@ type: 10 config: "{}" created_unix: 946684810 + +- + id: 112 + repo_id: 4 + type: 10 + config: "{}" + created_unix: 946684810 diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index 552a78cbd2..dfa514db37 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -110,6 +110,8 @@ num_closed_milestones: 0 num_projects: 0 num_closed_projects: 1 + num_action_runs: 4 + num_closed_action_runs: 3 is_private: false is_empty: false is_archived: false diff --git a/models/git/branch.go b/models/git/branch.go index 43be70f073..91b2fcd864 100644 --- a/models/git/branch.go +++ b/models/git/branch.go @@ -381,7 +381,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str } // 1. update branch in database - if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{ + if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Cols("name").Update(&Branch{ Name: to, }); err != nil { return err diff --git a/models/git/protected_branch.go b/models/git/protected_branch.go index 13e1ced0e1..1085c14cae 100644 --- a/models/git/protected_branch.go +++ b/models/git/protected_branch.go @@ -466,11 +466,13 @@ func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, c return currentWhitelist, nil } + prUserIDs, err := access_model.GetUserIDsWithUnitAccess(ctx, repo, perm.AccessModeRead, unit.TypePullRequests) + if err != nil { + return nil, err + } whitelist = make([]int64, 0, len(newWhitelist)) for _, userID := range newWhitelist { - if reader, err := access_model.IsRepoReader(ctx, repo, userID); err != nil { - return nil, err - } else if !reader { + if !prUserIDs.Contains(userID) { continue } whitelist = append(whitelist, userID) diff --git a/models/issues/comment.go b/models/issues/comment.go index 3a4049700d..fd0500833e 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -862,10 +862,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment if err = UpdateCommentAttachments(ctx, comment, opts.Attachments); err != nil { return err } - case CommentTypeReopen, CommentTypeClose: - if err = repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.Issue.IsPull, true); err != nil { - return err - } + // comment type reopen and close event have their own logic to update numbers but not here } // update the issue's updated_unix column return UpdateIssueCols(ctx, opts.Issue, "updated_unix") diff --git a/models/issues/issue_update.go b/models/issues/issue_update.go index 553e99aece..0a320ffc56 100644 --- a/models/issues/issue_update.go +++ b/models/issues/issue_update.go @@ -146,8 +146,19 @@ func updateIssueNumbers(ctx context.Context, issue *Issue, doer *user_model.User } // update repository's issue closed number - if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true); err != nil { - return nil, err + switch cmtType { + case CommentTypeClose, CommentTypeMergePull: + // only increase closed count + if err := IncrRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, false); err != nil { + return nil, err + } + case CommentTypeReopen: + // only decrease closed count + if err := DecrRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, false, true); err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("invalid comment type: %d", cmtType) } return CreateComment(ctx, &CreateCommentOptions{ @@ -318,7 +329,6 @@ type NewIssueOptions struct { Issue *Issue LabelIDs []int64 Attachments []string // In UUID format. - IsPull bool } // NewIssueWithIndex creates issue with given index @@ -369,7 +379,8 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue } } - if err := repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.IsPull, false); err != nil { + // Update repository issue total count + if err := IncrRepoIssueNumbers(ctx, opts.Repo.ID, opts.Issue.IsPull, true); err != nil { return err } @@ -439,6 +450,42 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, la }) } +// IncrRepoIssueNumbers increments repository issue numbers. +func IncrRepoIssueNumbers(ctx context.Context, repoID int64, isPull, totalOrClosed bool) error { + dbSession := db.GetEngine(ctx) + var colName string + if totalOrClosed { + colName = util.Iif(isPull, "num_pulls", "num_issues") + } else { + colName = util.Iif(isPull, "num_closed_pulls", "num_closed_issues") + } + _, err := dbSession.Incr(colName).ID(repoID). + NoAutoCondition().NoAutoTime(). + Update(new(repo_model.Repository)) + return err +} + +// DecrRepoIssueNumbers decrements repository issue numbers. +func DecrRepoIssueNumbers(ctx context.Context, repoID int64, isPull, includeTotal, includeClosed bool) error { + if !includeTotal && !includeClosed { + return fmt.Errorf("no numbers to decrease for repo id %d", repoID) + } + + dbSession := db.GetEngine(ctx) + if includeTotal { + colName := util.Iif(isPull, "num_pulls", "num_issues") + dbSession = dbSession.Decr(colName) + } + if includeClosed { + closedColName := util.Iif(isPull, "num_closed_pulls", "num_closed_issues") + dbSession = dbSession.Decr(closedColName) + } + _, err := dbSession.ID(repoID). + NoAutoCondition().NoAutoTime(). + Update(new(repo_model.Repository)) + return err +} + // UpdateIssueMentions updates issue-user relations for mentioned users. func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_model.User) error { if len(mentions) == 0 { diff --git a/models/issues/milestone.go b/models/issues/milestone.go index 373f39f4ff..82a82ac913 100644 --- a/models/issues/milestone.go +++ b/models/issues/milestone.go @@ -181,6 +181,7 @@ func updateMilestone(ctx context.Context, m *Milestone) error { func UpdateMilestoneCounters(ctx context.Context, id int64) error { e := db.GetEngine(ctx) _, err := e.ID(id). + Cols("num_issues", "num_closed_issues"). SetExpr("num_issues", builder.Select("count(*)").From("issue").Where( builder.Eq{"milestone_id": id}, )). diff --git a/models/issues/pull.go b/models/issues/pull.go index fb7dff3cc9..1ffcd683d5 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -467,13 +467,13 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Iss issue.Index = idx issue.Title = util.EllipsisDisplayString(issue.Title, 255) + issue.IsPull = true if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{ Repo: repo, Issue: issue, LabelIDs: labelIDs, Attachments: uuids, - IsPull: true, }); err != nil { if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) { return err diff --git a/models/migrations/v1_18/v229.go b/models/migrations/v1_18/v229.go index bc15e01390..1f69724365 100644 --- a/models/migrations/v1_18/v229.go +++ b/models/migrations/v1_18/v229.go @@ -21,6 +21,7 @@ func UpdateOpenMilestoneCounts(x *xorm.Engine) error { for _, id := range openMilestoneIDs { _, err := x.ID(id). + Cols("num_issues", "num_closed_issues"). SetExpr("num_issues", builder.Select("count(*)").From("issue").Where( builder.Eq{"milestone_id": id}, )). diff --git a/models/organization/team_repo.go b/models/organization/team_repo.go index b3e266dbc7..2652b34c6f 100644 --- a/models/organization/team_repo.go +++ b/models/organization/team_repo.go @@ -53,24 +53,45 @@ func RemoveTeamRepo(ctx context.Context, teamID, repoID int64) error { // GetTeamsWithAccessToAnyRepoUnit returns all teams in an organization that have given access level to the repository special unit. // This function is only used for finding some teams that can be used as branch protection allowlist or reviewers, it isn't really used for access control. // FIXME: TEAM-UNIT-PERMISSION this logic is not complete, search the fixme keyword to see more details -func GetTeamsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) ([]*Team, error) { - teams := make([]*Team, 0, 5) +func GetTeamsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) (teams []*Team, err error) { + teamIDs, err := getTeamIDsWithAccessToAnyRepoUnit(ctx, orgID, repoID, mode, unitType, unitTypesMore...) + if err != nil { + return nil, err + } + if len(teamIDs) == 0 { + return teams, nil + } + err = db.GetEngine(ctx).Where(builder.In("id", teamIDs)).OrderBy("team.name").Find(&teams) + return teams, err +} +func getTeamIDsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) (teamIDs []int64, err error) { sub := builder.Select("team_id").From("team_unit"). Where(builder.Expr("team_unit.team_id = team.id")). And(builder.In("team_unit.type", append([]unit.Type{unitType}, unitTypesMore...))). And(builder.Expr("team_unit.access_mode >= ?", mode)) - err := db.GetEngine(ctx). + err = db.GetEngine(ctx). + Select("team.id"). + Table("team"). Join("INNER", "team_repo", "team_repo.team_id = team.id"). - And("team_repo.org_id = ?", orgID). - And("team_repo.repo_id = ?", repoID). + And("team_repo.org_id = ? AND team_repo.repo_id = ?", orgID, repoID). And(builder.Or( builder.Expr("team.authorize >= ?", mode), builder.In("team.id", sub), )). - OrderBy("name"). - Find(&teams) + Find(&teamIDs) + return teamIDs, err +} - return teams, err +func GetTeamUserIDsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) (userIDs []int64, err error) { + teamIDs, err := getTeamIDsWithAccessToAnyRepoUnit(ctx, orgID, repoID, mode, unitType, unitTypesMore...) + if err != nil { + return nil, err + } + if len(teamIDs) == 0 { + return userIDs, nil + } + err = db.GetEngine(ctx).Table("team_user").Select("uid").Where(builder.In("team_id", teamIDs)).Find(&userIDs) + return userIDs, err } diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go index 034557db33..15526cb1e6 100644 --- a/models/perm/access/repo_permission.go +++ b/models/perm/access/repo_permission.go @@ -16,6 +16,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -498,54 +499,44 @@ func HasAnyUnitAccess(ctx context.Context, userID int64, repo *repo_model.Reposi return perm.HasAnyUnitAccess(), nil } -// getUsersWithAccessMode returns users that have at least given access mode to the repository. -func getUsersWithAccessMode(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode) (_ []*user_model.User, err error) { - if err = repo.LoadOwner(ctx); err != nil { +func GetUsersWithUnitAccess(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode, unitType unit.Type) (users []*user_model.User, err error) { + userIDs, err := GetUserIDsWithUnitAccess(ctx, repo, mode, unitType) + if err != nil { return nil, err } + if len(userIDs) == 0 { + return users, nil + } + if err = db.GetEngine(ctx).In("id", userIDs.Values()).OrderBy("`name`").Find(&users); err != nil { + return nil, err + } + return users, nil +} +func GetUserIDsWithUnitAccess(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode, unitType unit.Type) (container.Set[int64], error) { + userIDs := container.Set[int64]{} e := db.GetEngine(ctx) accesses := make([]*Access, 0, 10) - if err = e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil { + if err := e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil { return nil, err } + for _, a := range accesses { + userIDs.Add(a.UserID) + } - // Leave a seat for owner itself to append later, but if owner is an organization - // and just waste 1 unit is cheaper than re-allocate memory once. - users := make([]*user_model.User, 0, len(accesses)+1) - if len(accesses) > 0 { - userIDs := make([]int64, len(accesses)) - for i := 0; i < len(accesses); i++ { - userIDs[i] = accesses[i].UserID - } - - if err = e.In("id", userIDs).Find(&users); err != nil { - return nil, err - } + if err := repo.LoadOwner(ctx); err != nil { + return nil, err } if !repo.Owner.IsOrganization() { - users = append(users, repo.Owner) - } - - return users, nil -} - -// GetRepoReaders returns all users that have explicit read access or higher to the repository. -func GetRepoReaders(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) { - return getUsersWithAccessMode(ctx, repo, perm_model.AccessModeRead) -} - -// GetRepoWriters returns all users that have write access to the repository. -func GetRepoWriters(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) { - return getUsersWithAccessMode(ctx, repo, perm_model.AccessModeWrite) -} - -// IsRepoReader returns true if user has explicit read access or higher to the repository. -func IsRepoReader(ctx context.Context, repo *repo_model.Repository, userID int64) (bool, error) { - if repo.OwnerID == userID { - return true, nil + userIDs.Add(repo.Owner.ID) + } else { + teamUserIDs, err := organization.GetTeamUserIDsWithAccessToAnyRepoUnit(ctx, repo.OwnerID, repo.ID, mode, unitType) + if err != nil { + return nil, err + } + userIDs.AddMultiple(teamUserIDs...) } - return db.GetEngine(ctx).Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, perm_model.AccessModeRead).Get(&Access{}) + return userIDs, nil } // CheckRepoUnitUser check whether user could visit the unit of this repository diff --git a/models/perm/access/repo_permission_test.go b/models/perm/access/repo_permission_test.go index d81dfba288..a36be213ec 100644 --- a/models/perm/access/repo_permission_test.go +++ b/models/perm/access/repo_permission_test.go @@ -169,9 +169,9 @@ func TestGetUserRepoPermission(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) team := &organization.Team{OrgID: org.ID, LowerName: "test_team"} require.NoError(t, db.Insert(ctx, team)) + require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID})) t.Run("DoerInTeamWithNoRepo", func(t *testing.T) { - require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID})) perm, err := GetUserRepoPermission(ctx, repo32, user) require.NoError(t, err) assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode) @@ -219,6 +219,15 @@ func TestGetUserRepoPermission(t *testing.T) { assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode) assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode]) assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues]) + + users, err := GetUsersWithUnitAccess(ctx, repo3, perm_model.AccessModeRead, unit.TypeIssues) + require.NoError(t, err) + require.Len(t, users, 1) + assert.Equal(t, user.ID, users[0].ID) + + users, err = GetUsersWithUnitAccess(ctx, repo3, perm_model.AccessModeWrite, unit.TypeIssues) + require.NoError(t, err) + require.Empty(t, users) }) require.NoError(t, db.Insert(ctx, repo_model.Collaboration{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite})) @@ -229,5 +238,10 @@ func TestGetUserRepoPermission(t *testing.T) { assert.Equal(t, perm_model.AccessModeWrite, perm.AccessMode) assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode]) assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeIssues]) + + users, err := GetUsersWithUnitAccess(ctx, repo3, perm_model.AccessModeWrite, unit.TypeIssues) + require.NoError(t, err) + require.Len(t, users, 1) + assert.Equal(t, user.ID, users[0].ID) }) } diff --git a/models/pull/review_state.go b/models/pull/review_state.go index 137af00eab..e8b759c0cc 100644 --- a/models/pull/review_state.go +++ b/models/pull/review_state.go @@ -49,6 +49,19 @@ func init() { db.RegisterModel(new(ReviewState)) } +func (rs *ReviewState) GetViewedFileCount() int { + if len(rs.UpdatedFiles) == 0 { + return 0 + } + var numViewedFiles int + for _, state := range rs.UpdatedFiles { + if state == Viewed { + numViewedFiles++ + } + } + return numViewedFiles +} + // GetReviewState returns the ReviewState with all given values prefilled, whether or not it exists in the database. // If the review didn't exist before in the database, it won't afterwards either. // The returned boolean shows whether the review exists in the database diff --git a/models/repo/topic.go b/models/repo/topic.go index baeae01efa..f8f706fc1a 100644 --- a/models/repo/topic.go +++ b/models/repo/topic.go @@ -159,7 +159,7 @@ func RemoveTopicsFromRepo(ctx context.Context, repoID int64) error { builder.In("id", builder.Select("topic_id").From("repo_topic").Where(builder.Eq{"repo_id": repoID}), ), - ).Cols("repo_count").SetExpr("repo_count", "repo_count-1").Update(&Topic{}) + ).Decr("repo_count").Update(&Topic{}) if err != nil { return err } diff --git a/models/user/user.go b/models/user/user.go index d6e1eec276..925be83713 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -1262,8 +1262,8 @@ func GetUserByEmail(ctx context.Context, email string) (*User, error) { } // Finally, if email address is the protected email address: - if strings.HasSuffix(email, "@"+setting.Service.NoReplyAddress) { - username := strings.TrimSuffix(email, "@"+setting.Service.NoReplyAddress) + if before, ok := strings.CutSuffix(email, "@"+setting.Service.NoReplyAddress); ok { + username := before user := &User{} has, err := db.GetEngine(ctx).Where("lower_name=?", username).Get(user) if err != nil { diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index 69f71bf651..26a6ebc370 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -5,7 +5,6 @@ package actions import ( "bytes" - "io" "slices" "strings" @@ -13,6 +12,7 @@ import ( "code.gitea.io/gitea/modules/glob" "code.gitea.io/gitea/modules/log" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" webhook_module "code.gitea.io/gitea/modules/webhook" "github.com/nektos/act/pkg/jobparser" @@ -77,7 +77,7 @@ func GetContentFromEntry(entry *git.TreeEntry) ([]byte, error) { if err != nil { return nil, err } - content, err := io.ReadAll(f) + content, err := util.ReadWithLimit(f, 1024*1024) _ = f.Close() if err != nil { return nil, err diff --git a/modules/auth/webauthn/webauthn.go b/modules/auth/webauthn/webauthn.go index cbf5279c65..86f55c6b24 100644 --- a/modules/auth/webauthn/webauthn.go +++ b/modules/auth/webauthn/webauthn.go @@ -22,7 +22,7 @@ var WebAuthn *webauthn.WebAuthn // Init initializes the WebAuthn instance from the config. func Init() { - gob.Register(&webauthn.SessionData{}) + gob.Register(&webauthn.SessionData{}) // TODO: CHI-SESSION-GOB-REGISTER. appURL, _ := protocol.FullyQualifiedOrigin(setting.AppURL) diff --git a/modules/base/natural_sort.go b/modules/base/natural_sort.go index acb9002276..d1ee7b04ec 100644 --- a/modules/base/natural_sort.go +++ b/modules/base/natural_sort.go @@ -41,8 +41,8 @@ func naturalSortAdvance(str string, pos int) (end int, isNumber bool) { return end, isNumber } -// NaturalSortLess compares two strings so that they could be sorted in natural order -func NaturalSortLess(s1, s2 string) bool { +// NaturalSortCompare compares two strings so that they could be sorted in natural order +func NaturalSortCompare(s1, s2 string) int { // There is a bug in Golang's collate package: https://github.com/golang/go/issues/67997 // text/collate: CompareString(collate.Numeric) returns wrong result for "0.0" vs "1.0" #67997 // So we need to handle the number parts by ourselves @@ -55,16 +55,16 @@ func NaturalSortLess(s1, s2 string) bool { if isNum1 && isNum2 { if part1 != part2 { if len(part1) != len(part2) { - return len(part1) < len(part2) + return len(part1) - len(part2) } - return part1 < part2 + return c.CompareString(part1, part2) } } else { if cmp := c.CompareString(part1, part2); cmp != 0 { - return cmp < 0 + return cmp } } pos1, pos2 = end1, end2 } - return len(s1) < len(s2) + return len(s1) - len(s2) } diff --git a/modules/base/natural_sort_test.go b/modules/base/natural_sort_test.go index b001bc4ac9..451aba6618 100644 --- a/modules/base/natural_sort_test.go +++ b/modules/base/natural_sort_test.go @@ -11,12 +11,10 @@ import ( func TestNaturalSortLess(t *testing.T) { testLess := func(s1, s2 string) { - assert.True(t, NaturalSortLess(s1, s2), "s1 2 { diff --git a/modules/git/parse_gogit.go b/modules/git/parse_gogit.go deleted file mode 100644 index 74d258de8e..0000000000 --- a/modules/git/parse_gogit.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2018 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "bytes" - "fmt" - "strconv" - "strings" - - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/filemode" - "github.com/go-git/go-git/v5/plumbing/hash" - "github.com/go-git/go-git/v5/plumbing/object" -) - -// ParseTreeEntries parses the output of a `git ls-tree -l` command. -func ParseTreeEntries(data []byte) ([]*TreeEntry, error) { - return parseTreeEntries(data, nil) -} - -func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { - entries := make([]*TreeEntry, 0, 10) - for pos := 0; pos < len(data); { - // expect line to be of the form " \t" - entry := new(TreeEntry) - entry.gogitTreeEntry = &object.TreeEntry{} - entry.ptree = ptree - if pos+6 > len(data) { - return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data)) - } - switch string(data[pos : pos+6]) { - case "100644": - entry.gogitTreeEntry.Mode = filemode.Regular - pos += 12 // skip over "100644 blob " - case "100755": - entry.gogitTreeEntry.Mode = filemode.Executable - pos += 12 // skip over "100755 blob " - case "120000": - entry.gogitTreeEntry.Mode = filemode.Symlink - pos += 12 // skip over "120000 blob " - case "160000": - entry.gogitTreeEntry.Mode = filemode.Submodule - pos += 14 // skip over "160000 object " - case "040000": - entry.gogitTreeEntry.Mode = filemode.Dir - pos += 12 // skip over "040000 tree " - default: - return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+6])) - } - - // in hex format, not byte format .... - if pos+hash.Size*2 > len(data) { - return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data)) - } - var err error - entry.ID, err = NewIDFromString(string(data[pos : pos+hash.Size*2])) - if err != nil { - return nil, fmt.Errorf("invalid ls-tree output: %w", err) - } - entry.gogitTreeEntry.Hash = plumbing.Hash(entry.ID.RawValue()) - pos += 41 // skip over sha and trailing space - - end := pos + bytes.IndexByte(data[pos:], '\t') - if end < pos { - return nil, fmt.Errorf("Invalid ls-tree -l output: %s", string(data)) - } - entry.size, _ = strconv.ParseInt(strings.TrimSpace(string(data[pos:end])), 10, 64) - entry.sized = true - - pos = end + 1 - - end = pos + bytes.IndexByte(data[pos:], '\n') - if end < pos { - return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data)) - } - - // In case entry name is surrounded by double quotes(it happens only in git-shell). - if data[pos] == '"' { - var err error - entry.gogitTreeEntry.Name, err = strconv.Unquote(string(data[pos:end])) - if err != nil { - return nil, fmt.Errorf("Invalid ls-tree output: %w", err) - } - } else { - entry.gogitTreeEntry.Name = string(data[pos:end]) - } - - pos = end + 1 - entries = append(entries, entry) - } - return entries, nil -} diff --git a/modules/git/parse_gogit_test.go b/modules/git/parse_gogit_test.go deleted file mode 100644 index 3e171d7e56..0000000000 --- a/modules/git/parse_gogit_test.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2018 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build gogit - -package git - -import ( - "fmt" - "testing" - - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/filemode" - "github.com/go-git/go-git/v5/plumbing/object" - "github.com/stretchr/testify/assert" -) - -func TestParseTreeEntries(t *testing.T) { - testCases := []struct { - Input string - Expected []*TreeEntry - }{ - { - Input: "", - Expected: []*TreeEntry{}, - }, - { - Input: "100644 blob 61ab7345a1a3bbc590068ccae37b8515cfc5843c 1022\texample/file2.txt\n", - Expected: []*TreeEntry{ - { - ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), - gogitTreeEntry: &object.TreeEntry{ - Hash: plumbing.Hash(MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c").RawValue()), - Name: "example/file2.txt", - Mode: filemode.Regular, - }, - size: 1022, - sized: true, - }, - }, - }, - { - Input: "120000 blob 61ab7345a1a3bbc590068ccae37b8515cfc5843c 234131\t\"example/\\n.txt\"\n" + - "040000 tree 1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8 -\texample\n", - Expected: []*TreeEntry{ - { - ID: MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c"), - gogitTreeEntry: &object.TreeEntry{ - Hash: plumbing.Hash(MustIDFromString("61ab7345a1a3bbc590068ccae37b8515cfc5843c").RawValue()), - Name: "example/\n.txt", - Mode: filemode.Symlink, - }, - size: 234131, - sized: true, - }, - { - ID: MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8"), - sized: true, - gogitTreeEntry: &object.TreeEntry{ - Hash: plumbing.Hash(MustIDFromString("1d01fb729fb0db5881daaa6030f9f2d3cd3d5ae8").RawValue()), - Name: "example", - Mode: filemode.Dir, - }, - }, - }, - }, - } - - for _, testCase := range testCases { - entries, err := ParseTreeEntries([]byte(testCase.Input)) - assert.NoError(t, err) - if len(entries) > 1 { - fmt.Println(testCase.Expected[0].ID) - fmt.Println(entries[0].ID) - } - assert.EqualValues(t, testCase.Expected, entries) - } -} diff --git a/modules/git/parse_nogogit.go b/modules/git/parse_treeentry.go similarity index 99% rename from modules/git/parse_nogogit.go rename to modules/git/parse_treeentry.go index 78a0162889..e14d9f17b5 100644 --- a/modules/git/parse_nogogit.go +++ b/modules/git/parse_treeentry.go @@ -1,8 +1,6 @@ // Copyright 2018 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build !gogit - package git import ( diff --git a/modules/git/parse_nogogit_test.go b/modules/git/parse_treeentry_test.go similarity index 99% rename from modules/git/parse_nogogit_test.go rename to modules/git/parse_treeentry_test.go index 6594c84269..4223cbb3d7 100644 --- a/modules/git/parse_nogogit_test.go +++ b/modules/git/parse_treeentry_test.go @@ -1,8 +1,6 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build !gogit - package git import ( diff --git a/modules/git/repo_commit_gogit.go b/modules/git/repo_commit_gogit.go index 896d656039..c84aabde1a 100644 --- a/modules/git/repo_commit_gogit.go +++ b/modules/git/repo_commit_gogit.go @@ -107,7 +107,7 @@ func (repo *Repository) getCommit(id ObjectID) (*Commit, error) { } commit.Tree.ID = ParseGogitHash(tree.Hash) - commit.Tree.gogitTree = tree + commit.Tree.resolvedGogitTreeObject = tree return commit, nil } diff --git a/modules/git/repo_tree_gogit.go b/modules/git/repo_tree_gogit.go index e15663a32a..89d34e87da 100644 --- a/modules/git/repo_tree_gogit.go +++ b/modules/git/repo_tree_gogit.go @@ -26,7 +26,7 @@ func (repo *Repository) getTree(id ObjectID) (*Tree, error) { } tree := NewTree(repo, id) - tree.gogitTree = gogitTree + tree.resolvedGogitTreeObject = gogitTree return tree, nil } diff --git a/modules/git/tree.go b/modules/git/tree.go index 9c73aec735..c1898b20cb 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -11,11 +11,21 @@ import ( "code.gitea.io/gitea/modules/git/gitcmd" ) +type TreeCommon struct { + ID ObjectID + ResolvedID ObjectID + + repo *Repository + ptree *Tree // parent tree +} + // NewTree create a new tree according the repository and tree id func NewTree(repo *Repository, id ObjectID) *Tree { return &Tree{ - ID: id, - repo: repo, + TreeCommon: TreeCommon{ + ID: id, + repo: repo, + }, } } diff --git a/modules/git/tree_blob_gogit.go b/modules/git/tree_blob_gogit.go index f29e8f8b9e..2c0ff0e1b0 100644 --- a/modules/git/tree_blob_gogit.go +++ b/modules/git/tree_blob_gogit.go @@ -11,22 +11,16 @@ import ( "strings" "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/filemode" - "github.com/go-git/go-git/v5/plumbing/object" ) // GetTreeEntryByPath get the tree entries according the sub dir func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) { if len(relpath) == 0 { return &TreeEntry{ - ID: t.ID, - // Type: ObjectTree, - ptree: t, - gogitTreeEntry: &object.TreeEntry{ - Name: "", - Mode: filemode.Dir, - Hash: plumbing.Hash(t.ID.RawValue()), - }, + ID: t.ID, + ptree: t, + name: "", + entryMode: EntryModeTree, }, nil } diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index 5099d8ee79..e7e4ea2d5b 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -6,12 +6,60 @@ package git import ( "path" - "sort" + "slices" "strings" "code.gitea.io/gitea/modules/util" ) +// TreeEntry the leaf in the git tree +type TreeEntry struct { + ID ObjectID + + name string + ptree *Tree + + entryMode EntryMode + + size int64 + sized bool +} + +// Name returns the name of the entry (base name) +func (te *TreeEntry) Name() string { + return te.name +} + +// Mode returns the mode of the entry +func (te *TreeEntry) Mode() EntryMode { + return te.entryMode +} + +// IsSubModule if the entry is a submodule +func (te *TreeEntry) IsSubModule() bool { + return te.entryMode.IsSubModule() +} + +// IsDir if the entry is a sub dir +func (te *TreeEntry) IsDir() bool { + return te.entryMode.IsDir() +} + +// IsLink if the entry is a symlink +func (te *TreeEntry) IsLink() bool { + return te.entryMode.IsLink() +} + +// IsRegular if the entry is a regular file +func (te *TreeEntry) IsRegular() bool { + return te.entryMode.IsRegular() +} + +// IsExecutable if the entry is an executable file (not necessarily binary) +func (te *TreeEntry) IsExecutable() bool { + return te.entryMode.IsExecutable() +} + // Type returns the type of the entry (commit, tree, blob) func (te *TreeEntry) Type() string { switch te.Mode() { @@ -109,49 +157,16 @@ func (te *TreeEntry) GetSubJumpablePathName() string { // Entries a list of entry type Entries []*TreeEntry -type customSortableEntries struct { - Comparer func(s1, s2 string) bool - Entries -} - -var sorter = []func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool{ - func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool { - return (t1.IsDir() || t1.IsSubModule()) && !t2.IsDir() && !t2.IsSubModule() - }, - func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool { - return cmp(t1.Name(), t2.Name()) - }, -} - -func (ctes customSortableEntries) Len() int { return len(ctes.Entries) } - -func (ctes customSortableEntries) Swap(i, j int) { - ctes.Entries[i], ctes.Entries[j] = ctes.Entries[j], ctes.Entries[i] -} - -func (ctes customSortableEntries) Less(i, j int) bool { - t1, t2 := ctes.Entries[i], ctes.Entries[j] - var k int - for k = 0; k < len(sorter)-1; k++ { - s := sorter[k] - switch { - case s(t1, t2, ctes.Comparer): - return true - case s(t2, t1, ctes.Comparer): - return false - } - } - return sorter[k](t1, t2, ctes.Comparer) -} - -// Sort sort the list of entry -func (tes Entries) Sort() { - sort.Sort(customSortableEntries{func(s1, s2 string) bool { - return s1 < s2 - }, tes}) -} - // CustomSort customizable string comparing sort entry list -func (tes Entries) CustomSort(cmp func(s1, s2 string) bool) { - sort.Sort(customSortableEntries{cmp, tes}) +func (tes Entries) CustomSort(cmp func(s1, s2 string) int) { + slices.SortFunc(tes, func(a, b *TreeEntry) int { + s1Dir, s2Dir := a.IsDir() || a.IsSubModule(), b.IsDir() || b.IsSubModule() + if s1Dir != s2Dir { + if s1Dir { + return -1 + } + return 1 + } + return cmp(a.Name(), b.Name()) + }) } diff --git a/modules/git/tree_entry_gogit.go b/modules/git/tree_entry_gogit.go index e6845f1c77..27877a2e28 100644 --- a/modules/git/tree_entry_gogit.go +++ b/modules/git/tree_entry_gogit.go @@ -12,25 +12,21 @@ import ( "github.com/go-git/go-git/v5/plumbing/object" ) -// TreeEntry the leaf in the git tree -type TreeEntry struct { - ID ObjectID - - gogitTreeEntry *object.TreeEntry - ptree *Tree - - size int64 - sized bool +// gogitFileModeToEntryMode converts go-git filemode to EntryMode +func gogitFileModeToEntryMode(mode filemode.FileMode) EntryMode { + return EntryMode(mode) } -// Name returns the name of the entry -func (te *TreeEntry) Name() string { - return te.gogitTreeEntry.Name +func entryModeToGogitFileMode(mode EntryMode) filemode.FileMode { + return filemode.FileMode(mode) } -// Mode returns the mode of the entry -func (te *TreeEntry) Mode() EntryMode { - return EntryMode(te.gogitTreeEntry.Mode) +func (te *TreeEntry) toGogitTreeEntry() *object.TreeEntry { + return &object.TreeEntry{ + Name: te.name, + Mode: entryModeToGogitFileMode(te.entryMode), + Hash: plumbing.Hash(te.ID.RawValue()), + } } // Size returns the size of the entry @@ -41,7 +37,11 @@ func (te *TreeEntry) Size() int64 { return te.size } - file, err := te.ptree.gogitTree.TreeEntryFile(te.gogitTreeEntry) + ptreeGogitTree, err := te.ptree.gogitTreeObject() + if err != nil { + return 0 + } + file, err := ptreeGogitTree.TreeEntryFile(te.toGogitTreeEntry()) if err != nil { return 0 } @@ -51,40 +51,15 @@ func (te *TreeEntry) Size() int64 { return te.size } -// IsSubModule if the entry is a submodule -func (te *TreeEntry) IsSubModule() bool { - return te.gogitTreeEntry.Mode == filemode.Submodule -} - -// IsDir if the entry is a sub dir -func (te *TreeEntry) IsDir() bool { - return te.gogitTreeEntry.Mode == filemode.Dir -} - -// IsLink if the entry is a symlink -func (te *TreeEntry) IsLink() bool { - return te.gogitTreeEntry.Mode == filemode.Symlink -} - -// IsRegular if the entry is a regular file -func (te *TreeEntry) IsRegular() bool { - return te.gogitTreeEntry.Mode == filemode.Regular -} - -// IsExecutable if the entry is an executable file (not necessarily binary) -func (te *TreeEntry) IsExecutable() bool { - return te.gogitTreeEntry.Mode == filemode.Executable -} - // Blob returns the blob object the entry func (te *TreeEntry) Blob() *Blob { - encodedObj, err := te.ptree.repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, te.gogitTreeEntry.Hash) + encodedObj, err := te.ptree.repo.gogitRepo.Storer.EncodedObject(plumbing.AnyObject, te.toGogitTreeEntry().Hash) if err != nil { return nil } return &Blob{ - ID: ParseGogitHash(te.gogitTreeEntry.Hash), + ID: te.ID, gogitEncodedObj: encodedObj, name: te.Name(), } diff --git a/modules/git/tree_entry_gogit_test.go b/modules/git/tree_entry_gogit_test.go new file mode 100644 index 0000000000..ed14b45e9e --- /dev/null +++ b/modules/git/tree_entry_gogit_test.go @@ -0,0 +1,27 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +//go:build gogit + +package git + +import ( + "testing" + + "github.com/go-git/go-git/v5/plumbing/filemode" + "github.com/stretchr/testify/assert" +) + +func TestEntryGogit(t *testing.T) { + cases := map[EntryMode]filemode.FileMode{ + EntryModeBlob: filemode.Regular, + EntryModeCommit: filemode.Submodule, + EntryModeExec: filemode.Executable, + EntryModeSymlink: filemode.Symlink, + EntryModeTree: filemode.Dir, + } + for emode, fmode := range cases { + assert.EqualValues(t, fmode, entryModeToGogitFileMode(emode)) + assert.EqualValues(t, emode, gogitFileModeToEntryMode(fmode)) + } +} diff --git a/modules/git/tree_entry_nogogit.go b/modules/git/tree_entry_nogogit.go index 8fad96cdf8..fd2f3c567f 100644 --- a/modules/git/tree_entry_nogogit.go +++ b/modules/git/tree_entry_nogogit.go @@ -7,27 +7,6 @@ package git import "code.gitea.io/gitea/modules/log" -// TreeEntry the leaf in the git tree -type TreeEntry struct { - ID ObjectID - ptree *Tree - - entryMode EntryMode - name string - size int64 - sized bool -} - -// Name returns the name of the entry (base name) -func (te *TreeEntry) Name() string { - return te.name -} - -// Mode returns the mode of the entry -func (te *TreeEntry) Mode() EntryMode { - return te.entryMode -} - // Size returns the size of the entry func (te *TreeEntry) Size() int64 { if te.IsDir() { @@ -57,31 +36,6 @@ func (te *TreeEntry) Size() int64 { return te.size } -// IsSubModule if the entry is a submodule -func (te *TreeEntry) IsSubModule() bool { - return te.entryMode.IsSubModule() -} - -// IsDir if the entry is a sub dir -func (te *TreeEntry) IsDir() bool { - return te.entryMode.IsDir() -} - -// IsLink if the entry is a symlink -func (te *TreeEntry) IsLink() bool { - return te.entryMode.IsLink() -} - -// IsRegular if the entry is a regular file -func (te *TreeEntry) IsRegular() bool { - return te.entryMode.IsRegular() -} - -// IsExecutable if the entry is an executable file (not necessarily binary) -func (te *TreeEntry) IsExecutable() bool { - return te.entryMode.IsExecutable() -} - // Blob returns the blob object the entry func (te *TreeEntry) Blob() *Blob { return &Blob{ diff --git a/modules/git/tree_entry_test.go b/modules/git/tree_entry_test.go index 9ca82675e0..b28abfb545 100644 --- a/modules/git/tree_entry_test.go +++ b/modules/git/tree_entry_test.go @@ -1,55 +1,29 @@ // Copyright 2017 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:build gogit - package git import ( + "math/rand/v2" + "slices" + "strings" "testing" - "github.com/go-git/go-git/v5/plumbing/filemode" - "github.com/go-git/go-git/v5/plumbing/object" "github.com/stretchr/testify/assert" ) -func getTestEntries() Entries { - return Entries{ - &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v1.0", Mode: filemode.Dir}}, - &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.0", Mode: filemode.Dir}}, - &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.1", Mode: filemode.Dir}}, - &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.12", Mode: filemode.Dir}}, - &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v2.2", Mode: filemode.Dir}}, - &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "v12.0", Mode: filemode.Dir}}, - &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "abc", Mode: filemode.Regular}}, - &TreeEntry{gogitTreeEntry: &object.TreeEntry{Name: "bcd", Mode: filemode.Regular}}, - } -} - -func TestEntriesSort(t *testing.T) { - entries := getTestEntries() - entries.Sort() - assert.Equal(t, "v1.0", entries[0].Name()) - assert.Equal(t, "v12.0", entries[1].Name()) - assert.Equal(t, "v2.0", entries[2].Name()) - assert.Equal(t, "v2.1", entries[3].Name()) - assert.Equal(t, "v2.12", entries[4].Name()) - assert.Equal(t, "v2.2", entries[5].Name()) - assert.Equal(t, "abc", entries[6].Name()) - assert.Equal(t, "bcd", entries[7].Name()) -} - func TestEntriesCustomSort(t *testing.T) { - entries := getTestEntries() - entries.CustomSort(func(s1, s2 string) bool { - return s1 > s2 - }) - assert.Equal(t, "v2.2", entries[0].Name()) - assert.Equal(t, "v2.12", entries[1].Name()) - assert.Equal(t, "v2.1", entries[2].Name()) - assert.Equal(t, "v2.0", entries[3].Name()) - assert.Equal(t, "v12.0", entries[4].Name()) - assert.Equal(t, "v1.0", entries[5].Name()) - assert.Equal(t, "bcd", entries[6].Name()) - assert.Equal(t, "abc", entries[7].Name()) + entries := Entries{ + &TreeEntry{name: "a-dir", entryMode: EntryModeTree}, + &TreeEntry{name: "a-submodule", entryMode: EntryModeCommit}, + &TreeEntry{name: "b-dir", entryMode: EntryModeTree}, + &TreeEntry{name: "b-submodule", entryMode: EntryModeCommit}, + &TreeEntry{name: "a-file", entryMode: EntryModeBlob}, + &TreeEntry{name: "b-file", entryMode: EntryModeBlob}, + } + expected := slices.Clone(entries) + rand.Shuffle(len(entries), func(i, j int) { entries[i], entries[j] = entries[j], entries[i] }) + assert.NotEqual(t, expected, entries) + entries.CustomSort(strings.Compare) + assert.Equal(t, expected, entries) } diff --git a/modules/git/tree_gogit.go b/modules/git/tree_gogit.go index 272b018ffd..fec6e2704e 100644 --- a/modules/git/tree_gogit.go +++ b/modules/git/tree_gogit.go @@ -15,41 +15,34 @@ import ( // Tree represents a flat directory listing. type Tree struct { - ID ObjectID - ResolvedID ObjectID - repo *Repository + TreeCommon - gogitTree *object.Tree - - // parent tree - ptree *Tree + resolvedGogitTreeObject *object.Tree } -func (t *Tree) loadTreeObject() error { - gogitTree, err := t.repo.gogitRepo.TreeObject(plumbing.Hash(t.ID.RawValue())) - if err != nil { - return err +func (t *Tree) gogitTreeObject() (_ *object.Tree, err error) { + if t.resolvedGogitTreeObject == nil { + t.resolvedGogitTreeObject, err = t.repo.gogitRepo.TreeObject(plumbing.Hash(t.ID.RawValue())) + if err != nil { + return nil, err + } } - - t.gogitTree = gogitTree - return nil + return t.resolvedGogitTreeObject, nil } // ListEntries returns all entries of current tree. func (t *Tree) ListEntries() (Entries, error) { - if t.gogitTree == nil { - err := t.loadTreeObject() - if err != nil { - return nil, err - } + gogitTree, err := t.gogitTreeObject() + if err != nil { + return nil, err } - - entries := make([]*TreeEntry, len(t.gogitTree.Entries)) - for i, entry := range t.gogitTree.Entries { + entries := make([]*TreeEntry, len(gogitTree.Entries)) + for i, gogitTreeEntry := range gogitTree.Entries { entries[i] = &TreeEntry{ - ID: ParseGogitHash(entry.Hash), - gogitTreeEntry: &t.gogitTree.Entries[i], - ptree: t, + ID: ParseGogitHash(gogitTreeEntry.Hash), + ptree: t, + name: gogitTreeEntry.Name, + entryMode: gogitFileModeToEntryMode(gogitTreeEntry.Mode), } } @@ -57,37 +50,28 @@ func (t *Tree) ListEntries() (Entries, error) { } // ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees -func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) { - if t.gogitTree == nil { - err := t.loadTreeObject() - if err != nil { - return nil, err - } +func (t *Tree) ListEntriesRecursiveWithSize() (entries Entries, _ error) { + gogitTree, err := t.gogitTreeObject() + if err != nil { + return nil, err } - var entries []*TreeEntry - seen := map[plumbing.Hash]bool{} - walker := object.NewTreeWalker(t.gogitTree, true, seen) + walker := object.NewTreeWalker(gogitTree, true, nil) for { - _, entry, err := walker.Next() + fullName, gogitTreeEntry, err := walker.Next() if err == io.EOF { break - } - if err != nil { + } else if err != nil { return nil, err } - if seen[entry.Hash] { - continue - } - convertedEntry := &TreeEntry{ - ID: ParseGogitHash(entry.Hash), - gogitTreeEntry: &entry, - ptree: t, + ID: ParseGogitHash(gogitTreeEntry.Hash), + name: fullName, // FIXME: the "name" field is abused, here it is a full path + ptree: t, // FIXME: this ptree is not right, fortunately it isn't really used + entryMode: gogitFileModeToEntryMode(gogitTreeEntry.Mode), } entries = append(entries, convertedEntry) } - return entries, nil } diff --git a/modules/git/tree_nogogit.go b/modules/git/tree_nogogit.go index 956a5938f0..d0ddb1d041 100644 --- a/modules/git/tree_nogogit.go +++ b/modules/git/tree_nogogit.go @@ -14,18 +14,10 @@ import ( // Tree represents a flat directory listing. type Tree struct { - ID ObjectID - ResolvedID ObjectID - repo *Repository - - // parent tree - ptree *Tree + TreeCommon entries Entries entriesParsed bool - - entriesRecursive Entries - entriesRecursiveParsed bool } // ListEntries returns all entries of current tree. @@ -94,10 +86,6 @@ func (t *Tree) ListEntries() (Entries, error) { // listEntriesRecursive returns all entries of current tree recursively including all subtrees // extraArgs could be "-l" to get the size, which is slower func (t *Tree) listEntriesRecursive(extraArgs gitcmd.TrustedCmdArgs) (Entries, error) { - if t.entriesRecursiveParsed { - return t.entriesRecursive, nil - } - stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-t", "-r"). AddArguments(extraArgs...). AddDynamicArguments(t.ID.String()). @@ -107,13 +95,9 @@ func (t *Tree) listEntriesRecursive(extraArgs gitcmd.TrustedCmdArgs) (Entries, e return nil, runErr } - var err error - t.entriesRecursive, err = parseTreeEntries(stdout, t) - if err == nil { - t.entriesRecursiveParsed = true - } - - return t.entriesRecursive, err + // FIXME: the "name" field is abused, here it is a full path + // FIXME: this ptree is not right, fortunately it isn't really used + return parseTreeEntries(stdout, t) } // ListEntriesRecursiveFast returns all entries of current tree recursively including all subtrees, no size diff --git a/modules/issue/template/unmarshal.go b/modules/issue/template/unmarshal.go index 1d8e9dd02d..ceab6babf4 100644 --- a/modules/issue/template/unmarshal.go +++ b/modules/issue/template/unmarshal.go @@ -5,7 +5,6 @@ package template import ( "fmt" - "io" "path" "strconv" @@ -76,7 +75,7 @@ func unmarshalFromEntry(entry *git.TreeEntry, filename string) (*api.IssueTempla } defer r.Close() - content, err := io.ReadAll(r) + content, err := util.ReadWithLimit(r, 1024*1024) if err != nil { return nil, fmt.Errorf("read all: %w", err) } diff --git a/modules/packages/composer/metadata.go b/modules/packages/composer/metadata.go index 6035eae8ca..3aac7058aa 100644 --- a/modules/packages/composer/metadata.go +++ b/modules/packages/composer/metadata.go @@ -4,8 +4,13 @@ package composer import ( + "archive/tar" "archive/zip" + "compress/bzip2" + "compress/gzip" + "errors" "io" + "io/fs" "path" "regexp" "strings" @@ -29,8 +34,10 @@ var ( ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid") ) -// Package represents a Composer package -type Package struct { +// PackageInfo represents Composer package info +type PackageInfo struct { + Filename string + Name string Version string Type string @@ -44,7 +51,7 @@ type Metadata struct { Description string `json:"description,omitempty"` Readme string `json:"readme,omitempty"` Keywords []string `json:"keywords,omitempty"` - Comments Comments `json:"_comments,omitempty"` + Comments Comments `json:"_comment,omitempty"` Homepage string `json:"homepage,omitempty"` License Licenses `json:"license,omitempty"` Authors []Author `json:"authors,omitempty"` @@ -75,7 +82,7 @@ func (l *Licenses) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &values); err != nil { return err } - *l = Licenses(values) + *l = values } return nil } @@ -97,7 +104,7 @@ func (c *Comments) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &values); err != nil { return err } - *c = Comments(values) + *c = values } return nil } @@ -111,39 +118,121 @@ type Author struct { var nameMatch = regexp.MustCompile(`\A[a-z0-9]([_\.-]?[a-z0-9]+)*/[a-z0-9](([_\.]?|-{0,2})[a-z0-9]+)*\z`) -// ParsePackage parses the metadata of a Composer package file -func ParsePackage(r io.ReaderAt, size int64) (*Package, error) { - archive, err := zip.NewReader(r, size) +type ReadSeekAt interface { + io.Reader + io.ReaderAt + io.Seeker + Size() int64 +} + +func readPackageFileZip(r ReadSeekAt, filename string, limit int) ([]byte, error) { + archive, err := zip.NewReader(r, r.Size()) if err != nil { return nil, err } for _, file := range archive.File { - if strings.Count(file.Name, "/") > 1 { - continue - } - if strings.HasSuffix(strings.ToLower(file.Name), "composer.json") { + filePath := path.Clean(file.Name) + if util.AsciiEqualFold(filePath, filename) { f, err := archive.Open(file.Name) if err != nil { return nil, err } defer f.Close() - return ParseComposerFile(archive, path.Dir(file.Name), f) + return util.ReadWithLimit(f, limit) + } + } + return nil, fs.ErrNotExist +} + +func readPackageFileTar(r io.Reader, filename string, limit int) ([]byte, error) { + tarReader := tar.NewReader(r) + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } else if err != nil { + return nil, err + } + + filePath := path.Clean(header.Name) + if util.AsciiEqualFold(filePath, filename) { + return util.ReadWithLimit(tarReader, limit) } } - return nil, ErrMissingComposerFile + return nil, fs.ErrNotExist } -// ParseComposerFile parses a composer.json file to retrieve the metadata of a Composer package -func ParseComposerFile(archive *zip.Reader, pathPrefix string, r io.Reader) (*Package, error) { +const ( + pkgExtZip = ".zip" + pkgExtTarGz = ".tar.gz" + pkgExtTarBz2 = ".tar.bz2" +) + +func detectPackageExtName(r ReadSeekAt) (string, error) { + headBytes := make([]byte, 4) + _, err := r.ReadAt(headBytes, 0) + if err != nil { + return "", err + } + _, err = r.Seek(0, io.SeekStart) + if err != nil { + return "", err + } + switch { + case headBytes[0] == 'P' && headBytes[1] == 'K': + return pkgExtZip, nil + case string(headBytes[:3]) == "BZh": + return pkgExtTarBz2, nil + case headBytes[0] == 0x1f && headBytes[1] == 0x8b: + return pkgExtTarGz, nil + } + return "", util.NewInvalidArgumentErrorf("not a valid package file") +} + +func readPackageFile(pkgExt string, r ReadSeekAt, filename string, limit int) ([]byte, error) { + _, err := r.Seek(0, io.SeekStart) + if err != nil { + return nil, err + } + + switch pkgExt { + case pkgExtZip: + return readPackageFileZip(r, filename, limit) + case pkgExtTarBz2: + bzip2Reader := bzip2.NewReader(r) + return readPackageFileTar(bzip2Reader, filename, limit) + case pkgExtTarGz: + gzReader, err := gzip.NewReader(r) + if err != nil { + return nil, err + } + return readPackageFileTar(gzReader, filename, limit) + } + return nil, util.NewInvalidArgumentErrorf("not a valid package file") +} + +// ParsePackage parses the metadata of a Composer package file +func ParsePackage(r ReadSeekAt, optVersion ...string) (*PackageInfo, error) { + pkgExt, err := detectPackageExtName(r) + if err != nil { + return nil, err + } + dataComposerJSON, err := readPackageFile(pkgExt, r, "composer.json", 10*1024*1024) + if errors.Is(err, fs.ErrNotExist) { + return nil, ErrMissingComposerFile + } else if err != nil { + return nil, err + } + var cj struct { Name string `json:"name"` Version string `json:"version"` Type string `json:"type"` Metadata } - if err := json.NewDecoder(r).Decode(&cj); err != nil { + if err := json.Unmarshal(dataComposerJSON, &cj); err != nil { return nil, err } @@ -151,6 +240,9 @@ func ParseComposerFile(archive *zip.Reader, pathPrefix string, r io.Reader) (*Pa return nil, ErrInvalidName } + if cj.Version == "" { + cj.Version = util.OptionalArg(optVersion) + } if cj.Version != "" { if _, err := version.NewSemver(cj.Version); err != nil { return nil, ErrInvalidVersion @@ -168,17 +260,23 @@ func ParseComposerFile(archive *zip.Reader, pathPrefix string, r io.Reader) (*Pa if cj.Readme == "" { cj.Readme = "README.md" } - f, err := archive.Open(path.Join(pathPrefix, cj.Readme)) - if err == nil { - // 10kb limit for readme content - buf, _ := io.ReadAll(io.LimitReader(f, 10*1024)) - cj.Readme = string(buf) - _ = f.Close() - } else { + dataReadmeMd, _ := readPackageFile(pkgExt, r, cj.Readme, 10*1024) + + // FIXME: legacy problem, the "Readme" field is abused, it should always be the path to the readme file + if len(dataReadmeMd) == 0 { cj.Readme = "" + } else { + cj.Readme = string(dataReadmeMd) } - return &Package{ + // FIXME: legacy format: strings.ToLower(fmt.Sprintf("%s.%s.zip", strings.ReplaceAll(cp.Name, "/", "-"), cp.Version)), doesn't read good + pkgFilename := strings.ReplaceAll(cj.Name, "/", "-") + if cj.Version != "" { + pkgFilename += "." + cj.Version + } + pkgFilename += pkgExt + return &PackageInfo{ + Filename: pkgFilename, Name: cj.Name, Version: cj.Version, Type: cj.Type, diff --git a/modules/packages/composer/metadata_test.go b/modules/packages/composer/metadata_test.go index a5e317daf1..4eca4d92e7 100644 --- a/modules/packages/composer/metadata_test.go +++ b/modules/packages/composer/metadata_test.go @@ -4,14 +4,19 @@ package composer import ( + "archive/tar" "archive/zip" "bytes" + "compress/gzip" + "io" "strings" "testing" "code.gitea.io/gitea/modules/json" + "github.com/dsnet/compress/bzip2" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const ( @@ -26,8 +31,10 @@ const ( license = "MIT" ) -const composerContent = `{ +func buildComposerContent(version string) string { + return `{ "name": "` + name + `", + "version": "` + version + `", "description": "` + description + `", "type": "` + packageType + `", "license": "` + license + `", @@ -44,8 +51,9 @@ const composerContent = `{ "require": { "php": ">=7.2 || ^8.0" }, - "_comments": "` + comments + `" + "_comment": "` + comments + `" }` +} func TestLicenseUnmarshal(t *testing.T) { var l Licenses @@ -73,16 +81,34 @@ func TestParsePackage(t *testing.T) { archive := zip.NewWriter(&buf) for name, content := range files { w, _ := archive.Create(name) - w.Write([]byte(content)) + _, _ = w.Write([]byte(content)) + } + _ = archive.Close() + return buf.Bytes() + } + + createArchiveTar := func(comp func(io.Writer) io.WriteCloser, files map[string]string) []byte { + var buf bytes.Buffer + w := comp(&buf) + archive := tar.NewWriter(w) + for name, content := range files { + hdr := &tar.Header{ + Name: name, + Mode: 0o600, + Size: int64(len(content)), + } + _ = archive.WriteHeader(hdr) + _, _ = archive.Write([]byte(content)) } - archive.Close() + _ = w.Close() + _ = archive.Close() return buf.Bytes() } t.Run("MissingComposerFile", func(t *testing.T) { data := createArchive(map[string]string{"dummy.txt": ""}) - cp, err := ParsePackage(bytes.NewReader(data), int64(len(data))) + cp, err := ParsePackage(bytes.NewReader(data)) assert.Nil(t, cp) assert.ErrorIs(t, err, ErrMissingComposerFile) }) @@ -90,7 +116,7 @@ func TestParsePackage(t *testing.T) { t.Run("MissingComposerFileInRoot", func(t *testing.T) { data := createArchive(map[string]string{"sub/sub/composer.json": ""}) - cp, err := ParsePackage(bytes.NewReader(data), int64(len(data))) + cp, err := ParsePackage(bytes.NewReader(data)) assert.Nil(t, cp) assert.ErrorIs(t, err, ErrMissingComposerFile) }) @@ -98,7 +124,7 @@ func TestParsePackage(t *testing.T) { t.Run("InvalidComposerFile", func(t *testing.T) { data := createArchive(map[string]string{"composer.json": ""}) - cp, err := ParsePackage(bytes.NewReader(data), int64(len(data))) + cp, err := ParsePackage(bytes.NewReader(data)) assert.Nil(t, cp) assert.Error(t, err) }) @@ -106,7 +132,7 @@ func TestParsePackage(t *testing.T) { t.Run("InvalidPackageName", func(t *testing.T) { data := createArchive(map[string]string{"composer.json": "{}"}) - cp, err := ParsePackage(bytes.NewReader(data), int64(len(data))) + cp, err := ParsePackage(bytes.NewReader(data)) assert.Nil(t, cp) assert.ErrorIs(t, err, ErrInvalidName) }) @@ -114,7 +140,7 @@ func TestParsePackage(t *testing.T) { t.Run("InvalidPackageVersion", func(t *testing.T) { data := createArchive(map[string]string{"composer.json": `{"name": "gitea/composer-package", "version": "1.a.3"}`}) - cp, err := ParsePackage(bytes.NewReader(data), int64(len(data))) + cp, err := ParsePackage(bytes.NewReader(data)) assert.Nil(t, cp) assert.ErrorIs(t, err, ErrInvalidVersion) }) @@ -122,22 +148,21 @@ func TestParsePackage(t *testing.T) { t.Run("InvalidReadmePath", func(t *testing.T) { data := createArchive(map[string]string{"composer.json": `{"name": "gitea/composer-package", "readme": "sub/README.md"}`}) - cp, err := ParsePackage(bytes.NewReader(data), int64(len(data))) + cp, err := ParsePackage(bytes.NewReader(data)) assert.NoError(t, err) assert.NotNil(t, cp) assert.Empty(t, cp.Metadata.Readme) }) - t.Run("Valid", func(t *testing.T) { - data := createArchive(map[string]string{"composer.json": composerContent, "README.md": readme}) - - cp, err := ParsePackage(bytes.NewReader(data), int64(len(data))) - assert.NoError(t, err) + assertValidPackage := func(t *testing.T, data []byte, version, filename string) { + cp, err := ParsePackage(bytes.NewReader(data)) + require.NoError(t, err) assert.NotNil(t, cp) + assert.Equal(t, filename, cp.Filename) assert.Equal(t, name, cp.Name) - assert.Empty(t, cp.Version) + assert.Equal(t, version, cp.Version) assert.Equal(t, description, cp.Metadata.Description) assert.Equal(t, readme, cp.Metadata.Readme) assert.Len(t, cp.Metadata.Comments, 1) @@ -149,5 +174,25 @@ func TestParsePackage(t *testing.T) { assert.Equal(t, packageType, cp.Type) assert.Len(t, cp.Metadata.License, 1) assert.Equal(t, license, cp.Metadata.License[0]) + } + + t.Run("ValidZip", func(t *testing.T) { + data := createArchive(map[string]string{"composer.json": buildComposerContent(""), "README.md": readme}) + assertValidPackage(t, data, "", "gitea-composer-package.zip") + }) + + t.Run("ValidTarBz2", func(t *testing.T) { + data := createArchiveTar(func(w io.Writer) io.WriteCloser { + bz2Writer, _ := bzip2.NewWriter(w, nil) + return bz2Writer + }, map[string]string{"composer.json": buildComposerContent("1.0"), "README.md": readme}) + assertValidPackage(t, data, "1.0", "gitea-composer-package.1.0.tar.bz2") + }) + + t.Run("ValidTarGz", func(t *testing.T) { + data := createArchiveTar(func(w io.Writer) io.WriteCloser { + return gzip.NewWriter(w) + }, map[string]string{"composer.json": buildComposerContent(""), "README.md": readme}) + assertValidPackage(t, data, "", "gitea-composer-package.tar.gz") }) } diff --git a/modules/packages/nuget/metadata.go b/modules/packages/nuget/metadata.go index 513b4dd2b9..5124627395 100644 --- a/modules/packages/nuget/metadata.go +++ b/modules/packages/nuget/metadata.go @@ -216,7 +216,7 @@ func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) { if p.Metadata.Readme != "" { f, err := archive.Open(p.Metadata.Readme) if err == nil { - buf, _ := io.ReadAll(f) + buf, _ := util.ReadWithLimit(f, 1024*1024) m.Readme = string(buf) _ = f.Close() } diff --git a/modules/packages/pub/metadata.go b/modules/packages/pub/metadata.go index 9b00472eb2..a2cf6b728a 100644 --- a/modules/packages/pub/metadata.go +++ b/modules/packages/pub/metadata.go @@ -89,7 +89,7 @@ func ParsePackage(r io.Reader) (*Package, error) { return nil, err } } else if strings.EqualFold(hd.Name, "readme.md") { - data, err := io.ReadAll(tr) + data, err := util.ReadWithLimit(tr, 1024*1024) if err != nil { return nil, err } diff --git a/modules/repository/env.go b/modules/repository/env.go index 78e06f86fb..55a81f006e 100644 --- a/modules/repository/env.go +++ b/modules/repository/env.go @@ -25,6 +25,7 @@ const ( EnvKeyID = "GITEA_KEY_ID" // public key ID EnvDeployKeyID = "GITEA_DEPLOY_KEY_ID" EnvPRID = "GITEA_PR_ID" + EnvPRIndex = "GITEA_PR_INDEX" // not used by Gitea at the moment, it is for custom git hooks EnvPushTrigger = "GITEA_PUSH_TRIGGER" EnvIsInternal = "GITEA_INTERNAL_PUSH" EnvAppURL = "GITEA_ROOT_URL" @@ -50,11 +51,11 @@ func InternalPushingEnvironment(doer *user_model.User, repo *repo_model.Reposito // PushingEnvironment returns an os environment to allow hooks to work on push func PushingEnvironment(doer *user_model.User, repo *repo_model.Repository) []string { - return FullPushingEnvironment(doer, doer, repo, repo.Name, 0) + return FullPushingEnvironment(doer, doer, repo, repo.Name, 0, 0) } // FullPushingEnvironment returns an os environment to allow hooks to work on push -func FullPushingEnvironment(author, committer *user_model.User, repo *repo_model.Repository, repoName string, prID int64) []string { +func FullPushingEnvironment(author, committer *user_model.User, repo *repo_model.Repository, repoName string, prID, prIndex int64) []string { isWiki := "false" if strings.HasSuffix(repoName, ".wiki") { isWiki = "true" @@ -75,6 +76,7 @@ func FullPushingEnvironment(author, committer *user_model.User, repo *repo_model EnvPusherID+"="+strconv.FormatInt(committer.ID, 10), EnvRepoID+"="+strconv.FormatInt(repo.ID, 10), EnvPRID+"="+strconv.FormatInt(prID, 10), + EnvPRIndex+"="+strconv.FormatInt(prIndex, 10), EnvAppURL+"="+setting.AppURL, "SSH_ORIGINAL_COMMAND=gitea-internal", ) diff --git a/modules/setting/oauth2.go b/modules/setting/oauth2.go index 1a88f3cb08..ae2a9d7bee 100644 --- a/modules/setting/oauth2.go +++ b/modules/setting/oauth2.go @@ -96,6 +96,7 @@ var OAuth2 = struct { InvalidateRefreshTokens bool JWTSigningAlgorithm string `ini:"JWT_SIGNING_ALGORITHM"` JWTSigningPrivateKeyFile string `ini:"JWT_SIGNING_PRIVATE_KEY_FILE"` + JWTClaimIssuer string `ini:"JWT_CLAIM_ISSUER"` MaxTokenLength int DefaultApplications []string }{ diff --git a/modules/setting/server.go b/modules/setting/server.go index 38e166e02a..cedca32da9 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -235,9 +235,6 @@ func loadServerFrom(rootCfg ConfigProvider) { deprecatedSetting(rootCfg, "server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL", "v1.19.0") AcmeEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("") } - if AcmeEmail == "" { - log.Fatal("ACME Email is not set (ACME_EMAIL).") - } } else { CertFile = sec.Key("CERT_FILE").String() KeyFile = sec.Key("KEY_FILE").String() diff --git a/modules/storage/azureblob.go b/modules/storage/azureblob.go index 6860d81131..e7297cec77 100644 --- a/modules/storage/azureblob.go +++ b/modules/storage/azureblob.go @@ -250,6 +250,7 @@ func (a *AzureBlobStorage) Delete(path string) error { func (a *AzureBlobStorage) URL(path, name, _ string, reqParams url.Values) (*url.URL, error) { blobClient := a.getBlobClient(path) + // TODO: OBJECT-STORAGE-CONTENT-TYPE: "browser inline rendering images/PDF" needs proper Content-Type header from storage startTime := time.Now() u, err := blobClient.GetSASURL(sas.BlobPermissions{ Read: true, diff --git a/modules/storage/minio.go b/modules/storage/minio.go index 01f2c16267..6993ac2d92 100644 --- a/modules/storage/minio.go +++ b/modules/storage/minio.go @@ -279,20 +279,44 @@ func (m *MinioStorage) Delete(path string) error { } // URL gets the redirect URL to a file. The presigned link is valid for 5 minutes. -func (m *MinioStorage) URL(path, name, method string, serveDirectReqParams url.Values) (*url.URL, error) { +func (m *MinioStorage) URL(storePath, name, method string, serveDirectReqParams url.Values) (*url.URL, error) { // copy serveDirectReqParams reqParams, err := url.ParseQuery(serveDirectReqParams.Encode()) if err != nil { return nil, err } - // TODO it may be good to embed images with 'inline' like ServeData does, but we don't want to have to read the file, do we? - reqParams.Set("response-content-disposition", "attachment; filename=\""+quoteEscaper.Replace(name)+"\"") + + // Here we might not know the real filename, and it's quite inefficient to detect the mine type by pre-fetching the object head. + // So we just do a quick detection by extension name, at least if works for the "View Raw File" for an LFS file on the Web UI. + // Detect content type by extension name, only support the well-known safe types for inline rendering. + // TODO: OBJECT-STORAGE-CONTENT-TYPE: need a complete solution and refactor for Azure in the future + ext := path.Ext(name) + inlineExtMimeTypes := map[string]string{ + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".gif": "image/gif", + ".webp": "image/webp", + ".avif": "image/avif", + // ATTENTION! Don't support unsafe types like HTML/SVG due to security concerns: they can contain JS code, and maybe they need proper Content-Security-Policy + // HINT: PDF-RENDER-SANDBOX: PDF won't render in sandboxed context, it seems fine to render it inline + ".pdf": "application/pdf", + + // TODO: refactor with "modules/public/mime_types.go", for example: "DetectWellKnownSafeInlineMimeType" + } + if mimeType, ok := inlineExtMimeTypes[ext]; ok { + reqParams.Set("response-content-type", mimeType) + reqParams.Set("response-content-disposition", "inline") + } else { + reqParams.Set("response-content-disposition", fmt.Sprintf(`attachment; filename="%s"`, quoteEscaper.Replace(name))) + } + expires := 5 * time.Minute if method == http.MethodHead { - u, err := m.client.PresignedHeadObject(m.ctx, m.bucket, m.buildMinioPath(path), expires, reqParams) + u, err := m.client.PresignedHeadObject(m.ctx, m.bucket, m.buildMinioPath(storePath), expires, reqParams) return u, convertMinioErr(err) } - u, err := m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(path), expires, reqParams) + u, err := m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(storePath), expires, reqParams) return u, convertMinioErr(err) } diff --git a/modules/util/io.go b/modules/util/io.go index b3dde9d1f6..f5a3d320e5 100644 --- a/modules/util/io.go +++ b/modules/util/io.go @@ -29,7 +29,7 @@ func ReadAtMost(r io.Reader, buf []byte) (n int, err error) { // ReadWithLimit reads at most "limit" bytes from r into buf. // If EOF or ErrUnexpectedEOF occurs while reading, err will be nil. func ReadWithLimit(r io.Reader, n int) (buf []byte, err error) { - return readWithLimit(r, 1024, n) + return readWithLimit(r, 4*1024, n) } func readWithLimit(r io.Reader, batch, limit int) ([]byte, error) { diff --git a/options/fileicon/material-icon-rules.json b/options/fileicon/material-icon-rules.json index a9e3ad74f5..d8c7156291 100644 --- a/options/fileicon/material-icon-rules.json +++ b/options/fileicon/material-icon-rules.json @@ -592,10 +592,10 @@ ".settings": "folder-config", "_settings": "folder-config", "__settings__": "folder-config", - "META-INF": "folder-config", - ".META-INF": "folder-config", - "_META-INF": "folder-config", - "__META-INF__": "folder-config", + "meta-inf": "folder-config", + ".meta-inf": "folder-config", + "_meta-inf": "folder-config", + "__meta-inf__": "folder-config", "option": "folder-config", ".option": "folder-config", "_option": "folder-config", @@ -2196,14 +2196,14 @@ ".templates": "folder-template", "_templates": "folder-template", "__templates__": "folder-template", - "github/ISSUE_TEMPLATE": "folder-template", - ".github/ISSUE_TEMPLATE": "folder-template", - "_github/ISSUE_TEMPLATE": "folder-template", - "__github/ISSUE_TEMPLATE__": "folder-template", - "github/PULL_REQUEST_TEMPLATE": "folder-template", - ".github/PULL_REQUEST_TEMPLATE": "folder-template", - "_github/PULL_REQUEST_TEMPLATE": "folder-template", - "__github/PULL_REQUEST_TEMPLATE__": "folder-template", + "github/issue_template": "folder-template", + ".github/issue_template": "folder-template", + "_github/issue_template": "folder-template", + "__github/issue_template__": "folder-template", + "github/pull_request_template": "folder-template", + ".github/pull_request_template": "folder-template", + "_github/pull_request_template": "folder-template", + "__github/pull_request_template__": "folder-template", "util": "folder-utils", ".util": "folder-utils", "_util": "folder-utils", @@ -2328,22 +2328,22 @@ ".osx": "folder-macos", "_osx": "folder-macos", "__osx__": "folder-macos", - "DS_Store": "folder-macos", - ".DS_Store": "folder-macos", - "_DS_Store": "folder-macos", - "__DS_Store__": "folder-macos", - "iPhone": "folder-macos", - ".iPhone": "folder-macos", - "_iPhone": "folder-macos", - "__iPhone__": "folder-macos", - "iPad": "folder-macos", - ".iPad": "folder-macos", - "_iPad": "folder-macos", - "__iPad__": "folder-macos", - "iPod": "folder-macos", - ".iPod": "folder-macos", - "_iPod": "folder-macos", - "__iPod__": "folder-macos", + "ds_store": "folder-macos", + ".ds_store": "folder-macos", + "_ds_store": "folder-macos", + "__ds_store__": "folder-macos", + "iphone": "folder-macos", + ".iphone": "folder-macos", + "_iphone": "folder-macos", + "__iphone__": "folder-macos", + "ipad": "folder-macos", + ".ipad": "folder-macos", + "_ipad": "folder-macos", + "__ipad__": "folder-macos", + "ipod": "folder-macos", + ".ipod": "folder-macos", + "_ipod": "folder-macos", + "__ipod__": "folder-macos", "macbook": "folder-macos", ".macbook": "folder-macos", "_macbook": "folder-macos", @@ -3474,35 +3474,7 @@ "cues": "folder-cue", ".cues": "folder-cue", "_cues": "folder-cue", - "__cues__": "folder-cue", - "meta-inf": "folder-config", - ".meta-inf": "folder-config", - "_meta-inf": "folder-config", - "__meta-inf__": "folder-config", - "github/issue_template": "folder-template", - ".github/issue_template": "folder-template", - "_github/issue_template": "folder-template", - "__github/issue_template__": "folder-template", - "github/pull_request_template": "folder-template", - ".github/pull_request_template": "folder-template", - "_github/pull_request_template": "folder-template", - "__github/pull_request_template__": "folder-template", - "ds_store": "folder-macos", - ".ds_store": "folder-macos", - "_ds_store": "folder-macos", - "__ds_store__": "folder-macos", - "iphone": "folder-macos", - ".iphone": "folder-macos", - "_iphone": "folder-macos", - "__iphone__": "folder-macos", - "ipad": "folder-macos", - ".ipad": "folder-macos", - "_ipad": "folder-macos", - "__ipad__": "folder-macos", - "ipod": "folder-macos", - ".ipod": "folder-macos", - "_ipod": "folder-macos", - "__ipod__": "folder-macos" + "__cues__": "folder-cue" }, "folderNamesExpanded": { "rust": "folder-rust-open", @@ -7021,7 +6993,7 @@ "twee": "twine", "yml.dist": "yaml", "yaml.dist": "yaml", - "YAML-tmLanguage": "yaml", + "yaml-tmlanguage": "yaml", "xml": "xml", "plist": "xml", "xsd": "xml", @@ -7031,7 +7003,7 @@ "resx": "xml", "iml": "xml", "xquery": "xml", - "tmLanguage": "xml", + "tmlanguage": "xml", "manifest": "xml", "project": "xml", "xml.dist": "xml", @@ -8133,8 +8105,6 @@ "css.jsx": "vanilla-extract", "toc": "toc", "cue": "cue", - "yaml-tmlanguage": "yaml", - "tmlanguage": "xml", "cljx": "clojure", "clojure": "clojure", "edn": "clojure", @@ -8624,7 +8594,7 @@ "graphql.config.mts": "graphql", "graphql.config.cts": "graphql", ".graphqlconfig": "graphql", - "XamlStyler.json": "xaml", + "xamlstyler.json": "xaml", ".happo.js": "happo", ".happo.mjs": "happo", ".happo.cjs": "happo", @@ -8648,11 +8618,11 @@ ".git-for-windows-updater": "git", "git-history": "git", ".luacheckrc": "lua", - ".Rhistory": "r", + ".rhistory": "r", ".pubignore": "dart", "cmakelists.txt": "cmake", "cmakecache.txt": "cmake", - "CMakePresets.json": "cmake", + "cmakepresets.json": "cmake", "semgrep.yml": "semgrep", ".semgrepignore": "semgrep", "vue.config.js": "vue-config", @@ -8783,7 +8753,7 @@ "cabal.project": "cabal", "cabal.project.freeze": "cabal", "cabal.project.local": "cabal", - "CNAME": "http", + "cname": "http", "project.graphcool": "graphcool", "webpack.base.js": "webpack", "webpack.base.mjs": "webpack", @@ -9215,7 +9185,7 @@ "sonar-project.properties": "sonarcloud", ".sonarcloud.properties": "sonarcloud", "sonarcloud.yaml": "sonarcloud", - "SonarQube.Analysis.xml": "sonarcloud", + "sonarqube.analysis.xml": "sonarcloud", "protractor.conf.js": "protractor", "protractor.conf.ts": "protractor", "protractor.conf.coffee": "protractor", @@ -9577,7 +9547,7 @@ ".gitpod.yml": "gitpod", ".stackblitzrc": "stackblitz", "codeowners": "codeowners", - "OWNERS": "codeowners", + "owners": "codeowners", ".gcloudignore": "gcp", "amplify.yml": "amplify", ".huskyrc": "husky", @@ -9939,7 +9909,7 @@ "steadybit.yml": "steadybit", ".steadybit.yaml": "steadybit", "steadybit.yaml": "steadybit", - "Caddyfile": "caddy", + "caddyfile": "caddy", "openapi.json": "openapi", "openapi.yml": "openapi", "openapi.yaml": "openapi", @@ -10157,8 +10127,8 @@ "project.garden.yml": "garden", "project.garden.yaml": "garden", ".gardenignore": "garden", - "PklProject": "pkl", - "PklProject.deps.json": "pkl", + "pklproject": "pkl", + "pklproject.deps.json": "pkl", "k8s.yml": "kubernetes", "k8s.yaml": "kubernetes", "kubernetes.yml": "kubernetes", @@ -10266,7 +10236,7 @@ ".histoire.cts": "histoire", "install": "installation", "installation": "installation", - ".github/FUNDING.yml": "github-sponsors", + ".github/funding.yml": "github-sponsors", "fabric.mod.json": "minecraft-fabric", ".umirc.js": "umi", ".umirc.mjs": "umi", @@ -10297,15 +10267,15 @@ "packship.config.mjs": "packship", "packship.config.mts": "packship", "packship.config.json": "packship", - "Snakefile": "snakemake", + "snakefile": "snakemake", ".hadolint.yaml": "hadolint", ".hadolint.yml": "hadolint", "hadolint.yaml": "hadolint", "hadolint.yml": "hadolint", "tsdoc.json": "tsdoc", ".oxlintrc.json": "oxlint", - "CLAUDE.md": "claude", - "CLAUDE.local.md": "claude", + "claude.md": "claude", + "claude.local.md": "claude", ".cursorignore": "cursor", ".cursorindexingignore": "cursor", ".cursorrules": "cursor", @@ -10323,23 +10293,9 @@ "src/bashly-strings.yaml": "bashly-strings", "src/bashly-strings.yml": "bashly-strings", "google-services.json": "google", - "GoogleService-Info.plist": "google", + "googleservice-info.plist": "google", ".shellcheckrc": "shellcheck", "shellcheckrc": "shellcheck", - "xamlstyler.json": "xaml", - ".rhistory": "r", - "cmakepresets.json": "cmake", - "cname": "http", - "sonarqube.analysis.xml": "sonarcloud", - "owners": "codeowners", - "caddyfile": "caddy", - "pklproject": "pkl", - "pklproject.deps.json": "pkl", - ".github/funding.yml": "github-sponsors", - "snakefile": "snakemake", - "claude.md": "claude", - "claude.local.md": "claude", - "googleservice-info.plist": "google", "language-configuration.json": "jsonc", "icon-theme.json": "jsonc", "color-theme.json": "jsonc", diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 428b5f8178..f1f5434f47 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1482,6 +1482,7 @@ projects.column.new_submit = "Create Column" projects.column.new = "New Column" projects.column.set_default = "Set Default" projects.column.set_default_desc = "Set this column as default for uncategorized issues and pulls" +projects.column.default_column_hint = "New issues added to this project will be added to this column" projects.column.delete = "Delete Column" projects.column.deletion_desc = "Deleting a project column moves all related issues to the default column. Continue?" projects.column.color = "Color" @@ -3050,7 +3051,7 @@ dashboard.update_migration_poster_id = Update migration poster IDs dashboard.git_gc_repos = Garbage-collect all repositories dashboard.resync_all_sshkeys = Update the '.ssh/authorized_keys' file with Gitea SSH keys dashboard.resync_all_sshprincipals = Update the '.ssh/authorized_principals' file with Gitea SSH principals -dashboard.resync_all_hooks = Resynchronize pre-receive, update and post-receive hooks of all repositories +dashboard.resync_all_hooks = Resynchronize git hooks of all repositories (pre-receive, update, post-receive, proc-receive, ...) dashboard.reinit_missing_repos = Reinitialize all missing Git repositories for which records exist dashboard.sync_external_users = Synchronize external user data dashboard.cleanup_hook_task_table = Clean up hook_task table diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 4ee11f3b5d..221abb5d1f 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -3038,7 +3038,6 @@ dashboard.update_migration_poster_id=Actualiser les ID des affiches de migration dashboard.git_gc_repos=Exécuter le ramasse-miette des dépôts dashboard.resync_all_sshkeys=Mettre à jour le fichier « ssh/authorized_keys » avec les clés SSH Gitea. dashboard.resync_all_sshprincipals=Mettre à jour le fichier « .ssh/authorized_principals » avec les principaux de Gitea SSH. -dashboard.resync_all_hooks=Re-synchroniser les déclencheurs Git pre-receive, update et post-receive de tous les dépôts. dashboard.reinit_missing_repos=Réinitialiser tous les dépôts Git manquants pour lesquels un enregistrement existe dashboard.sync_external_users=Synchroniser les données de l’utilisateur externe dashboard.cleanup_hook_task_table=Nettoyer la table hook_task diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini index 045fb14f8b..abda334ff5 100644 --- a/options/locale/locale_ga-IE.ini +++ b/options/locale/locale_ga-IE.ini @@ -3038,7 +3038,6 @@ dashboard.update_migration_poster_id=Nuashonraigh ID póstaer imir dashboard.git_gc_repos=Bailitheoir bruscair gach stórais dashboard.resync_all_sshkeys=Nuashonraigh an comhad '.ssh/authorized_keys' le heochracha SSH Gitea dashboard.resync_all_sshprincipals=Nuashonraigh an comhad '.ssh/authorized_principals' le príomhoidí SSH Gitea -dashboard.resync_all_hooks=Athshioncrónaigh crúcaí réamhghlactha, nuashonraithe agus iarghlactha na stórais uile dashboard.reinit_missing_repos=Aththosaigh gach stórais Git atá in easnamh a bhfuil taifid ann dóibh dashboard.sync_external_users=Sioncrónaigh sonraí úsáideoirí seachtracha dashboard.cleanup_hook_task_table=Glan suas an tábla hook_task diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 0d7df0647b..32d7b51ae9 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -3038,7 +3038,6 @@ dashboard.update_migration_poster_id=移行する投稿者IDの更新 dashboard.git_gc_repos=すべてのリポジトリでガベージコレクションを実行 dashboard.resync_all_sshkeys='.ssh/authorized_keys' ファイルをGitea上のSSHキーで更新 dashboard.resync_all_sshprincipals='.ssh/authorized_principals' ファイルをGitea上のSSHプリンシパルで更新 -dashboard.resync_all_hooks=すべてのリポジトリの pre-receive, update, post-receive フックを再同期する dashboard.reinit_missing_repos=レコードが存在するが見当たらないすべてのGitリポジトリを再初期化する dashboard.sync_external_users=外部ユーザーデータの同期 dashboard.cleanup_hook_task_table=hook_taskテーブルのクリーンアップ diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 95ac1fb5a4..22e16de93f 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -1482,6 +1482,7 @@ projects.column.new_submit=Criar coluna projects.column.new=Nova coluna projects.column.set_default=Tornar predefinida projects.column.set_default_desc=Definir esta coluna como a predefinida para questões e pedidos de integração não categorizados +projects.column.default_column_hint=Novas questões adicionadas a este planeamento serão adicionadas a esta coluna projects.column.delete=Eliminar coluna projects.column.deletion_desc=Eliminar uma coluna de um planeamento faz com que todas as questões que nela constam sejam movidas para a coluna predefinida. Continuar? projects.column.color=Colorido @@ -3038,7 +3039,6 @@ dashboard.update_migration_poster_id=Sincronizar os IDs do remetente da migraç dashboard.git_gc_repos=Fazer a recolha do lixo em todos os repositórios dashboard.resync_all_sshkeys=Sincronizar o ficheiro '.ssh/authorized_keys' com as chaves SSH do Gitea dashboard.resync_all_sshprincipals=Modificar o ficheiro '.ssh/authorized_principals' com os protagonistas SSH do Gitea -dashboard.resync_all_hooks=Voltar a sincronizar automatismos de pré-acolhimento, modificação e pós-acolhimento de todos os repositórios dashboard.reinit_missing_repos=Reinicializar todos os repositórios Git em falta para os quais existam registos dashboard.sync_external_users=Sincronizar dados externos do utilizador dashboard.cleanup_hook_task_table=Limpar a tabela hook_task diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 8be6a587fb..654e610cab 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -3032,7 +3032,6 @@ dashboard.update_migration_poster_id=Taşıma poster kimliklerini güncelle dashboard.git_gc_repos=Tüm depolardaki atıkları temizle dashboard.resync_all_sshkeys='.ssh/authority_keys' dosyasını Gitea SSH anahtarlarıyla güncelle dashboard.resync_all_sshprincipals='.ssh/authorized_principals' dosyasını Gitea SSH sorumlularıyla güncelleyin -dashboard.resync_all_hooks=Tüm depoların alma öncesi, güncelleme ve alma sonrası kancalarını yeniden senkronize edin dashboard.reinit_missing_repos=Kayıtları bulunanlar için tüm eksik Git depolarını yeniden başlat dashboard.sync_external_users=Harici kullanıcı verisini senkronize et dashboard.cleanup_hook_task_table=Hook_task tablosunu temizle diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 9db58e71ac..6270d35334 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -3036,7 +3036,6 @@ dashboard.update_migration_poster_id=更新迁移的发表者ID dashboard.git_gc_repos=对仓库进行垃圾回收 dashboard.resync_all_sshkeys=使用 Gitea 的 SSH 密钥更新「.ssh/authorized_keys」文件。 dashboard.resync_all_sshprincipals=使用 Gitea 的 SSH 规则更新「.ssh/authorized_principals」文件。 -dashboard.resync_all_hooks=重新同步所有仓库的 pre-receive、update 和 post-receive 钩子 dashboard.reinit_missing_repos=重新初始化所有丢失的 Git 仓库存在的记录 dashboard.sync_external_users=同步外部用户数据 dashboard.cleanup_hook_task_table=清理 hook_task 表 diff --git a/package.json b/package.json index 015e87f478..2f81034952 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "type": "module", - "packageManager": "pnpm@10.19.0", + "packageManager": "pnpm@10.22.0", "engines": { "node": ">= 22.6.0", "pnpm": ">= 10.0.0" @@ -15,7 +15,7 @@ "@github/relative-time-element": "4.5.0", "@github/text-expander-element": "2.9.2", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", - "@primer/octicons": "19.19.0", + "@primer/octicons": "19.21.0", "@resvg/resvg-wasm": "2.6.2", "@silverwind/vue3-calendar-heatmap": "2.0.6", "@techknowlogick/license-checker-webpack-plugin": "0.3.0", @@ -25,10 +25,10 @@ "chart.js": "4.5.1", "chartjs-adapter-dayjs-4": "1.0.4", "chartjs-plugin-zoom": "2.2.0", - "clippie": "4.1.8", + "clippie": "4.1.9", "cropperjs": "1.6.2", "css-loader": "7.1.2", - "dayjs": "1.11.18", + "dayjs": "1.11.19", "dropzone": "6.0.0-beta.2", "easymde": "2.20.0", "esbuild-loader": "4.4.0", @@ -46,7 +46,7 @@ "postcss": "8.5.6", "postcss-loader": "8.2.0", "sortablejs": "1.15.6", - "swagger-ui-dist": "5.30.0", + "swagger-ui-dist": "5.30.2", "tailwindcss": "3.4.17", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", @@ -56,18 +56,18 @@ "typescript": "5.9.3", "uint8-to-base64": "0.2.1", "vanilla-colorful": "0.7.2", - "vue": "3.5.22", + "vue": "3.5.24", "vue-bar-graph": "2.2.0", - "vue-chartjs": "5.3.2", + "vue-chartjs": "5.3.3", "vue-loader": "17.4.2", - "webpack": "5.102.1", + "webpack": "5.103.0", "webpack-cli": "6.0.1", "wrap-ansi": "9.0.2" }, "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "4.5.0", "@playwright/test": "1.56.1", - "@stylistic/eslint-plugin": "5.5.0", + "@stylistic/eslint-plugin": "5.6.1", "@stylistic/stylelint-plugin": "4.0.0", "@types/codemirror": "5.60.17", "@types/dropzone": "5.7.9", @@ -79,25 +79,25 @@ "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/toastify-js": "1.12.4", - "@typescript-eslint/parser": "8.46.2", - "@vitejs/plugin-vue": "6.0.1", - "@vitest/eslint-plugin": "1.3.26", - "eslint": "9.38.0", + "@typescript-eslint/parser": "8.47.0", + "@vitejs/plugin-vue": "6.0.2", + "@vitest/eslint-plugin": "1.4.3", + "eslint": "9.39.1", "eslint-import-resolver-typescript": "4.4.4", "eslint-plugin-array-func": "5.1.0", "eslint-plugin-github": "6.0.0", "eslint-plugin-import-x": "4.16.1", "eslint-plugin-no-use-extend-native": "0.7.2", - "eslint-plugin-playwright": "2.2.2", + "eslint-plugin-playwright": "2.3.0", "eslint-plugin-regexp": "2.10.0", "eslint-plugin-sonarjs": "3.0.5", "eslint-plugin-unicorn": "62.0.0", "eslint-plugin-vue": "10.5.1", "eslint-plugin-vue-scoped-css": "2.12.0", "eslint-plugin-wc": "3.0.2", - "globals": "16.4.0", - "happy-dom": "20.0.8", - "markdownlint-cli": "0.45.0", + "globals": "16.5.0", + "happy-dom": "20.0.10", + "markdownlint-cli": "0.46.0", "material-icon-theme": "5.28.0", "nolyfill": "1.0.44", "postcss-html": "1.8.0", @@ -108,11 +108,11 @@ "stylelint-declaration-strict-value": "1.10.11", "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "4.0.0", - "typescript-eslint": "8.46.2", + "typescript-eslint": "8.47.0", "updates": "16.9.1", - "vite-string-plugin": "1.4.6", - "vitest": "4.0.4", - "vue-tsc": "3.1.2" + "vite-string-plugin": "1.4.9", + "vitest": "4.0.10", + "vue-tsc": "3.1.4" }, "browserslist": [ "defaults" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a41481869c..843372d094 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,20 +54,20 @@ importers: specifier: 0.1.0-alpha-3 version: 0.1.0-alpha-3 '@primer/octicons': - specifier: 19.19.0 - version: 19.19.0 + specifier: 19.21.0 + version: 19.21.0 '@resvg/resvg-wasm': specifier: 2.6.2 version: 2.6.2 '@silverwind/vue3-calendar-heatmap': specifier: 2.0.6 - version: 2.0.6(tippy.js@6.3.7)(vue@3.5.22(typescript@5.9.3)) + version: 2.0.6(tippy.js@6.3.7)(vue@3.5.24(typescript@5.9.3)) '@techknowlogick/license-checker-webpack-plugin': specifier: 0.3.0 - version: 0.3.0(webpack@5.102.1) + version: 0.3.0(webpack@5.103.0) add-asset-webpack-plugin: specifier: 3.1.1 - version: 3.1.1(webpack@5.102.1) + version: 3.1.1(webpack@5.103.0) ansi_up: specifier: 6.0.6 version: 6.0.6 @@ -79,22 +79,22 @@ 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.18) + version: 1.0.4(chart.js@4.5.1)(dayjs@1.11.19) chartjs-plugin-zoom: specifier: 2.2.0 version: 2.2.0(chart.js@4.5.1) clippie: - specifier: 4.1.8 - version: 4.1.8 + specifier: 4.1.9 + version: 4.1.9 cropperjs: specifier: 1.6.2 version: 1.6.2 css-loader: specifier: 7.1.2 - version: 7.1.2(webpack@5.102.1) + version: 7.1.2(webpack@5.103.0) dayjs: - specifier: 1.11.18 - version: 1.11.18 + specifier: 1.11.19 + version: 1.11.19 dropzone: specifier: 6.0.0-beta.2 version: 6.0.0-beta.2 @@ -103,7 +103,7 @@ importers: version: 2.20.0 esbuild-loader: specifier: 4.4.0 - version: 4.4.0(webpack@5.102.1) + version: 4.4.0(webpack@5.103.0) htmx.org: specifier: 2.0.8 version: 2.0.8 @@ -121,13 +121,13 @@ importers: version: 11.12.1 mini-css-extract-plugin: specifier: 2.9.4 - version: 2.9.4(webpack@5.102.1) + version: 2.9.4(webpack@5.103.0) monaco-editor: specifier: 0.54.0 version: 0.54.0 monaco-editor-webpack-plugin: specifier: 7.1.1 - version: 7.1.1(monaco-editor@0.54.0)(webpack@5.102.1) + version: 7.1.1(monaco-editor@0.54.0)(webpack@5.103.0) online-3d-viewer: specifier: 0.16.0 version: 0.16.0 @@ -142,13 +142,13 @@ importers: version: 8.5.6 postcss-loader: specifier: 8.2.0 - version: 8.2.0(postcss@8.5.6)(typescript@5.9.3)(webpack@5.102.1) + version: 8.2.0(postcss@8.5.6)(typescript@5.9.3)(webpack@5.103.0) sortablejs: specifier: 1.15.6 version: 1.15.6 swagger-ui-dist: - specifier: 5.30.0 - version: 5.30.0 + specifier: 5.30.2 + version: 5.30.2 tailwindcss: specifier: 3.4.17 version: 3.4.17 @@ -177,36 +177,36 @@ importers: specifier: 0.7.2 version: 0.7.2 vue: - specifier: 3.5.22 - version: 3.5.22(typescript@5.9.3) + specifier: 3.5.24 + version: 3.5.24(typescript@5.9.3) vue-bar-graph: specifier: 2.2.0 version: 2.2.0(typescript@5.9.3) vue-chartjs: - specifier: 5.3.2 - version: 5.3.2(chart.js@4.5.1)(vue@3.5.22(typescript@5.9.3)) + specifier: 5.3.3 + version: 5.3.3(chart.js@4.5.1)(vue@3.5.24(typescript@5.9.3)) vue-loader: specifier: 17.4.2 - version: 17.4.2(vue@3.5.22(typescript@5.9.3))(webpack@5.102.1) + version: 17.4.2(vue@3.5.24(typescript@5.9.3))(webpack@5.103.0) webpack: - specifier: 5.102.1 - version: 5.102.1(webpack-cli@6.0.1) + specifier: 5.103.0 + version: 5.103.0(webpack-cli@6.0.1) webpack-cli: specifier: 6.0.1 - version: 6.0.1(webpack@5.102.1) + version: 6.0.1(webpack@5.103.0) wrap-ansi: specifier: 9.0.2 version: 9.0.2 devDependencies: '@eslint-community/eslint-plugin-eslint-comments': specifier: 4.5.0 - version: 4.5.0(eslint@9.38.0(jiti@2.6.1)) + version: 4.5.0(eslint@9.39.1(jiti@2.6.1)) '@playwright/test': specifier: 1.56.1 version: 1.56.1 '@stylistic/eslint-plugin': - specifier: 5.5.0 - version: 5.5.0(eslint@9.38.0(jiti@2.6.1)) + specifier: 5.6.1 + version: 5.6.1(eslint@9.39.1(jiti@2.6.1)) '@stylistic/stylelint-plugin': specifier: 4.0.0 version: 4.0.0(stylelint@16.25.0(typescript@5.9.3)) @@ -241,62 +241,62 @@ importers: specifier: 1.12.4 version: 1.12.4 '@typescript-eslint/parser': - specifier: 8.46.2 - version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.47.0 + version: 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) '@vitejs/plugin-vue': - specifier: 6.0.1 - version: 6.0.1(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.22(typescript@5.9.3)) + specifier: 6.0.2 + version: 6.0.2(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1))(vue@3.5.24(typescript@5.9.3)) '@vitest/eslint-plugin': - specifier: 1.3.26 - version: 1.3.26(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.8)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.0)(yaml@2.8.1)) + specifier: 1.4.3 + version: 1.4.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.10(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1)) eslint: - specifier: 9.38.0 - version: 9.38.0(jiti@2.6.1) + specifier: 9.39.1 + version: 9.39.1(jiti@2.6.1) eslint-import-resolver-typescript: specifier: 4.4.4 - version: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.38.0(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.38.0(jiti@2.6.1)) + version: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-array-func: specifier: 5.1.0 - version: 5.1.0(eslint@9.38.0(jiti@2.6.1)) + version: 5.1.0(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-github: specifier: 6.0.0 - version: 6.0.0(@types/eslint@9.6.1)(eslint-import-resolver-typescript@4.4.4)(eslint@9.38.0(jiti@2.6.1)) + version: 6.0.0(@types/eslint@9.6.1)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-import-x: specifier: 4.16.1 - version: 4.16.1(@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.38.0(jiti@2.6.1)) + version: 4.16.1(@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-no-use-extend-native: specifier: 0.7.2 - version: 0.7.2(eslint@9.38.0(jiti@2.6.1)) + version: 0.7.2(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-playwright: - specifier: 2.2.2 - version: 2.2.2(eslint@9.38.0(jiti@2.6.1)) + specifier: 2.3.0 + version: 2.3.0(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-regexp: specifier: 2.10.0 - version: 2.10.0(eslint@9.38.0(jiti@2.6.1)) + version: 2.10.0(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-sonarjs: specifier: 3.0.5 - version: 3.0.5(eslint@9.38.0(jiti@2.6.1)) + version: 3.0.5(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-unicorn: specifier: 62.0.0 - version: 62.0.0(eslint@9.38.0(jiti@2.6.1)) + version: 62.0.0(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-vue: specifier: 10.5.1 - version: 10.5.1(@stylistic/eslint-plugin@5.5.0(eslint@9.38.0(jiti@2.6.1)))(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.38.0(jiti@2.6.1))) + version: 10.5.1(@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@2.6.1)))(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@2.6.1))) eslint-plugin-vue-scoped-css: specifier: 2.12.0 - version: 2.12.0(eslint@9.38.0(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.38.0(jiti@2.6.1))) + version: 2.12.0(eslint@9.39.1(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@2.6.1))) eslint-plugin-wc: specifier: 3.0.2 - version: 3.0.2(eslint@9.38.0(jiti@2.6.1)) + version: 3.0.2(eslint@9.39.1(jiti@2.6.1)) globals: - specifier: 16.4.0 - version: 16.4.0 + specifier: 16.5.0 + version: 16.5.0 happy-dom: - specifier: 20.0.8 - version: 20.0.8 + specifier: 20.0.10 + version: 20.0.10 markdownlint-cli: - specifier: 0.45.0 - version: 0.45.0 + specifier: 0.46.0 + version: 0.46.0 material-icon-theme: specifier: 5.28.0 version: 5.28.0 @@ -328,20 +328,20 @@ importers: specifier: 4.0.0 version: 4.0.0 typescript-eslint: - specifier: 8.46.2 - version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.47.0 + version: 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) updates: specifier: 16.9.1 version: 16.9.1 vite-string-plugin: - specifier: 1.4.6 - version: 1.4.6 + specifier: 1.4.9 + version: 1.4.9 vitest: - specifier: 4.0.4 - version: 4.0.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.8)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.0)(yaml@2.8.1) + specifier: 4.0.10 + version: 4.0.10(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1) vue-tsc: - specifier: 3.1.2 - version: 3.1.2(typescript@5.9.3) + specifier: 3.1.4 + version: 3.1.4(typescript@5.9.3) packages: @@ -383,14 +383,11 @@ packages: '@braintree/sanitize-url@7.1.1': resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} - '@cacheable/memoize@2.0.3': - resolution: {integrity: sha512-hl9wfQgpiydhQEIv7fkjEzTGE+tcosCXLKFDO707wYJ/78FVOlowb36djex5GdbSyeHnG62pomYLMuV/OT8Pbw==} + '@cacheable/memory@2.0.5': + resolution: {integrity: sha512-fkiAxCvssEyJZ5fxX4tcdZFRmW9JehSTGvvqmXn6rTzG5cH6V/3C4ad8yb01vOjp2xBydHkHrgpW0qeGtzt6VQ==} - '@cacheable/memory@2.0.3': - resolution: {integrity: sha512-R3UKy/CKOyb1LZG/VRCTMcpiMDyLH7SH3JrraRdK6kf3GweWCOU3sgvE13W3TiDRbxnDKylzKJvhUAvWl9LQOA==} - - '@cacheable/utils@2.1.0': - resolution: {integrity: sha512-ZdxfOiaarMqMj+H7qwlt5EBKWaeGihSYVHdQv5lUsbn8MJJOTW82OIwirQ39U5tMZkNvy3bQE+ryzC+xTAb9/g==} + '@cacheable/utils@2.3.1': + resolution: {integrity: sha512-38NJXjIr4W1Sghun8ju+uYWD8h2c61B4dKwfnQHVDFpAJ9oS28RpfqZQJ6Dgd3RceGkILDY9YT+72HJR3LoeSQ==} '@chevrotain/cst-dts-gen@11.0.3': resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} @@ -485,167 +482,167 @@ packages: '@dual-bundle/import-meta-resolve@4.2.1': resolution: {integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==} - '@emnapi/core@1.6.0': - resolution: {integrity: sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg==} + '@emnapi/core@1.7.1': + resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} - '@emnapi/runtime@1.6.0': - resolution: {integrity: sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==} + '@emnapi/runtime@1.7.1': + resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} - '@esbuild/aix-ppc64@0.25.11': - resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==} + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.11': - resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==} + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.11': - resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==} + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.11': - resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==} + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.11': - resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==} + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.11': - resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==} + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.11': - resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==} + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.11': - resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==} + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.11': - resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==} + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.11': - resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==} + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.11': - resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==} + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.11': - resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==} + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.11': - resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==} + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.11': - resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==} + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.11': - resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==} + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.11': - resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==} + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.11': - resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==} + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.11': - resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==} + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.11': - resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==} + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.11': - resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==} + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.11': - resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==} + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.11': - resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==} + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.11': - resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==} + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.11': - resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==} + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.11': - resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==} + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.11': - resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==} + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -670,8 +667,8 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/compat@1.4.0': - resolution: {integrity: sha512-DEzm5dKeDBPm3r08Ixli/0cmxr8LkRdwxMRUIJBlSCpAwSrvFEJpVBzV+66JhDxiaqKxnRzCXhtiMiczF7Hglg==} + '@eslint/compat@1.4.1': + resolution: {integrity: sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.40 || 9 @@ -683,28 +680,28 @@ packages: resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-helpers@0.4.1': - resolution: {integrity: sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==} + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.16.0': - resolution: {integrity: sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==} + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/eslintrc@3.3.1': resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.38.0': - resolution: {integrity: sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==} + '@eslint/js@9.39.1': + resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.7': resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.4.0': - resolution: {integrity: sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==} + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@github/browserslist-config@1.0.0': @@ -755,10 +752,6 @@ packages: resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} engines: {node: 20 || >=22} - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -775,11 +768,11 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@keyv/bigmap@1.1.0': - resolution: {integrity: sha512-MX7XIUNwVRK+hjZcAbNJ0Z8DREo+Weu9vinBOjGU1thEi9F6vPhICzBbk4CCf3eEefKRz7n6TfZXwUFZTSgj8Q==} + '@keyv/bigmap@1.3.0': + resolution: {integrity: sha512-KT01GjzV6AQD5+IYrcpoYLkCu1Jod3nau1Z7EsEuViO3TZGRacSbO9MfHmbJ1WaOXFtWLxPVj169cn2WNKPkIg==} engines: {node: '>= 18'} peerDependencies: - keyv: ^5.5.3 + keyv: ^5.5.4 '@keyv/serialize@1.1.1': resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} @@ -874,10 +867,6 @@ packages: resolution: {integrity: sha512-3dsKlf4Ma7o+uxLIg5OI1Tgwfet2pE8WTbPjEGWvOe6CSjMtK0skJnnSVHaEVX4N4mYU81To0qDeZOPqjaUotg==} engines: {node: '>=12.4.0'} - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - '@pkgr/core@0.2.9': resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -890,123 +879,123 @@ packages: '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - '@primer/octicons@19.19.0': - resolution: {integrity: sha512-LBbL8nOl6FWMDy7riKB5ppHLtffY7loRq+CDGj0D5G1Xdo2mKlSOQy3rWy2RVE8SxxPFL+mj46C1nG+smKBEZA==} + '@primer/octicons@19.21.0': + resolution: {integrity: sha512-87buZ9aPlWbbHvTTzPAy9zqqGZpCc/VH+Q6q9OsZou6zCaExjmsINj6rWjP6FxNK5ZWHfF0UFNKQCai72lhaLA==} '@resvg/resvg-wasm@2.6.2': resolution: {integrity: sha512-FqALmHI8D4o6lk/LRWDnhw95z5eO+eAa6ORjVg09YRR7BkcM6oPHU9uyC0gtQG5vpFLvgpeU4+zEAz2H8APHNw==} engines: {node: '>= 10'} - '@rolldown/pluginutils@1.0.0-beta.29': - resolution: {integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==} + '@rolldown/pluginutils@1.0.0-beta.50': + resolution: {integrity: sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==} - '@rollup/rollup-android-arm-eabi@4.52.5': - resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==} + '@rollup/rollup-android-arm-eabi@4.53.3': + resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.52.5': - resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==} + '@rollup/rollup-android-arm64@4.53.3': + resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.52.5': - resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==} + '@rollup/rollup-darwin-arm64@4.53.3': + resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.52.5': - resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==} + '@rollup/rollup-darwin-x64@4.53.3': + resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.52.5': - resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==} + '@rollup/rollup-freebsd-arm64@4.53.3': + resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.52.5': - resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==} + '@rollup/rollup-freebsd-x64@4.53.3': + resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.52.5': - resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.52.5': - resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.52.5': - resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} + '@rollup/rollup-linux-arm64-gnu@4.53.3': + resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.52.5': - resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} + '@rollup/rollup-linux-arm64-musl@4.53.3': + resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.52.5': - resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} + '@rollup/rollup-linux-loong64-gnu@4.53.3': + resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.52.5': - resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.52.5': - resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.52.5': - resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} + '@rollup/rollup-linux-riscv64-musl@4.53.3': + resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.52.5': - resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} + '@rollup/rollup-linux-s390x-gnu@4.53.3': + resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.52.5': - resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} + '@rollup/rollup-linux-x64-gnu@4.53.3': + resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.52.5': - resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} + '@rollup/rollup-linux-x64-musl@4.53.3': + resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} cpu: [x64] os: [linux] - '@rollup/rollup-openharmony-arm64@4.52.5': - resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} + '@rollup/rollup-openharmony-arm64@4.53.3': + resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.52.5': - resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==} + '@rollup/rollup-win32-arm64-msvc@4.53.3': + resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.52.5': - resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==} + '@rollup/rollup-win32-ia32-msvc@4.53.3': + resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.52.5': - resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==} + '@rollup/rollup-win32-x64-gnu@4.53.3': + resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.52.5': - resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==} + '@rollup/rollup-win32-x64-msvc@4.53.3': + resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} cpu: [x64] os: [win32] @@ -1044,8 +1033,8 @@ packages: '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} - '@stylistic/eslint-plugin@5.5.0': - resolution: {integrity: sha512-IeZF+8H0ns6prg4VrkhgL+yrvDXWDH2cKchrbh80ejG9dQgZWp10epHMbgRuQvgchLII/lfh6Xn3lu6+6L86Hw==} + '@stylistic/eslint-plugin@5.6.1': + resolution: {integrity: sha512-JCs+MqoXfXrRPGbGmho/zGS/jMcn3ieKl/A8YImqib76C8kjgZwq5uUFzc30lJkMvcchuRn6/v8IApLxli3Jyw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: '>=9.0.0' @@ -1208,11 +1197,11 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@20.19.23': - resolution: {integrity: sha512-yIdlVVVHXpmqRhtyovZAcSy0MiPcYWGkoO4CGe/+jpP0hmNuihm4XhHbADpK++MsiLHP5MVlv+bcgdF99kSiFQ==} + '@types/node@20.19.25': + resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==} - '@types/node@24.9.1': - resolution: {integrity: sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==} + '@types/node@24.10.1': + resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} '@types/pdfobject@2.2.5': resolution: {integrity: sha512-7gD5tqc/RUDq0PyoLemL0vEHxBYi+zY0WVaFAx/Y0jBsXFgot1vB9No1GhDZGwRGJMCIZbgAb74QG9MTyTNU/g==} @@ -1247,63 +1236,63 @@ packages: '@types/whatwg-mimetype@3.0.2': resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} - '@typescript-eslint/eslint-plugin@8.46.2': - resolution: {integrity: sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==} + '@typescript-eslint/eslint-plugin@8.47.0': + resolution: {integrity: sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.46.2 + '@typescript-eslint/parser': ^8.47.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.46.2': - resolution: {integrity: sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==} + '@typescript-eslint/parser@8.47.0': + resolution: {integrity: sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.46.2': - resolution: {integrity: sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==} + '@typescript-eslint/project-service@8.47.0': + resolution: {integrity: sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.46.2': - resolution: {integrity: sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==} + '@typescript-eslint/scope-manager@8.47.0': + resolution: {integrity: sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.46.2': - resolution: {integrity: sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==} + '@typescript-eslint/tsconfig-utils@8.47.0': + resolution: {integrity: sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.46.2': - resolution: {integrity: sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==} + '@typescript-eslint/type-utils@8.47.0': + resolution: {integrity: sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.46.2': - resolution: {integrity: sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==} + '@typescript-eslint/types@8.47.0': + resolution: {integrity: sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.46.2': - resolution: {integrity: sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==} + '@typescript-eslint/typescript-estree@8.47.0': + resolution: {integrity: sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.46.2': - resolution: {integrity: sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==} + '@typescript-eslint/utils@8.47.0': + resolution: {integrity: sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.46.2': - resolution: {integrity: sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==} + '@typescript-eslint/visitor-keys@8.47.0': + resolution: {integrity: sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -1401,15 +1390,15 @@ packages: cpu: [x64] os: [win32] - '@vitejs/plugin-vue@6.0.1': - resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==} + '@vitejs/plugin-vue@6.0.2': + resolution: {integrity: sha512-iHmwV3QcVGGvSC1BG5bZ4z6iwa1SOpAPWmnjOErd4Ske+lZua5K9TtAVdx0gMBClJ28DViCbSmZitjWZsWO3LA==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 vue: ^3.2.25 - '@vitest/eslint-plugin@1.3.26': - resolution: {integrity: sha512-oP+Vyqgp+kLuMagG0tRkcT7e2tUoE+XWgti1OFxqdTpmMlSZJ6BWSC3rv8vzhtDXReXNyAJI1eojuc7N0QqbNQ==} + '@vitest/eslint-plugin@1.4.3': + resolution: {integrity: sha512-ba+YDKyZdViNAOg8P86a9tIEawPA/O+nLbwhg8g7nkqsLOAVum6GoZhkNxgwX621oqWAbB8N2xb+G5kkpXehcA==} engines: {node: '>=18'} peerDependencies: eslint: '>=8.57.0' @@ -1421,11 +1410,11 @@ packages: vitest: optional: true - '@vitest/expect@4.0.4': - resolution: {integrity: sha512-0ioMscWJtfpyH7+P82sGpAi3Si30OVV73jD+tEqXm5+rIx9LgnfdaOn45uaFkKOncABi/PHL00Yn0oW/wK4cXw==} + '@vitest/expect@4.0.10': + resolution: {integrity: sha512-3QkTX/lK39FBNwARCQRSQr0TP9+ywSdxSX+LgbJ2M1WmveXP72anTbnp2yl5fH+dU6SUmBzNMrDHs80G8G2DZg==} - '@vitest/mocker@4.0.4': - resolution: {integrity: sha512-UTtKgpjWj+pvn3lUM55nSg34098obGhSHH+KlJcXesky8b5wCUgg7s60epxrS6yAG8slZ9W8T9jGWg4PisMf5Q==} + '@vitest/mocker@4.0.10': + resolution: {integrity: sha512-e2OfdexYkjkg8Hh3L9NVEfbwGXq5IZbDovkf30qW2tOh7Rh9sVtmSr2ztEXOFbymNxS4qjzLXUQIvATvN4B+lg==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0-0 @@ -1435,20 +1424,20 @@ packages: vite: optional: true - '@vitest/pretty-format@4.0.4': - resolution: {integrity: sha512-lHI2rbyrLVSd1TiHGJYyEtbOBo2SDndIsN3qY4o4xe2pBxoJLD6IICghNCvD7P+BFin6jeyHXiUICXqgl6vEaQ==} + '@vitest/pretty-format@4.0.10': + resolution: {integrity: sha512-99EQbpa/zuDnvVjthwz5bH9o8iPefoQZ63WV8+bsRJZNw3qQSvSltfut8yu1Jc9mqOYi7pEbsKxYTi/rjaq6PA==} - '@vitest/runner@4.0.4': - resolution: {integrity: sha512-99EDqiCkncCmvIZj3qJXBZbyoQ35ghOwVWNnQ5nj0Hnsv4Qm40HmrMJrceewjLVvsxV/JSU4qyx2CGcfMBmXJw==} + '@vitest/runner@4.0.10': + resolution: {integrity: sha512-EXU2iSkKvNwtlL8L8doCpkyclw0mc/t4t9SeOnfOFPyqLmQwuceMPA4zJBa6jw0MKsZYbw7kAn+gl7HxrlB8UQ==} - '@vitest/snapshot@4.0.4': - resolution: {integrity: sha512-XICqf5Gi4648FGoBIeRgnHWSNDp+7R5tpclGosFaUUFzY6SfcpsfHNMnC7oDu/iOLBxYfxVzaQpylEvpgii3zw==} + '@vitest/snapshot@4.0.10': + resolution: {integrity: sha512-2N4X2ZZl7kZw0qeGdQ41H0KND96L3qX1RgwuCfy6oUsF2ISGD/HpSbmms+CkIOsQmg2kulwfhJ4CI0asnZlvkg==} - '@vitest/spy@4.0.4': - resolution: {integrity: sha512-G9L13AFyYECo40QG7E07EdYnZZYCKMTSp83p9W8Vwed0IyCG1GnpDLxObkx8uOGPXfDpdeVf24P1Yka8/q1s9g==} + '@vitest/spy@4.0.10': + resolution: {integrity: sha512-AsY6sVS8OLb96GV5RoG8B6I35GAbNrC49AO+jNRF9YVGb/g9t+hzNm1H6kD0NDp8tt7VJLs6hb7YMkDXqu03iw==} - '@vitest/utils@4.0.4': - resolution: {integrity: sha512-4bJLmSvZLyVbNsYFRpPYdJViG9jZyRvMZ35IF4ymXbRZoS+ycYghmwTGiscTXduUg2lgKK7POWIyXJNute1hjw==} + '@vitest/utils@4.0.10': + resolution: {integrity: sha512-kOuqWnEwZNtQxMKg3WmPK1vmhZu9WcoX69iwWjVz+jvKTsF1emzsv3eoPcDr6ykA3qP2bsCQE7CwqfNtAVzsmg==} '@volar/language-core@2.4.23': resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==} @@ -1459,42 +1448,42 @@ packages: '@volar/typescript@2.4.23': resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==} - '@vue/compiler-core@3.5.22': - resolution: {integrity: sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==} + '@vue/compiler-core@3.5.24': + resolution: {integrity: sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==} - '@vue/compiler-dom@3.5.22': - resolution: {integrity: sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==} + '@vue/compiler-dom@3.5.24': + resolution: {integrity: sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==} - '@vue/compiler-sfc@3.5.22': - resolution: {integrity: sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==} + '@vue/compiler-sfc@3.5.24': + resolution: {integrity: sha512-8EG5YPRgmTB+YxYBM3VXy8zHD9SWHUJLIGPhDovo3Z8VOgvP+O7UP5vl0J4BBPWYD9vxtBabzW1EuEZ+Cqs14g==} - '@vue/compiler-ssr@3.5.22': - resolution: {integrity: sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==} + '@vue/compiler-ssr@3.5.24': + resolution: {integrity: sha512-trOvMWNBMQ/odMRHW7Ae1CdfYx+7MuiQu62Jtu36gMLXcaoqKvAyh+P73sYG9ll+6jLB6QPovqoKGGZROzkFFg==} - '@vue/language-core@3.1.2': - resolution: {integrity: sha512-PyFDCqpdfYUT+oMLqcc61oHfJlC6yjhybaefwQjRdkchIihToOEpJ2Wu/Ebq2yrnJdd1EsaAvZaXVAqcxtnDxQ==} + '@vue/language-core@3.1.4': + resolution: {integrity: sha512-n/58wm8SkmoxMWkUNUH/PwoovWe4hmdyPJU2ouldr3EPi1MLoS7iDN46je8CsP95SnVBs2axInzRglPNKvqMcg==} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true - '@vue/reactivity@3.5.22': - resolution: {integrity: sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==} + '@vue/reactivity@3.5.24': + resolution: {integrity: sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==} - '@vue/runtime-core@3.5.22': - resolution: {integrity: sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==} + '@vue/runtime-core@3.5.24': + resolution: {integrity: sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==} - '@vue/runtime-dom@3.5.22': - resolution: {integrity: sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==} + '@vue/runtime-dom@3.5.24': + resolution: {integrity: sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==} - '@vue/server-renderer@3.5.22': - resolution: {integrity: sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==} + '@vue/server-renderer@3.5.24': + resolution: {integrity: sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==} peerDependencies: - vue: 3.5.22 + vue: 3.5.24 - '@vue/shared@3.5.22': - resolution: {integrity: sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==} + '@vue/shared@3.5.24': + resolution: {integrity: sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==} '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -1616,8 +1605,8 @@ packages: ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - alien-signals@3.0.3: - resolution: {integrity: sha512-2JXjom6R7ZwrISpUphLhf4htUq1aKRCennTJ6u9kFfr3sLmC9+I4CxxVi+McoFnIg+p1HnVrfLT/iCt4Dlz//Q==} + alien-signals@3.1.0: + resolution: {integrity: sha512-yufC6VpSy8tK3I0lO67pjumo5JvDQVQyr38+3OHqe6CHl1t2VZekKZ7EKKZSqk0cRmE7U7tfZbpXiKNzuc+ckg==} ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} @@ -1699,8 +1688,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.8.20: - resolution: {integrity: sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==} + baseline-browser-mapping@2.8.29: + resolution: {integrity: sha512-sXdt2elaVnhpDNRDz+1BDx1JQoJRuNk7oVlAlbGiFkLikHCAQiccexF/9e91zVi6RCgqspl04aP+6Cnl9zRLrA==} hasBin: true big.js@5.2.2: @@ -1723,8 +1712,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.27.0: - resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==} + browserslist@4.28.0: + resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1746,8 +1735,8 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - cacheable@2.1.1: - resolution: {integrity: sha512-LmF4AXiSNdiRbI2UjH8pAp9NIXxeQsTotpEaegPiDcnN0YPygDJDV3l/Urc0mL72JWdATEorKqIHEx55nDlONg==} + cacheable@2.2.0: + resolution: {integrity: sha512-LEJxRqfeomiiRd2t0uON6hxAtgOoWDfY3fugebbz+J3vDLO+SkdfFChQcOHTZhj9SYa9iwE9MGYNX72dKiOE4w==} callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} @@ -1757,11 +1746,11 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001751: - resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==} + caniuse-lite@1.0.30001756: + resolution: {integrity: sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==} - chai@6.2.0: - resolution: {integrity: sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==} + chai@6.2.1: + resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} engines: {node: '>=18'} chalk@4.1.2: @@ -1826,8 +1815,8 @@ packages: resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} engines: {node: '>=4'} - clippie@4.1.8: - resolution: {integrity: sha512-GCDd4xnYPqohYPgPN/vbljc3QHD3P2Wsos6RO68ab3Ja6a7IDocXzbsIKXqwrNnK3cR31nog3A5Cyf+8GYc/Dg==} + clippie@4.1.9: + resolution: {integrity: sha512-YaNJI8f2bPRVVfdKDUeqSPuQEztyOowee7DIc/DJ48qNJGq/SziipiWN6oWT6q9FR4QN0JzFDpP+fDtkSZyFHw==} clone-deep@4.0.1: resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} @@ -1860,9 +1849,9 @@ packages: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} - commander@13.1.0: - resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} - engines: {node: '>=18'} + commander@14.0.2: + resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} + engines: {node: '>=20'} commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -1892,8 +1881,8 @@ packages: confbox@0.2.2: resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} - core-js-compat@3.46.0: - resolution: {integrity: sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==} + core-js-compat@3.47.0: + resolution: {integrity: sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==} core-js@3.32.2: resolution: {integrity: sha512-pxXSw1mYZPDGvTQqEc5vgIb83jGQKFGYWY76z4a7weZXUolw3G+OvpZqSRcfYOoOVUQJYEPsWeQK8pKEnUtWxQ==} @@ -1963,8 +1952,8 @@ packages: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} cytoscape-cose-bilkent@4.1.0: resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} @@ -2125,8 +2114,8 @@ packages: damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} - dayjs@1.11.18: - resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==} + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} @@ -2212,14 +2201,11 @@ packages: dropzone@6.0.0-beta.2: resolution: {integrity: sha512-k44yLuFFhRk53M8zP71FaaNzJYIzr99SKmpbO/oZKNslDjNXQsBTdfLs+iONd0U0L94zzlFzRnFdqbLcs7h9fQ==} - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - easymde@2.20.0: resolution: {integrity: sha512-V1Z5f92TfR42Na852OWnIZMbM7zotWQYTddNaLYZFVKj7APBbyZ3FYJ27gBw2grMW3R6Qdv9J8n5Ij7XRSIgXQ==} - electron-to-chromium@1.5.240: - resolution: {integrity: sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ==} + electron-to-chromium@1.5.256: + resolution: {integrity: sha512-uqYq1IQhpXXLX+HgiXdyOZml7spy4xfy42yPxcCCRjswp0fYM2X+JwCON07lqnpLEGVCj739B7Yr+FngmHBMEQ==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -2246,8 +2232,8 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} - envinfo@7.19.0: - resolution: {integrity: sha512-DoSM9VyG6O3vqBf+p3Gjgr/Q52HYBBtO3v+4koAxt1MnWr+zEnxE+nke/yXS4lt2P4SYCHQ4V3f1i88LQVOpAw==} + envinfo@7.20.0: + resolution: {integrity: sha512-+zUomDcLXsVkQ37vUqWBvQwLaLlj8eZPSi61llaEFAVBY5mhcXdaSw1pSJVl4yTYD5g/gEfpNl28YYk4IPvrrg==} engines: {node: '>=4'} hasBin: true @@ -2262,8 +2248,8 @@ packages: peerDependencies: webpack: ^4.40.0 || ^5.0.0 - esbuild@0.25.11: - resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==} + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} hasBin: true @@ -2409,9 +2395,9 @@ packages: peerDependencies: eslint: ^9.3.0 - eslint-plugin-playwright@2.2.2: - resolution: {integrity: sha512-j0jKpndIPOXRRP9uMkwb9l/nSmModOU3452nrFdgFJoEv/435J1onk8+aITzjDW8DfypxgmVaDMdmVIa6F7I0w==} - engines: {node: '>=16.6.0'} + eslint-plugin-playwright@2.3.0: + resolution: {integrity: sha512-7UeUuIb5SZrNkrUGb2F+iwHM97kn33/huajcVtAaQFCSMUYGNFvjzRPil5C0OIppslPfuOV68M/zsisXx+/ZvQ==} + engines: {node: '>=16.9.0'} peerDependencies: eslint: '>=8.40.0' @@ -2492,8 +2478,8 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.38.0: - resolution: {integrity: sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==} + eslint@9.39.1: + resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -2543,8 +2529,8 @@ packages: resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} - exsolve@1.0.7: - resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2614,8 +2600,8 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flat-cache@6.1.18: - resolution: {integrity: sha512-JUPnFgHMuAVmLmoH9/zoZ6RHOt5n9NlUw/sDXsTbROJ2SFoS2DS4s+swAV6UTeTbGH/CAsZIE6M8TaG/3jVxgQ==} + flat-cache@6.1.19: + resolution: {integrity: sha512-l/K33newPTZMTGAnnzaiqSl6NnH7Namh8jBNjrgjprWxGmZUuxx/sJNIRaijOh3n7q7ESbhNZC+pvVZMFdeU4A==} flat@5.0.2: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} @@ -2624,10 +2610,6 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -2666,15 +2648,6 @@ packages: glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} - hasBin: true - - glob@11.0.3: - resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} - engines: {node: 20 || >=22} - hasBin: true - glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -2687,10 +2660,6 @@ packages: resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} engines: {node: '>=6'} - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -2699,8 +2668,8 @@ packages: resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} engines: {node: '>=18'} - globals@16.4.0: - resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==} + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} globby@11.1.0: @@ -2723,8 +2692,8 @@ packages: resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==} engines: {node: '>=0.8.0'} - happy-dom@20.0.8: - resolution: {integrity: sha512-TlYaNQNtzsZ97rNMBAm8U+e2cUQXNithgfCizkDgc11lgmN4j9CKMhO3FPGKWQYPwwkFcPpoXYF/CqEPLgzfOg==} + happy-dom@20.0.10: + resolution: {integrity: sha512-6umCCHcjQrhP5oXhrHQQvLB0bwb1UzHAHdsXy+FjtKoYjUhmNZsQL8NivwM1vDvNEChJabVrUYxUnp/ZdYmy2g==} engines: {node: '>=20.0.0'} has-flag@4.0.0: @@ -2734,8 +2703,12 @@ packages: hash-sum@2.0.0: resolution: {integrity: sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==} - hookified@1.12.2: - resolution: {integrity: sha512-aokUX1VdTpI0DUsndvW+OiwmBpKCu/NgRsSSkuSY0zq8PY6Q6a+lmOfAFDXAAOtBqJELvcWY9L1EVtzjbQcMdg==} + hashery@1.2.0: + resolution: {integrity: sha512-43XJKpwle72Ik5Zpam7MuzRWyNdwwdf6XHlh8wCj2PggvWf+v/Dm5B0dxGZOmddidgeO6Ofu9As/o231Ti/9PA==} + engines: {node: '>=20'} + + hookified@1.13.0: + resolution: {integrity: sha512-6sPYUY8olshgM/1LDNW4QZQN0IqgKhtl/1C8koNZBJrKLBk3AZl6chQtNwpNztvfiApHMEwMHek5rv993PRbWw==} html-tags@3.3.1: resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} @@ -2895,13 +2868,6 @@ packages: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - - jackspeak@4.1.1: - resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} - engines: {node: 20 || >=22} - jest-worker@27.5.1: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} @@ -2930,8 +2896,8 @@ packages: resolution: {integrity: sha512-/c+n06zvqFQGxdz1BbElF7S3nEghjNchLN1TjQnk2j10HYDaUc57rcvl6BbnziTx8NQmrg0JOs/iwRpvcYaxjQ==} engines: {node: '>=18.20'} - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true jsdoc-type-pratt-parser@4.8.0: @@ -2992,8 +2958,8 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - keyv@5.5.3: - resolution: {integrity: sha512-h0Un1ieD+HUrzBH6dJXhod3ifSghk5Hw/2Y4/KHBziPlZecrFyE9YOTPU6eOs0V9pYl8gOs86fkr/KN8lUX39A==} + keyv@5.5.4: + resolution: {integrity: sha512-eohl3hKTiVyD1ilYdw9T0OiB4hnjef89e3dMYKz+mVKDzj+5IteTseASUsOB+EU9Tf6VNTCjDePcP6wkDGmLKQ==} khroma@2.1.0: resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} @@ -3094,13 +3060,6 @@ packages: resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - - lru-cache@11.2.2: - resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} - engines: {node: 20 || >=22} - magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -3108,13 +3067,13 @@ packages: resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} hasBin: true - markdownlint-cli@0.45.0: - resolution: {integrity: sha512-GiWr7GfJLVfcopL3t3pLumXCYs8sgWppjIA1F/Cc3zIMgD3tmkpyZ1xkm1Tej8mw53B93JsDjgA3KOftuYcfOw==} + markdownlint-cli@0.46.0: + resolution: {integrity: sha512-4gxTNzPjpLnY7ftrEZD4flPY0QBkQLiqezb6KURFSkV+vPHFOsYw8OMtY6fu82Yt8ghtSrWegpYdq1ix25VFLQ==} engines: {node: '>=20'} hasBin: true - markdownlint@0.38.0: - resolution: {integrity: sha512-xaSxkaU7wY/0852zGApM8LdlIfGCW8ETZ0Rr62IQtAnUMlMuifsg09vWJcNYeL4f0anvr8Vo4ZQar8jGpV0btQ==} + markdownlint@0.39.0: + resolution: {integrity: sha512-Xt/oY7bAiHwukL1iru2np5LIkhwD19Y7frlsiDILK62v3jucXCD6JXlZlwMG12HZOR+roHIVuJZrfCkOhp6k3g==} engines: {node: '>=20'} marked@14.0.0: @@ -3122,8 +3081,8 @@ packages: engines: {node: '>= 18'} hasBin: true - marked@16.4.1: - resolution: {integrity: sha512-ntROs7RaN3EvWfy3EZi14H4YxmT6A5YvywfhO+0pm+cH/dnSQRmdAmoFIc3B9aiwTehyk7pESH4ofyBY+V5hZg==} + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} engines: {node: '>= 20'} hasBin: true @@ -3255,8 +3214,8 @@ packages: peerDependencies: webpack: ^5.0.0 - minimatch@10.0.3: - resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} + minimatch@10.1.1: + resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} minimatch@3.1.2: @@ -3269,10 +3228,6 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} @@ -3334,8 +3289,8 @@ packages: encoding: optional: true - node-releases@2.0.26: - resolution: {integrity: sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==} + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} nolyfill@1.0.44: resolution: {integrity: sha512-PoggwVLiJUn0MnodpftsiC7EuknW5+6v62ntTOQ6T6l7g2r6aoaOwgk0tQW2BxGLYw9bF298LL8jDFTmEFuzlA==} @@ -3391,9 +3346,6 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - package-manager-detector@1.5.0: resolution: {integrity: sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==} @@ -3429,14 +3381,6 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - path-scurry@2.0.0: - resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} - engines: {node: 20 || >=22} - path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -3635,8 +3579,8 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qified@0.5.1: - resolution: {integrity: sha512-+BtFN3dCP+IaFA6IYNOu/f/uK1B8xD2QWyOeCse0rjtAebBmkzgd2d1OAXi3ikAzJMIBSdzZDNZ3wZKEUDQs5w==} + qified@0.5.2: + resolution: {integrity: sha512-7gJ6mxcQb9vUBOtbKm5mDevbe2uRcOEVp1g4gb/Q+oLntB3HY8eBhOYRxFI2mlDFlY1e4DOSCptzxarXRvzxCA==} engines: {node: '>=20'} quansync@0.2.11: @@ -3710,8 +3654,8 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - rollup@4.52.5: - resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} + rollup@4.53.3: + resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -3731,8 +3675,8 @@ packages: sax@1.2.4: resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} - sax@1.4.1: - resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + sax@1.4.3: + resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==} schema-utils@4.3.3: resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} @@ -3796,8 +3740,8 @@ packages: resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} engines: {node: '>=10'} - smol-toml@1.3.4: - resolution: {integrity: sha512-UOPtVuYkzYGee0Bd2Szz8d2G3RfMfJ2t3qVdZUAozZyAk+a0Sxa+QKix0YCwjL/A1RR0ar44nCxaoN9FxdJGwA==} + smol-toml@1.5.2: + resolution: {integrity: sha512-QlaZEqcAH3/RtNyet1IPIYPsEWAaYyXXv1Krsi+1L/QHppjX4Ifm8MQsBISz9vE8cHicIq3clogsheili5vhaQ==} engines: {node: '>= 18'} solid-js@1.9.10: @@ -3874,10 +3818,6 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} @@ -3941,8 +3881,8 @@ packages: resolution: {integrity: sha512-yOI6G8WYfr0q8v8rRvE91wbxFU+rJPo760Va4MF6K0I6BZjO4r+xSynkvyPBP9tV1CIEUeRsiidjIs2rzb1CnQ==} hasBin: true - sucrase@3.35.0: - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true @@ -3979,8 +3919,8 @@ packages: svgson@5.3.1: resolution: {integrity: sha512-qdPgvUNWb40gWktBJnbJRelWcPzkLed/ShhnRsjbayXz8OtdPOzbil9jtiZdrYvSDumAz/VNQr6JaNfPx/gvPA==} - swagger-ui-dist@5.30.0: - resolution: {integrity: sha512-BoiDSeT9PCtHfDYMgX5UpB/qTQy44UoSFRmzHqvhGfgxzEVPHxIW78a+HMLUIHnSFu3z63wjtbq6L6+Rto20Rw==} + swagger-ui-dist@5.30.2: + resolution: {integrity: sha512-HWCg1DTNE/Nmapt+0m2EPXFwNKNeKK4PwMjkwveN/zn1cV2Kxi9SURd+m0SpdcSgWEK/O64sf8bzXdtUhigtHA==} sync-fetch@0.4.5: resolution: {integrity: sha512-esiWJ7ixSKGpd9DJPBTC4ckChqdOjIwJfYhVHkcQ2Gnm41323p1TRmEI+esTQ9ppD+b5opps2OTEGTCGX5kF+g==} @@ -4019,8 +3959,8 @@ packages: uglify-js: optional: true - terser@5.44.0: - resolution: {integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==} + terser@5.44.1: + resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==} engines: {node: '>=10'} hasBin: true @@ -4047,8 +3987,9 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyexec@1.0.1: - resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} @@ -4097,12 +4038,8 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - - typescript-eslint@8.46.2: - resolution: {integrity: sha512-vbw8bOmiuYNdzzV3lsiWv6sRwjyuKJMQqWulBOU7M0RrxedXledX8G8kBbQeiOYDnTfiXz0Y4081E1QMNB6iQg==} + typescript-eslint@8.47.0: + resolution: {integrity: sha512-Lwe8i2XQ3WoMjua/r1PHrCTpkubPYJCAfOurtn+mtTzqB6jNd+14n9UN1bJ4s3F49x9ixAm0FLflB/JzQ57M8Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -4158,11 +4095,11 @@ packages: vanilla-colorful@0.7.2: resolution: {integrity: sha512-z2YZusTFC6KnLERx1cgoIRX2CjPRP0W75N+3CC6gbvdX5Ch47rZkEMGO2Xnf+IEmi3RiFLxS18gayMA27iU7Kg==} - vite-string-plugin@1.4.6: - resolution: {integrity: sha512-Csjtny8/uVIynzlaRRj4RpHrPAakNwlH9jw6kgQ8tQhc2f0zzA6bCbAgWD0y84EgB8aLNrz7pZFUqSt3LOtk+w==} + vite-string-plugin@1.4.9: + resolution: {integrity: sha512-mO7PVkMs8+FuTK9ZjBBCRSjabC9cobvUEbN2EjWtGJo6nu35SbW99bYesOh5Ho39ug/KSbT4VwM4GPC26Xk/mQ==} - vite@7.1.12: - resolution: {integrity: sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==} + vite@7.2.2: + resolution: {integrity: sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -4201,18 +4138,18 @@ packages: yaml: optional: true - vitest@4.0.4: - resolution: {integrity: sha512-hV31h0/bGbtmDQc0KqaxsTO1v4ZQeF8ojDFuy4sZhFadwAqqvJA0LDw68QUocctI5EDpFMql/jVWKuPYHIf2Ew==} + vitest@4.0.10: + resolution: {integrity: sha512-2Fqty3MM9CDwOVet/jaQalYlbcjATZwPYGcqpiYQqgQ/dLC7GuHdISKgTYIVF/kaishKxLzleKWWfbSDklyIKg==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/debug': ^4.1.12 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.4 - '@vitest/browser-preview': 4.0.4 - '@vitest/browser-webdriverio': 4.0.4 - '@vitest/ui': 4.0.4 + '@vitest/browser-playwright': 4.0.10 + '@vitest/browser-preview': 4.0.10 + '@vitest/browser-webdriverio': 4.0.10 + '@vitest/ui': 4.0.10 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -4261,8 +4198,8 @@ packages: vue-bar-graph@2.2.0: resolution: {integrity: sha512-1xFPho2nM6nFDziExLu48vKO+Q90gjxz1NyHfc+MhgfYDSxR9BMyhOIXUO5EmwKIVEX5dBoP2n3Ius8SjKRD4g==} - vue-chartjs@5.3.2: - resolution: {integrity: sha512-NrkbRRoYshbXbWqJkTN6InoDVwVb90C0R7eAVgMWcB9dPikbruaOoTFjFYHE/+tNPdIe6qdLCDjfjPHQ0fw4jw==} + vue-chartjs@5.3.3: + resolution: {integrity: sha512-jqxtL8KZ6YJ5NTv6XzrzLS7osyegOi28UGNZW0h9OkDL7Sh1396ht4Dorh04aKrl2LiSalQ84WtqiG0RIJb0tA==} peerDependencies: chart.js: ^4.1.1 vue: ^3.0.0-0 || ^2.7.0 @@ -4285,14 +4222,14 @@ packages: vue: optional: true - vue-tsc@3.1.2: - resolution: {integrity: sha512-3fd4DY0rFczs5f+VB3OhcLU83V6+3Puj2yLBe0Ak65k7ERk+STVNKaOAi0EBo6Lc15UiJB6LzU6Mxy4+h/pKew==} + vue-tsc@3.1.4: + resolution: {integrity: sha512-GsRJxttj4WkmXW/zDwYPGMJAN3np/4jTzoDFQTpTsI5Vg/JKMWamBwamlmLihgSVHO66y9P7GX+uoliYxeI4Hw==} hasBin: true peerDependencies: typescript: '>=5.0.0' - vue@3.5.22: - resolution: {integrity: sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==} + vue@3.5.24: + resolution: {integrity: sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==} peerDependencies: typescript: '*' peerDependenciesMeta: @@ -4331,8 +4268,8 @@ packages: resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==} engines: {node: '>=10.13.0'} - webpack@5.102.1: - resolution: {integrity: sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==} + webpack@5.103.0: + resolution: {integrity: sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -4373,14 +4310,6 @@ packages: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - wrap-ansi@9.0.2: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} @@ -4418,7 +4347,7 @@ snapshots: '@antfu/install-pkg@1.1.0': dependencies: package-manager-detector: 1.5.0 - tinyexec: 1.0.1 + tinyexec: 1.0.2 '@antfu/utils@9.3.0': {} @@ -4445,21 +4374,17 @@ snapshots: '@braintree/sanitize-url@7.1.1': {} - '@cacheable/memoize@2.0.3': - dependencies: - '@cacheable/utils': 2.1.0 - - '@cacheable/memory@2.0.3': + '@cacheable/memory@2.0.5': dependencies: - '@cacheable/memoize': 2.0.3 - '@cacheable/utils': 2.1.0 - '@keyv/bigmap': 1.1.0(keyv@5.5.3) - hookified: 1.12.2 - keyv: 5.5.3 + '@cacheable/utils': 2.3.1 + '@keyv/bigmap': 1.3.0(keyv@5.5.4) + hookified: 1.13.0 + keyv: 5.5.4 - '@cacheable/utils@2.1.0': + '@cacheable/utils@2.3.1': dependencies: - keyv: 5.5.3 + hashery: 1.2.0 + keyv: 5.5.4 '@chevrotain/cst-dts-gen@11.0.3': dependencies: @@ -4529,7 +4454,7 @@ snapshots: '@citation-js/plugin-yaml@0.6.1': dependencies: - js-yaml: 4.1.0 + js-yaml: 4.1.1 '@citation-js/plugin-zenodo@0.6.1': dependencies: @@ -4555,13 +4480,13 @@ snapshots: '@dual-bundle/import-meta-resolve@4.2.1': {} - '@emnapi/core@1.6.0': + '@emnapi/core@1.7.1': dependencies: '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.6.0': + '@emnapi/runtime@1.7.1': dependencies: tslib: 2.8.1 optional: true @@ -4571,104 +4496,104 @@ snapshots: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.25.11': + '@esbuild/aix-ppc64@0.25.12': optional: true - '@esbuild/android-arm64@0.25.11': + '@esbuild/android-arm64@0.25.12': optional: true - '@esbuild/android-arm@0.25.11': + '@esbuild/android-arm@0.25.12': optional: true - '@esbuild/android-x64@0.25.11': + '@esbuild/android-x64@0.25.12': optional: true - '@esbuild/darwin-arm64@0.25.11': + '@esbuild/darwin-arm64@0.25.12': optional: true - '@esbuild/darwin-x64@0.25.11': + '@esbuild/darwin-x64@0.25.12': optional: true - '@esbuild/freebsd-arm64@0.25.11': + '@esbuild/freebsd-arm64@0.25.12': optional: true - '@esbuild/freebsd-x64@0.25.11': + '@esbuild/freebsd-x64@0.25.12': optional: true - '@esbuild/linux-arm64@0.25.11': + '@esbuild/linux-arm64@0.25.12': optional: true - '@esbuild/linux-arm@0.25.11': + '@esbuild/linux-arm@0.25.12': optional: true - '@esbuild/linux-ia32@0.25.11': + '@esbuild/linux-ia32@0.25.12': optional: true - '@esbuild/linux-loong64@0.25.11': + '@esbuild/linux-loong64@0.25.12': optional: true - '@esbuild/linux-mips64el@0.25.11': + '@esbuild/linux-mips64el@0.25.12': optional: true - '@esbuild/linux-ppc64@0.25.11': + '@esbuild/linux-ppc64@0.25.12': optional: true - '@esbuild/linux-riscv64@0.25.11': + '@esbuild/linux-riscv64@0.25.12': optional: true - '@esbuild/linux-s390x@0.25.11': + '@esbuild/linux-s390x@0.25.12': optional: true - '@esbuild/linux-x64@0.25.11': + '@esbuild/linux-x64@0.25.12': optional: true - '@esbuild/netbsd-arm64@0.25.11': + '@esbuild/netbsd-arm64@0.25.12': optional: true - '@esbuild/netbsd-x64@0.25.11': + '@esbuild/netbsd-x64@0.25.12': optional: true - '@esbuild/openbsd-arm64@0.25.11': + '@esbuild/openbsd-arm64@0.25.12': optional: true - '@esbuild/openbsd-x64@0.25.11': + '@esbuild/openbsd-x64@0.25.12': optional: true - '@esbuild/openharmony-arm64@0.25.11': + '@esbuild/openharmony-arm64@0.25.12': optional: true - '@esbuild/sunos-x64@0.25.11': + '@esbuild/sunos-x64@0.25.12': optional: true - '@esbuild/win32-arm64@0.25.11': + '@esbuild/win32-arm64@0.25.12': optional: true - '@esbuild/win32-ia32@0.25.11': + '@esbuild/win32-ia32@0.25.12': optional: true - '@esbuild/win32-x64@0.25.11': + '@esbuild/win32-x64@0.25.12': optional: true - '@eslint-community/eslint-plugin-eslint-comments@4.5.0(eslint@9.38.0(jiti@2.6.1))': + '@eslint-community/eslint-plugin-eslint-comments@4.5.0(eslint@9.39.1(jiti@2.6.1))': dependencies: escape-string-regexp: 4.0.0 - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) ignore: 5.3.2 - '@eslint-community/eslint-utils@4.9.0(eslint@9.38.0(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@2.6.1))': dependencies: - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} '@eslint-community/regexpp@4.12.2': {} - '@eslint/compat@1.4.0(eslint@9.38.0(jiti@2.6.1))': + '@eslint/compat@1.4.1(eslint@9.39.1(jiti@2.6.1))': dependencies: - '@eslint/core': 0.16.0 + '@eslint/core': 0.17.0 optionalDependencies: - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) '@eslint/config-array@0.21.1': dependencies: @@ -4678,11 +4603,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.4.1': + '@eslint/config-helpers@0.4.2': dependencies: - '@eslint/core': 0.16.0 + '@eslint/core': 0.17.0 - '@eslint/core@0.16.0': + '@eslint/core@0.17.0': dependencies: '@types/json-schema': 7.0.15 @@ -4694,19 +4619,19 @@ snapshots: globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.0 + js-yaml: 4.1.1 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color - '@eslint/js@9.38.0': {} + '@eslint/js@9.39.1': {} '@eslint/object-schema@2.1.7': {} - '@eslint/plugin-kit@0.4.0': + '@eslint/plugin-kit@0.4.1': dependencies: - '@eslint/core': 0.16.0 + '@eslint/core': 0.17.0 levn: 0.4.1 '@github/browserslist-config@1.0.0': {} @@ -4756,15 +4681,6 @@ snapshots: dependencies: '@isaacs/balanced-match': 4.0.1 - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -4784,10 +4700,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@keyv/bigmap@1.1.0(keyv@5.5.3)': + '@keyv/bigmap@1.3.0(keyv@5.5.4)': dependencies: - hookified: 1.12.2 - keyv: 5.5.3 + hashery: 1.2.0 + hookified: 1.13.0 + keyv: 5.5.4 '@keyv/serialize@1.1.1': {} @@ -4805,8 +4722,8 @@ snapshots: '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@emnapi/core': 1.6.0 - '@emnapi/runtime': 1.6.0 + '@emnapi/core': 1.7.1 + '@emnapi/runtime': 1.7.1 '@tybys/wasm-util': 0.10.1 optional: true @@ -4874,9 +4791,6 @@ snapshots: dependencies: '@nolyfill/shared': 1.0.44 - '@pkgjs/parseargs@0.11.0': - optional: true - '@pkgr/core@0.2.9': {} '@playwright/test@1.56.1': @@ -4885,88 +4799,88 @@ snapshots: '@popperjs/core@2.11.8': {} - '@primer/octicons@19.19.0': + '@primer/octicons@19.21.0': dependencies: object-assign: 4.1.1 '@resvg/resvg-wasm@2.6.2': {} - '@rolldown/pluginutils@1.0.0-beta.29': {} + '@rolldown/pluginutils@1.0.0-beta.50': {} - '@rollup/rollup-android-arm-eabi@4.52.5': + '@rollup/rollup-android-arm-eabi@4.53.3': optional: true - '@rollup/rollup-android-arm64@4.52.5': + '@rollup/rollup-android-arm64@4.53.3': optional: true - '@rollup/rollup-darwin-arm64@4.52.5': + '@rollup/rollup-darwin-arm64@4.53.3': optional: true - '@rollup/rollup-darwin-x64@4.52.5': + '@rollup/rollup-darwin-x64@4.53.3': optional: true - '@rollup/rollup-freebsd-arm64@4.52.5': + '@rollup/rollup-freebsd-arm64@4.53.3': optional: true - '@rollup/rollup-freebsd-x64@4.52.5': + '@rollup/rollup-freebsd-x64@4.53.3': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.52.5': + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.52.5': + '@rollup/rollup-linux-arm-musleabihf@4.53.3': optional: true - '@rollup/rollup-linux-arm64-gnu@4.52.5': + '@rollup/rollup-linux-arm64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-arm64-musl@4.52.5': + '@rollup/rollup-linux-arm64-musl@4.53.3': optional: true - '@rollup/rollup-linux-loong64-gnu@4.52.5': + '@rollup/rollup-linux-loong64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.52.5': + '@rollup/rollup-linux-ppc64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.52.5': + '@rollup/rollup-linux-riscv64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-riscv64-musl@4.52.5': + '@rollup/rollup-linux-riscv64-musl@4.53.3': optional: true - '@rollup/rollup-linux-s390x-gnu@4.52.5': + '@rollup/rollup-linux-s390x-gnu@4.53.3': optional: true - '@rollup/rollup-linux-x64-gnu@4.52.5': + '@rollup/rollup-linux-x64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-x64-musl@4.52.5': + '@rollup/rollup-linux-x64-musl@4.53.3': optional: true - '@rollup/rollup-openharmony-arm64@4.52.5': + '@rollup/rollup-openharmony-arm64@4.53.3': optional: true - '@rollup/rollup-win32-arm64-msvc@4.52.5': + '@rollup/rollup-win32-arm64-msvc@4.53.3': optional: true - '@rollup/rollup-win32-ia32-msvc@4.52.5': + '@rollup/rollup-win32-ia32-msvc@4.53.3': optional: true - '@rollup/rollup-win32-x64-gnu@4.52.5': + '@rollup/rollup-win32-x64-gnu@4.53.3': optional: true - '@rollup/rollup-win32-x64-msvc@4.52.5': + '@rollup/rollup-win32-x64-msvc@4.53.3': optional: true '@rtsao/scc@1.1.0': {} '@scarf/scarf@1.4.0': {} - '@silverwind/vue3-calendar-heatmap@2.0.6(tippy.js@6.3.7)(vue@3.5.22(typescript@5.9.3))': + '@silverwind/vue3-calendar-heatmap@2.0.6(tippy.js@6.3.7)(vue@3.5.24(typescript@5.9.3))': dependencies: tippy.js: 6.3.7 - vue: 3.5.22(typescript@5.9.3) + vue: 3.5.24(typescript@5.9.3) '@simonwep/pickr@1.9.0': dependencies: @@ -4988,11 +4902,11 @@ snapshots: '@standard-schema/spec@1.0.0': {} - '@stylistic/eslint-plugin@5.5.0(eslint@9.38.0(jiti@2.6.1))': + '@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@2.6.1))': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) - '@typescript-eslint/types': 8.46.2 - eslint: 9.38.0(jiti@2.6.1) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + '@typescript-eslint/types': 8.47.0 + eslint: 9.39.1(jiti@2.6.1) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 @@ -5011,7 +4925,7 @@ snapshots: '@swc/helpers@0.2.14': {} - '@techknowlogick/license-checker-webpack-plugin@0.3.0(webpack@5.102.1)': + '@techknowlogick/license-checker-webpack-plugin@0.3.0(webpack@5.103.0)': dependencies: glob: 7.2.3 lodash: 4.17.21 @@ -5020,7 +4934,7 @@ snapshots: spdx-expression-validate: 2.0.0 spdx-satisfies: 5.0.1 superstruct: 0.10.13 - webpack: 5.102.1(webpack-cli@6.0.1) + webpack: 5.103.0(webpack-cli@6.0.1) webpack-sources: 1.4.3 wrap-ansi: 6.2.0 @@ -5195,11 +5109,11 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@20.19.23': + '@types/node@20.19.25': dependencies: undici-types: 6.21.0 - '@types/node@24.9.1': + '@types/node@24.10.1': dependencies: undici-types: 7.16.0 @@ -5228,15 +5142,15 @@ snapshots: '@types/whatwg-mimetype@3.0.2': {} - '@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.46.2 - '@typescript-eslint/type-utils': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.46.2 - eslint: 9.38.0(jiti@2.6.1) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.47.0 + '@typescript-eslint/type-utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.47.0 + eslint: 9.39.1(jiti@2.6.1) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 @@ -5245,56 +5159,56 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.46.2 - '@typescript-eslint/types': 8.46.2 - '@typescript-eslint/typescript-estree': 8.46.2(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.46.2 + '@typescript-eslint/scope-manager': 8.47.0 + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.47.0 debug: 4.4.3 - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.46.2(typescript@5.9.3)': + '@typescript-eslint/project-service@8.47.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.46.2(typescript@5.9.3) - '@typescript-eslint/types': 8.46.2 + '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3) + '@typescript-eslint/types': 8.47.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.46.2': + '@typescript-eslint/scope-manager@8.47.0': dependencies: - '@typescript-eslint/types': 8.46.2 - '@typescript-eslint/visitor-keys': 8.46.2 + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/visitor-keys': 8.47.0 - '@typescript-eslint/tsconfig-utils@8.46.2(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.47.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.46.2 - '@typescript-eslint/typescript-estree': 8.46.2(typescript@5.9.3) - '@typescript-eslint/utils': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.46.2': {} + '@typescript-eslint/types@8.47.0': {} - '@typescript-eslint/typescript-estree@8.46.2(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.47.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.46.2(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.46.2(typescript@5.9.3) - '@typescript-eslint/types': 8.46.2 - '@typescript-eslint/visitor-keys': 8.46.2 + '@typescript-eslint/project-service': 8.47.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3) + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/visitor-keys': 8.47.0 debug: 4.4.3 fast-glob: 3.3.3 is-glob: 4.0.3 @@ -5305,20 +5219,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.46.2 - '@typescript-eslint/types': 8.46.2 - '@typescript-eslint/typescript-estree': 8.46.2(typescript@5.9.3) - eslint: 9.38.0(jiti@2.6.1) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.47.0 + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.46.2': + '@typescript-eslint/visitor-keys@8.47.0': dependencies: - '@typescript-eslint/types': 8.46.2 + '@typescript-eslint/types': 8.47.0 eslint-visitor-keys: 4.2.1 '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -5380,60 +5294,60 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vitejs/plugin-vue@6.0.1(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.22(typescript@5.9.3))': + '@vitejs/plugin-vue@6.0.2(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1))(vue@3.5.24(typescript@5.9.3))': dependencies: - '@rolldown/pluginutils': 1.0.0-beta.29 - vite: 7.1.12(@types/node@24.9.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.0)(yaml@2.8.1) - vue: 3.5.22(typescript@5.9.3) + '@rolldown/pluginutils': 1.0.0-beta.50 + vite: 7.2.2(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1) + vue: 3.5.24(typescript@5.9.3) - '@vitest/eslint-plugin@1.3.26(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.8)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.0)(yaml@2.8.1))': + '@vitest/eslint-plugin@1.4.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.10(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1))': dependencies: - '@typescript-eslint/scope-manager': 8.46.2 - '@typescript-eslint/utils': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.38.0(jiti@2.6.1) + '@typescript-eslint/scope-manager': 8.47.0 + '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.1(jiti@2.6.1) optionalDependencies: typescript: 5.9.3 - vitest: 4.0.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.8)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.0)(yaml@2.8.1) + vitest: 4.0.10(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@vitest/expect@4.0.4': + '@vitest/expect@4.0.10': dependencies: '@standard-schema/spec': 1.0.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.0.4 - '@vitest/utils': 4.0.4 - chai: 6.2.0 + '@vitest/spy': 4.0.10 + '@vitest/utils': 4.0.10 + chai: 6.2.1 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.4(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.0)(yaml@2.8.1))': + '@vitest/mocker@4.0.10(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1))': dependencies: - '@vitest/spy': 4.0.4 + '@vitest/spy': 4.0.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.1.12(@types/node@24.9.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.0)(yaml@2.8.1) + vite: 7.2.2(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1) - '@vitest/pretty-format@4.0.4': + '@vitest/pretty-format@4.0.10': dependencies: tinyrainbow: 3.0.3 - '@vitest/runner@4.0.4': + '@vitest/runner@4.0.10': dependencies: - '@vitest/utils': 4.0.4 + '@vitest/utils': 4.0.10 pathe: 2.0.3 - '@vitest/snapshot@4.0.4': + '@vitest/snapshot@4.0.10': dependencies: - '@vitest/pretty-format': 4.0.4 + '@vitest/pretty-format': 4.0.10 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.0.4': {} + '@vitest/spy@4.0.10': {} - '@vitest/utils@4.0.4': + '@vitest/utils@4.0.10': dependencies: - '@vitest/pretty-format': 4.0.4 + '@vitest/pretty-format': 4.0.10 tinyrainbow: 3.0.3 '@volar/language-core@2.4.23': @@ -5448,71 +5362,71 @@ snapshots: path-browserify: 1.0.1 vscode-uri: 3.1.0 - '@vue/compiler-core@3.5.22': + '@vue/compiler-core@3.5.24': dependencies: '@babel/parser': 7.28.5 - '@vue/shared': 3.5.22 + '@vue/shared': 3.5.24 entities: 4.5.0 estree-walker: 2.0.2 source-map-js: 1.2.1 - '@vue/compiler-dom@3.5.22': + '@vue/compiler-dom@3.5.24': dependencies: - '@vue/compiler-core': 3.5.22 - '@vue/shared': 3.5.22 + '@vue/compiler-core': 3.5.24 + '@vue/shared': 3.5.24 - '@vue/compiler-sfc@3.5.22': + '@vue/compiler-sfc@3.5.24': dependencies: '@babel/parser': 7.28.5 - '@vue/compiler-core': 3.5.22 - '@vue/compiler-dom': 3.5.22 - '@vue/compiler-ssr': 3.5.22 - '@vue/shared': 3.5.22 + '@vue/compiler-core': 3.5.24 + '@vue/compiler-dom': 3.5.24 + '@vue/compiler-ssr': 3.5.24 + '@vue/shared': 3.5.24 estree-walker: 2.0.2 magic-string: 0.30.21 postcss: 8.5.6 source-map-js: 1.2.1 - '@vue/compiler-ssr@3.5.22': + '@vue/compiler-ssr@3.5.24': dependencies: - '@vue/compiler-dom': 3.5.22 - '@vue/shared': 3.5.22 + '@vue/compiler-dom': 3.5.24 + '@vue/shared': 3.5.24 - '@vue/language-core@3.1.2(typescript@5.9.3)': + '@vue/language-core@3.1.4(typescript@5.9.3)': dependencies: '@volar/language-core': 2.4.23 - '@vue/compiler-dom': 3.5.22 - '@vue/shared': 3.5.22 - alien-signals: 3.0.3 + '@vue/compiler-dom': 3.5.24 + '@vue/shared': 3.5.24 + alien-signals: 3.1.0 muggle-string: 0.4.1 path-browserify: 1.0.1 picomatch: 4.0.3 optionalDependencies: typescript: 5.9.3 - '@vue/reactivity@3.5.22': + '@vue/reactivity@3.5.24': dependencies: - '@vue/shared': 3.5.22 + '@vue/shared': 3.5.24 - '@vue/runtime-core@3.5.22': + '@vue/runtime-core@3.5.24': dependencies: - '@vue/reactivity': 3.5.22 - '@vue/shared': 3.5.22 + '@vue/reactivity': 3.5.24 + '@vue/shared': 3.5.24 - '@vue/runtime-dom@3.5.22': + '@vue/runtime-dom@3.5.24': dependencies: - '@vue/reactivity': 3.5.22 - '@vue/runtime-core': 3.5.22 - '@vue/shared': 3.5.22 - csstype: 3.1.3 + '@vue/reactivity': 3.5.24 + '@vue/runtime-core': 3.5.24 + '@vue/shared': 3.5.24 + csstype: 3.2.3 - '@vue/server-renderer@3.5.22(vue@3.5.22(typescript@5.9.3))': + '@vue/server-renderer@3.5.24(vue@3.5.24(typescript@5.9.3))': dependencies: - '@vue/compiler-ssr': 3.5.22 - '@vue/shared': 3.5.22 - vue: 3.5.22(typescript@5.9.3) + '@vue/compiler-ssr': 3.5.24 + '@vue/shared': 3.5.24 + vue: 3.5.24(typescript@5.9.3) - '@vue/shared@3.5.22': {} + '@vue/shared@3.5.24': {} '@webassemblyjs/ast@1.14.1': dependencies: @@ -5590,20 +5504,20 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@webpack-cli/configtest@3.0.1(webpack-cli@6.0.1)(webpack@5.102.1)': + '@webpack-cli/configtest@3.0.1(webpack-cli@6.0.1)(webpack@5.103.0)': dependencies: - webpack: 5.102.1(webpack-cli@6.0.1) - webpack-cli: 6.0.1(webpack@5.102.1) + webpack: 5.103.0(webpack-cli@6.0.1) + webpack-cli: 6.0.1(webpack@5.103.0) - '@webpack-cli/info@3.0.1(webpack-cli@6.0.1)(webpack@5.102.1)': + '@webpack-cli/info@3.0.1(webpack-cli@6.0.1)(webpack@5.103.0)': dependencies: - webpack: 5.102.1(webpack-cli@6.0.1) - webpack-cli: 6.0.1(webpack@5.102.1) + webpack: 5.103.0(webpack-cli@6.0.1) + webpack-cli: 6.0.1(webpack@5.103.0) - '@webpack-cli/serve@3.0.1(webpack-cli@6.0.1)(webpack@5.102.1)': + '@webpack-cli/serve@3.0.1(webpack-cli@6.0.1)(webpack@5.103.0)': dependencies: - webpack: 5.102.1(webpack-cli@6.0.1) - webpack-cli: 6.0.1(webpack@5.102.1) + webpack: 5.103.0(webpack-cli@6.0.1) + webpack-cli: 6.0.1(webpack@5.103.0) '@xtuc/ieee754@1.2.0': {} @@ -5619,9 +5533,9 @@ snapshots: acorn@8.15.0: {} - add-asset-webpack-plugin@3.1.1(webpack@5.102.1): + add-asset-webpack-plugin@3.1.1(webpack@5.103.0): optionalDependencies: - webpack: 5.102.1(webpack-cli@6.0.1) + webpack: 5.103.0(webpack-cli@6.0.1) ajv-formats@2.1.1(ajv@8.17.1): optionalDependencies: @@ -5646,7 +5560,7 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - alien-signals@3.0.3: {} + alien-signals@3.1.0: {} ansi-regex@5.0.1: {} @@ -5701,7 +5615,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.8.20: {} + baseline-browser-mapping@2.8.29: {} big.js@5.2.2: {} @@ -5722,13 +5636,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.27.0: + browserslist@4.28.0: dependencies: - baseline-browser-mapping: 2.8.20 - caniuse-lite: 1.0.30001751 - electron-to-chromium: 1.5.240 - node-releases: 2.0.26 - update-browserslist-db: 1.1.4(browserslist@4.27.0) + baseline-browser-mapping: 2.8.29 + caniuse-lite: 1.0.30001756 + electron-to-chromium: 1.5.256 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.28.0) buffer-from@1.1.2: {} @@ -5743,22 +5657,21 @@ snapshots: bytes@3.1.2: {} - cacheable@2.1.1: + cacheable@2.2.0: dependencies: - '@cacheable/memoize': 2.0.3 - '@cacheable/memory': 2.0.3 - '@cacheable/utils': 2.1.0 - hookified: 1.12.2 - keyv: 5.5.3 - qified: 0.5.1 + '@cacheable/memory': 2.0.5 + '@cacheable/utils': 2.3.1 + hookified: 1.13.0 + keyv: 5.5.4 + qified: 0.5.2 callsites@3.1.0: {} camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001751: {} + caniuse-lite@1.0.30001756: {} - chai@6.2.0: {} + chai@6.2.1: {} chalk@4.1.2: dependencies: @@ -5777,10 +5690,10 @@ snapshots: dependencies: '@kurkle/color': 0.3.4 - chartjs-adapter-dayjs-4@1.0.4(chart.js@4.5.1)(dayjs@1.11.18): + chartjs-adapter-dayjs-4@1.0.4(chart.js@4.5.1)(dayjs@1.11.19): dependencies: chart.js: 4.5.1 - dayjs: 1.11.18 + dayjs: 1.11.19 chartjs-plugin-zoom@2.2.0(chart.js@4.5.1): dependencies: @@ -5826,7 +5739,7 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 - clippie@4.1.8: {} + clippie@4.1.9: {} clone-deep@4.0.1: dependencies: @@ -5854,7 +5767,7 @@ snapshots: commander@12.1.0: {} - commander@13.1.0: {} + commander@14.0.2: {} commander@2.20.3: {} @@ -5872,9 +5785,9 @@ snapshots: confbox@0.2.2: {} - core-js-compat@3.46.0: + core-js-compat@3.47.0: dependencies: - browserslist: 4.27.0 + browserslist: 4.28.0 core-js@3.32.2: {} @@ -5890,7 +5803,7 @@ snapshots: dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.1.0 + js-yaml: 4.1.1 parse-json: 5.2.0 optionalDependencies: typescript: 5.9.3 @@ -5905,7 +5818,7 @@ snapshots: css-functions-list@3.2.3: {} - css-loader@7.1.2(webpack@5.102.1): + css-loader@7.1.2(webpack@5.103.0): dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 @@ -5916,7 +5829,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.3 optionalDependencies: - webpack: 5.102.1(webpack-cli@6.0.1) + webpack: 5.103.0(webpack-cli@6.0.1) css-select@5.2.2: dependencies: @@ -5950,7 +5863,7 @@ snapshots: dependencies: css-tree: 2.2.1 - csstype@3.1.3: {} + csstype@3.2.3: {} cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1): dependencies: @@ -6138,7 +6051,7 @@ snapshots: damerau-levenshtein@1.0.8: {} - dayjs@1.11.18: {} + dayjs@1.11.19: {} debug@3.2.7: dependencies: @@ -6216,8 +6129,6 @@ snapshots: '@swc/helpers': 0.2.14 just-extend: 5.1.1 - eastasianwidth@0.2.0: {} - easymde@2.20.0: dependencies: '@types/codemirror': 5.60.17 @@ -6226,7 +6137,7 @@ snapshots: codemirror-spell-checker: 1.1.2 marked: 4.3.0 - electron-to-chromium@1.5.240: {} + electron-to-chromium@1.5.256: {} emoji-regex@10.6.0: {} @@ -6245,7 +6156,7 @@ snapshots: env-paths@2.2.1: {} - envinfo@7.19.0: {} + envinfo@7.20.0: {} error-ex@1.3.4: dependencies: @@ -6253,42 +6164,42 @@ snapshots: es-module-lexer@1.7.0: {} - esbuild-loader@4.4.0(webpack@5.102.1): + esbuild-loader@4.4.0(webpack@5.103.0): dependencies: - esbuild: 0.25.11 + esbuild: 0.25.12 get-tsconfig: 4.13.0 loader-utils: 2.0.4 - webpack: 5.102.1(webpack-cli@6.0.1) + webpack: 5.103.0(webpack-cli@6.0.1) webpack-sources: 1.4.3 - esbuild@0.25.11: + esbuild@0.25.12: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.11 - '@esbuild/android-arm': 0.25.11 - '@esbuild/android-arm64': 0.25.11 - '@esbuild/android-x64': 0.25.11 - '@esbuild/darwin-arm64': 0.25.11 - '@esbuild/darwin-x64': 0.25.11 - '@esbuild/freebsd-arm64': 0.25.11 - '@esbuild/freebsd-x64': 0.25.11 - '@esbuild/linux-arm': 0.25.11 - '@esbuild/linux-arm64': 0.25.11 - '@esbuild/linux-ia32': 0.25.11 - '@esbuild/linux-loong64': 0.25.11 - '@esbuild/linux-mips64el': 0.25.11 - '@esbuild/linux-ppc64': 0.25.11 - '@esbuild/linux-riscv64': 0.25.11 - '@esbuild/linux-s390x': 0.25.11 - '@esbuild/linux-x64': 0.25.11 - '@esbuild/netbsd-arm64': 0.25.11 - '@esbuild/netbsd-x64': 0.25.11 - '@esbuild/openbsd-arm64': 0.25.11 - '@esbuild/openbsd-x64': 0.25.11 - '@esbuild/openharmony-arm64': 0.25.11 - '@esbuild/sunos-x64': 0.25.11 - '@esbuild/win32-arm64': 0.25.11 - '@esbuild/win32-ia32': 0.25.11 - '@esbuild/win32-x64': 0.25.11 + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 escalade@3.2.0: {} @@ -6296,14 +6207,14 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-compat-utils@0.6.5(eslint@9.38.0(jiti@2.6.1)): + eslint-compat-utils@0.6.5(eslint@9.39.1(jiti@2.6.1)): dependencies: - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) semver: 7.7.3 - eslint-config-prettier@10.1.8(eslint@9.38.0(jiti@2.6.1)): + eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)): dependencies: - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) eslint-import-context@0.1.9(unrs-resolver@1.11.1): dependencies: @@ -6320,10 +6231,10 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.38.0(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.38.0(jiti@2.6.1)): + eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)): dependencies: debug: 4.4.3 - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) eslint-import-context: 0.1.9(unrs-resolver@1.11.1) get-tsconfig: 4.13.0 is-bun-module: 2.0.0 @@ -6331,100 +6242,100 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.38.0(jiti@2.6.1)) - eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.38.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.38.0(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.38.0(jiti@2.6.1) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.38.0(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.38.0(jiti@2.6.1)) + eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-plugin-array-func@5.1.0(eslint@9.38.0(jiti@2.6.1)): + eslint-plugin-array-func@5.1.0(eslint@9.39.1(jiti@2.6.1)): dependencies: - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) - eslint-plugin-escompat@3.11.4(eslint@9.38.0(jiti@2.6.1)): + eslint-plugin-escompat@3.11.4(eslint@9.39.1(jiti@2.6.1)): dependencies: - browserslist: 4.27.0 - eslint: 9.38.0(jiti@2.6.1) + browserslist: 4.28.0 + eslint: 9.39.1(jiti@2.6.1) - eslint-plugin-eslint-comments@3.2.0(eslint@9.38.0(jiti@2.6.1)): + eslint-plugin-eslint-comments@3.2.0(eslint@9.39.1(jiti@2.6.1)): dependencies: escape-string-regexp: 1.0.5 - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) ignore: 5.3.2 - eslint-plugin-filenames@1.3.2(eslint@9.38.0(jiti@2.6.1)): + eslint-plugin-filenames@1.3.2(eslint@9.39.1(jiti@2.6.1)): dependencies: - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) 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(@types/eslint@9.6.1)(eslint-import-resolver-typescript@4.4.4)(eslint@9.38.0(jiti@2.6.1)): + eslint-plugin-github@6.0.0(@types/eslint@9.6.1)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)): dependencies: - '@eslint/compat': 1.4.0(eslint@9.38.0(jiti@2.6.1)) + '@eslint/compat': 1.4.1(eslint@9.39.1(jiti@2.6.1)) '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.38.0 + '@eslint/js': 9.39.1 '@github/browserslist-config': 1.0.0 - '@typescript-eslint/eslint-plugin': 8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) aria-query: 5.3.2 - eslint: 9.38.0(jiti@2.6.1) - eslint-config-prettier: 10.1.8(eslint@9.38.0(jiti@2.6.1)) - eslint-plugin-escompat: 3.11.4(eslint@9.38.0(jiti@2.6.1)) - eslint-plugin-eslint-comments: 3.2.0(eslint@9.38.0(jiti@2.6.1)) - eslint-plugin-filenames: 1.3.2(eslint@9.38.0(jiti@2.6.1)) - eslint-plugin-i18n-text: 1.0.1(eslint@9.38.0(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.38.0(jiti@2.6.1)) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.38.0(jiti@2.6.1)) + eslint: 9.39.1(jiti@2.6.1) + eslint-config-prettier: 10.1.8(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-escompat: 3.11.4(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-eslint-comments: 3.2.0(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-filenames: 1.3.2(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-i18n-text: 1.0.1(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-no-only-tests: 3.3.0 - eslint-plugin-prettier: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1))(prettier@3.6.2) + eslint-plugin-prettier: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2) eslint-rule-documentation: 1.0.23 - globals: 16.4.0 + globals: 16.5.0 jsx-ast-utils: 3.3.5 prettier: 3.6.2 svg-element-attributes: 1.3.1 typescript: 5.9.3 - typescript-eslint: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + typescript-eslint: 8.47.0(eslint@9.39.1(jiti@2.6.1))(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@9.38.0(jiti@2.6.1)): + eslint-plugin-i18n-text@1.0.1(eslint@9.39.1(jiti@2.6.1)): dependencies: - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) - eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.38.0(jiti@2.6.1)): + eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)): dependencies: - '@typescript-eslint/types': 8.46.2 + '@typescript-eslint/types': 8.47.0 comment-parser: 1.4.1 debug: 4.4.3 - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) eslint-import-context: 0.1.9(unrs-resolver@1.11.1) is-glob: 4.0.3 - minimatch: 10.0.3 + minimatch: 10.1.1 semver: 7.7.3 stable-hash-x: 0.2.0 unrs-resolver: 1.11.1 optionalDependencies: - '@typescript-eslint/utils': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.38.0(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: '@nolyfill/array-includes@1.0.44' @@ -6433,9 +6344,9 @@ snapshots: array.prototype.flatmap: '@nolyfill/array.prototype.flatmap@1.0.44' debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.38.0(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.1(jiti@2.6.1)) hasown: '@nolyfill/hasown@1.0.44' is-core-module: '@nolyfill/is-core-module@1.0.39' is-glob: 4.0.3 @@ -6447,13 +6358,13 @@ snapshots: string.prototype.trimend: '@nolyfill/string.prototype.trimend@1.0.44' tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@9.38.0(jiti@2.6.1)): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.1(jiti@2.6.1)): dependencies: aria-query: 5.3.2 array-includes: '@nolyfill/array-includes@1.0.44' @@ -6463,7 +6374,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) hasown: '@nolyfill/hasown@1.0.44' jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -6474,46 +6385,46 @@ snapshots: eslint-plugin-no-only-tests@3.3.0: {} - eslint-plugin-no-use-extend-native@0.7.2(eslint@9.38.0(jiti@2.6.1)): + eslint-plugin-no-use-extend-native@0.7.2(eslint@9.39.1(jiti@2.6.1)): dependencies: - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) is-get-set-prop: 2.0.0 is-js-type: 3.0.0 is-obj-prop: 2.0.0 is-proto-prop: 3.0.1 - eslint-plugin-playwright@2.2.2(eslint@9.38.0(jiti@2.6.1)): + eslint-plugin-playwright@2.3.0(eslint@9.39.1(jiti@2.6.1)): dependencies: - eslint: 9.38.0(jiti@2.6.1) - globals: 13.24.0 + eslint: 9.39.1(jiti@2.6.1) + globals: 16.5.0 - eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.38.0(jiti@2.6.1)))(eslint@9.38.0(jiti@2.6.1))(prettier@3.6.2): + eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2): dependencies: - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) prettier: 3.6.2 prettier-linter-helpers: 1.0.0 synckit: 0.11.11 optionalDependencies: '@types/eslint': 9.6.1 - eslint-config-prettier: 10.1.8(eslint@9.38.0(jiti@2.6.1)) + eslint-config-prettier: 10.1.8(eslint@9.39.1(jiti@2.6.1)) - eslint-plugin-regexp@2.10.0(eslint@9.38.0(jiti@2.6.1)): + eslint-plugin-regexp@2.10.0(eslint@9.39.1(jiti@2.6.1)): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 comment-parser: 1.4.1 - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) jsdoc-type-pratt-parser: 4.8.0 refa: 0.12.1 regexp-ast-analysis: 0.7.1 scslre: 0.3.0 - eslint-plugin-sonarjs@3.0.5(eslint@9.38.0(jiti@2.6.1)): + eslint-plugin-sonarjs@3.0.5(eslint@9.39.1(jiti@2.6.1)): dependencies: '@eslint-community/regexpp': 4.12.1 builtin-modules: 3.3.0 bytes: 3.1.2 - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) functional-red-black-tree: 1.0.1 jsx-ast-utils-x: 0.1.0 lodash.merge: 4.6.2 @@ -6522,19 +6433,19 @@ snapshots: semver: 7.7.2 typescript: 5.9.3 - eslint-plugin-unicorn@62.0.0(eslint@9.38.0(jiti@2.6.1)): + eslint-plugin-unicorn@62.0.0(eslint@9.39.1(jiti@2.6.1)): dependencies: '@babel/helper-validator-identifier': 7.28.5 - '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) - '@eslint/plugin-kit': 0.4.0 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + '@eslint/plugin-kit': 0.4.1 change-case: 5.4.4 ci-info: 4.3.1 clean-regexp: 1.0.0 - core-js-compat: 3.46.0 - eslint: 9.38.0(jiti@2.6.1) + core-js-compat: 3.47.0 + eslint: 9.39.1(jiti@2.6.1) esquery: 1.6.0 find-up-simple: 1.0.1 - globals: 16.4.0 + globals: 16.5.0 indent-string: 5.0.0 is-builtin-module: 5.0.0 jsesc: 3.1.0 @@ -6544,38 +6455,38 @@ snapshots: semver: 7.7.3 strip-indent: 4.1.1 - eslint-plugin-vue-scoped-css@2.12.0(eslint@9.38.0(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.38.0(jiti@2.6.1))): + eslint-plugin-vue-scoped-css@2.12.0(eslint@9.39.1(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@2.6.1))): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) - eslint: 9.38.0(jiti@2.6.1) - eslint-compat-utils: 0.6.5(eslint@9.38.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + eslint: 9.39.1(jiti@2.6.1) + eslint-compat-utils: 0.6.5(eslint@9.39.1(jiti@2.6.1)) lodash: 4.17.21 postcss: 8.5.6 postcss-safe-parser: 6.0.0(postcss@8.5.6) postcss-scss: 4.0.9(postcss@8.5.6) postcss-selector-parser: 7.1.0 postcss-styl: 0.12.3 - vue-eslint-parser: 10.2.0(eslint@9.38.0(jiti@2.6.1)) + vue-eslint-parser: 10.2.0(eslint@9.39.1(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-plugin-vue@10.5.1(@stylistic/eslint-plugin@5.5.0(eslint@9.38.0(jiti@2.6.1)))(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.38.0(jiti@2.6.1))): + eslint-plugin-vue@10.5.1(@stylistic/eslint-plugin@5.6.1(eslint@9.39.1(jiti@2.6.1)))(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@2.6.1))): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) - eslint: 9.38.0(jiti@2.6.1) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + eslint: 9.39.1(jiti@2.6.1) natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 6.1.2 semver: 7.7.3 - vue-eslint-parser: 10.2.0(eslint@9.38.0(jiti@2.6.1)) + vue-eslint-parser: 10.2.0(eslint@9.39.1(jiti@2.6.1)) xml-name-validator: 4.0.0 optionalDependencies: - '@stylistic/eslint-plugin': 5.5.0(eslint@9.38.0(jiti@2.6.1)) - '@typescript-eslint/parser': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@stylistic/eslint-plugin': 5.6.1(eslint@9.39.1(jiti@2.6.1)) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - eslint-plugin-wc@3.0.2(eslint@9.38.0(jiti@2.6.1)): + eslint-plugin-wc@3.0.2(eslint@9.39.1(jiti@2.6.1)): dependencies: - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) is-valid-element-name: 1.0.0 js-levenshtein-esm: 2.0.0 @@ -6595,16 +6506,16 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.38.0(jiti@2.6.1): + eslint@9.39.1(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.21.1 - '@eslint/config-helpers': 0.4.1 - '@eslint/core': 0.16.0 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.38.0 - '@eslint/plugin-kit': 0.4.0 + '@eslint/js': 9.39.1 + '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 @@ -6668,7 +6579,7 @@ snapshots: expect-type@1.2.2: {} - exsolve@1.0.7: {} + exsolve@1.0.8: {} fast-deep-equal@3.1.3: {} @@ -6708,7 +6619,7 @@ snapshots: file-entry-cache@10.1.4: dependencies: - flat-cache: 6.1.18 + flat-cache: 6.1.19 file-entry-cache@8.0.0: dependencies: @@ -6735,21 +6646,16 @@ snapshots: flatted: 3.3.3 keyv: 4.5.4 - flat-cache@6.1.18: + flat-cache@6.1.19: dependencies: - cacheable: 2.1.1 + cacheable: 2.2.0 flatted: 3.3.3 - hookified: 1.12.2 + hookified: 1.13.0 flat@5.0.2: {} flatted@3.3.3: {} - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - fs.realpath@1.0.0: {} fsevents@2.3.2: @@ -6778,24 +6684,6 @@ snapshots: glob-to-regexp@0.4.1: {} - glob@10.4.5: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - - glob@11.0.3: - dependencies: - foreground-child: 3.3.1 - jackspeak: 4.1.1 - minimatch: 10.0.3 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 2.0.0 - glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -6815,15 +6703,11 @@ snapshots: kind-of: 6.0.3 which: 1.3.1 - globals@13.24.0: - dependencies: - type-fest: 0.20.2 - globals@14.0.0: {} globals@15.15.0: {} - globals@16.4.0: {} + globals@16.5.0: {} globby@11.1.0: dependencies: @@ -6844,9 +6728,9 @@ snapshots: hammerjs@2.0.8: {} - happy-dom@20.0.8: + happy-dom@20.0.10: dependencies: - '@types/node': 20.19.23 + '@types/node': 20.19.25 '@types/whatwg-mimetype': 3.0.2 whatwg-mimetype: 3.0.0 @@ -6854,7 +6738,11 @@ snapshots: hash-sum@2.0.0: {} - hookified@1.12.2: {} + hashery@1.2.0: + dependencies: + hookified: 1.13.0 + + hookified@1.13.0: {} html-tags@3.3.1: {} @@ -6986,19 +6874,9 @@ snapshots: isobject@3.0.1: {} - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - jackspeak@4.1.1: - dependencies: - '@isaacs/cliui': 8.0.2 - jest-worker@27.5.1: dependencies: - '@types/node': 24.9.1 + '@types/node': 24.10.1 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -7016,7 +6894,7 @@ snapshots: js-types@4.0.0: {} - js-yaml@4.1.0: + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -7063,7 +6941,7 @@ snapshots: dependencies: json-buffer: 3.0.1 - keyv@5.5.3: + keyv@5.5.4: dependencies: '@keyv/serialize': 1.1.1 @@ -7152,10 +7030,6 @@ snapshots: lowercase-keys@3.0.0: {} - lru-cache@10.4.3: {} - - lru-cache@11.2.2: {} - magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -7169,23 +7043,24 @@ snapshots: punycode.js: 2.3.1 uc.micro: 2.1.0 - markdownlint-cli@0.45.0: + markdownlint-cli@0.46.0: dependencies: - commander: 13.1.0 - glob: 11.0.3 + commander: 14.0.2 + deep-extend: 0.6.0 ignore: 7.0.5 - js-yaml: 4.1.0 + js-yaml: 4.1.1 jsonc-parser: 3.3.1 jsonpointer: 5.0.1 markdown-it: 14.1.0 - markdownlint: 0.38.0 - minimatch: 10.0.3 + markdownlint: 0.39.0 + minimatch: 10.1.1 run-con: 1.3.2 - smol-toml: 1.3.4 + smol-toml: 1.5.2 + tinyglobby: 0.2.15 transitivePeerDependencies: - supports-color - markdownlint@0.38.0: + markdownlint@0.39.0: dependencies: micromark: 4.0.2 micromark-core-commonmark: 2.0.3 @@ -7200,7 +7075,7 @@ snapshots: marked@14.0.0: {} - marked@16.4.1: {} + marked@16.4.2: {} marked@4.3.0: {} @@ -7237,12 +7112,12 @@ snapshots: d3: 7.9.0 d3-sankey: 0.12.3 dagre-d3-es: 7.0.13 - dayjs: 1.11.18 + dayjs: 1.11.19 dompurify: 3.3.0 katex: 0.16.25 khroma: 2.1.0 lodash-es: 4.17.21 - marked: 16.4.1 + marked: 16.4.2 roughjs: 4.6.6 stylis: 4.3.6 ts-dedent: 2.2.0 @@ -7433,13 +7308,13 @@ snapshots: dependencies: mime-db: 1.52.0 - mini-css-extract-plugin@2.9.4(webpack@5.102.1): + mini-css-extract-plugin@2.9.4(webpack@5.103.0): dependencies: schema-utils: 4.3.3 tapable: 2.3.0 - webpack: 5.102.1(webpack-cli@6.0.1) + webpack: 5.103.0(webpack-cli@6.0.1) - minimatch@10.0.3: + minimatch@10.1.1: dependencies: '@isaacs/brace-expansion': 5.0.0 @@ -7453,8 +7328,6 @@ snapshots: minimist@1.2.8: {} - minipass@7.1.2: {} - mlly@1.8.0: dependencies: acorn: 8.15.0 @@ -7462,11 +7335,11 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.1 - monaco-editor-webpack-plugin@7.1.1(monaco-editor@0.54.0)(webpack@5.102.1): + monaco-editor-webpack-plugin@7.1.1(monaco-editor@0.54.0)(webpack@5.103.0): dependencies: loader-utils: 2.0.4 monaco-editor: 0.54.0 - webpack: 5.102.1(webpack-cli@6.0.1) + webpack: 5.103.0(webpack-cli@6.0.1) monaco-editor@0.54.0: dependencies: @@ -7503,7 +7376,7 @@ snapshots: dependencies: whatwg-url: 5.0.0 - node-releases@2.0.26: {} + node-releases@2.0.27: {} nolyfill@1.0.44: {} @@ -7556,8 +7429,6 @@ snapshots: p-try@2.2.0: {} - package-json-from-dist@1.0.1: {} - package-manager-detector@1.5.0: {} parent-module@1.0.1: @@ -7593,16 +7464,6 @@ snapshots: path-parse@1.0.7: {} - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - - path-scurry@2.0.0: - dependencies: - lru-cache: 11.2.2 - minipass: 7.1.2 - path-type@4.0.0: {} pathe@2.0.3: {} @@ -7634,7 +7495,7 @@ snapshots: pkg-types@2.3.0: dependencies: confbox: 0.2.2 - exsolve: 1.0.7 + exsolve: 1.0.8 pathe: 2.0.3 playwright-core@1.56.1: {} @@ -7680,14 +7541,14 @@ snapshots: optionalDependencies: postcss: 8.5.6 - postcss-loader@8.2.0(postcss@8.5.6)(typescript@5.9.3)(webpack@5.102.1): + postcss-loader@8.2.0(postcss@8.5.6)(typescript@5.9.3)(webpack@5.103.0): dependencies: cosmiconfig: 9.0.0(typescript@5.9.3) jiti: 2.6.1 postcss: 8.5.6 semver: 7.7.3 optionalDependencies: - webpack: 5.102.1(webpack-cli@6.0.1) + webpack: 5.103.0(webpack-cli@6.0.1) transitivePeerDependencies: - typescript @@ -7773,9 +7634,9 @@ snapshots: punycode@2.3.1: {} - qified@0.5.1: + qified@0.5.2: dependencies: - hookified: 1.12.2 + hookified: 1.13.0 quansync@0.2.11: {} @@ -7836,32 +7697,32 @@ snapshots: robust-predicates@3.0.2: {} - rollup@4.52.5: + rollup@4.53.3: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.52.5 - '@rollup/rollup-android-arm64': 4.52.5 - '@rollup/rollup-darwin-arm64': 4.52.5 - '@rollup/rollup-darwin-x64': 4.52.5 - '@rollup/rollup-freebsd-arm64': 4.52.5 - '@rollup/rollup-freebsd-x64': 4.52.5 - '@rollup/rollup-linux-arm-gnueabihf': 4.52.5 - '@rollup/rollup-linux-arm-musleabihf': 4.52.5 - '@rollup/rollup-linux-arm64-gnu': 4.52.5 - '@rollup/rollup-linux-arm64-musl': 4.52.5 - '@rollup/rollup-linux-loong64-gnu': 4.52.5 - '@rollup/rollup-linux-ppc64-gnu': 4.52.5 - '@rollup/rollup-linux-riscv64-gnu': 4.52.5 - '@rollup/rollup-linux-riscv64-musl': 4.52.5 - '@rollup/rollup-linux-s390x-gnu': 4.52.5 - '@rollup/rollup-linux-x64-gnu': 4.52.5 - '@rollup/rollup-linux-x64-musl': 4.52.5 - '@rollup/rollup-openharmony-arm64': 4.52.5 - '@rollup/rollup-win32-arm64-msvc': 4.52.5 - '@rollup/rollup-win32-ia32-msvc': 4.52.5 - '@rollup/rollup-win32-x64-gnu': 4.52.5 - '@rollup/rollup-win32-x64-msvc': 4.52.5 + '@rollup/rollup-android-arm-eabi': 4.53.3 + '@rollup/rollup-android-arm64': 4.53.3 + '@rollup/rollup-darwin-arm64': 4.53.3 + '@rollup/rollup-darwin-x64': 4.53.3 + '@rollup/rollup-freebsd-arm64': 4.53.3 + '@rollup/rollup-freebsd-x64': 4.53.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 + '@rollup/rollup-linux-arm-musleabihf': 4.53.3 + '@rollup/rollup-linux-arm64-gnu': 4.53.3 + '@rollup/rollup-linux-arm64-musl': 4.53.3 + '@rollup/rollup-linux-loong64-gnu': 4.53.3 + '@rollup/rollup-linux-ppc64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-musl': 4.53.3 + '@rollup/rollup-linux-s390x-gnu': 4.53.3 + '@rollup/rollup-linux-x64-gnu': 4.53.3 + '@rollup/rollup-linux-x64-musl': 4.53.3 + '@rollup/rollup-openharmony-arm64': 4.53.3 + '@rollup/rollup-win32-arm64-msvc': 4.53.3 + '@rollup/rollup-win32-ia32-msvc': 4.53.3 + '@rollup/rollup-win32-x64-gnu': 4.53.3 + '@rollup/rollup-win32-x64-msvc': 4.53.3 fsevents: 2.3.3 roughjs@4.6.6: @@ -7886,7 +7747,7 @@ snapshots: sax@1.2.4: {} - sax@1.4.1: {} + sax@1.4.3: {} schema-utils@4.3.3: dependencies: @@ -7939,11 +7800,11 @@ snapshots: astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 - smol-toml@1.3.4: {} + smol-toml@1.5.2: {} solid-js@1.9.10: dependencies: - csstype: 3.1.3 + csstype: 3.2.3 seroval: 1.3.2 seroval-plugins: 1.3.3(seroval@1.3.2) @@ -8016,12 +7877,6 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.2 - string-width@7.2.0: dependencies: emoji-regex: 10.6.0 @@ -8119,14 +7974,14 @@ snapshots: transitivePeerDependencies: - supports-color - sucrase@3.35.0: + sucrase@3.35.1: dependencies: '@jridgewell/gen-mapping': 0.3.13 commander: 4.1.1 - glob: 10.4.5 lines-and-columns: 1.2.4 mz: 2.7.0 pirates: 4.0.7 + tinyglobby: 0.2.15 ts-interface-checker: 0.1.13 superstruct@0.10.13: {} @@ -8158,14 +8013,14 @@ snapshots: css-what: 6.2.2 csso: 5.0.5 picocolors: 1.1.1 - sax: 1.4.1 + sax: 1.4.3 svgson@5.3.1: dependencies: deep-rename-keys: 0.2.1 xml-reader: 2.4.3 - swagger-ui-dist@5.30.0: + swagger-ui-dist@5.30.2: dependencies: '@scarf/scarf': 1.4.0 @@ -8211,22 +8066,22 @@ snapshots: postcss-nested: 6.2.0(postcss@8.5.6) postcss-selector-parser: 6.1.2 resolve: 1.22.11 - sucrase: 3.35.0 + sucrase: 3.35.1 transitivePeerDependencies: - ts-node tapable@2.3.0: {} - terser-webpack-plugin@5.3.14(webpack@5.102.1): + terser-webpack-plugin@5.3.14(webpack@5.103.0): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 serialize-javascript: 6.0.2 - terser: 5.44.0 - webpack: 5.102.1(webpack-cli@6.0.1) + terser: 5.44.1 + webpack: 5.103.0(webpack-cli@6.0.1) - terser@5.44.0: + terser@5.44.1: dependencies: '@jridgewell/source-map': 0.3.11 acorn: 8.15.0 @@ -8251,7 +8106,7 @@ snapshots: tinyexec@0.3.2: {} - tinyexec@1.0.1: {} + tinyexec@1.0.2: {} tinyglobby@0.2.15: dependencies: @@ -8296,15 +8151,13 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-fest@0.20.2: {} - - typescript-eslint@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.46.2(typescript@5.9.3) - '@typescript-eslint/utils': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.38.0(jiti@2.6.1) + '@typescript-eslint/eslint-plugin': 8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -8347,9 +8200,9 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - update-browserslist-db@1.1.4(browserslist@4.27.0): + update-browserslist-db@1.1.4(browserslist@4.28.0): dependencies: - browserslist: 4.27.0 + browserslist: 4.28.0 escalade: 3.2.0 picocolors: 1.1.1 @@ -8365,33 +8218,33 @@ snapshots: vanilla-colorful@0.7.2: {} - vite-string-plugin@1.4.6: {} + vite-string-plugin@1.4.9: {} - vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.0)(yaml@2.8.1): + vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1): dependencies: - esbuild: 0.25.11 + esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.52.5 + rollup: 4.53.3 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.9.1 + '@types/node': 24.10.1 fsevents: 2.3.3 jiti: 2.6.1 stylus: 0.57.0 - terser: 5.44.0 + terser: 5.44.1 yaml: 2.8.1 - vitest@4.0.4(@types/debug@4.1.12)(@types/node@24.9.1)(happy-dom@20.0.8)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.0)(yaml@2.8.1): + vitest@4.0.10(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1): dependencies: - '@vitest/expect': 4.0.4 - '@vitest/mocker': 4.0.4(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.0)(yaml@2.8.1)) - '@vitest/pretty-format': 4.0.4 - '@vitest/runner': 4.0.4 - '@vitest/snapshot': 4.0.4 - '@vitest/spy': 4.0.4 - '@vitest/utils': 4.0.4 + '@vitest/expect': 4.0.10 + '@vitest/mocker': 4.0.10(vite@7.2.2(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1)) + '@vitest/pretty-format': 4.0.10 + '@vitest/runner': 4.0.10 + '@vitest/snapshot': 4.0.10 + '@vitest/spy': 4.0.10 + '@vitest/utils': 4.0.10 debug: 4.4.3 es-module-lexer: 1.7.0 expect-type: 1.2.2 @@ -8403,12 +8256,12 @@ snapshots: tinyexec: 0.3.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.1.12(@types/node@24.9.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.0)(yaml@2.8.1) + vite: 7.2.2(@types/node@24.10.1)(jiti@2.6.1)(stylus@0.57.0)(terser@5.44.1)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 - '@types/node': 24.9.1 - happy-dom: 20.0.8 + '@types/node': 24.10.1 + happy-dom: 20.0.10 transitivePeerDependencies: - jiti - less @@ -8444,19 +8297,19 @@ snapshots: vue-bar-graph@2.2.0(typescript@5.9.3): dependencies: - vue: 3.5.22(typescript@5.9.3) + vue: 3.5.24(typescript@5.9.3) transitivePeerDependencies: - typescript - vue-chartjs@5.3.2(chart.js@4.5.1)(vue@3.5.22(typescript@5.9.3)): + vue-chartjs@5.3.3(chart.js@4.5.1)(vue@3.5.24(typescript@5.9.3)): dependencies: chart.js: 4.5.1 - vue: 3.5.22(typescript@5.9.3) + vue: 3.5.24(typescript@5.9.3) - vue-eslint-parser@10.2.0(eslint@9.38.0(jiti@2.6.1)): + vue-eslint-parser@10.2.0(eslint@9.39.1(jiti@2.6.1)): dependencies: debug: 4.4.3 - eslint: 9.38.0(jiti@2.6.1) + eslint: 9.39.1(jiti@2.6.1) eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 espree: 10.4.0 @@ -8465,28 +8318,28 @@ snapshots: transitivePeerDependencies: - supports-color - vue-loader@17.4.2(vue@3.5.22(typescript@5.9.3))(webpack@5.102.1): + vue-loader@17.4.2(vue@3.5.24(typescript@5.9.3))(webpack@5.103.0): dependencies: chalk: 4.1.2 hash-sum: 2.0.0 watchpack: 2.4.4 - webpack: 5.102.1(webpack-cli@6.0.1) + webpack: 5.103.0(webpack-cli@6.0.1) optionalDependencies: - vue: 3.5.22(typescript@5.9.3) + vue: 3.5.24(typescript@5.9.3) - vue-tsc@3.1.2(typescript@5.9.3): + vue-tsc@3.1.4(typescript@5.9.3): dependencies: '@volar/typescript': 2.4.23 - '@vue/language-core': 3.1.2(typescript@5.9.3) + '@vue/language-core': 3.1.4(typescript@5.9.3) typescript: 5.9.3 - vue@3.5.22(typescript@5.9.3): + vue@3.5.24(typescript@5.9.3): dependencies: - '@vue/compiler-dom': 3.5.22 - '@vue/compiler-sfc': 3.5.22 - '@vue/runtime-dom': 3.5.22 - '@vue/server-renderer': 3.5.22(vue@3.5.22(typescript@5.9.3)) - '@vue/shared': 3.5.22 + '@vue/compiler-dom': 3.5.24 + '@vue/compiler-sfc': 3.5.24 + '@vue/runtime-dom': 3.5.24 + '@vue/server-renderer': 3.5.24(vue@3.5.24(typescript@5.9.3)) + '@vue/shared': 3.5.24 optionalDependencies: typescript: 5.9.3 @@ -8497,21 +8350,21 @@ snapshots: webidl-conversions@3.0.1: {} - webpack-cli@6.0.1(webpack@5.102.1): + webpack-cli@6.0.1(webpack@5.103.0): dependencies: '@discoveryjs/json-ext': 0.6.3 - '@webpack-cli/configtest': 3.0.1(webpack-cli@6.0.1)(webpack@5.102.1) - '@webpack-cli/info': 3.0.1(webpack-cli@6.0.1)(webpack@5.102.1) - '@webpack-cli/serve': 3.0.1(webpack-cli@6.0.1)(webpack@5.102.1) + '@webpack-cli/configtest': 3.0.1(webpack-cli@6.0.1)(webpack@5.103.0) + '@webpack-cli/info': 3.0.1(webpack-cli@6.0.1)(webpack@5.103.0) + '@webpack-cli/serve': 3.0.1(webpack-cli@6.0.1)(webpack@5.103.0) colorette: 2.0.20 commander: 12.1.0 cross-spawn: 7.0.6 - envinfo: 7.19.0 + envinfo: 7.20.0 fastest-levenshtein: 1.0.16 import-local: 3.2.0 interpret: 3.1.1 rechoir: 0.8.0 - webpack: 5.102.1(webpack-cli@6.0.1) + webpack: 5.103.0(webpack-cli@6.0.1) webpack-merge: 6.0.1 webpack-merge@6.0.1: @@ -8527,7 +8380,7 @@ snapshots: webpack-sources@3.3.3: {} - webpack@5.102.1(webpack-cli@6.0.1): + webpack@5.103.0(webpack-cli@6.0.1): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -8537,7 +8390,7 @@ snapshots: '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.15.0 acorn-import-phases: 1.0.4(acorn@8.15.0) - browserslist: 4.27.0 + browserslist: 4.28.0 chrome-trace-event: 1.0.4 enhanced-resolve: 5.18.3 es-module-lexer: 1.7.0 @@ -8551,11 +8404,11 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.0 - terser-webpack-plugin: 5.3.14(webpack@5.102.1) + terser-webpack-plugin: 5.3.14(webpack@5.103.0) watchpack: 2.4.4 webpack-sources: 3.3.3 optionalDependencies: - webpack-cli: 6.0.1(webpack@5.102.1) + webpack-cli: 6.0.1(webpack@5.103.0) transitivePeerDependencies: - '@swc/core' - esbuild @@ -8591,18 +8444,6 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.1.2 - wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 diff --git a/public/assets/img/svg/gitea-colorblind-blueyellow.svg b/public/assets/img/svg/gitea-colorblind-blueyellow.svg new file mode 100644 index 0000000000..63a101b50d --- /dev/null +++ b/public/assets/img/svg/gitea-colorblind-blueyellow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-boolean-off.svg b/public/assets/img/svg/octicon-boolean-off.svg new file mode 100644 index 0000000000..9dd9cc51e4 --- /dev/null +++ b/public/assets/img/svg/octicon-boolean-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-boolean-on.svg b/public/assets/img/svg/octicon-boolean-on.svg new file mode 100644 index 0000000000..c85a30f87a --- /dev/null +++ b/public/assets/img/svg/octicon-boolean-on.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-compose.svg b/public/assets/img/svg/octicon-compose.svg new file mode 100644 index 0000000000..7bcecef394 --- /dev/null +++ b/public/assets/img/svg/octicon-compose.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-crosshairs.svg b/public/assets/img/svg/octicon-crosshairs.svg new file mode 100644 index 0000000000..1fdd94d252 --- /dev/null +++ b/public/assets/img/svg/octicon-crosshairs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-dice.svg b/public/assets/img/svg/octicon-dice.svg new file mode 100644 index 0000000000..af1531cb90 --- /dev/null +++ b/public/assets/img/svg/octicon-dice.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-exclamation.svg b/public/assets/img/svg/octicon-exclamation.svg new file mode 100644 index 0000000000..30c1791b59 --- /dev/null +++ b/public/assets/img/svg/octicon-exclamation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-file-check.svg b/public/assets/img/svg/octicon-file-check.svg new file mode 100644 index 0000000000..28cee87069 --- /dev/null +++ b/public/assets/img/svg/octicon-file-check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-flowchart.svg b/public/assets/img/svg/octicon-flowchart.svg new file mode 100644 index 0000000000..47aec70de1 --- /dev/null +++ b/public/assets/img/svg/octicon-flowchart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-focus-center.svg b/public/assets/img/svg/octicon-focus-center.svg new file mode 100644 index 0000000000..e6c2e2fb52 --- /dev/null +++ b/public/assets/img/svg/octicon-focus-center.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-git-branch-check.svg b/public/assets/img/svg/octicon-git-branch-check.svg new file mode 100644 index 0000000000..3b9d836776 --- /dev/null +++ b/public/assets/img/svg/octicon-git-branch-check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-graph-bar-horizontal.svg b/public/assets/img/svg/octicon-graph-bar-horizontal.svg new file mode 100644 index 0000000000..e910f3ff93 --- /dev/null +++ b/public/assets/img/svg/octicon-graph-bar-horizontal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-graph-bar-vertical.svg b/public/assets/img/svg/octicon-graph-bar-vertical.svg new file mode 100644 index 0000000000..2761cde321 --- /dev/null +++ b/public/assets/img/svg/octicon-graph-bar-vertical.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-inbox-fill.svg b/public/assets/img/svg/octicon-inbox-fill.svg new file mode 100644 index 0000000000..25052b5b8d --- /dev/null +++ b/public/assets/img/svg/octicon-inbox-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-node.svg b/public/assets/img/svg/octicon-node.svg new file mode 100644 index 0000000000..c31eefb9a4 --- /dev/null +++ b/public/assets/img/svg/octicon-node.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-pencil-ai.svg b/public/assets/img/svg/octicon-pencil-ai.svg new file mode 100644 index 0000000000..3c4587af84 --- /dev/null +++ b/public/assets/img/svg/octicon-pencil-ai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-smiley-frown.svg b/public/assets/img/svg/octicon-smiley-frown.svg new file mode 100644 index 0000000000..40ad10be88 --- /dev/null +++ b/public/assets/img/svg/octicon-smiley-frown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-smiley-frustrated.svg b/public/assets/img/svg/octicon-smiley-frustrated.svg new file mode 100644 index 0000000000..0dc5cddbb7 --- /dev/null +++ b/public/assets/img/svg/octicon-smiley-frustrated.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-smiley-grin.svg b/public/assets/img/svg/octicon-smiley-grin.svg new file mode 100644 index 0000000000..a44f35323c --- /dev/null +++ b/public/assets/img/svg/octicon-smiley-grin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-smiley-neutral.svg b/public/assets/img/svg/octicon-smiley-neutral.svg new file mode 100644 index 0000000000..e08375d2d5 --- /dev/null +++ b/public/assets/img/svg/octicon-smiley-neutral.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-spacing-large.svg b/public/assets/img/svg/octicon-spacing-large.svg new file mode 100644 index 0000000000..6504b7a5af --- /dev/null +++ b/public/assets/img/svg/octicon-spacing-large.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-spacing-medium.svg b/public/assets/img/svg/octicon-spacing-medium.svg new file mode 100644 index 0000000000..29333cf853 --- /dev/null +++ b/public/assets/img/svg/octicon-spacing-medium.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-spacing-small.svg b/public/assets/img/svg/octicon-spacing-small.svg new file mode 100644 index 0000000000..493bd94d6f --- /dev/null +++ b/public/assets/img/svg/octicon-spacing-small.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-split-view.svg b/public/assets/img/svg/octicon-split-view.svg new file mode 100644 index 0000000000..0f6e235e67 --- /dev/null +++ b/public/assets/img/svg/octicon-split-view.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-unwrap.svg b/public/assets/img/svg/octicon-unwrap.svg new file mode 100644 index 0000000000..bac12cc51d --- /dev/null +++ b/public/assets/img/svg/octicon-unwrap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/svg/octicon-vscode.svg b/public/assets/img/svg/octicon-vscode.svg index d226e3a574..04ac8cacd5 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 diff --git a/public/assets/img/svg/octicon-wrap.svg b/public/assets/img/svg/octicon-wrap.svg new file mode 100644 index 0000000000..13247d799d --- /dev/null +++ b/public/assets/img/svg/octicon-wrap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/routers/api/packages/composer/composer.go b/routers/api/packages/composer/composer.go index df04f49d2d..8eb66ca244 100644 --- a/routers/api/packages/composer/composer.go +++ b/routers/api/packages/composer/composer.go @@ -5,12 +5,10 @@ package composer import ( "errors" - "fmt" "io" "net/http" "net/url" "strconv" - "strings" "code.gitea.io/gitea/models/db" packages_model "code.gitea.io/gitea/models/packages" @@ -23,8 +21,6 @@ import ( "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/convert" packages_service "code.gitea.io/gitea/services/packages" - - "github.com/hashicorp/go-version" ) func apiError(ctx *context.Context, status int, obj any) { @@ -193,7 +189,7 @@ func UploadPackage(ctx *context.Context) { } defer buf.Close() - cp, err := composer_module.ParsePackage(buf, buf.Size()) + cp, err := composer_module.ParsePackage(buf, ctx.FormTrim("version")) if err != nil { if errors.Is(err, util.ErrInvalidArgument) { apiError(ctx, http.StatusBadRequest, err) @@ -209,12 +205,9 @@ func UploadPackage(ctx *context.Context) { } if cp.Version == "" { - v, err := version.NewVersion(ctx.FormTrim("version")) - if err != nil { - apiError(ctx, http.StatusBadRequest, composer_module.ErrInvalidVersion) - return - } - cp.Version = v.String() + // the version should be either set in the "composer.json", or as a query parameter "?version=xxx" + apiError(ctx, http.StatusBadRequest, composer_module.ErrInvalidVersion) + return } _, _, err = packages_service.CreatePackageAndAddFile( @@ -235,7 +228,7 @@ func UploadPackage(ctx *context.Context) { }, &packages_service.PackageFileCreationInfo{ PackageFileInfo: packages_service.PackageFileInfo{ - Filename: strings.ToLower(fmt.Sprintf("%s.%s.zip", strings.ReplaceAll(cp.Name, "/", "-"), cp.Version)), + Filename: cp.Filename, }, Creator: ctx.Doer, Data: buf, diff --git a/routers/api/packages/conda/conda.go b/routers/api/packages/conda/conda.go index f496002bb5..8519ae3e08 100644 --- a/routers/api/packages/conda/conda.go +++ b/routers/api/packages/conda/conda.go @@ -148,7 +148,7 @@ func EnumeratePackages(ctx *context.Context) { Timestamp: fileMetadata.Timestamp, Build: fileMetadata.Build, BuildNumber: fileMetadata.BuildNumber, - Dependencies: fileMetadata.Dependencies, + Dependencies: util.SliceNilAsEmpty(fileMetadata.Dependencies), License: versionMetadata.License, LicenseFamily: versionMetadata.LicenseFamily, HashMD5: pfd.Blob.HashMD5, diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go index db81dd13c2..7cf1c36375 100644 --- a/routers/api/packages/container/container.go +++ b/routers/api/packages/container/container.go @@ -290,8 +290,8 @@ func PostBlobsUploads(ctx *context.Context) { Creator: ctx.Doer, }, ); err != nil { - switch err { - case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: + switch { + case errors.Is(err, packages_service.ErrQuotaTotalCount), errors.Is(err, packages_service.ErrQuotaTypeSize), errors.Is(err, packages_service.ErrQuotaTotalSize): apiError(ctx, http.StatusForbidden, err) default: apiError(ctx, http.StatusInternalServerError, err) @@ -439,8 +439,8 @@ func PutBlobsUpload(ctx *context.Context) { Creator: ctx.Doer, }, ); err != nil { - switch err { - case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: + switch { + case errors.Is(err, packages_service.ErrQuotaTotalCount), errors.Is(err, packages_service.ErrQuotaTypeSize), errors.Is(err, packages_service.ErrQuotaTotalSize): apiError(ctx, http.StatusForbidden, err) default: apiError(ctx, http.StatusInternalServerError, err) @@ -592,13 +592,10 @@ func PutManifest(ctx *context.Context) { apiErrorDefined(ctx, namedError) } else if errors.Is(err, container_model.ErrContainerBlobNotExist) { apiErrorDefined(ctx, errBlobUnknown) + } else if errors.Is(err, packages_service.ErrQuotaTotalCount) || errors.Is(err, packages_service.ErrQuotaTypeSize) || errors.Is(err, packages_service.ErrQuotaTotalSize) { + apiError(ctx, http.StatusForbidden, err) } else { - switch err { - case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: - apiError(ctx, http.StatusForbidden, err) - default: - apiError(ctx, http.StatusInternalServerError, err) - } + apiError(ctx, http.StatusInternalServerError, err) } return } diff --git a/routers/api/packages/container/manifest.go b/routers/api/packages/container/manifest.go index de40215aa7..30d591e60a 100644 --- a/routers/api/packages/container/manifest.go +++ b/routers/api/packages/container/manifest.go @@ -10,7 +10,6 @@ import ( "io" "os" "strings" - "time" "code.gitea.io/gitea/models/db" packages_model "code.gitea.io/gitea/models/packages" @@ -83,9 +82,11 @@ type processManifestTxRet struct { } func handleCreateManifestResult(ctx context.Context, err error, mci *manifestCreationInfo, contentStore *packages_module.ContentStore, txRet *processManifestTxRet) (string, error) { - if err != nil && txRet.created && txRet.pb != nil { - if err := contentStore.Delete(packages_module.BlobHash256Key(txRet.pb.HashSHA256)); err != nil { - log.Error("Error deleting package blob from content store: %v", err) + if err != nil { + if txRet.created && txRet.pb != nil { + if err := contentStore.Delete(packages_module.BlobHash256Key(txRet.pb.HashSHA256)); err != nil { + log.Error("Error deleting package blob from content store: %v", err) + } } return "", err } @@ -199,14 +200,14 @@ func processOciImageIndex(ctx context.Context, mci *manifestCreationInfo, buf *p if errors.Is(err, container_model.ErrContainerBlobNotExist) { return errManifestBlobUnknown } - return err + return fmt.Errorf("GetContainerBlob: %w", err) } size, err := packages_model.CalculateFileSize(ctx, &packages_model.PackageFileSearchOptions{ VersionID: pfd.File.VersionID, }) if err != nil { - return err + return fmt.Errorf("CalculateFileSize: %w", err) } metadata.Manifests = append(metadata.Manifests, &container_module.Manifest{ @@ -218,7 +219,7 @@ func processOciImageIndex(ctx context.Context, mci *manifestCreationInfo, buf *p pv, err := createPackageAndVersion(ctx, mci, metadata) if err != nil { - return err + return fmt.Errorf("createPackageAndVersion: %w", err) } txRet.pv = pv @@ -241,7 +242,7 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met if p, err = packages_model.TryInsertPackage(ctx, p); err != nil { if !errors.Is(err, packages_model.ErrDuplicatePackage) { log.Error("Error inserting package: %v", err) - return nil, err + return nil, fmt.Errorf("TryInsertPackage: %w", err) } created = false } @@ -249,7 +250,7 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met if created { if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository, strings.ToLower(mci.Owner.LowerName+"/"+mci.Image)); err != nil { log.Error("Error setting package property: %v", err) - return nil, err + return nil, fmt.Errorf("InsertProperty(PropertyRepository): %w", err) } } @@ -257,9 +258,16 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met metadataJSON, err := json.Marshal(metadata) if err != nil { - return nil, err + return nil, fmt.Errorf("json.Marshal(metadata): %w", err) } + // "docker buildx imagetools create" multi-arch operations: + // {"type":"oci","is_tagged":false,"platform":"unknown/unknown"} + // {"type":"oci","is_tagged":false,"platform":"linux/amd64","layer_creation":["ADD file:9233f6f2237d79659a9521f7e390df217cec49f1a8aa3a12147bbca1956acdb9 in /","CMD [\"/bin/sh\"]"]} + // {"type":"oci","is_tagged":false,"platform":"unknown/unknown"} + // {"type":"oci","is_tagged":false,"platform":"linux/arm64","layer_creation":["ADD file:df53811312284306901fdaaff0a357a4bf40d631e662fe9ce6d342442e494b6c in /","CMD [\"/bin/sh\"]"]} + // {"type":"oci","is_tagged":true,"manifests":[{"platform":"linux/amd64","digest":"sha256:72bb73e706c0dec424d00a1febb21deaf1175a70ead009ad8b159729cfcf5769","size":2819478},{"platform":"linux/arm64","digest":"sha256:9e1426dd084a3221663b85ca1ee99d140c50b153917a5c5604c1f9b78229fd24","size":2716499},{"platform":"unknown/unknown","digest":"sha256:b93f03d0ae11b988243e1b2cd8d29accf5b9670547b7bd8c7d96abecc7283e6e","size":1798},{"platform":"unknown/unknown","digest":"sha256:f034b182ba66366c63a5d195c6dfcd3333c027409c0ac98e55ade36aaa3b2963","size":1798}]} + _pv := &packages_model.PackageVersion{ PackageID: p.ID, CreatorID: mci.Creator.ID, @@ -270,52 +278,43 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met pv, err := packages_model.GetOrInsertVersion(ctx, _pv) if err != nil { if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) { - log.Error("Error inserting package: %v", err) - return nil, err + log.Error("Error GetOrInsertVersion (first try) package: %v", err) + return nil, fmt.Errorf("GetOrInsertVersion: first try: %w", err) } - - if container_module.IsMediaTypeImageIndex(mci.MediaType) { - if pv.CreatedUnix.AsTime().Before(time.Now().Add(-24 * time.Hour)) { - if err = packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil { - return nil, err - } - // keep download count on overwriting - _pv.DownloadCount = pv.DownloadCount - if pv, err = packages_model.GetOrInsertVersion(ctx, _pv); err != nil { - if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) { - log.Error("Error inserting package: %v", err) - return nil, err - } - } - } else { - err = packages_model.UpdateVersion(ctx, &packages_model.PackageVersion{ID: pv.ID, MetadataJSON: _pv.MetadataJSON}) - if err != nil { - return nil, err - } + if err = packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil { + return nil, fmt.Errorf("DeletePackageVersionAndReferences: %w", err) + } + // keep download count on overwriting + _pv.DownloadCount = pv.DownloadCount + pv, err = packages_model.GetOrInsertVersion(ctx, _pv) + if err != nil { + if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) { + log.Error("Error GetOrInsertVersion (second try) package: %v", err) + return nil, fmt.Errorf("GetOrInsertVersion: second try: %w", err) } } } if err := packages_service.CheckCountQuotaExceeded(ctx, mci.Creator, mci.Owner); err != nil { - return nil, err + return nil, fmt.Errorf("CheckCountQuotaExceeded: %w", err) } if mci.IsTagged { if err = packages_model.InsertOrUpdateProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged, ""); err != nil { - return nil, err + return nil, fmt.Errorf("InsertOrUpdateProperty(ManifestTagged): %w", err) } } else { if err = packages_model.DeletePropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged); err != nil { - return nil, err + return nil, fmt.Errorf("DeletePropertiesByName(ManifestTagged): %w", err) } } if err = packages_model.DeletePropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference); err != nil { - return nil, err + return nil, fmt.Errorf("DeletePropertiesByName(ManifestReference): %w", err) } for _, manifest := range metadata.Manifests { if _, err = packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference, manifest.Digest); err != nil { - return nil, err + return nil, fmt.Errorf("InsertProperty(ManifestReference): %w", err) } } diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 6afa651448..abc4e52877 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -480,7 +480,7 @@ func RenameUser(ctx *context.APIContext) { newName := web.GetForm(ctx).(*api.RenameUserOption).NewName // Check if username has been changed - if err := user_service.RenameUser(ctx, ctx.ContextUser, newName); err != nil { + 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) } else { diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index e6238acce0..8e07685759 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -81,6 +81,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/v1/activitypub" "code.gitea.io/gitea/routers/api/v1/admin" @@ -774,7 +775,9 @@ func apiAuth(authMethod auth.Method) func(*context.APIContext) { return func(ctx *context.APIContext) { ar, err := common.AuthShared(ctx.Base, nil, authMethod) if err != nil { - ctx.APIError(http.StatusUnauthorized, err) + msg, ok := auth.ErrAsUserAuthMessage(err) + msg = util.Iif(ok, msg, "invalid username, password or token") + ctx.APIError(http.StatusUnauthorized, msg) return } ctx.Doer = ar.Doer diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go index 08e37e8df4..0c108a933c 100644 --- a/routers/api/v1/org/org.go +++ b/routers/api/v1/org/org.go @@ -340,7 +340,7 @@ func Rename(ctx *context.APIContext) { form := web.GetForm(ctx).(*api.RenameOrgOption) orgUser := ctx.Org.Organization.AsUser() - if err := user_service.RenameUser(ctx, orgUser, form.NewName); err != nil { + 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) } else { diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index a337ed7938..b9060e9cbd 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -897,7 +897,7 @@ func EditBranchProtection(ctx *context.APIContext) { } else { whitelistUsers = protectBranch.WhitelistUserIDs } - if form.ForcePushAllowlistDeployKeys != nil { + if form.ForcePushAllowlistUsernames != nil { forcePushAllowlistUsers, err = user_model.GetUserIDsByNames(ctx, form.ForcePushAllowlistUsernames, false) if err != nil { if user_model.IsErrUserNotExist(err) { diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index ba98263819..ec34d54d22 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -370,11 +370,11 @@ func ReqChangeRepoFileOptionsAndCheck(ctx *context.APIContext) { }, Signoff: commonOpts.Signoff, } - if commonOpts.Dates.Author.IsZero() { - commonOpts.Dates.Author = time.Now() + if changeFileOpts.Dates.Author.IsZero() { + changeFileOpts.Dates.Author = time.Now() } - if commonOpts.Dates.Committer.IsZero() { - commonOpts.Dates.Committer = time.Now() + if changeFileOpts.Dates.Committer.IsZero() { + changeFileOpts.Dates.Committer = time.Now() } ctx.Data["__APIChangeRepoFilesOptions"] = changeFileOpts } diff --git a/routers/api/v1/repo/issue_dependency.go b/routers/api/v1/repo/issue_dependency.go index 1b58beb7b6..b34e325e5d 100644 --- a/routers/api/v1/repo/issue_dependency.go +++ b/routers/api/v1/repo/issue_dependency.go @@ -201,7 +201,7 @@ func CreateIssueDependency(ctx *context.APIContext) { return } - dependencyPerm := getPermissionForRepo(ctx, target.Repo) + dependencyPerm := getPermissionForRepo(ctx, dependency.Repo) if ctx.Written() { return } @@ -262,7 +262,7 @@ func RemoveIssueDependency(ctx *context.APIContext) { return } - dependencyPerm := getPermissionForRepo(ctx, target.Repo) + dependencyPerm := getPermissionForRepo(ctx, dependency.Repo) if ctx.Written() { return } diff --git a/routers/api/v1/repo/release_tags.go b/routers/api/v1/repo/release_tags.go index b5e7d83b2a..8991e201d8 100644 --- a/routers/api/v1/repo/release_tags.go +++ b/routers/api/v1/repo/release_tags.go @@ -7,6 +7,7 @@ import ( "net/http" repo_model "code.gitea.io/gitea/models/repo" + unit_model "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/convert" release_service "code.gitea.io/gitea/services/release" @@ -58,6 +59,13 @@ func GetReleaseByTag(ctx *context.APIContext) { return } + if release.IsDraft { // only the users with write access can see draft releases + if !ctx.IsSigned || !ctx.Repo.CanWrite(unit_model.TypeReleases) { + ctx.APIErrorNotFound() + return + } + } + if err = release.LoadAttributes(ctx); err != nil { ctx.APIErrorInternal(err) return diff --git a/routers/common/middleware.go b/routers/common/middleware.go index 07adee18ce..bfa258b976 100644 --- a/routers/common/middleware.go +++ b/routers/common/middleware.go @@ -5,6 +5,7 @@ package common import ( "fmt" + "log" "net/http" "strings" @@ -107,7 +108,11 @@ func ForwardedHeadersHandler(limit int, trustedProxies []string) func(h http.Han return proxy.ForwardedHeaders(opt) } -func Sessioner() (func(next http.Handler) http.Handler, error) { +func MustInitSessioner() func(next http.Handler) http.Handler { + // TODO: CHI-SESSION-GOB-REGISTER: chi-session has a design problem: it calls gob.Register for "Set" + // But if the server restarts, then the first "Get" will fail to decode the previously stored session data because the structs are not registered yet. + // So each package should make sure their structs are registered correctly during startup for session storage. + middleware, err := session.Sessioner(session.Options{ Provider: setting.SessionConfig.Provider, ProviderConfig: setting.SessionConfig.ProviderConfig, @@ -120,8 +125,7 @@ func Sessioner() (func(next http.Handler) http.Handler, error) { Domain: setting.SessionConfig.Domain, }) if err != nil { - return nil, fmt.Errorf("failed to create session middleware: %w", err) + log.Fatalf("common.Sessioner failed: %v", err) } - - return middleware, nil + return middleware } diff --git a/routers/install/install.go b/routers/install/install.go index 4a9dabac6f..c5acf968bd 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -55,8 +55,8 @@ func getSupportedDbTypeNames() (dbTypeNames []map[string]string) { return dbTypeNames } -// Contexter prepare for rendering installation page -func Contexter() func(next http.Handler) http.Handler { +// installContexter prepare for rendering installation page +func installContexter() func(next http.Handler) http.Handler { rnd := templates.HTMLRenderer() dbTypeNames := getSupportedDbTypeNames() envConfigKeys := setting.CollectEnvConfigKeys() diff --git a/routers/install/routes.go b/routers/install/routes.go index d47c1f61ee..0914c921c0 100644 --- a/routers/install/routes.go +++ b/routers/install/routes.go @@ -8,7 +8,6 @@ import ( "html" "net/http" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" @@ -25,11 +24,8 @@ func Routes() *web.Router { base.Methods("GET, HEAD", "/assets/*", public.FileHandlerFunc()) r := web.NewRouter() - if sessionMid, err := common.Sessioner(); err == nil && sessionMid != nil { - r.Use(sessionMid, Contexter()) - } else { - log.Fatal("common.Sessioner failed: %v", err) - } + r.Use(common.MustInitSessioner(), installContexter()) + r.Get("/", Install) // it must be on the root, because the "install.js" use the window.location to replace the "localhost" AppURL r.Post("/", web.Bind(forms.InstallForm{}), SubmitInstall) r.Get("/post-install", InstallDone) diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 1f22d800a9..a338936151 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -345,7 +345,7 @@ func EditUserPost(ctx *context.Context) { } if form.UserName != "" { - if err := user_service.RenameUser(ctx, u, form.UserName); err != nil { + if err := user_service.RenameUser(ctx, u, form.UserName, ctx.Doer); err != nil { switch { case user_model.IsErrUserIsNotLocal(err): ctx.Data["Err_UserName"] = true diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index f1c155e78f..f7ce5875ca 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -277,8 +277,11 @@ type LinkAccountData struct { GothUser goth.User } +func init() { + gob.Register(LinkAccountData{}) // TODO: CHI-SESSION-GOB-REGISTER +} + func oauth2GetLinkAccountData(ctx *context.Context) *LinkAccountData { - gob.Register(LinkAccountData{}) v, ok := ctx.Session.Get("linkAccountData").(LinkAccountData) if !ok { return nil @@ -287,7 +290,6 @@ func oauth2GetLinkAccountData(ctx *context.Context) *LinkAccountData { } func Oauth2SetLinkAccountData(ctx *context.Context, linkAccountData LinkAccountData) error { - gob.Register(LinkAccountData{}) return updateSession(ctx, nil, map[string]any{ "linkAccountData": linkAccountData, }) diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go index 059cce8281..d524409c41 100644 --- a/routers/web/org/projects.go +++ b/routers/web/org/projects.go @@ -436,6 +436,7 @@ func ViewProject(ctx *context.Context) { ctx.Data["Project"] = project ctx.Data["IssuesMap"] = issuesMap ctx.Data["Columns"] = columns + ctx.Data["Title"] = fmt.Sprintf("%s - %s", project.Title, ctx.ContextUser.DisplayName()) if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { ctx.ServerError("RenderUserOrgHeader", err) diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index fe585b3a00..0e4dab8fb6 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -213,7 +213,7 @@ func SettingsRenamePost(ctx *context.Context) { return } - if err := user_service.RenameUser(ctx, ctx.Org.Organization.AsUser(), newOrgName); err != nil { + if err := user_service.RenameUser(ctx, ctx.Org.Organization.AsUser(), newOrgName, ctx.Doer); err != nil { if user_model.IsErrUserAlreadyExist(err) { ctx.JSONError(ctx.Tr("org.form.name_been_taken", newOrgName)) } else if db.IsErrNameReserved(err) { diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 0383e4ca9e..6bb9a8ae77 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -415,6 +415,8 @@ func Diff(ctx *context.Context) { ctx.ServerError("PostProcessCommitMessage", err) return } + } else if !git.IsErrNotExist(err) { + log.Error("GetNote: %v", err) } pr, _ := issues_model.GetPullRequestByMergedCommit(ctx, ctx.Repo.Repository.ID, commitID) diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index e579b44558..b82c9961c3 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -9,7 +9,6 @@ import ( "encoding/csv" "errors" "fmt" - "html" "io" "net/http" "net/url" @@ -972,30 +971,26 @@ func ExcerptBlob(ctx *context.Context) { ctx.HTTPError(http.StatusInternalServerError, "getExcerptLines") return } - if idxRight > lastRight { - lineText := " " - if rightHunkSize > 0 || leftHunkSize > 0 { - lineText = fmt.Sprintf("@@ -%d,%d +%d,%d @@\n", idxLeft, leftHunkSize, idxRight, rightHunkSize) - } - lineText = html.EscapeString(lineText) - lineSection := &gitdiff.DiffLine{ - Type: gitdiff.DiffLineSection, - Content: lineText, - SectionInfo: &gitdiff.DiffLineSectionInfo{ - Path: filePath, - LastLeftIdx: lastLeft, - LastRightIdx: lastRight, - LeftIdx: idxLeft, - RightIdx: idxRight, - LeftHunkSize: leftHunkSize, - RightHunkSize: rightHunkSize, - }, - } + + newLineSection := &gitdiff.DiffLine{ + Type: gitdiff.DiffLineSection, + SectionInfo: &gitdiff.DiffLineSectionInfo{ + Path: filePath, + LastLeftIdx: lastLeft, + LastRightIdx: lastRight, + LeftIdx: idxLeft, + RightIdx: idxRight, + LeftHunkSize: leftHunkSize, + RightHunkSize: rightHunkSize, + }, + } + if newLineSection.GetExpandDirection() != "" { + newLineSection.Content = fmt.Sprintf("@@ -%d,%d +%d,%d @@\n", idxLeft, leftHunkSize, idxRight, rightHunkSize) switch direction { case "up": - section.Lines = append([]*gitdiff.DiffLine{lineSection}, section.Lines...) + section.Lines = append([]*gitdiff.DiffLine{newLineSection}, section.Lines...) case "down": - section.Lines = append(section.Lines, lineSection) + section.Lines = append(section.Lines, newLineSection) } } diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index c6f5f74e4b..c7b53dcbfb 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -146,7 +146,13 @@ func httpBase(ctx *context.Context) *serviceHandler { // rely on the results of Contexter if !ctx.IsSigned { // TODO: support digit auth - which would be Authorization header with digit - ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`) + if setting.OAuth2.Enabled { + // `Basic realm="Gitea"` tells the GCM to use builtin OAuth2 application: https://github.com/git-ecosystem/git-credential-manager/pull/1442 + ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`) + } else { + // If OAuth2 is disabled, then use another realm to avoid GCM OAuth2 attempt + ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea (Basic Auth)"`) + } ctx.HTTPError(http.StatusUnauthorized) return nil } diff --git a/routers/web/repo/issue_content_history.go b/routers/web/repo/issue_content_history.go index 3602f4ec8a..a56df78163 100644 --- a/routers/web/repo/issue_content_history.go +++ b/routers/web/repo/issue_content_history.go @@ -206,12 +206,11 @@ func SoftDeleteContentHistory(ctx *context.Context) { ctx.NotFound(issues_model.ErrCommentNotExist{}) return } + if history.CommentID != commentID { + ctx.NotFound(issues_model.ErrCommentNotExist{}) + return + } if commentID != 0 { - if history.CommentID != commentID { - ctx.NotFound(issues_model.ErrCommentNotExist{}) - return - } - if comment, err = issues_model.GetCommentByID(ctx, commentID); err != nil { log.Error("can not get comment for issue content history %v. err=%v", historyID, err) return diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 0f9f551e12..4353e00840 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -782,12 +782,16 @@ func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) { // as the viewed information is designed to be loaded only on latest PR // diff and if you're signed in. var reviewState *pull_model.ReviewState + var numViewedFiles int if ctx.IsSigned && isShowAllCommits { reviewState, err = gitdiff.SyncUserSpecificDiff(ctx, ctx.Doer.ID, pull, gitRepo, diff, diffOptions) if err != nil { ctx.ServerError("SyncUserSpecificDiff", err) return } + if reviewState != nil { + numViewedFiles = reviewState.GetViewedFileCount() + } } diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, beforeCommitID, afterCommitID) @@ -796,10 +800,11 @@ func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) { return } ctx.Data["DiffShortStat"] = diffShortStat + ctx.Data["NumViewedFiles"] = numViewedFiles ctx.PageData["prReview"] = map[string]any{ "numberOfFiles": diffShortStat.NumFiles, - "numberOfViewedFiles": diff.NumViewedFiles, + "numberOfViewedFiles": numViewedFiles, } if err = diff.LoadComments(ctx, issue, ctx.Doer, ctx.Data["ShowOutdatedComments"].(bool)); err != nil { @@ -1238,7 +1243,11 @@ func MergePullRequest(ctx *context.Context) { func deleteBranchAfterMergeAndFlashMessage(ctx *context.Context, prID int64) { var fullBranchName string err := repo_service.DeleteBranchAfterMerge(ctx, ctx.Doer, prID, &fullBranchName) - if errTr := util.ErrorAsTranslatable(err); errTr != nil { + if errors.Is(err, util.ErrPermissionDenied) || errors.Is(err, util.ErrNotExist) { + // no need to show error to end users if no permission or branch not exist + log.Debug("DeleteBranchAfterMerge (ignore unnecessary error): %v", err) + return + } else if errTr := util.ErrorAsTranslatable(err); errTr != nil { ctx.Flash.Error(errTr.Translate(ctx.Locale)) return } else if err == nil { diff --git a/routers/web/repo/setting/protected_branch.go b/routers/web/repo/setting/protected_branch.go index c7bde087a2..4374e95340 100644 --- a/routers/web/repo/setting/protected_branch.go +++ b/routers/web/repo/setting/protected_branch.go @@ -73,10 +73,9 @@ func SettingsProtectedBranch(c *context.Context) { c.Data["PageIsSettingsBranches"] = true c.Data["Title"] = c.Locale.TrString("repo.settings.protected_branch") + " - " + rule.RuleName - - users, err := access_model.GetRepoReaders(c, c.Repo.Repository) + users, err := access_model.GetUsersWithUnitAccess(c, c.Repo.Repository, perm.AccessModeRead, unit.TypePullRequests) if err != nil { - c.ServerError("Repo.Repository.GetReaders", err) + c.ServerError("GetUsersWithUnitAccess", err) return } c.Data["Users"] = users diff --git a/routers/web/repo/setting/protected_tag.go b/routers/web/repo/setting/protected_tag.go index 50f5a28c4c..4b560e6f22 100644 --- a/routers/web/repo/setting/protected_tag.go +++ b/routers/web/repo/setting/protected_tag.go @@ -149,9 +149,9 @@ func setTagsContext(ctx *context.Context) error { } ctx.Data["ProtectedTags"] = protectedTags - users, err := access_model.GetRepoReaders(ctx, ctx.Repo.Repository) + users, err := access_model.GetUsersWithUnitAccess(ctx, ctx.Repo.Repository, perm.AccessModeRead, unit.TypePullRequests) if err != nil { - ctx.ServerError("Repo.Repository.GetReaders", err) + ctx.ServerError("GetUsersWithUnitAccess", err) return err } ctx.Data["Users"] = users diff --git a/routers/web/repo/treelist.go b/routers/web/repo/treelist.go index 340b2bc091..8a3ed0a1c9 100644 --- a/routers/web/repo/treelist.go +++ b/routers/web/repo/treelist.go @@ -33,7 +33,7 @@ func TreeList(ctx *context.Context) { ctx.ServerError("ListEntriesRecursiveFast", err) return } - entries.CustomSort(base.NaturalSortLess) + entries.CustomSort(base.NaturalSortCompare) files := make([]string, 0, len(entries)) for _, entry := range entries { diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 1d05a3aa51..09ac33cff4 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -95,6 +95,7 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) (buf []b meta, err := git_model.GetLFSMetaObjectByOid(ctx, repoID, pointer.Oid) if err != nil { // fallback to a plain file + fi.lfsMeta = &pointer log.Warn("Unable to access LFS pointer %s in repo %d: %v", pointer.Oid, repoID, err) return buf, dataRc, fi, nil } @@ -307,7 +308,7 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri ctx.ServerError("ListEntries", err) return nil } - allEntries.CustomSort(base.NaturalSortLess) + allEntries.CustomSort(base.NaturalSortCompare) commitInfoCtx := gocontext.Context(ctx) if timeout > 0 { diff --git a/routers/web/repo/view_file.go b/routers/web/repo/view_file.go index ea3920439d..167cd5f927 100644 --- a/routers/web/repo/view_file.go +++ b/routers/web/repo/view_file.go @@ -92,8 +92,6 @@ func handleFileViewRenderMarkup(ctx *context.Context, filename string, sniffedTy ctx.ServerError("Render", err) return true } - // to prevent iframe from loading third-party url - ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'") return true } @@ -241,14 +239,17 @@ func prepareFileView(ctx *context.Context, entry *git.TreeEntry) { // * IsRenderableXxx: some files are rendered by backend "markup" engine, some are rendered by frontend (pdf, 3d) // * DefaultViewMode: when there is no "display" query parameter, which view mode should be used by default, source or rendered - utf8Reader := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{}) + contentReader := io.MultiReader(bytes.NewReader(buf), dataRc) + if fInfo.st.IsRepresentableAsText() { + contentReader = charset.ToUTF8WithFallbackReader(contentReader, charset.ConvertOpts{}) + } switch { case fInfo.blobOrLfsSize >= setting.UI.MaxDisplayFileSize: ctx.Data["IsFileTooLarge"] = true - case handleFileViewRenderMarkup(ctx, entry.Name(), fInfo.st, buf, utf8Reader): + case handleFileViewRenderMarkup(ctx, entry.Name(), fInfo.st, buf, contentReader): // it also sets ctx.Data["FileContent"] and more ctx.Data["IsMarkup"] = true - case handleFileViewRenderSource(ctx, entry.Name(), attrs, fInfo, utf8Reader): + case handleFileViewRenderSource(ctx, entry.Name(), attrs, fInfo, contentReader): // it also sets ctx.Data["FileContent"] and more ctx.Data["IsDisplayingSource"] = true case handleFileViewRenderImage(ctx, fInfo, buf): diff --git a/routers/web/repo/view_readme.go b/routers/web/repo/view_readme.go index edf38b7892..f1fa5732f0 100644 --- a/routers/web/repo/view_readme.go +++ b/routers/web/repo/view_readme.go @@ -67,7 +67,7 @@ func findReadmeFileInEntries(ctx *context.Context, parentDir string, entries []* for _, entry := range entries { if i, ok := util.IsReadmeFileExtension(entry.Name(), exts...); ok { fullPath := path.Join(parentDir, entry.Name()) - if readmeFiles[i] == nil || base.NaturalSortLess(readmeFiles[i].Name(), entry.Blob().Name()) { + if readmeFiles[i] == nil || base.NaturalSortCompare(readmeFiles[i].Name(), entry.Blob().Name()) < 0 { if entry.IsLink() { res, err := git.EntryFollowLinks(ctx.Repo.Commit, fullPath, entry) if err == nil && (res.TargetEntry.IsExecutable() || res.TargetEntry.IsRegular()) { diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index 289db11a4f..542ac9c731 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -133,7 +133,7 @@ func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte { return nil } defer reader.Close() - content, err := io.ReadAll(reader) + content, err := util.ReadWithLimit(reader, 5*1024*1024) // 5MB should be enough for a wiki page if err != nil { ctx.ServerError("ReadAll", err) return nil @@ -567,7 +567,7 @@ func WikiPages(ctx *context.Context) { ctx.ServerError("ListEntries", err) return } - allEntries.CustomSort(base.NaturalSortLess) + allEntries.CustomSort(base.NaturalSortCompare) entries, _, err := allEntries.GetCommitsInfo(ctx, ctx.Repo.RepoLink, commit, treePath) if err != nil { diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 27b0c83a38..45a6c64f7b 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -75,7 +75,7 @@ func ProfilePost(ctx *context.Context) { ctx.Redirect(setting.AppSubURL + "/user/settings") return } - if err := user_service.RenameUser(ctx, ctx.Doer, form.Name); err != nil { + if err := user_service.RenameUser(ctx, ctx.Doer, form.Name, ctx.Doer); err != nil { switch { case user_model.IsErrUserIsNotLocal(err): ctx.Flash.Error(ctx.Tr("form.username_change_not_local_user")) diff --git a/routers/web/web.go b/routers/web/web.go index c92dc83651..708d9e3ec7 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -267,11 +267,7 @@ func Routes() *web.Router { routes.Get("/ssh_info", misc.SSHInfo) routes.Get("/api/healthz", healthcheck.Check) - if sessionMid, err := common.Sessioner(); err == nil && sessionMid != nil { - mid = append(mid, sessionMid, context.Contexter()) - } else { - log.Fatal("common.Sessioner failed: %v", err) - } + mid = append(mid, common.MustInitSessioner(), context.Contexter()) // Get user from session if logged in. mid = append(mid, webAuth(buildAuthGroup())) diff --git a/services/auth/auth.go b/services/auth/auth.go index fb6612290b..291e78a735 100644 --- a/services/auth/auth.go +++ b/services/auth/auth.go @@ -5,6 +5,7 @@ package auth import ( + "errors" "fmt" "net/http" "regexp" @@ -40,6 +41,20 @@ var globalVars = sync.OnceValue(func() *globalVarsStruct { } }) +type ErrUserAuthMessage string + +func (e ErrUserAuthMessage) Error() string { + return string(e) +} + +func ErrAsUserAuthMessage(err error) (string, bool) { + var msg ErrUserAuthMessage + if errors.As(err, &msg) { + return msg.Error(), true + } + return "", false +} + // Init should be called exactly once when the application starts to allow plugins // to allocate necessary resources func Init() { diff --git a/services/auth/basic.go b/services/auth/basic.go index 6d147deeb1..501924b4df 100644 --- a/services/auth/basic.go +++ b/services/auth/basic.go @@ -5,7 +5,6 @@ package auth import ( - "errors" "net/http" actions_model "code.gitea.io/gitea/models/actions" @@ -146,7 +145,7 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore return nil, err } if hasWebAuthn { - return nil, errors.New("basic authorization is not allowed while WebAuthn enrolled") + return nil, ErrUserAuthMessage("basic authorization is not allowed while WebAuthn enrolled") } if err := validateTOTP(req, u); err != nil { diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index 6005a4744a..4463bcc054 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -105,9 +105,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u } } if source.AttributeAvatar != "" { - if err := user_service.UploadAvatar(ctx, user, sr.Avatar); err != nil { - return user, err - } + _ = user_service.UploadAvatar(ctx, user, sr.Avatar) } } diff --git a/services/auth/source/oauth2/init.go b/services/auth/source/oauth2/init.go index 313f375281..2a165bac85 100644 --- a/services/auth/source/oauth2/init.go +++ b/services/auth/source/oauth2/init.go @@ -22,9 +22,6 @@ import ( var gothRWMutex = sync.RWMutex{} -// UsersStoreKey is the key for the store -const UsersStoreKey = "gitea-oauth2-sessions" - // ProviderHeaderKey is the HTTP header key const ProviderHeaderKey = "gitea-oauth2-provider" @@ -33,7 +30,7 @@ func Init(ctx context.Context) error { // Lock our mutex gothRWMutex.Lock() - gob.Register(&sessions.Session{}) + gob.Register(&sessions.Session{}) // TODO: CHI-SESSION-GOB-REGISTER. FIXME: it seems to be an abuse, why the Session struct itself is stored in session store again? gothic.Store = &SessionsStore{ maxLength: int64(setting.OAuth2.MaxTokenLength), diff --git a/services/convert/convert.go b/services/convert/convert.go index 9f8fff970c..c081aec771 100644 --- a/services/convert/convert.go +++ b/services/convert/convert.go @@ -139,7 +139,7 @@ func getWhitelistEntities[T *user_model.User | *organization.Team](entities []T, // ToBranchProtection convert a ProtectedBranch to api.BranchProtection func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo *repo_model.Repository) *api.BranchProtection { - readers, err := access_model.GetRepoReaders(ctx, repo) + readers, err := access_model.GetUsersWithUnitAccess(ctx, repo, perm.AccessModeRead, unit.TypePullRequests) if err != nil { log.Error("GetRepoReaders: %v", err) } @@ -542,8 +542,9 @@ func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerifi } if verif.SigningUser != nil { commitVerification.Signer = &api.PayloadUser{ - Name: verif.SigningUser.Name, - Email: verif.SigningUser.Email, + UserName: verif.SigningUser.Name, + Name: verif.SigningUser.DisplayName(), + Email: verif.SigningEmail, // Use the email from the signature, not from the user profile } } return commitVerification @@ -720,7 +721,7 @@ func ToAnnotatedTagObject(repo *repo_model.Repository, commit *git.Commit) *api. // ToTagProtection convert a git.ProtectedTag to an api.TagProtection func ToTagProtection(ctx context.Context, pt *git_model.ProtectedTag, repo *repo_model.Repository) *api.TagProtection { - readers, err := access_model.GetRepoReaders(ctx, repo) + readers, err := access_model.GetUsersWithUnitAccess(ctx, repo, perm.AccessModeRead, unit.TypePullRequests) if err != nil { log.Error("GetRepoReaders: %v", err) } diff --git a/services/doctor/actions.go b/services/doctor/actions.go index 28e26c88eb..cd3d19b724 100644 --- a/services/doctor/actions.go +++ b/services/doctor/actions.go @@ -7,12 +7,17 @@ import ( "context" "fmt" + actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" unit_model "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/timeutil" repo_service "code.gitea.io/gitea/services/repository" + + "xorm.io/builder" ) func disableMirrorActionsUnit(ctx context.Context, logger log.Logger, autofix bool) error { @@ -59,6 +64,95 @@ func disableMirrorActionsUnit(ctx context.Context, logger log.Logger, autofix bo return nil } +func fixUnfinishedRunStatus(ctx context.Context, logger log.Logger, autofix bool) error { + total := 0 + inconsistent := 0 + fixed := 0 + + cond := builder.In("status", []actions_model.Status{ + actions_model.StatusWaiting, + actions_model.StatusRunning, + actions_model.StatusBlocked, + }).And(builder.Lt{"updated": timeutil.TimeStampNow().AddDuration(-setting.Actions.ZombieTaskTimeout)}) + + err := db.Iterate( + ctx, + cond, + func(ctx context.Context, run *actions_model.ActionRun) error { + total++ + + jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID) + if err != nil { + return fmt.Errorf("GetRunJobsByRunID: %w", err) + } + expected := actions_model.AggregateJobStatus(jobs) + if expected == run.Status { + return nil + } + + inconsistent++ + logger.Warn("Run %d (repo_id=%d, index=%d) has status %s, expected %s", run.ID, run.RepoID, run.Index, run.Status, expected) + + if !autofix { + return nil + } + + run.Started, run.Stopped = getRunTimestampsFromJobs(run, expected, jobs) + run.Status = expected + + if err := actions_model.UpdateRun(ctx, run, "status", "started", "stopped"); err != nil { + return fmt.Errorf("UpdateRun: %w", err) + } + fixed++ + + return nil + }, + ) + if err != nil { + logger.Critical("Unable to iterate unfinished runs: %v", err) + return err + } + + if inconsistent == 0 { + logger.Info("Checked %d unfinished runs; all statuses are consistent.", total) + return nil + } + + if autofix { + logger.Info("Checked %d unfinished runs; fixed %d of %d runs.", total, fixed, inconsistent) + } else { + logger.Warn("Checked %d unfinished runs; found %d runs need to be fixed", total, inconsistent) + } + + return nil +} + +func getRunTimestampsFromJobs(run *actions_model.ActionRun, newStatus actions_model.Status, jobs actions_model.ActionJobList) (started, stopped timeutil.TimeStamp) { + started = run.Started + if (newStatus.IsRunning() || newStatus.IsDone()) && started.IsZero() { + var earliest timeutil.TimeStamp + for _, job := range jobs { + if job.Started > 0 && (earliest.IsZero() || job.Started < earliest) { + earliest = job.Started + } + } + started = earliest + } + + stopped = run.Stopped + if newStatus.IsDone() && stopped.IsZero() { + var latest timeutil.TimeStamp + for _, job := range jobs { + if job.Stopped > latest { + latest = job.Stopped + } + } + stopped = latest + } + + return started, stopped +} + func init() { Register(&Check{ Title: "Disable the actions unit for all mirrors", @@ -67,4 +161,11 @@ func init() { Run: disableMirrorActionsUnit, Priority: 9, }) + Register(&Check{ + Title: "Fix inconsistent status for unfinished actions runs", + Name: "fix-actions-unfinished-run-status", + IsDefault: false, + Run: fixUnfinishedRunStatus, + Priority: 9, + }) } diff --git a/services/doctor/actions_test.go b/services/doctor/actions_test.go new file mode 100644 index 0000000000..b2fd3d0d55 --- /dev/null +++ b/services/doctor/actions_test.go @@ -0,0 +1,24 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package doctor + +import ( + "testing" + + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/log" + + "github.com/stretchr/testify/assert" +) + +func Test_fixUnfinishedRunStatus(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + fixUnfinishedRunStatus(t.Context(), log.GetLogger(log.DEFAULT), true) + + // check if the run is cancelled by id + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 805}) + assert.Equal(t, actions_model.StatusCancelled, run.Status) +} diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 96aea8308c..4ad06bc04f 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -82,14 +82,34 @@ type DiffLine struct { // DiffLineSectionInfo represents diff line section meta data type DiffLineSectionInfo struct { - Path string - LastLeftIdx int - LastRightIdx int - LeftIdx int - RightIdx int + Path string + + // These line "idx" are 1-based line numbers + // Left/Right refer to the left/right side of the diff: + // + // LastLeftIdx | LastRightIdx + // [up/down expander] @@ hunk info @@ + // LeftIdx | RightIdx + + LastLeftIdx int + LastRightIdx int + LeftIdx int + RightIdx int + + // Hunk sizes of the hidden lines LeftHunkSize int RightHunkSize int + // For example: + // 17 | 31 + // [up/down] @@ -40,23 +54,9 @@ .... + // 40 | 54 + // + // In this case: + // LastLeftIdx = 17, LastRightIdx = 31 + // LeftHunkSize = 23, RightHunkSize = 9 + // LeftIdx = 40, RightIdx = 54 + HiddenCommentIDs []int64 // IDs of hidden comments in this section } @@ -158,13 +178,13 @@ func (d *DiffLine) getBlobExcerptQuery() string { return query } -func (d *DiffLine) getExpandDirection() string { +func (d *DiffLine) GetExpandDirection() string { if d.Type != DiffLineSection || d.SectionInfo == nil || d.SectionInfo.LeftIdx-d.SectionInfo.LastLeftIdx <= 1 || d.SectionInfo.RightIdx-d.SectionInfo.LastRightIdx <= 1 { return "" } if d.SectionInfo.LastLeftIdx <= 0 && d.SectionInfo.LastRightIdx <= 0 { return "up" - } else if d.SectionInfo.RightIdx-d.SectionInfo.LastRightIdx > BlobExcerptChunkSize && d.SectionInfo.RightHunkSize > 0 { + } else if d.SectionInfo.RightIdx-d.SectionInfo.LastRightIdx-1 > BlobExcerptChunkSize && d.SectionInfo.RightHunkSize > 0 { return "updown" } else if d.SectionInfo.LeftHunkSize <= 0 && d.SectionInfo.RightHunkSize <= 0 { return "down" @@ -202,13 +222,13 @@ func (d *DiffLine) RenderBlobExcerptButtons(fileNameHash string, data *DiffBlobE content += htmlutil.HTMLFormat(`%d`, tooltip, len(d.SectionInfo.HiddenCommentIDs)) } - expandDirection := d.getExpandDirection() - if expandDirection == "up" || expandDirection == "updown" { - content += makeButton("up", "octicon-fold-up") - } + expandDirection := d.GetExpandDirection() if expandDirection == "updown" || expandDirection == "down" { content += makeButton("down", "octicon-fold-down") } + if expandDirection == "up" || expandDirection == "updown" { + content += makeButton("up", "octicon-fold-up") + } if expandDirection == "single" { content += makeButton("single", "octicon-fold") } @@ -520,10 +540,9 @@ func getCommitFileLineCountAndLimitedContent(commit *git.Commit, filePath string // Diff represents a difference between two git trees. type Diff struct { - Start, End string - Files []*DiffFile - IsIncomplete bool - NumViewedFiles int // user-specific + Start, End string + Files []*DiffFile + IsIncomplete bool } // LoadComments loads comments into each line @@ -1412,7 +1431,6 @@ outer: // Check whether the file has already been viewed if fileViewedState == pull_model.Viewed { diffFile.IsViewed = true - diff.NumViewedFiles++ } } diff --git a/services/gitdiff/gitdiff_test.go b/services/gitdiff/gitdiff_test.go index 51fb9b58d6..721ae0dfc7 100644 --- a/services/gitdiff/gitdiff_test.go +++ b/services/gitdiff/gitdiff_test.go @@ -983,3 +983,126 @@ func TestDiffLine_RenderBlobExcerptButtons(t *testing.T) { }) } } + +func TestDiffLine_GetExpandDirection(t *testing.T) { + cases := []struct { + name string + diffLine *DiffLine + direction string + }{ + { + name: "NotSectionLine", + diffLine: &DiffLine{Type: DiffLineAdd, SectionInfo: &DiffLineSectionInfo{}}, + direction: "", + }, + { + name: "NilSectionInfo", + diffLine: &DiffLine{Type: DiffLineSection, SectionInfo: nil}, + direction: "", + }, + { + name: "NoHiddenLines", + // last block stops at line 100, next block starts at line 101, so no hidden lines, no expansion. + diffLine: &DiffLine{ + Type: DiffLineSection, + SectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 100, + LastLeftIdx: 100, + RightIdx: 101, + LeftIdx: 101, + }, + }, + direction: "", + }, + { + name: "FileHead", + diffLine: &DiffLine{ + Type: DiffLineSection, + SectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 0, // LastXxxIdx = 0 means this is the first section in the file. + LastLeftIdx: 0, + RightIdx: 1, + LeftIdx: 1, + }, + }, + direction: "", + }, + { + name: "FileHeadHiddenLines", + diffLine: &DiffLine{ + Type: DiffLineSection, + SectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 0, + LastLeftIdx: 0, + RightIdx: 101, + LeftIdx: 101, + }, + }, + direction: "up", + }, + { + name: "HiddenSingleHunk", + diffLine: &DiffLine{ + Type: DiffLineSection, + SectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 100, + LastLeftIdx: 100, + RightIdx: 102, + LeftIdx: 102, + RightHunkSize: 1234, // non-zero dummy value + LeftHunkSize: 5678, // non-zero dummy value + }, + }, + direction: "single", + }, + { + name: "HiddenSingleFullHunk", + // the hidden lines can exactly fit into one hunk + diffLine: &DiffLine{ + Type: DiffLineSection, + SectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 100, + LastLeftIdx: 100, + RightIdx: 100 + BlobExcerptChunkSize + 1, + LeftIdx: 100 + BlobExcerptChunkSize + 1, + RightHunkSize: 1234, // non-zero dummy value + LeftHunkSize: 5678, // non-zero dummy value + }, + }, + direction: "single", + }, + { + name: "HiddenUpDownHunks", + diffLine: &DiffLine{ + Type: DiffLineSection, + SectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 100, + LastLeftIdx: 100, + RightIdx: 100 + BlobExcerptChunkSize + 2, + LeftIdx: 100 + BlobExcerptChunkSize + 2, + RightHunkSize: 1234, // non-zero dummy value + LeftHunkSize: 5678, // non-zero dummy value + }, + }, + direction: "updown", + }, + { + name: "FileTail", + diffLine: &DiffLine{ + Type: DiffLineSection, + SectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 100, + LastLeftIdx: 100, + RightIdx: 102, + LeftIdx: 102, + RightHunkSize: 0, + LeftHunkSize: 0, + }, + }, + direction: "down", + }, + } + for _, c := range cases { + assert.Equal(t, c.direction, c.diffLine.GetExpandDirection(), "case %s expected direction: %s", c.name, c.direction) + } +} diff --git a/services/issue/issue.go b/services/issue/issue.go index 6ac8bdc95a..3a09fa8fce 100644 --- a/services/issue/issue.go +++ b/services/issue/issue.go @@ -270,16 +270,9 @@ func deleteIssue(ctx context.Context, issue *issues_model.Issue) ([]string, erro return nil, err } - // update the total issue numbers - if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, false); err != nil { + if err := issues_model.DecrRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true, issue.IsClosed); err != nil { return nil, err } - // if the issue is closed, update the closed issue numbers - if issue.IsClosed { - if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true); err != nil { - return nil, err - } - } if err := issues_model.UpdateMilestoneCounters(ctx, issue.MilestoneID); err != nil { return nil, fmt.Errorf("error updating counters for milestone id %d: %w", diff --git a/services/issue/template.go b/services/issue/template.go index 4b0f1aa987..99977c67cf 100644 --- a/services/issue/template.go +++ b/services/issue/template.go @@ -5,7 +5,6 @@ package issue import ( "fmt" - "io" "net/url" "path" "strings" @@ -15,6 +14,7 @@ import ( "code.gitea.io/gitea/modules/issue/template" "code.gitea.io/gitea/modules/log" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" "gopkg.in/yaml.v3" ) @@ -65,7 +65,7 @@ func GetTemplateConfig(gitRepo *git.Repository, path string, commit *git.Commit) defer reader.Close() - configContent, err := io.ReadAll(reader) + configContent, err := util.ReadWithLimit(reader, 1024*1024) if err != nil { return GetDefaultTemplateConfig(), err } diff --git a/services/oauth2_provider/access_token.go b/services/oauth2_provider/access_token.go index dce4ac765b..3a77c86d9e 100644 --- a/services/oauth2_provider/access_token.go +++ b/services/oauth2_provider/access_token.go @@ -112,8 +112,12 @@ func NewJwtRegisteredClaimsFromUser(clientID string, grantUserID int64, exp *jwt // to retrieve the configuration information. This MUST also be identical to the "iss" Claim value in ID Tokens issued from this Issuer. // * https://accounts.google.com/.well-known/openid-configuration // * https://github.com/login/oauth/.well-known/openid-configuration + issuer := setting.OAuth2.JWTClaimIssuer + if issuer == "" { + issuer = strings.TrimSuffix(setting.AppURL, "/") + } return jwt.RegisteredClaims{ - Issuer: strings.TrimSuffix(setting.AppURL, "/"), + Issuer: issuer, Audience: []string{clientID}, Subject: strconv.FormatInt(grantUserID, 10), ExpiresAt: exp, diff --git a/services/pull/merge.go b/services/pull/merge.go index 9c7e09a227..88e30c6832 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -403,6 +403,7 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use pr.BaseRepo, pr.BaseRepo.Name, pr.ID, + pr.Index, ) mergeCtx.env = append(mergeCtx.env, repo_module.EnvPushTrigger+"="+string(pushTrigger)) @@ -546,11 +547,15 @@ var escapedSymbols = regexp.MustCompile(`([*[?! \\])`) // IsUserAllowedToMerge check if user is allowed to merge PR with given permissions and branch protections func IsUserAllowedToMerge(ctx context.Context, pr *issues_model.PullRequest, p access_model.Permission, user *user_model.User) (bool, error) { + return isUserAllowedToMergeInRepoBranch(ctx, pr.BaseRepoID, pr.BaseBranch, p, user) +} + +func isUserAllowedToMergeInRepoBranch(ctx context.Context, repoID int64, branch string, p access_model.Permission, user *user_model.User) (bool, error) { if user == nil { return false, nil } - pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) + pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repoID, branch) if err != nil { return false, err } diff --git a/services/pull/merge_squash.go b/services/pull/merge_squash.go index 84bd67c445..b5f2a4deff 100644 --- a/services/pull/merge_squash.go +++ b/services/pull/merge_squash.go @@ -71,7 +71,8 @@ func doMergeStyleSquash(ctx *mergeContext, message string) error { } cmdCommit := gitcmd.NewCommand("commit"). AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email). - AddOptionFormat("--message=%s", message) + AddOptionFormat("--message=%s", message). + AddArguments("--allow-empty") if ctx.signKey == nil { cmdCommit.AddArguments("--no-gpg-sign") } else { diff --git a/services/pull/pull.go b/services/pull/pull.go index 06cc4cafe6..ba299bb8d6 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -13,6 +13,7 @@ import ( "regexp" "strings" "time" + "unicode/utf8" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" @@ -867,51 +868,53 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ stringBuilder := strings.Builder{} if !setting.Repository.PullRequest.PopulateSquashCommentWithCommitMessages { + // use PR's title and description as squash commit message message := strings.TrimSpace(pr.Issue.Content) stringBuilder.WriteString(message) if stringBuilder.Len() > 0 { stringBuilder.WriteRune('\n') if !commitMessageTrailersPattern.MatchString(message) { + // TODO: this trailer check doesn't work with the separator line added below for the co-authors stringBuilder.WriteRune('\n') } } - } - - // commits list is in reverse chronological order - first := true - for i := len(commits) - 1; i >= 0; i-- { - commit := commits[i] - - if setting.Repository.PullRequest.PopulateSquashCommentWithCommitMessages { - maxSize := setting.Repository.PullRequest.DefaultMergeMessageSize - if maxSize < 0 || stringBuilder.Len() < maxSize { - var toWrite []byte - if first { - first = false - toWrite = []byte(strings.TrimPrefix(commit.CommitMessage, pr.Issue.Title)) - } else { - toWrite = []byte(commit.CommitMessage) - } - - if len(toWrite) > maxSize-stringBuilder.Len() && maxSize > -1 { - toWrite = append(toWrite[:maxSize-stringBuilder.Len()], "..."...) - } - if _, err := stringBuilder.Write(toWrite); err != nil { - log.Error("Unable to write commit message Error: %v", err) - return "" - } + } else { + // use PR's commit messages as squash commit message + // commits list is in reverse chronological order + maxMsgSize := setting.Repository.PullRequest.DefaultMergeMessageSize + for i := len(commits) - 1; i >= 0; i-- { + commit := commits[i] + msg := strings.TrimSpace(commit.CommitMessage) + if msg == "" { + continue + } - if _, err := stringBuilder.WriteRune('\n'); err != nil { - log.Error("Unable to write commit message Error: %v", err) - return "" + // This format follows GitHub's squash commit message style, + // even if there are other "* " in the commit message body, they are written as-is. + // Maybe, ideally, we should indent those lines too. + _, _ = fmt.Fprintf(&stringBuilder, "* %s\n\n", msg) + if maxMsgSize > 0 && stringBuilder.Len() >= maxMsgSize { + tmp := stringBuilder.String() + wasValidUtf8 := utf8.ValidString(tmp) + tmp = tmp[:maxMsgSize] + "..." + if wasValidUtf8 { + // If the message was valid UTF-8 before truncation, ensure it remains valid after truncation + // For non-utf8 messages, we can't do much about it, end users should use utf-8 as much as possible + tmp = strings.ToValidUTF8(tmp, "") } + stringBuilder.Reset() + stringBuilder.WriteString(tmp) + break } } + } + // collect co-authors + for _, commit := range commits { authorString := commit.Author.String() if uniqueAuthors.Add(authorString) && authorString != posterSig { // Compare use account as well to avoid adding the same author multiple times - // times when email addresses are private or multiple emails are used. + // when email addresses are private or multiple emails are used. commitUser, _ := user_model.GetUserByEmail(ctx, commit.Author.Email) if commitUser == nil || commitUser.ID != pr.Issue.Poster.ID { authors = append(authors, authorString) @@ -919,12 +922,12 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ } } - // Consider collecting the remaining authors + // collect the remaining authors if limit >= 0 && setting.Repository.PullRequest.DefaultMergeMessageAllAuthors { skip := limit limit = 30 for { - commits, err := gitRepo.CommitsBetweenLimit(headCommit, mergeBase, limit, skip) + commits, err = gitRepo.CommitsBetweenLimit(headCommit, mergeBase, limit, skip) if err != nil { log.Error("Unable to get commits between: %s %s Error: %v", pr.HeadBranch, pr.MergeBase, err) return "" @@ -945,19 +948,15 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ } } + if stringBuilder.Len() > 0 && len(authors) > 0 { + // TODO: this separator line doesn't work with the trailer check (commitMessageTrailersPattern) above + stringBuilder.WriteString("---------\n\n") + } + for _, author := range authors { - if _, err := stringBuilder.WriteString("Co-authored-by: "); err != nil { - log.Error("Unable to write to string builder Error: %v", err) - return "" - } - if _, err := stringBuilder.WriteString(author); err != nil { - log.Error("Unable to write to string builder Error: %v", err) - return "" - } - if _, err := stringBuilder.WriteRune('\n'); err != nil { - log.Error("Unable to write to string builder Error: %v", err) - return "" - } + stringBuilder.WriteString("Co-authored-by: ") + stringBuilder.WriteString(author) + stringBuilder.WriteRune('\n') } return stringBuilder.String() diff --git a/services/pull/update.go b/services/pull/update.go index 436e3b52a6..462bbec55a 100644 --- a/services/pull/update.go +++ b/services/pull/update.go @@ -101,11 +101,11 @@ func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model. } // IsUserAllowedToUpdate check if user is allowed to update PR with given permissions and branch protections +// update PR means send new commits to PR head branch from base branch func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest, user *user_model.User) (mergeAllowed, rebaseAllowed bool, err error) { if pull.Flow == issues_model.PullRequestFlowAGit { return false, false, nil } - if user == nil { return false, false, nil } @@ -121,54 +121,46 @@ func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest, return false, false, err } - pr := &issues_model.PullRequest{ - HeadRepoID: pull.BaseRepoID, - HeadRepo: pull.BaseRepo, - BaseRepoID: pull.HeadRepoID, - BaseRepo: pull.HeadRepo, - HeadBranch: pull.BaseBranch, - BaseBranch: pull.HeadBranch, - } - - pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) - if err != nil { - return false, false, err - } - - if err := pr.LoadBaseRepo(ctx); err != nil { - return false, false, err - } - prUnit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests) - if err != nil { + // 1. check base repository's AllowRebaseUpdate configuration + // it is a config in base repo but controls the head (fork) repo's "Update" behavior + { + prBaseUnit, err := pull.BaseRepo.GetUnit(ctx, unit.TypePullRequests) if repo_model.IsErrUnitTypeNotExist(err) { - return false, false, nil + return false, false, nil // the PR unit is disabled in base repo + } else if err != nil { + return false, false, fmt.Errorf("get base repo unit: %v", err) } - log.Error("pr.BaseRepo.GetUnit(unit.TypePullRequests): %v", err) - return false, false, err + rebaseAllowed = prBaseUnit.PullRequestsConfig().AllowRebaseUpdate } - rebaseAllowed = prUnit.PullRequestsConfig().AllowRebaseUpdate - - // If branch protected, disable rebase unless user is whitelisted to force push (which extends regular push) - if pb != nil { - pb.Repo = pull.BaseRepo - if !pb.CanUserForcePush(ctx, user) { - rebaseAllowed = false + // 2. check head branch protection whether rebase is allowed, if pb not found then rebase depends on the above setting + { + pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pull.HeadRepoID, pull.HeadBranch) + if err != nil { + return false, false, err + } + // If branch protected, disable rebase unless user is whitelisted to force push (which extends regular push) + if pb != nil { + pb.Repo = pull.HeadRepo + rebaseAllowed = rebaseAllowed && pb.CanUserForcePush(ctx, user) } } + // 3. check whether user has write access to head branch baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, pull.BaseRepo, user) if err != nil { return false, false, err } - mergeAllowed, err = IsUserAllowedToMerge(ctx, pr, headRepoPerm, user) + mergeAllowed, err = isUserAllowedToMergeInRepoBranch(ctx, pull.HeadRepoID, pull.HeadBranch, headRepoPerm, user) if err != nil { return false, false, err } + // 4. if the pull creator allows maintainer to edit, it means the write permissions of the head branch has been + // granted to the user with write permission of the base repository if pull.AllowMaintainerEdit { - mergeAllowedMaintainer, err := IsUserAllowedToMerge(ctx, pr, baseRepoPerm, user) + mergeAllowedMaintainer, err := isUserAllowedToMergeInRepoBranch(ctx, pull.BaseRepoID, pull.BaseBranch, baseRepoPerm, user) if err != nil { return false, false, err } @@ -176,6 +168,9 @@ func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest, mergeAllowed = mergeAllowed || mergeAllowedMaintainer } + // if merge is not allowed, rebase is also not allowed + rebaseAllowed = rebaseAllowed && mergeAllowed + return mergeAllowed, rebaseAllowed, nil } diff --git a/services/pull/update_rebase.go b/services/pull/update_rebase.go index e6845f6b14..6a70c03467 100644 --- a/services/pull/update_rebase.go +++ b/services/pull/update_rebase.go @@ -80,6 +80,7 @@ func updateHeadByRebaseOnToBase(ctx context.Context, pr *issues_model.PullReques pr.HeadRepo, pr.HeadRepo.Name, pr.ID, + pr.Index, )). WithDir(mergeCtx.tmpBasePath). WithStdout(mergeCtx.outbuf). diff --git a/services/release/release.go b/services/release/release.go index 29df66e5e6..4f121bf6d3 100644 --- a/services/release/release.go +++ b/services/release/release.go @@ -361,7 +361,7 @@ func DeleteReleaseByID(ctx context.Context, repo *repo_model.Repository, rel *re if err != nil { return fmt.Errorf("GetProtectedTags: %w", err) } - isAllowed, err := git_model.IsUserAllowedToControlTag(ctx, protectedTags, rel.TagName, rel.PublisherID) + isAllowed, err := git_model.IsUserAllowedToControlTag(ctx, protectedTags, rel.TagName, doer.ID) if err != nil { return err } diff --git a/services/repository/avatar.go b/services/repository/avatar.go index 79da629aa6..7ab6badfc3 100644 --- a/services/repository/avatar.go +++ b/services/repository/avatar.go @@ -21,7 +21,7 @@ import ( func UploadAvatar(ctx context.Context, repo *repo_model.Repository, data []byte) error { avatarData, err := avatar.ProcessAvatarImage(data) if err != nil { - return err + return fmt.Errorf("UploadAvatar: failed to process repo avatar image: %w", err) } newAvatar := avatar.HashAvatar(repo.ID, data) @@ -36,19 +36,19 @@ func UploadAvatar(ctx context.Context, repo *repo_model.Repository, data []byte) // Then repo will be removed - only it avatar file will be removed repo.Avatar = newAvatar if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "avatar"); err != nil { - return fmt.Errorf("UploadAvatar: Update repository avatar: %w", err) + return fmt.Errorf("UploadAvatar: failed to update repository avatar: %w", err) } if err := storage.SaveFrom(storage.RepoAvatars, repo.CustomAvatarRelativePath(), func(w io.Writer) error { _, err := w.Write(avatarData) return err }); err != nil { - return fmt.Errorf("UploadAvatar %s failed: Failed to remove old repo avatar %s: %w", repo.RelativePath(), newAvatar, err) + return fmt.Errorf("UploadAvatar: failed to save repo avatar %s: %w", newAvatar, err) } if len(oldAvatarPath) > 0 { if err := storage.RepoAvatars.Delete(oldAvatarPath); err != nil { - return fmt.Errorf("UploadAvatar: Failed to remove old repo avatar %s: %w", oldAvatarPath, err) + return fmt.Errorf("UploadAvatar: failed to remove old repo avatar %s: %w", oldAvatarPath, err) } } return nil diff --git a/services/repository/generate.go b/services/repository/generate.go index 062c6f4fb1..caf15265a0 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -7,7 +7,9 @@ import ( "bufio" "bytes" "context" + "errors" "fmt" + "io/fs" "os" "path/filepath" "regexp" @@ -138,31 +140,37 @@ func (gt *giteaTemplateFileMatcher) Match(s string) bool { return false } -func readGiteaTemplateFile(tmpDir string) (*giteaTemplateFileMatcher, error) { - localPath := filepath.Join(tmpDir, ".gitea", "template") - if _, err := os.Stat(localPath); os.IsNotExist(err) { - return nil, nil - } else if err != nil { +func readLocalTmpRepoFileContent(localPath string, limit int) ([]byte, error) { + ok, err := util.IsRegularFile(localPath) + if err != nil { return nil, err + } else if !ok { + return nil, fs.ErrNotExist } - content, err := os.ReadFile(localPath) + f, err := os.Open(localPath) if err != nil { return nil, err } + defer f.Close() + + return util.ReadWithLimit(f, limit) +} +func readGiteaTemplateFile(tmpDir string) (*giteaTemplateFileMatcher, error) { + localPath := filepath.Join(tmpDir, ".gitea", "template") + content, err := readLocalTmpRepoFileContent(localPath, 1024*1024) + if err != nil { + return nil, err + } return newGiteaTemplateFileMatcher(localPath, content), nil } func substGiteaTemplateFile(ctx context.Context, tmpDir, tmpDirSubPath string, templateRepo, generateRepo *repo_model.Repository) error { tmpFullPath := filepath.Join(tmpDir, tmpDirSubPath) - if ok, err := util.IsRegularFile(tmpFullPath); !ok { - return err - } - - content, err := os.ReadFile(tmpFullPath) + content, err := readLocalTmpRepoFileContent(tmpFullPath, 1024*1024) if err != nil { - return err + return util.Iif(errors.Is(err, fs.ErrNotExist), nil, err) } if err := util.Remove(tmpFullPath); err != nil { return err @@ -172,7 +180,7 @@ func substGiteaTemplateFile(ctx context.Context, tmpDir, tmpDirSubPath string, t substSubPath := filepath.Clean(filePathSanitize(generateExpansion(ctx, tmpDirSubPath, templateRepo, generateRepo))) newLocalPath := filepath.Join(tmpDir, substSubPath) regular, err := util.IsRegularFile(newLocalPath) - if canWrite := regular || os.IsNotExist(err); !canWrite { + if canWrite := regular || errors.Is(err, fs.ErrNotExist); !canWrite { return nil } if err := os.MkdirAll(filepath.Dir(newLocalPath), 0o755); err != nil { @@ -242,15 +250,15 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r // Variable expansion fileMatcher, err := readGiteaTemplateFile(tmpDir) - if err != nil { - return fmt.Errorf("readGiteaTemplateFile: %w", err) - } - - if fileMatcher != nil { + if err == nil { err = processGiteaTemplateFile(ctx, tmpDir, templateRepo, generateRepo, fileMatcher) if err != nil { - return err + return fmt.Errorf("processGiteaTemplateFile: %w", err) } + } else if errors.Is(err, fs.ErrNotExist) { + log.Debug("skip processing repo template files: no available .gitea/template") + } else { + return fmt.Errorf("readGiteaTemplateFile: %w", err) } if err = git.InitRepository(ctx, tmpDir, false, templateRepo.ObjectFormatName); err != nil { diff --git a/services/repository/generate_test.go b/services/repository/generate_test.go index 19b84c7bde..9c01911ded 100644 --- a/services/repository/generate_test.go +++ b/services/repository/generate_test.go @@ -4,6 +4,7 @@ package repository import ( + "io/fs" "os" "path/filepath" "testing" @@ -175,6 +176,31 @@ func TestProcessGiteaTemplateFile(t *testing.T) { // subst from a link, skip, and the target is unchanged assertSymLink("subst-${TEMPLATE_NAME}-from-link", tmpDir+"/sub/link-target") } + + { + templateFilePath := tmpDir + "/.gitea/template" + + _ = os.Remove(templateFilePath) + _, err := os.Lstat(templateFilePath) + require.ErrorIs(t, err, fs.ErrNotExist) + _, err = readGiteaTemplateFile(tmpDir) // no template file + require.ErrorIs(t, err, fs.ErrNotExist) + + _ = os.WriteFile(templateFilePath+".target", []byte("test-data-target"), 0o644) + _ = os.Symlink(templateFilePath+".target", templateFilePath) + content, _ := os.ReadFile(templateFilePath) + require.Equal(t, "test-data-target", string(content)) + _, err = readGiteaTemplateFile(tmpDir) // symlinked template file + require.ErrorIs(t, err, fs.ErrNotExist) + + _ = os.Remove(templateFilePath) + _ = os.WriteFile(templateFilePath, []byte("test-data-regular"), 0o644) + content, _ = os.ReadFile(templateFilePath) + require.Equal(t, "test-data-regular", string(content)) + fm, err := readGiteaTemplateFile(tmpDir) // regular template file + require.NoError(t, err) + assert.Len(t, fm.globs, 1) + } } func TestTransformers(t *testing.T) { diff --git a/services/user/avatar.go b/services/user/avatar.go index df188e5adc..6a43681e9e 100644 --- a/services/user/avatar.go +++ b/services/user/avatar.go @@ -21,21 +21,21 @@ import ( func UploadAvatar(ctx context.Context, u *user_model.User, data []byte) error { avatarData, err := avatar.ProcessAvatarImage(data) if err != nil { - return err + return fmt.Errorf("UploadAvatar: failed to process user avatar image: %w", err) } return db.WithTx(ctx, func(ctx context.Context) error { u.UseCustomAvatar = true u.Avatar = avatar.HashAvatar(u.ID, data) if err = user_model.UpdateUserCols(ctx, u, "use_custom_avatar", "avatar"); err != nil { - return fmt.Errorf("updateUser: %w", err) + return fmt.Errorf("UploadAvatar: failed to update user avatar: %w", err) } if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error { _, err := w.Write(avatarData) return err }); err != nil { - return fmt.Errorf("Failed to create dir %s: %w", u.CustomAvatarRelativePath(), err) + return fmt.Errorf("UploadAvatar: failed to save user avatar %s: %w", u.CustomAvatarRelativePath(), err) } return nil diff --git a/services/user/user.go b/services/user/user.go index d8abf199c3..8e42fa3ccd 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -31,17 +31,15 @@ import ( ) // RenameUser renames a user -func RenameUser(ctx context.Context, u *user_model.User, newUserName string) error { +func RenameUser(ctx context.Context, u *user_model.User, newUserName string, doer *user_model.User) error { if newUserName == u.Name { return nil } - // Non-local users are not allowed to change their username. - if !u.IsOrganization() && !u.IsLocal() { - return user_model.ErrUserIsNotLocal{ - UID: u.ID, - Name: u.Name, - } + // Non-local users are not allowed to change their own username, but admins are + isExternalUser := !u.IsOrganization() && !u.IsLocal() + if isExternalUser && !doer.IsAdmin { + return user_model.ErrUserIsNotLocal{UID: u.ID, Name: u.Name} } if err := user_model.IsUsableUsername(newUserName); err != nil { diff --git a/services/user/user_test.go b/services/user/user_test.go index 48852b4cb9..25e8ee7b2f 100644 --- a/services/user/user_test.go +++ b/services/user/user_test.go @@ -20,6 +20,7 @@ import ( org_service "code.gitea.io/gitea/services/org" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMain(m *testing.M) { @@ -101,23 +102,31 @@ func TestRenameUser(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 21}) - t.Run("Non-Local", func(t *testing.T) { - u := &user_model.User{ - Type: user_model.UserTypeIndividual, - LoginType: auth.OAuth2, + t.Run("External user", func(t *testing.T) { + adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1, IsAdmin: true}) + externalUser := &user_model.User{ + Name: "external_user", + Email: "external_user@gitea.io", + LoginType: auth.LDAP, } - assert.ErrorIs(t, RenameUser(t.Context(), u, "user_rename"), user_model.ErrUserIsNotLocal{}) + require.NoError(t, user_model.CreateUser(t.Context(), externalUser, &user_model.Meta{})) + + err := RenameUser(t.Context(), externalUser, externalUser.Name+"_changed", externalUser) + assert.True(t, user_model.IsErrUserIsNotLocal(err), "external user is not allowed to rename themselves") + + err = RenameUser(t.Context(), externalUser, externalUser.Name+"_changed", adminUser) + assert.NoError(t, err, "admin can rename external user") }) t.Run("Same username", func(t *testing.T) { - assert.NoError(t, RenameUser(t.Context(), user, user.Name)) + assert.NoError(t, RenameUser(t.Context(), user, user.Name, user)) }) t.Run("Non usable username", func(t *testing.T) { usernames := []string{"--diff", ".well-known", "gitea-actions", "aaa.atom", "aa.png"} for _, username := range usernames { assert.Error(t, user_model.IsUsableUsername(username), "non-usable username: %s", username) - assert.Error(t, RenameUser(t.Context(), user, username), "non-usable username: %s", username) + assert.Error(t, RenameUser(t.Context(), user, username, user), "non-usable username: %s", username) } }) @@ -126,7 +135,7 @@ func TestRenameUser(t *testing.T) { unittest.AssertNotExistsBean(t, &user_model.User{ID: user.ID, Name: caps}) unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, OwnerName: user.Name}) - assert.NoError(t, RenameUser(t.Context(), user, caps)) + assert.NoError(t, RenameUser(t.Context(), user, caps, user)) unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: user.ID, Name: caps}) unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, OwnerName: caps}) @@ -135,17 +144,17 @@ func TestRenameUser(t *testing.T) { t.Run("Already exists", func(t *testing.T) { existUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - assert.ErrorIs(t, RenameUser(t.Context(), user, existUser.Name), user_model.ErrUserAlreadyExist{Name: existUser.Name}) - assert.ErrorIs(t, RenameUser(t.Context(), user, existUser.LowerName), user_model.ErrUserAlreadyExist{Name: existUser.LowerName}) + assert.ErrorIs(t, RenameUser(t.Context(), user, existUser.Name, user), user_model.ErrUserAlreadyExist{Name: existUser.Name}) + assert.ErrorIs(t, RenameUser(t.Context(), user, existUser.LowerName, user), user_model.ErrUserAlreadyExist{Name: existUser.LowerName}) newUsername := fmt.Sprintf("uSEr%d", existUser.ID) - assert.ErrorIs(t, RenameUser(t.Context(), user, newUsername), user_model.ErrUserAlreadyExist{Name: newUsername}) + assert.ErrorIs(t, RenameUser(t.Context(), user, newUsername, user), user_model.ErrUserAlreadyExist{Name: newUsername}) }) t.Run("Normal", func(t *testing.T) { oldUsername := user.Name newUsername := "User_Rename" - assert.NoError(t, RenameUser(t.Context(), user, newUsername)) + assert.NoError(t, RenameUser(t.Context(), user, newUsername, user)) unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: user.ID, Name: newUsername, LowerName: strings.ToLower(newUsername)}) redirectUID, err := user_model.LookupUserRedirect(t.Context(), oldUsername) diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go index b6611a3576..58fba9f68d 100644 --- a/services/webhook/deliver.go +++ b/services/webhook/deliver.go @@ -30,6 +30,7 @@ import ( "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" webhook_module "code.gitea.io/gitea/modules/webhook" ) @@ -264,7 +265,7 @@ func Deliver(ctx context.Context, t *webhook_model.HookTask) error { t.ResponseInfo.Headers[k] = strings.Join(vals, ",") } - p, err := io.ReadAll(resp.Body) + p, err := util.ReadWithLimit(resp.Body, 1024*1024) if err != nil { t.ResponseInfo.Body = fmt.Sprintf("read body: %s", err) return fmt.Errorf("unable to deliver webhook task[%d] in %s as unable to read response body: %w", t.ID, w.URL, err) diff --git a/services/webtheme/webtheme.go b/services/webtheme/webtheme.go index 72f01a76c7..57d63f4e07 100644 --- a/services/webtheme/webtheme.go +++ b/services/webtheme/webtheme.go @@ -39,6 +39,9 @@ func (info *ThemeMetaInfo) GetDescription() string { if info.ColorblindType == "red-green" { return "Red-green colorblind friendly" } + if info.ColorblindType == "blue-yellow" { + return "Blue-yellow colorblind friendly" + } return "" } @@ -46,6 +49,9 @@ func (info *ThemeMetaInfo) GetExtraIconName() string { if info.ColorblindType == "red-green" { return "gitea-colorblind-redgreen" } + if info.ColorblindType == "blue-yellow" { + return "gitea-colorblind-blueyellow" + } return "" } diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index 25f836dd5d..a9dc726982 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -223,6 +223,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model repo, repo.Name+".wiki", 0, + 0, ), }); err != nil { log.Error("Push failed: %v", err) @@ -341,6 +342,7 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model repo, repo.Name+".wiki", 0, + 0, ), }); err != nil { if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { diff --git a/services/wiki/wiki_path.go b/services/wiki/wiki_path.go index 212a35ea25..fc032244b5 100644 --- a/services/wiki/wiki_path.go +++ b/services/wiki/wiki_path.go @@ -129,8 +129,8 @@ func GitPathToWebPath(s string) (wp WebPath, err error) { func WebPathToUserTitle(s WebPath) (dir, display string) { dir = path.Dir(string(s)) display = path.Base(string(s)) - if strings.HasSuffix(display, ".md") { - display = strings.TrimSuffix(display, ".md") + if before, ok := strings.CutSuffix(display, ".md"); ok { + display = before display, _ = url.PathUnescape(display) } display, _ = unescapeSegment(display) diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl index 879b5cb550..5a11d5bb72 100644 --- a/templates/admin/user/edit.tmpl +++ b/templates/admin/user/edit.tmpl @@ -9,7 +9,7 @@ {{.CsrfTokenHtml}}
- +
diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index b721779c95..8cb72d6f98 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -120,6 +120,13 @@ {{svg "octicon-question"}} {{ctx.Locale.Tr "help"}} + {{if .IsAdmin}} +
+ + {{svg "octicon-server"}} + {{ctx.Locale.Tr "admin_panel"}} + + {{end}}
{{svg "octicon-sign-out"}} diff --git a/templates/package/content/arch.tmpl b/templates/package/content/arch.tmpl index 1c568cbb78..6ce18affac 100644 --- a/templates/package/content/arch.tmpl +++ b/templates/package/content/arch.tmpl @@ -4,9 +4,11 @@
-
[{{.PackageDescriptor.Owner.LowerName}}.{{.PackageRegistryHost}}]
+				
{{range $i, $repo := .Repositories}}{{if $i}}
+{{end}}[{{$repo}}]
 SigLevel = Optional TrustAll
-Server = 
+Server = +{{end}}
diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl index 21bc287643..5801396e3c 100644 --- a/templates/projects/view.tmpl +++ b/templates/projects/view.tmpl @@ -78,7 +78,9 @@
{{.NumIssues}}
-
{{.Title}}
+
+ {{if .Default}}{{svg "octicon-star"}} {{end}}{{.Title}} +
{{if $canWriteProject}}
{{end}} {{template "repo/diff/whitespace_dropdown" .}} diff --git a/templates/repo/home_sidebar_bottom.tmpl b/templates/repo/home_sidebar_bottom.tmpl index 01e630ccbf..4ba6aa05bf 100644 --- a/templates/repo/home_sidebar_bottom.tmpl +++ b/templates/repo/home_sidebar_bottom.tmpl @@ -6,8 +6,8 @@
diff --git a/templates/repo/home_sidebar_top.tmpl b/templates/repo/home_sidebar_top.tmpl index 8c2089c839..edbf01db09 100644 --- a/templates/repo/home_sidebar_top.tmpl +++ b/templates/repo/home_sidebar_top.tmpl @@ -9,7 +9,7 @@
{{ctx.Locale.Tr "repo.repo_desc"}}
-
+
{{- $description := .Repository.DescriptionHTML ctx -}} diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl index 882ffe40b7..b7a60a44ed 100644 --- a/templates/repo/release/list.tmpl +++ b/templates/repo/release/list.tmpl @@ -78,18 +78,6 @@ {{ctx.Locale.Tr "repo.release.downloads"}}
{{end}} + {{if and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) ($.Permission.CanRead ctx.Consts.RepoUnitTypeCode)}} +
  • + + {{svg "octicon-file-zip" 16 "download-icon"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP) + +
  • +
  • + + {{svg "octicon-file-zip" 16 "download-icon"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ) + +
  • + {{end}}
    diff --git a/templates/repo/wiki/pages.tmpl b/templates/repo/wiki/pages.tmpl index 5ceb8a4d5b..120c1cda32 100644 --- a/templates/repo/wiki/pages.tmpl +++ b/templates/repo/wiki/pages.tmpl @@ -11,7 +11,7 @@ {{if $.Permission.IsAdmin}}
    {{ctx.Locale.Tr "repo.default_branch"}}: {{.Repository.DefaultWikiBranch}}
    {{end}} - +
    {{range .Pages}} diff --git a/templates/shared/search/code/results.tmpl b/templates/shared/search/code/results.tmpl index 8a08f5c25c..42f7a181a3 100644 --- a/templates/shared/search/code/results.tmpl +++ b/templates/shared/search/code/results.tmpl @@ -12,7 +12,7 @@ {{range $result := .SearchResults}} {{$repo := or $.Repo (index $.RepoMaps .RepoID)}}
    -

    +

    {{if not $.Repo}} {{$repo.FullName}} diff --git a/templates/user/heatmap.tmpl b/templates/user/heatmap.tmpl index b604b929a3..6186edd4dd 100644 --- a/templates/user/heatmap.tmpl +++ b/templates/user/heatmap.tmpl @@ -1,4 +1,5 @@ {{if .HeatmapData}} +
    -
    +
    +
    {{end}} diff --git a/tests/e2e/README.md b/tests/e2e/README.md index db083793d8..ea3805ab95 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -17,13 +17,7 @@ make clean frontend ## Install playwright system dependencies ``` -npx playwright install-deps -``` - - -## Run all tests via local act_runner -``` -act_runner exec -W ./.github/workflows/pull-e2e-tests.yml --event=pull_request --default-actions-url="https://github.com" -i catthehacker/ubuntu:runner-latest +pnpm exec playwright install-deps ``` ## Run sqlite e2e tests @@ -85,8 +79,8 @@ TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=test TEST_MSSQL_USERNAME=sa TES Although the main goal of e2e is assertion testing, we have added a framework for visual regress testing. If you are working on front-end features, please use the following: - Check out `main`, `make clean frontend`, and run e2e tests with `VISUAL_TEST=1` to generate outputs. This will initially fail, as no screenshots exist. You can run the e2e tests again to assert it passes. - - Check out your branch, `make clean frontend`, and run e2e tests with `VISUAL_TEST=1`. You should be able to assert you front-end changes don't break any other tests unintentionally. + - Check out your branch, `make clean frontend`, and run e2e tests with `VISUAL_TEST=1`. You should be able to assert you front-end changes don't break any other tests unintentionally. -VISUAL_TEST=1 will create screenshots in tests/e2e/test-snapshots. The test will fail the first time this is enabled (until we get visual test image persistence figured out), because it will be testing against an empty screenshot folder. +VISUAL_TEST=1 will create screenshots in tests/e2e/test-snapshots. The test will fail the first time this is enabled (until we get visual test image persistence figured out), because it will be testing against an empty screenshot folder. ACCEPT_VISUAL=1 will overwrite the snapshot images with new images. diff --git a/tests/gitea-repositories-meta/user30/renderer.git/HEAD b/tests/gitea-repositories-meta/user30/renderer.git/HEAD deleted file mode 100644 index cb089cd89a..0000000000 --- a/tests/gitea-repositories-meta/user30/renderer.git/HEAD +++ /dev/null @@ -1 +0,0 @@ -ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user30/renderer.git/config b/tests/gitea-repositories-meta/user30/renderer.git/config deleted file mode 100644 index e6da231579..0000000000 --- a/tests/gitea-repositories-meta/user30/renderer.git/config +++ /dev/null @@ -1,6 +0,0 @@ -[core] - repositoryformatversion = 0 - filemode = true - bare = true - ignorecase = true - precomposeunicode = true diff --git a/tests/gitea-repositories-meta/user30/renderer.git/description b/tests/gitea-repositories-meta/user30/renderer.git/description deleted file mode 100644 index 04c23973b8..0000000000 --- a/tests/gitea-repositories-meta/user30/renderer.git/description +++ /dev/null @@ -1 +0,0 @@ -The repository will be used to test third-party renderer in TestExternalMarkupRenderer diff --git a/tests/gitea-repositories-meta/user30/renderer.git/hooks/post-receive b/tests/gitea-repositories-meta/user30/renderer.git/hooks/post-receive deleted file mode 100644 index f1f2709ddd..0000000000 --- a/tests/gitea-repositories-meta/user30/renderer.git/hooks/post-receive +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -data=$(cat) -exitcodes="" -hookname=$(basename $0) -GIT_DIR=${GIT_DIR:-$(dirname $0)} - -for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do -test -x "${hook}" && test -f "${hook}" || continue -echo "${data}" | "${hook}" -exitcodes="${exitcodes} $?" -done - -for i in ${exitcodes}; do -[ ${i} -eq 0 ] || exit ${i} -done diff --git a/tests/gitea-repositories-meta/user30/renderer.git/hooks/post-receive.d/gitea b/tests/gitea-repositories-meta/user30/renderer.git/hooks/post-receive.d/gitea deleted file mode 100644 index 43a948da3a..0000000000 --- a/tests/gitea-repositories-meta/user30/renderer.git/hooks/post-receive.d/gitea +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive diff --git a/tests/gitea-repositories-meta/user30/renderer.git/hooks/pre-receive b/tests/gitea-repositories-meta/user30/renderer.git/hooks/pre-receive deleted file mode 100644 index f1f2709ddd..0000000000 --- a/tests/gitea-repositories-meta/user30/renderer.git/hooks/pre-receive +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -data=$(cat) -exitcodes="" -hookname=$(basename $0) -GIT_DIR=${GIT_DIR:-$(dirname $0)} - -for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do -test -x "${hook}" && test -f "${hook}" || continue -echo "${data}" | "${hook}" -exitcodes="${exitcodes} $?" -done - -for i in ${exitcodes}; do -[ ${i} -eq 0 ] || exit ${i} -done diff --git a/tests/gitea-repositories-meta/user30/renderer.git/hooks/pre-receive.d/gitea b/tests/gitea-repositories-meta/user30/renderer.git/hooks/pre-receive.d/gitea deleted file mode 100644 index 49d0940636..0000000000 --- a/tests/gitea-repositories-meta/user30/renderer.git/hooks/pre-receive.d/gitea +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive diff --git a/tests/gitea-repositories-meta/user30/renderer.git/hooks/update b/tests/gitea-repositories-meta/user30/renderer.git/hooks/update deleted file mode 100644 index df5bd27f10..0000000000 --- a/tests/gitea-repositories-meta/user30/renderer.git/hooks/update +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -exitcodes="" -hookname=$(basename $0) -GIT_DIR=${GIT_DIR:-$(dirname $0)} - -for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do -test -x "${hook}" && test -f "${hook}" || continue -"${hook}" $1 $2 $3 -exitcodes="${exitcodes} $?" -done - -for i in ${exitcodes}; do -[ ${i} -eq 0 ] || exit ${i} -done diff --git a/tests/gitea-repositories-meta/user30/renderer.git/hooks/update.d/gitea b/tests/gitea-repositories-meta/user30/renderer.git/hooks/update.d/gitea deleted file mode 100644 index 38101c2426..0000000000 --- a/tests/gitea-repositories-meta/user30/renderer.git/hooks/update.d/gitea +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3 diff --git a/tests/gitea-repositories-meta/user30/renderer.git/objects/06/0d5c2acd8bf4b6f14010acd1a73d73392ec46e b/tests/gitea-repositories-meta/user30/renderer.git/objects/06/0d5c2acd8bf4b6f14010acd1a73d73392ec46e deleted file mode 100644 index 994f25602c..0000000000 Binary files a/tests/gitea-repositories-meta/user30/renderer.git/objects/06/0d5c2acd8bf4b6f14010acd1a73d73392ec46e and /dev/null differ diff --git a/tests/gitea-repositories-meta/user30/renderer.git/objects/45/14a93050edb2c3165bdd0a3c03be063e879e68 b/tests/gitea-repositories-meta/user30/renderer.git/objects/45/14a93050edb2c3165bdd0a3c03be063e879e68 deleted file mode 100644 index b1fff27753..0000000000 Binary files a/tests/gitea-repositories-meta/user30/renderer.git/objects/45/14a93050edb2c3165bdd0a3c03be063e879e68 and /dev/null differ diff --git a/tests/gitea-repositories-meta/user30/renderer.git/objects/c9/61cc4d1ba6b7ee1ba228a9a02b00b7746d8033 b/tests/gitea-repositories-meta/user30/renderer.git/objects/c9/61cc4d1ba6b7ee1ba228a9a02b00b7746d8033 deleted file mode 100644 index 66488767ae..0000000000 Binary files a/tests/gitea-repositories-meta/user30/renderer.git/objects/c9/61cc4d1ba6b7ee1ba228a9a02b00b7746d8033 and /dev/null differ diff --git a/tests/gitea-repositories-meta/user30/renderer.git/packed-refs b/tests/gitea-repositories-meta/user30/renderer.git/packed-refs deleted file mode 100644 index 63f8af0f12..0000000000 --- a/tests/gitea-repositories-meta/user30/renderer.git/packed-refs +++ /dev/null @@ -1,2 +0,0 @@ -# pack-refs with: peeled fully-peeled sorted -c961cc4d1ba6b7ee1ba228a9a02b00b7746d8033 refs/heads/master diff --git a/tests/gitea-repositories-meta/user30/renderer.git/refs/.keep b/tests/gitea-repositories-meta/user30/renderer.git/refs/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/integration/api_auth_test.go b/tests/integration/api_auth_test.go new file mode 100644 index 0000000000..a6ff6a6519 --- /dev/null +++ b/tests/integration/api_auth_test.go @@ -0,0 +1,32 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIAuth(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequestf(t, "GET", "/api/v1/user").AddBasicAuth("user2") + MakeRequest(t, req, http.StatusOK) + + req = NewRequestf(t, "GET", "/api/v1/user").AddBasicAuth("user2", "wrong-password") + resp := MakeRequest(t, req, http.StatusUnauthorized) + assert.Contains(t, resp.Body.String(), `{"message":"invalid username, password or token"`) + + req = NewRequestf(t, "GET", "/api/v1/user").AddBasicAuth("user-not-exist") + resp = MakeRequest(t, req, http.StatusUnauthorized) + assert.Contains(t, resp.Body.String(), `{"message":"invalid username, password or token"`) + + req = NewRequestf(t, "GET", "/api/v1/users/user2/repos").AddTokenAuth("Bearer wrong_token") + resp = MakeRequest(t, req, http.StatusUnauthorized) + assert.Contains(t, resp.Body.String(), `{"message":"invalid username, password or token"`) +} diff --git a/tests/integration/api_issue_dependency_test.go b/tests/integration/api_issue_dependency_test.go new file mode 100644 index 0000000000..8356d6058d --- /dev/null +++ b/tests/integration/api_issue_dependency_test.go @@ -0,0 +1,152 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "fmt" + "net/http" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/perm" + access_model "code.gitea.io/gitea/models/perm/access" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + repo_service "code.gitea.io/gitea/services/repository" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func enableRepoDependencies(t *testing.T, repoID int64) { + t.Helper() + + repoUnit := unittest.AssertExistsAndLoadBean(t, &repo_model.RepoUnit{RepoID: repoID, Type: unit.TypeIssues}) + repoUnit.IssuesConfig().EnableDependencies = true + assert.NoError(t, repo_model.UpdateRepoUnit(t.Context(), repoUnit)) +} + +func TestAPICreateIssueDependencyCrossRepoPermission(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + targetRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + targetIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: targetRepo.ID, Index: 1}) + dependencyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + assert.True(t, dependencyRepo.IsPrivate) + dependencyIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: dependencyRepo.ID, Index: 1}) + + enableRepoDependencies(t, targetIssue.RepoID) + enableRepoDependencies(t, dependencyRepo.ID) + + // remove user 40 access from target repository + _, err := db.DeleteByID[access_model.Access](t.Context(), 30) + assert.NoError(t, err) + + url := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/dependencies", "user2", "repo1", targetIssue.Index) + dependencyMeta := &api.IssueMeta{ + Owner: "org3", + Name: "repo3", + Index: dependencyIssue.Index, + } + + user40 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) + // user40 has no access to both target issue and dependency issue + writerToken := getUserToken(t, "user40", auth_model.AccessTokenScopeWriteIssue) + req := NewRequestWithJSON(t, "POST", url, dependencyMeta). + AddTokenAuth(writerToken) + MakeRequest(t, req, http.StatusNotFound) + unittest.AssertNotExistsBean(t, &issues_model.IssueDependency{ + IssueID: targetIssue.ID, + DependencyID: dependencyIssue.ID, + }) + + // add user40 as a collaborator to dependency repository with read permission + assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), dependencyRepo, user40, perm.AccessModeRead)) + + // try again after getting read permission to dependency repository + req = NewRequestWithJSON(t, "POST", url, dependencyMeta). + AddTokenAuth(writerToken) + MakeRequest(t, req, http.StatusNotFound) + unittest.AssertNotExistsBean(t, &issues_model.IssueDependency{ + IssueID: targetIssue.ID, + DependencyID: dependencyIssue.ID, + }) + + // add user40 as a collaborator to target repository with write permission + assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), targetRepo, user40, perm.AccessModeWrite)) + + req = NewRequestWithJSON(t, "POST", url, dependencyMeta). + AddTokenAuth(writerToken) + MakeRequest(t, req, http.StatusCreated) + unittest.AssertExistsAndLoadBean(t, &issues_model.IssueDependency{ + IssueID: targetIssue.ID, + DependencyID: dependencyIssue.ID, + }) +} + +func TestAPIDeleteIssueDependencyCrossRepoPermission(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + targetRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + targetIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: targetRepo.ID, Index: 1}) + dependencyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + assert.True(t, dependencyRepo.IsPrivate) + dependencyIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: dependencyRepo.ID, Index: 1}) + + enableRepoDependencies(t, targetIssue.RepoID) + enableRepoDependencies(t, dependencyRepo.ID) + + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + assert.NoError(t, issues_model.CreateIssueDependency(t.Context(), user1, targetIssue, dependencyIssue)) + + // remove user 40 access from target repository + _, err := db.DeleteByID[access_model.Access](t.Context(), 30) + assert.NoError(t, err) + + url := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/dependencies", "user2", "repo1", targetIssue.Index) + dependencyMeta := &api.IssueMeta{ + Owner: "org3", + Name: "repo3", + Index: dependencyIssue.Index, + } + + user40 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) + // user40 has no access to both target issue and dependency issue + writerToken := getUserToken(t, "user40", auth_model.AccessTokenScopeWriteIssue) + req := NewRequestWithJSON(t, "DELETE", url, dependencyMeta). + AddTokenAuth(writerToken) + MakeRequest(t, req, http.StatusNotFound) + unittest.AssertExistsAndLoadBean(t, &issues_model.IssueDependency{ + IssueID: targetIssue.ID, + DependencyID: dependencyIssue.ID, + }) + + // add user40 as a collaborator to dependency repository with read permission + assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), dependencyRepo, user40, perm.AccessModeRead)) + + // try again after getting read permission to dependency repository + req = NewRequestWithJSON(t, "DELETE", url, dependencyMeta). + AddTokenAuth(writerToken) + MakeRequest(t, req, http.StatusNotFound) + unittest.AssertExistsAndLoadBean(t, &issues_model.IssueDependency{ + IssueID: targetIssue.ID, + DependencyID: dependencyIssue.ID, + }) + + // add user40 as a collaborator to target repository with write permission + assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), targetRepo, user40, perm.AccessModeWrite)) + + req = NewRequestWithJSON(t, "DELETE", url, dependencyMeta). + AddTokenAuth(writerToken) + MakeRequest(t, req, http.StatusCreated) + unittest.AssertNotExistsBean(t, &issues_model.IssueDependency{ + IssueID: targetIssue.ID, + DependencyID: dependencyIssue.ID, + }) +} diff --git a/tests/integration/api_packages_conda_test.go b/tests/integration/api_packages_conda_test.go index b69a8c9066..8dbcba5b54 100644 --- a/tests/integration/api_packages_conda_test.go +++ b/tests/integration/api_packages_conda_test.go @@ -237,6 +237,8 @@ func TestPackageConda(t *testing.T) { assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5) assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256) assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size) + assert.NotNil(t, packageInfo.Dependencies) + assert.Empty(t, packageInfo.Dependencies) }) t.Run(".conda", func(t *testing.T) { @@ -268,6 +270,8 @@ func TestPackageConda(t *testing.T) { assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5) assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256) assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size) + assert.NotNil(t, packageInfo.Dependencies) + assert.Empty(t, packageInfo.Dependencies) }) }) } diff --git a/tests/integration/api_packages_container_test.go b/tests/integration/api_packages_container_test.go index 7e93cb47a2..3c2d8bac33 100644 --- a/tests/integration/api_packages_container_test.go +++ b/tests/integration/api_packages_container_test.go @@ -28,6 +28,7 @@ import ( oci "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestPackageContainer(t *testing.T) { @@ -70,13 +71,12 @@ func TestPackageContainer(t *testing.T) { manifestDigest := "sha256:4f10484d1c1bb13e3956b4de1cd42db8e0f14a75be1617b60f2de3cd59c803c6" manifestContent := `{"schemaVersion":2,"mediaType":"` + container_module.ContentTypeDockerDistributionManifestV2 + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}` - manifestContentType := container_module.ContentTypeDockerDistributionManifestV2 untaggedManifestDigest := "sha256:4305f5f5572b9a426b88909b036e52ee3cf3d7b9c1b01fac840e90747f56623d" untaggedManifestContent := `{"schemaVersion":2,"mediaType":"` + oci.MediaTypeImageManifest + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}` - indexManifestDigest := "sha256:bab112d6efb9e7f221995caaaa880352feb5bd8b1faf52fae8d12c113aa123ec" - indexManifestContent := `{"schemaVersion":2,"mediaType":"` + oci.MediaTypeImageIndex + `","manifests":[{"mediaType":"application/vnd.docker.distribution.manifest.v2+json","digest":"` + manifestDigest + `","platform":{"os":"linux","architecture":"arm","variant":"v7"}},{"mediaType":"` + oci.MediaTypeImageManifest + `","digest":"` + untaggedManifestDigest + `","platform":{"os":"linux","architecture":"arm64","variant":"v8"}}]}` + indexManifestDigest := "sha256:2c6b5afb967d5de02795ee1d177c3746d005df4b4c2b829385b0d186b3414b6b" + indexManifestContent := `{"schemaVersion":2,"mediaType":"` + oci.MediaTypeImageIndex + `","is_tagged":true,"manifests":[{"mediaType":"application/vnd.docker.distribution.manifest.v2+json","digest":"` + manifestDigest + `","platform":{"os":"linux","architecture":"arm","variant":"v7"}},{"mediaType":"` + oci.MediaTypeImageManifest + `","digest":"` + untaggedManifestDigest + `","platform":{"os":"linux","architecture":"arm64","variant":"v8"}}]}` anonymousToken := "" userToken := "" @@ -467,15 +467,16 @@ func TestPackageContainer(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, pv.DownloadCount) - // Overwrite existing tag should keep the download count - req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/manifests/%s", url, tag), strings.NewReader(manifestContent)). - AddTokenAuth(userToken). - SetHeader("Content-Type", oci.MediaTypeImageManifest) - MakeRequest(t, req, http.StatusCreated) + t.Run("OverwriteTagKeepDownloadCount", func(t *testing.T) { + req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/manifests/%s", url, tag), strings.NewReader(manifestContent)). + AddTokenAuth(userToken). + SetHeader("Content-Type", oci.MediaTypeImageManifest) + MakeRequest(t, req, http.StatusCreated) - pv, err = packages_model.GetVersionByNameAndVersion(t.Context(), user.ID, packages_model.TypeContainer, image, tag) - assert.NoError(t, err) - assert.EqualValues(t, 1, pv.DownloadCount) + pv, err = packages_model.GetVersionByNameAndVersion(t.Context(), user.ID, packages_model.TypeContainer, image, tag) + assert.NoError(t, err) + assert.EqualValues(t, 1, pv.DownloadCount) + }) }) t.Run("HeadManifest", func(t *testing.T) { @@ -505,7 +506,7 @@ func TestPackageContainer(t *testing.T) { resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, strconv.Itoa(len(manifestContent)), resp.Header().Get("Content-Length")) - assert.Equal(t, manifestContentType, resp.Header().Get("Content-Type")) + assert.Equal(t, oci.MediaTypeImageManifest, resp.Header().Get("Content-Type")) // the manifest is overwritten by above OverwriteTagKeepDownloadCount assert.Equal(t, manifestDigest, resp.Header().Get("Docker-Content-Digest")) assert.Equal(t, manifestContent, resp.Body.String()) }) @@ -599,6 +600,17 @@ func TestPackageContainer(t *testing.T) { assert.True(t, pd.Files[0].File.IsLead) assert.Equal(t, oci.MediaTypeImageIndex, pd.Files[0].Properties.GetByName(container_module.PropertyMediaType)) assert.Equal(t, indexManifestDigest, pd.Files[0].Properties.GetByName(container_module.PropertyDigest)) + + lastPackageVersionID := pv.ID + t.Run("UploadAgain", func(t *testing.T) { + req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/manifests/%s", url, multiTag), strings.NewReader(indexManifestContent)). + AddTokenAuth(userToken). + SetHeader("Content-Type", oci.MediaTypeImageIndex) + MakeRequest(t, req, http.StatusCreated) + pv, err := packages_model.GetVersionByNameAndVersion(t.Context(), user.ID, packages_model.TypeContainer, image, multiTag) + require.NoError(t, err) + assert.NotEqual(t, lastPackageVersionID, pv.ID) + }) }) t.Run("HeadBlob", func(t *testing.T) { diff --git a/tests/integration/api_releases_test.go b/tests/integration/api_releases_test.go index 53d37df8e0..b3b30a33d5 100644 --- a/tests/integration/api_releases_test.go +++ b/tests/integration/api_releases_test.go @@ -15,6 +15,8 @@ import ( "testing" auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" + access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -269,6 +271,42 @@ func TestAPIGetReleaseByTag(t *testing.T) { assert.NotEmpty(t, err.Message) } +func TestAPIGetDraftReleaseByTag(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + tag := "draft-release" + // anonymous should not be able to get draft release + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, tag)) + MakeRequest(t, req, http.StatusNotFound) + + // user 40 should be able to get draft release because he has write access to the repository + token := getUserToken(t, "user40", auth_model.AccessTokenScopeReadRepository) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, tag)).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + release := api.Release{} + DecodeJSON(t, resp, &release) + assert.Equal(t, "draft-release", release.Title) + + // remove user 40 access from the repository + _, err := db.DeleteByID[access_model.Access](t.Context(), 30) + assert.NoError(t, err) + + // user 40 should not be able to get draft release + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, tag)).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + + // user 2 should be able to get draft release because he is the publisher + user2Token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, tag)).AddTokenAuth(user2Token) + resp = MakeRequest(t, req, http.StatusOK) + release = api.Release{} + DecodeJSON(t, resp, &release) + assert.Equal(t, "draft-release", release.Title) +} + func TestAPIDeleteReleaseByTagName(t *testing.T) { defer tests.PrepareTestEnv(t)() diff --git a/tests/integration/api_repo_file_helpers.go b/tests/integration/api_repo_file_helpers.go index 785416b0f5..f8d6fc803f 100644 --- a/tests/integration/api_repo_file_helpers.go +++ b/tests/integration/api_repo_file_helpers.go @@ -19,6 +19,9 @@ import ( type createFileInBranchOptions struct { OldBranch, NewBranch string + CommitMessage string + CommitterName string + CommitterEmail string } func testCreateFileInBranch(t *testing.T, user *user_model.User, repo *repo_model.Repository, createOpts createFileInBranchOptions, files map[string]string) *api.FilesResponse { @@ -29,7 +32,17 @@ func testCreateFileInBranch(t *testing.T, user *user_model.User, repo *repo_mode func createFileInBranch(user *user_model.User, repo *repo_model.Repository, createOpts createFileInBranchOptions, files map[string]string) (*api.FilesResponse, error) { ctx := context.TODO() - opts := &files_service.ChangeRepoFilesOptions{OldBranch: createOpts.OldBranch, NewBranch: createOpts.NewBranch} + opts := &files_service.ChangeRepoFilesOptions{ + OldBranch: createOpts.OldBranch, + NewBranch: createOpts.NewBranch, + Message: createOpts.CommitMessage, + } + if createOpts.CommitterName != "" || createOpts.CommitterEmail != "" { + opts.Committer = &files_service.IdentityOptions{ + GitUserName: createOpts.CommitterName, + GitUserEmail: createOpts.CommitterEmail, + } + } for path, content := range files { opts.Files = append(opts.Files, &files_service.ChangeRepoFile{ Operation: "create", diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go index 1478cb9bff..deacd68a49 100644 --- a/tests/integration/api_repo_test.go +++ b/tests/integration/api_repo_test.go @@ -41,17 +41,6 @@ func TestAPIUserReposNotLogin(t *testing.T) { } } -func TestAPIUserReposWithWrongToken(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - wrongToken := "Bearer " + "wrong_token" - req := NewRequestf(t, "GET", "/api/v1/users/%s/repos", user.Name). - AddTokenAuth(wrongToken) - resp := MakeRequest(t, req, http.StatusUnauthorized) - - assert.Contains(t, resp.Body.String(), "user does not exist") -} - func TestAPISearchRepo(t *testing.T) { defer tests.PrepareTestEnv(t)() const keyword = "test" diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index 9dfa0ccd5d..760efb2a65 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -271,8 +271,8 @@ type RequestWrapper struct { *http.Request } -func (req *RequestWrapper) AddBasicAuth(username string) *RequestWrapper { - req.Request.SetBasicAuth(username, userPassword) +func (req *RequestWrapper) AddBasicAuth(username string, password ...string) *RequestWrapper { + req.Request.SetBasicAuth(username, util.OptionalArg(password, userPassword)) return req } diff --git a/tests/integration/markup_external_test.go b/tests/integration/markup_external_test.go index 9985333cd7..b965766b5c 100644 --- a/tests/integration/markup_external_test.go +++ b/tests/integration/markup_external_test.go @@ -12,6 +12,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/external" "code.gitea.io/gitea/modules/setting" @@ -25,29 +26,45 @@ import ( func TestExternalMarkupRenderer(t *testing.T) { defer tests.PrepareTestEnv(t)() if !setting.Database.Type.IsSQLite3() { - t.Skip() + t.Skip("only SQLite3 test config supports external markup renderer") return } + const binaryContentPrefix = "any prefix text." + const binaryContent = binaryContentPrefix + "\xfe\xfe\xfe\x00\xff\xff" + detectedEncoding, _ := charset.DetectEncoding([]byte(binaryContent)) + assert.NotEqual(t, binaryContent, strings.ToValidUTF8(binaryContent, "?")) + assert.Equal(t, "ISO-8859-2", detectedEncoding) // even if the binary content can be detected as text encoding, it shouldn't affect the raw rendering + onGiteaRun(t, func(t *testing.T, _ *url.URL) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - _, err := createFile(user2, repo1, "file.no-sanitizer", "master", `any content`) + _, err := createFileInBranch(user2, repo1, createFileInBranchOptions{}, map[string]string{ + "test.html": `
    `, + "html.no-sanitizer": ``, + "bin.no-sanitizer": binaryContent, + }) require.NoError(t, err) t.Run("RenderNoSanitizer", func(t *testing.T) { - req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/file.no-sanitizer") + req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/html.no-sanitizer") resp := MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - div := doc.Find("div.file-view") + div := NewHTMLParser(t, resp.Body).Find("div.file-view") data, err := div.Html() assert.NoError(t, err) - assert.Equal(t, ``, strings.TrimSpace(data)) + assert.Equal(t, ``, strings.TrimSpace(data)) + + req = NewRequest(t, "GET", "/user2/repo1/src/branch/master/bin.no-sanitizer") + resp = MakeRequest(t, req, http.StatusOK) + div = NewHTMLParser(t, resp.Body).Find("div.file-view") + data, err = div.Html() + assert.NoError(t, err) + assert.Equal(t, strings.ReplaceAll(binaryContent, "\x00", ""), strings.TrimSpace(data)) // HTML template engine removes the null bytes }) }) t.Run("RenderContentDirectly", func(t *testing.T) { - req := NewRequest(t, "GET", "/user30/renderer/src/branch/master/README.html") + req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/test.html") resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type")) @@ -55,18 +72,21 @@ func TestExternalMarkupRenderer(t *testing.T) { div := doc.Find("div.file-view") data, err := div.Html() assert.NoError(t, err) - assert.Equal(t, "
    \n\ttest external renderer\n
    ", strings.TrimSpace(data)) + // the content is fully sanitized + assert.Equal(t, `
    <script></script>
    `, strings.TrimSpace(data)) }) - // above tested "no-sanitizer" mode, then we test iframe mode below + // above tested in-page rendering (no iframe), then we test iframe mode below r := markup.GetRendererByFileName("any-file.html").(*external.Renderer) defer test.MockVariableValue(&r.RenderContentMode, setting.RenderContentModeIframe)() + assert.True(t, r.NeedPostProcess()) r = markup.GetRendererByFileName("any-file.no-sanitizer").(*external.Renderer) defer test.MockVariableValue(&r.RenderContentMode, setting.RenderContentModeIframe)() + assert.False(t, r.NeedPostProcess()) t.Run("RenderContentInIFrame", func(t *testing.T) { t.Run("DefaultSandbox", func(t *testing.T) { - req := NewRequest(t, "GET", "/user30/renderer/src/branch/master/README.html") + req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/test.html") t.Run("ParentPage", func(t *testing.T) { respParent := MakeRequest(t, req, http.StatusOK) @@ -77,31 +97,42 @@ func TestExternalMarkupRenderer(t *testing.T) { // default sandbox on parent page assert.Equal(t, "allow-scripts allow-popups", iframe.AttrOr("sandbox", "")) - assert.Equal(t, "/user30/renderer/render/branch/master/README.html", iframe.AttrOr("data-src", "")) + assert.Equal(t, "/user2/repo1/render/branch/master/test.html", iframe.AttrOr("data-src", "")) }) t.Run("SubPage", func(t *testing.T) { - req = NewRequest(t, "GET", "/user30/renderer/render/branch/master/README.html") + req = NewRequest(t, "GET", "/user2/repo1/render/branch/master/test.html") respSub := MakeRequest(t, req, http.StatusOK) assert.Equal(t, "text/html; charset=utf-8", respSub.Header().Get("Content-Type")) // default sandbox in sub page response assert.Equal(t, "frame-src 'self'; sandbox allow-scripts allow-popups", respSub.Header().Get("Content-Security-Policy")) - assert.Equal(t, "
    \n\ttest external renderer\n
    \n", respSub.Body.String()) + // FIXME: actually here is a bug (legacy design problem), the "PostProcess" will escape "
    <script></script>
    `, respSub.Body.String()) }) }) t.Run("NoSanitizerNoSandbox", func(t *testing.T) { - req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/file.no-sanitizer") - respParent := MakeRequest(t, req, http.StatusOK) - iframe := NewHTMLParser(t, respParent.Body).Find("iframe.external-render-iframe") - assert.Equal(t, "/user2/repo1/render/branch/master/file.no-sanitizer", iframe.AttrOr("data-src", "")) + t.Run("BinaryContent", func(t *testing.T) { + req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/bin.no-sanitizer") + respParent := MakeRequest(t, req, http.StatusOK) + iframe := NewHTMLParser(t, respParent.Body).Find("iframe.external-render-iframe") + assert.Equal(t, "/user2/repo1/render/branch/master/bin.no-sanitizer", iframe.AttrOr("data-src", "")) - req = NewRequest(t, "GET", "/user2/repo1/render/branch/master/file.no-sanitizer") - respSub := MakeRequest(t, req, http.StatusOK) + req = NewRequest(t, "GET", "/user2/repo1/render/branch/master/bin.no-sanitizer") + respSub := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, binaryContent, respSub.Body.String()) // raw content should keep the raw bytes (including invalid UTF-8 bytes), and no "external-render-iframe" helpers + + // no sandbox (disabled by RENDER_CONTENT_SANDBOX) + assert.Empty(t, iframe.AttrOr("sandbox", "")) + assert.Equal(t, "frame-src 'self'", respSub.Header().Get("Content-Security-Policy")) + }) - // no sandbox (disabled by RENDER_CONTENT_SANDBOX) - assert.Empty(t, iframe.AttrOr("sandbox", "")) - assert.Equal(t, "frame-src 'self'", respSub.Header().Get("Content-Security-Policy")) + t.Run("HTMLContentWithExternalRenderIframeHelper", func(t *testing.T) { + req := NewRequest(t, "GET", "/user2/repo1/render/branch/master/html.no-sanitizer") + respSub := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, ``, respSub.Body.String()) + assert.Equal(t, "frame-src 'self'", respSub.Header().Get("Content-Security-Policy")) + }) }) }) } diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go index eab95ba688..e7edace653 100644 --- a/tests/integration/oauth_test.go +++ b/tests/integration/oauth_test.go @@ -919,20 +919,32 @@ func TestOAuth_GrantScopesClaimAllGroups(t *testing.T) { } func testOAuth2WellKnown(t *testing.T) { + defer test.MockVariableValue(&setting.AppURL, "https://try.gitea.io/")() urlOpenidConfiguration := "/.well-known/openid-configuration" - defer test.MockVariableValue(&setting.AppURL, "https://try.gitea.io/")() - req := NewRequest(t, "GET", urlOpenidConfiguration) - resp := MakeRequest(t, req, http.StatusOK) - var respMap map[string]any - DecodeJSON(t, resp, &respMap) - assert.Equal(t, "https://try.gitea.io", respMap["issuer"]) - assert.Equal(t, "https://try.gitea.io/login/oauth/authorize", respMap["authorization_endpoint"]) - assert.Equal(t, "https://try.gitea.io/login/oauth/access_token", respMap["token_endpoint"]) - assert.Equal(t, "https://try.gitea.io/login/oauth/keys", respMap["jwks_uri"]) - assert.Equal(t, "https://try.gitea.io/login/oauth/userinfo", respMap["userinfo_endpoint"]) - assert.Equal(t, "https://try.gitea.io/login/oauth/introspect", respMap["introspection_endpoint"]) - assert.Equal(t, []any{"RS256"}, respMap["id_token_signing_alg_values_supported"]) + t.Run("WellKnown", func(t *testing.T) { + req := NewRequest(t, "GET", urlOpenidConfiguration) + resp := MakeRequest(t, req, http.StatusOK) + var respMap map[string]any + DecodeJSON(t, resp, &respMap) + assert.Equal(t, "https://try.gitea.io", respMap["issuer"]) + assert.Equal(t, "https://try.gitea.io/login/oauth/authorize", respMap["authorization_endpoint"]) + assert.Equal(t, "https://try.gitea.io/login/oauth/access_token", respMap["token_endpoint"]) + assert.Equal(t, "https://try.gitea.io/login/oauth/keys", respMap["jwks_uri"]) + assert.Equal(t, "https://try.gitea.io/login/oauth/userinfo", respMap["userinfo_endpoint"]) + assert.Equal(t, "https://try.gitea.io/login/oauth/introspect", respMap["introspection_endpoint"]) + assert.Equal(t, []any{"RS256"}, respMap["id_token_signing_alg_values_supported"]) + }) + + t.Run("WellKnownWithIssuer", func(t *testing.T) { + defer test.MockVariableValue(&setting.OAuth2.JWTClaimIssuer, "https://try.gitea.io/")() + req := NewRequest(t, "GET", urlOpenidConfiguration) + resp := MakeRequest(t, req, http.StatusOK) + var respMap map[string]any + DecodeJSON(t, resp, &respMap) + assert.Equal(t, "https://try.gitea.io/", respMap["issuer"]) // has trailing by JWTClaimIssuer + assert.Equal(t, "https://try.gitea.io/login/oauth/authorize", respMap["authorization_endpoint"]) + }) defer test.MockVariableValue(&setting.OAuth2.Enabled, false)() MakeRequest(t, NewRequest(t, "GET", urlOpenidConfiguration), http.StatusNotFound) diff --git a/tests/integration/pull_create_test.go b/tests/integration/pull_create_test.go index d9811d000f..ddafdf33b8 100644 --- a/tests/integration/pull_create_test.go +++ b/tests/integration/pull_create_test.go @@ -10,9 +10,12 @@ import ( "net/url" "path" "strings" + "sync" "testing" auth_model "code.gitea.io/gitea/models/auth" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/tests" @@ -137,8 +140,15 @@ func TestPullCreate(t *testing.T) { session := loginUser(t, "user1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"}) + assert.Equal(t, 3, repo1.NumPulls) + assert.Equal(t, 3, repo1.NumOpenPulls) resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") + repo1 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"}) + assert.Equal(t, 4, repo1.NumPulls) + assert.Equal(t, 4, repo1.NumOpenPulls) + // check the redirected URL url := test.RedirectURL(resp) assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url) @@ -285,6 +295,44 @@ func TestPullCreatePrFromBaseToFork(t *testing.T) { }) } +func TestPullCreateParallel(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + sessionFork := loginUser(t, "user1") + testRepoFork(t, sessionFork, "user2", "repo1", "user1", "repo1", "") + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"}) + assert.Equal(t, 3, repo1.NumPulls) + assert.Equal(t, 3, repo1.NumOpenPulls) + + var wg sync.WaitGroup + for i := range 5 { + wg.Go(func() { + branchName := fmt.Sprintf("new-branch-%d", i) + testEditFileToNewBranch(t, sessionFork, "user1", "repo1", "master", branchName, "README.md", fmt.Sprintf("Hello, World (Edited) %d\n", i)) + + // Create a PR + resp := testPullCreateDirectly(t, sessionFork, createPullRequestOptions{ + BaseRepoOwner: "user2", + BaseRepoName: "repo1", + BaseBranch: "master", + HeadRepoOwner: "user1", + HeadRepoName: "repo1", + HeadBranch: branchName, + Title: fmt.Sprintf("This is a pull title %d", i), + }) + // check the redirected URL + url := test.RedirectURL(resp) + assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url) + }) + } + wg.Wait() + + repo1 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"}) + assert.Equal(t, 8, repo1.NumPulls) + assert.Equal(t, 8, repo1.NumOpenPulls) + }) +} + func TestCreateAgitPullWithReadPermission(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { dstPath := t.TempDir() @@ -300,11 +348,19 @@ func TestCreateAgitPullWithReadPermission(t *testing.T) { TreeFileContent: "temp content", })(t) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"}) + assert.Equal(t, 3, repo.NumPulls) + assert.Equal(t, 3, repo.NumOpenPulls) + err := gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master", "-o"). AddDynamicArguments("topic=test-topic"). WithDir(dstPath). Run(t.Context()) assert.NoError(t, err) + + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"}) + assert.Equal(t, 4, repo.NumPulls) + assert.Equal(t, 4, repo.NumOpenPulls) }) } diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index 683ea0a48c..20dabb3570 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -33,6 +33,7 @@ import ( api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/automerge" "code.gitea.io/gitea/services/automergequeue" pull_service "code.gitea.io/gitea/services/pull" @@ -41,6 +42,7 @@ import ( files_service "code.gitea.io/gitea/services/repository/files" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type MergeOptions struct { @@ -113,8 +115,16 @@ func TestPullMerge(t *testing.T) { testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"}) + assert.Equal(t, 3, repo.NumPulls) + assert.Equal(t, 3, repo.NumOpenPulls) + resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) + assert.Equal(t, 4, repo.NumPulls) + assert.Equal(t, 4, repo.NumOpenPulls) + elem := strings.Split(test.RedirectURL(resp), "/") assert.Equal(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], MergeOptions{ @@ -122,6 +132,10 @@ func TestPullMerge(t *testing.T) { DeleteBranch: false, }) + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) + assert.Equal(t, 4, repo.NumPulls) + assert.Equal(t, 3, repo.NumOpenPulls) + hookTasks, err = webhook.HookTasks(t.Context(), 1, 1) assert.NoError(t, err) assert.Len(t, hookTasks, hookTasksLenBefore+1) @@ -138,8 +152,16 @@ func TestPullRebase(t *testing.T) { testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"}) + assert.Equal(t, 3, repo.NumPulls) + assert.Equal(t, 3, repo.NumOpenPulls) + resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) + assert.Equal(t, 4, repo.NumPulls) + assert.Equal(t, 4, repo.NumOpenPulls) + elem := strings.Split(test.RedirectURL(resp), "/") assert.Equal(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], MergeOptions{ @@ -147,6 +169,10 @@ func TestPullRebase(t *testing.T) { DeleteBranch: false, }) + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) + assert.Equal(t, 4, repo.NumPulls) + assert.Equal(t, 3, repo.NumOpenPulls) + hookTasks, err = webhook.HookTasks(t.Context(), 1, 1) assert.NoError(t, err) assert.Len(t, hookTasks, hookTasksLenBefore+1) @@ -163,8 +189,16 @@ func TestPullRebaseMerge(t *testing.T) { testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"}) + assert.Equal(t, 3, repo.NumPulls) + assert.Equal(t, 3, repo.NumOpenPulls) + resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) + assert.Equal(t, 4, repo.NumPulls) + assert.Equal(t, 4, repo.NumOpenPulls) + elem := strings.Split(test.RedirectURL(resp), "/") assert.Equal(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], MergeOptions{ @@ -172,6 +206,10 @@ func TestPullRebaseMerge(t *testing.T) { DeleteBranch: false, }) + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) + assert.Equal(t, 4, repo.NumPulls) + assert.Equal(t, 3, repo.NumOpenPulls) + hookTasks, err = webhook.HookTasks(t.Context(), 1, 1) assert.NoError(t, err) assert.Len(t, hookTasks, hookTasksLenBefore+1) @@ -215,6 +253,10 @@ func TestPullSquashWithHeadCommitID(t *testing.T) { testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited!)\n") + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"}) + assert.Equal(t, 3, repo.NumPulls) + assert.Equal(t, 3, repo.NumOpenPulls) + resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: "repo1"}) @@ -224,11 +266,19 @@ func TestPullSquashWithHeadCommitID(t *testing.T) { elem := strings.Split(test.RedirectURL(resp), "/") assert.Equal(t, "pulls", elem[3]) + + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) + assert.Equal(t, 4, repo.NumPulls) + assert.Equal(t, 4, repo.NumOpenPulls) + testPullMerge(t, session, elem[1], elem[2], elem[4], MergeOptions{ Style: repo_model.MergeStyleSquash, DeleteBranch: false, HeadCommitID: headBranch.CommitID, }) + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) + assert.Equal(t, 4, repo.NumPulls) + assert.Equal(t, 3, repo.NumOpenPulls) hookTasks, err = webhook.HookTasks(t.Context(), 1, 1) assert.NoError(t, err) @@ -242,15 +292,28 @@ func TestPullCleanUpAfterMerge(t *testing.T) { testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited - TestPullCleanUpAfterMerge)\n") + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"}) + assert.Equal(t, 3, repo.NumPulls) + assert.Equal(t, 3, repo.NumOpenPulls) + resp := testPullCreate(t, session, "user1", "repo1", false, "master", "feature/test", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") assert.Equal(t, "pulls", elem[3]) + + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) + assert.Equal(t, 4, repo.NumPulls) + assert.Equal(t, 4, repo.NumOpenPulls) + testPullMerge(t, session, elem[1], elem[2], elem[4], MergeOptions{ Style: repo_model.MergeStyleMerge, DeleteBranch: false, }) + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) + assert.Equal(t, 4, repo.NumPulls) + assert.Equal(t, 3, repo.NumOpenPulls) + // Check PR branch deletion resp = testPullCleanUp(t, session, elem[1], elem[2], elem[4]) respJSON := struct { @@ -1115,3 +1178,172 @@ func TestPullNonMergeForAdminWithBranchProtection(t *testing.T) { session.MakeRequest(t, mergeReq, http.StatusMethodNotAllowed) }) } + +func TestPullSquashMergeEmpty(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user1") + testEditFileToNewBranch(t, session, "user2", "repo1", "master", "pr-squash-empty", "README.md", "Hello, World (Edited)\n") + resp := testPullCreate(t, session, "user2", "repo1", false, "master", "pr-squash-empty", "This is a pull title") + + elem := strings.Split(test.RedirectURL(resp), "/") + assert.Equal(t, "pulls", elem[3]) + + httpContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository) + dstPath := t.TempDir() + + u.Path = httpContext.GitPath() + u.User = url.UserPassword("user2", userPassword) + + t.Run("Clone", doGitClone(dstPath, u)) + doGitCheckoutBranch(dstPath, "-b", "pr-squash-empty", "remotes/origin/pr-squash-empty")(t) + doGitCheckoutBranch(dstPath, "master")(t) + _, _, err := gitcmd.NewCommand("cherry-pick").AddArguments("pr-squash-empty"). + WithDir(dstPath). + RunStdString(t.Context()) + assert.NoError(t, err) + + doGitPushTestRepository(dstPath)(t) + + testPullMerge(t, session, elem[1], elem[2], elem[4], MergeOptions{ + Style: repo_model.MergeStyleSquash, + DeleteBranch: false, + }) + }) +} + +func TestPullSquashMessage(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + user2Session := loginUser(t, user2.Name) + + defer test.MockVariableValue(&setting.Repository.PullRequest.PopulateSquashCommentWithCommitMessages, true)() + defer test.MockVariableValue(&setting.Repository.PullRequest.DefaultMergeMessageSize, 80)() + + repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ + Name: "squash-message-test", + Description: "Test squash message", + AutoInit: true, + Readme: "Default", + DefaultBranch: "main", + }) + require.NoError(t, err) + + type commitInfo struct { + userName string + commitMessage string + } + + testCases := []struct { + name string + commitInfos []*commitInfo + expectedMessage string + }{ + { + name: "Single-line messages", + commitInfos: []*commitInfo{ + { + userName: user2.Name, + commitMessage: "commit msg 1", + }, + { + userName: user2.Name, + commitMessage: "commit msg 2", + }, + }, + expectedMessage: `* commit msg 1 + +* commit msg 2 + +`, + }, + { + name: "Multiple-line messages", + commitInfos: []*commitInfo{ + { + userName: user2.Name, + commitMessage: `commit msg 1 + +Commit description.`, + }, + { + userName: user2.Name, + commitMessage: `commit msg 2 + +- Detail 1 +- Detail 2`, + }, + }, + expectedMessage: `* commit msg 1 + +Commit description. + +* commit msg 2 + +- Detail 1 +- Detail 2 + +`, + }, + { + name: "Too long message", + commitInfos: []*commitInfo{ + { + userName: user2.Name, + commitMessage: `loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong message`, + }, + }, + expectedMessage: `* looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo...`, + }, + { + name: "Test Co-authored-by", + commitInfos: []*commitInfo{ + { + userName: user2.Name, + commitMessage: "commit msg 1", + }, + { + userName: "user4", + commitMessage: "commit msg 2", + }, + }, + expectedMessage: `* commit msg 1 + +* commit msg 2 + +--------- + +Co-authored-by: user4 +`, + }, + } + + for tcNum, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + branchName := "test-branch-" + strconv.Itoa(tcNum) + for infoIdx, info := range tc.commitInfos { + createFileOpts := createFileInBranchOptions{ + CommitMessage: info.commitMessage, + CommitterName: info.userName, + CommitterEmail: util.Iif(info.userName != "", info.userName+"@example.com", ""), + OldBranch: util.Iif(infoIdx == 0, "main", branchName), + NewBranch: branchName, + } + testCreateFileInBranch(t, user2, repo, createFileOpts, map[string]string{"dummy-file-" + strconv.Itoa(infoIdx): "dummy content"}) + } + resp := testPullCreateDirectly(t, user2Session, createPullRequestOptions{ + BaseRepoOwner: user2.Name, + BaseRepoName: repo.Name, + BaseBranch: repo.DefaultBranch, + HeadBranch: branchName, + Title: "Pull for " + branchName, + }) + elems := strings.Split(test.RedirectURL(resp), "/") + pullIndex, err := strconv.ParseInt(elems[4], 10, 64) + assert.NoError(t, err) + pullRequest := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, Index: pullIndex}) + squashMergeCommitMessage := pull_service.GetSquashMergeCommitMessages(t.Context(), pullRequest) + assert.Equal(t, tc.expectedMessage, squashMergeCommitMessage) + }) + } + }) +} diff --git a/tests/integration/pull_update_test.go b/tests/integration/pull_update_test.go index eadc61d849..46b3769b79 100644 --- a/tests/integration/pull_update_test.go +++ b/tests/integration/pull_update_test.go @@ -11,7 +11,11 @@ import ( "time" auth_model "code.gitea.io/gitea/models/auth" + git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/perm" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/gitrepo" @@ -58,6 +62,14 @@ func TestAPIPullUpdate(t *testing.T) { }) } +func enableRepoAllowUpdateWithRebase(t *testing.T, repoID int64, allow bool) { + t.Helper() + + repoUnit := unittest.AssertExistsAndLoadBean(t, &repo_model.RepoUnit{RepoID: repoID, Type: unit.TypePullRequests}) + repoUnit.PullRequestsConfig().AllowRebaseUpdate = allow + assert.NoError(t, repo_model.UpdateRepoUnit(t.Context(), repoUnit)) +} + func TestAPIPullUpdateByRebase(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { // Create PR to test @@ -73,10 +85,32 @@ func TestAPIPullUpdateByRebase(t *testing.T) { assert.Equal(t, 1, diffCount.Ahead) assert.NoError(t, pr.LoadIssue(t.Context())) + enableRepoAllowUpdateWithRebase(t, pr.BaseRepo.ID, false) + session := loginUser(t, "user2") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?style=rebase", pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index). AddTokenAuth(token) + session.MakeRequest(t, req, http.StatusForbidden) + + enableRepoAllowUpdateWithRebase(t, pr.BaseRepo.ID, true) + assert.NoError(t, pr.LoadHeadRepo(t.Context())) + + // use a user which have write access to the pr but not write permission to the head repository to do the rebase + user40 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) + err = repo_service.AddOrUpdateCollaborator(t.Context(), pr.BaseRepo, user40, perm.AccessModeWrite) + assert.NoError(t, err) + token40 := getUserToken(t, "user40", auth_model.AccessTokenScopeWriteRepository) + + req = NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?style=rebase", pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index). + AddTokenAuth(token40) + session.MakeRequest(t, req, http.StatusForbidden) + + err = repo_service.AddOrUpdateCollaborator(t.Context(), pr.HeadRepo, user40, perm.AccessModeWrite) + assert.NoError(t, err) + + req = NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?style=rebase", pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index). + AddTokenAuth(token40) session.MakeRequest(t, req, http.StatusOK) // Test GetDiverging after update @@ -87,6 +121,49 @@ func TestAPIPullUpdateByRebase(t *testing.T) { }) } +func TestAPIPullUpdateByRebase2(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // Create PR to test + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26}) + pr := createOutdatedPR(t, user, org26) + assert.NoError(t, pr.LoadBaseRepo(t.Context())) + assert.NoError(t, pr.LoadIssue(t.Context())) + + enableRepoAllowUpdateWithRebase(t, pr.BaseRepo.ID, false) + + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?style=rebase", pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index). + AddTokenAuth(token) + session.MakeRequest(t, req, http.StatusForbidden) + + enableRepoAllowUpdateWithRebase(t, pr.BaseRepo.ID, true) + assert.NoError(t, pr.LoadHeadRepo(t.Context())) + + // add a protected branch rule to the head branch to block rebase + pb := git_model.ProtectedBranch{ + RepoID: pr.HeadRepo.ID, + RuleName: pr.HeadBranch, + CanPush: false, + CanForcePush: false, + } + err := git_model.UpdateProtectBranch(t.Context(), pr.HeadRepo, &pb, git_model.WhitelistOptions{}) + assert.NoError(t, err) + req = NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?style=rebase", pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index). + AddTokenAuth(token) + session.MakeRequest(t, req, http.StatusForbidden) + + // remove the protected branch rule to allow rebase + err = git_model.DeleteProtectedBranch(t.Context(), pr.HeadRepo, pb.ID) + assert.NoError(t, err) + + req = NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?style=rebase", pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index). + AddTokenAuth(token) + session.MakeRequest(t, req, http.StatusOK) + }) +} + func createOutdatedPR(t *testing.T, actor, forkOrg *user_model.User) *issues_model.PullRequest { baseRepo, err := repo_service.CreateRepository(t.Context(), actor, actor, repo_service.CreateRepoOptions{ Name: "repo-pr-update", diff --git a/tests/integration/repo_tag_test.go b/tests/integration/repo_tag_test.go index b31d9bf4a2..93ed163235 100644 --- a/tests/integration/repo_tag_test.go +++ b/tests/integration/repo_tag_test.go @@ -149,12 +149,18 @@ func TestRepushTag(t *testing.T) { // delete the tag _, _, err = gitcmd.NewCommand("push", "origin", "--delete", "v2.0").WithDir(dstPath).RunStdString(t.Context()) assert.NoError(t, err) - // query the release by API and it should be a draft + + // query the release by API with no auth and it should be 404 req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, "v2.0")) + MakeRequest(t, req, http.StatusNotFound) + + // query the release by API and it should be a draft + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, "v2.0")).AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) var respRelease *api.Release DecodeJSON(t, resp, &respRelease) assert.True(t, respRelease.IsDraft) + // re-push the tag _, _, err = gitcmd.NewCommand("push", "origin", "--tags", "v2.0").WithDir(dstPath).RunStdString(t.Context()) assert.NoError(t, err) diff --git a/tests/sqlite.ini.tmpl b/tests/sqlite.ini.tmpl index 9d184bce6a..61f7e2a46d 100644 --- a/tests/sqlite.ini.tmpl +++ b/tests/sqlite.ini.tmpl @@ -122,7 +122,7 @@ RENDER_CONTENT_MODE = sanitized [markup.no-sanitizer] ENABLED = true FILE_EXTENSIONS = .no-sanitizer -RENDER_COMMAND = echo '' +RENDER_COMMAND = go run tools/test-echo.go ; This test case is reused, at first it is used to test "no-sanitizer" (sandbox doesn't take effect here) ; Then it will be updated and used to test "iframe + sandbox-disabled" RENDER_CONTENT_MODE = no-sanitizer diff --git a/web_src/css/base.css b/web_src/css/base.css index a09839ea1e..be28cd6fea 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -626,7 +626,6 @@ img.ui.avatar, font-family: var(--fonts-monospace); font-size: 13px; font-weight: var(--font-weight-normal); - padding: 3px 5px; flex-shrink: 0; } diff --git a/web_src/css/features/heatmap.css b/web_src/css/features/heatmap.css index c064590c46..e40adf1fe4 100644 --- a/web_src/css/features/heatmap.css +++ b/web_src/css/features/heatmap.css @@ -4,23 +4,44 @@ position: relative; } -/* before the Vue component is mounted, show a loading indicator with dummy size */ -/* the ratio is guesswork, see https://github.com/razorness/vue3-calendar-heatmap/issues/26 */ -#user-heatmap.is-loading { - aspect-ratio: 5.415; /* the size is about 790 x 145 */ +.activity-heatmap-container { + container-type: inline-size; } -.user.profile #user-heatmap.is-loading { - aspect-ratio: 5.645; /* the size is about 953 x 169 */ + +@container (width > 0) { + #user-heatmap { + /* Set element to fixed height so that it does not resize after load. The calculation is complex + because the element does not scale with a fixed aspect ratio. */ + height: calc((100cqw / 5) - (100cqw / 25) + 20px); + } +} + +/* Fallback height adjustment above for browsers that don't support container queries */ +@supports not (container-type: inline-size) { + /* Before the Vue component is mounted, show a loading indicator with dummy size */ + /* The ratio is guesswork for legacy browsers, new browsers use the "@container" approach above */ + #user-heatmap.is-loading { + aspect-ratio: 5.4823972051; /* the size is about 816 x 148.84 */ + } + .user.profile #user-heatmap.is-loading { + aspect-ratio: 5.6290608387; /* the size is about 953 x 169.3 */ + } } #user-heatmap text { fill: currentcolor !important; } +/* root legend */ +#user-heatmap .vch__container > .vch__legend { + display: flex; + font-size: 11px; + justify-content: space-between; +} + /* for the "Less" and "More" legend */ #user-heatmap .vch__legend .vch__legend { display: flex; - font-size: 11px; align-items: center; justify-content: right; } @@ -34,25 +55,3 @@ #user-heatmap .vch__day__square:hover { outline: 1.5px solid var(--color-text); } - -/* move the "? contributions in the last ? months" text from top to bottom */ -#user-heatmap .total-contributions { - font-size: 11px; - position: absolute; - bottom: 0; - left: 25px; -} - -@media (max-width: 1200px) { - #user-heatmap .total-contributions { - left: 21px; - } -} - -@media (max-width: 1000px) { - #user-heatmap .total-contributions { - font-size: 10px; - left: 17px; - bottom: -4px; - } -} diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 070623d24e..9b70e0e6db 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -387,6 +387,7 @@ td .commit-summary { .repository.view.issue .pull-desc code { color: var(--color-primary); + background: transparent; } .repository.view.issue .pull-desc a[data-clipboard-text] { @@ -527,9 +528,12 @@ td .commit-summary { } .repository.view.issue .comment-list .timeline-item .comment-text-line { + /* TODO: this "line-height" is not ideal (actually it is abused), many layouts depend on this magic value, + for example: alignment of the header arrow and the avatar, view PR commit list left icon layout, dismiss review with reason, etc */ line-height: 32px; vertical-align: middle; color: var(--color-text-light); + min-width: 0; } .repository.view.issue .comment-list .timeline-item .comment-text-line .ui.label { @@ -600,9 +604,6 @@ td .commit-summary { width: 100%; margin: 0; } - .repository.view.issue .comment-list .comment .content .form .button:not(:last-child) { - margin-bottom: 1rem; - } } .repository.view.issue .comment-list .comment .merge-section { @@ -653,7 +654,7 @@ td .commit-summary { .repository.view.issue .comment-list .code-comment { border: 1px solid transparent; - margin: 0; + padding: 8px; } .repository.view.issue .comment-list .code-comment .comment-header { @@ -663,6 +664,7 @@ td .commit-summary { } .repository.view.issue .comment-list .code-comment .comment-content { + margin-top: 6px; margin-left: 24px; } @@ -1285,9 +1287,9 @@ td .commit-summary { box-shadow: 0 0 0 3px var(--color-primary-alpha-30) !important; } -.comment:target .header::before { +.comment:target .comment-header::before { border-right-color: var(--color-primary) !important; - filter: drop-shadow(-3px 0 0 var(--color-primary-alpha-30)) !important; + filter: drop-shadow(-4px 0 0 var(--color-primary-alpha-30)) !important; } .code-comment:target, @@ -1307,7 +1309,6 @@ td .commit-summary { padding: 0.5em 1rem; position: relative; color: var(--color-text); - min-height: 41px; display: flex; justify-content: space-between; align-items: center; @@ -1315,6 +1316,10 @@ td .commit-summary { gap: 0.25em; } +.comment-header.avatar-content-left-arrow { + min-height: 41px; /* for a comment header with left arrow, the arrow is absolutely positioned, but the header content varies (for example: no "roles", etc), so it needs a min-height */ +} + .comment-header.avatar-content-left-arrow::after { border-right-color: var(--color-box-header); } @@ -1338,7 +1343,7 @@ td .commit-summary { .comment-header-right { display: flex; align-items: center; - gap: 0.5em; + gap: 6px; } .comment-header-right { @@ -1346,6 +1351,10 @@ td .commit-summary { justify-content: end; } +.comment-header-right > .item.action { + padding: 4px; /* add some padding to make click area larger for the "item action ... ui dropdown" items */ +} + .comment-body { background: var(--color-box-body); border: none !important; diff --git a/web_src/css/repo/reactions.css b/web_src/css/repo/reactions.css index 8fe01af4f0..f7db80fbbc 100644 --- a/web_src/css/repo/reactions.css +++ b/web_src/css/repo/reactions.css @@ -41,16 +41,16 @@ margin-left: 4px; } -.ui.dropdown.select-reaction .menu { - min-width: 170px; /* item-outer-width * 4 */ +.ui.dropdown.select-reaction .menu.visible { + display: grid !important; + grid-template-columns: repeat(4, 1fr); + padding: 4px; } .ui.dropdown.select-reaction .menu > .item { - float: left; - margin: 4px; - font-size: 20px; width: 34px; height: 34px; + font-size: 16px; border-radius: var(--border-radius); display: flex; align-items: center; diff --git a/web_src/css/repo/wiki.css b/web_src/css/repo/wiki.css index 144cb1206c..831a7752c3 100644 --- a/web_src/css/repo/wiki.css +++ b/web_src/css/repo/wiki.css @@ -1,7 +1,3 @@ -.repository.wiki .wiki-pages-list tr:hover { - background-color: var(--color-hover); -} - .repository.wiki .wiki-pages-list .wiki-git-entry { margin-left: 10px; display: none; diff --git a/web_src/css/review.css b/web_src/css/review.css index 39916d1bd8..9e320346d8 100644 --- a/web_src/css/review.css +++ b/web_src/css/review.css @@ -52,7 +52,7 @@ } .comment-code-cloud { - padding: 0.5rem 1rem !important; + padding: 0.5rem !important; position: relative; } diff --git a/web_src/css/themes/theme-gitea-auto-tritanopia.css b/web_src/css/themes/theme-gitea-auto-tritanopia.css new file mode 100644 index 0000000000..178a23983b --- /dev/null +++ b/web_src/css/themes/theme-gitea-auto-tritanopia.css @@ -0,0 +1,8 @@ +@import "./theme-gitea-light-tritanopia.css" (prefers-color-scheme: light); +@import "./theme-gitea-dark-tritanopia.css" (prefers-color-scheme: dark); + +gitea-theme-meta-info { + --theme-display-name: "Auto"; + --theme-colorblind-type: "blue-yellow"; + --theme-color-scheme: "auto"; +} diff --git a/web_src/css/themes/theme-gitea-dark-tritanopia.css b/web_src/css/themes/theme-gitea-dark-tritanopia.css new file mode 100644 index 0000000000..1dbd9967dd --- /dev/null +++ b/web_src/css/themes/theme-gitea-dark-tritanopia.css @@ -0,0 +1,15 @@ +@import "./theme-gitea-dark-protanopia-deuteranopia.css"; + +gitea-theme-meta-info { + --theme-display-name: "Dark"; + --theme-colorblind-type: "blue-yellow"; + --theme-color-scheme: "dark"; +} + +/* blue/yellow colorblind-friendly colors */ +/* from GitHub: blue yellow blindness is based on red green blindness, and --diffBlob-deletion-* restored to the normal theme color */ +:root { + --color-diff-removed-linenum-bg: #482121; + --color-diff-removed-row-bg: #301e1e; + --color-diff-removed-word-bg: #6f3333; +} diff --git a/web_src/css/themes/theme-gitea-light-tritanopia.css b/web_src/css/themes/theme-gitea-light-tritanopia.css new file mode 100644 index 0000000000..a50fd9c1a4 --- /dev/null +++ b/web_src/css/themes/theme-gitea-light-tritanopia.css @@ -0,0 +1,15 @@ +@import "./theme-gitea-light-protanopia-deuteranopia.css"; + +gitea-theme-meta-info { + --theme-display-name: "Light"; + --theme-colorblind-type: "blue-yellow"; + --theme-color-scheme: "light"; +} + +/* blue/yellow colorblind-friendly colors */ +/* from GitHub: blue yellow blindness is based on red green blindness, and --diffBlob-deletion-* restored to the normal theme color */ +:root { + --color-diff-removed-linenum-bg: #ffcecb; + --color-diff-removed-row-bg: #ffeef0; + --color-diff-removed-word-bg: #fdb8c0; +} diff --git a/web_src/js/components/ActivityHeatmap.vue b/web_src/js/components/ActivityHeatmap.vue index 296cb61cff..d805817630 100644 --- a/web_src/js/components/ActivityHeatmap.vue +++ b/web_src/js/components/ActivityHeatmap.vue @@ -53,9 +53,6 @@ function handleDayClick(e: Event & {date: Date}) { } diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index 300f635793..00748ee9bb 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -489,7 +489,7 @@ export default defineComponent({ -

    @@ -520,7 +520,7 @@ export default defineComponent({ {{ job.name }} - + {{ job.duration }} diff --git a/web_src/js/features/pull-view-file.ts b/web_src/js/features/pull-view-file.ts index 1124886238..e563c13ef5 100644 --- a/web_src/js/features/pull-view-file.ts +++ b/web_src/js/features/pull-view-file.ts @@ -20,14 +20,6 @@ function refreshViewedFilesSummary() { .replace('%[2]d', prReview.numberOfFiles); } -// Explicitly recounts how many files the user has currently reviewed by counting the number of checked "viewed" checkboxes -// Additionally, the viewed files summary will be updated if it exists -export function countAndUpdateViewedFiles() { - // The number of files is constant, but the number of viewed files can change because files can be loaded dynamically - prReview.numberOfViewedFiles = document.querySelectorAll(`${viewedCheckboxSelector} > input[type=checkbox][checked]`).length; - refreshViewedFilesSummary(); -} - // Initializes a listener for all children of the given html element // (for example 'document' in the most basic case) // to watch for changes of viewed-file checkboxes diff --git a/web_src/js/features/repo-diff.ts b/web_src/js/features/repo-diff.ts index 20cec2939d..6f5cb2f63b 100644 --- a/web_src/js/features/repo-diff.ts +++ b/web_src/js/features/repo-diff.ts @@ -2,7 +2,7 @@ import {initRepoIssueContentHistory} from './repo-issue-content.ts'; import {initDiffFileTree} from './repo-diff-filetree.ts'; import {initDiffCommitSelect} from './repo-diff-commitselect.ts'; import {validateTextareaNonEmpty} from './comp/ComboMarkdownEditor.ts'; -import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.ts'; +import {initViewedCheckboxListenerFor, initExpandAndCollapseFilesButton} from './pull-view-file.ts'; import {initImageDiff} from './imagediff.ts'; import {showErrorToast} from '../modules/toast.ts'; import {submitEventSubmitter, queryElemSiblings, hideElem, showElem, animateOnce, addDelegatedEventListener, createElementFromHTML, queryElems} from '../utils/dom.ts'; @@ -152,7 +152,6 @@ function onShowMoreFiles() { // TODO: replace these calls with the "observer.ts" methods initRepoIssueContentHistory(); initViewedCheckboxListenerFor(); - countAndUpdateViewedFiles(); initImageDiff(); initDiffHeaderPopup(); } diff --git a/web_src/js/index-domready.ts b/web_src/js/index-domready.ts index ecd8cb1baa..df56c85c86 100644 --- a/web_src/js/index-domready.ts +++ b/web_src/js/index-domready.ts @@ -1,4 +1,3 @@ -import './globals.ts'; import '../fomantic/build/fomantic.js'; import '../../node_modules/easymde/dist/easymde.min.css'; // TODO: lazy load in "switchToEasyMDE" diff --git a/web_src/js/index.ts b/web_src/js/index.ts index af53cc488c..153b8049c9 100644 --- a/web_src/js/index.ts +++ b/web_src/js/index.ts @@ -1,5 +1,10 @@ // bootstrap module must be the first one to be imported, it handles webpack lazy-loading and global errors import './bootstrap.ts'; + +// many users expect to use jQuery in their custom scripts (https://docs.gitea.com/administration/customizing-gitea#example-plantuml) +// so load globals (including jQuery) as early as possible +import './globals.ts'; + import './webcomponents/index.ts'; import {onDomReady} from './utils/dom.ts'; diff --git a/web_src/js/modules/init.ts b/web_src/js/modules/init.ts index 538fafd83f..d98c68ce43 100644 --- a/web_src/js/modules/init.ts +++ b/web_src/js/modules/init.ts @@ -1,6 +1,6 @@ export class InitPerformanceTracer { results: {name: string, dur: number}[] = []; - recordCall(name: string, func: ()=>void) { + recordCall(name: string, func: () => void) { const start = performance.now(); func(); this.results.push({name, dur: performance.now() - start}); diff --git a/web_src/svg/gitea-colorblind-blueyellow.svg b/web_src/svg/gitea-colorblind-blueyellow.svg new file mode 100644 index 0000000000..752ff88432 --- /dev/null +++ b/web_src/svg/gitea-colorblind-blueyellow.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file