Merge branch 'main' into main

This commit is contained in:
Karthik Bhandary
2026-04-02 10:58:20 +05:30
committed by GitHub
335 changed files with 7929 additions and 5144 deletions
-1
View File
@@ -20,7 +20,6 @@
"customizations": {
"vscode": {
"settings": {},
// same extensions as Gitpod, should match /.gitpod.yml
"extensions": [
"editorconfig.editorconfig",
"dbaeumer.vscode-eslint",
-1
View File
@@ -43,7 +43,6 @@ modifies/internal:
- ".editorconfig"
- ".eslintrc.cjs"
- ".golangci.yml"
- ".gitpod.yml"
- ".markdownlint.yaml"
- ".spectral.yaml"
- "stylelint.config.*"
+1 -2
View File
@@ -79,6 +79,7 @@ cpu.out
/yarn-error.log
/npm-debug.log*
/.pnpm-store
/public/assets/.vite
/public/assets/js
/public/assets/css
/public/assets/fonts
@@ -87,8 +88,6 @@ cpu.out
/VERSION
/.air
# Files and folders that were previously generated
/public/assets/img/webpack
# Snapcraft
/gitea_a*.txt
-51
View File
@@ -1,51 +0,0 @@
tasks:
- name: Setup
init: |
cp -r contrib/ide/vscode .vscode
make deps
make build
command: |
gp sync-done setup
exit 0
- name: Run backend
command: |
gp sync-await setup
# Get the URL and extract the domain
url=$(gp url 3000)
domain=$(echo $url | awk -F[/:] '{print $4}')
if [ -f custom/conf/app.ini ]; then
sed -i "s|^ROOT_URL =.*|ROOT_URL = ${url}/|" custom/conf/app.ini
sed -i "s|^DOMAIN =.*|DOMAIN = ${domain}|" custom/conf/app.ini
sed -i "s|^SSH_DOMAIN =.*|SSH_DOMAIN = ${domain}|" custom/conf/app.ini
sed -i "s|^NO_REPLY_ADDRESS =.*|SSH_DOMAIN = noreply.${domain}|" custom/conf/app.ini
else
mkdir -p custom/conf/
echo -e "[server]\nROOT_URL = ${url}/" > custom/conf/app.ini
echo -e "\n[database]\nDB_TYPE = sqlite3\nPATH = $GITPOD_REPO_ROOT/data/gitea.db" >> custom/conf/app.ini
fi
export TAGS="sqlite sqlite_unlock_notify"
make watch-backend
- name: Run frontend
command: |
gp sync-await setup
make watch-frontend
openMode: split-right
vscode:
extensions:
- editorconfig.editorconfig
- dbaeumer.vscode-eslint
- golang.go
- stylelint.vscode-stylelint
- DavidAnson.vscode-markdownlint
- Vue.volar
- ms-azuretools.vscode-docker
- vitest.explorer
- cweijan.vscode-database-client2
- GitHub.vscode-pull-request-github
ports:
- name: Gitea
port: 3000
+5 -2
View File
@@ -4,5 +4,8 @@
- Run `make tidy` after any `go.mod` changes
- Add the current year into the copyright header of new `.go` files
- Ensure no trailing whitespace in edited files
- Never force-push to pull request branches
- Always start issue and pull request comments with an authorship attribution
- Never force-push, amend, or squash unless asked. Use new commits and normal push for pull request updates
- Preserve existing code comments, do not remove or rewrite comments that are still relevant
- In TypeScript, use `!` (non-null assertion) instead of `?.`/`??` when a value is known to always exist
- Include authorship attribution in issue and pull request comments
- Add `Co-Authored-By` lines to all commits, indicating name and model used
+15 -12
View File
@@ -70,16 +70,18 @@ For configuring IDEs for Gitea development, see the [contributed IDE configurati
## AI Contribution Policy
Contributions made with the assistance of AI tools are welcome, but contributors must use them responsibly.
Contributions made with the assistance of AI tools are welcome, but contributors must use them responsibly and disclose that use clearly.
1. Include related issues or pull requests in the prompt so that the AI has ideal context.
2. Review AI-generated code closely before submitting a pull request.
3. Manually test the changes and add appropriate automated tests where feasible.
4. Only use AI to assist in contributions that you understand well enough to respond to feedback without relying on AI.
5. Indicate AI-generated content in issue and pull requests descriptions and comments. Specify which model was used.
6. Do not use AI to reply to questions about your issue or pull request. The questions are for you, not an AI model.
1. Review AI-generated code closely before marking a pull request ready for review.
2. Manually test the changes and add appropriate automated tests where feasible.
3. Only use AI to assist in contributions that you understand well enough to explain, defend, and revise yourself during review.
4. Disclose AI-assisted content clearly.
5. Do not use AI to reply to questions about your issue or pull request. The questions are for you, not an AI model.
6. AI may be used to help draft issues and pull requests, but contributors remain responsible for the accuracy, completeness, and intent of what they submit.
Maintainers reserve the right to close pull requests and issues that appear to be low-quality AI-generated content. We welcome new contributors, but cannot sustain the effort of supporting contributors who primarily defer to AI rather than engaging substantively with the review process.
Maintainers reserve the right to close pull requests and issues that do not disclose AI assistance, that appear to be low-quality AI-generated content, or where the contributor cannot explain or defend the proposed changes themselves.
We welcome new contributors, but cannot sustain the effort of supporting contributors who primarily defer to AI rather than engaging substantively with the review process.
## Issues
@@ -196,10 +198,11 @@ Here's how to run the test suite:
- E2E test environment variables
| Variable | Description |
| :------------------------ | :---------------------------------------------------------------- |
| ``GITEA_TEST_E2E_DEBUG`` | When set, show Gitea server output |
| ``GITEA_TEST_E2E_FLAGS`` | Additional flags passed to Playwright, for example ``--ui`` |
| Variable | Description |
| :-------------------------------- | :---------------------------------------------------------- |
| ``GITEA_TEST_E2E_DEBUG`` | When set, show Gitea server output |
| ``GITEA_TEST_E2E_FLAGS`` | Additional flags passed to Playwright, for example ``--ui`` |
| ``GITEA_TEST_E2E_TIMEOUT_FACTOR`` | Timeout multiplier (default: 3 on CI, 1 locally) |
## Translation
+1 -1
View File
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
# Build frontend on the native platform to avoid QEMU-related issues with esbuild/webpack
# Build frontend on the native platform to avoid QEMU-related issues with nodejs ecosystem
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.26-alpine3.23 AS frontend-build
RUN apk --no-cache add build-base git nodejs pnpm
WORKDIR /src
+1 -1
View File
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
# Build frontend on the native platform to avoid QEMU-related issues with esbuild/webpack
# Build frontend on the native platform to avoid QEMU-related issues with nodejs ecosystem
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.26-alpine3.23 AS frontend-build
RUN apk --no-cache add build-base git nodejs pnpm
WORKDIR /src
+44 -48
View File
@@ -15,7 +15,7 @@ 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.11.2
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1
@@ -81,15 +81,6 @@ STORED_VERSION_FILE := VERSION
GITHUB_REF_TYPE ?= branch
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
# Enable typescript support in Node.js before 22.18
# TODO: Remove this once we can raise the minimum Node.js version to 22.18 (alpine >= 3.23)
NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v 2>/dev/null | cut -c2- | sed 's/-.*//' | tr '.' ' '))
ifeq ($(shell test "$(NODE_VERSION)" -lt "022018000"; echo $$?),0)
NODE_VARS := NODE_OPTIONS="--experimental-strip-types"
else
NODE_VARS :=
endif
ifneq ($(GITHUB_REF_TYPE),branch)
VERSION ?= $(subst v,,$(GITHUB_REF_NAME))
GITEA_VERSION ?= $(VERSION)
@@ -120,10 +111,11 @@ LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/r
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration,$(shell $(GO) list ./... | grep -v /vendor/))
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
WEBPACK_CONFIGS := webpack.config.ts tailwind.config.ts
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts
FRONTEND_SOURCES := $(shell find web_src/js web_src/css -type f)
FRONTEND_CONFIGS := vite.config.ts tailwind.config.ts
FRONTEND_DEST := public/assets/.vite/manifest.json
FRONTEND_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts public/assets/.vite
FRONTEND_DEV_LOG_LEVEL ?= warn
BINDATA_DEST_WILDCARD := modules/migration/bindata.* modules/public/bindata.* modules/options/bindata.* modules/templates/bindata.*
@@ -199,7 +191,7 @@ git-check:
.PHONY: clean-all
clean-all: clean ## delete backend, frontend and integration files
rm -rf $(WEBPACK_DEST_ENTRIES) node_modules
rm -rf $(FRONTEND_DEST_ENTRIES) node_modules
.PHONY: clean
clean: ## delete backend and integration files
@@ -293,33 +285,33 @@ lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backen
.PHONY: lint-js
lint-js: node_modules ## lint js and ts files
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY) $(ESLINT_FILES)
$(NODE_VARS) pnpm exec vue-tsc
pnpm exec eslint --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY) $(ESLINT_FILES)
pnpm exec vue-tsc
.PHONY: lint-js-fix
lint-js-fix: node_modules ## lint js and ts files and fix issues
$(NODE_VARS) pnpm exec eslint --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY) $(ESLINT_FILES) --fix
$(NODE_VARS) pnpm exec vue-tsc
pnpm exec eslint --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY) $(ESLINT_FILES) --fix
pnpm exec vue-tsc
.PHONY: lint-css
lint-css: node_modules ## lint css files
$(NODE_VARS) pnpm exec stylelint --color --max-warnings=0 $(STYLELINT_FILES)
pnpm exec stylelint --color --max-warnings=0 $(STYLELINT_FILES)
.PHONY: lint-css-fix
lint-css-fix: node_modules ## lint css files and fix issues
$(NODE_VARS) pnpm exec stylelint --color --max-warnings=0 $(STYLELINT_FILES) --fix
pnpm exec stylelint --color --max-warnings=0 $(STYLELINT_FILES) --fix
.PHONY: lint-swagger
lint-swagger: node_modules ## lint swagger files
$(NODE_VARS) pnpm exec spectral lint -q -F hint $(SWAGGER_SPEC)
pnpm exec spectral lint -q -F hint $(SWAGGER_SPEC)
.PHONY: lint-md
lint-md: node_modules ## lint markdown files
$(NODE_VARS) pnpm exec markdownlint *.md
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
pnpm exec markdownlint --fix *.md
.PHONY: lint-spell
lint-spell: ## lint spelling
@@ -369,20 +361,19 @@ lint-yaml: .venv ## lint yaml files
.PHONY: lint-json
lint-json: node_modules ## lint json files
$(NODE_VARS) pnpm exec eslint -c eslint.json.config.ts --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY)
pnpm exec eslint -c eslint.json.config.ts --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY)
.PHONY: lint-json-fix
lint-json-fix: node_modules ## lint and fix json files
$(NODE_VARS) pnpm exec eslint -c eslint.json.config.ts --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY) --fix
pnpm exec eslint -c eslint.json.config.ts --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY) --fix
.PHONY: watch
watch: ## watch everything and continuously rebuild
@bash tools/watch.sh
.PHONY: watch-frontend
watch-frontend: node_modules ## watch frontend files and continuously rebuild
@rm -rf $(WEBPACK_DEST_ENTRIES)
NODE_ENV=development $(NODE_VARS) pnpm exec webpack --watch --progress
watch-frontend: node_modules ## start vite dev server for frontend
NODE_ENV=development pnpm exec vite --logLevel $(FRONTEND_DEV_LOG_LEVEL)
.PHONY: watch-backend
watch-backend: ## watch backend files and continuously rebuild
@@ -398,7 +389,7 @@ test-backend: ## test backend files
.PHONY: test-frontend
test-frontend: node_modules ## test frontend files
$(NODE_VARS) pnpm exec vitest
pnpm exec vitest
.PHONY: test-check
test-check:
@@ -534,7 +525,7 @@ test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test
.PHONY: playwright
playwright: deps-frontend
@# on GitHub Actions VMs, playwright's system deps are pre-installed
@$(NODE_VARS) pnpm exec playwright install $(if $(GITHUB_ACTIONS),,--with-deps) chromium $(if $(CI),firefox) $(PLAYWRIGHT_FLAGS)
@pnpm exec playwright install $(if $(GITHUB_ACTIONS),,--with-deps) chromium $(if $(CI),firefox) $(PLAYWRIGHT_FLAGS)
.PHONY: test-e2e
test-e2e: playwright $(EXECUTABLE_E2E)
@@ -645,7 +636,7 @@ install: $(wildcard *.go)
build: frontend backend ## build everything
.PHONY: frontend
frontend: $(WEBPACK_DEST) ## build frontend files
frontend: $(FRONTEND_DEST) ## build frontend files
.PHONY: backend
backend: generate-backend $(EXECUTABLE) ## build backend files
@@ -672,7 +663,7 @@ ifneq ($(and $(STATIC),$(findstring pam,$(TAGS))),)
endif
CGO_ENABLED="$(CGO_ENABLED)" CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(EXTLDFLAGS) $(LDFLAGS)' -o $@
$(EXECUTABLE_E2E): $(GO_SOURCES) $(WEBPACK_DEST)
$(EXECUTABLE_E2E): $(GO_SOURCES) $(FRONTEND_DEST)
CGO_ENABLED=1 $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TEST_TAGS)' -ldflags '-s -w $(EXTLDFLAGS) $(LDFLAGS)' -o $@
.PHONY: release
@@ -750,7 +741,7 @@ deps-tools: ## install tool dependencies
wait
node_modules: pnpm-lock.yaml
$(NODE_VARS) pnpm install --frozen-lockfile
pnpm install --frozen-lockfile
@touch node_modules
.venv: uv.lock
@@ -758,33 +749,38 @@ node_modules: pnpm-lock.yaml
@touch .venv
.PHONY: update
update: update-js update-py ## update js and py dependencies
update: update-go update-js update-py ## update dependencies
.PHONY: update-go
update-go: ## update go dependencies
$(GO) get -u ./...
$(MAKE) tidy
.PHONY: update-js
update-js: node_modules ## update js dependencies
$(NODE_VARS) pnpm exec updates -u -f package.json
pnpm exec updates -u -f package.json
rm -rf node_modules pnpm-lock.yaml
$(NODE_VARS) pnpm install
$(NODE_VARS) pnpm exec nolyfill install
$(NODE_VARS) pnpm install
pnpm install
pnpm exec nolyfill install
pnpm install
@touch node_modules
.PHONY: update-py
update-py: node_modules ## update py dependencies
$(NODE_VARS) pnpm exec updates -u -f pyproject.toml
pnpm exec updates -u -f pyproject.toml
rm -rf .venv uv.lock
uv sync
@touch .venv
.PHONY: webpack
webpack: $(WEBPACK_DEST) ## build webpack files
.PHONY: vite
vite: $(FRONTEND_DEST) ## build vite files
$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) pnpm-lock.yaml
$(FRONTEND_DEST): $(FRONTEND_SOURCES) $(FRONTEND_CONFIGS) pnpm-lock.yaml
@$(MAKE) -s node_modules
@rm -rf $(WEBPACK_DEST_ENTRIES)
@echo "Running webpack..."
@BROWSERSLIST_IGNORE_OLD_DATA=true $(NODE_VARS) pnpm exec webpack
@touch $(WEBPACK_DEST)
@rm -rf $(FRONTEND_DEST_ENTRIES)
@echo "Running vite build..."
@pnpm exec vite build
@touch $(FRONTEND_DEST)
.PHONY: svg
svg: node_modules ## build svg files
@@ -803,7 +799,7 @@ svg-check: svg
.PHONY: lockfile-check
lockfile-check:
$(NODE_VARS) pnpm install --frozen-lockfile
pnpm install --frozen-lockfile
@diff=$$(git diff --color=always pnpm-lock.yaml); \
if [ -n "$$diff" ]; then \
echo "pnpm-lock.yaml is inconsistent with package.json"; \
-1
View File
@@ -8,7 +8,6 @@
[![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
[![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
[![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT")
[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea)
[![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin")
[繁體中文](./README.zh-tw.md) | [简体中文](./README.zh-cn.md)
-1
View File
@@ -8,7 +8,6 @@
[![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
[![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
[![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT")
[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea)
[![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin")
[English](./README.md) | [繁體中文](./README.zh-tw.md)
-1
View File
@@ -8,7 +8,6 @@
[![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
[![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
[![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT")
[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea)
[![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin")
[English](./README.md) | [简体中文](./README.zh-cn.md)
+49 -24
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -16,7 +16,7 @@ import (
"strconv"
"strings"
"github.com/google/go-github/v74/github"
"github.com/google/go-github/v84/github"
"github.com/urfave/cli/v3"
"gopkg.in/yaml.v3"
)
+12 -4
View File
@@ -329,7 +329,7 @@ export default defineConfig([
'github/no-innerText': [2],
'github/no-then': [2],
'github/no-useless-passive': [2],
'github/prefer-observers': [2],
'github/prefer-observers': [0],
'github/require-passive-events': [2],
'github/unescaped-html-literal': [2],
'grouped-accessor-pairs': [2],
@@ -572,7 +572,11 @@ export default defineConfig([
'no-restricted-exports': [0],
'no-restricted-globals': [2, ...restrictedGlobals],
'no-restricted-properties': [2, ...restrictedProperties],
'no-restricted-imports': [0],
'no-restricted-imports': [2, {paths: [
{name: 'jquery', message: 'Use the global $ instead', allowTypeImports: true},
{name: 'htmx.org', message: 'Use the global htmx instead', allowTypeImports: true},
{name: 'idiomorph/htmx', message: 'Loaded in globals.ts', allowTypeImports: true},
]}],
'no-restricted-syntax': [2, 'WithStatement', 'ForInStatement', 'LabeledStatement', 'SequenceExpression'],
'no-return-assign': [0],
'no-script-url': [2],
@@ -762,6 +766,7 @@ export default defineConfig([
'unicorn/catch-error-name': [0],
'unicorn/consistent-destructuring': [2],
'unicorn/consistent-empty-array-spread': [2],
'unicorn/consistent-template-literal-escape': [2],
'unicorn/consistent-existence-index-check': [0],
'unicorn/consistent-function-scoping': [0],
'unicorn/custom-error-definition': [0],
@@ -817,6 +822,7 @@ export default defineConfig([
'unicorn/no-unused-properties': [2],
'unicorn/no-useless-collection-argument': [2],
'unicorn/no-useless-fallback-in-spread': [2],
'unicorn/no-useless-iterator-to-array': [2],
'unicorn/no-useless-length-check': [2],
'unicorn/no-useless-promise-resolve-reject': [2],
'unicorn/no-useless-spread': [2],
@@ -866,6 +872,7 @@ export default defineConfig([
'unicorn/prefer-response-static-json': [2],
'unicorn/prefer-set-has': [0],
'unicorn/prefer-set-size': [2],
'unicorn/prefer-simple-condition-first': [0],
'unicorn/prefer-spread': [0],
'unicorn/prefer-string-raw': [0],
'unicorn/prefer-string-replace-all': [0],
@@ -884,6 +891,7 @@ export default defineConfig([
'unicorn/require-post-message-target-origin': [0],
'unicorn/string-content': [0],
'unicorn/switch-case-braces': [0],
'unicorn/switch-case-break-position': [2],
'unicorn/template-indent': [2],
'unicorn/text-encoding-identifier-case': [0],
'unicorn/throw-new-error': [2],
@@ -1009,11 +1017,11 @@ export default defineConfig([
},
},
{
files: ['*', 'tools/**/*'],
files: ['*', 'tools/**/*', 'tests/**/*'],
languageOptions: {globals: globals.nodeBuiltin},
},
{
files: ['web_src/**/*'],
languageOptions: {globals: {...globals.browser, ...globals.webpack}},
languageOptions: {globals: {...globals.browser, ...globals.jquery, htmx: false}},
},
]);
Generated
+3 -3
View File
@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1773821835,
"narHash": "sha256-TJ3lSQtW0E2JrznGVm8hOQGVpXjJyXY2guAxku2O9A4=",
"lastModified": 1774386573,
"narHash": "sha256-4hAV26quOxdC6iyG7kYaZcM3VOskcPUrdCQd/nx8obc=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "b40629efe5d6ec48dd1efba650c797ddbd39ace0",
"rev": "46db2e09e1d3f113a13c0d7b81e2f221c63b8ce9",
"type": "github"
},
"original": {
+2 -2
View File
@@ -33,9 +33,9 @@
inherit (pkgs) lib;
# only bump toolchain versions here
go = pkgs.go_1_25;
go = pkgs.go_1_26;
nodejs = pkgs.nodejs_24;
python3 = pkgs.python312;
python3 = pkgs.python314;
pnpm = pkgs.pnpm_10;
# Platform-specific dependencies
+115 -106
View File
@@ -9,7 +9,7 @@ godebug x509negativeserial=1
require (
code.gitea.io/actions-proto-go v0.4.1
code.gitea.io/sdk/gitea v0.23.2
code.gitea.io/sdk/gitea v0.24.1
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
connectrpc.com/connect v1.19.1
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed
@@ -18,22 +18,22 @@ require (
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
github.com/42wim/sshsig v0.0.0-20250502153856-5100632e8920
github.com/42wim/httpsig v1.2.4
github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
github.com/ProtonMail/go-crypto v1.3.0
github.com/PuerkitoBio/goquery v1.11.0
github.com/Azure/go-ntlmssp v0.1.0
github.com/ProtonMail/go-crypto v1.4.1
github.com/PuerkitoBio/goquery v1.12.0
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0
github.com/alecthomas/chroma/v2 v2.23.1
github.com/aws/aws-sdk-go-v2/credentials v1.19.7
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.8
github.com/aws/aws-sdk-go-v2/credentials v1.19.13
github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.12
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
github.com/blevesearch/bleve/v2 v2.5.7
github.com/bohde/codel v0.2.0
github.com/buildkite/terminal-to-html/v3 v3.16.8
github.com/caddyserver/certmagic v0.25.1
github.com/caddyserver/certmagic v0.25.2
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21
github.com/chi-middleware/proxy v1.1.1
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21
@@ -49,42 +49,42 @@ require (
github.com/gliderlabs/ssh v0.3.8
github.com/go-chi/chi/v5 v5.2.5
github.com/go-chi/cors v1.2.2
github.com/go-co-op/gocron v1.37.0
github.com/go-enry/go-enry/v2 v2.9.4
github.com/go-git/go-billy/v5 v5.7.0
github.com/go-git/go-git/v5 v5.16.5
github.com/go-ldap/ldap/v3 v3.4.12
github.com/go-redsync/redsync/v4 v4.15.0
github.com/go-co-op/gocron/v2 v2.19.1
github.com/go-enry/go-enry/v2 v2.9.5
github.com/go-git/go-billy/v5 v5.8.0
github.com/go-git/go-git/v5 v5.17.2
github.com/go-ldap/ldap/v3 v3.4.13
github.com/go-redsync/redsync/v4 v4.16.0
github.com/go-sql-driver/mysql v1.9.3
github.com/go-webauthn/webauthn v0.13.4
github.com/goccy/go-json v0.10.5
github.com/go-webauthn/webauthn v0.16.1
github.com/goccy/go-json v0.10.6
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/go-github/v74 v74.0.0
github.com/google/go-github/v84 v84.0.0
github.com/google/licenseclassifier/v2 v2.0.0
github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc
github.com/google/uuid v1.6.0
github.com/gorilla/feeds v1.2.0
github.com/gorilla/sessions v1.4.0
github.com/hashicorp/go-version v1.8.0
github.com/hashicorp/go-version v1.9.0
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/huandu/xstrings v1.5.0
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056
github.com/jhillyerd/enmime v1.3.0
github.com/jhillyerd/enmime/v2 v2.3.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.18.3
github.com/klauspost/compress v1.18.5
github.com/klauspost/cpuid/v2 v2.3.0
github.com/lib/pq v1.11.1
github.com/lib/pq v1.12.1
github.com/markbates/goth v1.82.0
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-sqlite3 v1.14.33
github.com/meilisearch/meilisearch-go v0.36.0
github.com/mattn/go-sqlite3 v1.14.38
github.com/meilisearch/meilisearch-go v0.36.1
github.com/mholt/archives v0.1.5
github.com/microcosm-cc/bluemonday v1.0.27
github.com/microsoft/go-mssqldb v1.9.6
github.com/minio/minio-go/v7 v7.0.98
github.com/msteinert/pam v1.2.0
github.com/minio/minio-go/v7 v7.0.99
github.com/msteinert/pam/v2 v2.1.0
github.com/nektos/act v0.2.63
github.com/niklasfasching/go-org v1.9.1
github.com/olivere/elastic/v7 v7.0.32
@@ -93,38 +93,38 @@ require (
github.com/pquerna/otp v1.5.0
github.com/prometheus/client_golang v1.23.2
github.com/quasoft/websspi v1.1.2
github.com/redis/go-redis/v9 v9.17.3
github.com/redis/go-redis/v9 v9.18.0
github.com/robfig/cron/v3 v3.0.1
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
github.com/sassoftware/go-rpmutils v0.4.0
github.com/sergi/go-diff v1.4.0
github.com/stretchr/testify v1.11.1
github.com/syndtr/goleveldb v1.0.0
github.com/tstranex/u2f v1.0.0
github.com/ulikunitz/xz v0.5.15
github.com/urfave/cli-docs/v3 v3.0.0-alpha6
github.com/urfave/cli-docs/v3 v3.1.0
github.com/urfave/cli/v3 v3.4.1
github.com/wneessen/go-mail v0.7.2
github.com/xeipuuv/gojsonschema v1.2.0
github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.7.16
github.com/yuin/goldmark v1.8.2
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
go.yaml.in/yaml/v4 v4.0.0-rc.2
golang.org/x/crypto v0.47.0
golang.org/x/image v0.35.0
golang.org/x/net v0.49.0
golang.org/x/oauth2 v0.34.0
golang.org/x/sync v0.19.0
golang.org/x/sys v0.40.0
golang.org/x/text v0.33.0
google.golang.org/grpc v1.78.0
gitlab.com/gitlab-org/api/client-go v1.46.0
go.yaml.in/yaml/v4 v4.0.0-rc.3
golang.org/x/crypto v0.49.0
golang.org/x/image v0.38.0
golang.org/x/net v0.52.0
golang.org/x/oauth2 v0.36.0
golang.org/x/sync v0.20.0
golang.org/x/sys v0.42.0
golang.org/x/text v0.35.0
google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.11
gopkg.in/ini.v1 v1.67.1
gopkg.in/yaml.v3 v3.0.1
mvdan.cc/xurls/v2 v2.6.0
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
strk.kbt.io/projects/go/libravatar v0.0.0-20260301104140-add494e31dab
xorm.io/builder v0.3.13
xorm.io/xorm v1.3.11
)
@@ -133,75 +133,78 @@ require (
cloud.google.com/go/compute/metadata v0.9.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
filippo.io/edwards25519 v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
github.com/DataDog/zstd v1.5.7 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/RoaringBitmap/roaring/v2 v2.10.0 // indirect
github.com/RoaringBitmap/roaring/v2 v2.16.0 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/andybalholm/brotli v1.2.1 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aws/aws-sdk-go-v2 v1.41.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect
github.com/aws/smithy-go v1.24.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
github.com/aws/smithy-go v1.24.2 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.24.0 // indirect
github.com/blevesearch/bleve_index_api v1.2.11 // indirect
github.com/blevesearch/geo v0.2.4 // indirect
github.com/blevesearch/go-faiss v1.0.26 // indirect
github.com/bits-and-blooms/bitset v1.24.4 // indirect
github.com/blevesearch/bleve_index_api v1.3.7 // indirect
github.com/blevesearch/geo v0.2.5 // indirect
github.com/blevesearch/go-faiss v1.0.30 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/gtreap v0.1.1 // indirect
github.com/blevesearch/mmap-go v1.0.4 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.3.13 // indirect
github.com/blevesearch/mmap-go v1.2.0 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.4.5 // indirect
github.com/blevesearch/segment v0.9.1 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
github.com/blevesearch/vellum v1.1.0 // indirect
github.com/blevesearch/zapx/v11 v11.4.2 // indirect
github.com/blevesearch/zapx/v12 v12.4.2 // indirect
github.com/blevesearch/zapx/v13 v13.4.2 // indirect
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
github.com/blevesearch/zapx/v16 v16.2.8 // indirect
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
github.com/blevesearch/vellum v1.2.0 // indirect
github.com/blevesearch/zapx/v11 v11.4.3 // indirect
github.com/blevesearch/zapx/v12 v12.4.3 // indirect
github.com/blevesearch/zapx/v13 v13.4.3 // indirect
github.com/blevesearch/zapx/v14 v14.4.3 // indirect
github.com/blevesearch/zapx/v15 v15.4.3 // indirect
github.com/blevesearch/zapx/v16 v16.3.2 // indirect
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
github.com/caddyserver/zerossl v0.1.4 // indirect
github.com/caddyserver/zerossl v0.1.5 // indirect
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/couchbase/go-couchbase v0.1.1 // indirect
github.com/couchbase/gomemcached v0.3.3 // indirect
github.com/couchbase/goutils v0.1.2 // indirect
github.com/couchbase/gomemcached v0.3.4 // indirect
github.com/couchbase/goutils v0.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-enry/go-oniguruma v1.2.1 // indirect
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-webauthn/x v0.1.24 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/go-webauthn/x v0.2.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/go-tpm v0.9.5 // indirect
github.com/google/flatbuffers v25.12.19+incompatible // indirect
github.com/google/go-querystring v1.2.0 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
@@ -209,55 +212,55 @@ require (
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/inbucket/html2text v1.0.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect
github.com/kevinburke/ssh_config v1.6.0 // indirect
github.com/klauspost/crc32 v1.3.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/libdns/libdns v1.1.1 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mailru/easyjson v0.9.2 // indirect
github.com/markbates/going v1.0.3 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.21 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect
github.com/mholt/acmez/v3 v3.1.4 // indirect
github.com/miekg/dns v1.1.69 // indirect
github.com/mholt/acmez/v3 v3.1.6 // indirect
github.com/miekg/dns v1.1.72 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/crc64nvme v1.1.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minlz v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/minio/minlz v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
github.com/olekukonko/cat v0.0.0-20250817074551-3280053e4e00 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.1.0 // indirect
github.com/olekukonko/tablewriter v1.0.9 // indirect
github.com/nwaples/rardecode/v2 v2.2.2 // indirect
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
github.com/olekukonko/errors v1.2.0 // indirect
github.com/olekukonko/ll v0.1.8 // indirect
github.com/olekukonko/tablewriter v1.1.4 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pjbgf/sha1cd v0.4.0 // indirect
github.com/pierrec/lz4/v4 v4.1.26 // indirect
github.com/pjbgf/sha1cd v0.5.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/rhysd/actionlint v1.7.7 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/procfs v0.20.1 // indirect
github.com/rhysd/actionlint v1.7.12 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/skeema/knownhosts v1.3.2 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/tinylib/msgp v1.6.1 // indirect
github.com/tinylib/msgp v1.6.3 // indirect
github.com/unknwon/com v1.0.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
@@ -271,14 +274,14 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.40.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
golang.org/x/mod v0.34.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.43.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401020348-3a24fdc17823 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
@@ -288,16 +291,22 @@ ignore (
./node_modules
)
replace github.com/jaytaylor/html2text => github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347
// When doing "go get -u ./...", Golang will try to update all dependencies
// But not all latest versions of dependencies are compatible with other packages or our codebase, so we need to pin some dependencies to specific versions
// Need to regularly maintain this list to try to update them to latest versions, especially the TODO ones
replace github.com/nektos/act => gitea.com/gitea/act v0.261.8
replace github.com/jaytaylor/html2text => github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347 // jaytaylor/html2text is unmaintained
exclude github.com/gofrs/uuid v3.2.0+incompatible
replace github.com/nektos/act => gitea.com/gitea/act v0.261.10 // gitea maintains its own package
exclude github.com/gofrs/uuid v4.0.0+incompatible
replace github.com/urfave/cli/v3 => github.com/urfave/cli/v3 v3.4.1 // v3.6.2 breaks -c flag parsing in help commands
exclude github.com/goccy/go-json v0.4.11
replace go.yaml.in/yaml/v4 => go.yaml.in/yaml/v4 v4.0.0-rc.3 // rc.4 changes block scalar serialization, wait for stable release
exclude github.com/satori/go.uuid v1.2.0
replace github.com/Azure/azure-sdk-for-go/sdk/azcore => github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 // v1.21.0+ uses API version unsupported by Azurite in CI
replace github.com/Azure/azure-sdk-for-go/sdk/storage/azblob => github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 // v1.6.4+ uses API version unsupported by Azurite in CI
replace github.com/microsoft/go-mssqldb => github.com/microsoft/go-mssqldb v1.9.7 // downgraded with Azure SDK
tool code.gitea.io/gitea-vet
+234 -406
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -139,7 +139,7 @@ func Test_CalcCommitStatus(t *testing.T) {
},
},
expected: &git_model.CommitStatus{
State: commitstatus.CommitStatusPending,
State: commitstatus.CommitStatusFailure,
},
},
{
+2 -2
View File
@@ -495,9 +495,9 @@ func updateUserWhitelist(ctx context.Context, repo *repo_model.Repository, curre
if err != nil {
return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
}
perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
perm, err := access_model.GetIndividualUserRepoPermission(ctx, repo, user)
if err != nil {
return nil, fmt.Errorf("GetUserRepoPermission [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
return nil, fmt.Errorf("GetIndividualUserRepoPermission [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
}
if !perm.CanWrite(unit.TypeCode) {
+1 -1
View File
@@ -356,7 +356,7 @@ func ClearIssueLabels(ctx context.Context, issue *Issue, doer *user_model.User)
return err
}
perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
perm, err := access_model.GetDoerRepoPermission(ctx, issue.Repo, doer)
if err != nil {
return err
}
-30
View File
@@ -64,36 +64,6 @@ func LoadProjectIssueColumnMap(ctx context.Context, projectID, defaultColumnID i
return result, nil
}
// LoadIssuesFromColumn load issues assigned to this column
func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, opts *IssuesOptions) (IssueList, error) {
issueList, err := Issues(ctx, opts.Copy(func(o *IssuesOptions) {
o.ProjectColumnID = b.ID
o.ProjectID = b.ProjectID
o.SortType = "project-column-sorting"
}))
if err != nil {
return nil, err
}
if b.Default {
issues, err := Issues(ctx, opts.Copy(func(o *IssuesOptions) {
o.ProjectColumnID = db.NoConditionID
o.ProjectID = b.ProjectID
o.SortType = "project-column-sorting"
}))
if err != nil {
return nil, err
}
issueList = append(issueList, issues...)
}
if err := issueList.LoadComments(ctx); err != nil {
return nil, err
}
return issueList, nil
}
// IssueAssignOrRemoveProject changes the project associated with an issue
// If newProjectID is 0, the issue is removed from the project
func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID, newColumnID int64) error {
+2 -2
View File
@@ -665,9 +665,9 @@ func ResolveIssueMentionsByVisibility(ctx context.Context, issue *Issue, doer *u
continue
}
// Normal users must have read access to the referencing issue
perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, user)
perm, err := access_model.GetIndividualUserRepoPermission(ctx, issue.Repo, user)
if err != nil {
return nil, fmt.Errorf("GetUserRepoPermission [%d]: %w", user.ID, err)
return nil, fmt.Errorf("GetIndividualUserRepoPermission [%d]: %w", user.ID, err)
}
if !perm.CanReadIssuesOrPulls(issue.IsPull) {
continue
+1 -1
View File
@@ -207,7 +207,7 @@ func (issue *Issue) verifyReferencedIssue(stdCtx context.Context, ctx *crossRefe
// Check doer permissions; set action to None if the doer can't change the destination
if refIssue.RepoID != ctx.OrigIssue.RepoID || ref.Action != references.XRefActionNone {
perm, err := access_model.GetUserRepoPermission(stdCtx, refIssue.Repo, ctx.Doer)
perm, err := access_model.GetDoerRepoPermission(stdCtx, refIssue.Repo, ctx.Doer)
if err != nil {
return nil, references.XRefActionNone, err
}
+1 -1
View File
@@ -93,7 +93,7 @@ func CanMaintainerWriteToBranch(ctx context.Context, p access_model.Permission,
if err != nil {
continue
}
prPerm, err := access_model.GetUserRepoPermission(ctx, pr.BaseRepo, user)
prPerm, err := access_model.GetIndividualUserRepoPermission(ctx, pr.BaseRepo, user)
if err != nil {
continue
}
+1 -1
View File
@@ -919,7 +919,7 @@ func CanMarkConversation(ctx context.Context, issue *Issue, doer *user_model.Use
return false, nil
}
if doer.ID != issue.PosterID {
p, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
p, err := access_model.GetDoerRepoPermission(ctx, issue.Repo, doer)
if err != nil {
return false, err
}
+1
View File
@@ -404,6 +404,7 @@ func prepareMigrationTasks() []*migration {
newMigration(327, "Add disabled state to action runners", v1_26.AddDisabledToActionRunner),
newMigration(328, "Add TokenPermissions column to ActionRunJob", v1_26.AddTokenPermissionsToActionRunJob),
newMigration(329, "Add unique constraint for user badge", v1_26.AddUniqueIndexForUserBadge),
newMigration(330, "Add name column to webhook", v1_26.AddNameToWebhook),
}
return preparedMigrations
}
+16
View File
@@ -0,0 +1,16 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_26
import (
"xorm.io/xorm"
)
func AddNameToWebhook(x *xorm.Engine) error {
type Webhook struct {
Name string `xorm:"VARCHAR(255) NOT NULL DEFAULT ''"`
}
_, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, new(Webhook))
return err
}
+58 -21
View File
@@ -91,43 +91,80 @@ func updateUserAccess(accessMap map[int64]*userAccess, user *user_model.User, mo
}
}
// FIXME: do cross-comparison so reduce deletions and additions to the minimum?
// refreshAccesses updates the repository's access records in the database by comparing the provided accessMap
// with existing records. It minimizes DB operations by performing selective inserts, updates, and deletes
// instead of removing all existing records and re-adding them.
func refreshAccesses(ctx context.Context, repo *repo_model.Repository, accessMap map[int64]*userAccess) (err error) {
minMode := perm.AccessModeRead
minModeToKeep := perm.AccessModeRead
if err := repo.LoadOwner(ctx); err != nil {
return fmt.Errorf("LoadOwner: %w", err)
}
// If the repo isn't private and isn't owned by a organization,
// If the repo isn't private and isn't owned by an organization,
// increase the minMode to Write.
if !repo.IsPrivate && !repo.Owner.IsOrganization() {
minMode = perm.AccessModeWrite
minModeToKeep = perm.AccessModeWrite
}
newAccesses := make([]Access, 0, len(accessMap))
// Query existing accesses for cross-comparison
var existingAccesses []Access
if err := db.GetEngine(ctx).Where(builder.Eq{"repo_id": repo.ID}).Find(&existingAccesses); err != nil {
return fmt.Errorf("find existing accesses: %w", err)
}
existingMap := make(map[int64]perm.AccessMode, len(existingAccesses))
for _, a := range existingAccesses {
existingMap[a.UserID] = a.Mode
}
var toDelete []int64
var toInsert, toUpdate []Access
// Determine changes
for userID, ua := range accessMap {
if ua.Mode < minMode && !ua.User.IsRestricted {
continue
if ua.Mode < minModeToKeep && !ua.User.IsRestricted {
// No explicit access record needed (handled by default permissions, e.g., public repo access)
if _, exists := existingMap[userID]; exists {
toDelete = append(toDelete, userID)
}
} else {
desiredMode := ua.Mode
if existingMode, exists := existingMap[userID]; exists {
if existingMode != desiredMode {
toUpdate = append(toUpdate, Access{UserID: userID, RepoID: repo.ID, Mode: desiredMode})
}
} else {
toInsert = append(toInsert, Access{UserID: userID, RepoID: repo.ID, Mode: desiredMode})
}
}
newAccesses = append(newAccesses, Access{
UserID: userID,
RepoID: repo.ID,
Mode: ua.Mode,
})
delete(existingMap, userID)
}
// Delete old accesses and insert new ones for repository.
if _, err = db.DeleteByBean(ctx, &Access{RepoID: repo.ID}); err != nil {
return fmt.Errorf("delete old accesses: %w", err)
}
if len(newAccesses) == 0 {
return nil
// Remaining in existingMap should be deleted
for userID := range existingMap {
toDelete = append(toDelete, userID)
}
if err = db.Insert(ctx, newAccesses); err != nil {
return fmt.Errorf("insert new accesses: %w", err)
// Execute deletions
if len(toDelete) > 0 {
if _, err = db.GetEngine(ctx).In("user_id", toDelete).And("repo_id = ?", repo.ID).Delete(&Access{}); err != nil {
return fmt.Errorf("delete accesses: %w", err)
}
}
// Execute updates
for _, u := range toUpdate {
if _, err = db.GetEngine(ctx).Where("user_id = ? AND repo_id = ?", u.UserID, repo.ID).Cols("mode").Update(&Access{Mode: u.Mode}); err != nil {
return fmt.Errorf("update access for user %d: %w", u.UserID, err)
}
}
// Execute insertions
if len(toInsert) > 0 {
if err = db.Insert(ctx, toInsert); err != nil {
return fmt.Errorf("insert new accesses: %w", err)
}
}
return nil
}
+78 -9
View File
@@ -15,11 +15,20 @@ import (
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAccessLevel(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
func TestAccess(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
t.Run("AccessLevel", testAccessLevel)
t.Run("HasAccess", testHasAccess)
t.Run("RecalculateAccesses", testRecalculateAccesses)
t.Run("RecalculateAccesses2", testRecalculateAccesses2)
t.Run("RecalculateAccessesUpdateMode", testRecalculateAccessesUpdateMode)
t.Run("RecalculateAccessesRemoveAccess", testRecalculateAccessesRemoveAccess)
}
func testAccessLevel(t *testing.T) {
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29})
@@ -75,9 +84,7 @@ func TestAccessLevel(t *testing.T) {
assert.Equal(t, perm_model.AccessModeRead, level)
}
func TestHasAccess(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
func testHasAccess(t *testing.T) {
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
// A public repository owned by User 2
@@ -101,9 +108,8 @@ func TestHasAccess(t *testing.T) {
assert.NoError(t, err)
}
func TestRepository_RecalculateAccesses(t *testing.T) {
func testRecalculateAccesses(t *testing.T) {
// test with organization repo
assert.NoError(t, unittest.PrepareTestDatabase())
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
assert.NoError(t, repo1.LoadOwner(t.Context()))
@@ -118,9 +124,8 @@ func TestRepository_RecalculateAccesses(t *testing.T) {
assert.Equal(t, perm_model.AccessModeOwner, access.Mode)
}
func TestRepository_RecalculateAccesses2(t *testing.T) {
func testRecalculateAccesses2(t *testing.T) {
// test with non-organization repo
assert.NoError(t, unittest.PrepareTestDatabase())
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
assert.NoError(t, repo1.LoadOwner(t.Context()))
@@ -132,3 +137,67 @@ func TestRepository_RecalculateAccesses2(t *testing.T) {
assert.NoError(t, err)
assert.False(t, has)
}
func testRecalculateAccessesUpdateMode(t *testing.T) {
// Test the update path in refreshAccesses optimization
// Scenario: User's access mode changes from Read to Write
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
assert.NoError(t, repo.LoadOwner(t.Context()))
// Verify initial access mode
_ = db.Insert(t.Context(), &repo_model.Collaboration{UserID: 4, RepoID: 4, Mode: perm_model.AccessModeWrite})
_ = db.Insert(t.Context(), &access_model.Access{UserID: 4, RepoID: 4, Mode: perm_model.AccessModeWrite})
initialAccess := &access_model.Access{UserID: 4, RepoID: 4}
has, err := db.GetEngine(t.Context()).Get(initialAccess)
assert.NoError(t, err)
assert.True(t, has)
initialMode := initialAccess.Mode
// Change collaboration mode to trigger update path
newMode := perm_model.AccessModeAdmin
assert.NotEqual(t, initialMode, newMode, "New mode should differ from initial mode")
_, err = db.GetEngine(t.Context()).
Where("user_id = ? AND repo_id = ?", 4, 4).
Cols("mode").
Update(&repo_model.Collaboration{Mode: newMode})
assert.NoError(t, err)
// Recalculate accesses - should UPDATE existing access, not delete+insert
assert.NoError(t, access_model.RecalculateAccesses(t.Context(), repo))
// Verify access was updated, not deleted and re-inserted
updatedAccess := &access_model.Access{UserID: 4, RepoID: 4}
has, err = db.GetEngine(t.Context()).Get(updatedAccess)
assert.NoError(t, err)
assert.True(t, has, "Access should still exist")
assert.Equal(t, newMode, updatedAccess.Mode, "Access mode should be updated to new collaboration mode")
}
func testRecalculateAccessesRemoveAccess(t *testing.T) {
// Test the delete path in refreshAccesses optimization
// Scenario: Remove a user's collaboration, access should be deleted
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
assert.NoError(t, repo.LoadOwner(t.Context()))
// Verify initial access exists
initialAccess := &access_model.Access{UserID: 4, RepoID: 4}
has, err := db.GetEngine(t.Context()).Get(initialAccess)
assert.NoError(t, err)
assert.True(t, has, "Access should exist initially")
// Remove the collaboration to trigger delete path
_, err = db.GetEngine(t.Context()).
Where("user_id = ? AND repo_id = ?", 4, 4).
Delete(&repo_model.Collaboration{})
assert.NoError(t, err)
// Recalculate accesses - should DELETE the access record
assert.NoError(t, access_model.RecalculateAccesses(t.Context(), repo))
// Verify access was deleted
removedAccess := &access_model.Access{UserID: 4, RepoID: 4}
has, err = db.GetEngine(t.Context()).Get(removedAccess)
assert.NoError(t, err)
assert.False(t, has, "Access should be deleted after removing collaboration")
}
+22 -8
View File
@@ -331,7 +331,7 @@ func GetActionsUserRepoPermission(ctx context.Context, repo *repo_model.Reposito
// Check permission like simple user but limit to read-only (PR #36095)
// Enhanced to also grant read-only access if isSameRepo is true and target repository is public
botPerm, err := GetUserRepoPermission(ctx, repo, user_model.NewActionsUser())
botPerm, err := GetIndividualUserRepoPermission(ctx, repo, user_model.NewActionsUser())
if err != nil {
return perm, err
}
@@ -379,8 +379,19 @@ func GetActionsUserRepoPermission(ctx context.Context, repo *repo_model.Reposito
return perm, nil
}
// GetUserRepoPermission returns the user permissions to the repository
func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) {
// GetDoerRepoPermission returns the repository permission for the current actor,
// dispatching to GetActionsUserRepoPermission when the actor is an Actions token user.
func GetDoerRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (Permission, error) {
if taskID, ok := user_model.GetActionsUserTaskID(user); ok {
return GetActionsUserRepoPermission(ctx, repo, user, taskID)
}
return GetIndividualUserRepoPermission(ctx, repo, user)
}
// GetIndividualUserRepoPermission returns the permissions for an explicit user identity.
// In most request paths, callers should use GetDoerRepoPermission instead.
// Unlike GetDoerRepoPermission, this helper does not resolve Actions task users.
func GetIndividualUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) {
defer func() {
if err == nil {
finalProcessRepoUnitPermission(user, &perm)
@@ -539,8 +550,9 @@ func AccessLevel(ctx context.Context, user *user_model.User, repo *repo_model.Re
// AccessLevelUnit returns the Access a user has to a repository's. Will return NoneAccess if the
// user does not have access.
// This helper only supports explicit user identities and does not resolve Actions task users.
func AccessLevelUnit(ctx context.Context, user *user_model.User, repo *repo_model.Repository, unitType unit.Type) (perm_model.AccessMode, error) { //nolint:revive // export stutter
perm, err := GetUserRepoPermission(ctx, repo, user)
perm, err := GetIndividualUserRepoPermission(ctx, repo, user)
if err != nil {
return perm_model.AccessModeNone, err
}
@@ -559,7 +571,7 @@ func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.
if user.IsOrganization() {
return false, fmt.Errorf("organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
}
perm, err := GetUserRepoPermission(ctx, repo, user)
perm, err := GetIndividualUserRepoPermission(ctx, repo, user)
if err != nil {
return false, err
}
@@ -568,6 +580,7 @@ func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.
}
// HasAnyUnitAccess see the comment of "perm.HasAnyUnitAccess"
// This helper only supports explicit user identities and does not resolve Actions task users.
func HasAnyUnitAccess(ctx context.Context, userID int64, repo *repo_model.Repository) (bool, error) {
var user *user_model.User
var err error
@@ -577,7 +590,7 @@ func HasAnyUnitAccess(ctx context.Context, userID int64, repo *repo_model.Reposi
return false, err
}
}
perm, err := GetUserRepoPermission(ctx, repo, user)
perm, err := GetIndividualUserRepoPermission(ctx, repo, user)
if err != nil {
return false, err
}
@@ -625,13 +638,14 @@ func GetUserIDsWithUnitAccess(ctx context.Context, repo *repo_model.Repository,
}
// CheckRepoUnitUser check whether user could visit the unit of this repository
// This helper only supports explicit user identities and does not resolve Actions task users.
func CheckRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool {
if user != nil && user.IsAdmin {
return true
}
perm, err := GetUserRepoPermission(ctx, repo, user)
perm, err := GetIndividualUserRepoPermission(ctx, repo, user)
if err != nil {
log.Error("GetUserRepoPermission: %w", err)
log.Error("GetIndividualUserRepoPermission: %w", err)
return false
}
+36 -8
View File
@@ -6,6 +6,7 @@ package access
import (
"testing"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
perm_model "code.gitea.io/gitea/models/perm"
@@ -157,8 +158,13 @@ func TestUnitAccessMode(t *testing.T) {
assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "has unit, and map, use map")
}
func TestGetUserRepoPermission(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
func TestGetRepoPermission(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
t.Run("GetIndividualUserRepoPermission", testGetIndividualUserRepoPermission)
t.Run("GetDoerRepoPermission", testGetDoerRepoPermission)
}
func testGetIndividualUserRepoPermission(t *testing.T) {
ctx := t.Context()
repo32 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 32}) // org public repo
require.NoError(t, repo32.LoadOwner(ctx))
@@ -172,7 +178,7 @@ func TestGetUserRepoPermission(t *testing.T) {
require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID}))
t.Run("DoerInTeamWithNoRepo", func(t *testing.T) {
perm, err := GetUserRepoPermission(ctx, repo32, user)
perm, err := GetIndividualUserRepoPermission(ctx, repo32, user)
require.NoError(t, err)
assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode)
assert.Nil(t, perm.unitsMode) // doer in the team, but has no access to the repo
@@ -181,7 +187,7 @@ func TestGetUserRepoPermission(t *testing.T) {
require.NoError(t, db.Insert(ctx, &organization.TeamRepo{OrgID: org.ID, TeamID: team.ID, RepoID: repo32.ID}))
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeNone}))
t.Run("DoerWithTeamUnitAccessNone", func(t *testing.T) {
perm, err := GetUserRepoPermission(ctx, repo32, user)
perm, err := GetIndividualUserRepoPermission(ctx, repo32, user)
require.NoError(t, err)
assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeCode])
@@ -191,7 +197,7 @@ func TestGetUserRepoPermission(t *testing.T) {
require.NoError(t, db.TruncateBeans(ctx, &organization.TeamUnit{}))
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeWrite}))
t.Run("DoerWithTeamUnitAccessWrite", func(t *testing.T) {
perm, err := GetUserRepoPermission(ctx, repo32, user)
perm, err := GetIndividualUserRepoPermission(ctx, repo32, user)
require.NoError(t, err)
assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
@@ -204,7 +210,7 @@ func TestGetUserRepoPermission(t *testing.T) {
require.NoError(t, db.TruncateBeans(ctx, &organization.TeamUnit{}, &Access{})) // The user has access set of that repo, remove it, it is useless for our test
require.NoError(t, db.Insert(ctx, &organization.TeamRepo{OrgID: org.ID, TeamID: team.ID, RepoID: repo3.ID}))
t.Run("DoerWithNoopTeamOnPrivateRepo", func(t *testing.T) {
perm, err := GetUserRepoPermission(ctx, repo3, user)
perm, err := GetIndividualUserRepoPermission(ctx, repo3, user)
require.NoError(t, err)
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode])
@@ -214,7 +220,7 @@ func TestGetUserRepoPermission(t *testing.T) {
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeNone}))
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeIssues, AccessMode: perm_model.AccessModeRead}))
t.Run("DoerWithReadIssueTeamOnPrivateRepo", func(t *testing.T) {
perm, err := GetUserRepoPermission(ctx, repo3, user)
perm, err := GetIndividualUserRepoPermission(ctx, repo3, user)
require.NoError(t, err)
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode])
@@ -233,7 +239,7 @@ func TestGetUserRepoPermission(t *testing.T) {
require.NoError(t, db.Insert(ctx, repo_model.Collaboration{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite}))
require.NoError(t, db.Insert(ctx, Access{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite}))
t.Run("DoerWithReadIssueTeamAndWriteCollaboratorOnPrivateRepo", func(t *testing.T) {
perm, err := GetUserRepoPermission(ctx, repo3, user)
perm, err := GetIndividualUserRepoPermission(ctx, repo3, user)
require.NoError(t, err)
assert.Equal(t, perm_model.AccessModeWrite, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
@@ -245,3 +251,25 @@ func TestGetUserRepoPermission(t *testing.T) {
assert.Equal(t, user.ID, users[0].ID)
})
}
func testGetDoerRepoPermission(t *testing.T) {
ctx := t.Context()
repo4 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
task47 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 47})
actionsDoer := user_model.NewActionsUserWithTaskID(task47.ID)
regularUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
actionsPerm, err := GetDoerRepoPermission(ctx, repo4, actionsDoer)
require.NoError(t, err)
directPerm, err := GetActionsUserRepoPermission(ctx, repo4, actionsDoer, task47.ID)
require.NoError(t, err)
assert.Equal(t, directPerm, actionsPerm)
doerPerm, err := GetDoerRepoPermission(ctx, repo1, regularUser)
require.NoError(t, err)
individualPerm, err := GetIndividualUserRepoPermission(ctx, repo1, regularUser)
require.NoError(t, err)
assert.Equal(t, individualPerm, doerPerm)
}
+24 -19
View File
@@ -257,9 +257,12 @@ func (p *Project) GetColumns(ctx context.Context) (ColumnList, error) {
return columns, nil
}
// getDefaultColumn return default column and ensure only one exists
func (p *Project) getDefaultColumn(ctx context.Context) (*Column, error) {
// getDefaultColumnWithFallback return default column if one exists
// otherwise return the first column by sorting and set it as default column
func (p *Project) getDefaultColumnWithFallback(ctx context.Context) (*Column, error) {
var column Column
// try to find a column "default=true"
has, err := db.GetEngine(ctx).
Where("project_id=? AND `default` = ?", p.ID, true).
Desc("id").Get(&column)
@@ -270,23 +273,9 @@ func (p *Project) getDefaultColumn(ctx context.Context) (*Column, error) {
if has {
return &column, nil
}
return nil, ErrProjectColumnNotExist{ColumnID: 0}
}
// MustDefaultColumn returns the default column for a project.
// If one exists, it is returned
// If none exists, the first column will be elevated to the default column of this project
func (p *Project) MustDefaultColumn(ctx context.Context) (*Column, error) {
c, err := p.getDefaultColumn(ctx)
if err != nil && !IsErrProjectColumnNotExist(err) {
return nil, err
}
if c != nil {
return c, nil
}
var column Column
has, err := db.GetEngine(ctx).Where("project_id=?", p.ID).OrderBy("sorting, id").Get(&column)
// try to find the first column by sorting
has, err = db.GetEngine(ctx).Where("project_id=?", p.ID).OrderBy("sorting, id").Get(&column)
if err != nil {
return nil, err
}
@@ -298,8 +287,24 @@ func (p *Project) MustDefaultColumn(ctx context.Context) (*Column, error) {
return &column, nil
}
return nil, ErrProjectColumnNotExist{ColumnID: 0}
}
// MustDefaultColumn returns the default column for a project.
// If one exists, it is returned
// If none exists, the first column will be elevated to the default column of this project
// If there is no column, it creates a default column and returns it
func (p *Project) MustDefaultColumn(ctx context.Context) (*Column, error) {
c, err := p.getDefaultColumnWithFallback(ctx)
if err != nil && !IsErrProjectColumnNotExist(err) {
return nil, err
}
if c != nil {
return c, nil
}
// create a default column if none is found
column = Column{
column := Column{
ProjectID: p.ID,
Default: true,
Title: "Uncategorized",
+1
View File
@@ -126,6 +126,7 @@ type Webhook struct {
OwnerID int64 `xorm:"INDEX"`
IsSystemWebhook bool
URL string `xorm:"url TEXT"`
Name string `xorm:"VARCHAR(255) NOT NULL DEFAULT ''"`
HTTPMethod string `xorm:"http_method"`
ContentType HookContentType
Secret string `xorm:"TEXT"`
+6 -6
View File
@@ -135,7 +135,7 @@ func rewriteSubExpression(in string, forceFormat bool) (string, error) {
exprStart := -1
strStart := -1
var results []string
formatOut := ""
var formatOut strings.Builder
for pos < len(in) {
if strStart > -1 {
matches := strPattern.FindStringIndex(in[pos:])
@@ -158,7 +158,7 @@ func rewriteSubExpression(in string, forceFormat bool) (string, error) {
}
if exprEnd > -1 {
formatOut += fmt.Sprintf("{%d}", len(results))
fmt.Fprintf(&formatOut, "{%d}", len(results))
results = append(results, strings.TrimSpace(in[exprStart:pos+exprEnd]))
pos += exprEnd + 2
exprStart = -1
@@ -170,20 +170,20 @@ func rewriteSubExpression(in string, forceFormat bool) (string, error) {
} else {
exprStart = strings.Index(in[pos:], "${{")
if exprStart != -1 {
formatOut += escapeFormatString(in[pos : pos+exprStart])
formatOut.WriteString(escapeFormatString(in[pos : pos+exprStart]))
exprStart = pos + exprStart + 3
pos = exprStart
} else {
formatOut += escapeFormatString(in[pos:])
formatOut.WriteString(escapeFormatString(in[pos:]))
pos = len(in)
}
}
}
if len(results) == 1 && formatOut == "{0}" && !forceFormat {
if len(results) == 1 && formatOut.String() == "{0}" && !forceFormat {
return in, nil
}
out := fmt.Sprintf("format('%s', %s)", strings.ReplaceAll(formatOut, "'", "''"), strings.Join(results, ", "))
out := fmt.Sprintf("format('%s', %s)", strings.ReplaceAll(formatOut.String(), "'", "''"), strings.Join(results, ", "))
return out, nil
}
+25
View File
@@ -9,7 +9,9 @@ import (
"io/fs"
"os"
"path/filepath"
"slices"
"sort"
"strings"
"time"
"code.gitea.io/gitea/modules/container"
@@ -61,6 +63,8 @@ type LayeredFS struct {
layers []*Layer
}
var _ fs.ReadDirFS = (*LayeredFS)(nil)
// Layered returns a new LayeredFS with the given layers. The first layer is the top layer.
func Layered(layers ...*Layer) *LayeredFS {
return &LayeredFS{layers: layers}
@@ -83,6 +87,27 @@ func (l *LayeredFS) ReadFile(elems ...string) ([]byte, error) {
return bs, err
}
func (l *LayeredFS) ReadDir(name string) (files []fs.DirEntry, _ error) {
filesMap := map[string]fs.DirEntry{}
for _, layer := range l.layers {
entries, err := readDirOptional(layer, name)
if err != nil {
return nil, err
}
for _, entry := range entries {
entryName := entry.Name()
if _, exist := filesMap[entryName]; !exist && shouldInclude(entry) {
filesMap[entryName] = entry
}
}
}
for _, file := range filesMap {
files = append(files, file)
}
slices.SortFunc(files, func(a, b fs.DirEntry) int { return strings.Compare(a.Name(), b.Name()) })
return files, nil
}
// ReadLayeredFile reads the named file, and returns the layer name.
func (l *LayeredFS) ReadLayeredFile(elems ...string) ([]byte, string, error) {
name := util.PathJoinRel(elems...)
+2 -1
View File
@@ -8,7 +8,7 @@ package pam
import (
"errors"
"github.com/msteinert/pam"
"github.com/msteinert/pam/v2"
)
// Supported is true when built with PAM
@@ -28,6 +28,7 @@ func Auth(serviceName, userName, passwd string) (string, error) {
if err != nil {
return "", err
}
defer t.End()
if err = t.Authenticate(0); err != nil {
return "", err
+3 -2
View File
@@ -61,16 +61,17 @@ type CommitStatusStates []CommitStatusState //nolint:revive // export stutter
// According to https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#get-the-combined-status-for-a-specific-reference
// > Additionally, a combined state is returned. The state is one of:
// > failure if any of the contexts report as error or failure
// > failure if any of the contexts report as warning (Gitea specific behavior)
// > pending if there are no statuses or a context is pending
// > success if the latest status for all contexts is success
func (css CommitStatusStates) Combine() CommitStatusState {
successCnt := 0
for _, state := range css {
switch {
case state.IsError() || state.IsFailure():
case state.IsError() || state.IsFailure() || state.IsWarning():
return CommitStatusFailure
case state.IsPending():
case state.IsSuccess() || state.IsWarning() || state.IsSkipped():
case state.IsSuccess() || state.IsSkipped():
successCnt++
}
}
+7 -7
View File
@@ -41,7 +41,7 @@ func TestCombine(t *testing.T) {
{
name: "warning",
states: CommitStatusStates{CommitStatusWarning},
expected: CommitStatusSuccess,
expected: CommitStatusFailure,
},
// 2 states
{
@@ -62,7 +62,7 @@ func TestCombine(t *testing.T) {
{
name: "pending and warning",
states: CommitStatusStates{CommitStatusPending, CommitStatusWarning},
expected: CommitStatusPending,
expected: CommitStatusFailure,
},
{
name: "success and error",
@@ -77,7 +77,7 @@ func TestCombine(t *testing.T) {
{
name: "success and warning",
states: CommitStatusStates{CommitStatusSuccess, CommitStatusWarning},
expected: CommitStatusSuccess,
expected: CommitStatusFailure,
},
{
name: "error and failure",
@@ -98,7 +98,7 @@ func TestCombine(t *testing.T) {
{
name: "pending, success and warning",
states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess, CommitStatusWarning},
expected: CommitStatusPending,
expected: CommitStatusFailure,
},
{
name: "pending, success and error",
@@ -133,7 +133,7 @@ func TestCombine(t *testing.T) {
{
name: "success, warning and skipped",
states: CommitStatusStates{CommitStatusSuccess, CommitStatusWarning, CommitStatusSkipped},
expected: CommitStatusSuccess,
expected: CommitStatusFailure,
},
// All success
{
@@ -181,12 +181,12 @@ func TestCombine(t *testing.T) {
{
name: "mixed states with all success",
states: CommitStatusStates{CommitStatusSuccess, CommitStatusSuccess, CommitStatusPending, CommitStatusWarning},
expected: CommitStatusPending,
expected: CommitStatusFailure,
},
{
name: "all success with warning",
states: CommitStatusStates{CommitStatusSuccess, CommitStatusSuccess, CommitStatusSuccess, CommitStatusWarning},
expected: CommitStatusSuccess,
expected: CommitStatusFailure,
},
}
+17 -10
View File
@@ -22,16 +22,16 @@ import (
var catFileBatchDebugWaitClose atomic.Int64
type catFileBatchCommunicator struct {
cancel context.CancelFunc
closeFunc func(err error)
reqWriter io.Writer
respReader *bufio.Reader
debugGitCmd *gitcmd.Command
}
func (b *catFileBatchCommunicator) Close() {
if b.cancel != nil {
b.cancel()
b.cancel = nil
if b.closeFunc != nil {
b.closeFunc(nil)
b.closeFunc = nil
}
}
@@ -47,10 +47,19 @@ func newCatFileBatch(ctx context.Context, repoPath string, cmdCatFile *gitcmd.Co
}
stdPipeClose()
}
closeFunc := func(err error) {
ctxCancel(err)
pipeClose()
}
return newCatFileBatchWithCloseFunc(ctx, repoPath, cmdCatFile, stdinWriter, stdoutReader, closeFunc)
}
ret = &catFileBatchCommunicator{
func newCatFileBatchWithCloseFunc(ctx context.Context, repoPath string, cmdCatFile *gitcmd.Command,
stdinWriter gitcmd.PipeWriter, stdoutReader gitcmd.PipeReader, closeFunc func(err error),
) *catFileBatchCommunicator {
ret := &catFileBatchCommunicator{
debugGitCmd: cmdCatFile,
cancel: func() { ctxCancel(nil) },
closeFunc: closeFunc,
reqWriter: stdinWriter,
respReader: bufio.NewReaderSize(stdoutReader, 32*1024), // use a buffered reader for rich operations
}
@@ -60,8 +69,7 @@ func newCatFileBatch(ctx context.Context, repoPath string, cmdCatFile *gitcmd.Co
log.Error("Unable to start git command %v: %v", cmdCatFile.LogString(), err)
// ideally here it should return the error, but it would require refactoring all callers
// so just return a dummy communicator that does nothing, almost the same behavior as before, not bad
ctxCancel(err)
pipeClose()
closeFunc(err)
return ret
}
@@ -70,8 +78,7 @@ func newCatFileBatch(ctx context.Context, repoPath string, cmdCatFile *gitcmd.Co
if err != nil && !errors.Is(err, context.Canceled) {
log.Error("cat-file --batch command failed in repo %s, error: %v", repoPath, err)
}
ctxCancel(err)
pipeClose()
closeFunc(err)
}()
return ret
+5
View File
@@ -12,7 +12,12 @@ import (
func newHTTPServer(network, address, name string, handler http.Handler) (*Server, ServeFunction) {
server := NewServer(network, address, name)
protocols := http.Protocols{}
protocols.SetHTTP1(true)
protocols.SetHTTP2(true) // HTTP/2 can only be used when Gitea is configured to use TLS
protocols.SetUnencryptedHTTP2(true) // Allow HTTP/2 without TLS, in case Gitea is behind a reverse proxy
httpServer := http.Server{
Protocols: &protocols,
Handler: handler,
BaseContext: func(net.Listener) context.Context { return GetManager().HammerContext() },
}
+5 -6
View File
@@ -9,6 +9,7 @@ import (
"io"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/public"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
@@ -61,19 +62,17 @@ func (p *openAPIRenderer) Render(ctx *markup.RenderContext, input io.Reader, out
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="%s/assets/css/swagger.css?v=%s">
<link rel="stylesheet" href="%s">
</head>
<body>
<div id="swagger-ui"><textarea class="swagger-spec-content" data-spec-filename="%s">%s</textarea></div>
<script src="%s/assets/js/swagger.js?v=%s"></script>
<script type="module" src="%s"></script>
</body>
</html>`,
setting.StaticURLPrefix,
setting.AssetVersion,
public.AssetURI("css/swagger.css"),
html.EscapeString(ctx.RenderOptions.RelativePath),
html.EscapeString(util.UnsafeBytesToString(content)),
setting.StaticURLPrefix,
setting.AssetVersion,
public.AssetURI("js/swagger.js"),
))
return err
}
+4 -3
View File
@@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/htmlutil"
"code.gitea.io/gitea/modules/markup/internal"
"code.gitea.io/gitea/modules/public"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
@@ -237,10 +238,10 @@ func RenderWithRenderer(ctx *RenderContext, renderer Renderer, input io.Reader,
return renderIFrame(ctx, extOpts.ContentSandbox, output)
}
// else: this is a standalone page, fallthrough to the real rendering, and add extra JS/CSS
extraStyleHref := setting.AppSubURL + "/assets/css/external-render-iframe.css"
extraScriptSrc := setting.AppSubURL + "/assets/js/external-render-iframe.js"
extraStyleHref := public.AssetURI("css/external-render-iframe.css")
extraScriptSrc := public.AssetURI("js/external-render-iframe.js")
// "<script>" must go before "<link>", to make Golang's http.DetectContentType() can still recognize the content as "text/html"
extraHeadHTML = htmlutil.HTMLFormat(`<script src="%s"></script><link rel="stylesheet" href="%s">`, extraScriptSrc, extraStyleHref)
extraHeadHTML = htmlutil.HTMLFormat(`<script type="module" src="%s"></script><link rel="stylesheet" href="%s">`, extraScriptSrc, extraStyleHref)
}
ctx.usedByRender = true
+9 -2
View File
@@ -12,10 +12,17 @@ import (
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"github.com/santhosh-tekuri/jsonschema/v5"
"github.com/santhosh-tekuri/jsonschema/v6"
"gopkg.in/yaml.v3"
)
// schemaLoader implements jsonschema.URLLoader
type schemaLoader struct{}
func (l *schemaLoader) Load(url string) (any, error) {
return openSchema(url)
}
// Load project data from file, with optional validation
func Load(filename string, data any, validation bool) error {
isJSON := strings.HasSuffix(filename, ".json")
@@ -43,7 +50,7 @@ func unmarshal(bs []byte, data any, isJSON bool) error {
func getSchema(filename string) (*jsonschema.Schema, error) {
c := jsonschema.NewCompiler()
c.LoadURL = openSchema
c.UseLoader(&schemaLoader{})
return c.Compile(filename)
}
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"strings"
"testing"
"github.com/santhosh-tekuri/jsonschema/v5"
"github.com/santhosh-tekuri/jsonschema/v6"
"github.com/stretchr/testify/assert"
)
+9 -3
View File
@@ -8,7 +8,6 @@
package migration
import (
"io"
"io/fs"
"path"
"sync"
@@ -16,6 +15,8 @@ import (
_ "embed"
"code.gitea.io/gitea/modules/assetfs"
"github.com/santhosh-tekuri/jsonschema/v6"
)
//go:embed bindata.dat
@@ -25,6 +26,11 @@ var BuiltinAssets = sync.OnceValue(func() fs.FS {
return assetfs.NewEmbeddedFS(bindata)
})
func openSchema(filename string) (io.ReadCloser, error) {
return BuiltinAssets().Open(path.Base(filename))
func openSchema(filename string) (any, error) {
f, err := BuiltinAssets().Open(path.Base(filename))
if err != nil {
return nil, err
}
defer f.Close()
return jsonschema.UnmarshalJSON(f)
}
+9 -3
View File
@@ -6,14 +6,15 @@
package migration
import (
"io"
"net/url"
"os"
"path"
"path/filepath"
"github.com/santhosh-tekuri/jsonschema/v6"
)
func openSchema(s string) (io.ReadCloser, error) {
func openSchema(s string) (any, error) {
u, err := url.Parse(s)
if err != nil {
return nil, err
@@ -34,5 +35,10 @@ func openSchema(s string) (io.ReadCloser, error) {
filename = filepath.Join("modules/migration/schemas", basename)
}
}
return os.Open(filename)
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
return jsonschema.UnmarshalJSON(f)
}
+1 -1
View File
@@ -140,7 +140,7 @@ type nuspecPackage struct {
func ParsePackageMetaData(r io.ReaderAt, size int64) (*Package, error) {
archive, err := zip.NewReader(r, size)
if err != nil {
return nil, err
return nil, util.NewInvalidArgumentErrorf("unable to parse package meta: %v", err)
}
for _, file := range archive.File {
+1 -1
View File
@@ -42,7 +42,7 @@ func (l PortablePdbList) Close() {
func ExtractPortablePdb(r io.ReaderAt, size int64) (PortablePdbList, error) {
archive, err := zip.NewReader(r, size)
if err != nil {
return nil, err
return nil, util.NewInvalidArgumentErrorf("unable to extract portable pdb: %v", err)
}
var pdbs PortablePdbList
+156
View File
@@ -0,0 +1,156 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package public
import (
"io"
"path"
"sync"
"sync/atomic"
"time"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
type manifestEntry struct {
File string `json:"file"`
Name string `json:"name"`
IsEntry bool `json:"isEntry"`
CSS []string `json:"css"`
}
type manifestDataStruct struct {
paths map[string]string // unhashed path -> hashed path
names map[string]string // hashed path -> entry name
modTime int64
checkTime time.Time
}
var (
manifestData atomic.Pointer[manifestDataStruct]
manifestFS = sync.OnceValue(AssetFS)
)
const manifestPath = "assets/.vite/manifest.json"
func parseManifest(data []byte) (map[string]string, map[string]string) {
var manifest map[string]manifestEntry
if err := json.Unmarshal(data, &manifest); err != nil {
log.Error("Failed to parse frontend manifest: %v", err)
return nil, nil
}
paths := make(map[string]string)
names := make(map[string]string)
for _, entry := range manifest {
if !entry.IsEntry || entry.Name == "" {
continue
}
// Build unhashed key from file path: "js/index.js", "css/theme-gitea-dark.css"
dir := path.Dir(entry.File)
ext := path.Ext(entry.File)
key := dir + "/" + entry.Name + ext
paths[key] = entry.File
names[entry.File] = entry.Name
// Map associated CSS files, e.g. "css/index.css" -> "css/index.B3zrQPqD.css"
for _, css := range entry.CSS {
cssKey := path.Dir(css) + "/" + entry.Name + path.Ext(css)
paths[cssKey] = css
names[css] = entry.Name
}
}
return paths, names
}
func reloadManifest(existingData *manifestDataStruct) *manifestDataStruct {
now := time.Now()
data := existingData
if data != nil && now.Sub(data.checkTime) < time.Second {
// a single request triggers multiple calls to getHashedPath
// do not check the manifest file too frequently
return data
}
f, err := manifestFS().Open(manifestPath)
if err != nil {
log.Error("Failed to open frontend manifest: %v", err)
return data
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
log.Error("Failed to stat frontend manifest: %v", err)
return data
}
needReload := data == nil || fi.ModTime().UnixNano() != data.modTime
if !needReload {
return data
}
manifestContent, err := io.ReadAll(f)
if err != nil {
log.Error("Failed to read frontend manifest: %v", err)
return data
}
return storeManifestFromBytes(manifestContent, fi.ModTime().UnixNano(), now)
}
func storeManifestFromBytes(manifestContent []byte, modTime int64, checkTime time.Time) *manifestDataStruct {
paths, names := parseManifest(manifestContent)
data := &manifestDataStruct{
paths: paths,
names: names,
modTime: modTime,
checkTime: checkTime,
}
manifestData.Store(data)
return data
}
func getManifestData() *manifestDataStruct {
data := manifestData.Load()
// In production the manifest is immutable (embedded in the binary).
// In dev mode, check if it changed on disk (for watch-frontend).
if data == nil || !setting.IsProd {
data = reloadManifest(data)
}
if data == nil {
data = &manifestDataStruct{}
}
return data
}
// getHashedPath resolves an unhashed asset path (origin path) to its content-hashed path from the frontend manifest.
// Example: getHashedPath("js/index.js") returns "js/index.C6Z2MRVQ.js"
// Falls back to returning the input path unchanged if the manifest is unavailable.
func getHashedPath(originPath string) string {
data := getManifestData()
if p, ok := data.paths[originPath]; ok {
return p
}
return originPath
}
// AssetURI returns the URI for a frontend asset.
// It may return a relative path or a full URL depending on the StaticURLPrefix setting.
// In Vite dev mode, known entry points are mapped to their source paths
// so the reverse proxy serves them from the Vite dev server.
// In production, it resolves the content-hashed path from the manifest.
func AssetURI(originPath string) string {
if src := viteDevSourceURL(originPath); src != "" {
return src
}
return setting.StaticURLPrefix + "/assets/" + getHashedPath(originPath)
}
// AssetNameFromHashedPath returns the asset entry name for a given hashed asset path.
// Example: returns "theme-gitea-dark" for "css/theme-gitea-dark.CyAaQnn5.css".
// Returns empty string if the path is not found in the manifest.
func AssetNameFromHashedPath(hashedPath string) string {
return getManifestData().names[hashedPath]
}
+91
View File
@@ -0,0 +1,91 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package public
import (
"testing"
"time"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
)
func TestViteManifest(t *testing.T) {
defer test.MockVariableValue(&setting.IsProd, true)()
const testManifest = `{
"web_src/js/index.ts": {
"file": "js/index.C6Z2MRVQ.js",
"name": "index",
"src": "web_src/js/index.ts",
"isEntry": true,
"css": ["css/index.B3zrQPqD.css"]
},
"web_src/js/standalone/swagger.ts": {
"file": "js/swagger.SujiEmYM.js",
"name": "swagger",
"src": "web_src/js/standalone/swagger.ts",
"isEntry": true,
"css": ["css/swagger._-APWT_3.css"]
},
"web_src/css/themes/theme-gitea-dark.css": {
"file": "css/theme-gitea-dark.CyAaQnn5.css",
"name": "theme-gitea-dark",
"src": "web_src/css/themes/theme-gitea-dark.css",
"isEntry": true
},
"web_src/js/features/eventsource.sharedworker.ts": {
"file": "js/eventsource.sharedworker.Dug1twio.js",
"name": "eventsource.sharedworker",
"src": "web_src/js/features/eventsource.sharedworker.ts",
"isEntry": true
},
"_chunk.js": {
"file": "js/chunk.abc123.js",
"name": "chunk"
}
}`
t.Run("EmptyManifest", func(t *testing.T) {
storeManifestFromBytes([]byte(``), 0, time.Now())
assert.Equal(t, "/assets/js/index.js", AssetURI("js/index.js"))
assert.Equal(t, "/assets/css/theme-gitea-dark.css", AssetURI("css/theme-gitea-dark.css"))
assert.Equal(t, "", AssetNameFromHashedPath("css/no-such-file.css"))
})
t.Run("ParseManifest", func(t *testing.T) {
storeManifestFromBytes([]byte(testManifest), 0, time.Now())
paths, names := manifestData.Load().paths, manifestData.Load().names
// JS entries
assert.Equal(t, "js/index.C6Z2MRVQ.js", paths["js/index.js"])
assert.Equal(t, "js/swagger.SujiEmYM.js", paths["js/swagger.js"])
assert.Equal(t, "js/eventsource.sharedworker.Dug1twio.js", paths["js/eventsource.sharedworker.js"])
// Associated CSS from JS entries
assert.Equal(t, "css/index.B3zrQPqD.css", paths["css/index.css"])
assert.Equal(t, "css/swagger._-APWT_3.css", paths["css/swagger.css"])
// CSS-only entries
assert.Equal(t, "css/theme-gitea-dark.CyAaQnn5.css", paths["css/theme-gitea-dark.css"])
// Non-entry chunks should not be included
assert.Empty(t, paths["js/chunk.js"])
// Names: hashed path -> entry name
assert.Equal(t, "index", names["js/index.C6Z2MRVQ.js"])
assert.Equal(t, "index", names["css/index.B3zrQPqD.css"])
assert.Equal(t, "swagger", names["js/swagger.SujiEmYM.js"])
assert.Equal(t, "swagger", names["css/swagger._-APWT_3.css"])
assert.Equal(t, "theme-gitea-dark", names["css/theme-gitea-dark.CyAaQnn5.css"])
assert.Equal(t, "eventsource.sharedworker", names["js/eventsource.sharedworker.Dug1twio.js"])
// Test Asset related functions
assert.Equal(t, "/assets/js/index.C6Z2MRVQ.js", AssetURI("js/index.js"))
assert.Equal(t, "/assets/css/theme-gitea-dark.CyAaQnn5.css", AssetURI("css/theme-gitea-dark.css"))
assert.Equal(t, "theme-gitea-dark", AssetNameFromHashedPath("css/theme-gitea-dark.CyAaQnn5.css"))
})
}
+30 -22
View File
@@ -4,31 +4,36 @@
package public
import (
"mime"
"strings"
"sync"
)
// wellKnownMimeTypesLower comes from Golang's builtin mime package: `builtinTypesLower`, see the comment of DetectWellKnownMimeType
var wellKnownMimeTypesLower = map[string]string{
".avif": "image/avif",
".css": "text/css; charset=utf-8",
".gif": "image/gif",
".htm": "text/html; charset=utf-8",
".html": "text/html; charset=utf-8",
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".js": "text/javascript; charset=utf-8",
".json": "application/json",
".mjs": "text/javascript; charset=utf-8",
".pdf": "application/pdf",
".png": "image/png",
".svg": "image/svg+xml",
".wasm": "application/wasm",
".webp": "image/webp",
".xml": "text/xml; charset=utf-8",
// wellKnownMimeTypesLower comes from Golang's builtin mime package: `builtinTypesLower`,
// see the comment of DetectWellKnownMimeType
var wellKnownMimeTypesLower = sync.OnceValue(func() map[string]string {
return map[string]string{
".avif": "image/avif",
".css": "text/css; charset=utf-8",
".gif": "image/gif",
".htm": "text/html; charset=utf-8",
".html": "text/html; charset=utf-8",
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".js": "text/javascript; charset=utf-8",
".json": "application/json",
".mjs": "text/javascript; charset=utf-8",
".pdf": "application/pdf",
".png": "image/png",
".svg": "image/svg+xml",
".wasm": "application/wasm",
".webp": "image/webp",
".xml": "text/xml; charset=utf-8",
// well, there are some types missing from the builtin list
".txt": "text/plain; charset=utf-8",
}
// well, there are some types missing from the builtin list
".txt": "text/plain; charset=utf-8",
}
})
// DetectWellKnownMimeType will return the mime-type for a well-known file ext name
// The purpose of this function is to bypass the unstable behavior of Golang's mime.TypeByExtension
@@ -38,5 +43,8 @@ var wellKnownMimeTypesLower = map[string]string{
// DetectWellKnownMimeType makes the Content-Type for well-known files stable.
func DetectWellKnownMimeType(ext string) string {
ext = strings.ToLower(ext)
return wellKnownMimeTypesLower[ext]
if s, ok := wellKnownMimeTypesLower()[ext]; ok {
return s
}
return mime.TypeByExtension(ext)
}
+201
View File
@@ -0,0 +1,201 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package public
import (
"net/http"
"net/http/httputil"
"net/url"
"os"
"path/filepath"
"strings"
"sync/atomic"
"time"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/routing"
)
const viteDevPortFile = "public/assets/.vite/dev-port"
var viteDevProxy atomic.Pointer[httputil.ReverseProxy]
func getViteDevServerBaseURL() string {
portFile := filepath.Join(setting.StaticRootPath, viteDevPortFile)
portContent, _ := os.ReadFile(portFile)
port := strings.TrimSpace(string(portContent))
if port == "" {
return ""
}
return "http://localhost:" + port
}
func getViteDevProxy() *httputil.ReverseProxy {
if proxy := viteDevProxy.Load(); proxy != nil {
return proxy
}
viteDevServerBaseURL := getViteDevServerBaseURL()
if viteDevServerBaseURL == "" {
return nil
}
target, err := url.Parse(viteDevServerBaseURL)
if err != nil {
log.Error("Failed to parse Vite dev server base URL %s, err: %v", viteDevServerBaseURL, err)
return nil
}
// there is a strange error log (from Golang's HTTP package)
// 2026/03/28 19:50:13 modules/log/misc.go:72:(*loggerToWriter).Write() [I] Unsolicited response received on idle HTTP channel starting with "HTTP/1.1 400 Bad Request\r\n\r\n"; err=<nil>
// maybe it is caused by that the Vite dev server doesn't support keep-alive connections? or different keep-alive timeouts?
transport := &http.Transport{
IdleConnTimeout: 5 * time.Second,
ResponseHeaderTimeout: 5 * time.Second,
}
log.Info("Proxying Vite dev server requests to %s", target)
proxy := &httputil.ReverseProxy{
Transport: transport,
Rewrite: func(r *httputil.ProxyRequest) {
r.SetURL(target)
r.Out.Host = target.Host
},
ModifyResponse: func(resp *http.Response) error {
// add a header to indicate the Vite dev server port,
// make developers know that this request is proxied to Vite dev server and which port it is
resp.Header.Add("X-Gitea-Vite-Dev-Server", viteDevServerBaseURL)
return nil
},
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
log.Error("Error proxying to Vite dev server: %v", err)
http.Error(w, "Error proxying to Vite dev server: "+err.Error(), http.StatusBadGateway)
},
}
viteDevProxy.Store(proxy)
return proxy
}
// ViteDevMiddleware proxies matching requests to the Vite dev server.
// It is registered as middleware in non-production mode and lazily discovers
// the Vite dev server port from the port file written by the viteDevServerPortPlugin.
// It is needed because there are container-based development, only Gitea web server's port is exposed.
func ViteDevMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
if !isViteDevRequest(req) {
next.ServeHTTP(resp, req)
return
}
proxy := getViteDevProxy()
if proxy == nil {
next.ServeHTTP(resp, req)
return
}
routing.MarkLongPolling(resp, req)
proxy.ServeHTTP(resp, req)
})
}
var viteDevModeCheck atomic.Pointer[struct {
isDev bool
time time.Time
}]
// IsViteDevMode returns true if the Vite dev server port file exists and the server is alive
func IsViteDevMode() bool {
if setting.IsProd {
return false
}
now := time.Now()
lastCheck := viteDevModeCheck.Load()
if lastCheck != nil && time.Now().Sub(lastCheck.time) < time.Second {
return lastCheck.isDev
}
viteDevServerBaseURL := getViteDevServerBaseURL()
if viteDevServerBaseURL == "" {
return false
}
req := httplib.NewRequest(viteDevServerBaseURL+"/web_src/js/__vite_dev_server_check", "GET")
resp, _ := req.Response()
if resp != nil {
_ = resp.Body.Close()
}
isDev := resp != nil && resp.StatusCode == http.StatusOK
viteDevModeCheck.Store(&struct {
isDev bool
time time.Time
}{
isDev: isDev,
time: now,
})
return isDev
}
func viteDevSourceURL(name string) string {
if !IsViteDevMode() {
return ""
}
if strings.HasPrefix(name, "css/theme-") {
// Only redirect built-in themes to Vite source; custom themes are served from custom/public/assets/css/
themeFile := strings.TrimPrefix(name, "css/")
srcPath := filepath.Join(setting.StaticRootPath, "web_src/css/themes", themeFile)
if _, err := os.Stat(srcPath); err == nil {
return setting.AppSubURL + "/web_src/css/themes/" + themeFile
}
return ""
}
if strings.HasPrefix(name, "css/") {
return setting.AppSubURL + "/web_src/" + name
}
if name == "js/eventsource.sharedworker.js" {
return setting.AppSubURL + "/web_src/js/features/eventsource.sharedworker.ts"
}
if name == "js/iife.js" {
return setting.AppSubURL + "/web_src/js/__vite_iife.js"
}
if name == "js/index.js" {
return setting.AppSubURL + "/web_src/js/index.ts"
}
return ""
}
// isViteDevRequest returns true if the request should be proxied to the Vite dev server.
// Ref: Vite source packages/vite/src/node/constants.ts and packages/vite/src/shared/constants.ts
func isViteDevRequest(req *http.Request) bool {
if req.Header.Get("Upgrade") == "websocket" {
wsProtocol := req.Header.Get("Sec-WebSocket-Protocol")
return wsProtocol == "vite-hmr" || wsProtocol == "vite-ping"
}
path := req.URL.Path
// vite internal requests
if strings.HasPrefix(path, "/@vite/") /* HMR client */ ||
strings.HasPrefix(path, "/@fs/") /* out-of-root file access, see vite.config.ts: fs.allow */ ||
strings.HasPrefix(path, "/@id/") /* virtual modules */ {
return true
}
// local source requests (VITE-DEV-SERVER-SECURITY: don't serve sensitive files outside the allowed paths)
if strings.HasPrefix(path, "/node_modules/") ||
strings.HasPrefix(path, "/public/assets/") ||
strings.HasPrefix(path, "/web_src/") {
return true
}
// Vite uses a path relative to project root and adds "?import" to non-JS/CSS asset imports:
// - {WebSite}/public/assets/... (e.g. SVG icons from "{RepoRoot}/public/assets/img/svg/")
// - {WebSite}/assets/emoji.json: it is an exception for the frontend assets, it is imported by JS code, but:
// - KEEP IN MIND: all static frontend assets are served from "{AssetFS}/assets" to "{WebSite}/assets" by Gitea Web Server
// - "{AssetFS}" is a layered filesystem from "{RepoRoot}/public" or embedded assets, and user's custom files in "{CustomPath}/public"
// - "{RepoRoot}/assets/emoji.json" just happens to have the dir name "assets", it is not related to frontend assets
// - BAD DESIGN: indeed it is a "conflicted and polluted name" sample
if path == "/assets/emoji.json" {
return true
}
return false
}
-5
View File
@@ -72,9 +72,6 @@ var (
// It maps to ini:"LOCAL_ROOT_URL" in [server]
LocalURL string
// AssetVersion holds an opaque value that is used for cache-busting assets
AssetVersion string
// appTempPathInternal is the temporary path for the app, it is only an internal variable
// DO NOT use it directly, always use AppDataTempDir
appTempPathInternal string
@@ -317,8 +314,6 @@ func loadServerFrom(rootCfg ConfigProvider) {
}
AbsoluteAssetURL = MakeAbsoluteAssetURL(appURL, StaticURLPrefix)
AssetVersion = strings.ReplaceAll(AppVer, "+", "~") // make sure the version string is clear (no real escaping is needed)
manifestBytes := MakeManifestData(AppName, AppURL, AbsoluteAssetURL)
ManifestData = `application/json;base64,` + base64.StdEncoding.EncodeToString(manifestBytes)
+8 -2
View File
@@ -28,8 +28,10 @@ var (
CfgProvider ConfigProvider
IsWindows bool
// IsInTesting indicates whether the testing is running. A lot of unreliable code causes a lot of nonsense error logs during testing
// TODO: this is only a temporary solution, we should make the test code more reliable
// IsInTesting indicates whether the testing is running (unit test or integration test). It can be used for:
// * Skip nonsense error logs during testing caused by unreliable code (TODO: this is only a temporary solution, we should make the test code more reliable)
// * Panic in dev or testing mode to make the problem more obvious and easier to debug
// * Mock some functions or options to make testing easier (eg: session store, time, URL detection, etc.)
IsInTesting = false
)
@@ -57,6 +59,10 @@ func IsRunUserMatchCurrentUser(runUser string) (string, bool) {
return currentUser, runUser == currentUser
}
func IsInE2eTesting() bool {
return os.Getenv("GITEA_TEST_E2E") == "true"
}
// PrepareAppDataPath creates app data directory if necessary
func PrepareAppDataPath() error {
// FIXME: There are too many calls to MkdirAll in old code. It is incorrect.
+1 -1
View File
@@ -12,7 +12,7 @@ type Activity struct {
UserID int64 `json:"user_id"` // Receiver user
// the type of action
//
// enum: create_repo,rename_repo,star_repo,watch_repo,commit_repo,create_issue,create_pull_request,transfer_repo,push_tag,comment_issue,merge_pull_request,close_issue,reopen_issue,close_pull_request,reopen_pull_request,delete_tag,delete_branch,mirror_sync_push,mirror_sync_create,mirror_sync_delete,approve_pull_request,reject_pull_request,comment_pull,publish_release,pull_review_dismissed,pull_request_ready_for_review,auto_merge_pull_request
// enum: ["create_repo","rename_repo","star_repo","watch_repo","commit_repo","create_issue","create_pull_request","transfer_repo","push_tag","comment_issue","merge_pull_request","close_issue","reopen_issue","close_pull_request","reopen_pull_request","delete_tag","delete_branch","mirror_sync_push","mirror_sync_create","mirror_sync_delete","approve_pull_request","reject_pull_request","comment_pull","publish_release","pull_review_dismissed","pull_request_ready_for_review","auto_merge_pull_request"]
OpType string `json:"op_type"`
// The ID of the user who performed the action
ActUserID int64 `json:"act_user_id"`
+7 -1
View File
@@ -19,6 +19,8 @@ var ErrInvalidReceiveHook = errors.New("Invalid JSON payload received over webho
type Hook struct {
// The unique identifier of the webhook
ID int64 `json:"id"`
// Optional human-readable name for the webhook
Name string `json:"name"`
// The type of the webhook (e.g., gitea, slack, discord)
Type string `json:"type"`
// Branch filter pattern to determine which branches trigger the webhook
@@ -51,7 +53,7 @@ type CreateHookOptionConfig map[string]string
// CreateHookOption options when create a hook
type CreateHookOption struct {
// required: true
// enum: dingtalk,discord,gitea,gogs,msteams,slack,telegram,feishu,wechatwork,packagist
// enum: ["dingtalk","discord","gitea","gogs","msteams","slack","telegram","feishu","wechatwork","packagist"]
// The type of the webhook to create
Type string `json:"type" binding:"Required"`
// required: true
@@ -66,6 +68,8 @@ type CreateHookOption struct {
// default: false
// Whether the webhook should be active upon creation
Active bool `json:"active"`
// Optional human-readable name for the webhook
Name string `json:"name" binding:"MaxSize(255)"`
}
// EditHookOption options when modify one hook
@@ -80,6 +84,8 @@ type EditHookOption struct {
AuthorizationHeader string `json:"authorization_header"`
// Whether the webhook is active and will be triggered
Active *bool `json:"active"`
// Optional human-readable name
Name *string `json:"name,omitzero" binding:"MaxSize(255)"`
}
// Payloader payload is some part of one hook
+18 -12
View File
@@ -14,6 +14,8 @@ import (
)
// StateType issue state type
//
// swagger:enum StateType
type StateType string
const (
@@ -21,10 +23,11 @@ const (
StateOpen StateType = "open"
// StateClosed pr is closed
StateClosed StateType = "closed"
// StateAll is all
StateAll StateType = "all"
)
// StateAll is a query parameter filter value, not a valid object state.
const StateAll = "all"
// PullRequestMeta PR info if an issue is a PR
type PullRequestMeta struct {
HasMerged bool `json:"merged"`
@@ -58,15 +61,11 @@ type Issue struct {
Labels []*Label `json:"labels"`
Milestone *Milestone `json:"milestone"`
// deprecated
Assignee *User `json:"assignee"`
Assignees []*User `json:"assignees"`
// Whether the issue is open or closed
//
// type: string
// enum: open,closed
State StateType `json:"state"`
IsLocked bool `json:"is_locked"`
Comments int `json:"comments"`
Assignee *User `json:"assignee"`
Assignees []*User `json:"assignees"`
State StateType `json:"state"`
IsLocked bool `json:"is_locked"`
Comments int `json:"comments"`
// swagger:strfmt date-time
Created time.Time `json:"created_at"`
// swagger:strfmt date-time
@@ -82,6 +81,8 @@ type Issue struct {
Repo *RepositoryMeta `json:"repository"`
PinOrder int `json:"pin_order"`
// The version of the issue content for optimistic locking
ContentVersion int `json:"content_version"`
}
// CreateIssueOption options to create one issue
@@ -115,6 +116,8 @@ type EditIssueOption struct {
// swagger:strfmt date-time
Deadline *time.Time `json:"due_date"`
RemoveDeadline *bool `json:"unset_due_date"`
// The current version of the issue content to detect conflicts during editing
ContentVersion *int `json:"content_version"`
}
// EditDeadlineOption options for creating a deadline
@@ -132,6 +135,8 @@ type IssueDeadline struct {
}
// IssueFormFieldType defines issue form field type, can be "markdown", "textarea", "input", "dropdown" or "checkboxes"
//
// swagger:enum IssueFormFieldType
type IssueFormFieldType string
const (
@@ -168,7 +173,8 @@ func (iff IssueFormField) VisibleInContent() bool {
}
// IssueFormFieldVisible defines issue form field visible
// swagger:model
//
// swagger:enum IssueFormFieldVisible
type IssueFormFieldVisible string
const (
+2 -1
View File
@@ -40,7 +40,7 @@ type CreateMilestoneOption struct {
// swagger:strfmt date-time
// Deadline is the due date for the milestone
Deadline *time.Time `json:"due_on"`
// enum: open,closed
// enum: ["open","closed"]
// State indicates the initial state of the milestone
State string `json:"state"`
}
@@ -52,6 +52,7 @@ type EditMilestoneOption struct {
// Description provides updated details about the milestone
Description *string `json:"description"`
// State indicates the updated state of the milestone
// enum: ["open","closed"]
State *string `json:"state"`
// Deadline is the updated due date for the milestone
Deadline *time.Time `json:"due_on"`
+16 -1
View File
@@ -40,7 +40,7 @@ type NotificationSubject struct {
// Type indicates the type of the notification subject
Type NotifySubjectType `json:"type" binding:"In(Issue,Pull,Commit,Repository)"`
// State indicates the current state of the notification subject
State StateType `json:"state"`
State NotifySubjectStateType `json:"state"`
}
// NotificationCount number of unread notifications
@@ -49,7 +49,22 @@ type NotificationCount struct {
New int64 `json:"new"`
}
// NotifySubjectStateType represents the state of a notification subject
// swagger:enum NotifySubjectStateType
type NotifySubjectStateType string
const (
// NotifySubjectStateOpen is an open subject
NotifySubjectStateOpen NotifySubjectStateType = "open"
// NotifySubjectStateClosed is a closed subject
NotifySubjectStateClosed NotifySubjectStateType = "closed"
// NotifySubjectStateMerged is a merged pull request
NotifySubjectStateMerged NotifySubjectStateType = "merged"
)
// NotifySubjectType represent type of notification subject
//
// swagger:enum NotifySubjectType
type NotifySubjectType string
const (
+2 -2
View File
@@ -60,7 +60,7 @@ type CreateOrgOption struct {
// The location of the organization
Location string `json:"location" binding:"MaxSize(50)"`
// possible values are `public` (default), `limited` or `private`
// enum: public,limited,private
// enum: ["public","limited","private"]
Visibility string `json:"visibility" binding:"In(,public,limited,private)"`
// Whether repository administrators can change team access
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
@@ -79,7 +79,7 @@ type EditOrgOption struct {
// The location of the organization
Location *string `json:"location" binding:"MaxSize(50)"`
// possible values are `public`, `limited` or `private`
// enum: public,limited,private
// enum: ["public","limited","private"]
Visibility *string `json:"visibility" binding:"In(,public,limited,private)"`
// Whether repository administrators can change team access
RepoAdminChangeTeamAccess *bool `json:"repo_admin_change_team_access"`
+3 -3
View File
@@ -16,7 +16,7 @@ type Team struct {
Organization *Organization `json:"organization"`
// Whether the team has access to all repositories in the organization
IncludesAllRepositories bool `json:"includes_all_repositories"`
// enum: none,read,write,admin,owner
// enum: ["none","read","write","admin","owner"]
Permission string `json:"permission"`
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
// Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions.
@@ -35,7 +35,7 @@ type CreateTeamOption struct {
Description string `json:"description" binding:"MaxSize(255)"`
// Whether the team has access to all repositories in the organization
IncludesAllRepositories bool `json:"includes_all_repositories"`
// enum: read,write,admin
// enum: ["read","write","admin"]
Permission string `json:"permission"`
// example: ["repo.actions","repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.ext_wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
// Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions.
@@ -54,7 +54,7 @@ type EditTeamOption struct {
Description *string `json:"description" binding:"MaxSize(255)"`
// Whether the team has access to all repositories in the organization
IncludesAllRepositories *bool `json:"includes_all_repositories"`
// enum: read,write,admin
// enum: ["read","write","admin"]
Permission string `json:"permission"`
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
// Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions.
+4
View File
@@ -91,6 +91,8 @@ type PullRequest struct {
// The pin order for the pull request
PinOrder int `json:"pin_order"`
// The version of the pull request content for optimistic locking
ContentVersion int `json:"content_version"`
}
// PRBranchInfo information about a branch
@@ -168,6 +170,8 @@ type EditPullRequestOption struct {
RemoveDeadline *bool `json:"unset_due_date"`
// Whether to allow maintainer edits
AllowMaintainerEdit *bool `json:"allow_maintainer_edit"`
// The current version of the pull request content to detect conflicts during editing
ContentVersion *int `json:"content_version"`
}
// ChangedFile store information about files affected by the pull request
+5 -2
View File
@@ -8,6 +8,8 @@ import (
)
// ReviewStateType review state type
//
// swagger:enum ReviewStateType
type ReviewStateType string
const (
@@ -21,10 +23,11 @@ const (
ReviewStateRequestChanges ReviewStateType = "REQUEST_CHANGES"
// ReviewStateRequestReview review is requested from user
ReviewStateRequestReview ReviewStateType = "REQUEST_REVIEW"
// ReviewStateUnknown state of pr is unknown
ReviewStateUnknown ReviewStateType = ""
)
// ReviewStateUnknown is an internal sentinel for unknown review state, not a valid API value.
const ReviewStateUnknown = ""
// PullReview represents a pull request review
type PullReview struct {
ID int64 `json:"id"`
+4 -4
View File
@@ -114,7 +114,7 @@ type Repository struct {
Internal bool `json:"internal"`
MirrorInterval string `json:"mirror_interval"`
// ObjectFormatName of the underlying git repository
// enum: sha1,sha256
// enum: ["sha1","sha256"]
ObjectFormatName string `json:"object_format_name"`
// swagger:strfmt date-time
MirrorUpdated time.Time `json:"mirror_updated"`
@@ -150,10 +150,10 @@ type CreateRepoOption struct {
// DefaultBranch of the repository (used when initializes and in template)
DefaultBranch string `json:"default_branch" binding:"GitRefName;MaxSize(100)"`
// TrustModel of the repository
// enum: default,collaborator,committer,collaboratorcommitter
// enum: ["default","collaborator","committer","collaboratorcommitter"]
TrustModel string `json:"trust_model"`
// ObjectFormatName of the underlying git repository, empty string for default (sha1)
// enum: sha1,sha256
// enum: ["sha1","sha256"]
ObjectFormatName string `json:"object_format_name" binding:"MaxSize(6)"`
}
@@ -378,7 +378,7 @@ type MigrateRepoOptions struct {
// required: true
RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
// enum: git,github,gitea,gitlab,gogs,onedev,gitbucket,codebase,codecommit
// enum: ["git","github","gitea","gitlab","gogs","onedev","gitbucket","codebase","codecommit"]
Service string `json:"service"`
AuthUsername string `json:"auth_username"`
AuthPassword string `json:"auth_password"`
+1 -1
View File
@@ -5,7 +5,7 @@ package structs
// AddCollaboratorOption options when adding a user as a collaborator of a repository
type AddCollaboratorOption struct {
// enum: read,write,admin
// enum: ["read","write","admin"]
// Permission level to grant the collaborator
Permission *string `json:"permission"`
}
+1 -1
View File
@@ -72,7 +72,7 @@ type ChangeFileOperation struct {
// indicates what to do with the file: "create" for creating a new file, "update" for updating an existing file,
// "upload" for creating or updating a file, "rename" for renaming a file, and "delete" for deleting an existing file.
// required: true
// enum: create,update,upload,rename,delete
// enum: ["create","update","upload","rename","delete"]
Operation string `json:"operation" binding:"Required"`
// path to the existing or new file
// required: true
+32 -3
View File
@@ -6,15 +6,18 @@ package templates
import (
"fmt"
"html"
"html/template"
"net/url"
"strconv"
"strings"
"sync"
"time"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/htmlutil"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/public"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/svg"
"code.gitea.io/gitea/modules/templates/eval"
@@ -68,6 +71,8 @@ func NewFuncMap() template.FuncMap {
return strconv.FormatInt(time.Since(startTime).Nanoseconds()/1e6, 10) + "ms"
},
"AssetURI": public.AssetURI,
"ScriptImport": scriptImport,
// -----------------------------------------------------------------
// setting
"AppName": func() string {
@@ -92,9 +97,6 @@ func NewFuncMap() template.FuncMap {
"AppDomain": func() string { // documented in mail-templates.md
return setting.Domain
},
"AssetVersion": func() string {
return setting.AssetVersion
},
"ShowFooterTemplateLoadTime": func() bool {
return setting.Other.ShowFooterTemplateLoadTime
},
@@ -303,3 +305,30 @@ func QueryBuild(a ...any) template.URL {
}
return template.URL(s)
}
var globalVars = sync.OnceValue(func() (ret struct {
scriptImportRemainingPart string
},
) {
// add onerror handler to alert users when the script fails to load:
// * for end users: there were many users reporting that "UI doesn't work", actually they made mistakes in their config
// * for developers: help them to remember to run "make watch-frontend" to build frontend assets
// the message will be directly put in the onerror JS code's string
onScriptErrorPrompt := `Please make sure the asset files can be accessed.`
if !setting.IsProd {
onScriptErrorPrompt += `\n\nFor development, run: make watch-frontend.`
}
onScriptErrorJS := fmt.Sprintf(`alert('Failed to load asset file from ' + this.src + '. %s')`, onScriptErrorPrompt)
ret.scriptImportRemainingPart = `onerror="` + html.EscapeString(onScriptErrorJS) + `"></script>`
return ret
})
func scriptImport(path string, typ ...string) template.HTML {
if len(typ) > 0 {
if typ[0] == "module" {
return template.HTML(`<script type="module" src="` + html.EscapeString(public.AssetURI(path)) + `" ` + globalVars().scriptImportRemainingPart)
}
panic("unsupported script type: " + typ[0])
}
return template.HTML(`<script src="` + html.EscapeString(public.AssetURI(path)) + `" ` + globalVars().scriptImportRemainingPart)
}
+2 -2
View File
@@ -53,11 +53,11 @@ func (lc *LogChecker) checkLogEvent(event *log.EventFormatted) {
}
}
var checkerIndex int64
var checkerIndex atomic.Int64
func NewLogChecker(namePrefix string) (logChecker *LogChecker, cancel func()) {
logger := log.GetManager().GetLogger(namePrefix)
newCheckerIndex := atomic.AddInt64(&checkerIndex, 1)
newCheckerIndex := checkerIndex.Add(1)
writerName := namePrefix + "-" + strconv.FormatInt(newCheckerIndex, 10)
lc := &LogChecker{}
+12 -12
View File
@@ -12,19 +12,19 @@ import (
)
func TestDebounce(t *testing.T) {
var c int64
var c atomic.Int64
d := Debounce(50 * time.Millisecond)
d(func() { atomic.AddInt64(&c, 1) })
assert.EqualValues(t, 0, atomic.LoadInt64(&c))
d(func() { atomic.AddInt64(&c, 1) })
d(func() { atomic.AddInt64(&c, 1) })
d(func() { c.Add(1) })
assert.EqualValues(t, 0, c.Load())
d(func() { c.Add(1) })
d(func() { c.Add(1) })
time.Sleep(100 * time.Millisecond)
assert.EqualValues(t, 1, atomic.LoadInt64(&c))
d(func() { atomic.AddInt64(&c, 1) })
assert.EqualValues(t, 1, atomic.LoadInt64(&c))
d(func() { atomic.AddInt64(&c, 1) })
d(func() { atomic.AddInt64(&c, 1) })
d(func() { atomic.AddInt64(&c, 1) })
assert.EqualValues(t, 1, c.Load())
d(func() { c.Add(1) })
assert.EqualValues(t, 1, c.Load())
d(func() { c.Add(1) })
d(func() { c.Add(1) })
d(func() { c.Add(1) })
time.Sleep(100 * time.Millisecond)
assert.EqualValues(t, 2, atomic.LoadInt64(&c))
assert.EqualValues(t, 2, c.Load())
}
+9
View File
@@ -4,7 +4,9 @@
package web
import (
"bufio"
"fmt"
"net"
"net/http"
"reflect"
@@ -47,6 +49,13 @@ func (r *responseWriter) WriteHeader(statusCode int) {
r.respWriter.WriteHeader(statusCode)
}
func (r *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if hj, ok := r.respWriter.(http.Hijacker); ok {
return hj.Hijack()
}
return nil, nil, http.ErrNotSupported
}
var (
httpReqType = reflect.TypeFor[*http.Request]()
respWriterType = reflect.TypeFor[http.ResponseWriter]()
+2
View File
@@ -7,6 +7,7 @@ import (
"context"
"time"
"code.gitea.io/gitea/modules/public"
"code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting"
)
@@ -36,5 +37,6 @@ func CommonTemplateContextData() reqctx.ContextData {
"PageStartTime": time.Now(),
"RunModeIsProd": setting.IsProd,
"ViteModeIsDev": public.IsViteDevMode(),
}
}
+13
View File
@@ -8,6 +8,7 @@ import (
"net/http"
"code.gitea.io/gitea/modules/gtprof"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/reqctx"
)
@@ -40,6 +41,18 @@ func MarkLongPolling(resp http.ResponseWriter, req *http.Request) {
record.lock.Lock()
record.isLongPolling = true
record.logLevel = log.TRACE
record.lock.Unlock()
}
func MarkLogLevelTrace(resp http.ResponseWriter, req *http.Request) {
record, ok := req.Context().Value(contextKey).(*requestRecord)
if !ok {
return
}
record.lock.Lock()
record.logLevel = log.TRACE
record.lock.Unlock()
}
+14 -23
View File
@@ -5,7 +5,6 @@ package routing
import (
"net/http"
"strings"
"time"
"code.gitea.io/gitea/modules/log"
@@ -36,17 +35,8 @@ var (
func logPrinter(logger log.Logger) func(trigger Event, record *requestRecord) {
const callerName = "HTTPRequest"
logTrace := func(fmt string, args ...any) {
logger.Log(2, &log.Event{Level: log.TRACE, Caller: callerName}, fmt, args...)
}
logInfo := func(fmt string, args ...any) {
logger.Log(2, &log.Event{Level: log.INFO, Caller: callerName}, fmt, args...)
}
logWarn := func(fmt string, args ...any) {
logger.Log(2, &log.Event{Level: log.WARN, Caller: callerName}, fmt, args...)
}
logError := func(fmt string, args ...any) {
logger.Log(2, &log.Event{Level: log.ERROR, Caller: callerName}, fmt, args...)
logRequest := func(level log.Level, fmt string, args ...any) {
logger.Log(2, &log.Event{Level: level, Caller: callerName}, fmt, args...)
}
return func(trigger Event, record *requestRecord) {
if trigger == StartEvent {
@@ -57,7 +47,7 @@ func logPrinter(logger log.Logger) func(trigger Event, record *requestRecord) {
}
// when a request starts, we have no information about the handler function information, we only have the request path
req := record.request
logTrace("router: %s %v %s for %s", startMessage, log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr)
logRequest(log.TRACE, "router: %s %v %s for %s", startMessage, log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr)
return
}
@@ -73,12 +63,12 @@ func logPrinter(logger log.Logger) func(trigger Event, record *requestRecord) {
if trigger == StillExecutingEvent {
message := slowMessage
logf := logWarn
logLevel := log.WARN
if isLongPolling {
logf = logInfo
logLevel = log.INFO
message = pollingMessage
}
logf("router: %s %v %s for %s, elapsed %v @ %s",
logRequest(logLevel, "router: %s %v %s for %s, elapsed %v @ %s",
message,
log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr,
log.ColoredTime(time.Since(record.startTime)),
@@ -88,7 +78,7 @@ func logPrinter(logger log.Logger) func(trigger Event, record *requestRecord) {
}
if panicErr != nil {
logWarn("router: %s %v %s for %s, panic in %v @ %s, err=%v",
logRequest(log.WARN, "router: %s %v %s for %s, panic in %v @ %s, err=%v",
failedMessage,
log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr,
log.ColoredTime(time.Since(record.startTime)),
@@ -102,21 +92,22 @@ func logPrinter(logger log.Logger) func(trigger Event, record *requestRecord) {
if v, ok := record.responseWriter.(types.ResponseStatusProvider); ok {
status = v.WrittenStatus()
}
logf := logInfo
logLevel := record.logLevel
if logLevel == log.UNDEFINED {
logLevel = log.INFO
}
// lower the log level for some specific requests, in most cases these logs are not useful
if status > 0 && status < 400 &&
strings.HasPrefix(req.RequestURI, "/assets/") /* static assets */ ||
req.RequestURI == "/user/events" /* Server-Sent Events (SSE) handler */ ||
req.RequestURI == "/api/actions/runner.v1.RunnerService/FetchTask" /* Actions Runner polling */ {
logf = logTrace
logLevel = log.TRACE
}
message := completedMessage
if isUnknownHandler {
logf = logError
logLevel = log.ERROR
message = unknownHandlerMessage
}
logf("router: %s %v %s for %s, %v %v in %v @ %s",
logRequest(logLevel, "router: %s %v %s for %s, %v %v in %v @ %s",
message,
log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr,
log.ColoredStatus(status), log.ColoredStatus(status, http.StatusText(status)), log.ColoredTime(time.Since(record.startTime)),
+3
View File
@@ -7,6 +7,8 @@ import (
"net/http"
"sync"
"time"
"code.gitea.io/gitea/modules/log"
)
type requestRecord struct {
@@ -23,6 +25,7 @@ type requestRecord struct {
// mutable fields
isLongPolling bool
logLevel log.Level
funcInfo *FuncInfo
panicError error
}
+116 -4
View File
@@ -1155,6 +1155,11 @@
"_dockerhub": "folder-docker",
"-dockerhub": "folder-docker",
"__dockerhub__": "folder-docker",
"nginx": "folder-nginx",
".nginx": "folder-nginx",
"_nginx": "folder-nginx",
"-nginx": "folder-nginx",
"__nginx__": "folder-nginx",
"astro": "folder-astro",
".astro": "folder-astro",
"_astro": "folder-astro",
@@ -1785,6 +1790,11 @@
"_pytest_cache": "folder-python",
"-pytest_cache": "folder-python",
"__pytest_cache__": "folder-python",
"r": "folder-r",
".r": "folder-r",
"_r": "folder-r",
"-r": "folder-r",
"__r__": "folder-r",
"sandbox": "folder-sandbox",
".sandbox": "folder-sandbox",
"_sandbox": "folder-sandbox",
@@ -4517,7 +4527,37 @@
".forms": "folder-form",
"_forms": "folder-form",
"-forms": "folder-form",
"__forms__": "folder-form"
"__forms__": "folder-form",
"deprecated": "folder-deprecated",
".deprecated": "folder-deprecated",
"_deprecated": "folder-deprecated",
"-deprecated": "folder-deprecated",
"__deprecated__": "folder-deprecated",
"scrap": "folder-scrap",
".scrap": "folder-scrap",
"_scrap": "folder-scrap",
"-scrap": "folder-scrap",
"__scrap__": "folder-scrap",
"skill": "folder-skills",
".skill": "folder-skills",
"_skill": "folder-skills",
"-skill": "folder-skills",
"__skill__": "folder-skills",
"skills": "folder-skills",
".skills": "folder-skills",
"_skills": "folder-skills",
"-skills": "folder-skills",
"__skills__": "folder-skills",
"instruction": "folder-instructions",
".instruction": "folder-instructions",
"_instruction": "folder-instructions",
"-instruction": "folder-instructions",
"__instruction__": "folder-instructions",
"instructions": "folder-instructions",
".instructions": "folder-instructions",
"_instructions": "folder-instructions",
"-instructions": "folder-instructions",
"__instructions__": "folder-instructions"
},
"folderNamesExpanded": {
"rust": "folder-rust-open",
@@ -5675,6 +5715,11 @@
"_dockerhub": "folder-docker-open",
"-dockerhub": "folder-docker-open",
"__dockerhub__": "folder-docker-open",
"nginx": "folder-nginx-open",
".nginx": "folder-nginx-open",
"_nginx": "folder-nginx-open",
"-nginx": "folder-nginx-open",
"__nginx__": "folder-nginx-open",
"astro": "folder-astro-open",
".astro": "folder-astro-open",
"_astro": "folder-astro-open",
@@ -6305,6 +6350,11 @@
"_pytest_cache": "folder-python-open",
"-pytest_cache": "folder-python-open",
"__pytest_cache__": "folder-python-open",
"r": "folder-r-open",
".r": "folder-r-open",
"_r": "folder-r-open",
"-r": "folder-r-open",
"__r__": "folder-r-open",
"sandbox": "folder-sandbox-open",
".sandbox": "folder-sandbox-open",
"_sandbox": "folder-sandbox-open",
@@ -9037,7 +9087,37 @@
".forms": "folder-form-open",
"_forms": "folder-form-open",
"-forms": "folder-form-open",
"__forms__": "folder-form-open"
"__forms__": "folder-form-open",
"deprecated": "folder-deprecated-open",
".deprecated": "folder-deprecated-open",
"_deprecated": "folder-deprecated-open",
"-deprecated": "folder-deprecated-open",
"__deprecated__": "folder-deprecated-open",
"scrap": "folder-scrap-open",
".scrap": "folder-scrap-open",
"_scrap": "folder-scrap-open",
"-scrap": "folder-scrap-open",
"__scrap__": "folder-scrap-open",
"skill": "folder-skills-open",
".skill": "folder-skills-open",
"_skill": "folder-skills-open",
"-skill": "folder-skills-open",
"__skill__": "folder-skills-open",
"skills": "folder-skills-open",
".skills": "folder-skills-open",
"_skills": "folder-skills-open",
"-skills": "folder-skills-open",
"__skills__": "folder-skills-open",
"instruction": "folder-instructions-open",
".instruction": "folder-instructions-open",
"_instruction": "folder-instructions-open",
"-instruction": "folder-instructions-open",
"__instruction__": "folder-instructions-open",
"instructions": "folder-instructions-open",
".instructions": "folder-instructions-open",
"_instructions": "folder-instructions-open",
"-instructions": "folder-instructions-open",
"__instructions__": "folder-instructions-open"
},
"rootFolderNames": {},
"rootFolderNamesExpanded": {},
@@ -9063,6 +9143,7 @@
"json5": "json",
"jsonl": "json",
"ndjson": "json",
"schema.json": "json_schema",
"hjson": "hjson",
"jinja": "jinja",
"jinja2": "jinja",
@@ -9261,6 +9342,7 @@
"vcxitems.filters": "visualstudio",
"vcxproj": "visualstudio",
"vcxproj.filters": "visualstudio",
"wixproj": "visualstudio",
"vcl": "varnish",
"pdb": "database",
"sql": "database",
@@ -9984,6 +10066,7 @@
"ast": "sas",
"sast": "sas",
"nupkg": "nuget",
"nuspec": "nuget",
"command": "command",
"dsc": "denizenscript",
"code-search": "search",
@@ -10206,6 +10289,10 @@
"lean": "lean",
"sls": "salt",
"m2": "macaulay2",
"skill.md": "skill",
"skills.md": "skill",
"instructions.md": "instructions",
"instruction.md": "instructions",
"cljx": "clojure",
"clojure": "clojure",
"edn": "clojure",
@@ -10363,7 +10450,6 @@
"jmx": "xml",
"launch": "xml",
"menu": "xml",
"nuspec": "xml",
"opml": "xml",
"owl": "xml",
"proj": "xml",
@@ -11094,7 +11180,18 @@
"rstest.config.ts": "rstack",
"rstest.config.mts": "rstack",
"rstest.config.cts": "rstack",
"rspress.config.js": "rstack",
"rspress.config.mjs": "rstack",
"rspress.config.cjs": "rstack",
"rspress.config.ts": "rstack",
"rspress.config.mts": "rstack",
"rspress.config.cts": "rstack",
"rslint.config.js": "rstack",
"rslint.config.mjs": "rstack",
"rslint.config.cjs": "rstack",
"rslint.config.ts": "rstack",
"rslint.config.mts": "rstack",
"rslint.config.cts": "rstack",
"rslint.json": "rstack",
"rslint.jsonc": "rstack",
"lynx.config.js": "lynx",
@@ -11141,6 +11238,8 @@
".env.dist": "tune",
".env.prod": "tune",
".env.production": "tune",
".env.prod.example": "tune",
".env.production.example": "tune",
".env.stg": "tune",
".env.stage": "tune",
".env.staging": "tune",
@@ -11850,6 +11949,12 @@
"velite.config.ts": "velite",
"velite.config.mts": "velite",
"velite.config.cts": "velite",
"rolldown.config.js": "rolldown",
"rolldown.config.mjs": "rolldown",
"rolldown.config.cjs": "rolldown",
"rolldown.config.ts": "rolldown",
"rolldown.config.mts": "rolldown",
"rolldown.config.cts": "rolldown",
"lerna.json": "lerna",
"windi.config.js": "windicss",
"windi.config.cjs": "windicss",
@@ -12352,6 +12457,7 @@
"lefthookrc": "lefthook",
".github/labeler.yml": "label",
".github/labeler.yaml": "label",
"tags": "label",
"zeabur.json": "zeabur",
"zeabur.jsonc": "zeabur",
"zeabur.json5": "zeabur",
@@ -12427,6 +12533,7 @@
".oxfmtrc.json": "oxc",
".oxfmtrc.jsonc": "oxc",
"oxlint.config.ts": "oxc",
"oxfmt.config.ts": "oxc",
"claude.md": "claude",
"claude.local.md": "claude",
".cursorignore": "cursor",
@@ -12450,6 +12557,9 @@
".shellcheckrc": "shellcheck",
"shellcheckrc": "shellcheck",
"warp.md": "warp",
"skill.md": "skill",
"instructions.md": "instructions",
"instruction.md": "instructions",
"language-configuration.json": "jsonc",
"icon-theme.json": "jsonc",
"color-theme.json": "jsonc",
@@ -12653,7 +12763,9 @@
"helm": "helm",
"nginx": "nginx",
"cue": "cue",
"lean": "lean"
"lean": "lean",
"skill": "skill",
"instructions": "instructions"
},
"light": {
"fileExtensions": {
+16
View File
@@ -319,6 +319,8 @@
"folder-decorators": "<svg viewBox='0 0 32 32'><path fill='#ab47bc' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#e1bee7' d='M23.66 30a7.8 7.8 0 0 1-3.737-.929 7.06 7.06 0 0 1-2.81-2.784 9.2 9.2 0 0 1-1.07-4.655 18.3 18.3 0 0 1 .863-5.874 12.6 12.6 0 0 1 2.349-4.267 10.1 10.1 0 0 1 3.392-2.604A9.3 9.3 0 0 1 26.607 8a5.22 5.22 0 0 1 4.101 1.455A5.64 5.64 0 0 1 32 13.347a5.4 5.4 0 0 1-.069.832q-.07.443-.153.914l-1.611 7.97h-2.308l-.029-1.006h-.11c-.464.258-.96.665-1.488.96a3.96 3.96 0 0 1-1.96.444 3.03 3.03 0 0 1-2.098-.818 2.79 2.79 0 0 1-.904-2.175 4.34 4.34 0 0 1 1.877-3.781 13 13 0 0 1 5.907-1.76 6 6 0 0 0 .167-1.22 3.94 3.94 0 0 0-.611-2.258 2.71 2.71 0 0 0-2.39-.9 4.9 4.9 0 0 0-2.42.692 7.7 7.7 0 0 0-2.266 2.051 10.9 10.9 0 0 0-1.682 3.367 15.3 15.3 0 0 0-.638 4.641 7.05 7.05 0 0 0 .721 3.366 5 5 0 0 0 1.864 2.009 4.67 4.67 0 0 0 2.39.665 4.4 4.4 0 0 0 1.668-.29 6.2 6.2 0 0 0 1.418-.818l1.279 2.272a7.3 7.3 0 0 1-2.21 1.122A9 9 0 0 1 23.66 30m2.018-9.056a2.8 2.8 0 0 0 1.04-.21 4.8 4.8 0 0 0 1.04-.574l.56-2.742a6.8 6.8 0 0 0-2.99.951 1.87 1.87 0 0 0-.772 1.512 1 1 0 0 0 .31.783 1.16 1.16 0 0 0 .812.28'/></svg>",
"folder-delta-open": "<svg viewBox='0 0 32 32'><path fill='#ec407a' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#f8bbd0' d='M23 17.699 28.337 26H17.663zM23 14l-9 14h18z'/></svg>",
"folder-delta": "<svg viewBox='0 0 32 32'><path fill='#ec407a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#f8bbd0' d='M23 17.699 28.337 26H17.663zM23 14l-9 14h18z'/></svg>",
"folder-deprecated-open.clone": "<svg viewBox='0 0 1024 1024'><path fill='#616161' d='M926.912 384H302.144a64 64 0 0 0-60.736 43.776L128 768V320h768a64 64 0 0 0-64-64H483.968a64 64 0 0 1-40.96-14.848l-41.216-34.304A64 64 0 0 0 360.832 192H128a64 64 0 0 0-64 64v512a64 64 0 0 0 64 64h704l153.792-358.784A64 64 0 0 0 926.912 384'/><path fill='#bdbdbd' d='M512 320c-35.456 0-64 28.544-64 64v64c0 35.456 28.544 64 64 64v320c0 35.456 28.544 64 64 64h320c35.456 0 64-28.544 64-64V512c35.456 0 64-28.544 64-64v-64c0-35.456-28.544-64-64-64zm0 64h448v64H512zm128 128h192v64H640z'/></svg>",
"folder-deprecated.clone": "<svg viewBox='0 0 1024 1024'><path fill='#616161' d='m443.008 241.152-41.216-34.304A64 64 0 0 0 360.832 192H128a64 64 0 0 0-64 64v512a64 64 0 0 0 64 64h768a64 64 0 0 0 64-64V320a64 64 0 0 0-64-64H483.968a64 64 0 0 1-40.96-14.848'/><path fill='#bdbdbd' d='M512 320c-35.456 0-64 28.544-64 64v64c0 35.456 28.544 64 64 64v320c0 35.456 28.544 64 64 64h320c35.456 0 64-28.544 64-64V512c35.456 0 64-28.544 64-64v-64c0-35.456-28.544-64-64-64zm0 64h448v64H512zm128 128h192v64H640z'/></svg>",
"folder-desktop-open": "<svg viewBox='0 0 32 32'><path fill='#039be5' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#b3e5fc' d='M30 12H14a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h6v2h-2v2h8v-2h-2v-2h6a2 2 0 0 0 2-2V14a2 2 0 0 0-2-2m0 12H14V14h16Z'/></svg>",
"folder-desktop": "<svg viewBox='0 0 32 32'><path fill='#039be5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b3e5fc' d='M30 12H14a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h6v2h-2v2h8v-2h-2v-2h6a2 2 0 0 0 2-2V14a2 2 0 0 0-2-2m0 12H14V14h16Z'/></svg>",
"folder-development-open.clone": "<svg viewBox='0 0 32 32'><path fill='#0288d1' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#b3e5fc' d='M18.473 30a1 1 0 0 1-.238-.028 1.137 1.137 0 0 1-.828-1.323L20.5 12.905a1.13 1.13 0 0 1 .507-.744 1.06 1.06 0 0 1 .8-.134 1.14 1.14 0 0 1 .828 1.324l-3.101 15.744a1.12 1.12 0 0 1-.504.743 1.06 1.06 0 0 1-.557.162m6.2-2h-.077a1.08 1.08 0 0 1-.762-.412 1.164 1.164 0 0 1 .113-1.548l5.319-4.967-5.296-4.623a1.165 1.165 0 0 1-.162-1.544 1.08 1.08 0 0 1 .754-.437 1.06 1.06 0 0 1 .81.258l6.244 5.455a1.156 1.156 0 0 1 .003 1.723l-6.218 5.808a1.07 1.07 0 0 1-.729.289Zm-9.31 0a1.07 1.07 0 0 1-.728-.292l-6.226-5.811a1.16 1.16 0 0 1-.01-1.692l.02-.018 6.246-5.454a1.03 1.03 0 0 1 .8-.26 1.08 1.08 0 0 1 .76.436 1.165 1.165 0 0 1-.16 1.547l-5.294 4.62 5.32 4.964a1.156 1.156 0 0 1 .112 1.548 1.07 1.07 0 0 1-.762.412Z'/></svg>",
@@ -427,6 +429,8 @@
"folder-include": "<svg viewBox='0 0 32 32'><path fill='#039be5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b3e5fc' d='M25 14a7 7 0 1 0 7 7 7 7 0 0 0-7-7m1 8v4h-2v-4h-4v-2h4v-4h2v4h4v2Z'/></svg>",
"folder-input-open": "<svg viewBox='0 0 32 32'><path fill='#00acc1' d='M28.965 12H9.441a2 2 0 0 0-1.898 1.368L4 24V10h23.998a2 2 0 0 0-2-2H15.122a2 2 0 0 1-1.28-.464l-1.287-1.072A2 2 0 0 0 11.275 6H3.999a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h21.998l4.805-11.212A2 2 0 0 0 28.964 12'/><path fill='#b2ebf2' d='M13.98 10c-1.098 0-1.98.994-1.98 2.229V16h2v-4h16v14H14v-4h-2v3.772c0 1.234.882 2.228 1.98 2.228h16.039c1.098 0 1.98-.994 1.98-2.228V12.229C32 10.994 31.118 10 30.02 10zM22 14v4H12v2h10v4l5.12-5z'/></svg>",
"folder-input": "<svg viewBox='0 0 32 32'><path fill='#00acc1' d='m13.844 7.537-1.287-1.073A2 2 0 0 0 11.277 6H4.001A2 2 0 0 0 2 8v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.125a2 2 0 0 1-1.282-.463'/><path fill='#b2ebf2' d='M13.98 10c-1.098 0-1.98.994-1.98 2.229V16h2v-4h16v14H14v-4h-2v3.772c0 1.234.882 2.228 1.98 2.228h16.039c1.098 0 1.98-.994 1.98-2.228V12.229C32 10.994 31.118 10 30.02 10zM22 14v4H12v2h10v4l5.12-5z'/></svg>",
"folder-instructions-open.clone": "<svg viewBox='0 0 32 32'><path fill='#00b8d4' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#b2ebf2' d='M16 16h-2v13a1 1 0 0 0 1 1h13v-2H16Z'/><path fill='#b2ebf2' d='M31 12H19a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V13a1 1 0 0 0-1-1m-5 12h-6v-2h6Zm4-4H20v-2h10Zm0-4H20v-2h10Z'/></svg>",
"folder-instructions.clone": "<svg viewBox='0 0 32 32'><path fill='#00b8d4' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b2ebf2' d='M16 16h-2v13a1 1 0 0 0 1 1h13v-2H16Z'/><path fill='#b2ebf2' d='M31 12H19a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V13a1 1 0 0 0-1-1m-5 12h-6v-2h6Zm4-4H20v-2h10Zm0-4H20v-2h10Z'/></svg>",
"folder-intellij-open": "<svg viewBox='0 0 32 32'><defs data-mit-no-recolor='true'><linearGradient id='a' x1='.445' x2='104.977' y1='3272.835' y2='3209.742' gradientTransform='translate(18.126 -331.024)scale(.11021)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#fdd835'/><stop offset='1' stop-color='#f57c00'/></linearGradient><linearGradient id='b' x1='22.55' x2='117.962' y1='3121.343' y2='3204.873' gradientTransform='translate(18.126 -331.024)scale(.11021)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#ef5350'/><stop offset='.57' stop-color='#ff6e40'/><stop offset='1' stop-color='#f57c00'/></linearGradient><linearGradient id='c' x1='28.608' x2='-27.937' y1='3197.064' y2='3161.75' gradientTransform='translate(18.126 -331.024)scale(.11021)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#8e24aa'/><stop offset='.385' stop-color='#ab47bc'/><stop offset='.765' stop-color='#ec407a'/><stop offset='.957' stop-color='#ec407a'/></linearGradient><linearGradient id='d' x1='27.588' x2='-27.616' y1='3117.085' y2='3162.678' gradientTransform='translate(18.126 -331.024)scale(.11021)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#ef5350'/><stop offset='.364' stop-color='#ec407a'/><stop offset='1' stop-color='#ec407a'/></linearGradient></defs><path fill='#546e7a' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='url(#a)' d='M30.93 22.519a.68.68 0 0 0 .22-.47.69.69 0 0 0-.647-.72.72.72 0 0 0-.485.161l-12.314 6.745a1.44 1.44 0 0 0-.69.602 1.48 1.48 0 0 0 .506 2.03l.022.013a1.51 1.51 0 0 0 1.573-.03c.03-.029.073-.043.103-.073l11.461-8.053a2 2 0 0 0 .25-.205Z'/><path fill='url(#b)' d='m30.959 21.534-9.376-9.199a1.133 1.133 0 1 0-1.66 1.543 2 2 0 0 0 .176.147l9.904 8.48a.76.76 0 0 0 .441.19.69.69 0 0 0 .72-.646.73.73 0 0 0-.205-.515'/><path fill='url(#c)' d='M21.892 20.711c-.015 0-5.79-4.555-5.907-4.628l-.265-.133a1.644 1.644 0 0 0-1.44 2.94 1.3 1.3 0 0 0 .294.131c.059.03 6.671 2.763 6.671 2.763a.63.63 0 0 0 .647-1.073'/><path fill='url(#d)' d='M20.746 11.968a1.2 1.2 0 0 0-.676.22l-5.849 3.939c-.014.014-.03.014-.03.029h-.014a1.638 1.638 0 0 0 .397 2.865 1.61 1.61 0 0 0 1.528-.205 1.4 1.4 0 0 0 .265-.235l5.084-4.585a1.132 1.132 0 0 0-.705-2.028'/></svg>",
"folder-intellij-open_light": "<svg viewBox='0 0 32 32'><defs data-mit-no-recolor='true'><linearGradient id='a' x1='.445' x2='104.977' y1='3611.926' y2='3548.833' gradientTransform='translate(18.126 -368.395)scale(.11021)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#fdd835'/><stop offset='1' stop-color='#f57c00'/></linearGradient><linearGradient id='b' x1='22.55' x2='117.962' y1='3460.434' y2='3543.963' gradientTransform='translate(18.126 -368.395)scale(.11021)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#ef5350'/><stop offset='.57' stop-color='#ff6e40'/><stop offset='1' stop-color='#f57c00'/></linearGradient><linearGradient id='c' x1='28.608' x2='-27.937' y1='3536.154' y2='3500.841' gradientTransform='translate(18.126 -368.395)scale(.11021)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#8e24aa'/><stop offset='.385' stop-color='#ab47bc'/><stop offset='.765' stop-color='#ec407a'/><stop offset='.957' stop-color='#ec407a'/></linearGradient><linearGradient id='d' x1='27.588' x2='-27.616' y1='3456.176' y2='3501.769' gradientTransform='translate(18.126 -368.395)scale(.11021)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#ef5350'/><stop offset='.364' stop-color='#ec407a'/><stop offset='1' stop-color='#ec407a'/></linearGradient></defs><path fill='#b0bec5' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='url(#a)' d='M30.93 22.519a.68.68 0 0 0 .22-.47.69.69 0 0 0-.647-.72.72.72 0 0 0-.485.161l-12.314 6.745a1.44 1.44 0 0 0-.69.602 1.48 1.48 0 0 0 .506 2.03l.022.013a1.51 1.51 0 0 0 1.573-.03c.03-.029.073-.043.103-.073l11.461-8.053a2 2 0 0 0 .25-.205Z'/><path fill='url(#b)' d='m30.959 21.534-9.376-9.199a1.133 1.133 0 1 0-1.66 1.543 2 2 0 0 0 .176.147l9.904 8.48a.76.76 0 0 0 .441.19.69.69 0 0 0 .72-.646.73.73 0 0 0-.205-.515'/><path fill='url(#c)' d='M21.892 20.711c-.015 0-5.79-4.555-5.907-4.628l-.265-.133a1.644 1.644 0 0 0-1.44 2.94 1.3 1.3 0 0 0 .294.131c.059.03 6.671 2.763 6.671 2.763a.63.63 0 0 0 .647-1.073'/><path fill='url(#d)' d='M20.746 11.968a1.2 1.2 0 0 0-.676.22l-5.849 3.939c-.014.014-.03.014-.03.029h-.014a1.638 1.638 0 0 0 .397 2.865 1.61 1.61 0 0 0 1.528-.205 1.4 1.4 0 0 0 .265-.235l5.084-4.585a1.132 1.132 0 0 0-.705-2.028'/></svg>",
"folder-intellij": "<svg viewBox='0 0 32 32'><defs data-mit-no-recolor='true'><linearGradient id='a' x1='-338.646' x2='-234.114' y1='3272.835' y2='3209.742' gradientTransform='translate(55.497 -331.024)scale(.11021)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#fdd835'/><stop offset='1' stop-color='#f57c00'/></linearGradient><linearGradient id='b' x1='-316.541' x2='-221.129' y1='3121.343' y2='3204.873' gradientTransform='translate(55.497 -331.024)scale(.11021)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#ef5350'/><stop offset='.57' stop-color='#ff6e40'/><stop offset='1' stop-color='#f57c00'/></linearGradient><linearGradient id='c' x1='-310.483' x2='-367.028' y1='3197.064' y2='3161.75' gradientTransform='translate(55.497 -331.024)scale(.11021)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#8e24aa'/><stop offset='.385' stop-color='#ab47bc'/><stop offset='.765' stop-color='#ec407a'/><stop offset='.957' stop-color='#ec407a'/></linearGradient><linearGradient id='d' x1='-311.503' x2='-366.707' y1='3117.085' y2='3162.678' gradientTransform='translate(55.497 -331.024)scale(.11021)' gradientUnits='userSpaceOnUse'><stop offset='0' stop-color='#ef5350'/><stop offset='.364' stop-color='#ec407a'/><stop offset='1' stop-color='#ec407a'/></linearGradient></defs><path fill='#546e7a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='url(#a)' d='M30.93 22.519a.68.68 0 0 0 .22-.47.69.69 0 0 0-.647-.72.72.72 0 0 0-.485.161l-12.314 6.745a1.44 1.44 0 0 0-.69.602 1.48 1.48 0 0 0 .506 2.03l.022.013a1.51 1.51 0 0 0 1.573-.03c.03-.029.073-.043.103-.073l11.461-8.053a2 2 0 0 0 .25-.205Z'/><path fill='url(#b)' d='m30.959 21.534-9.376-9.199a1.133 1.133 0 1 0-1.66 1.543 2 2 0 0 0 .176.147l9.904 8.48a.76.76 0 0 0 .441.19.69.69 0 0 0 .72-.646.73.73 0 0 0-.205-.515'/><path fill='url(#c)' d='M21.892 20.711c-.015 0-5.79-4.555-5.907-4.628l-.265-.133a1.644 1.644 0 0 0-1.44 2.94 1.3 1.3 0 0 0 .294.131c.059.03 6.671 2.763 6.671 2.763a.63.63 0 0 0 .647-1.073'/><path fill='url(#d)' d='M20.746 11.968a1.2 1.2 0 0 0-.676.22l-5.849 3.939c-.014.014-.03.014-.03.029h-.014a1.638 1.638 0 0 0 .397 2.865 1.61 1.61 0 0 0 1.528-.205 1.4 1.4 0 0 0 .265-.235l5.084-4.585a1.132 1.132 0 0 0-.705-2.028'/></svg>",
@@ -517,6 +521,8 @@
"folder-netlify": "<svg viewBox='0 0 32 32'><path fill='#26a69a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#a7ffeb' d='M22 16h-4v6h2v-4h1.5a.5.5 0 0 1 .5.5V22h2v-4a2 2 0 0 0-2-2'/><rect width='6' height='2' x='26' y='18' fill='#a7ffeb' rx='.5'/><rect width='2' height='6' x='20' y='8' fill='#a7ffeb' rx='.5'/><rect width='6' height='2' x='10' y='18' fill='#a7ffeb' rx='.5'/><rect width='2' height='6' x='20' y='24' fill='#a7ffeb' rx='.5'/><path fill='#a7ffeb' d='m13 12.172 1.414-1.414 2.828 2.828L15.828 15zM15.828 23l1.414 1.414-2.828 2.828L13 25.828z'/></svg>",
"folder-next-open": "<svg viewBox='0 0 32 32'><path fill='#546e7a' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#cfd8dc' d='M24 12a8 8 0 1 0 3.969 14.94L22 19v4.5a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-7a.5.5 0 0 1 .5-.5h1.232a.5.5 0 0 1 .416.223l6.736 10.103A7.993 7.993 0 0 0 24 12m4 8h-2v-3.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5Z'/></svg>",
"folder-next": "<svg viewBox='0 0 32 32'><path fill='#546e7a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#cfd8dc' d='M24 12a8 8 0 1 0 3.969 14.94L22 19v4.5a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-7a.5.5 0 0 1 .5-.5h1.232a.5.5 0 0 1 .416.223l6.736 10.103A7.993 7.993 0 0 0 24 12m4 8h-2v-3.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5Z'/></svg>",
"folder-nginx-open": "<svg viewBox='0 0 32 32'><path fill='#388e3c' d='M28.966 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.806-11.212A2 2 0 0 0 28.966 12'/><path fill='#a5d6a7' d='m24 10.857-8 4.571v9.143l8 4.572 8-4.572v-9.143zM28 23.5v.5h-1.76a.5.5 0 0 1-.39-.187L22 19v5h-2v-8h1.76a.5.5 0 0 1 .39.188L26 21v-5h2z'/></svg>",
"folder-nginx": "<svg viewBox='0 0 32 32'><path fill='#388e3c' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#a5d6a7' d='m24 10.857-8 4.571v9.143l8 4.572 8-4.572v-9.143zM28 24h-1.76a.5.5 0 0 1-.39-.187L22 19v5h-2v-8h1.76a.5.5 0 0 1 .39.188L26 21v-5h2z'/></svg>",
"folder-ngrx-actions-open.clone": "<svg viewBox='0 0 32 32'><path fill='#ab47bc' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#e1bee7' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
"folder-ngrx-actions.clone": "<svg viewBox='0 0 32 32'><path fill='#ab47bc' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#e1bee7' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
"folder-ngrx-effects-open.clone": "<svg viewBox='0 0 32 32'><path fill='#00bcd4' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#b2ebf2' d='m23 9-9 3 1 12 8 4 8-4 1-12Zm-1.869 2.785a2.3 2.3 0 0 1 1.124.324 5.3 5.3 0 0 0 1.214.305 6.63 6.63 0 0 1 4.433 2.834c.448.875.356 1.348-.33 1.7-.59.303-1.799.157-3.554-.432l-1.481-.497-.527.199a3.53 3.53 0 0 0-1.84 1.73 2.9 2.9 0 0 0-.218 1.622 2.9 2.9 0 0 0 .41 1.645c.35.613 1.15 1.395 1.287 1.259.038-.038-.044-.287-.182-.553a1.1 1.1 0 0 1-.178-.595c.038-.061.4.165.802.504a5.6 5.6 0 0 0 2.898 1.333c.787.081.967-.064.377-.307a1.8 1.8 0 0 1-.547-.363c-.23-.252-.243-.244.738-.462a4.6 4.6 0 0 0 1.887-.996c.023-.073-.173-.102-.495-.077-.292.023-.53-.006-.53-.067a3 3 0 0 1 .53-.656 4.93 4.93 0 0 0 1.585-3.596l.08-1.114.258.53a3.2 3.2 0 0 1 .133 2.148c-.168.605-.056.672.253.152.382-.644.505-.543.438.364a3.95 3.95 0 0 1-1.183 2.561c-.627.68-.551.803.207.34.731-.449.83-.379.453.325a6.08 6.08 0 0 1-3.782 2.831 6.2 6.2 0 0 1-2.487.16 7.33 7.33 0 0 1-5.44-3.849 13 13 0 0 0-.836-1.437c-.403-.542-.436-.785-.166-1.197a.92.92 0 0 0 .111-.73c-.257-1.451-.248-1.496.337-2.088.512-.513.543-.581.543-1.182 0-.52.052-.69.29-.925a1 1 0 0 1 .561-.291 2.88 2.88 0 0 0 1.624-.865 1.67 1.67 0 0 1 1.203-.587'/></svg>",
@@ -582,6 +588,8 @@
"folder-quasar": "<svg viewBox='0 0 32 32'><path fill='#1976d2' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#bbdefb' d='M24.026 20A2.028 2.028 0 1 1 22 18.048 1.99 1.99 0 0 1 24.026 20m6.967-5.002a10 10 0 0 0-1.59-2.002L27.06 14.3a7.9 7.9 0 0 0-2.445-1.365 9.3 9.3 0 0 0-1.893 2.6 11.74 11.74 0 0 1 7.8 2.618l1.473-.819A9.8 9.8 0 0 0 30.993 15Zm0 10.002A9.8 9.8 0 0 0 32 22.67l-2.342-1.303a7.2 7.2 0 0 0 .005-2.72 10 10 0 0 0-3.285-.278 10.7 10.7 0 0 1 1.545 7.812l1.473.82A10 10 0 0 0 30.993 25m-8.992 5a10.8 10.8 0 0 0 2.597-.326v-2.603a7.9 7.9 0 0 0 2.451-1.357 9.1 9.1 0 0 0-1.392-2.88 11.36 11.36 0 0 1-6.255 5.196v1.64a10.8 10.8 0 0 0 2.599.33m-8.994-5a10 10 0 0 0 1.592 2.004L16.94 25.7a7.8 7.8 0 0 0 2.447 1.365 9.3 9.3 0 0 0 1.891-2.6 11.75 11.75 0 0 1-7.8-2.618l-1.471.819a9.8 9.8 0 0 0 1 2.333Zm0-10A9.8 9.8 0 0 0 12 17.33l2.343 1.303a7.2 7.2 0 0 0-.005 2.72 10 10 0 0 0 3.286.278 10.7 10.7 0 0 1-1.545-7.814l-1.475-.82a10 10 0 0 0-1.597 2.005Zm8.992-5a10.8 10.8 0 0 0-2.597.326v2.603a7.9 7.9 0 0 0-2.45 1.357 9.1 9.1 0 0 0 1.393 2.88A11.35 11.35 0 0 1 24.6 11.97v-1.64A10.8 10.8 0 0 0 22 10Z'/></svg>",
"folder-queue-open": "<svg viewBox='0 0 32 32'><path fill='#039be5' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#b3e5fc' d='M24 16v-2h-3a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h3v-2h-2v-8Zm8-2v-2h-5a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h5v-2h-4V14Zm-16 2h2v8h-2z'/></svg>",
"folder-queue": "<svg viewBox='0 0 32 32'><path fill='#039be5' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#b3e5fc' d='M24 16v-2h-3a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h3v-2h-2v-8Zm8-2v-2h-5a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h5v-2h-4V14Zm-16 2h2v8h-2z'/></svg>",
"folder-r-open": "<svg viewBox='0 0 32 32'><path fill='#1976d2' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#90caf9' fill-rule='evenodd' d='M16 22.405v2.786c-4.8-1.097-8.263-4.17-8.263-7.804 0-4.553 5.432-8.245 12.132-8.245S32 12.834 32 17.387a6.46 6.46 0 0 1-1.915 4.433l-.49-.855a4.65 4.65 0 0 0 .981-2.962c0-3.91-3.758-5.637-8.85-5.637-5.093 0-9.221 2.523-9.221 5.637 0 1.787 1.372 3.37 3.495 4.402m15.332 5.596V28h-.001Zm0-.001h-4.656l-2.177-4.208a5.4 5.4 0 0 0-.72-1.017c-.23-.19-.327-.258-.553-.258H22V28h-4V14h8.274S30 14.07 30 17.76a3.866 3.866 0 0 1-3.559 3.964 9 9 0 0 1 1.156.448 2.2 2.2 0 0 1 .586.431 1.6 1.6 0 0 1 .268.396ZM26 17.5a1.5 1.5 0 0 0-1.5-1.5H22v4h2.5a1.5 1.5 0 0 0 1.5-1.5Z'/></svg>",
"folder-r": "<svg viewBox='0 0 32 32'><path fill='#1976d2' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#90caf9' fill-rule='evenodd' d='M16 22.405v2.786c-4.8-1.097-8.263-4.17-8.263-7.804 0-4.553 5.432-8.245 12.132-8.245S32 12.834 32 17.387a6.46 6.46 0 0 1-1.915 4.433l-.49-.855a4.65 4.65 0 0 0 .981-2.962c0-3.91-3.758-5.637-8.85-5.637-5.093 0-9.221 2.523-9.221 5.637 0 1.787 1.372 3.37 3.495 4.402m15.332 5.596V28h-.001Zm0-.001h-4.656l-2.177-4.208a5.4 5.4 0 0 0-.72-1.017c-.23-.19-.327-.258-.553-.258H22V28h-4V14h8.274S30 14.07 30 17.76a3.866 3.866 0 0 1-3.559 3.964 9 9 0 0 1 1.156.448 2.2 2.2 0 0 1 .586.431 1.6 1.6 0 0 1 .268.396ZM26 17.5a1.5 1.5 0 0 0-1.5-1.5H22v4h2.5a1.5 1.5 0 0 0 1.5-1.5Z'/></svg>",
"folder-react-components-open": "<svg viewBox='0 0 16 16'><path fill='#00bcd4' d='M14.484 6H4.72a1 1 0 0 0-.949.684L2 12V5h13a1 1 0 0 0-1-1H7.562a1 1 0 0 1-.64-.232l-.644-.536A1 1 0 0 0 5.638 3H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h11l2.403-5.606A1 1 0 0 0 14.483 6'/><g fill='#b2ebf2'><path d='M10.5 8.399c2.924 0 4.714 1.037 4.714 1.6s-1.79 1.602-4.714 1.602S5.785 10.564 5.785 10s1.79-1.601 4.715-1.601m0-.8c-3.038 0-5.5 1.075-5.5 2.4s2.462 2.402 5.5 2.402S16 11.326 16 10s-2.463-2.401-5.5-2.401'/><path d='M10.5 9.2a.786.8 0 1 0 .785.8.786.8 0 0 0-.785-.8'/><path d='M8.322 5.8c.793 0 2.333 1.272 3.538 3.4 1.463 2.58 1.476 4.677.997 4.959a.354.36 0 0 1-.18.04c-.792 0-2.333-1.271-3.538-3.399-1.463-2.58-1.476-4.677-.997-4.96a.354.36 0 0 1 .18-.04m0-.8a1.128 1.149 0 0 0-.572.147c-1.128.663-.81 3.374.708 6.054C9.748 13.478 11.491 15 12.678 15a1.128 1.149 0 0 0 .572-.148c1.127-.663.81-3.373-.71-6.053C11.25 6.522 9.509 5 8.323 5Z'/><path d='M12.677 5.8a.354.36 0 0 1 .18.04c.48.283.466 2.38-.997 4.96-1.206 2.128-2.746 3.4-3.538 3.4a.354.36 0 0 1-.18-.04c-.48-.284-.466-2.38.997-4.96 1.206-2.128 2.746-3.4 3.538-3.4m0-.8c-1.186 0-2.929 1.522-4.22 3.8-1.517 2.68-1.835 5.39-.707 6.052a1.128 1.149 0 0 0 .572.148c1.186 0 2.929-1.523 4.22-3.8 1.517-2.68 1.835-5.39.708-6.052A1.128 1.149 0 0 0 12.677 5'/></g></svg>",
"folder-react-components": "<svg viewBox='0 0 16 16'><path fill='#00bcd4' d='m6.922 3.768-.644-.536A1 1 0 0 0 5.638 3H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H7.562a1 1 0 0 1-.64-.232'/><g fill='#b2ebf2'><path d='M10.5 8.399c2.924 0 4.714 1.037 4.714 1.6s-1.79 1.602-4.714 1.602S5.785 10.564 5.785 10s1.79-1.601 4.715-1.601m0-.8c-3.038 0-5.5 1.075-5.5 2.4s2.462 2.402 5.5 2.402S16 11.326 16 10s-2.463-2.401-5.5-2.401'/><path d='M10.5 9.2a.786.8 0 1 0 .785.8.786.8 0 0 0-.785-.8'/><path d='M8.322 5.8c.793 0 2.333 1.272 3.538 3.4 1.463 2.58 1.476 4.677.997 4.959a.354.36 0 0 1-.18.04c-.792 0-2.333-1.271-3.538-3.399-1.463-2.58-1.476-4.677-.997-4.96a.354.36 0 0 1 .18-.04m0-.8a1.128 1.149 0 0 0-.572.147c-1.128.663-.81 3.374.708 6.054C9.748 13.478 11.491 15 12.678 15a1.128 1.149 0 0 0 .572-.148c1.127-.663.81-3.373-.71-6.053C11.25 6.522 9.509 5 8.323 5Z'/><path d='M12.677 5.8a.354.36 0 0 1 .18.04c.48.283.466 2.38-.997 4.96-1.206 2.128-2.746 3.4-3.538 3.4a.354.36 0 0 1-.18-.04c-.48-.284-.466-2.38.997-4.96 1.206-2.128 2.746-3.4 3.538-3.4m0-.8c-1.186 0-2.929 1.522-4.22 3.8-1.517 2.68-1.835 5.39-.707 6.052a1.128 1.149 0 0 0 .572.148c1.186 0 2.929-1.523 4.22-3.8 1.517-2.68 1.835-5.39.708-6.052A1.128 1.149 0 0 0 12.677 5'/></g></svg>",
"folder-redux-actions-open.clone": "<svg viewBox='0 0 32 32'><path fill='#ab47bc' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#f3e5f5' stroke='#f3e5f5' stroke-linejoin='round' stroke-width='.293' d='M25.948 24.114a1.65 1.65 0 0 0 .97-.6 1.8 1.8 0 0 0 .381-1.274 1.72 1.72 0 0 0-1.69-1.596h-.06a1.724 1.724 0 0 0-1.61 1.814 1.85 1.85 0 0 0 .34.985 8.85 8.85 0 0 1-3.863 3.799 6.15 6.15 0 0 1-3.876.771 3.13 3.13 0 0 1-2.32-1.411 3.67 3.67 0 0 1-.18-3.738 5.8 5.8 0 0 1 1.605-1.986.3.3 0 0 0 .098-.313 14 14 0 0 1-.315-1.298.29.29 0 0 0-.172-.213.28.28 0 0 0-.272.036c-3.731 2.836-3.326 6.763-2.188 8.579a5.36 5.36 0 0 0 4.294 2.229q.125 0 .24-.005h.04a6 6 0 0 0 1.5-.188 9.88 9.88 0 0 0 7.078-5.591Z'/><path fill='#f3e5f5' stroke='#f3e5f5' stroke-linejoin='round' stroke-width='.293' d='M30.327 20.428a10.12 10.12 0 0 0-7.774-3.69q-.133 0-.265.003h-.234a1.61 1.61 0 0 0-1.377-.78h-.053a1.62 1.62 0 0 0-1.175.535 1.806 1.806 0 0 0 .039 2.466 1.67 1.67 0 0 0 1.19.494h.064a1.68 1.68 0 0 0 1.375-.886h.27a8.83 8.83 0 0 1 5.126 1.646 6.6 6.6 0 0 1 2.522 3.202 3.48 3.48 0 0 1-.046 2.831 3.39 3.39 0 0 1-3.137 1.97 5.8 5.8 0 0 1-2.32-.522.27.27 0 0 0-.304.054 14 14 0 0 1-1.088.912.294.294 0 0 0 .039.495 7.7 7.7 0 0 0 3.313.84l.192.002a5.66 5.66 0 0 0 4.886-2.948 6.39 6.39 0 0 0-1.243-6.624Z'/><path fill='#f3e5f5' stroke='#f3e5f5' stroke-linejoin='round' stroke-width='.293' d='m17.249 24.295.123-.01-.123.02a1.705 1.705 0 0 0 1.67 1.682h.053a1.715 1.715 0 0 0 1.64-1.778 1.78 1.78 0 0 0-.507-1.224 1.6 1.6 0 0 0-1.187-.493h-.076a9.6 9.6 0 0 1-1.154-5.448 6.83 6.83 0 0 1 1.39-3.853 3.97 3.97 0 0 1 2.842-1.363h.055c2.438 0 3.415 3.34 3.477 4.491a.29.29 0 0 0 .216.265c.299.073.822.246 1.213.384a.27.27 0 0 0 .266-.048.3.3 0 0 0 .105-.247C26.928 12.088 24.204 10 21.804 10a5.96 5.96 0 0 0-5.36 4.39 11.38 11.38 0 0 0 .936 9.155 1.5 1.5 0 0 0-.131.75Z'/></svg>",
@@ -622,6 +630,8 @@
"folder-scala": "<svg viewBox='0 0 32 32'><path fill='#f44336' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffcdd2' d='M18 26v4c2.281-.781 8.713-1.025 10-2v-4c-1.287.975-7.719 1.219-10 2m0-6v4c2.281-.781 8.713-1.025 10-2v-4c-1.287.975-7.719 1.219-10 2m0-6v4c2.281-.781 8.713-1.025 10-2v-4c-1.287.975-7.719 1.219-10 2'/></svg>",
"folder-scons-open": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffcdd2' d='M12 24h8v4h-8Zm12 0h8v4h-8Zm-12-6h4v4h-4Zm16 0h4v4h-4Zm-10-8h8v4h-8Zm4 14-4-4h2v-4h4v4h2Z'/></svg>",
"folder-scons": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124c-.468 0-.921-.164-1.28-.464'/><path fill='#ffcdd2' d='M12 24h8v4h-8Zm12 0h8v4h-8Zm-12-6h4v4h-4Zm16 0h4v4h-4Zm-10-8h8v4h-8Zm4 14-4-4h2v-4h4v4h2Z'/></svg>",
"folder-scrap-open.clone": "<svg fill-rule='evenodd' clip-rule='evenodd' image-rendering='optimizeQuality' shape-rendering='geometricPrecision' text-rendering='geometricPrecision' viewBox='0 0 512 512'><path fill='#616161' d='M463.47 192H151.06c-13.77 0-26 8.82-30.35 21.89L64 384V160h384c0-17.67-14.33-32-32-32H241.98a32 32 0 0 1-20.48-7.42l-20.6-17.15c-5.75-4.8-13-7.43-20.48-7.43H64c-17.67 0-32 14.33-32 32v256c0 17.67 14.33 32 32 32h352l76.88-179.39c1.7-3.98 2.59-8.28 2.59-12.61 0-17.67-14.33-32-32-32'/><path fill='#bdbdbd' d='M320 160v32h-96v32h32v192c0 17.63 14.38 32 32 32h160c17.63 0 32-14.37 32-32V224h32v-32h-96v-32zm0 96v128h32V256zm64 0v128h32V256z'/></svg>",
"folder-scrap.clone": "<svg fill-rule='evenodd' clip-rule='evenodd' image-rendering='optimizeQuality' shape-rendering='geometricPrecision' text-rendering='geometricPrecision' viewBox='0 0 512 512'><path fill='#616161' d='m221.5 120.58-20.6-17.16A32 32 0 0 0 180.42 96H64c-17.67 0-32 14.33-32 32v256c0 17.67 14.33 32 32 32h384c17.67 0 32-14.33 32-32V160c0-17.67-14.33-32-32-32H241.98a32 32 0 0 1-20.48-7.42'/><path fill='#bdbdbd' d='M320 160v32h-96v32h32v192c0 17.63 14.38 32 32 32h160c17.63 0 32-14.37 32-32V224h32v-32h-96v-32zm0 96v128h32V256zm64 0v128h32V256z'/></svg>",
"folder-scripts-open": "<svg viewBox='0 0 32 32'><path fill='#546e7a' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#cfd8dc' d='M28 12h-6a4 4 0 0 0-4 4v8h2v-8h8v9.893a2.074 2.074 0 0 1-1.664 2.08A2 2 0 0 1 24 26h-8a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4V16h2a4 4 0 0 0-4-4'/></svg>",
"folder-scripts": "<svg viewBox='0 0 32 32'><path fill='#546e7a' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#cfd8dc' d='M28 12h-6a4 4 0 0 0-4 4v8h2v-8h8v9.893a2.074 2.074 0 0 1-1.664 2.08A2 2 0 0 1 24 26h-8a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4V16h2a4 4 0 0 0-4-4'/></svg>",
"folder-secure-open": "<svg viewBox='0 0 32 32'><path fill='#f9a825' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#fff9c4' d='M28 16v-3.828a4.116 4.116 0 0 0-3.607-4.153A4 4 0 0 0 20 12v4h-2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2Zm-4 8a2 2 0 1 1 2-2 2 2 0 0 1-2 2m2-8h-4v-4a2 2 0 0 1 4 0Z'/></svg>",
@@ -638,6 +648,8 @@
"folder-shared": "<svg viewBox='0 0 32 32'><path fill='#ab47bc' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#e1bee7' d='M28 26a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2H18a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h-4v2h18v-2Zm-5-6v-2l4 2.798L23 24v-2a4.12 4.12 0 0 0-4 2c.448-2.003.888-3.595 4-4'/></svg>",
"folder-simulations-open": "<svg viewBox='0 0 32 32'><path fill='#ab47bc' d='M28.965 12H9.441a2 2 0 0 0-1.898 1.368L4 24V10h23.998a2 2 0 0 0-2-2H15.122a2 2 0 0 1-1.28-.464l-1.287-1.072A2 2 0 0 0 11.275 6H3.999a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h21.998l4.805-11.212A2 2 0 0 0 28.964 12'/><path fill='#e1bee7' d='m24.029 28.172-1.474-3.447c1.417-.523 2.733-1.225 3.97-2.044zm-6.749-8.723-3.448-1.466 5.501-2.493a18.6 18.6 0 0 0-2.053 3.96m14.383-9.1s-4.458-1.907-9.55 3.187c-1.974 1.97-3.154 4.14-3.914 6.04a1.79 1.79 0 0 0 .409 1.916l1.916 1.908c.5.504 1.237.666 1.906.414a17.2 17.2 0 0 0 6.046-3.914c5.093-5.095 3.187-9.55 3.187-9.55m-6.374 6.363a1.82 1.82 0 0 1 0-2.547 1.81 1.81 0 0 1 2.552 0 1.81 1.81 0 0 1 0 2.547 1.81 1.81 0 0 1-2.552 0m-5.093 6.364-1.26-1.27zm-2.371 4.923 3.278-3.276a2.8 2.8 0 0 1-.873-.406l-3.675 3.682zm-3.811 0h1.259l4.299-4.284-1.282-1.27-4.276 4.285zm0-2.548 3.675-3.671a2.6 2.6 0 0 1-.408-.874l-3.267 3.277z'/></svg>",
"folder-simulations": "<svg viewBox='0 0 32 32'><path fill='#ab47bc' d='m13.843 7.537-1.287-1.073A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h23.998A2 2 0 0 0 30 24V10a2 2 0 0 0-2-2.001H15.122a2 2 0 0 1-1.28-.463'/><path fill='#e1bee7' d='m24.029 28.172-1.474-3.447c1.417-.523 2.733-1.225 3.97-2.044zm-6.749-8.723-3.448-1.466 5.501-2.493a18.6 18.6 0 0 0-2.053 3.96m14.383-9.1s-4.458-1.907-9.55 3.187c-1.974 1.97-3.154 4.14-3.914 6.04a1.79 1.79 0 0 0 .409 1.916l1.916 1.908c.5.504 1.237.666 1.906.414a17.2 17.2 0 0 0 6.046-3.914c5.093-5.095 3.187-9.55 3.187-9.55m-6.374 6.363a1.82 1.82 0 0 1 0-2.547 1.81 1.81 0 0 1 2.552 0 1.81 1.81 0 0 1 0 2.547 1.81 1.81 0 0 1-2.552 0m-5.093 6.364-1.26-1.27zm-2.371 4.923 3.278-3.276a2.8 2.8 0 0 1-.873-.406l-3.675 3.682zm-3.811 0h1.259l4.299-4.284-1.282-1.27-4.276 4.285zm0-2.548 3.675-3.671a2.6 2.6 0 0 1-.408-.874l-3.267 3.277z'/></svg>",
"folder-skills-open": "<svg viewBox='0 0 32 32'><path fill='#ff8f00' d='M28.967 12H9.442a2 2 0 0 0-1.898 1.368L4 24V10h24a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464l-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.805-11.212A2 2 0 0 0 28.967 12'/><path fill='#ffecb3' d='M24 26v2h4v-2zm0-16-4 4v4l2 2v2l2 2h4l2-2v-2l2-2v-4l-4-4-2 2v2l-2 2h-2v-2l2-2h2l2-2z'/></svg>",
"folder-skills": "<svg viewBox='0 0 32 32'><path fill='#ff8f00' d='m13.844 7.536-1.288-1.072A2 2 0 0 0 11.276 6H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2H15.124a2 2 0 0 1-1.28-.464'/><path fill='#ffecb3' d='M24 26v2h4v-2zm0-16-4 4v4l2 2v2l2 2h4l2-2v-2l2-2v-4l-4-4-2 2v2l-2 2h-2v-2l2-2h2l2-2z'/></svg>",
"folder-snapcraft-open": "<svg viewBox='0 0 16 16'><path fill='#66bb6a' d='M14.033 6H4.597a.97.97 0 0 0-.918.684L2 12V5h11.566c0-.552-.433-1-.967-1H7.343a.95.95 0 0 1-.619-.232l-.622-.536A.95.95 0 0 0 5.483 3H1.967C1.433 3 1 3.448 1 4v8c0 .552.433 1 .967 1h10.632l2.323-5.606c.273-.66-.195-1.394-.889-1.394'/><path fill='#dcedc8' d='m12.538 7.077 2.077 1.038-2.077 2.077zM8.385 14l3.807-3.462-1.73-1.73zM7 5l5.192 5.192V7.077zm8.654 2.077-3.116-.346L16 8.46z'/></svg>",
"folder-snapcraft": "<svg viewBox='0 0 16 16'><path fill='#66bb6a' d='m6.922 3.768-.644-.536A1 1 0 0 0 5.638 3H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H7.562a1 1 0 0 1-.64-.232'/><path fill='#dcedc8' d='m12.538 7.077 2.077 1.038-2.077 2.077zM8.385 14l3.807-3.462-1.73-1.73zM7 5l5.192 5.192V7.077zm8.654 2.077-3.116-.346L16 8.46z'/></svg>",
"folder-snippet-open": "<svg viewBox='0 0 32 32'><path fill='#ff9800' d='M29 12H9.4a2 2 0 0 0-1.9 1.4L4 24V10h24a2 2 0 0 0-2-2H15.1a2 2 0 0 1-1.3-.5l-1.2-1a2 2 0 0 0-1.3-.5H4a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h22l4.8-11.2A2 2 0 0 0 29 12'/><path fill='#ffe0b2' d='M20 10s-2 1.86-2 4 6 8 6 8l-.89.88a3 3 0 0 0-5.1 2A3.01 3.01 0 0 0 20.87 28a3.02 3.02 0 0 0 3.11-3.24L26 23.5l.25.31A3.02 3.02 0 0 0 28.88 28 3.01 3.01 0 0 0 32 25.12a3.01 3.01 0 0 0-4.38-2.78zm10 0-4 8 2 2s4-3.94 4-6-2-4-2-4m-9.06 14h.1c.51.02.9.4.95.89v.2a.98.98 0 0 1-1.03.91.99.99 0 0 1-.96-1.04.98.98 0 0 1 .94-.96m8 0h.1c.56.02.98.48.96 1.04a.98.98 0 0 1-1.04.96.98.98 0 0 1-.96-1.04.98.98 0 0 1 .94-.96' style='-inkscape-stroke:none'/></svg>",
@@ -808,6 +820,7 @@
"image": "<svg viewBox='0 0 16 16'><path fill='#26a69a' d='M8.5 6h4l-4-4zM3.875 1H9.5l4 4v8.6c0 .773-.616 1.4-1.375 1.4h-8.25c-.76 0-1.375-.627-1.375-1.4V2.4c0-.777.612-1.4 1.375-1.4M4 13.6h8V8l-2.625 2.8L8 9.4zm1.25-7.7c-.76 0-1.375.627-1.375 1.4s.616 1.4 1.375 1.4c.76 0 1.375-.627 1.375-1.4S6.009 5.9 5.25 5.9'/></svg>",
"imba": "<svg stroke-linejoin='round' stroke-miterlimit='1.414' clip-rule='evenodd' viewBox='0 0 201 201'><path fill='#ffc400' d='M161.96 61.952c-3.043 13.905-32.633 79.576-36.431 94.457-2.698 10.575 11.229 23.851 13.555 15.159 6.84-25.548 37.32-86.251 39.023-98.893 1.468-10.897-14.66-17.516-16.147-10.723m-37.128 48.192a4.97 4.97 0 0 1 5.726 6.91c.023.012.021.015.021.016a19.04 19.04 0 0 1-13.667 10.676c-3.4.645-7.236 1.182-11.504 1.588-15.316 1.453-31.743-17.007-20.624-16.49 16.552.77 29.747-.447 40.047-2.7zm16.256-17.347a13.36 13.36 0 0 1-9.677 8.152c-20.232 4.242-49.32 2.59-63.662-.888-13.94-3.38-23.102-23.665-14.05-20.64 21.019 7.024 60.118 9.347 82.248 6.838a4.808 4.808 0 0 1 5.133 6.523c.011.004.011.004.008.015m8.398-23.8a11.39 11.39 0 0 1-9.973 8.037c-40.633 2.924-92.83-6.466-107.91-22.019C20.273 43.326 21 20.85 27.442 27.992c24.417 27.072 84.437 34.865 117.12 34.521a5.022 5.022 0 0 1 4.92 6.481q.003.002.001.003z'/></svg>",
"installation": "<svg viewBox='0 0 16 16'><path fill='#ff5722' d='M12 7h-2V2H6v5H4l4 4zm-9 5.5V14h10v-1.5z'/></svg>",
"instructions.clone": "<svg fill='none' viewBox='0 0 24 24'><path d='M0 0h24v24H0z'/><path fill='#00b8d4' d='M4 6H2v14c0 1.1.9 2 2 2h14v-2H4zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2m0 14H8V4h12zM10 9h8v2h-8zm0 3h4v2h-4zm0-6h8v2h-8z'/></svg>",
"ionic": "<svg viewBox='0 0 512 512'><g fill='#448aff'><path d='M423.59 132.8a31.86 31.86 0 0 0 5.408-17.804c0-17.675-14.33-32-32-32a31.85 31.85 0 0 0-17.805 5.409c-34.486-25.394-77.085-40.409-123.2-40.409-114.88 0-208 93.125-208 208 0 114.88 93.125 208 208 208 114.87 0 208-93.123 208-208 0-46.111-15.016-88.71-40.408-123.2zm-31.762 259.03c-17.646 17.646-38.191 31.499-61.064 41.174-23.672 10.012-48.826 15.089-74.766 15.089s-51.095-5.077-74.767-15.089c-22.873-9.675-43.417-23.527-61.064-41.174s-31.5-38.191-41.174-61.064c-10.013-23.672-15.09-48.828-15.09-74.768s5.077-51.095 15.089-74.767c9.674-22.873 23.527-43.417 41.174-61.064s38.191-31.5 61.064-41.174c23.673-10.013 48.828-15.09 74.768-15.09s51.094 5.077 74.766 15.089a191.2 191.2 0 0 1 37.802 21.327 31.85 31.85 0 0 0-3.568 14.679c0 17.675 14.327 32 32 32 5.293 0 10.28-1.293 14.678-3.568a191 191 0 0 1 21.327 37.801c10.013 23.672 15.09 48.827 15.09 74.767s-5.077 51.096-15.09 74.768c-9.675 22.873-23.527 43.418-41.175 61.064'/><circle cx='256' cy='256' r='96'/></g></svg>",
"istanbul": "<svg viewBox='0 0 32 32'><path fill='#fdd835' d='M30 13v-3h-2v3h-1v.5a.5.5 0 0 0 .5.5h.5v10h-1v-2h-1v-8h-1v6h-2v-3h-1v1h-1v-1h1a6.35 6.35 0 0 0-6-4 6.35 6.35 0 0 0-6 4h1v1h-1v-1H9v3H7v-6H6v8H5v2H4V14h.5a.5.5 0 0 0 .5-.5V13H4v-3H2v3H1v.5a.5.5 0 0 0 .5.5H2v12h8v-6h2v6h8v-6h2v6h8V14h.5a.5.5 0 0 0 .5-.5V13ZM14 24h-1v-2h1Zm3 0h-2v-2a1 1 0 0 1 2 0Zm2 0h-1v-2h1Zm-1-7v1h-1v-1Zm-3 0v1h-1v-1Zm-3 0h1v1h-1Zm7 1v-1h1v1ZM3 6 2 9h2zm26 0-1 3h2z'/><path fill='#fdd835' d='m16 10-1 2h2zm9.5 1-.5 2h1zm-19 0L6 13h1z'/></svg>",
"jar": "<svg viewBox='0 0 32 32'><path fill='#f44336' d='M22 10h2v4h-2z'/><path fill='#f44336' d='M28 2H4a2 2 0 0 0-2 2v24a2 2 0 0 0 2 2h24a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2m-2 12a2 2 0 0 1-2 2h-2v4a4 4 0 0 1-4 4h-8a4 4 0 0 1-4-4V8h18a2 2 0 0 1 2 2Z'/></svg>",
@@ -821,6 +834,7 @@
"jinja_light": "<svg viewBox='0 0 32 32'><path fill='#616161' d='M30 8V4a94.4 94.4 0 0 1-14 1A94.4 94.4 0 0 1 2 4v4s1.482.247 3.95.495L5.4 14H2v2h3.2L4 28h6V16h12v12h6l-1.2-12H30v-2h-3.4l-.55-5.505C28.517 8.247 30 8 30 8m-20 6V8.817c1.235.074 2.576.13 4 .16V14Zm12 0h-4V8.977a104 104 0 0 0 4-.16Z'/></svg>",
"jsconfig": "<svg viewBox='0 0 32 32'><path fill='#757575' d='M15 2H6a2.006 2.006 0 0 0-2 2v22a2.006 2.006 0 0 0 2 2h6v-4H6v-2h6v-2H6v-2h6v-2H6v-2h6v-2h2V4l8 8h2v-1Z' data-mit-no-recolor='true'/><path fill='#ffca28' d='M12 12v18h18V12zm8 12a2.006 2.006 0 0 1-2 2h-2a2.006 2.006 0 0 1-2-2v-2h2v2h2v-8h2zm8-6h-4v2h2a2.006 2.006 0 0 1 2 2v2a2.006 2.006 0 0 1-2 2h-4v-2h4v-2h-2a2.006 2.006 0 0 1-2-2v-2a2.006 2.006 0 0 1 2-2h4z'/></svg>",
"json": "<svg viewBox='0 -960 960 960'><path fill='#f9a825' d='M560-160v-80h120q17 0 28.5-11.5T720-280v-80q0-38 22-69t58-44v-14q-36-13-58-44t-22-69v-80q0-17-11.5-28.5T680-720H560v-80h120q50 0 85 35t35 85v80q0 17 11.5 28.5T840-560h40v160h-40q-17 0-28.5 11.5T800-360v80q0 50-35 85t-85 35zm-280 0q-50 0-85-35t-35-85v-80q0-17-11.5-28.5T120-400H80v-160h40q17 0 28.5-11.5T160-600v-80q0-50 35-85t85-35h120v80H280q-17 0-28.5 11.5T240-680v80q0 38-22 69t-58 44v14q36 13 58 44t22 69v80q0 17 11.5 28.5T280-240h120v80z'/></svg>",
"json_schema": "<svg viewBox='0 -960 960 960'><path fill='#f9a825' d='M560-160v-80h120q17 0 28.5-11.5T720-280v-80q0-38 22-69t58-44v-14q-36-13-58-44t-22-69v-80q0-17-11.5-28.5T680-720H560v-80h120q50 0 85 35t35 85v80q0 17 11.5 28.5T840-560h40v160h-40q-17 0-28.5 11.5T800-360v80q0 50-35 85t-85 35zm-280 0q-50 0-85-35t-35-85v-80q0-17-11.5-28.5T120-400H80v-160h40q17 0 28.5-11.5T160-600v-80q0-50 35-85t85-35h120v80H280q-17 0-28.5 11.5T240-680v80q0 38-22 69t-58 44v14q36 13 58 44t22 69v80q0 17 11.5 28.5T280-240h120v80z'/><path fill='#f9a825' stroke='#f9a825' stroke-width='36.192' d='M581.79-570.48 446.07-434.76l-67.859-67.859-22.62 22.62 90.479 90.479 158.34-158.34z'/></svg>",
"jsr": "<svg viewBox='0 0 16 16'><path fill='#fdd835' d='M2 7h1v2h1V5h1v5H2m4-5h4v1H7v1.5h3V11H6v-1h3V8.5H6M11 6h3v2.5h-1V7h-1v4h-1'/></svg>",
"jsr_light": "<svg viewBox='0 0 16 16'><path fill='#37474f' d='M1 6h2V4h8v1h4v5h-2v2H5v-1H1'/><path fill='#fdd835' d='M2 7h1v2h1V5h1v5H2m4-5h4v1H7v1.5h3V11H6v-1h3V8.5H6M11 6h3v2.5h-1V7h-1v4h-1'/></svg>",
"julia": "<svg viewBox='0 0 50 50'><g transform='translate(.21 -247.01)'><circle cx='13.497' cy='281.63' r='9.555' fill='#c62828'/><circle cx='36.081' cy='281.63' r='9.555' fill='#7e57c2'/><circle cx='24.722' cy='262.39' r='9.555' fill='#388e3c'/></g></svg>",
@@ -1024,6 +1038,7 @@
"robots": "<svg viewBox='0 0 32 32'><path fill='#ff5252' d='M28.586 18H28a8 8 0 0 0-8-8h-2V8.445a4 4 0 1 0-4 0V10h-2a8 8 0 0 0-8 8h-.586A1.414 1.414 0 0 0 2 19.414v3.172A1.414 1.414 0 0 0 3.414 24H4v1a3 3 0 0 0 3 3h18a3 3 0 0 0 3-3v-1h.586A1.414 1.414 0 0 0 30 22.586v-3.172A1.414 1.414 0 0 0 28.586 18M11 22a3 3 0 1 1 3-3 3 3 0 0 1-3 3m10 0a3 3 0 1 1 3-3 3 3 0 0 1-3 3'/></svg>",
"rocket": "<svg viewBox='0 0 32 32'><path fill='#ef5350' d='M12 26c-1.71 1.905-7.64 2.149-7.93 1.937l-.004.003-.002-.005-.005-.002.004-.003C3.85 27.64 4.1 21.715 6 20ZM29.64 7.908c-.655 2.69-1.681 5.64-5.64 10.092l-.789 4.749a4 4 0 0 1-1.116 2.171l-4.863 4.868A.72.72 0 0 1 16 29.277v-6.23L9 16H2.723a.721.721 0 0 1-.511-1.232L7.08 9.905A4 4 0 0 1 9.25 8.79L14 8c4.453-3.959 7.402-4.985 10.092-5.64a11.1 11.1 0 0 1 3.642-.329 3.1 3.1 0 0 1 1.72.515 3.1 3.1 0 0 1 .515 1.72 11.1 11.1 0 0 1-.33 3.642ZM26 10a4 4 0 1 0-4 4 4 4 0 0 0 4-4'/></svg>",
"rojo": "<svg fill='none' viewBox='0 0 24 24'><path fill='#e53935' d='M9.586 2h.077l-.077.135v.29c.598-.116.908-.27.908-.425l.425.058A36 36 0 0 1 12.869 2h.077c.927 0 2.009.502 3.284 1.526.27.444.424.83.502 1.197v.695c-.155.966-.425 1.7-.773 2.183q-.319.26-1.39 1.738h-.078v-.135l.194-.29h-.116a17.6 17.6 0 0 1-5.601 3.226 6.2 6.2 0 0 1-1.545.212c.656.772 1.873 1.796 3.63 3.07.174.097 1.295.812 3.361 2.183 1.719 1.062 3.766 2.221 6.161 3.496v.077h-.29c-.463-.174-.695-.309-.695-.367l-.135.077h-.135c-.058 0-.078-.019-.078-.077-.173.039-.309.213-.424.502.29.078.424.174.424.27-.115.097-.193.155-.27.155l-.348-.077v.077c0 .078.058.135.194.135v.213h-.058c-.097 0-1.16-.618-3.148-1.835-1.41-.715-3.149-1.815-5.176-3.283-.676-.31-1.912-1.217-3.728-2.723h-.193c-.27.405-.618 1.41-1.062 3.013-.097.367-.56.907-1.39 1.602h-.155l-.135-.212v-.077c0-.097.077-.31.212-.618v-.077h-.077c0 .135-.29.347-.83.637H3v-.135c1.642-4.867 2.781-8.035 3.438-9.522q1.68-3.852 1.68-4.403c-.251 0-.811.309-1.68.965h-.135l.058-.135v-.135c-.252 0-.831.406-1.739 1.255h-.077v-.077l.908-1.043.154-.212v-.077h-.154l-.83.695c-.097 0-.136-.039-.136-.116A11.1 11.1 0 0 1 7.693 3.12c1.178-.309 1.758-.656 1.758-1.062zm6.373 1.95v.078c0 .077.058.135.135.135v-.077c0-.077-.038-.135-.135-.135M7.77 10.673h.058c.85-.174 1.275-.31 1.275-.425-.058-.097-.077-.155-.077-.213C10.803 9.436 12 8.837 12.599 8.22c.058 0 .425-.386 1.12-1.198h.077V7.1c-.096.154-.154.27-.154.347h.077c.599-.463.908-.965.908-1.525v-.425c-.136 0-.213-.058-.213-.155q.348-.057.348-.347 0-.348-1.39-.696c-.754-.193-1.565-.27-2.453-.27-.445 0-.889.811-1.333 2.453-.193.193-.792 1.583-1.816 4.19m.83-5.949V4.8c.078 0 .136-.077.213-.212v-.077c-.058 0-.135.077-.212.212m.078 1.333h.077c.136-.058.213-.193.213-.425-.077 0-.174.135-.29.425m6.586.27v.155c.058 0 .135-.078.193-.213v-.077H15.4c-.096.02-.135.058-.135.135m-7.57 4.693v.078h.134a.5.5 0 0 1 .213-.078v.078c1.14-.213 1.815-.445 2.047-.696h-.077c-1.024.232-1.796.444-2.318.618m-.271.696h.058l1.274-.27-.077-.136c-.83.097-1.255.251-1.255.406m2.78 4.345v.135c.735.56 1.179.85 1.334.85-.174-.135-.27-.251-.27-.348zm2.048 1.545c.039.174.174.27.406.27h.077v-.077c-.116 0-.251-.058-.425-.193zm4.539 2.51q.55.495 1.68.986l.077-.135c-.58-.348-1.12-.638-1.603-.85zm3.862 1.121h.213c.077.02.135.058.135.135-.097 0-.135.078-.135.213h-.155l.078-.136v-.077h-.136z'/></svg>",
"rolldown": "<svg fill='none' viewBox='0 0 16 16'><path fill='#f4511e' d='M13.628 1c.3 0 .465.35.272.582l-3.277 3.933c-.385.462-.229 1.485.374 1.485h3.644c.301 0 .465.35.273.582l-6.645 7.29a.355.355 0 0 1-.545 0l-6.64-7.29A.355.355 0 0 1 1.355 7h3.64c.602 0 .759-1.023.373-1.485L2.092 1.582A.355.355 0 0 1 2.364 1z'/></svg>",
"rollup": "<svg viewBox='100 100 800 800'><path fill='#f44336' d='M733.79 394.71c0 77.407-42.308 144.79-104.67 180.51-3.76 3.134-5.954 8.148-3.76 12.849l106.87 211.22c2.82 6.581-1.568 14.103-8.776 14.103h-408.35l2.194-1.254c15.356-8.774 121.91-219.06 225.95-318.72 104.05-99.658 117.21-66.439 59.857-174.87 0 0 44.188 86.182 6.581 92.763-29.459 5.328-97.15-60.17-72.08-119.09 25.071-57.664 123.79-46.695 169.23.314 17.236 30.085 26.952 64.872 26.952 102.17m-385.47 140.71c-41.367 76.154-67.692 131.62-82.108 170.48v-509.57c0-5.328 4.388-9.715 9.715-9.715h252.91c73.333 1.253 137.58 40.114 173.62 98.718-26.325-32.906-67.692-51.71-108.43-51.71-77.407 0-96.837 28.206-245.7 301.79z'/></svg>",
"rome": "<svg viewBox='0 0 32 32'><path fill='#546e7a' d='M9.875 5.409A14.02 14.02 0 0 0 2.01 17.48a.51.51 0 0 0 .508.519H5.52a.495.495 0 0 0 .491-.481 10.01 10.01 0 0 1 5.273-8.337.494.494 0 0 0 .243-.592l-.958-2.882a.505.505 0 0 0-.694-.3Zm11.556.299-.958 2.882a.494.494 0 0 0 .243.592 10.01 10.01 0 0 1 5.273 8.337.495.495 0 0 0 .49.481h3.004a.51.51 0 0 0 .508-.519A14.02 14.02 0 0 0 22.125 5.41a.505.505 0 0 0-.694.299ZM26 20.5v9a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5m-24 0v9a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5'/><path fill='#ffc400' d='M16.13 10h-.26A7.87 7.87 0 0 0 8 17.87V29.5a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5V17.87A3.88 3.88 0 0 1 15.87 14h.26A3.88 3.88 0 0 1 20 17.87V29.5a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5V17.87A7.87 7.87 0 0 0 16.13 10m1.51-2h-3.28a.5.5 0 0 1-.474-.342l-1.667-5A.5.5 0 0 1 12.694 2h6.612a.5.5 0 0 1 .475.658l-1.667 5A.5.5 0 0 1 17.64 8'/></svg>",
"routing": "<svg viewBox='0 0 32 32'><path fill='#43a047' d='M18 14v-2h8l2-3-2-3h-8V4l-2-2-2 2v6H6l-2 3 2 3h8v10a4 4 0 0 0-4 4h12a4 4 0 0 0-4-4v-6h8l2-3-2-3Z'/></svg>",
@@ -1060,6 +1075,7 @@
"simulink": "<svg viewBox='0 0 4.233 4.233'><path fill='#9e9e9e' d='M.53 1.323v.264h2.38v.794h.265V1.323z'/><path fill='#ff6e40' d='M2.381 2.381h1.323v1.323H2.381z'/><path fill='#64b5f6' d='m2.381 1.455-1.587.926V.53z'/></svg>",
"siyuan": "<svg viewBox='0 0 32 32'><path fill='#e53935' d='M2 11.976 10 4v16l-8 8Z'/><path fill='#455a64' d='M30 11.976 22 4v15.99L30 28ZM10 4l6 6v16l-6-6Z'/><path fill='#e53935' d='m22 4-6 6v16l6-6.01Z'/></svg>",
"sketch": "<svg viewBox='0 0 24 24'><path fill='#ffc107' d='M15.705 9.221h2.779l-4.632 6.484m-3.705-6.484h3.705L12 16.631m-6.484-7.41h2.779l1.852 6.484M14.78 4.59h1.852l1.853 2.779h-2.78m-4.63-2.779h1.852l.926 2.779h-3.705M7.368 4.59h1.853l-.926 2.78h-2.78m.927-4.631L2.737 8.294 12 21.263l9.262-12.968-3.705-5.557z'/></svg>",
"skill": "<svg viewBox='0 0 16 16'><path fill='#ff8f00' d='M6 12v1h4v-1zm1 2v1h2v-1zM7 1 5 2 4 4v2l1 2 1 1v1l1 1h2l1-1V9l1-1 1-2V4l-1-2-2-1v2L7 5H6V4l2-2h1v1-2z'/></svg>",
"slim": "<svg viewBox='0 0 32 32'><path fill='#f57f17' d='M23 2H9a7 7 0 0 0-7 7v14a7 7 0 0 0 7 7h14a7 7 0 0 0 7-7V9a7 7 0 0 0-7-7m-5 14-4-6v6H6a10 10 0 0 1 20 0Z'/></svg>",
"slint": "<svg viewBox='0 0 16 16'><path fill='#2979ff' d='M12 1 3 7l5 2-2-2Z'/><path fill='#2979ff' d='m4 15 9-6-5-2 2 2Z'/></svg>",
"slug": "<svg viewBox='0 0 24 24'><path fill='#f9a825' d='m9.164 21.221-.983-.056c-3.402-.19-5.654-.714-6.125-1.427-.136-.205-.146-.515-.022-.681.115-.154.377-.28.744-.355l.313-.064.373.122c1.568.517 2.903.589 4.875.263.82-.135 1.26-.29 1.736-.613.183-.124.26-.152.413-.152.62-.002 1.581-.168 2.066-.357 1.392-.544 2.655-2.023 2.979-3.49.159-.717.072-1.83-.211-2.693l-.13-.397.028-.747c.023-.606.047-.818.126-1.123.29-1.108.991-1.878 1.957-2.145.374-.103 1.17-.093 1.589.02a3.34 3.34 0 0 1 1.595.941c.505.548.707 1.025.735 1.738.021.55-.034.809-.283 1.316-.211.43-.542.833-.909 1.107-.15.113-.302.23-.336.26-.052.046-.067.192-.087.837-.014.43-.053.986-.086 1.235-.458 3.354-2.234 5.421-5.307 6.174-1.023.25-1.37.283-3.183.292-.908.005-1.748.002-1.867-.005m-3.967-2.877-.45-.07-.3-.322c-1.514-1.613-2.085-4.002-1.43-5.98.646-1.953 2.32-3.39 4.496-3.86.557-.121 1.912-.133 2.437-.023a6.7 6.7 0 0 1 1.631.56c1.828.904 2.982 2.495 3.205 4.421.111.955-.13 1.793-.763 2.651-.712.966-1.618 1.525-2.681 1.654l-.233.028.267-.277c.475-.495.596-.93.596-2.15 0-.683-.011-.831-.087-1.134-.182-.721-.476-1.239-.967-1.705-.643-.611-1.44-.923-2.349-.92-1.295.005-2.196.828-2.196 2.006 0 .398.069.695.24 1.033.135.269.446.62.68.769.368.233.984.35 1.195.225a.387.387 0 0 0 .096-.61c-.09-.101-.15-.126-.364-.154-.337-.043-.437-.086-.642-.275-.26-.239-.395-.57-.397-.964-.002-.269.014-.348.11-.543.133-.27.396-.497.703-.603.327-.114.98-.096 1.386.038.854.281 1.456.918 1.699 1.797.054.196.07.401.07.903 0 .57-.011.683-.092.941-.103.327-.31.758-.488 1.019-.295.429-.853.927-1.283 1.144-.691.348-2.98.573-4.09.4zM19.199 6.578l-.244-.078.14-.4c.537-1.544 1.334-2.521 2.057-2.521.567 0 1.043.588.862 1.067-.045.12-.244.345-.37.419-.044.025-.31.054-.597.065-.497.019-.524.024-.688.139-.221.154-.503.575-.69 1.03-.081.2-.166.361-.188.36a3 3 0 0 1-.282-.081m-2.185-.06c-.068-.115-.127-.837-.126-1.545.001-.639.015-.863.07-1.08.194-.765.529-1.121 1.055-1.121.588 0 .932.42.782.957-.072.258-.183.365-.591.568-.312.155-.394.216-.496.369-.176.262-.228.59-.2 1.253l.023.538-.165.027c-.09.014-.202.035-.249.047s-.092.006-.103-.012z'/></svg>",
+13 -4
View File
@@ -213,6 +213,9 @@
"editor.buttons.switch_to_legacy.tooltip": "Use the legacy editor instead",
"editor.buttons.enable_monospace_font": "Enable monospace font",
"editor.buttons.disable_monospace_font": "Disable monospace font",
"editor.code_editor.command_palette": "Command Palette",
"editor.code_editor.find": "Find",
"editor.code_editor.placeholder": "Enter file content here",
"filter.string.asc": "AZ",
"filter.string.desc": "ZA",
"error.occurred": "An error occurred",
@@ -2250,13 +2253,14 @@
"repo.settings.webhook.delivery.success": "An event has been added to the delivery queue. It may take few seconds before it shows up in the delivery history.",
"repo.settings.githooks_desc": "Git Hooks are powered by Git itself. You can edit hook files below to set up custom operations.",
"repo.settings.githook_edit_desc": "If the hook is inactive, sample content will be presented. Leaving content to an empty value will disable this hook.",
"repo.settings.githook_name": "Hook Name",
"repo.settings.githook_content": "Hook Content",
"repo.settings.update_githook": "Update Hook",
"repo.settings.add_webhook_desc": "Gitea will send <code>POST</code> requests with a specified content type to the target URL. Read more in the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">webhooks guide</a>.",
"repo.settings.payload_url": "Target URL",
"repo.settings.http_method": "HTTP Method",
"repo.settings.content_type": "POST Content Type",
"repo.settings.webhook.name": "Webhook name",
"repo.settings.webhook.name_helper": "Optionally give this webhook a friendly name",
"repo.settings.webhook.name_empty": "Unnamed Webhook",
"repo.settings.secret": "Secret",
"repo.settings.webhook_secret_desc": "If the webhook server supports using secret, you can follow the webhook's manual and fill in a secret here.",
"repo.settings.slack_username": "Username",
@@ -2778,9 +2782,9 @@
"org.settings.labels_desc": "Add labels which can be used on issues for <strong>all repositories</strong> under this organization.",
"org.members.membership_visibility": "Membership Visibility:",
"org.members.public": "Visible",
"org.members.public_helper": "make hidden",
"org.members.public_helper": "Make hidden",
"org.members.private": "Hidden",
"org.members.private_helper": "make visible",
"org.members.private_helper": "Make visible",
"org.members.member_role": "Member Role:",
"org.members.owner": "Owner",
"org.members.member": "Member",
@@ -2808,7 +2812,10 @@
"org.teams.no_desc": "This team has no description",
"org.teams.settings": "Settings",
"org.teams.owners_permission_desc": "Owners have full access to <strong>all repositories</strong> and have <strong>administrator access</strong> to the organization.",
"org.teams.owners_permission_suggestion": "You can create new teams for members to get fine-grained access control.",
"org.teams.members": "Team Members",
"org.teams.manage_team_member": "Manage teams and members",
"org.teams.manage_team_member_prompt": "Members are managed through teams. Add users to a team to invite them to this organization.",
"org.teams.update_settings": "Update Settings",
"org.teams.delete_team": "Delete Team",
"org.teams.add_team_member": "Add Team Member",
@@ -3715,6 +3722,8 @@
"actions.runs.workflow_run_count_1": "%d workflow run",
"actions.runs.workflow_run_count_n": "%d workflow runs",
"actions.runs.commit": "Commit",
"actions.runs.run_details": "Run Details",
"actions.runs.workflow_file": "Workflow file",
"actions.runs.scheduled": "Scheduled",
"actions.runs.pushed_by": "pushed by",
"actions.runs.invalid_workflow_helper": "Workflow config file is invalid. Please check your config file: %s",
+36 -26
View File
@@ -2,7 +2,7 @@
"type": "module",
"packageManager": "pnpm@10.33.0",
"engines": {
"node": ">= 22.6.0",
"node": ">= 22.18.0",
"pnpm": ">= 10.0.0"
},
"dependencies": {
@@ -10,44 +10,56 @@
"@citation-js/plugin-bibtex": "0.7.21",
"@citation-js/plugin-csl": "0.7.22",
"@citation-js/plugin-software-formats": "0.6.2",
"@codemirror/autocomplete": "6.20.1",
"@codemirror/commands": "6.10.3",
"@codemirror/lang-json": "6.0.2",
"@codemirror/lang-markdown": "6.5.0",
"@codemirror/language": "6.12.3",
"@codemirror/language-data": "6.5.2",
"@codemirror/legacy-modes": "6.5.2",
"@codemirror/lint": "6.9.5",
"@codemirror/search": "6.6.0",
"@codemirror/state": "6.6.0",
"@codemirror/view": "6.41.0",
"@github/markdown-toolbar-element": "2.2.3",
"@github/paste-markdown": "1.5.3",
"@github/text-expander-element": "2.9.4",
"@lezer/highlight": "1.2.3",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@mermaid-js/layout-elk": "0.2.1",
"@primer/octicons": "19.23.1",
"@replit/codemirror-indentation-markers": "6.5.3",
"@replit/codemirror-lang-nix": "6.0.1",
"@replit/codemirror-lang-svelte": "6.0.0",
"@replit/codemirror-vscode-keymap": "6.0.2",
"@resvg/resvg-wasm": "2.6.2",
"@silverwind/vue3-calendar-heatmap": "2.1.1",
"@techknowlogick/license-checker-webpack-plugin": "0.3.0",
"add-asset-webpack-plugin": "3.1.1",
"@vitejs/plugin-vue": "6.0.5",
"ansi_up": "6.0.6",
"asciinema-player": "3.15.1",
"chart.js": "4.5.1",
"chartjs-adapter-dayjs-4": "1.0.4",
"chartjs-plugin-zoom": "2.2.0",
"clippie": "4.1.10",
"codemirror-lang-elixir": "4.0.1",
"colord": "2.9.3",
"compare-versions": "6.1.1",
"cropperjs": "1.6.2",
"css-loader": "7.1.4",
"dayjs": "1.11.20",
"dropzone": "6.0.0-beta.2",
"easymde": "2.20.0",
"esbuild-loader": "4.4.2",
"esbuild": "0.27.4",
"htmx.org": "2.0.8",
"idiomorph": "0.7.4",
"jquery": "4.0.0",
"js-yaml": "4.1.1",
"katex": "0.16.43",
"mermaid": "11.13.0",
"mini-css-extract-plugin": "2.10.2",
"monaco-editor": "0.55.1",
"monaco-editor-webpack-plugin": "7.1.1",
"katex": "0.16.44",
"mermaid": "11.14.0",
"online-3d-viewer": "0.18.0",
"pdfobject": "2.3.1",
"perfect-debounce": "2.1.0",
"postcss": "8.5.8",
"postcss-loader": "8.2.1",
"rollup-plugin-license": "3.7.0",
"sortablejs": "1.15.7",
"swagger-ui-dist": "5.32.1",
"tailwindcss": "3.4.19",
@@ -57,20 +69,19 @@
"tributejs": "5.1.3",
"uint8-to-base64": "0.2.1",
"vanilla-colorful": "0.7.2",
"vite": "8.0.3",
"vite-string-plugin": "2.0.2",
"vue": "3.5.31",
"vue-bar-graph": "2.2.0",
"vue-chartjs": "5.3.3",
"vue-loader": "17.4.2",
"webpack": "5.105.4",
"webpack-cli": "7.0.2",
"wrap-ansi": "10.0.0"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "4.7.1",
"@eslint/json": "1.2.0",
"@playwright/test": "1.58.2",
"@playwright/test": "1.59.0",
"@stylistic/eslint-plugin": "5.10.0",
"@stylistic/stylelint-plugin": "5.0.1",
"@stylistic/stylelint-plugin": "5.1.0",
"@types/codemirror": "5.60.17",
"@types/dropzone": "5.7.9",
"@types/jquery": "4.0.0",
@@ -82,27 +93,27 @@
"@types/swagger-ui-dist": "3.30.6",
"@types/throttle-debounce": "5.0.2",
"@types/toastify-js": "1.12.4",
"@typescript-eslint/parser": "8.57.2",
"@typescript-eslint/parser": "8.58.0",
"@vitejs/plugin-vue": "6.0.5",
"@vitest/eslint-plugin": "1.6.13",
"@vitest/eslint-plugin": "1.6.14",
"eslint": "10.1.0",
"eslint-import-resolver-typescript": "4.4.4",
"eslint-plugin-array-func": "5.1.1",
"eslint-plugin-github": "6.0.0",
"eslint-plugin-de-morgan": "2.1.1",
"eslint-plugin-github": "6.0.0",
"eslint-plugin-import-x": "4.16.2",
"eslint-plugin-playwright": "2.10.1",
"eslint-plugin-regexp": "3.1.0",
"eslint-plugin-sonarjs": "4.0.2",
"eslint-plugin-unicorn": "63.0.0",
"eslint-plugin-unicorn": "64.0.0",
"eslint-plugin-vue": "10.8.0",
"eslint-plugin-vue-scoped-css": "3.0.0",
"eslint-plugin-wc": "3.1.0",
"globals": "17.4.0",
"happy-dom": "20.8.8",
"happy-dom": "20.8.9",
"jiti": "2.6.1",
"markdownlint-cli": "0.48.0",
"material-icon-theme": "5.32.0",
"material-icon-theme": "5.33.1",
"nolyfill": "1.0.44",
"postcss-html": "1.8.1",
"spectral-cli-bundle": "1.0.7",
@@ -112,10 +123,9 @@
"stylelint-declaration-strict-value": "1.11.1",
"stylelint-value-no-unknown-custom-properties": "6.1.1",
"svgo": "4.0.1",
"typescript": "5.9.3",
"typescript-eslint": "8.57.2",
"updates": "17.12.0",
"vite-string-plugin": "2.0.2",
"typescript": "6.0.2",
"typescript-eslint": "8.58.0",
"updates": "17.13.1",
"vitest": "4.1.2",
"vue-tsc": "3.2.6"
},
+8 -5
View File
@@ -1,21 +1,24 @@
import {env} from 'node:process';
import {defineConfig, devices} from '@playwright/test';
const timeoutFactor = Number(env.GITEA_TEST_E2E_TIMEOUT_FACTOR) || 1;
const timeout = 5000 * timeoutFactor;
export default defineConfig({
testDir: './tests/e2e/',
outputDir: './tests/e2e-output/',
testMatch: /.*\.test\.ts/,
forbidOnly: Boolean(env.CI),
reporter: 'list',
timeout: env.CI ? 12000 : 6000,
timeout: 2 * timeout,
expect: {
timeout: env.CI ? 6000 : 3000,
timeout,
},
use: {
baseURL: env.GITEA_TEST_E2E_URL?.replace?.(/\/$/g, ''),
baseURL: env.GITEA_TEST_E2E_URL?.replace?.(/\/$/, ''),
locale: 'en-US',
actionTimeout: env.CI ? 6000 : 3000,
navigationTimeout: env.CI ? 12000 : 6000,
actionTimeout: timeout,
navigationTimeout: 2 * timeout,
},
projects: [
{
+1361 -1276
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -169,7 +169,7 @@ func UploadPackage(ctx *context.Context) {
canWrite := repo.OwnerID == ctx.Doer.ID
if !canWrite {
perms, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
perms, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
+3 -3
View File
@@ -40,7 +40,7 @@ func ListRunners(ctx *context.APIContext) {
// required: false
// responses:
// "200":
// "$ref": "#/definitions/ActionRunnersResponse"
// "$ref": "#/responses/RunnerList"
// "400":
// "$ref": "#/responses/error"
// "404":
@@ -63,7 +63,7 @@ func GetRunner(ctx *context.APIContext) {
// required: true
// responses:
// "200":
// "$ref": "#/definitions/ActionRunner"
// "$ref": "#/responses/Runner"
// "400":
// "$ref": "#/responses/error"
// "404":
@@ -115,7 +115,7 @@ func UpdateRunner(ctx *context.APIContext) {
// "$ref": "#/definitions/EditActionRunnerOption"
// responses:
// "200":
// "$ref": "#/definitions/ActionRunner"
// "$ref": "#/responses/Runner"
// "400":
// "$ref": "#/responses/error"
// "404":
+1 -3
View File
@@ -11,11 +11,9 @@
//
// Consumes:
// - application/json
// - text/plain
//
// Produces:
// - application/json
// - text/html
//
// Security:
// - BasicAuth :
@@ -202,7 +200,7 @@ func repoAssignment() func(ctx *context.APIContext) {
if needTwoFactor {
ctx.Repo.Permission = access_model.PermissionNoAccess()
} else {
ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
ctx.Repo.Permission, err = access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
+3 -3
View File
@@ -492,7 +492,7 @@ func (Action) ListRunners(ctx *context.APIContext) {
// required: false
// responses:
// "200":
// "$ref": "#/definitions/ActionRunnersResponse"
// "$ref": "#/responses/RunnerList"
// "400":
// "$ref": "#/responses/error"
// "404":
@@ -520,7 +520,7 @@ func (Action) GetRunner(ctx *context.APIContext) {
// required: true
// responses:
// "200":
// "$ref": "#/definitions/ActionRunner"
// "$ref": "#/responses/Runner"
// "400":
// "$ref": "#/responses/error"
// "404":
@@ -582,7 +582,7 @@ func (Action) UpdateRunner(ctx *context.APIContext) {
// "$ref": "#/definitions/EditActionRunnerOption"
// responses:
// "200":
// "$ref": "#/definitions/ActionRunner"
// "$ref": "#/responses/Runner"
// "400":
// "$ref": "#/responses/error"
// "404":
+2 -2
View File
@@ -576,7 +576,7 @@ func GetTeamRepos(ctx *context.APIContext) {
}
repos := make([]*api.Repository, len(teamRepos))
for i, repo := range teamRepos {
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -628,7 +628,7 @@ func GetTeamRepo(ctx *context.APIContext) {
return
}
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
+4 -4
View File
@@ -561,7 +561,7 @@ func (Action) ListRunners(ctx *context.APIContext) {
// required: false
// responses:
// "200":
// "$ref": "#/definitions/ActionRunnersResponse"
// "$ref": "#/responses/RunnerList"
// "400":
// "$ref": "#/responses/error"
// "404":
@@ -594,7 +594,7 @@ func (Action) GetRunner(ctx *context.APIContext) {
// required: true
// responses:
// "200":
// "$ref": "#/definitions/ActionRunner"
// "$ref": "#/responses/Runner"
// "400":
// "$ref": "#/responses/error"
// "404":
@@ -666,7 +666,7 @@ func (Action) UpdateRunner(ctx *context.APIContext) {
// "$ref": "#/definitions/EditActionRunnerOption"
// responses:
// "200":
// "$ref": "#/definitions/ActionRunner"
// "$ref": "#/responses/Runner"
// "400":
// "$ref": "#/responses/error"
// "404":
@@ -1192,7 +1192,7 @@ func GetWorkflowRun(ctx *context.APIContext) {
// - name: run
// in: path
// description: id of the run
// type: string
// type: integer
// required: true
// responses:
// "200":
+1 -1
View File
@@ -291,7 +291,7 @@ func GetRepoPermissions(ctx *context.APIContext) {
return
}
permission, err := access_model.GetUserRepoPermission(ctx, ctx.Repo.Repository, collaborator)
permission, err := access_model.GetIndividualUserRepoPermission(ctx, ctx.Repo.Repository, collaborator)
if err != nil {
ctx.APIErrorInternal(err)
return
+1 -1
View File
@@ -71,7 +71,7 @@ func ListForks(ctx *context.APIContext) {
apiForks := make([]*api.Repository, len(forks))
for i, fork := range forks {
permission, err := access_model.GetUserRepoPermission(ctx, fork, ctx.Doer)
permission, err := access_model.GetDoerRepoPermission(ctx, fork, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
+18 -2
View File
@@ -726,6 +726,9 @@ func EditIssue(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/issues/{index} issue issueEditIssue
// ---
// summary: Edit an issue. If using deadline only the date will be taken into account, and time of day ignored.
// description: |
// Pass `content_version` to enable optimistic locking on body edits.
// If the version doesn't match the current value, the request fails with 409 Conflict.
// consumes:
// - application/json
// produces:
@@ -785,6 +788,15 @@ func EditIssue(ctx *context.APIContext) {
return
}
// Fail fast: if content_version is provided and already stale, reject
// before any mutations. The DB-level check in ChangeContent still
// handles concurrent requests.
// TODO: wrap all mutations in a transaction to fully prevent partial writes.
if form.ContentVersion != nil && *form.ContentVersion != issue.ContentVersion {
ctx.APIError(http.StatusConflict, issues_model.ErrIssueAlreadyChanged)
return
}
if len(form.Title) > 0 {
err = issue_service.ChangeTitle(ctx, issue, ctx.Doer, form.Title)
if err != nil {
@@ -793,10 +805,14 @@ func EditIssue(ctx *context.APIContext) {
}
}
if form.Body != nil {
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, issue.ContentVersion)
contentVersion := issue.ContentVersion
if form.ContentVersion != nil {
contentVersion = *form.ContentVersion
}
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, contentVersion)
if err != nil {
if errors.Is(err, issues_model.ErrIssueAlreadyChanged) {
ctx.APIError(http.StatusBadRequest, err)
ctx.APIError(http.StatusConflict, err)
return
}
+1 -1
View File
@@ -219,7 +219,7 @@ func isXRefCommentAccessible(ctx stdCtx.Context, user *user_model.User, c *issue
if err != nil {
return false
}
perm, err := access_model.GetUserRepoPermission(ctx, c.RefRepo, user)
perm, err := access_model.GetDoerRepoPermission(ctx, c.RefRepo, user)
if err != nil {
return false
}
+3 -3
View File
@@ -102,7 +102,7 @@ func GetIssueDependencies(ctx *context.APIContext) {
perm = existPerm
} else {
var err error
perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
perm, err = access_model.GetDoerRepoPermission(ctx, &blocker.Repository, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -351,7 +351,7 @@ func GetIssueBlocks(ctx *context.APIContext) {
perm = existPerm
} else {
var err error
perm, err = access_model.GetUserRepoPermission(ctx, &depMeta.Repository, ctx.Doer)
perm, err = access_model.GetDoerRepoPermission(ctx, &depMeta.Repository, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -537,7 +537,7 @@ func getPermissionForRepo(ctx *context.APIContext, repo *repo_model.Repository)
return &ctx.Repo.Permission
}
perm, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
perm, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return nil
+4 -6
View File
@@ -175,7 +175,7 @@ func DeleteIssueCommentReaction(ctx *context.APIContext) {
// schema:
// "$ref": "#/definitions/EditReactionOption"
// responses:
// "200":
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
@@ -248,8 +248,7 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp
ctx.APIErrorInternal(err)
return
}
// ToDo respond 204
ctx.Status(http.StatusOK)
ctx.Status(http.StatusNoContent)
}
}
@@ -408,7 +407,7 @@ func DeleteIssueReaction(ctx *context.APIContext) {
// schema:
// "$ref": "#/definitions/EditReactionOption"
// responses:
// "200":
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
@@ -464,7 +463,6 @@ func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, i
ctx.APIErrorInternal(err)
return
}
// ToDo respond 204
ctx.Status(http.StatusOK)
ctx.Status(http.StatusNoContent)
}
}
+19 -4
View File
@@ -657,6 +657,15 @@ func EditPullRequest(ctx *context.APIContext) {
return
}
// Fail fast: if content_version is provided and already stale, reject
// before any mutations. The DB-level check in ChangeContent still
// handles concurrent requests.
// TODO: wrap all mutations in a transaction to fully prevent partial writes.
if form.ContentVersion != nil && *form.ContentVersion != issue.ContentVersion {
ctx.APIError(http.StatusConflict, issues_model.ErrIssueAlreadyChanged)
return
}
if len(form.Title) > 0 {
err = issue_service.ChangeTitle(ctx, issue, ctx.Doer, form.Title)
if err != nil {
@@ -665,10 +674,14 @@ func EditPullRequest(ctx *context.APIContext) {
}
}
if form.Body != nil {
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, issue.ContentVersion)
contentVersion := issue.ContentVersion
if form.ContentVersion != nil {
contentVersion = *form.ContentVersion
}
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, contentVersion)
if err != nil {
if errors.Is(err, issues_model.ErrIssueAlreadyChanged) {
ctx.APIError(http.StatusBadRequest, err)
ctx.APIError(http.StatusConflict, err)
return
}
@@ -898,6 +911,8 @@ func MergePullRequest(ctx *context.APIContext) {
// responses:
// "200":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "405":
@@ -1113,7 +1128,7 @@ func parseCompareInfo(ctx *context.APIContext, compareParam string) (result *git
}()
// user should have permission to read baseRepo's codes and pulls, NOT headRepo's
permBase, err := access_model.GetUserRepoPermission(ctx, baseRepo, ctx.Doer)
permBase, err := access_model.GetDoerRepoPermission(ctx, baseRepo, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return nil, nil
@@ -1127,7 +1142,7 @@ func parseCompareInfo(ctx *context.APIContext, compareParam string) (result *git
// user should have permission to read headRepo's codes
// TODO: could the logic be simplified if the headRepo is the same as the baseRepo? Need to think more about it.
permHead, err := access_model.GetUserRepoPermission(ctx, headRepo, ctx.Doer)
permHead, err := access_model.GetDoerRepoPermission(ctx, headRepo, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return nil, nil
+1 -1
View File
@@ -833,7 +833,7 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions
return
}
permDoer, err := access_model.GetUserRepoPermission(ctx, pr.Issue.Repo, ctx.Doer)
permDoer, err := access_model.GetDoerRepoPermission(ctx, pr.Issue.Repo, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
+2 -2
View File
@@ -221,7 +221,7 @@ func Search(ctx *context.APIContext) {
})
return
}
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.JSON(http.StatusInternalServerError, api.SearchError{
OK: false,
@@ -588,7 +588,7 @@ func GetByID(ctx *context.APIContext) {
return
}
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
+2 -2
View File
@@ -37,7 +37,7 @@ func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) {
apiRepos := make([]*api.Repository, 0, len(repos))
for i := range repos {
permission, err := access_model.GetUserRepoPermission(ctx, repos[i], ctx.Doer)
permission, err := access_model.GetDoerRepoPermission(ctx, repos[i], ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -123,7 +123,7 @@ func ListMyRepos(ctx *context.APIContext) {
ctx.APIErrorInternal(err)
return
}
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
}
+3 -3
View File
@@ -40,7 +40,7 @@ func ListRunners(ctx *context.APIContext) {
// required: false
// responses:
// "200":
// "$ref": "#/definitions/ActionRunnersResponse"
// "$ref": "#/responses/RunnerList"
// "400":
// "$ref": "#/responses/error"
// "404":
@@ -63,7 +63,7 @@ func GetRunner(ctx *context.APIContext) {
// required: true
// responses:
// "200":
// "$ref": "#/definitions/ActionRunner"
// "$ref": "#/responses/Runner"
// "400":
// "$ref": "#/responses/error"
// "404":
@@ -115,7 +115,7 @@ func UpdateRunner(ctx *context.APIContext) {
// "$ref": "#/definitions/EditActionRunnerOption"
// responses:
// "200":
// "$ref": "#/definitions/ActionRunner"
// "$ref": "#/responses/Runner"
// "400":
// "$ref": "#/responses/error"
// "404":
+1 -1
View File
@@ -31,7 +31,7 @@ func getStarredRepos(ctx *context.APIContext, user *user_model.User, private boo
repos := make([]*api.Repository, len(starredRepos))
for i, starred := range starredRepos {
permission, err := access_model.GetUserRepoPermission(ctx, starred, user)
permission, err := access_model.GetIndividualUserRepoPermission(ctx, starred, user)
if err != nil {
return nil, err
}

Some files were not shown because too many files have changed in this diff Show More